/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Copyright(c) 2004
 *	Damien Bergamini <damien.bergamini@free.fr>. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice unmodified, this list of conditions, and the following
 *    disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <sys/types.h>
#include <sys/byteorder.h>
#include <sys/conf.h>
#include <sys/cmn_err.h>
#include <sys/stat.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/strsubr.h>
#include <sys/ethernet.h>
#include <inet/common.h>
#include <inet/nd.h>
#include <inet/mi.h>
#include <sys/note.h>
#include <sys/stream.h>
#include <sys/strsun.h>
#include <sys/modctl.h>
#include <sys/devops.h>
#include <sys/dlpi.h>
#include <sys/mac.h>
#include <net/if.h>
#include <sys/mac_wifi.h>
#include <sys/varargs.h>
#include <sys/policy.h>

#include "ipw2100.h"
#include "ipw2100_impl.h"
#include <inet/wifi_ioctl.h>

/*
 * kCF framework include files
 */
#include <sys/crypto/common.h>
#include <sys/crypto/api.h>

static void   *ipw2100_ssp	= NULL;
static char   ipw2100_ident[]	= IPW2100_DRV_DESC " " IPW2100_DRV_REV;

/*
 * PIO access attribute for register
 */
static ddi_device_acc_attr_t ipw2100_csr_accattr = {
	DDI_DEVICE_ATTR_V0,
	DDI_STRUCTURE_LE_ACC,
	DDI_STRICTORDER_ACC
};

static ddi_device_acc_attr_t ipw2100_dma_accattr = {
	DDI_DEVICE_ATTR_V0,
	DDI_NEVERSWAP_ACC,
	DDI_STRICTORDER_ACC
};

static ddi_dma_attr_t ipw2100_dma_attr = {
	DMA_ATTR_V0,
	0x0000000000000000ULL,
	0x00000000ffffffffULL,
	0x00000000ffffffffULL,
	0x0000000000000004ULL,
	0xfff,
	1,
	0x00000000ffffffffULL,
	0x00000000ffffffffULL,
	1,
	1,
	0
};

static const struct ieee80211_rateset ipw2100_rateset_11b = { 4,
	{2, 4, 11, 22}
};

/*
 * For mfthread only
 */
extern pri_t minclsyspri;

/*
 * ipw2100 specific hardware operations
 */
static void	ipw2100_hwconf_get(struct ipw2100_softc *sc);
static int	ipw2100_chip_reset(struct ipw2100_softc *sc);
static void	ipw2100_master_stop(struct ipw2100_softc *sc);
static void	ipw2100_stop(struct ipw2100_softc *sc);
static int	ipw2100_config(struct ipw2100_softc *sc);
static int	ipw2100_cmd(struct ipw2100_softc *sc, uint32_t type,
    void *buf, size_t len);
static int	ipw2100_dma_region_alloc(struct ipw2100_softc *sc,
    struct dma_region *dr, size_t size, uint_t dir, uint_t flags);
static void	ipw2100_dma_region_free(struct dma_region *dr);
static void	ipw2100_tables_init(struct ipw2100_softc *sc);
static void	ipw2100_ring_hwsetup(struct ipw2100_softc *sc);
static int	ipw2100_ring_alloc(struct ipw2100_softc *sc);
static void	ipw2100_ring_free(struct ipw2100_softc *sc);
static void	ipw2100_ring_reset(struct ipw2100_softc *sc);
static int	ipw2100_ring_init(struct ipw2100_softc *sc);

/*
 * GLD specific operations
 */
static int	ipw2100_m_stat(void *arg, uint_t stat, uint64_t *val);
static int	ipw2100_m_start(void *arg);
static void	ipw2100_m_stop(void *arg);
static int	ipw2100_m_unicst(void *arg, const uint8_t *macaddr);
static int	ipw2100_m_multicst(void *arg, boolean_t add, const uint8_t *m);
static int	ipw2100_m_promisc(void *arg, boolean_t on);
static mblk_t  *ipw2100_m_tx(void *arg, mblk_t *mp);
static void	ipw2100_m_ioctl(void *arg, queue_t *wq, mblk_t *mp);

/*
 * Interrupt and Data transferring operations
 */
static uint_t	ipw2100_intr(caddr_t arg);
static int	ipw2100_send(struct ieee80211com *ic, mblk_t *mp, uint8_t type);
static void	ipw2100_rcvpkt(struct ipw2100_softc *sc,
    struct ipw2100_status *status, uint8_t *rxbuf);

/*
 * WiFi specific operations
 */
static int	ipw2100_newstate(struct ieee80211com *ic,
    enum ieee80211_state state, int arg);
static void	ipw2100_thread(struct ipw2100_softc *sc);

/*
 * IOCTL Handler
 */
static int	ipw2100_ioctl(struct ipw2100_softc *sc, queue_t *q, mblk_t *m);
static int	ipw2100_getset(struct ipw2100_softc *sc,
    mblk_t *m, uint32_t cmd, boolean_t *need_net80211);
static int	ipw_wificfg_radio(struct ipw2100_softc *sc,
    uint32_t cmd,  wldp_t *outfp);
static int	ipw_wificfg_desrates(wldp_t *outfp);
static int	ipw_wificfg_disassoc(struct ipw2100_softc *sc,
    wldp_t *outfp);

/*
 * Mac Call Back entries
 */
mac_callbacks_t	ipw2100_m_callbacks = {
	MC_IOCTL,
	ipw2100_m_stat,
	ipw2100_m_start,
	ipw2100_m_stop,
	ipw2100_m_promisc,
	ipw2100_m_multicst,
	ipw2100_m_unicst,
	ipw2100_m_tx,
	NULL,
	ipw2100_m_ioctl
};


/*
 * DEBUG Facility
 */
#define	MAX_MSG (128)
uint32_t ipw2100_debug = 0;
/*
 * supported debug marsks:
 *	| IPW2100_DBG_INIT
 *	| IPW2100_DBG_GLD
 *	| IPW2100_DBG_TABLE
 *	| IPW2100_DBG_SOFTINT
 *	| IPW2100_DBG_CSR
 *	| IPW2100_DBG_INT
 *	| IPW2100_DBG_FW
 *	| IPW2100_DBG_IOCTL
 *	| IPW2100_DBG_HWCAP
 *	| IPW2100_DBG_STATISTIC
 *	| IPW2100_DBG_RING
 *	| IPW2100_DBG_WIFI
 */

/*
 * global tuning parameters to work around unknown hardware issues
 */
static uint32_t delay_config_stable 	= 100000;	/* 100ms */
static uint32_t delay_fatal_recover	= 100000 * 20;	/* 2s */
static uint32_t delay_aux_thread 	= 100000;	/* 100ms */

void
ipw2100_dbg(dev_info_t *dip, int level, const char *fmt, ...)
{
	va_list	ap;
	char    buf[MAX_MSG];
	int	instance;

	va_start(ap, fmt);
	(void) vsnprintf(buf, sizeof (buf), fmt, ap);
	va_end(ap);

	if (dip) {
		instance = ddi_get_instance(dip);
		cmn_err(level, "%s%d: %s", IPW2100_DRV_NAME, instance, buf);
	} else
		cmn_err(level, "%s: %s", IPW2100_DRV_NAME, buf);
}

/*
 * device operations
 */
int
ipw2100_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
	struct ipw2100_softc	*sc;
	ddi_acc_handle_t	cfgh;
	caddr_t			regs;
	struct ieee80211com	*ic;
	int			instance, err, i;
	char			strbuf[32];
	wifi_data_t		wd = { 0 };
	mac_register_t		*macp;

	if (cmd != DDI_ATTACH) {
		err = DDI_FAILURE;
		goto fail1;
	}

	instance = ddi_get_instance(dip);
	err = ddi_soft_state_zalloc(ipw2100_ssp, instance);
	if (err != DDI_SUCCESS) {
		IPW2100_WARN((dip, CE_WARN,
		    "ipw2100_attach(): unable to allocate soft state\n"));
		goto fail1;
	}
	sc = ddi_get_soft_state(ipw2100_ssp, instance);
	sc->sc_dip = dip;

	/*
	 * Map config spaces register
	 */
	err = ddi_regs_map_setup(dip, IPW2100_PCI_CFG_RNUM, &regs,
	    0, 0, &ipw2100_csr_accattr, &cfgh);
	if (err != DDI_SUCCESS) {
		IPW2100_WARN((dip, CE_WARN,
		    "ipw2100_attach(): unable to map spaces regs\n"));
		goto fail2;
	}
	ddi_put8(cfgh, (uint8_t *)(regs + 0x41), 0);
	ddi_regs_map_free(&cfgh);

	/*
	 * Map operating registers
	 */
	err = ddi_regs_map_setup(dip, IPW2100_PCI_CSR_RNUM, &sc->sc_regs,
	    0, 0, &ipw2100_csr_accattr, &sc->sc_ioh);
	if (err != DDI_SUCCESS) {
		IPW2100_WARN((dip, CE_WARN,
		    "ipw2100_attach(): unable to map device regs\n"));
		goto fail2;
	}

	/*
	 * Reset the chip
	 */
	err = ipw2100_chip_reset(sc);
	if (err != DDI_SUCCESS) {
		IPW2100_WARN((dip, CE_WARN,
		    "ipw2100_attach(): reset failed\n"));
		goto fail3;
	}

	/*
	 * Get the hw conf, including MAC address, then init all rings.
	 */
	ipw2100_hwconf_get(sc);
	err = ipw2100_ring_init(sc);
	if (err != DDI_SUCCESS) {
		IPW2100_WARN((dip, CE_WARN,
		    "ipw2100_attach(): "
		    "unable to allocate and initialize rings\n"));
		goto fail3;
	}

	/*
	 * Initialize mutexs and condvars
	 */
	err = ddi_get_iblock_cookie(dip, 0, &sc->sc_iblk);
	if (err != DDI_SUCCESS) {
		IPW2100_WARN((dip, CE_WARN,
		    "ipw2100_attach(): ddi_get_iblock_cookie() failed\n"));
		goto fail4;
	}
	/*
	 * interrupt lock
	 */
	mutex_init(&sc->sc_ilock, "interrupt-lock", MUTEX_DRIVER,
	    (void *) sc->sc_iblk);
	cv_init(&sc->sc_fw_cond, "firmware", CV_DRIVER, NULL);
	cv_init(&sc->sc_cmd_cond, "command", CV_DRIVER, NULL);
	/*
	 * tx ring lock
	 */
	mutex_init(&sc->sc_tx_lock, "tx-ring", MUTEX_DRIVER,
	    (void *) sc->sc_iblk);
	cv_init(&sc->sc_tx_cond, "tx-ring", CV_DRIVER, NULL);
	/*
	 * rescheuled lock
	 */
	mutex_init(&sc->sc_resched_lock, "reschedule-lock", MUTEX_DRIVER,
	    (void *) sc->sc_iblk);
	/*
	 * initialize the mfthread
	 */
	mutex_init(&sc->sc_mflock, "function-lock", MUTEX_DRIVER,
	    (void *) sc->sc_iblk);
	cv_init(&sc->sc_mfthread_cv, NULL, CV_DRIVER, NULL);
	sc->sc_mf_thread = NULL;
	sc->sc_mfthread_switch = 0;
	/*
	 * Initialize the wifi part, which will be used by
	 * generic layer
	 */
	ic = &sc->sc_ic;
	ic->ic_phytype  = IEEE80211_T_DS;
	ic->ic_opmode   = IEEE80211_M_STA;
	ic->ic_state    = IEEE80211_S_INIT;
	ic->ic_maxrssi  = 49;
	/*
	 * Future, could use s/w to handle encryption: IEEE80211_C_WEP
	 * and need to add support for IEEE80211_C_IBSS
	 */
	ic->ic_caps = IEEE80211_C_SHPREAMBLE | IEEE80211_C_TXPMGT |
	    IEEE80211_C_PMGT;
	ic->ic_sup_rates[IEEE80211_MODE_11B] = ipw2100_rateset_11b;
	IEEE80211_ADDR_COPY(ic->ic_macaddr, sc->sc_macaddr);
	for (i = 1; i < 16; i++) {
		if (sc->sc_chmask &(1 << i)) {
			/* IEEE80211_CHAN_B */
			ic->ic_sup_channels[i].ich_freq  = ieee80211_ieee2mhz(i,
			    IEEE80211_CHAN_2GHZ | IEEE80211_CHAN_CCK);
			ic->ic_sup_channels[i].ich_flags =
			    IEEE80211_CHAN_2GHZ | IEEE80211_CHAN_CCK;
		}
	}
	ic->ic_ibss_chan = &ic->ic_sup_channels[0];
	ic->ic_xmit = ipw2100_send;
	/*
	 * init Wifi layer
	 */
	ieee80211_attach(ic);

	/*
	 * Override 80211 default routines
	 */
	ieee80211_media_init(ic);
	sc->sc_newstate = ic->ic_newstate;
	ic->ic_newstate = ipw2100_newstate;
	/*
	 * initialize default tx key
	 */
	ic->ic_def_txkey = 0;
	/*
	 * Set the Authentication to AUTH_Open only.
	 */
	sc->sc_authmode = IEEE80211_AUTH_OPEN;

	/*
	 * Add the interrupt handler
	 */
	err = ddi_add_intr(dip, 0, &sc->sc_iblk, NULL,
	    ipw2100_intr, (caddr_t)sc);
	if (err != DDI_SUCCESS) {
		IPW2100_WARN((dip, CE_WARN,
		    "ipw2100_attach(): ddi_add_intr() failed\n"));
		goto fail5;
	}

	/*
	 * Initialize pointer to device specific functions
	 */
	wd.wd_secalloc = WIFI_SEC_NONE;
	wd.wd_opmode = ic->ic_opmode;
	IEEE80211_ADDR_COPY(wd.wd_bssid, ic->ic_macaddr);

	macp = mac_alloc(MAC_VERSION);
	if (err != 0) {
		IPW2100_WARN((dip, CE_WARN,
		    "ipw2100_attach(): mac_alloc() failed\n"));
		goto fail6;
	}

	macp->m_type_ident	= MAC_PLUGIN_IDENT_WIFI;
	macp->m_driver		= sc;
	macp->m_dip		= dip;
	macp->m_src_addr	= ic->ic_macaddr;
	macp->m_callbacks	= &ipw2100_m_callbacks;
	macp->m_min_sdu		= 0;
	macp->m_max_sdu		= IEEE80211_MTU;
	macp->m_pdata		= &wd;
	macp->m_pdata_size	= sizeof (wd);

	/*
	 * Register the macp to mac
	 */
	err = mac_register(macp, &ic->ic_mach);
	mac_free(macp);
	if (err != DDI_SUCCESS) {
		IPW2100_WARN((dip, CE_WARN,
		    "ipw2100_attach(): mac_register() failed\n"));
		goto fail6;
	}

	/*
	 * Create minor node of type DDI_NT_NET_WIFI
	 */
	(void) snprintf(strbuf, sizeof (strbuf), "%s%d",
	    IPW2100_DRV_NAME, instance);
	err = ddi_create_minor_node(dip, strbuf, S_IFCHR,
	    instance + 1, DDI_NT_NET_WIFI, 0);
	if (err != DDI_SUCCESS)
		IPW2100_WARN((dip, CE_WARN,
		    "ipw2100_attach(): ddi_create_minor_node() failed\n"));

	/*
	 * Cache firmware, always return true
	 */
	(void) ipw2100_cache_firmware(sc);

	/*
	 * Notify link is down now
	 */
	mac_link_update(ic->ic_mach, LINK_STATE_DOWN);

	/*
	 * create the mf thread to handle the link status,
	 * recovery fatal error, etc.
	 */
	sc->sc_mfthread_switch = 1;
	if (sc->sc_mf_thread == NULL)
		sc->sc_mf_thread = thread_create((caddr_t)NULL, 0,
		    ipw2100_thread, sc, 0, &p0, TS_RUN, minclsyspri);

	return (DDI_SUCCESS);

