/* * Copyright 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright(c) 2004 * Damien Bergamini . 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ipw2100.h" #include "ipw2100_impl.h" #include /* * kCF framework include files */ #include #include static void *ipw2100_ssp = NULL; static char ipw2100_ident[] = IPW2100_DRV_DESC; /* * 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); static int ipw2100_m_setprop(void *arg, const char *pr_name, mac_prop_id_t wldp_pr_num, uint_t wldp_length, const void *wldp_buf); static int ipw2100_m_getprop(void *arg, const char *pr_name, mac_prop_id_t wldp_pr_num, uint_t wldp_length, void *wldp_buf); static void ipw2100_m_propinfo(void *, const char *, mac_prop_id_t, mac_prop_info_handle_t); /* * 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); /* * Suspend / Resume operations */ static int ipw2100_cpr_suspend(struct ipw2100_softc *sc); static int ipw2100_cpr_resume(struct ipw2100_softc *sc); /* * Mac Call Back entries */ mac_callbacks_t ipw2100_m_callbacks = { MC_IOCTL | MC_SETPROP | MC_GETPROP | MC_PROPINFO, 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, NULL, NULL, NULL, ipw2100_m_setprop, ipw2100_m_getprop, ipw2100_m_propinfo }; /* * 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 * | IPW2100_DBG_BRUSSELS */ /* * 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; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: sc = ddi_get_soft_state(ipw2100_ssp, ddi_get_instance(dip)); if (sc == NULL) { err = DDI_FAILURE; goto fail1; } return (ipw2100_cpr_resume(sc)); default: 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, ®s, 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_bss->in_bssid); 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); switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: return (ipw2100_cpr_suspend(sc)); default: 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); /* * Unregister 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); } int ipw2100_cpr_suspend(struct ipw2100_softc *sc) { IPW2100_DBG(IPW2100_DBG_INIT, (sc->sc_dip, CE_CONT, "ipw2100_cpr_suspend(): enter\n")); /* * 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); /* * stop the hardware; this mask all interrupts */ ipw2100_stop(sc); sc->sc_flags &= ~IPW2100_FLAG_RUNNING; sc->sc_suspended = 1; (void) ipw2100_free_firmware(sc); ipw2100_ring_free(sc); return (DDI_SUCCESS); } int ipw2100_cpr_resume(struct ipw2100_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; dev_info_t *dip = sc->sc_dip; int err; IPW2100_DBG(IPW2100_DBG_INIT, (sc->sc_dip, CE_CONT, "ipw2100_cpr_resume(): enter\n")); /* * Reset the chip */ err = ipw2100_chip_reset(sc); if (err != DDI_SUCCESS) { IPW2100_WARN((dip, CE_WARN, "ipw2100_attach(): reset failed\n")); return (DDI_FAILURE); } /* * 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")); return (DDI_FAILURE); } /* * 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); /* * enable all interrupts */ sc->sc_suspended = 0; ipw2100_csr_put32(sc, IPW2100_CSR_INTR_MASK, IPW2100_INTR_MASK_ALL); /* * initialize ipw2100 hardware */ (void) ipw2100_init(sc); sc->sc_flags |= IPW2100_FLAG_RUNNING; return (DDI_SUCCESS); } /* * quiesce(9E) entry point. * This function is called when the system is single-threaded at high * PIL with preemption disabled. Therefore, this function must not be * blocked. * This function returns DDI_SUCCESS on success, or DDI_FAILURE on failure. * DDI_FAILURE indicates an error condition and should almost never happen. * Contributed by Juergen Keil, . */ static int ipw2100_quiesce(dev_info_t *dip) { struct ipw2100_softc *sc = ddi_get_soft_state(ipw2100_ssp, ddi_get_instance(dip)); if (sc == NULL) return (DDI_FAILURE); /* * No more blocking is allowed while we are in the * quiesce(9E) entry point. */ sc->sc_flags |= IPW2100_FLAG_QUIESCED; /* * Disable and mask all interrupts. */ 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; if (!(sc->sc_flags & IPW2100_FLAG_QUIESCED)) 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 */ clk = drv_usectohz(1000000); /* 1 second */ mutex_enter(&sc->sc_ilock); while (sc->sc_done == 0) { /* * pending for the response */ if (cv_reltimedwait(&sc->sc_cmd_cond, &sc->sc_ilock, clk, TR_CLOCK_TICK) < 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; IPW2100_DBG(IPW2100_DBG_INIT, (sc->sc_dip, CE_CONT, "ipw2100_init(): enter\n")); /* * 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 */ IPW2100_DBG(IPW2100_DBG_INIT, (sc->sc_dip, CE_CONT, "ipw2100_init(): loading microcode\n")); 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 */ IPW2100_DBG(IPW2100_DBG_INIT, (sc->sc_dip, CE_CONT, "ipw2100_init(): loading firmware\n")); 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 && !(sc->sc_flags & IPW2100_FLAG_QUIESCED)) 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 adapter 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) { i--; ipw2100_dma_region_free(&sc->sc_dma_txbufs[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) { i--; ipw2100_dma_region_free(&sc->sc_dma_rxbufs[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 (0); } /* * 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 (0); } 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 (0); fail: return (EIO); } 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 (0); fail: return (EIO); } 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 *)(uintptr_t)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 && (ret = secpolicy_dl_config(iocp->ioc_cr)) != 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 *)(uintptr_t)m->b_rptr; outfp = (wldp_t *)(uintptr_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); } /* * Call back functions for get/set proporty */ static int ipw2100_m_getprop(void *arg, const char *pr_name, mac_prop_id_t wldp_pr_num, uint_t wldp_length, void *wldp_buf) { struct ipw2100_softc *sc = (struct ipw2100_softc *)arg; struct ieee80211com *ic = &sc->sc_ic; int err = 0; switch (wldp_pr_num) { /* mac_prop_id */ case MAC_PROP_WL_DESIRED_RATES: IPW2100_DBG(IPW2100_DBG_BRUSSELS, (sc->sc_dip, CE_CONT, "ipw2100_m_getprop(): Not Support DESIRED_RATES\n")); break; case MAC_PROP_WL_RADIO: *(wl_linkstatus_t *)wldp_buf = ipw2100_get_radio(sc); break; default: /* go through net80211 */ err = ieee80211_getprop(ic, pr_name, wldp_pr_num, wldp_length, wldp_buf); break; } return (err); } static void ipw2100_m_propinfo(void *arg, const char *pr_name, mac_prop_id_t wldp_pr_num, mac_prop_info_handle_t prh) { struct ipw2100_softc *sc = (struct ipw2100_softc *)arg; struct ieee80211com *ic = &sc->sc_ic; ieee80211_propinfo(ic, pr_name, wldp_pr_num, prh); } static int ipw2100_m_setprop(void *arg, const char *pr_name, mac_prop_id_t wldp_pr_num, uint_t wldp_length, const void *wldp_buf) { struct ipw2100_softc *sc = (struct ipw2100_softc *)arg; struct ieee80211com *ic = &sc->sc_ic; int err; switch (wldp_pr_num) { /* mac_prop_id */ case MAC_PROP_WL_DESIRED_RATES: IPW2100_DBG(IPW2100_DBG_BRUSSELS, (sc->sc_dip, CE_CONT, "ipw2100_m_setprop(): Not Support DESIRED_RATES\n")); err = ENOTSUP; break; case MAC_PROP_WL_RADIO: IPW2100_DBG(IPW2100_DBG_BRUSSELS, (sc->sc_dip, CE_CONT, "ipw2100_m_setprop(): Not Support RADIO\n")); err = ENOTSUP; break; default: /* go through net80211 */ err = ieee80211_setprop(ic, pr_name, wldp_pr_num, wldp_length, wldp_buf); break; } if (err == ENETRESET) { if (sc->sc_flags & IPW2100_FLAG_RUNNING) { (void) ipw2100_m_start(sc); (void) ieee80211_new_state(ic, IEEE80211_S_SCAN, -1); } err = 0; } return (err); } 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 *)(uintptr_t)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 if (sc->sc_suspended) return (DDI_INTR_UNCLAIMED); 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); goto enable_interrupt; } /* * 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 *)(uintptr_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_interrupt: /* * 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, nodev, NULL, D_MP, NULL, ipw2100_quiesce); 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)); }