Add a new kernel-version independent broadcom binary driver for brcm47xx (contributed...
authorFelix Fietkau <nbd@openwrt.org>
Tue, 15 Jun 2010 11:40:05 +0000 (11:40 +0000)
committerFelix Fietkau <nbd@openwrt.org>
Tue, 15 Jun 2010 11:40:05 +0000 (11:40 +0000)
SVN-Revision: 21809

package/broadcom-wl/Makefile [new file with mode: 0644]
package/broadcom-wl/files/etc/hotplug.d/net/20-broadcom_wds [new file with mode: 0644]
package/broadcom-wl/files/lib/wifi/broadcom.sh [new file with mode: 0644]
package/broadcom-wl/src/wlc.c [new file with mode: 0644]

diff --git a/package/broadcom-wl/Makefile b/package/broadcom-wl/Makefile
new file mode 100644 (file)
index 0000000..b9104bd
--- /dev/null
@@ -0,0 +1,159 @@
+#
+# Copyright (C) 2006-2010 OpenWrt.org
+#
+# This is free software, licensed under the GNU General Public License v2.
+# See /LICENSE for more information.
+#
+
+include $(TOPDIR)/rules.mk
+include $(INCLUDE_DIR)/kernel.mk
+
+PKG_NAME:=broadcom-wl
+PKG_VERSION:=5.10.56.27.0
+PKG_RELEASE:=1
+
+PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.bz2
+PKG_SOURCE_URL:=http://downloads.openwrt.org/sources
+PKG_MD5SUM:=dfaee8bdafaa4bb5ccbcdfe541c29e7d
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/broadcom-wl/Default
+  SECTION:=kernel
+  CATEGORY:=Kernel modules
+  DEPENDS:=@PACKAGE_kmod-brcm-wl||PACKAGE_kmod-brcm-wl-mimo
+  SUBMENU:=Proprietary BCM43xx WiFi driver
+  SUBMENUDEP:=@TARGET_brcm47xx
+endef
+
+define KernelPackage/brcm-wl/Default
+  $(call Package/broadcom-wl/Default)
+  SECTION:=kernel
+  DEPENDS:=@TARGET_brcm47xx +wireless-tools
+  TITLE:=Kernel driver for BCM43xx chipsets
+  FILES:=$(PKG_BUILD_DIR)/driver$(1)/wl.$(LINUX_KMOD_SUFFIX)
+  AUTOLOAD:=$(call AutoLoad,30,wl)
+endef
+
+define KernelPackage/brcm-wl/Default/description
+ This package contains the proprietary wireless driver for the Broadcom 
+ BCM43xx chipset.
+endef
+
+define KernelPackage/brcm-wl
+$(call KernelPackage/brcm-wl/Default,)
+  TITLE+= (normal version)
+endef
+
+define KernelPackage/brcm-wl/description
+$(call KernelPackage/brcm-wl/Default/description)
+endef
+
+define KernelPackage/brcm-wl-mini
+$(call KernelPackage/brcm-wl/Default,-mini)
+  TITLE+= (Legacy version)
+endef
+
+define KernelPackage/brcm-wl-mini/description
+$(call KernelPackage/brcm-wl/Default/description)
+endef
+
+define Package/wlc
+$(call Package/broadcom-wl/Default)
+  TITLE:=wl driver setup utility
+endef
+
+define Package/wlc/description
+ This package contains an utility for initializing the proprietary Broadcom 
+ wl driver.
+endef
+
+define Package/wl
+$(call Package/broadcom-wl/Default)
+  TITLE:=Proprietary Broadcom wl driver config utility
+endef
+
+define Package/wl/description
+ This package contains the proprietary utility (wl) for configuring the 
+ proprietary Broadcom wl driver.
+endef
+
+define Package/nas
+$(call Package/broadcom-wl/Default)
+  TITLE:=Proprietary Broadcom WPA/WPA2 authenticator
+endef
+
+define Package/nas/description
+ This package contains the proprietary WPA/WPA2 authenticator (nas) for the 
+ proprietary Broadcom wl driver.
+endef
+
+MAKE_KMOD := $(MAKE) -C "$(LINUX_DIR)" \
+               CROSS_COMPILE="$(TARGET_CROSS)" \
+               ARCH="$(LINUX_KARCH)" \
+               PATH="$(TARGET_PATH)" \
+               SUBDIRS="$(PKG_BUILD_DIR)/kmod" \
+
+define Build/Prepare
+       $(call Build/Prepare/Default)
+       $(CP) $(PKG_BUILD_DIR)/driver $(PKG_BUILD_DIR)/driver-mini
+endef
+
+define Build/Compile
+       # Compile the kernel part
+       $(MAKE_KMOD) \
+               SUBDIRS="$(PKG_BUILD_DIR)/driver" \
+               MODFLAGS="-DMODULE -mlong-calls" \
+               modules
+
+       $(MAKE_KMOD) \
+               SUBDIRS="$(PKG_BUILD_DIR)/driver-mini" \
+               MODFLAGS="-DMODULE -mlong-calls" \
+               BUILD_TYPE="wl_apsta_mini" \
+               modules
+
+       # Compile libshared
+       $(MAKE) -C $(PKG_BUILD_DIR)/shared \
+               $(TARGET_CONFIGURE_OPTS) \
+               CFLAGS="$(TARGET_CFLAGS) -I. -I$(PKG_BUILD_DIR)/driver/include" \
+               all
+
+       $(TARGET_CC) -o $(PKG_BUILD_DIR)/wlc \
+               -I$(PKG_BUILD_DIR)/shared -I$(PKG_BUILD_DIR)/driver/include \
+               ./src/wlc.c $(PKG_BUILD_DIR)/shared/libshared.a
+
+       $(TARGET_CC) -o $(PKG_BUILD_DIR)/nas \
+               $(PKG_BUILD_DIR)/nas_exe.o \
+               $(PKG_BUILD_DIR)/shared/libshared.a
+
+       $(TARGET_CC) -o $(PKG_BUILD_DIR)/wl $(PKG_BUILD_DIR)/wl_exe.o
+endef
+
+define Build/InstallDev
+       $(INSTALL_DIR) $(1)/usr/lib
+       $(CP) $(PKG_BUILD_DIR)/shared/libshared.a $(1)/usr/lib/
+endef
+
+define Package/wlc/install
+       $(CP) ./files/* $(1)/
+       $(INSTALL_DIR) $(1)/sbin
+       $(INSTALL_BIN) $(PKG_BUILD_DIR)/wlc $(1)/sbin/
+endef
+
+define Package/wl/install
+       $(INSTALL_DIR) $(1)/usr/sbin
+       $(INSTALL_BIN) $(PKG_BUILD_DIR)/wl $(1)/usr/sbin/
+endef
+
+define Package/nas/install
+       $(INSTALL_DIR) $(1)/usr/sbin
+       $(INSTALL_BIN) $(PKG_BUILD_DIR)/nas $(1)/usr/sbin/
+       ln -sf nas $(1)/usr/sbin/nas4not
+       ln -sf nas $(1)/usr/sbin/nas4wds
+endef
+
+$(eval $(call KernelPackage,brcm-wl))
+$(eval $(call KernelPackage,brcm-wl-mini))
+$(eval $(call BuildPackage,wlc))
+$(eval $(call BuildPackage,wl))
+$(eval $(call BuildPackage,nas))
diff --git a/package/broadcom-wl/files/etc/hotplug.d/net/20-broadcom_wds b/package/broadcom-wl/files/etc/hotplug.d/net/20-broadcom_wds
new file mode 100644 (file)
index 0000000..f314a25
--- /dev/null
@@ -0,0 +1,59 @@
+include /lib/wifi
+
+setup_broadcom_wds() {
+       local iface="$1"
+       local remote="$(wlc ifname "$iface" wdsmac)"
+
+       [ -z "$remote" ] && return
+       
+       config_cb() {
+               [ -z "$CONFIG_SECTION" ] && return
+       
+               config_get type "$CONFIG_SECTION" TYPE
+               [ "$type" = "wifi-iface" ] || return
+               
+               config_get network "$CONFIG_SECTION" network
+               [ -z "$network" ] && return
+               
+               config_get addr "$CONFIG_SECTION" bssid
+               addr=$(echo "$addr" | tr 'A-F' 'a-f')
+               [ "$addr" = "$remote" ] && {
+                       local cfg="$CONFIG_SECTION"
+                       
+                       include /lib/network
+                       scan_interfaces
+
+                       setup_interface "$iface" "$network"
+                       
+                       config_get encryption "$cfg" encryption
+                       config_get key "$cfg" key
+                       config_get ssid "$cfg" ssid
+               
+                       [ "$encryption" != "none" ] && {
+                               sleep 5
+                               case "$encryption" in
+                                       psk|PSK)
+                                               nas4not "$network" "$iface" up auto tkip psk "$key" "$ssid"
+                                               ;;
+                                       psk2|PSK2)
+                                               nas4not "$network" "$iface" up auto aes psk "$key" "$ssid"
+                                               ;;
+                                       psk+psk2|psk2+psk|PSK+PSK2|PSK2+PSK)
+                                               nas4not "$network" "$iface" up auto aes+tkip psk "$key" "$ssid"
+                                               ;;
+                                       *)
+                                               nas4not lan "$iface" up auto aes "$encryption" "$key" "$ssid"
+                                               ;;
+                                       esac
+                       }
+               }
+       }
+
+       config_load wireless
+}
+
+case "$ACTION" in
+       add|register)
+               [ "${INTERFACE%%0.*}" = wds ] && setup_broadcom_wds "$INTERFACE"
+       ;;
+esac
diff --git a/package/broadcom-wl/files/lib/wifi/broadcom.sh b/package/broadcom-wl/files/lib/wifi/broadcom.sh
new file mode 100644 (file)
index 0000000..ffe8d1d
--- /dev/null
@@ -0,0 +1,361 @@
+append DRIVERS "broadcom"
+
+scan_broadcom() {
+       local device="$1"
+       local wds
+       local adhoc sta apmode mon
+       local adhoc_if sta_if ap_if mon_if
+       local _c=0
+
+       config_get vifs "$device" vifs
+       for vif in $vifs; do
+               config_get mode "$vif" mode
+               _c=$(($_c + 1))
+               case "$mode" in
+                       adhoc)
+                               adhoc=1
+                               adhoc_if="$vif"
+                       ;;
+                       sta)
+                               sta=1
+                               sta_if="$vif"
+                       ;;
+                       ap)
+                               apmode=1
+                               ap_if="${ap_if:+$ap_if }$vif"
+                       ;;
+                       wds)
+                               config_get addr "$vif" bssid
+                               [ -z "$addr" ] || {
+                                       addr=$(echo "$addr" | tr 'A-F' 'a-f')
+                                       append wds "$addr"
+                               }
+                       ;;
+                       monitor)
+                               mon=1
+                               mon_if="$vif"
+                       ;;
+                       *) echo "$device($vif): Invalid mode";;
+               esac
+       done
+       config_set "$device" wds "$wds"
+
+       local _c=
+       for vif in ${adhoc_if:-$sta_if $ap_if $mon_if}; do
+               config_set "$vif" ifname "${device}${_c:+.$_c}"
+               _c=$((${_c:-0} + 1))
+       done
+       config_set "$device" vifs "${adhoc_if:-$sta_if $ap_if $mon_if}"
+
+       ifdown="down"
+       for vif in 0 1 2 3; do
+               append ifdown "vif $vif" "$N"
+               append ifdown "enabled 0" "$N"
+       done
+
+       ap=1
+       infra=1
+       if [ "$_c" -gt 1 ]; then
+               mssid=1
+       else
+               mssid=
+       fi
+       apsta=0
+       radio=1
+       monitor=0
+       case "$adhoc:$sta:$apmode:$mon" in
+               1*)
+                       ap=0
+                       mssid=
+                       infra=0
+               ;;
+               :1:1:)
+                       apsta=1
+                       wet=1
+               ;;
+               :1::)
+                       wet=1
+                       ap=0
+                       mssid=
+               ;;
+               :::1)
+                       wet=1
+                       ap=0
+                       mssid=
+                       monitor=1
+               ;;
+               ::)
+                       radio=0
+               ;;
+       esac
+}
+
+disable_broadcom() {
+       local device="$1"
+       set_wifi_down "$device"
+       wlc ifname "$device" down
+       wlc ifname "$device" bssid `wlc ifname "$device" default_bssid`
+       (
+               include /lib/network
+
+               # make sure the interfaces are down and removed from all bridges
+               for dev in $device ${device}.1 ${device}.2 ${device}.3; do
+                       ifconfig "$dev" down 2>/dev/null >/dev/null && {
+                               unbridge "$dev"
+                       }
+               done
+       )
+       true
+}
+
+enable_broadcom() {
+       local device="$1"
+       local _c
+       config_get channel "$device" channel
+       config_get country "$device" country
+       config_get maxassoc "$device" maxassoc
+       config_get wds "$device" wds
+       config_get vifs "$device" vifs
+       config_get distance "$device" distance
+       config_get slottime "$device" slottime
+       config_get rxantenna "$device" rxantenna
+       config_get txantenna "$device" txantenna
+       config_get_bool frameburst "$device" frameburst
+       config_get macfilter "$device" macfilter
+       config_get maclist "$device" maclist
+       config_get macaddr "$device" macaddr
+       config_get txpower "$device" txpower
+       config_get frag "$device" frag
+       config_get rts "$device" rts
+       config_get hwmode "$device" hwmode
+       local vif_pre_up vif_post_up vif_do_up vif_txpower
+       local doth=0
+       local wmm=0
+
+       _c=0
+       nas="$(which nas)"
+       nas_cmd=
+       if_up=
+
+       [ -z "$slottime" ] && {
+               [ -n "$distance" ] && {
+                       # slottime = 9 + (distance / 150) + (distance % 150 ? 1 : 0)
+                       slottime="$((9 + ($distance / 150) + 1 - (150 - ($distance % 150)) / 150 ))"
+               }
+       } || {
+               slottime="${slottime:--1}"
+       }
+
+       case "$macfilter" in
+               allow|2)
+                       macfilter=2;
+               ;;
+               deny|1)
+                       macfilter=1;
+               ;;
+               disable|none|0)
+                       macfilter=0;
+               ;;
+       esac
+
+       case "$hwmode" in
+               *b)   hwmode=0;;
+               *bg)  hwmode=1;;
+               *g)   hwmode=2;;
+               *gst) hwmode=4;;
+               *lrs) hwmode=5;;
+               *)    hwmode=1;;
+       esac
+
+       for vif in $vifs; do
+               config_get vif_txpower "$vif" txpower
+
+               config_get mode "$vif" mode
+               append vif_pre_up "vif $_c" "$N"
+               append vif_post_up "vif $_c" "$N"
+               append vif_do_up "vif $_c" "$N"
+
+               config_get_bool wmm "$vif" wmm "$wmm"
+               config_get_bool doth "$vif" doth "$doth"
+
+               [ "$mode" = "sta" ] || {
+                       config_get_bool hidden "$vif" hidden 0
+                       append vif_pre_up "closed $hidden" "$N"
+                       config_get_bool isolate "$vif" isolate 0
+                       append vif_pre_up "ap_isolate $isolate" "$N"
+               }
+
+               wsec_r=0
+               eap_r=0
+               wsec=0
+               auth=0
+               nasopts=
+               config_get enc "$vif" encryption
+               case "$enc" in
+                       *WEP*|*wep*)
+                               wsec_r=1
+                               wsec=1
+                               defkey=1
+                               config_get key "$vif" key
+                               case "$enc" in
+                                       *shared*) append vif_do_up "wepauth 1" "$N";;
+                                       *) append vif_do_up "wepauth 0" "$N";;
+                               esac
+                               case "$key" in
+                                       [1234])
+                                               defkey="$key"
+                                               for knr in 1 2 3 4; do
+                                                       config_get k "$vif" key$knr
+                                                       [ -n "$k" ] || continue
+                                                       [ "$defkey" = "$knr" ] && def="=" || def=""
+                                                       append vif_do_up "wepkey $def$knr,$k" "$N"
+                                               done
+                                       ;;
+                                       "");;
+                                       *) append vif_do_up "wepkey =1,$key" "$N";;
+                               esac
+                       ;;
+                       *psk*|*PSK*)
+                               wsec_r=1
+                               config_get key "$vif" key
+                               case "$enc" in
+                                       wpa*+wpa2*|WPA*+WPA2*|*psk+*psk2|*PSK+*PSK2) auth=132; wsec=6;;
+                                       wpa2*|WPA2*|*PSK2|*psk2) auth=128; wsec=4;;
+                                       *aes|*AES) auth=4; wsec=4;;
+                                       *) auth=4; wsec=2;;
+                               esac
+                               eval "${vif}_key=\"\$key\""
+                               nasopts="-k \"\$${vif}_key\""
+                       ;;
+                       *wpa*|*WPA*)
+                               wsec_r=1
+                               eap_r=1
+                               config_get key "$vif" key
+                               config_get server "$vif" server
+                               config_get port "$vif" port
+                               case "$enc" in
+                                       wpa*+wpa2*|WPA*+WPA2*) auth=66; wsec=6;;
+                                       wpa2*|WPA2*) auth=64; wsec=4;;
+                                       *) auth=2; wsec=2;;
+                               esac
+                               eval "${vif}_key=\"\$key\""
+                               nasopts="-r \"\$${vif}_key\" -h $server -p ${port:-1812}"
+                       ;;
+               esac
+               append vif_do_up "wsec $wsec" "$N"
+               append vif_do_up "wpa_auth $auth" "$N"
+               append vif_do_up "wsec_restrict $wsec_r" "$N"
+               append vif_do_up "eap_restrict $eap_r" "$N"
+
+               config_get ssid "$vif" ssid
+               append vif_post_up "vlan_mode 0" "$N"
+               append vif_post_up "ssid $ssid" "$N"
+               append vif_do_up "ssid $ssid" "$N"
+
+               [ "$mode" = "monitor" ] && {
+                       append vif_post_up "monitor $monitor" "$N"
+               }
+
+               [ "$mode" = "adhoc" ] && {
+                       config_get bssid "$vif" bssid
+                       [ -n "$bssid" ] && {
+                               append vif_pre_up "bssid $bssid" "$N"
+                               append vif_pre_up "ibss_merge 0" "$N"
+                       } || {
+                               append vif_pre_up "ibss_merge 1" "$N"
+                       }
+               }
+
+               append vif_post_up "enabled 1" "$N"
+
+               config_get ifname "$vif" ifname
+               #append if_up "ifconfig $ifname up" ";$N"
+
+               local net_cfg
+               net_cfg="$(find_net_config "$vif")"
+               [ -z "$net_cfg" ] || {
+                       append if_up "set_wifi_up '$vif' '$ifname'" ";$N"
+                       append if_up "start_net '$ifname' '$net_cfg'" ";$N"
+               }
+               [ -z "$nasopts" ] || {
+                       eval "${vif}_ssid=\"\$ssid\""
+                       nas_mode="-A"
+                       [ "$mode" = "sta" ] && nas_mode="-S"
+                       [ -z "$nas" ] || {
+                               nas_cmd="${nas_cmd:+$nas_cmd$N}start-stop-daemon -S -b -p /var/run/nas.$ifname.pid -x $nas -- -P /var/run/nas.$ifname.pid -H 34954 -i $ifname $nas_mode -m $auth -w $wsec -s \"\$${vif}_ssid\" -g 3600 -F $nasopts"
+                       }
+               }
+               _c=$(($_c + 1))
+       done
+       killall -KILL nas >&- 2>&-
+       wlc ifname "$device" stdin <<EOF
+$ifdown
+
+gmode ${hwmode:-1}
+apsta $apsta
+ap $ap
+${mssid:+mssid $mssid}
+infra $infra
+${wet:+wet 1}
+802.11d 0
+802.11h ${doth:-0}
+wme ${wmm:-0}
+rxant ${rxantenna:-3}
+txant ${txantenna:-3}
+fragthresh ${frag:-2346}
+rtsthresh ${rts:-2347}
+monitor ${monitor:-0}
+
+radio ${radio:-1}
+macfilter ${macfilter:-0}
+maclist ${maclist:-none}
+wds none
+${wds:+wds $wds}
+country ${country:-US}
+${channel:+channel $channel}
+maxassoc ${maxassoc:-128}
+slottime ${slottime:--1}
+${frameburst:+frameburst $frameburst}
+
+$vif_pre_up
+up
+$vif_post_up
+EOF
+       eval "$if_up"
+       wlc ifname "$device" stdin <<EOF
+$vif_do_up
+EOF
+
+       # use vif_txpower (from last wifi-iface) instead of txpower (from
+       # wifi-device) if the latter does not exist
+       txpower=${txpower:-$vif_txpower}
+       [ -z "$txpower" ] || iwconfig $device txpower ${txpower}dBm
+
+       eval "$nas_cmd"
+}
+
+
+detect_broadcom() {
+       local i=-1
+
+       while grep -qs "^ *wl$((++i)):" /proc/net/dev; do
+               config_get type wl${i} type
+               [ "$type" = broadcom ] && continue
+               cat <<EOF
+config wifi-device  wl${i}
+       option type     broadcom
+       option channel  5
+
+       # REMOVE THIS LINE TO ENABLE WIFI:
+       option disabled 1
+
+config wifi-iface
+       option device   wl${i}
+       option network  lan
+       option mode     ap
+       option ssid     OpenWrt${i#0}
+       option encryption none
+
+EOF
+       done
+}
diff --git a/package/broadcom-wl/src/wlc.c b/package/broadcom-wl/src/wlc.c
new file mode 100644 (file)
index 0000000..253809c
--- /dev/null
@@ -0,0 +1,1086 @@
+/*
+ * wlc - Broadcom Wireless Driver Control Utility
+ *
+ * Copyright (C) 2006 Felix Fietkau <nbd@openwrt.org>
+ *
+ * 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.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <glob.h>
+#include <ctype.h>
+
+#include <typedefs.h>
+#include <wlutils.h>
+#include <proto/802.11.h>
+
+#define VERSION "0.1"
+#define BUFSIZE 8192
+#define PTABLE_MAGIC 0xbadc0ded
+#define PTABLE_SLT1 1
+#define PTABLE_SLT2 2
+#define PTABLE_ACKW 3
+#define PTABLE_ADHM 4
+#define PTABLE_END 0xffffffff
+
+/* 
+ * Copy each token in wordlist delimited by space into word 
+ * Taken from Broadcom shutils.h
+ */
+#define foreach(word, wordlist, next) \
+       for (next = &wordlist[strspn(wordlist, " ")], \
+                strncpy(word, next, sizeof(word)), \
+                word[strcspn(word, " ")] = '\0', \
+                word[sizeof(word) - 1] = '\0', \
+                next = strchr(next, ' '); \
+                strlen(word); \
+                next = next ? &next[strspn(next, " ")] : "", \
+                strncpy(word, next, sizeof(word)), \
+                word[strcspn(word, " ")] = '\0', \
+                word[sizeof(word) - 1] = '\0', \
+                next = strchr(next, ' '))
+
+static char wlbuf[8192];
+static char interface[16] = "wl0";
+static unsigned long kmem_offset = 0;
+static int vif = 0, debug = 1, fromstdin = 0;
+
+typedef enum {
+       NONE =   0x00,
+
+       /* types */
+       PARAM_TYPE =    0x00f,
+       INT =    0x001,
+       STRING = 0x002,
+       MAC =    0x003,
+
+       /* options */
+       PARAM_OPTIONS = 0x0f0,
+       NOARG =  0x010,
+
+       /* modes */
+       PARAM_MODE =    0xf00,
+       GET =    0x100,
+       SET =    0x200,
+} wlc_param;
+
+struct wlc_call {
+       const char *name;
+       wlc_param param;
+       int (*handler)(wlc_param param, void *data, void *value);
+       union {
+               int num;
+               char *str;
+               void *ptr;
+       } data;
+       const char *desc;
+};
+
+/* can't use the system include because of the stupid broadcom header files */
+extern struct ether_addr *ether_aton(const char *asc);
+static inline int my_ether_ntoa(unsigned char *ea, char *buf)
+{
+       return sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x",
+               ea[0], ea[1], ea[2], ea[3], ea[4], ea[5]);
+}
+
+static int wlc_ioctl(wlc_param param, void *data, void *value)
+{
+       unsigned int *var = ((unsigned int *) data);
+       unsigned int ioc = *var;
+
+       if (param & NOARG) {
+               return wl_ioctl(interface, ioc, NULL, 0);
+       }
+       switch(param & PARAM_TYPE) {
+               case MAC:
+                       return wl_ioctl(interface, ((param & SET) ? (ioc) : (ioc >> 16)) & 0xffff, value, 6);
+               case INT:
+                       return wl_ioctl(interface, ((param & SET) ? (ioc) : (ioc >> 16)) & 0xffff, value, sizeof(int));
+               case STRING:
+                       return wl_ioctl(interface, ((param & SET) ? (ioc) : (ioc >> 16)) & 0xffff, value, BUFSIZE);
+       }
+       return 0;
+}
+
+static int wlc_iovar(wlc_param param, void *data, void *value)
+{
+       int *val = (int *) value;
+       char *iov = *((char **) data);
+       int ret = 0;
+       
+       if (param & SET) {
+               switch(param & PARAM_TYPE) {
+                       case INT:
+                               ret = wl_iovar_setint(interface, iov, *val);
+                               break;
+                       case MAC:
+                               ret = wl_iovar_set(interface, iov, value, 6);
+                               break;
+               }
+       }
+       if (param & GET) {
+               switch(param & PARAM_TYPE) {
+                       case INT:
+                               ret = wl_iovar_get(interface, iov, val, sizeof(int));
+                               break;
+                       case MAC:
+                               ret = wl_iovar_get(interface, iov, value, 6);
+                               break;
+               }
+       }
+
+       return ret;
+}
+
+static int wlc_bssiovar(wlc_param param, void *data, void *value)
+{
+       int *val = (int *) value;
+       char *iov = *((char **) data);
+       int ret = 0;
+       
+       if (param & SET) {
+               switch(param & PARAM_TYPE) {
+                       case INT:
+                               ret = wl_bssiovar_setint(interface, iov, vif, *val);
+               }
+       }
+       if (param & GET) {
+               switch(param & PARAM_TYPE) {
+                       case INT:
+                               ret = wl_bssiovar_get(interface, iov, vif, val, sizeof(int));
+               }
+       }
+
+       return ret;
+}
+
+static int wlc_vif_enabled(wlc_param param, void *data, void *value)
+{
+       int *val = (int *) value;
+       int buf[3];
+       int ret = 0;
+       
+       sprintf((char *) buf, "bss");
+       buf[1] = vif;
+       if (param & SET) {
+               buf[2] = (*val ? 1 : 0);
+               ret = wl_ioctl(interface, WLC_SET_VAR, buf, sizeof(buf));
+       } else if (param & GET) {
+               ret = wl_ioctl(interface, WLC_GET_VAR, buf, sizeof(buf));
+               *val = buf[0];
+       }
+
+       return ret;
+}
+
+static int wlc_ssid(wlc_param param, void *data, void *value)
+{
+       int ret = -1, ret2 = -1;
+       char *dest = (char *) value;
+       wlc_ssid_t ssid;
+       
+       if ((param & PARAM_MODE) == GET) {
+               ret = wl_bssiovar_get(interface, "ssid", vif, &ssid, sizeof(ssid));
+
+               if (ret)
+                       /* if we can't get the ssid through the bssiovar, try WLC_GET_SSID */
+                       ret = wl_ioctl(interface, WLC_GET_SSID, &ssid, sizeof(ssid));
+               
+               if (!ret) {
+                       memcpy(dest, ssid.SSID, ssid.SSID_len);
+                       dest[ssid.SSID_len] = 0;
+               }
+       } else if ((param & PARAM_MODE) == SET) {
+               strncpy(ssid.SSID, value, 32);
+               ssid.SSID_len = strlen(value);
+               
+               if (ssid.SSID_len > 32)
+                       ssid.SSID_len = 32;
+               
+               if (vif == 0) {
+                       /* for the main interface, also try the WLC_SET_SSID call */
+                       ret2 = wl_ioctl(interface, WLC_SET_SSID, &ssid, sizeof(ssid));
+               }
+               
+               ret = wl_bssiovar_set(interface, "ssid", vif, &ssid, sizeof(ssid));
+               ret = (!ret2 ? 0 : ret);
+       }
+       
+       return ret;
+}
+
+static int wlc_int(wlc_param param, void *data, void *value)
+{
+       int *var = *((int **) data);
+       int *val = (int *) value;
+
+       if ((param & PARAM_MODE) == SET) {
+               *var = *val;
+       } else if ((param & PARAM_MODE) == GET) {
+               *val = *var;
+       }
+
+       return 0;
+}
+
+static int wlc_flag(wlc_param param, void *data, void *value)
+{
+       int *var = *((int **) data);
+
+       *var = 1;
+
+       return 0;
+}
+
+static int wlc_string(wlc_param param, void *data, void *value)
+{
+       char *var = *((char **) data);
+       
+       if ((param & PARAM_MODE) == GET) {
+               strcpy(value, var);
+       }
+
+       return 0;
+}
+
+static int wlc_afterburner(wlc_param param, void *data, void *value)
+{
+       int *val = (int *) value;
+       int ret = 0;
+
+       if ((param & PARAM_MODE) == GET) {
+               ret = wl_iovar_get(interface, "afterburner", val, sizeof(int));
+       } else {
+               wl_iovar_setint(interface, "wlfeatureflag", (*val ? 3 : 0));
+               ret = wl_iovar_setint(interface, "afterburner", (*val ? 1 : 0));
+               wl_iovar_setint(interface, "afterburner_override", *val);
+       }
+
+       return ret;
+}
+
+static int wlc_maclist(wlc_param param, void *data, void *value)
+{
+       unsigned int *var = ((unsigned int *) data);
+       unsigned int ioc = *var;
+       int limit = (sizeof(wlbuf) - 4) / sizeof(struct ether_addr);
+       struct maclist *list = (struct maclist *) wlbuf;
+       char *str = (char *) value;
+       char astr[30], *p;
+       struct ether_addr *addr;
+       int isset = 0;
+       int ret;
+
+       if ((param & PARAM_MODE) == GET) {
+               list->count = limit;
+               ret = wl_ioctl(interface, (ioc >> 16) & 0xffff, wlbuf, sizeof(wlbuf));
+               
+               if (!ret) 
+                       while (list->count) {
+                               str += sprintf(str, "%s", ((((char *) value) == str) ? "" : " "));
+                               str += my_ether_ntoa((unsigned char *) &list->ea[list->count-- - 1], str);
+                       }
+               
+               return ret;
+       } else {
+               while (*str && isspace(*str))
+                       *str++;
+               
+               if (*str == '+') {
+                       str++;
+
+                       list->count = limit;
+                       if (wl_ioctl(interface, (ioc >> 16) & 0xffff, wlbuf, sizeof(wlbuf)) == 0)
+                               isset = 1;
+
+                       while (*str && isspace(*str))
+                               str++;
+               }
+               
+               if (!isset)
+                       memset(wlbuf, 0, sizeof(wlbuf));
+               
+               foreach(astr, str, p) {
+                       if (list->count >= limit)
+                               break;
+                       
+                       if ((addr = ether_aton(astr)) != NULL)
+                               memcpy(&list->ea[list->count++], addr, sizeof(struct ether_addr));
+               }
+
+               return wl_ioctl(interface, ioc & 0xffff, wlbuf, sizeof(wlbuf));
+       }
+}
+
+static int wlc_radio(wlc_param param, void *data, void *value)
+{
+       int *val = (int *) value;
+       int ret;
+
+       if ((param & PARAM_MODE) == GET) {
+               ret = wl_ioctl(interface, WLC_GET_RADIO, val, sizeof(int));
+               *val = ((*val & 1) ? 0 : 1);
+       } else {
+               *val = (1 << 16) | (*val ? 0 : 1); 
+               ret = wl_ioctl(interface, WLC_SET_RADIO, val, sizeof(int));
+       }
+
+       return ret;
+}
+
+static int wlc_wsec_key(wlc_param param, void *null, void *value)
+{
+       wl_wsec_key_t wsec_key;
+       unsigned char *index = value;
+       unsigned char *key;
+       unsigned char *data;
+       unsigned char hex[3];
+       
+       if ((param & PARAM_MODE) != SET)
+               return 0;
+
+       memset(&wsec_key, 0, sizeof(wsec_key));
+       if (index[0] == '=') {
+               wsec_key.flags = WL_PRIMARY_KEY;
+               index++;
+       }
+       
+       if ((index[0] < '1') || (index[0] > '4') || (index[1] != ','))
+               return -1;
+       
+       key = index + 2;
+       if (strncmp(key, "d:", 2) == 0) { /* delete key */
+       } else if (strncmp(key, "s:", 2) == 0) { /* ascii key */
+               key += 2;
+               wsec_key.len = strlen(key);
+
+               if ((wsec_key.len != 5) && (wsec_key.len != 13))
+                       return -1;
+               
+               strcpy(wsec_key.data, key);
+       } else { /* hex key */
+               wsec_key.len = strlen(key);
+               if ((wsec_key.len != 10) && (wsec_key.len != 26))
+                       return -1;
+               
+               wsec_key.len /= 2;
+               data = wsec_key.data;
+               hex[2] = 0;
+               do {
+                       hex[0] = *(key++);
+                       hex[1] = *(key++);
+                       *(data++) = (unsigned char) strtoul(hex, NULL, 16);
+               } while (*key != 0);
+       }
+
+       return wl_bssiovar_set(interface, "wsec_key", vif, &wsec_key, sizeof(wsec_key));
+}
+
+static inline int cw2ecw(int cw)
+{
+       int i;  
+       for (cw++, i = 0; cw; i++) cw >>=1;
+       return i - 1;
+}
+
+static int wlc_wme_ac(wlc_param param, void *data, void *value)
+{
+       char *type = *((char **) data);
+       char *settings = (char *) value;
+       char cmd[100], *p, *val;
+       edcf_acparam_t params[AC_COUNT];
+       int ret;
+       int intval;
+       int cur = -1;
+       char *buf = wlbuf;
+
+       if ((param & PARAM_MODE) != SET)
+               return -1;
+       
+       memset(params, 0, sizeof(params));
+       ret = wl_iovar_get(interface, type, params, sizeof(params));
+       memset(buf, 0, BUFSIZE);
+       strcpy(buf, type);
+       buf += strlen(buf) + 1;
+       
+       foreach(cmd, settings, p) {
+               val = strchr(cmd, '=');
+               if (val == NULL) {
+                       if (strcmp(cmd, "be") == 0)
+                               cur = AC_BE;
+                       else if (strcmp(cmd, "bk") == 0)
+                               cur = AC_BK;
+                       else if (strcmp(cmd, "vi") == 0)
+                               cur = AC_VI;
+                       else if (strcmp(cmd, "vo") == 0)
+                               cur = AC_VO;
+                       else
+                               return -1;
+
+                       /* just in case */
+                       params[cur].ACI = (params[cur].ACI & (0x3 << 5)) | (cur << 5);
+               } else {
+                       *(val++) = 0;
+                       
+                       intval = strtoul(val, NULL, 10);
+                       if (strcmp(cmd, "cwmin") == 0)
+                               params[cur].ECW = (params[cur].ECW & ~(0xf)) | cw2ecw(intval);
+                       else if (strcmp(cmd, "ecwmin") == 0)
+                               params[cur].ECW = (params[cur].ECW & ~(0xf)) | (intval & 0xf);
+                       else if (strcmp(cmd, "cwmax") == 0)
+                               params[cur].ECW = (params[cur].ECW & ~(0xf << 4)) | (cw2ecw(intval) << 4);
+                       else if (strcmp(cmd, "ecwmax") == 0)
+                               params[cur].ECW = (params[cur].ECW & ~(0xf << 4)) | ((intval & 0xf) << 4);
+                       else if (strcmp(cmd, "aifsn") == 0)
+                               params[cur].ACI = (params[cur].ACI & ~(0xf)) | (intval & 0xf);
+                       else if (strcmp(cmd, "txop") == 0)
+                               params[cur].TXOP = intval >> 5;
+                       else if (strcmp(cmd, "force") == 0)
+                               params[cur].ACI = (params[cur].ACI & ~(1 << 4)) | ((intval) ? (1 << 4) : 0);
+                       else return -1;
+                       
+                       memcpy(buf, &params[cur], sizeof(edcf_acparam_t));
+                       wl_ioctl(interface, WLC_SET_VAR, wlbuf, BUFSIZE);
+               }
+       }
+       return ret;
+}
+
+static int wlc_ifname(wlc_param param, void *data, void *value)
+{
+       char *val = (char *) value;
+       int ret = 0;
+       
+       if (param & SET) {
+               if (strlen(val) < 16)
+                       strcpy(interface, val);
+               else ret = -1;
+       }
+       if (param & GET) {
+               strcpy(val, interface);
+       }
+
+       return ret;
+}
+
+static int wlc_wdsmac(wlc_param param, void *data, void *value)
+{
+       unsigned char mac[6];
+       int ret = 0;
+       
+       ret = wl_ioctl(interface, WLC_WDS_GET_REMOTE_HWADDR, &mac, 6);
+       if (ret == 0)
+               my_ether_ntoa(mac, value);
+
+       return ret;
+}
+
+static int wlc_pmk(wlc_param param, void *data, void *value)
+{
+       int ret = -1;
+       char *str = (char *) value;
+       wsec_pmk_t pmk;
+       
+       /* driver doesn't support GET */
+
+       if ((param & PARAM_MODE) == SET) {
+               strncpy(pmk.key, value, WSEC_MAX_PSK_LEN);
+               pmk.key_len = strlen(value);
+
+               if (pmk.key_len > WSEC_MAX_PSK_LEN)
+                       pmk.key_len = WSEC_MAX_PSK_LEN;
+
+               pmk.flags = WSEC_PASSPHRASE;
+
+               ret = wl_ioctl(interface, WLC_SET_WSEC_PMK, &pmk, sizeof(pmk));
+       }
+       
+       return ret;
+}
+
+static const struct wlc_call wlc_calls[] = {
+       {
+               .name = "version",
+               .param = STRING|NOARG,
+               .handler = wlc_string,
+               .data.str = VERSION,
+               .desc = "Version of this program"
+       },
+       {
+               .name = "debug",
+               .param = INT,
+               .handler = wlc_int,
+               .data.ptr = &debug,
+               .desc = "wlc debug level"
+       },
+       {
+               .name = "stdin",
+               .param = NOARG,
+               .handler = wlc_flag,
+               .data.ptr = &fromstdin,
+               .desc = "Accept input from stdin"
+       },
+       {
+               .name = "ifname",
+               .param = STRING,
+               .handler = wlc_ifname,
+               .desc = "interface to send commands to"
+       },
+       {
+               .name = "up",
+               .param = NOARG,
+               .handler = wlc_ioctl,
+               .data.num = WLC_UP,
+               .desc = "Bring the interface up"
+       },
+       {
+               .name = "down",
+               .param = NOARG,
+               .handler = wlc_ioctl,
+               .data.num = WLC_DOWN,
+               .desc = "Bring the interface down"
+       },
+       {
+               .name = "radio",
+               .param = INT,
+               .handler = wlc_radio,
+               .desc = "Radio enabled flag"
+       },
+       {
+               .name = "ap",
+               .param = INT,
+               .handler = wlc_ioctl,
+               .data.num = ((WLC_GET_AP << 16) | WLC_SET_AP),
+               .desc = "Access Point mode"
+       },
+       {
+               .name = "mssid",
+               .param = INT,
+               .handler = wlc_iovar,
+               .data.str = "mbss",
+               .desc = "Multi-ssid mode"
+       },
+       {
+               .name = "apsta",
+               .param = INT,
+               .handler = wlc_iovar,
+               .data.str = "apsta",
+               .desc = "AP+STA mode"
+       },
+       {
+               .name = "infra",
+               .param = INT,
+               .handler = wlc_ioctl,
+               .data.num = ((WLC_GET_INFRA << 16) | WLC_SET_INFRA),
+               .desc = "Infrastructure mode"
+       },
+       {
+               .name = "wet",
+               .param = INT,
+               .handler = wlc_ioctl,
+               .data.num = ((WLC_GET_WET << 16) | WLC_SET_WET),
+               .desc = "Wireless repeater mode",
+       },
+       {
+               .name = "statimeout",
+               .param = INT,
+               .handler = wlc_iovar,
+               .data.str = "sta_retry_time",
+               .desc = "STA connection timeout"
+       },
+       {
+               .name = "country",
+               .param = STRING,
+               .handler = wlc_ioctl,
+               .data.num = ((WLC_GET_COUNTRY << 16) | WLC_SET_COUNTRY),
+               .desc = "Country code"
+       },
+       {
+               .name = "channel",
+               .param = INT,
+               .handler = wlc_ioctl,
+               .data.num = ((WLC_GET_CHANNEL << 16) | WLC_SET_CHANNEL),
+               .desc = "Channel",
+       },
+       {
+               .name = "vlan_mode",
+               .param = INT,
+               .handler = wlc_bssiovar,
+               .data.str = "vlan_mode",
+               .desc = "Parse 802.1Q tags",
+       },
+       {
+               .name = "vif",
+               .param = INT,
+               .handler = wlc_int,
+               .data.ptr = &vif,
+               .desc = "Current vif index"
+       },
+       {
+               .name = "enabled",
+               .param = INT,
+               .handler = wlc_vif_enabled,
+               .desc = "vif enabled flag"
+       },
+       {
+               .name = "ssid",
+               .param = STRING,
+               .handler = wlc_ssid,
+               .desc = "Interface ESSID"
+       },
+       {
+               .name = "closed",
+               .param = INT,
+               .handler = wlc_bssiovar,
+               .data.str = "closednet",
+               .desc = "Hidden ESSID flag"
+       },
+       {
+               .name = "wsec",
+               .param = INT,
+               .handler = wlc_bssiovar,
+               .data.str = "wsec",
+               .desc = "Security mode flags"
+       },
+       {
+               .name = "wepkey",
+               .param = STRING,
+               .handler = wlc_wsec_key,
+               .desc = "Set/Remove WEP keys"
+       },
+       {
+               .name = "wepauth",
+               .param = INT,
+               .handler = wlc_ioctl,
+               .data.num = ((WLC_GET_AUTH << 16) | WLC_SET_AUTH),
+               .desc = "WEP authentication type. 0 = OpenSystem, 1 = SharedKey"
+       },
+       {
+               .name = "wsec_restrict",
+               .param = INT,
+               .handler = wlc_bssiovar,
+               .data.str = "wsec_restrict",
+               .desc = "Drop unencrypted traffic"
+       },
+       {
+               .name = "eap_restrict",
+               .param = INT,
+               .handler = wlc_bssiovar,
+               .data.str = "eap_restrict",
+               .desc = "Only allow 802.1X traffic until 802.1X authorized"
+       },
+       {
+               .name = "wpa_auth",
+               .param = INT,
+               .handler = wlc_bssiovar,
+               .data.str = "wpa_auth",
+               .desc = "WPA authentication modes"
+       },
+       {
+               .name = "ap_isolate",
+               .param = INT,
+               .handler = wlc_bssiovar,
+               .data.str = "ap_isolate",
+               .desc = "Isolate connected clients"
+       },
+       {
+               .name = "supplicant",
+               .param = INT,
+               .handler = wlc_iovar,
+               .data.str = "sup_wpa",
+               .desc = "Built-in WPA supplicant"
+       },
+       {
+               .name = "passphrase",
+               .param = STRING,
+               .handler = wlc_pmk,
+               .desc = "Passphrase for built-in WPA supplicant",
+       },
+       {
+               .name = "maxassoc",
+               .param = INT,
+               .handler = wlc_iovar,
+               .data.str = "maxassoc",
+               .desc = "Max. number of associated clients",
+       },
+       {
+               .name = "wme",
+               .param = INT,
+               .handler = wlc_iovar,
+               .data.str = "wme",
+               .desc = "WME enabled"
+       },
+       {
+               .name = "wme_ac_ap",
+               .param = STRING,
+               .handler = wlc_wme_ac,
+               .data.str = "wme_ac_ap",
+               .desc = "Set WME AC options for AP mode",
+       },
+       {
+               .name = "wme_ac_sta",
+               .param = STRING,
+               .handler = wlc_wme_ac,
+               .data.str = "wme_ac_sta",
+               .desc = "Set WME AC options for STA mode",
+       },
+       {
+               .name = "wme_noack",
+               .param = INT,
+               .handler = wlc_iovar,
+               .data.str = "wme_noack",
+               .desc = "WME ACK disable request",
+       },
+       {
+               .name = "802.11d",
+               .param = INT,
+               .handler = wlc_ioctl,
+               .data.num = ((WLC_GET_REGULATORY << 16) | WLC_SET_REGULATORY),
+               .desc = "Enable/disable 802.11d regulatory management",
+       },
+       {
+               .name = "802.11h",
+               .param = INT,
+               .handler = wlc_ioctl,
+               .data.num = ((WLC_GET_SPECT_MANAGMENT << 16) | WLC_SET_SPECT_MANAGMENT),
+               .desc = "Enable/disable 802.11h spectrum management",
+       },
+       {
+               .name = "fragthresh",
+               .param = INT,
+               .handler = wlc_iovar,
+               .data.str = "fragthresh",
+               .desc = "Fragmentation threshold",
+       },
+       {
+               .name = "rtsthresh",
+               .param = INT,
+               .handler = wlc_iovar,
+               .data.str = "rtsthresh",
+               .desc = "RTS threshold"
+       },
+       {
+               .name = "slottime",
+               .param = INT,
+               .handler = wlc_iovar,
+               .data.str = "acktiming",
+               .desc = "Slot time"
+       },
+       {
+               .name = "rxant",
+               .param = INT,
+               .handler = wlc_ioctl,
+               .data.num = ((WLC_GET_ANTDIV << 16) | WLC_SET_ANTDIV),
+               .desc = "Rx antenna selection"
+       },
+       {
+               .name = "txant",
+               .param = INT,
+               .handler = wlc_ioctl,
+               .data.num = ((WLC_GET_TXANT << 16) | WLC_SET_TXANT),
+               .desc = "Tx antenna selection"
+       },
+       {
+               .name = "dtim",
+               .param = INT,
+               .handler = wlc_ioctl,
+               .data.num = ((WLC_GET_DTIMPRD << 16) | WLC_SET_DTIMPRD),
+               .desc = "DTIM period",
+       },
+       {
+               .name = "bcn",
+               .param = INT,
+               .handler = wlc_ioctl,
+               .data.num = ((WLC_GET_BCNPRD << 16) | WLC_SET_BCNPRD),
+               .desc = "Beacon interval"
+       },
+       {
+               .name = "frameburst",
+               .param = INT,
+               .handler = wlc_ioctl,
+               .data.num = ((WLC_GET_FAKEFRAG << 16) | WLC_SET_FAKEFRAG),
+               .desc = "Framebursting"
+       },
+       {
+               .name = "monitor",
+               .param = INT,
+               .handler = wlc_ioctl,
+               .data.num = ((WLC_GET_MONITOR << 16) | WLC_SET_MONITOR),
+               .desc = "Monitor mode"
+       },
+       {
+               .name = "passive_scan",
+               .param = INT,
+               .handler = wlc_ioctl,
+               .data.num = ((WLC_GET_PASSIVE_SCAN << 16) | WLC_SET_PASSIVE_SCAN),
+               .desc = "Passive scan mode"
+       },
+       {
+               .name = "macfilter",
+               .param = INT,
+               .handler = wlc_ioctl,
+               .data.num = ((WLC_GET_MACMODE << 16) | WLC_SET_MACMODE),
+               .desc = "MAC filter mode (0:disabled, 1:deny, 2:allow)"
+       },
+       {
+               .name = "maclist",
+               .param = STRING,
+               .data.num = ((WLC_GET_MACLIST << 16) | WLC_SET_MACLIST),
+               .handler = wlc_maclist,
+               .desc = "MAC filter list"
+       },
+       {
+               .name = "autowds",
+               .param = INT,
+               .handler = wlc_ioctl,
+               .data.num = ((WLC_GET_LAZYWDS << 16) | WLC_SET_LAZYWDS),
+               .desc = "Automatic WDS"
+       },
+       {
+               .name = "wds",
+               .param = STRING,
+               .data.num = ((WLC_GET_WDSLIST << 16) | WLC_SET_WDSLIST),
+               .handler = wlc_maclist,
+               .desc = "WDS connection list"
+       },
+       {
+               .name = "wdstimeout",
+               .param = INT,
+               .handler = wlc_iovar,
+               .data.str = "wdstimeout",
+               .desc = "WDS link detection timeout"
+       },
+       {
+               .name = "wdsmac",
+               .param = STRING|NOARG,
+               .handler = wlc_wdsmac,
+               .desc = "MAC of the remote WDS endpoint (only with wds0.* interfaces)"
+       },
+       {
+               .name = "afterburner",
+               .param = INT,
+               .handler = wlc_afterburner,
+               .desc = "Broadcom Afterburner"
+       },
+       {
+               .name = "ibss_merge",
+               .param = INT,
+               .handler = wlc_iovar,
+               .data.str = "ibss_coalesce_allowed",
+               .desc = "Allow IBSS merges"
+       },
+       {
+               .name = "bssid",
+               .param = MAC,
+               .handler = wlc_ioctl,
+               .data.num = ((WLC_GET_BSSID << 16) | WLC_SET_BSSID),
+               .desc = "BSSID"
+       },
+       {
+               .name = "default_bssid",
+               .param = MAC,
+               .handler = wlc_iovar,
+               .data.str = "perm_etheraddr",
+               .desc = "Default BSSID (read-only)"
+       },
+       {
+               .name = "assoclist",
+               .param = STRING,
+               .data.num = (WLC_GET_ASSOCLIST << 16),
+               .handler = wlc_maclist,
+               .desc = "MACs of associated stations"
+       },
+       {
+               .name = "gmode",
+               .param = INT,
+               .data.num = ((WLC_GET_GMODE << 16) | WLC_SET_GMODE),
+               .handler = wlc_ioctl,
+               .desc = "G Mode"
+       },
+};
+#define wlc_calls_size (sizeof(wlc_calls) / sizeof(struct wlc_call))
+
+static void usage(char *cmd)
+{
+       int i;
+       fprintf(stderr, "Usage: %s <command> [<argument> ...]\n"
+                                       "\n"
+                                       "Available commands:\n", cmd);
+       for (i = 0; i < wlc_calls_size; i++) {
+               fprintf(stderr, "\t%-16s\t%s\n", wlc_calls[i].name ?: "", wlc_calls[i].desc ?: "");
+       }
+       fprintf(stderr, "\n");
+       exit(1);
+}
+
+static int do_command(const struct wlc_call *cmd, char *arg)
+{
+       static char buf[BUFSIZE];
+       int set;
+       int ret = 0;
+       char *format, *end;
+       int intval;
+       void *ptr = (void *) buf;
+
+       if (debug >= 10) {
+               fprintf(stderr, "do_command %-16s\t'%s'\n", cmd->name, arg);
+       }
+       
+       if ((arg == NULL) && ((cmd->param & PARAM_TYPE) != NONE)) {
+               set = 0;
+               ret = cmd->handler(cmd->param | GET, (void *) &cmd->data, (void *) buf);
+               if (ret == 0) {
+                       switch(cmd->param & PARAM_TYPE) {
+                               case INT:
+                                       intval = *((int *) buf);
+                                       
+                                       if (intval > 65535)
+                                               format = "0x%08x\n";
+                                       else if (intval > 255)
+                                               format = "0x%04x\n";
+                                       else
+                                               format = "%d\n";
+                                       
+                                       fprintf(stdout, format, intval);
+                                       break;
+                               case STRING:
+                                       fprintf(stdout, "%s\n", buf);
+                                       break;
+                               case MAC:
+                                       my_ether_ntoa(buf, buf + 6);
+                                       fprintf(stdout, "%s\n", buf + 6);
+                                       break;
+                       }
+               }
+       } else { /* SET */
+               set = 1;
+               switch(cmd->param & PARAM_TYPE) {
+                       case INT:
+                               intval = strtoul(arg, &end, 10);
+                               if (end && !(*end)) {
+                                       memcpy(buf, &intval, sizeof(intval));
+                               } else {
+                                       fprintf(stderr, "%s: Invalid argument\n", cmd->name);
+                                       return -1;
+                               }
+                               break;
+                       case STRING:
+                               strncpy(buf, arg, BUFSIZE);
+                               buf[BUFSIZE - 1] = 0;
+                               break;
+                       case MAC:
+                               ptr = ether_aton(arg);
+                               if (!ptr) {
+                                       fprintf(stderr, "%s: Invalid mac address '%s'\n", cmd->name, arg);
+                                       return -1;
+                               }
+                               break;
+               }
+
+               ret = cmd->handler(cmd->param | SET, (void *) &cmd->data, ptr);
+       }
+       
+       if ((debug > 0) && (ret != 0)) 
+               fprintf(stderr, "Command '%s %s' failed: %d\n", (set == 1 ? "set" : "get"), cmd->name, ret);
+       
+       return ret;
+}
+
+static struct wlc_call *find_cmd(char *name)
+{
+       int found = 0, i = 0;
+
+       while (!found && (i < wlc_calls_size)) {
+               if (strcmp(name, wlc_calls[i].name) == 0)
+                       found = 1;
+               else
+                       i++;
+       }
+
+       return (struct wlc_call *) (found ? &wlc_calls[i] : NULL);
+}
+
+int main(int argc, char **argv)
+{
+       static char buf[BUFSIZE];
+       char *s, *s2;
+       char *cmd = argv[0];
+       struct wlc_call *call;
+       int ret = 0;
+
+       if (argc < 2)
+               usage(argv[0]);
+
+       for(interface[2] = '0'; (interface[2] < '3') && (wl_probe(interface) != 0); interface[2]++);
+       if (interface[2] == '3') {
+               fprintf(stderr, "No Broadcom wl interface found!\n");
+               return -1;
+       }
+
+       argv++;
+       argc--;
+       while ((argc > 0) && (argv[0] != NULL)) {
+               if ((call = find_cmd(argv[0])) == NULL) {
+                       fprintf(stderr, "Invalid command: %s\n\n", argv[0]);
+                       usage(cmd);
+               }
+               if ((argc > 1) && (!(call->param & NOARG))) {
+                       ret = do_command(call, argv[1]);
+                       argv += 2;
+                       argc -= 2;
+               } else {
+                       ret = do_command(call, NULL);
+                       argv++;
+                       argc--;
+               }
+       }
+
+       while (fromstdin && !feof(stdin)) {
+               *buf = 0;
+               fgets(buf, BUFSIZE - 1, stdin);
+               
+               if (*buf == 0)
+                       continue;
+               
+               if ((s = strchr(buf, '\r')) != NULL)
+                       *s = 0;
+               if ((s = strchr(buf, '\n')) != NULL)
+                       *s = 0;
+
+               s = buf;
+               while (isspace(*s))
+                       s++;
+
+               if (!*s)
+                       continue;
+       
+               if ((s2 = strchr(buf, ' ')) != NULL)
+                       *(s2++) = 0;
+               
+               while (s2 && isspace(*s2))
+                       s2++;
+               
+               if ((call = find_cmd(buf)) == NULL) {
+                       fprintf(stderr, "Invalid command: %s\n", buf);
+                       ret = -1;
+               } else
+                       ret = do_command(call, ((call->param & NOARG) ? NULL : s2));
+       }
+
+       return ret;
+}