fail6:
	ddi_remove_intr(dip, 0, sc->sc_iblk);
fail5:
	ieee80211_detach(ic);

	mutex_destroy(&sc->sc_ilock);
	mutex_destroy(&sc->sc_tx_lock);
	mutex_destroy(&sc->sc_mflock);
	mutex_destroy(&sc->sc_resched_lock);
	cv_destroy(&sc->sc_mfthread_cv);
	cv_destroy(&sc->sc_tx_cond);
	cv_destroy(&sc->sc_cmd_cond);
	cv_destroy(&sc->sc_fw_cond);
fail4:
	ipw2100_ring_free(sc);
fail3:
	ddi_regs_map_free(&sc->sc_ioh);
fail2:
	ddi_soft_state_free(ipw2100_ssp, instance);
fail1:
	return (err);
}

int
ipw2100_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
	struct ipw2100_softc	*sc =
	    ddi_get_soft_state(ipw2100_ssp, ddi_get_instance(dip));
	int err;

	ASSERT(sc != NULL);

	if (cmd != DDI_DETACH)
		return (DDI_FAILURE);

	/*
	 * Destroy the mf_thread
	 */
	mutex_enter(&sc->sc_mflock);
	sc->sc_mfthread_switch = 0;
	while (sc->sc_mf_thread != NULL) {
		if (cv_wait_sig(&sc->sc_mfthread_cv, &sc->sc_mflock) == 0)
			break;
	}
	mutex_exit(&sc->sc_mflock);

	/*
	 * Unregiste from the MAC layer subsystem
	 */
	err = mac_unregister(sc->sc_ic.ic_mach);
	if (err != DDI_SUCCESS)
		return (err);

	ddi_remove_intr(dip, 0, sc->sc_iblk);

	/*
	 * destroy the cv
	 */
	mutex_destroy(&sc->sc_ilock);
	mutex_destroy(&sc->sc_tx_lock);
	mutex_destroy(&sc->sc_mflock);
	mutex_destroy(&sc->sc_resched_lock);
	cv_destroy(&sc->sc_mfthread_cv);
	cv_destroy(&sc->sc_tx_cond);
	cv_destroy(&sc->sc_cmd_cond);
	cv_destroy(&sc->sc_fw_cond);

	/*
	 * detach ieee80211
	 */
	ieee80211_detach(&sc->sc_ic);

	(void) ipw2100_free_firmware(sc);
	ipw2100_ring_free(sc);

	ddi_regs_map_free(&sc->sc_ioh);
	ddi_remove_minor_node(dip, NULL);
	ddi_soft_state_free(ipw2100_ssp, ddi_get_instance(dip));

	return (DDI_SUCCESS);
}

/* ARGSUSED */
int
ipw2100_reset(dev_info_t *dip, ddi_reset_cmd_t cmd)
{
	struct ipw2100_softc	*sc =
	    ddi_get_soft_state(ipw2100_ssp, ddi_get_instance(dip));
	ASSERT(sc != NULL);

	ipw2100_stop(sc);

	return (DDI_SUCCESS);
}

static void
ipw2100_tables_init(struct ipw2100_softc *sc)
{
	sc->sc_table1_base = ipw2100_csr_get32(sc, IPW2100_CSR_TABLE1_BASE);
	sc->sc_table2_base = ipw2100_csr_get32(sc, IPW2100_CSR_TABLE2_BASE);
}

static void
ipw2100_stop(struct ipw2100_softc *sc)
{
	struct ieee80211com	*ic = &sc->sc_ic;

	ipw2100_master_stop(sc);
	ipw2100_csr_put32(sc, IPW2100_CSR_RST, IPW2100_RST_SW_RESET);
	sc->sc_flags &= ~IPW2100_FLAG_FW_INITED;

	ieee80211_new_state(ic, IEEE80211_S_INIT, -1);
}

