/* * Copyright 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright (c) 2007 by Lukas Turek * Copyright (c) 2007 by Jiri Svoboda * Copyright (c) 2007 by Martin Krulis * Copyright (c) 2006 by Damien Bergamini * Copyright (c) 2006 by Florian Stoehr * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ /* * ZD1211 wLAN driver * Driver major routines */ #include #include #include #include #include #include #include #include #include #include "zyd.h" #include "zyd_reg.h" static int zyd_attach(dev_info_t *dip, ddi_attach_cmd_t cmd); static int zyd_detach(dev_info_t *dip, ddi_detach_cmd_t cmd); static int zyd_m_stat(void *arg, uint_t stat, uint64_t *val); static int zyd_m_start(void *arg); static void zyd_m_stop(void *arg); static int zyd_m_unicst(void *arg, const uint8_t *macaddr); static int zyd_m_multicst(void *arg, boolean_t add, const uint8_t *m); static int zyd_m_promisc(void *arg, boolean_t on); static void zyd_m_ioctl(void *arg, queue_t *wq, mblk_t *mp); static mblk_t *zyd_m_tx(void *arg, mblk_t *mp); static int zyd_m_getprop(void *arg, const char *pr_name, mac_prop_id_t wldp_pr_num, uint_t wldp_length, void *wldp_buf); static void zyd_m_propinfo(void *arg, const char *pr_name, mac_prop_id_t wldp_pr_num, mac_prop_info_handle_t mph); static int zyd_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 zyd_newstate(struct ieee80211com *ic, enum ieee80211_state state, int arg); /* Driver identification */ static char zyd_ident[] = ZYD_DRV_DESC " " ZYD_DRV_REV; /* Global state pointer for managing per-device soft states */ void *zyd_ssp; /* * Mac Call Back entries */ static mac_callbacks_t zyd_m_callbacks = { MC_IOCTL | MC_SETPROP | MC_GETPROP | MC_PROPINFO, zyd_m_stat, /* Get the value of a statistic */ zyd_m_start, /* Start the device */ zyd_m_stop, /* Stop the device */ zyd_m_promisc, /* Enable or disable promiscuous mode */ zyd_m_multicst, /* Enable or disable a multicast addr */ zyd_m_unicst, /* Set the unicast MAC address */ zyd_m_tx, /* Transmit a packet */ NULL, zyd_m_ioctl, /* Process an unknown ioctl */ NULL, /* mc_getcapab */ NULL, NULL, zyd_m_setprop, zyd_m_getprop, zyd_m_propinfo }; /* * Module Loading Data & Entry Points */ DDI_DEFINE_STREAM_OPS(zyd_devops, /* name */ nulldev, /* identify */ nulldev, /* probe */ zyd_attach, /* attach */ zyd_detach, /* detach */ nodev, /* reset */ NULL, /* getinfo */ D_MP, /* flag */ NULL, /* stream_tab */ ddi_quiesce_not_needed /* quiesce */ ); static struct modldrv zyd_modldrv = { &mod_driverops, /* drv_modops */ zyd_ident, /* drv_linkinfo */ &zyd_devops /* drv_dev_ops */ }; static struct modlinkage zyd_ml = { MODREV_1, /* ml_rev */ {&zyd_modldrv, NULL} /* ml_linkage */ }; /* * Wireless-specific structures */ static const struct ieee80211_rateset zyd_rateset_11b = { 4, {2, 4, 11, 22} /* units are 0.5Mbit! */ }; static const struct ieee80211_rateset zyd_rateset_11g = { 12, {2, 4, 11, 22, 12, 18, 24, 36, 48, 72, 96, 108} }; #ifdef DEBUG uint32_t zyd_dbg_flags; void zyd_dbg(uint32_t dbg_mask, const int8_t *fmt, ...) { va_list args; if (dbg_mask & zyd_dbg_flags) { va_start(args, fmt); vcmn_err(CE_CONT, fmt, args); va_end(args); } } #endif void zyd_warn(const int8_t *fmt, ...) { va_list args; va_start(args, fmt); vcmn_err(CE_WARN, fmt, args); va_end(args); } /* * Internal functions */ static uint8_t zyd_plcp_signal(uint16_t rate) { switch (rate) { /* CCK rates (returned values are device-dependent) */ case 2: return (0x0); case 4: return (0x1); case 11: return (0x2); case 22: return (0x3); /* OFDM rates (cf IEEE Std 802.11a-1999, pp. 14 Table 80) */ case 12: return (0xb); case 18: return (0xf); case 24: return (0xa); case 36: return (0xe); case 48: return (0x9); case 72: return (0xd); case 96: return (0x8); case 108: return (0xc); /* unsupported rates (should not get there) */ default: return (0xff); } } /* * Timeout function for scanning. * * Called at the end of each scanning round. */ static void zyd_next_scan(void *arg) { struct zyd_softc *sc = arg; struct ieee80211com *ic = &sc->ic; ZYD_DEBUG((ZYD_DBG_SCAN, "scan timer: fired\n")); if (ic->ic_state == IEEE80211_S_SCAN) { ieee80211_next_scan(ic); } else { ZYD_DEBUG((ZYD_DBG_SCAN, "scan timer: no work\n")); } } /* * Extract a 802.11 frame from the received packet and forward it to net80211. */ void zyd_receive(struct zyd_softc *sc, const uint8_t *buf, uint16_t len) { const struct zyd_rx_stat *stat; struct ieee80211com *ic = &sc->ic; struct ieee80211_frame *wh; struct ieee80211_node *in; int rlen; /* Actual frame length */ uint8_t rssi; mblk_t *m; if (len < ZYD_MIN_FRAGSZ) { /* Packet is too short, silently drop it */ sc->rx_err++; return; } stat = (const struct zyd_rx_stat *) (buf + len - sizeof (struct zyd_rx_stat)); if (stat->flags & ZYD_RX_ERROR) { /* Frame is corrupted, silently drop it */ sc->rx_err++; return; } /* compute actual frame length */ rlen = len - sizeof (struct zyd_plcphdr) - sizeof (struct zyd_rx_stat) - IEEE80211_CRC_LEN; m = allocb(rlen, BPRI_MED); if (m == NULL) { sc->rx_nobuf++; return; } /* Copy frame to new buffer */ bcopy(buf + sizeof (struct zyd_plcphdr), m->b_wptr, rlen); m->b_wptr += rlen; /* Send frame to net80211 stack */ wh = (struct ieee80211_frame *)m->b_rptr; in = ieee80211_find_rxnode(ic, wh); rssi = (stat->rssi < 25) ? 230 : (255 - stat->rssi) / 2; (void) ieee80211_input(ic, m, in, (int32_t)rssi, 0); ieee80211_free_node(in); } /* * xxx_send callback for net80211. * * Transmit a 802.11 frame. * * Constructs a packet from zyd_tx_header and 802.11 frame data * and sends it to the chip. */ /*ARGSUSED*/ static int zyd_send(ieee80211com_t *ic, mblk_t *mp, uint8_t type) { struct zyd_softc *sc = ZYD_IC_TO_SOFTC(ic); struct zyd_tx_header *buf_hdr; struct ieee80211_frame *wh; struct ieee80211_node *in; struct ieee80211_key *k; mblk_t *m, *m0; int len, off, mblen; uint16_t frame_size, additional_size, rate; uint8_t service; int res; ASSERT(mp->b_next == NULL); /* device not ready, drop all frames */ if (!sc->usb.connected || sc->suspended || !sc->running) { freemsg(mp); if (type == IEEE80211_FC0_TYPE_DATA) return (DDI_SUCCESS); else return (DDI_FAILURE); } /* device queue overrun */ if (sc->tx_queued >= ZYD_TX_LIST_COUNT) { /* drop management frames */ if (type != IEEE80211_FC0_TYPE_DATA) { freemsg(mp); } else { (void) zyd_serial_enter(sc, ZYD_NO_SIG); sc->resched = B_TRUE; zyd_serial_exit(sc); } return (DDI_FAILURE); } m = allocb(msgdsize(mp) + sizeof (struct zyd_tx_header) + 32, BPRI_MED); if (m == NULL) { sc->tx_nobuf++; (void) zyd_serial_enter(sc, ZYD_NO_SIG); sc->resched = B_TRUE; zyd_serial_exit(sc); return (DDI_FAILURE); } m->b_rptr += sizeof (struct zyd_tx_header); m->b_wptr = m->b_rptr; for (off = 0, m0 = mp; m0 != NULL; m0 = m0->b_cont) { mblen = MBLKL(m0); (void) memcpy(m->b_rptr + off, m0->b_rptr, mblen); off += mblen; } m->b_wptr += off; wh = (struct ieee80211_frame *)m->b_rptr; in = ieee80211_find_txnode(ic, wh->i_addr1); if (in == NULL) { freemsg(m); sc->tx_err++; freemsg(mp); return (DDI_SUCCESS); } in->in_inact = 0; if (type == IEEE80211_FC0_TYPE_DATA) (void) ieee80211_encap(ic, m, in); if (wh->i_fc[1] & IEEE80211_FC1_WEP) { k = ieee80211_crypto_encap(ic, m); if (k == NULL) { sc->tx_err++; ieee80211_free_node(in); freemsg(m); freemsg(mp); return (DDI_SUCCESS); } /* packet header may have moved, reset our local pointer */ wh = (struct ieee80211_frame *)m->b_rptr; } /* * pickup a rate. May need work to make adaptive - at present, * picks best rate for mode. */ if (type == IEEE80211_FC0_TYPE_MGT) { /* mgmt frames are sent at 1M */ rate = (uint16_t)in->in_rates.ir_rates[0]; } else if (ic->ic_fixed_rate != IEEE80211_FIXED_RATE_NONE) { rate = (uint16_t)ic->ic_sup_rates[ic->ic_curmode]. ir_rates[ic->ic_fixed_rate]; } else { rate = (uint16_t)ic->ic_sup_rates[ic->ic_curmode]. ir_rates[in->in_txrate]; } rate &= IEEE80211_RATE_VAL; if (rate == 0) /* should not happen */ rate = 2; /* Get total length of frame */ len = msgsize(m); m->b_rptr -= sizeof (struct zyd_tx_header); buf_hdr = (struct zyd_tx_header *)m->b_rptr; frame_size = (uint16_t)len + 4; /* include CRC32 */ buf_hdr->frame_size = LE_16(frame_size); /* * Compute "packet size". What the 10 stands for, * nobody knows. */ additional_size = sizeof (struct zyd_tx_header) + 10; if (sc->mac_rev == ZYD_ZD1211) buf_hdr->packet_size = LE_16(frame_size + additional_size); else buf_hdr->packet_size = LE_16(additional_size); buf_hdr->rate_mod_flags = LE_8(zyd_plcp_signal(rate)); buf_hdr->type_flags = LE_8(ZYD_TX_FLAG_BACKOFF); if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { /* multicast frames are not sent at OFDM rates in 802.11b/g */ if (frame_size > ic->ic_rtsthreshold) { buf_hdr->type_flags |= ZYD_TX_FLAG_RTS; } else if (ZYD_RATE_IS_OFDM(rate) && (ic->ic_flags & IEEE80211_F_USEPROT)) { if (ic->ic_protmode == IEEE80211_PROT_CTSONLY) buf_hdr->type_flags |= ZYD_TX_FLAG_CTS_TO_SELF; else if (ic->ic_protmode == IEEE80211_PROT_RTSCTS) buf_hdr->type_flags |= ZYD_TX_FLAG_RTS; } } else buf_hdr->type_flags |= ZYD_TX_FLAG_MULTICAST; if ((type == IEEE80211_FC0_TYPE_CTL) && (wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK) == IEEE80211_FC0_SUBTYPE_PS_POLL) buf_hdr->type_flags |= ZYD_TX_FLAG_TYPE(ZYD_TX_TYPE_PS_POLL); if (ZYD_RATE_IS_OFDM(rate)) { buf_hdr->rate_mod_flags |= ZYD_TX_RMF_OFDM; if (ic->ic_curmode == IEEE80211_MODE_11A) buf_hdr->rate_mod_flags |= ZYD_TX_RMF_5GHZ; } else if (rate != 2 && (ic->ic_flags & IEEE80211_F_SHPREAMBLE)) buf_hdr->rate_mod_flags |= ZYD_TX_RMF_SH_PREAMBLE; /* * Compute frame duration and length-extension service flag. */ service = 0x00; buf_hdr->frame_duration = LE_16((16 * frame_size + rate - 1) / rate); buf_hdr->service = service; buf_hdr->next_frame_duration = LE_16(0); if (rate == 22) { const int remainder = (16 * frame_size) % 22; if (remainder != 0 && remainder < 7) buf_hdr->service |= ZYD_TX_SERVICE_LENGTH_EXTENSION; } res = zyd_usb_send_packet(&sc->usb, m); if (res != ZYD_SUCCESS) { sc->tx_err++; } else { (void) zyd_serial_enter(sc, ZYD_NO_SIG); sc->tx_queued++; zyd_serial_exit(sc); freemsg(mp); ic->ic_stats.is_tx_frags++; ic->ic_stats.is_tx_bytes += len; } ieee80211_free_node(in); return (DDI_SUCCESS); } /* * Register with the MAC layer. */ static zyd_res zyd_mac_init(struct zyd_softc *sc) { struct ieee80211com *ic = &sc->ic; mac_register_t *macp; wifi_data_t wd = { 0 }; int err; /* * Initialize mac structure */ macp = mac_alloc(MAC_VERSION); if (macp == NULL) { ZYD_WARN("failed to allocate MAC structure\n"); return (ZYD_FAILURE); } /* * Initialize pointer to device specific functions */ wd.wd_secalloc = WIFI_SEC_NONE; wd.wd_opmode = sc->ic.ic_opmode; IEEE80211_ADDR_COPY(wd.wd_bssid, ic->ic_macaddr); macp->m_type_ident = MAC_PLUGIN_IDENT_WIFI; macp->m_driver = sc; macp->m_dip = sc->dip; macp->m_src_addr = ic->ic_macaddr; macp->m_callbacks = &zyd_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, &sc->ic.ic_mach); mac_free(macp); if (err != DDI_SUCCESS) { ZYD_WARN("failed to register MAC structure\n"); return (ZYD_FAILURE); } return (ZYD_SUCCESS); } /* * Register with net80211. */ static void zyd_wifi_init(struct zyd_softc *sc) { struct ieee80211com *ic = &sc->ic; int i; /* * Initialize the WiFi part, which will be used by generic layer */ ic->ic_phytype = IEEE80211_T_OFDM; ic->ic_opmode = IEEE80211_M_STA; ic->ic_state = IEEE80211_S_INIT; ic->ic_maxrssi = 255; ic->ic_xmit = zyd_send; /* set device capabilities */ ic->ic_caps = IEEE80211_C_TXPMGT | /* tx power management */ IEEE80211_C_SHPREAMBLE | /* short preamble supported */ IEEE80211_C_SHSLOT | IEEE80211_C_WPA; /* Support WPA/WPA2 */ /* Copy MAC address */ IEEE80211_ADDR_COPY(ic->ic_macaddr, sc->macaddr); /* * set supported .11b and .11g rates */ ic->ic_sup_rates[IEEE80211_MODE_11B] = zyd_rateset_11b; ic->ic_sup_rates[IEEE80211_MODE_11G] = zyd_rateset_11g; /* * set supported .11b and .11g channels(1 through 14) */ for (i = 1; i <= 14; i++) { ic->ic_sup_channels[i].ich_freq = ieee80211_ieee2mhz(i, IEEE80211_CHAN_2GHZ); ic->ic_sup_channels[i].ich_flags = IEEE80211_CHAN_CCK | IEEE80211_CHAN_OFDM | IEEE80211_CHAN_DYN | IEEE80211_CHAN_2GHZ; } /* * Init generic layer (it cannot fail) */ ieee80211_attach(ic); /* register WPA door */ ieee80211_register_door(ic, ddi_driver_name(sc->dip), ddi_get_instance(sc->dip)); /* Must be after attach! */ sc->newstate = ic->ic_newstate; ic->ic_newstate = zyd_newstate; ieee80211_media_init(ic); ic->ic_def_txkey = 0; } /* * Device operations */ /* * Binding the driver to a device. * * Concurrency: Until zyd_attach() returns with success, * the only other entry point that can be executed is getinfo(). * Thus no locking here yet. */ static int zyd_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct zyd_softc *sc; char strbuf[32]; int instance; int err; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: sc = ddi_get_soft_state(zyd_ssp, ddi_get_instance(dip)); ASSERT(sc != NULL); (void) zyd_resume(sc); return (DDI_SUCCESS); default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); err = ddi_soft_state_zalloc(zyd_ssp, instance); if (err != DDI_SUCCESS) { ZYD_WARN("failed to allocate soft state\n"); return (DDI_FAILURE); } sc = ddi_get_soft_state(zyd_ssp, instance); sc->dip = dip; sc->timeout_id = 0; if (zyd_usb_init(sc) != ZYD_SUCCESS) { ddi_soft_state_free(zyd_ssp, instance); return (DDI_FAILURE); } if (zyd_hw_init(sc) != ZYD_SUCCESS) { zyd_usb_deinit(sc); ddi_soft_state_free(zyd_ssp, instance); return (DDI_FAILURE); } zyd_wifi_init(sc); if (zyd_mac_init(sc) != DDI_SUCCESS) { ieee80211_detach(&sc->ic); zyd_usb_deinit(sc); ddi_soft_state_free(zyd_ssp, instance); return (DDI_FAILURE); } /* * Create minor node of type DDI_NT_NET_WIFI */ (void) snprintf(strbuf, sizeof (strbuf), ZYD_DRV_NAME"%d", instance); err = ddi_create_minor_node(dip, strbuf, S_IFCHR, instance + 1, DDI_NT_NET_WIFI, 0); if (err != DDI_SUCCESS) ZYD_WARN("failed to create minor node\n"); /* initialize locking */ zyd_serial_init(sc); return (DDI_SUCCESS); } /* * Detach the driver from a device. * * Concurrency: Will be called only after a successful attach * (and not concurrently). */ static int zyd_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { struct zyd_softc *sc = NULL; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: sc = ddi_get_soft_state(zyd_ssp, ddi_get_instance(dip)); ASSERT(sc != NULL); return (zyd_suspend(sc)); default: return (DDI_FAILURE); } sc = ddi_get_soft_state(zyd_ssp, ddi_get_instance(dip)); ASSERT(sc != NULL); if (mac_disable(sc->ic.ic_mach) != 0) return (DDI_FAILURE); /* * Unregister from the MAC layer subsystem */ (void) mac_unregister(sc->ic.ic_mach); /* * Detach ieee80211 */ ieee80211_detach(&sc->ic); zyd_hw_deinit(sc); zyd_usb_deinit(sc); /* At this point it should be safe to release & destroy the locks */ zyd_serial_deinit(sc); ddi_remove_minor_node(dip, NULL); ddi_soft_state_free(zyd_ssp, ddi_get_instance(dip)); return (DDI_SUCCESS); } /* * Mac Call Back functions */ /* * Read device statistic information. */ static int zyd_m_stat(void *arg, uint_t stat, uint64_t *val) { struct zyd_softc *sc = (struct zyd_softc *)arg; ieee80211com_t *ic = &sc->ic; ieee80211_node_t *in; switch (stat) { case MAC_STAT_IFSPEED: if (!sc->usb.connected || sc->suspended || !sc->running) return (ENOTSUP); in = ieee80211_ref_node(ic->ic_bss); *val = ((ic->ic_fixed_rate == IEEE80211_FIXED_RATE_NONE) ? IEEE80211_RATE(in->in_txrate) : ic->ic_fixed_rate) / 2 * 1000000; ieee80211_free_node(in); break; case MAC_STAT_NOXMTBUF: *val = sc->tx_nobuf; break; case MAC_STAT_NORCVBUF: *val = sc->rx_nobuf; break; case MAC_STAT_IERRORS: *val = sc->rx_err; break; 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; case MAC_STAT_OERRORS: case WIFI_STAT_TX_FAILED: *val = sc->tx_err; break; case WIFI_STAT_TX_RETRANS: case WIFI_STAT_FCS_ERRORS: case WIFI_STAT_WEP_ERRORS: case WIFI_STAT_TX_FRAGS: case WIFI_STAT_MCAST_TX: case WIFI_STAT_RTS_SUCCESS: case WIFI_STAT_RTS_FAILURE: case WIFI_STAT_ACK_FAILURE: case WIFI_STAT_RX_FRAGS: case WIFI_STAT_MCAST_RX: case WIFI_STAT_RX_DUPS: return (ieee80211_stat(ic, stat, val)); default: return (ENOTSUP); } return (0); } /* * Start the device. * * Concurrency: Presumably fully concurrent, must lock. */ static int zyd_m_start(void *arg) { struct zyd_softc *sc = (struct zyd_softc *)arg; (void) zyd_serial_enter(sc, ZYD_NO_SIG); if ((!sc->usb.connected) || (zyd_hw_start(sc) != ZYD_SUCCESS)) { zyd_serial_exit(sc); return (DDI_FAILURE); } zyd_serial_exit(sc); ieee80211_new_state(&sc->ic, IEEE80211_S_INIT, -1); sc->running = B_TRUE; return (DDI_SUCCESS); } /* * Stop the device. */ static void zyd_m_stop(void *arg) { struct zyd_softc *sc = (struct zyd_softc *)arg; sc->running = B_FALSE; ieee80211_new_state(&sc->ic, IEEE80211_S_INIT, -1); (void) zyd_serial_enter(sc, ZYD_NO_SIG); sc->resched = B_FALSE; zyd_hw_stop(sc); zyd_serial_exit(sc); } /* * Change the MAC address of the device. */ /*ARGSUSED*/ static int zyd_m_unicst(void *arg, const uint8_t *macaddr) { return (DDI_FAILURE); } /* * Enable/disable multicast. */ /*ARGSUSED*/ static int zyd_m_multicst(void *arg, boolean_t add, const uint8_t *m) { ZYD_DEBUG((ZYD_DBG_GLD, "multicast not implemented\n")); return (DDI_SUCCESS); } /* * Enable/disable promiscuous mode. */ /*ARGSUSED*/ static int zyd_m_promisc(void *arg, boolean_t on) { ZYD_DEBUG((ZYD_DBG_GLD, "promiscuous not implemented\n")); return (DDI_SUCCESS); } /* * IOCTL request. */ static void zyd_m_ioctl(void *arg, queue_t *wq, mblk_t *mp) { struct zyd_softc *sc = (struct zyd_softc *)arg; struct ieee80211com *ic = &sc->ic; if (!sc->usb.connected || sc->suspended || !sc->running) { miocnak(wq, mp, 0, ENXIO); return; } if (ieee80211_ioctl(ic, wq, mp) == ENETRESET) { if (sc->running && ic->ic_des_esslen) { zyd_m_stop(sc); if (zyd_m_start(sc) != DDI_SUCCESS) return; ieee80211_new_state(ic, IEEE80211_S_SCAN, -1); } } } /* * callback functions for /get/set properties */ static int zyd_m_setprop(void *arg, const char *pr_name, mac_prop_id_t wldp_pr_num, uint_t wldp_length, const void *wldp_buf) { struct zyd_softc *sc = (struct zyd_softc *)arg; struct ieee80211com *ic = &sc->ic; int err; if (!sc->usb.connected || sc->suspended || !sc->running) { return (ENXIO); } err = ieee80211_setprop(ic, pr_name, wldp_pr_num, wldp_length, wldp_buf); if (err == ENETRESET) { if (sc->running && ic->ic_des_esslen) { zyd_m_stop(sc); if (zyd_m_start(sc) != DDI_SUCCESS) return (DDI_FAILURE); ieee80211_new_state(ic, IEEE80211_S_SCAN, -1); } err = 0; } return (err); } static int zyd_m_getprop(void *arg, const char *pr_name, mac_prop_id_t wldp_pr_num, uint_t wldp_length, void *wldp_buf) { struct zyd_softc *sc = (struct zyd_softc *)arg; int err; if (!sc->usb.connected || sc->suspended || !sc->running) { return (DDI_FAILURE); } err = ieee80211_getprop(&sc->ic, pr_name, wldp_pr_num, wldp_length, wldp_buf); return (err); } static void zyd_m_propinfo(void *arg, const char *pr_name, mac_prop_id_t wldp_pr_num, mac_prop_info_handle_t mph) { struct zyd_softc *sc = (struct zyd_softc *)arg; ieee80211_propinfo(&sc->ic, pr_name, wldp_pr_num, mph); } /* * Transmit a data frame. */ static mblk_t * zyd_m_tx(void *arg, mblk_t *mp) { struct zyd_softc *sc = (struct zyd_softc *)arg; struct ieee80211com *ic = &sc->ic; mblk_t *next; ASSERT(mp != NULL); /* not associated, drop data frames */ if (ic->ic_state != IEEE80211_S_RUN) { freemsg(mp); return (DDI_SUCCESS); } while (mp != NULL) { next = mp->b_next; mp->b_next = NULL; if (zyd_send(ic, mp, IEEE80211_FC0_TYPE_DATA) != DDI_SUCCESS) { mp->b_next = next; break; } mp = next; } return (mp); } /* * xxx_newstate callback for net80211. * * Called by net80211 whenever the ieee80211 state changes. */ static int zyd_newstate(struct ieee80211com *ic, enum ieee80211_state nstate, int arg) { struct zyd_softc *sc = ZYD_IC_TO_SOFTC(ic); struct ieee80211_node *in; uint_t chan; if (sc->timeout_id != 0) { (void) untimeout(sc->timeout_id); sc->timeout_id = 0; } if (!sc->usb.connected || sc->suspended || !sc->running) { return (sc->newstate(ic, nstate, arg)); } switch (nstate) { case IEEE80211_S_SCAN: ZYD_DEBUG((ZYD_DBG_SCAN, "scan timer: starting next\n")); sc->timeout_id = timeout(zyd_next_scan, sc, drv_usectohz(ZYD_DWELL_TIME)); /*FALLTHRU*/ case IEEE80211_S_AUTH: case IEEE80211_S_ASSOC: case IEEE80211_S_RUN: chan = ieee80211_chan2ieee(ic, ic->ic_curchan); if (chan == 0 || chan == IEEE80211_CHAN_ANY) { ZYD_WARN("invalid channel number\n"); return (0); } (void) zyd_serial_enter(sc, ZYD_SER_SIG); zyd_hw_set_channel(sc, chan); zyd_serial_exit(sc); in = ic->ic_bss; in->in_txrate = in->in_rates.ir_nrates - 1; default: break; } return (sc->newstate(ic, nstate, arg)); } /* * USB-safe synchronization. * Debugging routines. * * Kmutexes should never be held when making calls to USBA * or when sleeping. Thus, we implement our own "mutex" on top * of kmutexes and kcondvars. * * Usage: Any (possibly concurrent) access to the soft state or device must * be serialized with a pair of zyd_serial_enter()/zyd_serial_exit(). */ /* * Initialize the serialization object. */ void zyd_serial_init(struct zyd_softc *sc) { mutex_init(&sc->serial.lock, NULL, MUTEX_DRIVER, sc->usb.cdata->dev_iblock_cookie); cv_init(&sc->serial.wait, NULL, CV_DRIVER, NULL); sc->serial.held = B_FALSE; sc->serial.initialized = B_TRUE; } /* * Wait for the serialization object. * * If wait_sig is ZYD_SER_SIG, the function may return * a signal is received. In this case, the serialization object * is not acquired (but the mutex is) and the return value is ZYD_FAILURE. * * In any other case the function returns ZYD_SUCCESS and the * serialization object is acquired. */ zyd_res zyd_serial_enter(struct zyd_softc *sc, boolean_t wait_sig) { zyd_res res; mutex_enter(&sc->serial.lock); res = ZYD_SUCCESS; while (sc->serial.held != B_FALSE) { if (wait_sig == ZYD_SER_SIG) { res = cv_wait_sig(&sc->serial.wait, &sc->serial.lock); } else { cv_wait(&sc->serial.wait, &sc->serial.lock); } } sc->serial.held = B_TRUE; mutex_exit(&sc->serial.lock); return (res); } /* * Release the serialization object. */ void zyd_serial_exit(struct zyd_softc *sc) { mutex_enter(&sc->serial.lock); sc->serial.held = B_FALSE; cv_broadcast(&sc->serial.wait); mutex_exit(&sc->serial.lock); } /* * Destroy the serialization object. */ void zyd_serial_deinit(struct zyd_softc *sc) { cv_destroy(&sc->serial.wait); mutex_destroy(&sc->serial.lock); sc->serial.initialized = B_FALSE; } /* * zyd_cb_lock: a special signal structure that is used for notification * that a callback function has been called. */ /* Initializes the zyd_cb_lock structure. */ void zyd_cb_lock_init(struct zyd_cb_lock *lock) { ASSERT(lock != NULL); mutex_init(&lock->mutex, NULL, MUTEX_DRIVER, NULL); cv_init(&lock->cv, NULL, CV_DRIVER, NULL); lock->done = B_FALSE; } /* Deinitalizes the zyd_cb_lock structure. */ void zyd_cb_lock_destroy(struct zyd_cb_lock *lock) { ASSERT(lock != NULL); mutex_destroy(&lock->mutex); cv_destroy(&lock->cv); } /* * Wait on lock until someone calls the "signal" function or the timeout * expires. Note: timeout is in microseconds. */ zyd_res zyd_cb_lock_wait(struct zyd_cb_lock *lock, clock_t timeout) { zyd_res res; clock_t etime; int cv_res; ASSERT(lock != NULL); mutex_enter(&lock->mutex); if (timeout < 0) { /* no timeout - wait as long as needed */ while (lock->done == B_FALSE) (void) cv_wait(&lock->cv, &lock->mutex); } else { /* wait with timeout (given in usec) */ etime = ddi_get_lbolt() + drv_usectohz(timeout); while (lock->done == B_FALSE) { cv_res = cv_timedwait_sig(&lock->cv, &lock->mutex, etime); if (cv_res <= 0) break; } } res = (lock->done == B_TRUE) ? ZYD_SUCCESS : ZYD_FAILURE; mutex_exit(&lock->mutex); return (res); } /* Signal that the job (eg. callback) is done and unblock anyone who waits. */ void zyd_cb_lock_signal(struct zyd_cb_lock *lock) { ASSERT(lock != NULL); mutex_enter(&lock->mutex); lock->done = B_TRUE; cv_broadcast(&lock->cv); mutex_exit(&lock->mutex); } /* * Loadable module configuration entry points */ /* * _init module entry point. * * Called when the module is being loaded into memory. */ int _init(void) { int err; err = ddi_soft_state_init(&zyd_ssp, sizeof (struct zyd_softc), 1); if (err != DDI_SUCCESS) return (err); mac_init_ops(&zyd_devops, ZYD_DRV_NAME); err = mod_install(&zyd_ml); if (err != DDI_SUCCESS) { mac_fini_ops(&zyd_devops); ddi_soft_state_fini(&zyd_ssp); } return (err); } /* * _info module entry point. * * Called to obtain information about the module. */ int _info(struct modinfo *modinfop) { return (mod_info(&zyd_ml, modinfop)); } /* * _fini module entry point. * * Called when the module is being unloaded. */ int _fini(void) { int err; err = mod_remove(&zyd_ml); if (err == DDI_SUCCESS) { mac_fini_ops(&zyd_devops); ddi_soft_state_fini(&zyd_ssp); } return (err); }