/*
* Copyright 2010 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Copyright (c) 2007 by Lukas Turek <turek@ksvi.mff.cuni.cz>
* Copyright (c) 2007 by Jiri Svoboda <jirik.svoboda@seznam.cz>
* Copyright (c) 2007 by Martin Krulis <martin.krulis@matfyz.cz>
* Copyright (c) 2006 by Damien Bergamini <damien.bergamini@free.fr>
* Copyright (c) 2006 by Florian Stoehr <ich@florian-stoehr.de>
*
* 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 <sys/byteorder.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/conf.h>
#include <sys/modctl.h>
#include <sys/mac_provider.h>
#include <sys/mac_wifi.h>
#include <sys/strsun.h>
#include <sys/ksynch.h>
#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);
}