static int
ipw2100_config(struct ipw2100_softc *sc)
{
	struct ieee80211com		*ic = &sc->sc_ic;
	struct ipw2100_security		sec;
	struct ipw2100_wep_key		wkey;
	struct ipw2100_scan_options	sopt;
	struct ipw2100_configuration	cfg;
	uint32_t			data;
	int				err, i;

	/*
	 * operation mode
	 */
	switch (ic->ic_opmode) {
	case IEEE80211_M_STA:
	case IEEE80211_M_HOSTAP:
		data = LE_32(IPW2100_MODE_BSS);
		break;

	case IEEE80211_M_IBSS:
	case IEEE80211_M_AHDEMO:
		data = LE_32(IPW2100_MODE_IBSS);
		break;
	}

	IPW2100_DBG(IPW2100_DBG_WIFI, (sc->sc_dip, CE_CONT,
	    "ipw2100_config(): Setting mode to %u\n", LE_32(data)));

	err = ipw2100_cmd(sc, IPW2100_CMD_SET_MODE,
	    &data, sizeof (data));
	if (err != DDI_SUCCESS)
		return (err);

	/*
	 * operation channel if IBSS or MONITOR
	 */
	if (ic->ic_opmode == IEEE80211_M_IBSS) {

		data = LE_32(ieee80211_chan2ieee(ic, ic->ic_ibss_chan));

		IPW2100_DBG(IPW2100_DBG_WIFI, (sc->sc_dip, CE_CONT,
		    "ipw2100_config(): Setting channel to %u\n", LE_32(data)));

		err = ipw2100_cmd(sc, IPW2100_CMD_SET_CHANNEL,
		    &data, sizeof (data));
		if (err != DDI_SUCCESS)
			return (err);
	}

	/*
	 * set MAC address
	 */
	IPW2100_DBG(IPW2100_DBG_WIFI, (sc->sc_dip, CE_CONT,
	    "ipw2100_config(): Setting MAC address to "
	    "%02x:%02x:%02x:%02x:%02x:%02x\n",
	    ic->ic_macaddr[0], ic->ic_macaddr[1], ic->ic_macaddr[2],
	    ic->ic_macaddr[3], ic->ic_macaddr[4], ic->ic_macaddr[5]));
	err = ipw2100_cmd(sc, IPW2100_CMD_SET_MAC_ADDRESS, ic->ic_macaddr,
	    IEEE80211_ADDR_LEN);
	if (err != DDI_SUCCESS)
		return (err);

	/*
	 * configuration capabilities
	 */
	cfg.flags = IPW2100_CFG_BSS_MASK | IPW2100_CFG_IBSS_MASK |
	    IPW2100_CFG_PREAMBLE_AUTO | IPW2100_CFG_802_1x_ENABLE;
	if (ic->ic_opmode == IEEE80211_M_IBSS)
		cfg.flags |= IPW2100_CFG_IBSS_AUTO_START;
	if (sc->if_flags & IFF_PROMISC)
		cfg.flags |= IPW2100_CFG_PROMISCUOUS;
	cfg.flags	= LE_32(cfg.flags);
	cfg.bss_chan	= LE_32(sc->sc_chmask >> 1);
	cfg.ibss_chan	= LE_32(sc->sc_chmask >> 1);

	IPW2100_DBG(IPW2100_DBG_WIFI, (sc->sc_dip, CE_CONT,
	    "ipw2100_config(): Setting configuration to 0x%x\n",
	    LE_32(cfg.flags)));

	err = ipw2100_cmd(sc, IPW2100_CMD_SET_CONFIGURATION,
	    &cfg, sizeof (cfg));

	if (err != DDI_SUCCESS)
		return (err);

	/*
	 * set 802.11 Tx rates
	 */
	data = LE_32(0x3);  /* 1, 2 */
	IPW2100_DBG(IPW2100_DBG_WIFI, (sc->sc_dip, CE_CONT,
	    "ipw2100_config(): Setting 802.11 Tx rates to 0x%x\n",
	    LE_32(data)));
	err = ipw2100_cmd(sc, IPW2100_CMD_SET_BASIC_TX_RATES,
	    &data, sizeof (data));
	if (err != DDI_SUCCESS)
		return (err);

	/*
	 * set 802.11b Tx rates
	 */
	data = LE_32(0xf);  /* 1, 2, 5.5, 11 */
	IPW2100_DBG(IPW2100_DBG_WIFI, (sc->sc_dip, CE_CONT,
	    "ipw2100_config(): Setting 802.11b Tx rates to 0x%x\n",
	    LE_32(data)));
	err = ipw2100_cmd(sc, IPW2100_CMD_SET_TX_RATES, &data, sizeof (data));
	if (err != DDI_SUCCESS)
		return (err);

	/*
	 * set power mode
	 */
	data = LE_32(IPW2100_POWER_MODE_CAM);
	IPW2100_DBG(IPW2100_DBG_WIFI, (sc->sc_dip, CE_CONT,
	    "ipw2100_config(): Setting power mode to %u\n", LE_32(data)));
	err = ipw2100_cmd(sc, IPW2100_CMD_SET_POWER_MODE, &data, sizeof (data));
	if (err != DDI_SUCCESS)
		return (err);

	/*
	 * set power index
	 */
	if (ic->ic_opmode == IEEE80211_M_IBSS) {
		data = LE_32(32);
		IPW2100_DBG(IPW2100_DBG_WIFI, (sc->sc_dip, CE_CONT,
		    "ipw2100_config(): Setting Tx power index to %u\n",
		    LE_32(data)));
		err = ipw2100_cmd(sc, IPW2100_CMD_SET_TX_POWER_INDEX,
		    &data, sizeof (data));
		if (err != DDI_SUCCESS)
			return (err);
	}

	/*
	 * set RTS threshold
	 */
	ic->ic_rtsthreshold = 2346;
	data = LE_32(ic->ic_rtsthreshold);
	IPW2100_DBG(IPW2100_DBG_WIFI, (sc->sc_dip, CE_CONT,
	    "ipw2100_config(): Setting RTS threshold to %u\n", LE_32(data)));
	err = ipw2100_cmd(sc, IPW2100_CMD_SET_RTS_THRESHOLD,
	    &data, sizeof (data));
	if (err != DDI_SUCCESS)
		return (err);

	/*
	 * set frag threshold
	 */
	ic->ic_fragthreshold = 2346;
	data = LE_32(ic->ic_fragthreshold);
	IPW2100_DBG(IPW2100_DBG_WIFI, (sc->sc_dip, CE_CONT,
	    "ipw2100_config(): Setting frag threshold to %u\n", LE_32(data)));
	err = ipw2100_cmd(sc, IPW2100_CMD_SET_FRAG_THRESHOLD,
	    &data, sizeof (data));
	if (err != DDI_SUCCESS)
		return (err);

	/*
	 * set ESSID
	 */
	IPW2100_DBG(IPW2100_DBG_WIFI, (sc->sc_dip, CE_CONT,
	    "ipw2100_config(): Setting ESSID to %u, ESSID[0]%c\n",
	    ic->ic_des_esslen, ic->ic_des_essid[0]));
	err = ipw2100_cmd(sc, IPW2100_CMD_SET_ESSID,
	    ic->ic_des_essid, ic->ic_des_esslen);
	if (err != DDI_SUCCESS)
		return (err);

	/*
	 * no mandatory BSSID
	 */
	err = ipw2100_cmd(sc, IPW2100_CMD_SET_MANDATORY_BSSID, NULL, 0);
	if (err != DDI_SUCCESS)
		return (err);

	/*
	 * set BSSID, if any
	 */
	if (ic->ic_flags & IEEE80211_F_DESBSSID) {
		IPW2100_DBG(IPW2100_DBG_WIFI, (sc->sc_dip, CE_CONT,
		    "ipw2100_config(): Setting BSSID to %u\n",
		    IEEE80211_ADDR_LEN));
		err = ipw2100_cmd(sc, IPW2100_CMD_SET_DESIRED_BSSID,
		    ic->ic_des_bssid, IEEE80211_ADDR_LEN);
		if (err != DDI_SUCCESS)
			return (err);
	}

	/*
	 * set security information
	 */
	(void) memset(&sec, 0, sizeof (sec));
	/*
	 * use the value set to ic_bss to retrieve current sharedmode
	 */
	sec.authmode = (ic->ic_bss->in_authmode == WL_SHAREDKEY) ?
	    IPW2100_AUTH_SHARED : IPW2100_AUTH_OPEN;
	sec.ciphers = LE_32(IPW2100_CIPHER_NONE);
	IPW2100_DBG(IPW2100_DBG_WIFI, (sc->sc_dip, CE_CONT,
	    "ipw2100_config(): Setting authmode to %u\n", sec.authmode));
	err = ipw2100_cmd(sc, IPW2100_CMD_SET_SECURITY_INFORMATION,
	    &sec, sizeof (sec));
	if (err != DDI_SUCCESS)
		return (err);

	/*
	 * set WEP if any
	 */
	if (ic->ic_flags & IEEE80211_F_PRIVACY) {
		for (i = 0; i < IEEE80211_WEP_NKID; i++) {
			if (ic->ic_nw_keys[i].wk_keylen == 0)
				continue;
			wkey.idx = (uint8_t)i;
			wkey.len = ic->ic_nw_keys[i].wk_keylen;
			(void) memset(wkey.key, 0, sizeof (wkey.key));
			if (ic->ic_nw_keys[i].wk_keylen)
				(void) memcpy(wkey.key,
				    ic->ic_nw_keys[i].wk_key,
				    ic->ic_nw_keys[i].wk_keylen);
			err = ipw2100_cmd(sc, IPW2100_CMD_SET_WEP_KEY,
			    &wkey, sizeof (wkey));
			if (err != DDI_SUCCESS)
				return (err);
		}
		data = LE_32(ic->ic_def_txkey);
		err = ipw2100_cmd(sc, IPW2100_CMD_SET_WEP_KEY_INDEX,
		    &data, sizeof (data));
		if (err != DDI_SUCCESS)
			return (err);
	}

	/*
	 * turn on WEP
	 */
	data = LE_32((ic->ic_flags & IEEE80211_F_PRIVACY) ? 0x8 : 0);
	IPW2100_DBG(IPW2100_DBG_WIFI, (sc->sc_dip, CE_CONT,
	    "ipw2100_config(): Setting WEP flags to %u\n", LE_32(data)));
	err = ipw2100_cmd(sc, IPW2100_CMD_SET_WEP_FLAGS, &data, sizeof (data));
	if (err != DDI_SUCCESS)
		return (err);

	/*
	 * set beacon interval if IBSS or HostAP
	 */
	if (ic->ic_opmode == IEEE80211_M_IBSS ||
	    ic->ic_opmode == IEEE80211_M_HOSTAP) {

		data = LE_32(ic->ic_lintval);
		IPW2100_DBG(IPW2100_DBG_WIFI, (sc->sc_dip, CE_CONT,
		    "ipw2100_config(): Setting beacon interval to %u\n",
		    LE_32(data)));
		err = ipw2100_cmd(sc, IPW2100_CMD_SET_BEACON_INTERVAL,
		    &data, sizeof (data));
		if (err != DDI_SUCCESS)
			return (err);
	}

	/*
	 * set scan options
	 */
	sopt.flags = LE_32(0);
	sopt.channels = LE_32(sc->sc_chmask >> 1);
	err = ipw2100_cmd(sc, IPW2100_CMD_SET_SCAN_OPTIONS,
	    &sopt, sizeof (sopt));
	if (err != DDI_SUCCESS)
		return (err);

en_adapter:

	IPW2100_DBG(IPW2100_DBG_WIFI, (sc->sc_dip, CE_CONT,
	    "ipw2100_config(): Enabling adapter\n"));

	return (ipw2100_cmd(sc, IPW2100_CMD_ENABLE, NULL, 0));
}

static int
ipw2100_cmd(struct ipw2100_softc *sc, uint32_t type, void *buf, size_t len)
{
	struct ipw2100_bd	*txbd;
	clock_t			clk;
	uint32_t		idx;

	/*
	 * prepare command buffer
	 */
	sc->sc_cmd->type = LE_32(type);
	sc->sc_cmd->subtype = LE_32(0);
	sc->sc_cmd->seq = LE_32(0);
	/*
	 * copy data if any
	 */
	if (len && buf)
		(void) memcpy(sc->sc_cmd->data, buf, len);
	sc->sc_cmd->len = LE_32(len);

	/*
	 * get host & device descriptor to submit command
	 */
	mutex_enter(&sc->sc_tx_lock);

	IPW2100_DBG(IPW2100_DBG_RING, (sc->sc_dip, CE_CONT,
	    "ipw2100_cmd(): tx-free=%d\n", sc->sc_tx_free));

	/*
	 * command need 1 descriptor
	 */
	while (sc->sc_tx_free < 1)  {
		sc->sc_flags |= IPW2100_FLAG_CMD_WAIT;
		cv_wait(&sc->sc_tx_cond, &sc->sc_tx_lock);
	}
	idx = sc->sc_tx_cur;

	IPW2100_DBG(IPW2100_DBG_RING, (sc->sc_dip, CE_CONT,
	    "ipw2100_cmd(): tx-cur=%d\n", idx));

	sc->sc_done = 0;

	txbd		= &sc->sc_txbd[idx];
	txbd->phyaddr	= LE_32(sc->sc_dma_cmd.dr_pbase);
	txbd->len	= LE_32(sizeof (struct ipw2100_cmd));
	txbd->flags	= IPW2100_BD_FLAG_TX_FRAME_COMMAND
	    | IPW2100_BD_FLAG_TX_LAST_FRAGMENT;
	txbd->nfrag	= 1;
	/*
	 * sync for device
	 */
	(void) ddi_dma_sync(sc->sc_dma_cmd.dr_hnd, 0,
	    sizeof (struct ipw2100_cmd), DDI_DMA_SYNC_FORDEV);
	(void) ddi_dma_sync(sc->sc_dma_txbd.dr_hnd,
	    idx * sizeof (struct ipw2100_bd),
	    sizeof (struct ipw2100_bd), DDI_DMA_SYNC_FORDEV);

	/*
	 * ring move forward
	 */
	sc->sc_tx_cur = RING_FORWARD(sc->sc_tx_cur, 1, IPW2100_NUM_TXBD);
	sc->sc_tx_free--;
	ipw2100_csr_put32(sc, IPW2100_CSR_TX_WRITE_INDEX, sc->sc_tx_cur);
	mutex_exit(&sc->sc_tx_lock);

	/*
	 * wait for command done
	 */
	mutex_enter(&sc->sc_ilock);
	while (sc->sc_done == 0) {
		/*
		 * pending for the response
		 */
		clk = ddi_get_lbolt() + drv_usectohz(1000000);  /* 1 second */
		if (cv_timedwait(&sc->sc_cmd_cond, &sc->sc_ilock, clk) < 0)
			break;
	}
	mutex_exit(&sc->sc_ilock);

	IPW2100_DBG(IPW2100_DBG_RING, (sc->sc_dip, CE_CONT,
	    "ipw2100_cmd(): cmd-done=%s\n", sc->sc_done ? "yes" : "no"));

	if (sc->sc_done == 0)
		return (DDI_FAILURE);

	return (DDI_SUCCESS);
}

int
ipw2100_init(struct ipw2100_softc *sc)
{
	int	err;

	/*
	 * no firmware is available, return fail directly
	 */
	if (!(sc->sc_flags & IPW2100_FLAG_FW_CACHED)) {
		IPW2100_WARN((sc->sc_dip, CE_WARN,
		    "ipw2100_init(): no firmware is available\n"));
		return (DDI_FAILURE);
	}

	ipw2100_stop(sc);

	err = ipw2100_chip_reset(sc);
	if (err != DDI_SUCCESS) {
		IPW2100_WARN((sc->sc_dip, CE_WARN,
		    "ipw2100_init(): could not reset adapter\n"));
		goto fail;
	}

	/*
	 * load microcode
	 */
	err = ipw2100_load_uc(sc);
	if (err != DDI_SUCCESS) {
		IPW2100_WARN((sc->sc_dip, CE_WARN,
		    "ipw2100_init(): could not load microcode, try again\n"));
		goto fail;
	}

	ipw2100_master_stop(sc);

	ipw2100_ring_hwsetup(sc);

	/*
	 * load firmware
	 */
	err = ipw2100_load_fw(sc);
	if (err != DDI_SUCCESS) {
		IPW2100_WARN((sc->sc_dip, CE_WARN,
		    "ipw2100_init(): could not load firmware, try again\n"));
		goto fail;
	}

	/*
	 * initialize tables
	 */
	ipw2100_tables_init(sc);
	ipw2100_table1_put32(sc, IPW2100_INFO_LOCK, 0);

	/*
	 * Hardware will be enabled after configuration
	 */
	err = ipw2100_config(sc);
	if (err != DDI_SUCCESS) {
		IPW2100_WARN((sc->sc_dip, CE_WARN,
		    "ipw2100_init(): device configuration failed\n"));
		goto fail;
	}

	delay(drv_usectohz(delay_config_stable));

	return (DDI_SUCCESS);

fail:
	ipw2100_stop(sc);

	return (err);
}

/*
 * get hardware configurations from EEPROM embedded within chip
 */
static void
ipw2100_hwconf_get(struct ipw2100_softc *sc)
{
	int		i;
	uint16_t	val;

	/*
	 * MAC address
	 */
	i = 0;
	val = ipw2100_rom_get16(sc, IPW2100_ROM_MAC + 0);
	sc->sc_macaddr[i++] = val >> 8;
	sc->sc_macaddr[i++] = val & 0xff;
	val = ipw2100_rom_get16(sc, IPW2100_ROM_MAC + 1);
	sc->sc_macaddr[i++] = val >> 8;
	sc->sc_macaddr[i++] = val & 0xff;
	val = ipw2100_rom_get16(sc, IPW2100_ROM_MAC + 2);
	sc->sc_macaddr[i++] = val >> 8;
	sc->sc_macaddr[i++] = val & 0xff;

	/*
	 * formatted MAC address string
	 */
	(void) snprintf(sc->sc_macstr, sizeof (sc->sc_macstr),
	    "%02x:%02x:%02x:%02x:%02x:%02x",
	    sc->sc_macaddr[0], sc->sc_macaddr[1],
	    sc->sc_macaddr[2], sc->sc_macaddr[3],
	    sc->sc_macaddr[4], sc->sc_macaddr[5]);

	/*
	 * channel mask
	 */
	val = ipw2100_rom_get16(sc, IPW2100_ROM_CHANNEL_LIST);
	if (val == 0)
		val = 0x7ff;
	sc->sc_chmask = val << 1;
	IPW2100_DBG(IPW2100_DBG_HWCAP, (sc->sc_dip, CE_CONT,
	    "ipw2100_hwconf_get(): channel-mask=0x%08x\n", sc->sc_chmask));

	/*
	 * radio switch
	 */
	val = ipw2100_rom_get16(sc, IPW2100_ROM_RADIO);
	if (val & 0x08)
		sc->sc_flags |= IPW2100_FLAG_HAS_RADIO_SWITCH;

	IPW2100_DBG(IPW2100_DBG_HWCAP, (sc->sc_dip, CE_CONT,
	    "ipw2100_hwconf_get(): has-radio-switch=%s(%u)\n",
	    (sc->sc_flags & IPW2100_FLAG_HAS_RADIO_SWITCH)?  "yes" : "no",
	    val));
}

/*
 * all ipw2100 interrupts will be masked by this routine
 */
static void
ipw2100_master_stop(struct ipw2100_softc *sc)
{
	uint32_t	tmp;
	int		ntries;

	/*
	 * disable interrupts
	 */
	ipw2100_csr_put32(sc, IPW2100_CSR_INTR_MASK, 0);

	ipw2100_csr_put32(sc, IPW2100_CSR_RST, IPW2100_RST_STOP_MASTER);
	for (ntries = 0; ntries < 50; ntries++) {
		if (ipw2100_csr_get32(sc, IPW2100_CSR_RST)
		    & IPW2100_RST_MASTER_DISABLED)
			break;
		drv_usecwait(10);
	}
	if (ntries == 50)
		IPW2100_WARN((sc->sc_dip, CE_WARN,
		    "ipw2100_master_stop(): timeout when stop master\n"));

	tmp = ipw2100_csr_get32(sc, IPW2100_CSR_RST);
	ipw2100_csr_put32(sc, IPW2100_CSR_RST,
	    tmp | IPW2100_RST_PRINCETON_RESET);

	sc->sc_flags &= ~IPW2100_FLAG_FW_INITED;
}

/*
 * all ipw2100 interrupts will be masked by this routine
 */
static int
ipw2100_chip_reset(struct ipw2100_softc *sc)
{
	int		ntries;
	uint32_t	tmp;

	ipw2100_master_stop(sc);

	/*
	 * move adatper to DO state
	 */
	tmp = ipw2100_csr_get32(sc, IPW2100_CSR_CTL);
	ipw2100_csr_put32(sc, IPW2100_CSR_CTL, tmp | IPW2100_CTL_INIT);

	/*
	 * wait for clock stabilization
	 */
	for (ntries = 0; ntries < 1000; ntries++) {
		if (ipw2100_csr_get32(sc, IPW2100_CSR_CTL)
		    & IPW2100_CTL_CLOCK_READY)
			break;
		drv_usecwait(200);
	}
	if (ntries == 1000)
		return (DDI_FAILURE);

	tmp = ipw2100_csr_get32(sc, IPW2100_CSR_RST);
	ipw2100_csr_put32(sc, IPW2100_CSR_RST, tmp | IPW2100_RST_SW_RESET);

	drv_usecwait(10);

	tmp = ipw2100_csr_get32(sc, IPW2100_CSR_CTL);
	ipw2100_csr_put32(sc, IPW2100_CSR_CTL, tmp | IPW2100_CTL_INIT);

	return (DDI_SUCCESS);
}

/*
 * get the radio status from IPW_CSR_IO, invoked by wificonfig/dladm
 */
int
ipw2100_get_radio(struct ipw2100_softc *sc)
{
	if (ipw2100_csr_get32(sc, IPW2100_CSR_IO) & IPW2100_IO_RADIO_DISABLED)
		return (0);
	else
		return (1);

}
/*
 * This function is used to get the statistic, invoked by wificonfig/dladm
 */
void
ipw2100_get_statistics(struct ipw2100_softc *sc)
{
	struct ieee80211com	*ic = &sc->sc_ic;
	uint32_t		addr, size, i;
	uint32_t		atbl[256], *datatbl;

	datatbl = atbl;

	if (!(sc->sc_flags & IPW2100_FLAG_FW_INITED)) {
		IPW2100_DBG(IPW2100_DBG_STATISTIC, (sc->sc_dip, CE_CONT,
		    "ipw2100_get_statistic(): fw doesn't download yet."));
		return;
	}

	ipw2100_csr_put32(sc, IPW2100_CSR_AUTOINC_ADDR, sc->sc_table1_base);

	size = ipw2100_csr_get32(sc, IPW2100_CSR_AUTOINC_DATA);
	atbl[0] = size;
	for (i = 1, ++datatbl; i < size; i++, datatbl++) {
		addr = ipw2100_csr_get32(sc, IPW2100_CSR_AUTOINC_DATA);
		*datatbl = ipw2100_imem_get32(sc, addr);
	}

	/*
	 * To retrieve the statistic information into proper places. There are
	 * lot of information.
	 */
	IPW2100_DBG(IPW2100_DBG_STATISTIC, (sc->sc_dip, CE_CONT,
	    "ipw2100_get_statistic(): \n"
	    "operating mode = %u\n"
	    "type of authentification= %u\n"
	    "average RSSI= %u\n"
	    "current channel = %d\n",
	    atbl[191], atbl[199], atbl[173], atbl[189]));
	/* WIFI_STAT_TX_FRAGS */
	ic->ic_stats.is_tx_frags = (uint32_t)atbl[2];
	/* WIFI_STAT_MCAST_TX = (all frame - unicast frame) */
	ic->ic_stats.is_tx_mcast = (uint32_t)atbl[2] - (uint32_t)atbl[3];
	/* WIFI_STAT_TX_RETRANS */
	ic->ic_stats.is_tx_retries = (uint32_t)atbl[42];
	/* WIFI_STAT_TX_FAILED */
	ic->ic_stats.is_tx_failed = (uint32_t)atbl[51];
	/* MAC_STAT_OBYTES */
	ic->ic_stats.is_tx_bytes = (uint32_t)atbl[41];
	/* WIFI_STAT_RX_FRAGS */
	ic->ic_stats.is_rx_frags = (uint32_t)atbl[61];
	/* WIFI_STAT_MCAST_RX */
	ic->ic_stats.is_rx_mcast = (uint32_t)atbl[71];
	/* MAC_STAT_IBYTES */
	ic->ic_stats.is_rx_bytes = (uint32_t)atbl[101];
	/* WIFI_STAT_ACK_FAILURE */
	ic->ic_stats.is_ack_failure = (uint32_t)atbl[59];
	/* WIFI_STAT_RTS_SUCCESS */
	ic->ic_stats.is_rts_success = (uint32_t)atbl[22];
}

/*
 * dma region alloc
 */
static int
ipw2100_dma_region_alloc(struct ipw2100_softc *sc,
    struct dma_region *dr, size_t size, uint_t dir, uint_t flags)
{
	dev_info_t	*dip = sc->sc_dip;
	int		err;

	IPW2100_DBG(IPW2100_DBG_DMA, (dip, CE_CONT,
	    "ipw2100_dma_region_alloc() name=%s size=%u\n",
	    dr->dr_name, size));

	err = ddi_dma_alloc_handle(dip, &ipw2100_dma_attr, DDI_DMA_SLEEP, NULL,
	    &dr->dr_hnd);
	if (err != DDI_SUCCESS) {
		IPW2100_DBG(IPW2100_DBG_DMA, (dip, CE_CONT,
		    "ipw2100_dma_region_alloc(): "
		    "ddi_dma_alloc_handle() failed\n"));
		goto fail0;
	}

	err = ddi_dma_mem_alloc(dr->dr_hnd, size, &ipw2100_dma_accattr,
	    flags, DDI_DMA_SLEEP, NULL, &dr->dr_base,
	    &dr->dr_size, &dr->dr_acc);
	if (err != DDI_SUCCESS) {
		IPW2100_DBG(IPW2100_DBG_DMA, (dip, CE_CONT,
		    "ipw2100_dma_region_alloc(): "
		    "ddi_dma_mem_alloc() failed\n"));
		goto fail1;
	}

	err = ddi_dma_addr_bind_handle(dr->dr_hnd, NULL,
	    dr->dr_base, dr->dr_size, dir | flags, DDI_DMA_SLEEP, NULL,
	    &dr->dr_cookie, &dr->dr_ccnt);
	if (err != DDI_DMA_MAPPED) {
		IPW2100_DBG(IPW2100_DBG_DMA, (dip, CE_CONT,
		    "ipw2100_dma_region_alloc(): "
		    "ddi_dma_addr_bind_handle() failed\n"));
		goto fail2;
	}

	if (dr->dr_ccnt != 1) {
		err = DDI_FAILURE;
		goto fail3;
	}
	dr->dr_pbase = dr->dr_cookie.dmac_address;

	IPW2100_DBG(IPW2100_DBG_DMA, (dip, CE_CONT,
	    "ipw2100_dma_region_alloc(): get physical-base=0x%08x\n",
	    dr->dr_pbase));

	return (DDI_SUCCESS);

fail3:
	(void) ddi_dma_unbind_handle(dr->dr_hnd);
fail2:
	ddi_dma_mem_free(&dr->dr_acc);
fail1:
	ddi_dma_free_handle(&dr->dr_hnd);
fail0:
	return (err);
}

static void
ipw2100_dma_region_free(struct dma_region *dr)
{
	(void) ddi_dma_unbind_handle(dr->dr_hnd);
	ddi_dma_mem_free(&dr->dr_acc);
	ddi_dma_free_handle(&dr->dr_hnd);
}

static int
ipw2100_ring_alloc(struct ipw2100_softc *sc)
{
	int	err, i;

	/*
	 * tx ring
	 */
	sc->sc_dma_txbd.dr_name = "ipw2100-tx-ring-bd";
	err = ipw2100_dma_region_alloc(sc, &sc->sc_dma_txbd,
	    IPW2100_TXBD_SIZE, DDI_DMA_WRITE, DDI_DMA_CONSISTENT);
	if (err != DDI_SUCCESS)
		goto fail0;
	/*
	 * tx bufs
	 */
	for (i = 0; i < IPW2100_NUM_TXBUF; i++) {
		sc->sc_dma_txbufs[i].dr_name = "ipw2100-tx-buf";
		err = ipw2100_dma_region_alloc(sc, &sc->sc_dma_txbufs[i],
		    IPW2100_TXBUF_SIZE, DDI_DMA_WRITE, DDI_DMA_STREAMING);
		if (err != DDI_SUCCESS) {
			while (i >= 0) {
				ipw2100_dma_region_free(&sc->sc_dma_txbufs[i]);
				i--;
			}
			goto fail1;
		}
	}
	/*
	 * rx ring
	 */
	sc->sc_dma_rxbd.dr_name = "ipw2100-rx-ring-bd";
	err = ipw2100_dma_region_alloc(sc, &sc->sc_dma_rxbd,
	    IPW2100_RXBD_SIZE, DDI_DMA_WRITE, DDI_DMA_CONSISTENT);
	if (err != DDI_SUCCESS)
		goto fail2;
	/*
	 * rx bufs
	 */
	for (i = 0; i < IPW2100_NUM_RXBUF; i++) {
		sc->sc_dma_rxbufs[i].dr_name = "ipw2100-rx-buf";
		err = ipw2100_dma_region_alloc(sc, &sc->sc_dma_rxbufs[i],
		    IPW2100_RXBUF_SIZE, DDI_DMA_READ, DDI_DMA_STREAMING);
		if (err != DDI_SUCCESS) {
			while (i >= 0) {
				ipw2100_dma_region_free(&sc->sc_dma_rxbufs[i]);
				i--;
			}
			goto fail3;
		}
	}
	/*
	 * status
	 */
	sc->sc_dma_status.dr_name = "ipw2100-rx-status";
	err = ipw2100_dma_region_alloc(sc, &sc->sc_dma_status,
	    IPW2100_STATUS_SIZE, DDI_DMA_READ, DDI_DMA_CONSISTENT);
	if (err != DDI_SUCCESS)
		goto fail4;
	/*
	 * command
	 */
	sc->sc_dma_cmd.dr_name = "ipw2100-cmd";
	err = ipw2100_dma_region_alloc(sc, &sc->sc_dma_cmd, IPW2100_CMD_SIZE,
	    DDI_DMA_WRITE, DDI_DMA_CONSISTENT);
	if (err != DDI_SUCCESS)
		goto fail5;

	return (DDI_SUCCESS);

fail5:
	ipw2100_dma_region_free(&sc->sc_dma_status);
fail4:
	for (i = 0; i < IPW2100_NUM_RXBUF; i++)
		ipw2100_dma_region_free(&sc->sc_dma_rxbufs[i]);
fail3:
	ipw2100_dma_region_free(&sc->sc_dma_rxbd);
fail2:
	for (i = 0; i < IPW2100_NUM_TXBUF; i++)
		ipw2100_dma_region_free(&sc->sc_dma_txbufs[i]);
fail1:
	ipw2100_dma_region_free(&sc->sc_dma_txbd);
fail0:
	return (err);
}

static void
ipw2100_ring_free(struct ipw2100_softc *sc)
{
	int	i;

	/*
	 * tx ring
	 */
	ipw2100_dma_region_free(&sc->sc_dma_txbd);
	/*
	 * tx buf
	 */
	for (i = 0; i < IPW2100_NUM_TXBUF; i++)
		ipw2100_dma_region_free(&sc->sc_dma_txbufs[i]);
	/*
	 * rx ring
	 */
	ipw2100_dma_region_free(&sc->sc_dma_rxbd);
	/*
	 * rx buf
	 */
	for (i = 0; i < IPW2100_NUM_RXBUF; i++)
		ipw2100_dma_region_free(&sc->sc_dma_rxbufs[i]);
	/*
	 * status
	 */
	ipw2100_dma_region_free(&sc->sc_dma_status);
	/*
	 * command
	 */
	ipw2100_dma_region_free(&sc->sc_dma_cmd);
}

static void
ipw2100_ring_reset(struct ipw2100_softc *sc)
{
	int	i;

	/*
	 * tx ring
	 */
	sc->sc_tx_cur   = 0;
	sc->sc_tx_free  = IPW2100_NUM_TXBD;
	sc->sc_txbd	= (struct ipw2100_bd *)sc->sc_dma_txbd.dr_base;
	for (i = 0; i < IPW2100_NUM_TXBUF; i++)
		sc->sc_txbufs[i] =
		    (struct ipw2100_txb *)sc->sc_dma_txbufs[i].dr_base;
	/*
	 * rx ring
	 */
	sc->sc_rx_cur   = 0;
	sc->sc_rx_free  = IPW2100_NUM_RXBD;
	sc->sc_status   = (struct ipw2100_status *)sc->sc_dma_status.dr_base;
	sc->sc_rxbd	= (struct ipw2100_bd *)sc->sc_dma_rxbd.dr_base;
	for (i = 0; i < IPW2100_NUM_RXBUF; i++) {
		sc->sc_rxbufs[i] =
		    (struct ipw2100_rxb *)sc->sc_dma_rxbufs[i].dr_base;
		/*
		 * initialize Rx buffer descriptors, both host and device
		 */
		sc->sc_rxbd[i].phyaddr  = LE_32(sc->sc_dma_rxbufs[i].dr_pbase);
		sc->sc_rxbd[i].len	= LE_32(sc->sc_dma_rxbufs[i].dr_size);
		sc->sc_rxbd[i].flags	= 0;
		sc->sc_rxbd[i].nfrag	= 1;
	}
	/*
	 * command
	 */
	sc->sc_cmd = (struct ipw2100_cmd *)sc->sc_dma_cmd.dr_base;
}

/*
 * tx, rx rings and command initialization
 */
static int
ipw2100_ring_init(struct ipw2100_softc *sc)
{
	int	err;

	err = ipw2100_ring_alloc(sc);
	if (err != DDI_SUCCESS)
		return (err);

	ipw2100_ring_reset(sc);

	return (DDI_SUCCESS);
}

static void
ipw2100_ring_hwsetup(struct ipw2100_softc *sc)
{
	ipw2100_ring_reset(sc);
	/*
	 * tx ring
	 */
	ipw2100_csr_put32(sc, IPW2100_CSR_TX_BD_BASE, sc->sc_dma_txbd.dr_pbase);
	ipw2100_csr_put32(sc, IPW2100_CSR_TX_BD_SIZE, IPW2100_NUM_TXBD);
	/*
	 * no new packet to transmit, tx-rd-index == tx-wr-index
	 */
	ipw2100_csr_put32(sc, IPW2100_CSR_TX_READ_INDEX, sc->sc_tx_cur);
	ipw2100_csr_put32(sc, IPW2100_CSR_TX_WRITE_INDEX, sc->sc_tx_cur);
	/*
	 * rx ring
	 */
	ipw2100_csr_put32(sc, IPW2100_CSR_RX_BD_BASE, sc->sc_dma_rxbd.dr_pbase);
	ipw2100_csr_put32(sc, IPW2100_CSR_RX_BD_SIZE, IPW2100_NUM_RXBD);
	/*
	 * all rx buffer are empty, rx-rd-index == 0 && rx-wr-index == N-1
	 */
	IPW2100_DBG(IPW2100_DBG_RING, (sc->sc_dip, CE_CONT,
	    "ipw2100_ring_hwsetup(): rx-cur=%u, backward=%u\n",
	    sc->sc_rx_cur, RING_BACKWARD(sc->sc_rx_cur, 1, IPW2100_NUM_RXBD)));
	ipw2100_csr_put32(sc, IPW2100_CSR_RX_READ_INDEX, sc->sc_rx_cur);
	ipw2100_csr_put32(sc, IPW2100_CSR_RX_WRITE_INDEX,
	    RING_BACKWARD(sc->sc_rx_cur, 1, IPW2100_NUM_RXBD));
	/*
	 * status
	 */
	ipw2100_csr_put32(sc, IPW2100_CSR_RX_STATUS_BASE,
	    sc->sc_dma_status.dr_pbase);
}

/*
 * ieee80211_new_state() is not be used, since the hardware can handle the
 * state transfer. Here, we just keep the status of the hardware notification
 * result.
 */
/* ARGSUSED */
static int
ipw2100_newstate(struct ieee80211com *ic, enum ieee80211_state state, int arg)
{
	struct ipw2100_softc	*sc = (struct ipw2100_softc *)ic;
	struct ieee80211_node	*in;
	uint8_t			macaddr[IEEE80211_ADDR_LEN];
	uint32_t		len;
	wifi_data_t		wd = { 0 };

	IPW2100_DBG(IPW2100_DBG_WIFI, (sc->sc_dip, CE_CONT,
	    "ipw2100_newstate(): %s -> %s\n",
	    ieee80211_state_name[ic->ic_state], ieee80211_state_name[state]));

	switch (state) {
	case IEEE80211_S_RUN:
		/*
		 * we only need to use BSSID as to find the node
		 */
		drv_usecwait(200); /* firmware needs a short delay here */
		len = IEEE80211_ADDR_LEN;
		(void) ipw2100_table2_getbuf(sc, IPW2100_INFO_CURRENT_BSSID,
		    macaddr, &len);

		in = ieee80211_find_node(&ic->ic_scan, macaddr);
		if (in == NULL)
			break;

		(void) ieee80211_sta_join(ic, in);
		ieee80211_node_authorize(in);

		/*
		 * We can send data now; update the fastpath with our
		 * current associated BSSID.
		 */
		if (ic->ic_flags & IEEE80211_F_PRIVACY)
			wd.wd_secalloc = WIFI_SEC_WEP;
		else
			wd.wd_secalloc = WIFI_SEC_NONE;
		wd.wd_opmode = ic->ic_opmode;
		IEEE80211_ADDR_COPY(wd.wd_bssid, ic->ic_bss->in_bssid);
		(void) mac_pdata_update(ic->ic_mach, &wd, sizeof (wd));

		break;

	case IEEE80211_S_INIT:
	case IEEE80211_S_SCAN:
	case IEEE80211_S_AUTH:
	case IEEE80211_S_ASSOC:
		break;
	}

	/*
	 * notify to update the link
	 */
	if ((ic->ic_state != IEEE80211_S_RUN) && (state == IEEE80211_S_RUN)) {
		/*
		 * previously disconnected and now connected
		 */
		sc->sc_linkstate = LINK_STATE_UP;
		sc->sc_flags |= IPW2100_FLAG_LINK_CHANGE;
	} else if ((ic->ic_state == IEEE80211_S_RUN) &&
	    (state != IEEE80211_S_RUN)) {
		/*
		 * previously connected andd now disconnected
		 */
		sc->sc_linkstate = LINK_STATE_DOWN;
		sc->sc_flags |= IPW2100_FLAG_LINK_CHANGE;
	}

	ic->ic_state = state;
	return (DDI_SUCCESS);
}

/*
 * GLD operations
 */
/* ARGSUSED */
static int
ipw2100_m_stat(void *arg, uint_t stat, uint64_t *val)
{
	ieee80211com_t	*ic = (ieee80211com_t *)arg;
	IPW2100_DBG(IPW2100_DBG_GLD, (((struct ipw2100_softc *)arg)->sc_dip,
	    CE_CONT,
	    "ipw2100_m_stat(): enter\n"));
	/*
	 * some of below statistic data are from hardware, some from net80211
	 */
	switch (stat) {
	case MAC_STAT_RBYTES:
		*val = ic->ic_stats.is_rx_bytes;
		break;
	case MAC_STAT_IPACKETS:
		*val = ic->ic_stats.is_rx_frags;
		break;
	case MAC_STAT_OBYTES:
		*val = ic->ic_stats.is_tx_bytes;
		break;
	case MAC_STAT_OPACKETS:
		*val = ic->ic_stats.is_tx_frags;
		break;
	/*
	 * Get below from hardware statistic, retrieve net80211 value once 1s
	 */
	case WIFI_STAT_TX_FRAGS:
	case WIFI_STAT_MCAST_TX:
	case WIFI_STAT_TX_FAILED:
	case WIFI_STAT_TX_RETRANS:
	case WIFI_STAT_RTS_SUCCESS:
	case WIFI_STAT_ACK_FAILURE:
	case WIFI_STAT_RX_FRAGS:
	case WIFI_STAT_MCAST_RX:
	/*
	 * Get blow information from net80211
	 */
	case WIFI_STAT_RTS_FAILURE:
	case WIFI_STAT_RX_DUPS:
	case WIFI_STAT_FCS_ERRORS:
	case WIFI_STAT_WEP_ERRORS:
		return (ieee80211_stat(ic, stat, val));
	/*
	 * need be supported in the future
	 */
	case MAC_STAT_IFSPEED:
	case MAC_STAT_NOXMTBUF:
	case MAC_STAT_IERRORS:
	case MAC_STAT_OERRORS:
	default:
		return (ENOTSUP);
	}
	return (0);
}

/* ARGSUSED */
static int
ipw2100_m_multicst(void *arg, boolean_t add, const uint8_t *mca)
{
	/* not supported */
	IPW2100_DBG(IPW2100_DBG_GLD, (((struct ipw2100_softc *)arg)->sc_dip,
	    CE_CONT,
	    "ipw2100_m_multicst(): enter\n"));

	return (DDI_SUCCESS);
}

/*
 * This thread function is used to handle the fatal error.
 */
static void
ipw2100_thread(struct ipw2100_softc *sc)
{
	struct ieee80211com	*ic = &sc->sc_ic;
	int32_t			nlstate;
	int			stat_cnt = 0;

	IPW2100_DBG(IPW2100_DBG_SOFTINT, (sc->sc_dip, CE_CONT,
	    "ipw2100_thread(): into ipw2100 thread--> %d\n",
	    sc->sc_linkstate));

	mutex_enter(&sc->sc_mflock);

	while (sc->sc_mfthread_switch) {
		/*
		 * notify the link state
		 */
		if (ic->ic_mach && (sc->sc_flags & IPW2100_FLAG_LINK_CHANGE)) {
			IPW2100_DBG(IPW2100_DBG_SOFTINT, (sc->sc_dip, CE_CONT,
			    "ipw2100_thread(): link status --> %d\n",
			    sc->sc_linkstate));

			sc->sc_flags &= ~IPW2100_FLAG_LINK_CHANGE;
			nlstate = sc->sc_linkstate;

			mutex_exit(&sc->sc_mflock);
			mac_link_update(ic->ic_mach, nlstate);
			mutex_enter(&sc->sc_mflock);
		}

		/*
		 * recovery interrupt fatal error
		 */
		if (ic->ic_mach &&
		    (sc->sc_flags & IPW2100_FLAG_HW_ERR_RECOVER)) {

			IPW2100_DBG(IPW2100_DBG_FATAL, (sc->sc_dip, CE_CONT,
			    "try to recover fatal hw error\n"));
			sc->sc_flags &= ~IPW2100_FLAG_HW_ERR_RECOVER;

			mutex_exit(&sc->sc_mflock);
			(void) ipw2100_init(sc); /* Force stat machine */
			delay(drv_usectohz(delay_fatal_recover));
			mutex_enter(&sc->sc_mflock);
		}

		/*
		 * get statistic, the value will be retrieved by m_stat
		 */
		if (stat_cnt == 10) {
			stat_cnt = 0; /* re-start */

			mutex_exit(&sc->sc_mflock);
			ipw2100_get_statistics(sc);
			mutex_enter(&sc->sc_mflock);
		} else
			stat_cnt++; /* until 1s */

		mutex_exit(&sc->sc_mflock);
		delay(drv_usectohz(delay_aux_thread));
		mutex_enter(&sc->sc_mflock);
	}
	sc->sc_mf_thread = NULL;
	cv_broadcast(&sc->sc_mfthread_cv);
	mutex_exit(&sc->sc_mflock);
}

static int
ipw2100_m_start(void *arg)
{
	struct ipw2100_softc	*sc = (struct ipw2100_softc *)arg;

	IPW2100_DBG(IPW2100_DBG_GLD, (sc->sc_dip, CE_CONT,
	    "ipw2100_m_start(): enter\n"));

	/*
	 * initialize ipw2100 hardware
	 */
	(void) ipw2100_init(sc);

	sc->sc_flags |= IPW2100_FLAG_RUNNING;

	/*
	 * fix KCF bug. - workaround, need to fix it in net80211
	 */
	(void) crypto_mech2id(SUN_CKM_RC4);

	return (DDI_SUCCESS);
}

static void
ipw2100_m_stop(void *arg)
{
	struct ipw2100_softc	*sc = (struct ipw2100_softc *)arg;

	IPW2100_DBG(IPW2100_DBG_GLD, (sc->sc_dip, CE_CONT,
	    "ipw2100_m_stop(): enter\n"));

	ipw2100_stop(sc);

	sc->sc_flags &= ~IPW2100_FLAG_RUNNING;
}

static int
ipw2100_m_unicst(void *arg, const uint8_t *macaddr)
{
	struct ipw2100_softc	*sc = (struct ipw2100_softc *)arg;
	struct ieee80211com	*ic = &sc->sc_ic;
	int			err;

	IPW2100_DBG(IPW2100_DBG_GLD, (sc->sc_dip, CE_CONT,
	    "ipw2100_m_unicst(): enter\n"));

	IPW2100_DBG(IPW2100_DBG_GLD, (sc->sc_dip, CE_CONT,
	    "ipw2100_m_unicst(): GLD setting MAC address to "
	    "%02x:%02x:%02x:%02x:%02x:%02x\n",
	    macaddr[0], macaddr[1], macaddr[2],
	    macaddr[3], macaddr[4], macaddr[5]));

	if (!IEEE80211_ADDR_EQ(ic->ic_macaddr, macaddr)) {
		IEEE80211_ADDR_COPY(ic->ic_macaddr, macaddr);

		if (sc->sc_flags & IPW2100_FLAG_RUNNING) {
			err = ipw2100_config(sc);
			if (err != DDI_SUCCESS) {
				IPW2100_WARN((sc->sc_dip, CE_WARN,
				    "ipw2100_m_unicst(): "
				    "device configuration failed\n"));
				goto fail;
			}
		}
	}

	return (DDI_SUCCESS);
fail:
	return (err);
}

static int
ipw2100_m_promisc(void *arg, boolean_t on)
{
	struct ipw2100_softc	*sc = (struct ipw2100_softc *)arg;
	int recfg, err;

	IPW2100_DBG(IPW2100_DBG_GLD, (sc->sc_dip, CE_CONT,
	    "ipw2100_m_promisc(): enter. "
	    "GLD setting promiscuous mode - %d\n", on));

	recfg = 0;
	if (on)
		if (!(sc->if_flags & IFF_PROMISC)) {
			sc->if_flags |= IFF_PROMISC;
			recfg = 1;
		}
	else
		if (sc->if_flags & IFF_PROMISC) {
			sc->if_flags &= ~IFF_PROMISC;
			recfg = 1;
		}

	if (recfg && (sc->sc_flags & IPW2100_FLAG_RUNNING)) {
		err = ipw2100_config(sc);
		if (err != DDI_SUCCESS) {
			IPW2100_WARN((sc->sc_dip, CE_WARN,
			    "ipw2100_m_promisc(): "
			    "device configuration failed\n"));
			goto fail;
		}
	}

	return (DDI_SUCCESS);
fail:
	return (err);
}

static mblk_t *
ipw2100_m_tx(void *arg, mblk_t *mp)
{
	struct ipw2100_softc	*sc = (struct ipw2100_softc *)arg;
	struct ieee80211com	*ic = &sc->sc_ic;
	mblk_t			*next;

	/*
	 * No data frames go out unless we're associated; this
	 * should not happen as the 802.11 layer does not enable
	 * the xmit queue until we enter the RUN state.
	 */
	if (ic->ic_state != IEEE80211_S_RUN) {
		IPW2100_DBG(IPW2100_DBG_GLD, (sc->sc_dip, CE_CONT,
		    "ipw2100_m_tx(): discard msg, ic_state = %u\n",
		    ic->ic_state));
		freemsgchain(mp);
		return (NULL);
	}

	while (mp != NULL) {
		next = mp->b_next;
		mp->b_next = NULL;
		if (ipw2100_send(ic, mp, IEEE80211_FC0_TYPE_DATA) !=
		    DDI_SUCCESS) {
			mp->b_next = next;
			break;
		}
		mp = next;
	}
	return (mp);
}

/* ARGSUSED */
static int
ipw2100_send(ieee80211com_t *ic, mblk_t *mp, uint8_t type)
{
	struct ipw2100_softc	*sc = (struct ipw2100_softc *)ic;
	struct ieee80211_node	*in;
	struct ieee80211_frame	wh, *wh_tmp;
	struct ieee80211_key	*k;
	uint8_t			*hdat;
	mblk_t			*m0, *m;
	size_t			cnt, off;
	struct ipw2100_bd	*txbd[2];
	struct ipw2100_txb	*txbuf;
	struct dma_region	*dr;
	struct ipw2100_hdr	*h;
	uint32_t		idx, bidx;
	int			err;

	ASSERT(mp->b_next == NULL);

	m0 = NULL;
	m = NULL;
	err = DDI_SUCCESS;

	IPW2100_DBG(IPW2100_DBG_GLD, (sc->sc_dip, CE_CONT,
	    "ipw2100_send(): enter\n"));

	if ((type & IEEE80211_FC0_TYPE_MASK) != IEEE80211_FC0_TYPE_DATA) {
		/*
		 * it is impossible to send non-data 802.11 frame in current
		 * ipw driver. Therefore, drop the package
		 */
		freemsg(mp);
		err = DDI_SUCCESS;
		goto fail0;
	}

	mutex_enter(&sc->sc_tx_lock);

	/*
	 * need 2 descriptors: 1 for SEND cmd parameter header,
	 * and the other for payload, i.e., 802.11 frame including 802.11
	 * frame header
	 */
	if (sc->sc_tx_free < 2) {
		mutex_enter(&sc->sc_resched_lock);
		IPW2100_DBG(IPW2100_DBG_RING, (sc->sc_dip, CE_WARN,
		    "ipw2100_send(): no enough descriptors(%d)\n",
		    sc->sc_tx_free));
		ic->ic_stats.is_tx_nobuf++; /* no enough buffer */
		sc->sc_flags |= IPW2100_FLAG_TX_SCHED;
		err = DDI_FAILURE;
		mutex_exit(&sc->sc_resched_lock);
		goto fail1;
	}
	IPW2100_DBG(IPW2100_DBG_RING, (sc->sc_dip, CE_CONT,
	    "ipw2100_send(): tx-free=%d,tx-curr=%d\n",
	    sc->sc_tx_free, sc->sc_tx_cur));

	wh_tmp = (struct ieee80211_frame *)mp->b_rptr;
	in = ieee80211_find_txnode(ic, wh_tmp->i_addr1);
	if (in == NULL) { /* can not find tx node, drop the package */
		freemsg(mp);
		err = DDI_SUCCESS;
		goto fail1;
	}
	in->in_inact = 0;
	(void) ieee80211_encap(ic, mp, in);
	ieee80211_free_node(in);

	if (wh_tmp->i_fc[1] & IEEE80211_FC1_WEP) {
		/*
		 * it is very bad that ieee80211_crypto_encap can only accept a
		 * single continuous buffer.
		 */
		/*
		 * allocate 32 more bytes is to be compatible with further
		 * ieee802.11i standard.
		 */
		m = allocb(msgdsize(mp) + 32, BPRI_MED);
		if (m == NULL) { /* can not alloc buf, drop this package */
			IPW2100_DBG(IPW2100_DBG_WIFI, (sc->sc_dip, CE_CONT,
			    "ipw2100_send(): msg allocation failed\n"));

			freemsg(mp);

			err = DDI_SUCCESS;
			goto fail1;
		}
		off = 0;
		m0 = mp;
		while (m0) {
			cnt = MBLKL(m0);
			if (cnt) {
				(void) memcpy(m->b_rptr + off, m0->b_rptr, cnt);
				off += cnt;
			}
			m0 = m0->b_cont;
		}
		m->b_wptr += off;
		IPW2100_DBG(IPW2100_DBG_WIFI, (sc->sc_dip, CE_CONT,
		    "ipw2100_send(): "
		    "Encrypting 802.11 frame started, %d, %d\n",
		    msgdsize(mp), MBLKL(mp)));
		k = ieee80211_crypto_encap(ic, m);
		if (k == NULL) { /* can not get the key, drop packages */
			IPW2100_DBG(IPW2100_DBG_WIFI, (sc->sc_dip, CE_CONT,
			    "ipw2100_send(): "
			    "Encrypting 802.11 frame failed\n"));

			freemsg(mp);
			err = DDI_SUCCESS;
			goto fail2;
		}
		IPW2100_DBG(IPW2100_DBG_WIFI, (sc->sc_dip, CE_CONT,
		    "ipw2100_send(): "
		    "Encrypting 802.11 frame finished, %d, %d, k=0x%08x\n",
		    msgdsize(mp), MBLKL(mp), k->wk_flags));
	}

	/*
	 * header descriptor
	 */
	idx = sc->sc_tx_cur;
	txbd[0]  = &sc->sc_txbd[idx];
	if ((idx & 1) == 0)
		bidx = idx / 2;
	sc->sc_tx_cur = RING_FORWARD(sc->sc_tx_cur, 1, IPW2100_NUM_TXBD);
	sc->sc_tx_free--;

	/*
	 * payload descriptor
	 */
	idx = sc->sc_tx_cur;
	txbd[1]  = &sc->sc_txbd[idx];
	if ((idx & 1) == 0)
		bidx = idx / 2;
	sc->sc_tx_cur = RING_FORWARD(sc->sc_tx_cur, 1, IPW2100_NUM_TXBD);
	sc->sc_tx_free--;

	/*
	 * one buffer, SEND cmd header and payload buffer
	 */
	txbuf = sc->sc_txbufs[bidx];
	dr = &sc->sc_dma_txbufs[bidx];

	/*
	 * extract 802.11 header from message, fill wh from m0
	 */
	hdat = (uint8_t *)&wh;
	off = 0;
	if (m)
		m0 = m;
	else
		m0 = mp;
	while (off < sizeof (wh)) {
		cnt = MBLKL(m0);
		if (cnt > (sizeof (wh) - off))
			cnt = sizeof (wh) - off;
		if (cnt) {
			(void) memcpy(hdat + off, m0->b_rptr, cnt);
			off += cnt;
			m0->b_rptr += cnt;
		}
		else
			m0 = m0->b_cont;
	}

	/*
	 * prepare SEND cmd header
	 */
	h		= &txbuf->txb_hdr;
	h->type		= LE_32(IPW2100_CMD_SEND);
	h->subtype	= LE_32(0);
	h->encrypted    = ic->ic_flags & IEEE80211_F_PRIVACY ? 1 : 0;
	h->encrypt	= 0;
	h->keyidx	= 0;
	h->keysz	= 0;
	h->fragsz	= LE_16(0);
	IEEE80211_ADDR_COPY(h->saddr, wh.i_addr2);
	if (ic->ic_opmode == IEEE80211_M_STA)
		IEEE80211_ADDR_COPY(h->daddr, wh.i_addr3);
	else
		IEEE80211_ADDR_COPY(h->daddr, wh.i_addr1);

	/*
	 * extract payload from message into tx data buffer
	 */
	off = 0;
	while (m0) {
		cnt = MBLKL(m0);
		if (cnt) {
			(void) memcpy(&txbuf->txb_dat[off], m0->b_rptr, cnt);
			off += cnt;
		}
		m0 = m0->b_cont;
	}

	/*
	 * fill SEND cmd header descriptor
	 */
	txbd[0]->phyaddr = LE_32(dr->dr_pbase +
	    OFFSETOF(struct ipw2100_txb, txb_hdr));
	txbd[0]->len	= LE_32(sizeof (struct ipw2100_hdr));
	txbd[0]->flags	= IPW2100_BD_FLAG_TX_FRAME_802_3 |
	    IPW2100_BD_FLAG_TX_NOT_LAST_FRAGMENT;
	txbd[0]->nfrag	= 2;
	/*
	 * fill payload descriptor
	 */
	txbd[1]->phyaddr = LE_32(dr->dr_pbase +
	    OFFSETOF(struct ipw2100_txb, txb_dat[0]));
	txbd[1]->len	= LE_32(off);
	txbd[1]->flags	= IPW2100_BD_FLAG_TX_FRAME_802_3 |
	    IPW2100_BD_FLAG_TX_LAST_FRAGMENT;
	txbd[1]->nfrag	= 0;

	/*
	 * dma sync
	 */
	(void) ddi_dma_sync(dr->dr_hnd, 0, sizeof (struct ipw2100_txb),
	    DDI_DMA_SYNC_FORDEV);
	(void) ddi_dma_sync(sc->sc_dma_txbd.dr_hnd,
	    (txbd[0] - sc->sc_txbd) * sizeof (struct ipw2100_bd),
	    sizeof (struct ipw2100_bd), DDI_DMA_SYNC_FORDEV);
	/*
	 * since txbd[1] may not be successive to txbd[0] due to the ring
	 * organization, another dma_sync is needed to simplify the logic
	 */
	(void) ddi_dma_sync(sc->sc_dma_txbd.dr_hnd,
	    (txbd[1] - sc->sc_txbd) * sizeof (struct ipw2100_bd),
	    sizeof (struct ipw2100_bd), DDI_DMA_SYNC_FORDEV);
	/*
	 * update txcur
	 */
	ipw2100_csr_put32(sc, IPW2100_CSR_TX_WRITE_INDEX, sc->sc_tx_cur);

	if (mp) /* success, free the original message */
		freemsg(mp);
fail2:
	if (m)
		freemsg(m);
fail1:
	mutex_exit(&sc->sc_tx_lock);
fail0:
	IPW2100_DBG(IPW2100_DBG_GLD, (sc->sc_dip, CE_CONT,
	    "ipw2100_send(): exit - err=%d\n", err));

	return (err);
}

/*
 * IOCTL Handler
 */
#define	IEEE80211_IOCTL_REQUIRED	(1)
#define	IEEE80211_IOCTL_NOT_REQUIRED	(0)
static void
ipw2100_m_ioctl(void *arg, queue_t *q, mblk_t *m)
{
	struct ipw2100_softc	*sc  = (struct ipw2100_softc *)arg;
	struct ieee80211com	*ic = &sc->sc_ic;
	int			err;

	IPW2100_DBG(IPW2100_DBG_GLD, (sc->sc_dip, CE_CONT,
	    "ipw2100_m_ioctl(): enter\n"));

	/*
	 * check whether or not need to handle this in net80211
	 */
	if (ipw2100_ioctl(sc, q, m) == IEEE80211_IOCTL_NOT_REQUIRED)
		return; /* succes or fail */

	err = ieee80211_ioctl(ic, q, m);
	if (err == ENETRESET) {
		if (sc->sc_flags & IPW2100_FLAG_RUNNING) {
			(void) ipw2100_m_start(sc);
			(void) ieee80211_new_state(ic,
			    IEEE80211_S_SCAN, -1);
		}
	}
	if (err == ERESTART) {
		if (sc->sc_flags & IPW2100_FLAG_RUNNING)
			(void) ipw2100_chip_reset(sc);
	}
}

static int
ipw2100_ioctl(struct ipw2100_softc *sc, queue_t *q, mblk_t *m)
{
	struct iocblk	*iocp;
	uint32_t	len, ret, cmd;
	mblk_t		*m0;
	boolean_t	need_privilege;
	boolean_t	need_net80211;

	if (MBLKL(m) < sizeof (struct iocblk)) {
		IPW2100_DBG(IPW2100_DBG_IOCTL, (sc->sc_dip, CE_CONT,
		    "ipw2100_ioctl(): ioctl buffer too short, %u\n",
		    MBLKL(m)));
		miocnak(q, m, 0, EINVAL);
		return (IEEE80211_IOCTL_NOT_REQUIRED);
	}

	/*
	 * Validate the command
	 */
	iocp = (struct iocblk *)m->b_rptr;
	iocp->ioc_error = 0;
	cmd = iocp->ioc_cmd;
	need_privilege = B_TRUE;
	switch (cmd) {
	case WLAN_SET_PARAM:
	case WLAN_COMMAND:
		break;
	case WLAN_GET_PARAM:
		need_privilege = B_FALSE;
		break;
	default:
		IPW2100_DBG(IPW2100_DBG_IOCTL, (sc->sc_dip, CE_CONT,
		    "ieee80211_ioctl(): unknown cmd 0x%x", cmd));
		miocnak(q, m, 0, EINVAL);
		return (IEEE80211_IOCTL_NOT_REQUIRED);
	}

	if (need_privilege) {
		/*
		 * Check for specific net_config privilege on Solaris 10+.
		 * Otherwise just check for root access ...
		 */
		if (secpolicy_net_config != NULL)
			ret = secpolicy_net_config(iocp->ioc_cr, B_FALSE);
		else
			ret = drv_priv(iocp->ioc_cr);
		if (ret != 0) {
			miocnak(q, m, 0, ret);
			return (IEEE80211_IOCTL_NOT_REQUIRED);
		}
	}
	/*
	 * sanity check
	 */
	m0 = m->b_cont;
	if (iocp->ioc_count == 0 || iocp->ioc_count < sizeof (wldp_t) ||
	    m0 == NULL) {
		miocnak(q, m, 0, EINVAL);
		return (IEEE80211_IOCTL_NOT_REQUIRED);
	}
	/*
	 * assuming single data block
	 */
	if (m0->b_cont) {
		freemsg(m0->b_cont);
		m0->b_cont = NULL;
	}

	need_net80211 = B_FALSE;
	ret = ipw2100_getset(sc, m0, cmd, &need_net80211);
	if (!need_net80211) {
		len = msgdsize(m0);

		IPW2100_DBG(IPW2100_DBG_IOCTL, (sc->sc_dip, CE_CONT,
		    "ipw2100_ioctl(): go to call miocack with "
		    "ret = %d, len = %d\n", ret, len));
		miocack(q, m, len, ret);
		return (IEEE80211_IOCTL_NOT_REQUIRED);
	}

	/*
	 * IEEE80211_IOCTL_REQUIRED - need net80211 handle
	 */
	return (IEEE80211_IOCTL_REQUIRED);
}

static int
ipw2100_getset(struct ipw2100_softc *sc, mblk_t *m, uint32_t cmd,
	boolean_t *need_net80211)
{
	wldp_t		*infp, *outfp;
	uint32_t	id;
	int		ret; /* IEEE80211_IOCTL - handled by net80211 */

	infp = (wldp_t *)m->b_rptr;
	outfp = (wldp_t *)m->b_rptr;
	outfp->wldp_result = WL_NOTSUPPORTED;

	id = infp->wldp_id;
	IPW2100_DBG(IPW2100_DBG_IOCTL, (sc->sc_dip, CE_CONT,
	    "ipw2100_getset(): id = 0x%x\n", id));
	switch (id) {
	/*
	 * which is not supported by net80211, so it
	 * has to be handled from driver side
	 */
	case WL_RADIO:
		ret = ipw_wificfg_radio(sc, cmd, outfp);
		break;
	/*
	 * so far, drier doesn't support fix-rates
	 */
	case WL_DESIRED_RATES:
		ret = ipw_wificfg_desrates(outfp);
		break;
	/*
	 * current net80211 implementation clears the bssid while
	 * this command received, which will result in the all zero
	 * mac address for scan'ed AP which is just disconnected.
	 * This is a workaround solution until net80211 find a
	 * better method.
	 */
	case WL_DISASSOCIATE:
		ret = ipw_wificfg_disassoc(sc, outfp);
		break;
	default:
		/*
		 * The wifi IOCTL net80211 supported:
		 *	case WL_ESSID:
		 *	case WL_BSSID:
		 *	case WL_WEP_KEY_TAB:
		 *	case WL_WEP_KEY_ID:
		 *	case WL_AUTH_MODE:
		 *	case WL_ENCRYPTION:
		 *	case WL_BSS_TYPE:
		 *	case WL_ESS_LIST:
		 *	case WL_LINKSTATUS:
		 *	case WL_RSSI:
		 *	case WL_SCAN:
		 *	case WL_LOAD_DEFAULTS:
		 */

		/*
		 * When radio is off, need to ignore all ioctl.  What need to
		 * do is to check radio status firstly.  If radio is ON, pass
		 * it to net80211, otherwise, return to upper layer directly.
		 *
		 * Considering the WL_SUCCESS also means WL_CONNECTED for
		 * checking linkstatus, one exception for WL_LINKSTATUS is to
		 * let net80211 handle it.
		 */
		if ((ipw2100_get_radio(sc) == 0) &&
		    (id != WL_LINKSTATUS)) {

			IPW2100_REPORT((sc->sc_dip, CE_WARN,
			    "ipw: RADIO is OFF\n"));

			outfp->wldp_length = WIFI_BUF_OFFSET;
			outfp->wldp_result = WL_SUCCESS;
			ret = 0;
			break;
		}

		*need_net80211 = B_TRUE; /* let net80211 do the rest */
		return (0);
	}
	/*
	 * we will overwrite everything
	 */
	m->b_wptr = m->b_rptr + outfp->wldp_length;

	return (ret);
}

static int
ipw_wificfg_radio(struct ipw2100_softc *sc, uint32_t cmd, wldp_t *outfp)
{
	uint32_t	ret = ENOTSUP;

	switch (cmd) {
	case WLAN_GET_PARAM:
		*(wl_linkstatus_t *)(outfp->wldp_buf) = ipw2100_get_radio(sc);
		outfp->wldp_length = WIFI_BUF_OFFSET + sizeof (wl_linkstatus_t);
		outfp->wldp_result = WL_SUCCESS;
		ret = 0; /* command sucess */
		break;
	case WLAN_SET_PARAM:
	default:
		break;
	}
	return (ret);
}

static int
ipw_wificfg_desrates(wldp_t *outfp)
{
	/*
	 * return success, but with result NOTSUPPORTED
	 */
	outfp->wldp_length = WIFI_BUF_OFFSET;
	outfp->wldp_result = WL_NOTSUPPORTED;
	return (0);
}

static int
ipw_wificfg_disassoc(struct ipw2100_softc *sc, wldp_t *outfp)
{
	struct ieee80211com	*ic = &sc->sc_ic;

	/*
	 * init the state
	 */
	if (ic->ic_state != IEEE80211_S_INIT) {
		(void) ieee80211_new_state(ic, IEEE80211_S_INIT, -1);
	}

	/*
	 * return success always
	 */
	outfp->wldp_length = WIFI_BUF_OFFSET;
	outfp->wldp_result = WL_SUCCESS;
	return (0);
}
/* End of IOCTL Handler */

static void
ipw2100_fix_channel(struct ieee80211com *ic, mblk_t *m)
{
	struct ieee80211_frame	*wh;
	uint8_t			subtype;
	uint8_t			*frm, *efrm;

	wh = (struct ieee80211_frame *)m->b_rptr;

	if ((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) != IEEE80211_FC0_TYPE_MGT)
		return;

	subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK;

	if (subtype != IEEE80211_FC0_SUBTYPE_BEACON &&
	    subtype != IEEE80211_FC0_SUBTYPE_PROBE_RESP)
		return;

	/*
	 * assume the message contains only 1 block
	 */
	frm   = (uint8_t *)(wh + 1);
	efrm  = (uint8_t *)m->b_wptr;
	frm  += 12;  /* skip tstamp, bintval and capinfo fields */
	while (frm < efrm) {
		if (*frm == IEEE80211_ELEMID_DSPARMS) {
#if IEEE80211_CHAN_MAX < 255
			if (frm[2] <= IEEE80211_CHAN_MAX)
#endif
			{
				ic->ic_curchan = &ic->ic_sup_channels[frm[2]];
			}
		}
		frm += frm[1] + 2;
	}
}

static void
ipw2100_rcvpkt(struct ipw2100_softc *sc, struct ipw2100_status *status,
    uint8_t *rxbuf)
{
	struct ieee80211com	*ic = &sc->sc_ic;
	mblk_t			*m;
	struct ieee80211_frame	*wh = (struct ieee80211_frame *)rxbuf;
	struct ieee80211_node	*in;
	uint32_t		rlen;

	in = ieee80211_find_rxnode(ic, wh);
	rlen = LE_32(status->len);
	m = allocb(rlen, BPRI_MED);
	if (m) {
		(void) memcpy(m->b_wptr, rxbuf, rlen);
		m->b_wptr += rlen;
		if (ic->ic_state == IEEE80211_S_SCAN)
			ipw2100_fix_channel(ic, m);
		(void) ieee80211_input(ic, m, in, status->rssi, 0);
	} else
		IPW2100_WARN((sc->sc_dip, CE_WARN,
		    "ipw2100_rcvpkg(): cannot allocate receive message(%u)\n",
		    LE_32(status->len)));
	ieee80211_free_node(in);
}

static uint_t
ipw2100_intr(caddr_t arg)
{
	struct ipw2100_softc	*sc = (struct ipw2100_softc *)arg;
	uint32_t		ireg, ridx, len, i;
	struct ieee80211com	*ic = &sc->sc_ic;
	struct ipw2100_status	*status;
	uint8_t			*rxbuf;
	struct dma_region	*dr;
	uint32_t		state;
#if DEBUG
	struct ipw2100_bd *rxbd;
#endif

	ireg = ipw2100_csr_get32(sc, IPW2100_CSR_INTR);

	if (!(ireg & IPW2100_INTR_MASK_ALL))
		return (DDI_INTR_UNCLAIMED);

	/*
	 * mask all interrupts
	 */
	ipw2100_csr_put32(sc, IPW2100_CSR_INTR_MASK, 0);

	/*
	 * acknowledge all fired interrupts
	 */
	ipw2100_csr_put32(sc, IPW2100_CSR_INTR, ireg);

	IPW2100_DBG(IPW2100_DBG_INT, (sc->sc_dip, CE_CONT,
	    "ipw2100_intr(): interrupt is fired. int=0x%08x\n", ireg));

	if (ireg & IPW2100_INTR_MASK_ERR) {

		IPW2100_DBG(IPW2100_DBG_FATAL, (sc->sc_dip, CE_CONT,
		    "ipw2100_intr(): interrupt is fired, MASK = 0x%08x\n",
		    ireg));

		/*
		 * inform mfthread to recover hw error
		 */
		mutex_enter(&sc->sc_mflock);
		sc->sc_flags |= IPW2100_FLAG_HW_ERR_RECOVER;
		mutex_exit(&sc->sc_mflock);

	} else {

		/*
		 * FW intr
		 */
		if (ireg & IPW2100_INTR_FW_INIT_DONE) {
			mutex_enter(&sc->sc_ilock);
			sc->sc_flags |= IPW2100_FLAG_FW_INITED;
			cv_signal(&sc->sc_fw_cond);
			mutex_exit(&sc->sc_ilock);
		}

		/*
		 * RX intr
		 */
		if (ireg & IPW2100_INTR_RX_TRANSFER) {
			ridx = ipw2100_csr_get32(sc,
			    IPW2100_CSR_RX_READ_INDEX);

			for (; sc->sc_rx_cur != ridx;
			    sc->sc_rx_cur = RING_FORWARD(
			    sc->sc_rx_cur, 1, IPW2100_NUM_RXBD)) {

				i	= sc->sc_rx_cur;
				status	= &sc->sc_status[i];
				rxbuf	= &sc->sc_rxbufs[i]->rxb_dat[0];
				dr	= &sc->sc_dma_rxbufs[i];

				/*
				 * sync
				 */
				(void) ddi_dma_sync(sc->sc_dma_status.dr_hnd,
				    i * sizeof (struct ipw2100_status),
				    sizeof (struct ipw2100_status),
				    DDI_DMA_SYNC_FORKERNEL);
				(void) ddi_dma_sync(sc->sc_dma_rxbd.dr_hnd,
				    i * sizeof (struct ipw2100_bd),
				    sizeof (struct ipw2100_bd),
				    DDI_DMA_SYNC_FORKERNEL);
				(void) ddi_dma_sync(dr->dr_hnd, 0,
				    sizeof (struct ipw2100_rxb),
				    DDI_DMA_SYNC_FORKERNEL);
				IPW2100_DBG(IPW2100_DBG_INT,
				    (sc->sc_dip, CE_CONT,
				    "ipw2100_intr(): "
				    "status code=0x%04x, len=0x%08x, "
				    "flags=0x%02x, rssi=%02x\n",
				    LE_16(status->code), LE_32(status->len),
				    status->flags, status->rssi));
#if DEBUG
				rxbd	= &sc->sc_rxbd[i];
				IPW2100_DBG(IPW2100_DBG_INT,
				    (sc->sc_dip, CE_CONT,
				    "ipw2100_intr(): "
				    "rxbd,phyaddr=0x%08x, len=0x%08x, "
				    "flags=0x%02x,nfrag=%02x\n",
				    LE_32(rxbd->phyaddr), LE_32(rxbd->len),
				    rxbd->flags, rxbd->nfrag));
#endif
				switch (LE_16(status->code) & 0x0f) {
				/*
				 * command complete response
				 */
				case IPW2100_STATUS_CODE_COMMAND:
					mutex_enter(&sc->sc_ilock);
					sc->sc_done = 1;
					cv_signal(&sc->sc_cmd_cond);
					mutex_exit(&sc->sc_ilock);
					break;
				/*
				 * change state
				 */
				case IPW2100_STATUS_CODE_NEWSTATE:
					state = LE_32(*((uint32_t *)rxbuf));
					IPW2100_DBG(IPW2100_DBG_INT,
					    (sc->sc_dip, CE_CONT,
					    "ipw2100_intr(): "
					    "newstate,state=0x%x\n", state));

					switch (state) {
					case IPW2100_STATE_ASSOCIATED:
						ieee80211_new_state(ic,
						    IEEE80211_S_RUN, -1);
						break;
					case IPW2100_STATE_ASSOCIATION_LOST:
					case IPW2100_STATE_DISABLED:
						ieee80211_new_state(ic,
						    IEEE80211_S_INIT, -1);
						break;
					/*
					 * When radio is OFF, need a better
					 * scan approach to ensure scan
					 * result correct.
					 */
					case IPW2100_STATE_RADIO_DISABLED:
						IPW2100_REPORT((sc->sc_dip,
						    CE_WARN,
						    "ipw2100_intr(): "
						    "RADIO is OFF\n"));
						ipw2100_stop(sc);
						break;
					case IPW2100_STATE_SCAN_COMPLETE:
						ieee80211_cancel_scan(ic);
						break;
					case IPW2100_STATE_SCANNING:
						if (ic->ic_state !=
						    IEEE80211_S_RUN)
							ieee80211_new_state(ic,
							    IEEE80211_S_SCAN,
							    -1);
						ic->ic_flags |=
						    IEEE80211_F_SCAN;

						break;
					default:
						break;
					}
					break;
				case IPW2100_STATUS_CODE_DATA_802_11:
				case IPW2100_STATUS_CODE_DATA_802_3:
					ipw2100_rcvpkt(sc, status, rxbuf);
					break;
				case IPW2100_STATUS_CODE_NOTIFICATION:
					break;
				default:
					IPW2100_WARN((sc->sc_dip, CE_WARN,
					    "ipw2100_intr(): "
					    "unknown status code 0x%04x\n",
					    LE_16(status->code)));
					break;
				}
			}
			/*
			 * write sc_rx_cur backward 1 step to RX_WRITE_INDEX
			 */
			ipw2100_csr_put32(sc, IPW2100_CSR_RX_WRITE_INDEX,
			    RING_BACKWARD(sc->sc_rx_cur, 1, IPW2100_NUM_RXBD));
		}

		/*
		 * TX intr
		 */
		if (ireg & IPW2100_INTR_TX_TRANSFER) {
			mutex_enter(&sc->sc_tx_lock);
			ridx = ipw2100_csr_get32(sc, IPW2100_CSR_TX_READ_INDEX);
			len = RING_FLEN(RING_FORWARD(sc->sc_tx_cur,
			    sc->sc_tx_free, IPW2100_NUM_TXBD),
			    ridx, IPW2100_NUM_TXBD);
			sc->sc_tx_free += len;
			IPW2100_DBG(IPW2100_DBG_INT, (sc->sc_dip, CE_CONT,
			    "ipw2100_intr(): len=%d\n", len));
			mutex_exit(&sc->sc_tx_lock);

			mutex_enter(&sc->sc_resched_lock);
			if (len > 1 && (sc->sc_flags & IPW2100_FLAG_TX_SCHED)) {
				sc->sc_flags &= ~IPW2100_FLAG_TX_SCHED;
				mac_tx_update(ic->ic_mach);
			}
			mutex_exit(&sc->sc_resched_lock);
		}
	}

	/*
	 * enable all interrupts
	 */
	ipw2100_csr_put32(sc, IPW2100_CSR_INTR_MASK, IPW2100_INTR_MASK_ALL);

	return (DDI_INTR_CLAIMED);
}


/*
 * Module Loading Data & Entry Points
 */
DDI_DEFINE_STREAM_OPS(ipw2100_devops, nulldev, nulldev, ipw2100_attach,
    ipw2100_detach, ipw2100_reset, NULL, D_MP, NULL);

static struct modldrv ipw2100_modldrv = {
	&mod_driverops,
	ipw2100_ident,
	&ipw2100_devops
};

static struct modlinkage ipw2100_modlinkage = {
	MODREV_1,
	&ipw2100_modldrv,
	NULL
};

int
_init(void)
{
	int	status;

	status = ddi_soft_state_init(&ipw2100_ssp,
	    sizeof (struct ipw2100_softc), 1);
	if (status != DDI_SUCCESS)
		return (status);

	mac_init_ops(&ipw2100_devops, IPW2100_DRV_NAME);
	status = mod_install(&ipw2100_modlinkage);
	if (status != DDI_SUCCESS) {
		mac_fini_ops(&ipw2100_devops);
		ddi_soft_state_fini(&ipw2100_ssp);
	}

	return (status);
}

int
_fini(void)
{
	int status;

	status = mod_remove(&ipw2100_modlinkage);
	if (status == DDI_SUCCESS) {
		mac_fini_ops(&ipw2100_devops);
		ddi_soft_state_fini(&ipw2100_ssp);
	}

	return (status);
}

int
_info(struct modinfo *mip)
{
	return (mod_info(&ipw2100_modlinkage, mip));
}