/*
* Copyright 2010 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Copyright (c) 2003, 2004
* Daan Vreeken <Danovitsch@Vitsch.net>. 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, 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.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Daan Vreeken.
* 4. Neither the name of the author nor the names of any co-contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY Daan Vreeken 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 Daan Vreeken OR THE VOICES IN HIS HEAD
* 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.
*/
/*
* Atmel AT76c503 / AT76c503a / AT76c505 / AT76c505a USB WLAN driver
*
* Originally written by Daan Vreeken <Danovitsch @ Vitsch . net>
* http://vitsch.net/bsd/atuwi
*
* Contributed to by :
* Chris Whitehouse, Alistair Phillips, Peter Pilka, Martijn van Buul,
* Suihong Liang, Arjan van Leeuwen, Stuart Walsh
*
* Ported to OpenBSD by Theo de Raadt and David Gwynne.
*/
#include <sys/strsubr.h>
#include <sys/strsun.h>
#include <sys/mac_provider.h>
#include <sys/mac_wifi.h>
#include <sys/net80211.h>
#define USBDRV_MAJOR_VER 2
#define USBDRV_MINOR_VER 0
#include <sys/usb/usba.h>
#include <sys/usb/usba/usba_types.h>
#include "fw/atmel_rfmd.hex"
#include "fw/atmel_rfmd2958.hex"
#include "fw/atmel_rfmd2958-smc.hex"
#include "fw/atmel_intersil.hex"
#include "fw/atmel_at76c505_rfmd.hex"
#include "fw/atmel_at76c503_rfmd_acc.hex"
#include "fw/atmel_at76c503_i3863.hex"
#include "atu.h"
static void *atu_soft_state_p;
static mac_callbacks_t atu_m_callbacks;
static const struct ieee80211_rateset atu_rateset = {4, {2, 4, 11, 22}};
static int
atu_usb_request(struct atu_softc *sc, uint8_t type,
uint8_t request, uint16_t value, uint16_t index, uint16_t length,
uint8_t *data)
{
usb_ctrl_setup_t req;
usb_cb_flags_t cf;
usb_cr_t cr;
mblk_t *mp = NULL;
int uret = USB_SUCCESS;
bzero(&req, sizeof (req));
req.bmRequestType = type;
req.bRequest = request;
req.wValue = value;
req.wIndex = index;
req.wLength = length;
req.attrs = USB_ATTRS_NONE;
if (type & USB_DEV_REQ_DEV_TO_HOST) {
req.attrs = USB_ATTRS_AUTOCLEARING;
uret = usb_pipe_ctrl_xfer_wait(sc->sc_udev->dev_default_ph,
&req, &mp, &cr, &cf, 0);
if (mp == NULL)
return (EIO);
if (uret == USB_SUCCESS)
bcopy(mp->b_rptr, data, length);
} else {
if ((mp = allocb(length, BPRI_HI)) == NULL)
return (ENOMEM);
bcopy(data, mp->b_wptr, length);
mp->b_wptr += length;
uret = usb_pipe_ctrl_xfer_wait(sc->sc_udev->dev_default_ph,
&req, &mp, &cr, &cf, 0);
}
if (mp)
freemsg(mp);
return (uret == USB_SUCCESS ? 0 : EIO);
}
static int
atu_get_mib(struct atu_softc *sc, uint8_t type, uint8_t size,
uint8_t index, uint8_t *buf)
{
return atu_usb_request(sc, ATU_VENDOR_IF_IN, 0x033,
type << 8, index, size, buf);
}
static int
atu_get_cmd_status(struct atu_softc *sc, uint8_t cmd, uint8_t *status)
{
/*
* all other drivers (including Windoze) request 40 bytes of status
* and get a short-xfer of just 6 bytes. we can save 34 bytes of
* buffer if we just request those 6 bytes in the first place :)
*/
return atu_usb_request(sc, ATU_VENDOR_IF_IN, 0x22, cmd,
0x0000, 6, status);
}
static uint8_t
atu_get_dfu_state(struct atu_softc *sc)
{
uint8_t state;
if (atu_usb_request(sc, DFU_GETSTATE, 0, 0, 1, &state))
return (DFUState_DFUError);
return (state);
}
static int
atu_get_opmode(struct atu_softc *sc, uint8_t *mode)
{
return atu_usb_request(sc, ATU_VENDOR_IF_IN, 0x33, 0x0001,
0x0000, 1, mode);
}
static int
atu_get_config(struct atu_softc *sc)
{
struct ieee80211com *ic = &sc->sc_ic;
struct atu_rfmd_conf rfmd_conf;
struct atu_intersil_conf intersil_conf;
int err;
switch (sc->sc_radio) {
case RadioRFMD:
case RadioRFMD2958:
case RadioRFMD2958_SMC:
case AT76C503_RFMD_ACC:
case AT76C505_RFMD:
err = atu_usb_request(sc, ATU_VENDOR_IF_IN, 0x33, 0x0a02,
0x0000, sizeof (rfmd_conf), (uint8_t *)&rfmd_conf);
if (err) {
cmn_err(CE_WARN, "%s: get RFMD config failed\n",
sc->sc_name);
return (err);
}
bcopy(rfmd_conf.MACAddr, ic->ic_macaddr, IEEE80211_ADDR_LEN);
break;
case RadioIntersil:
case AT76C503_i3863:
err = atu_usb_request(sc, ATU_VENDOR_IF_IN, 0x33, 0x0902,
0x0000, sizeof (intersil_conf), (uint8_t *)&intersil_conf);
if (err) {
cmn_err(CE_WARN, "%s: get Intersil config failed\n",
sc->sc_name);
return (err);
}
bcopy(intersil_conf.MACAddr, ic->ic_macaddr,
IEEE80211_ADDR_LEN);
break;
}
return (0);
}
static int
atu_wait_completion(struct atu_softc *sc, uint8_t cmd, uint8_t *status)
{
uint8_t statusreq[6];
int idle_count = 0, err;
while ((err = atu_get_cmd_status(sc, cmd, statusreq)) == 0) {
if ((statusreq[5] != STATUS_IN_PROGRESS) &&
(statusreq[5] != STATUS_IDLE)) {
if (status != NULL)
*status = statusreq[5];
return (0);
} else if (idle_count++ > 60) {
cmn_err(CE_WARN, "%s: command (0x%02x) timeout\n",
sc->sc_name, cmd);
return (ETIME);
}
drv_usecwait(10 * 1000);
}
return (err);
}
static int
atu_send_command(struct atu_softc *sc, uint8_t *command, int size)
{
return atu_usb_request(sc, ATU_VENDOR_DEV_OUT, 0x0e, 0x0000,
0x0000, size, command);
}
static int
atu_send_mib(struct atu_softc *sc, uint8_t type, uint8_t size,
uint8_t index, void *data)
{
struct atu_cmd_set_mib request;
int err;
bzero(&request, sizeof (request));
request.AtCmd = CMD_SET_MIB;
request.AtSize = size + 4;
request.MIBType = type;
request.MIBSize = size;
request.MIBIndex = index;
request.MIBReserved = 0;
/*
* For 1 and 2 byte requests we assume a direct value,
* everything bigger than 2 bytes we assume a pointer to the data
*/
switch (size) {
case 0:
break;
case 1:
request.data[0] = (long)data & 0x000000ff;
break;
case 2:
request.data[0] = (long)data & 0x000000ff;
request.data[1] = (long)data >> 8;
break;
default:
bcopy(data, request.data, size);
break;
}
err = atu_usb_request(sc, ATU_VENDOR_DEV_OUT, 0x0e, 0x0000,
0x0000, size+8, (uint8_t *)&request);
if (err)
return (err);
return (atu_wait_completion(sc, CMD_SET_MIB, NULL));
}
static int
atu_switch_radio(struct atu_softc *sc, boolean_t on)
{
struct atu_cmd radio;
boolean_t ostate;
int err;
/* Intersil doesn't seem to support radio switch */
if (sc->sc_radio == RadioIntersil)
return (0);
ostate = ATU_RADIO_ON(sc) ? B_TRUE : B_FALSE;
if (on != ostate) {
bzero(&radio, sizeof (radio));
radio.Cmd = on ? CMD_RADIO_ON : CMD_RADIO_OFF;
err = atu_send_command(sc, (uint8_t *)&radio,
sizeof (radio));
if (err)
return (err);
err = atu_wait_completion(sc, radio.Cmd, NULL);
if (err)
return (err);
if (on)
sc->sc_flags |= ATU_FLAG_RADIO_ON;
else
sc->sc_flags &= ~ATU_FLAG_RADIO_ON;
}
return (0);
}
static int
atu_config(struct atu_softc *sc)
{
struct ieee80211com *ic = &sc->sc_ic;
struct ieee80211_key *k;
struct atu_cmd_card_config cmd;
uint8_t rates[4] = {0x82, 0x84, 0x8B, 0x96};
int err, i;
err = atu_send_mib(sc, MIB_MAC_ADDR_STA, ic->ic_macaddr);
if (err) {
cmn_err(CE_WARN, "%s: setting MAC address failed\n",
sc->sc_name);
return (err);
}
bzero(&cmd, sizeof (cmd));
cmd.Cmd = CMD_STARTUP;
cmd.Reserved = 0;
cmd.Size = sizeof (cmd) - 4;
cmd.Channel = ATU_DEF_CHAN;
cmd.ShortRetryLimit = 7;
cmd.RTS_Threshold = 2347;
cmd.FragThreshold = 2346;
cmd.PromiscuousMode = 1;
cmd.AutoRateFallback = 1;
bcopy(rates, cmd.BasicRateSet, 4);
if (ic->ic_flags & IEEE80211_F_PRIVACY) {
k = ic->ic_nw_keys + ic->ic_def_txkey;
switch (k->wk_keylen) {
case 5:
cmd.EncryptionType = ATU_ENC_WEP40;
break;
case 13:
cmd.EncryptionType = ATU_ENC_WEP104;
break;
default:
cmn_err(CE_WARN, "%s: key invalid (%d bytes)\n",
sc->sc_name, k->wk_keylen);
goto nowep;
}
cmd.PrivacyInvoked = 1;
cmd.ExcludeUnencrypted = 1;
cmd.WEP_DefaultKeyID = ic->ic_def_txkey;
for (i = 0; i < IEEE80211_WEP_NKID; i++) {
k = ic->ic_nw_keys + i;
if (k->wk_keylen == 0)
continue;
bcopy(k->wk_key, cmd.WEP_DefaultKey + i, k->wk_keylen);
}
} else {
nowep:
cmd.EncryptionType = ATU_ENC_NONE;
}
bcopy(ic->ic_des_essid, cmd.SSID, ic->ic_des_esslen);
cmd.SSID_Len = ic->ic_des_esslen;
cmd.BeaconPeriod = 100;
err = atu_send_command(sc, (uint8_t *)&cmd, sizeof (cmd));
if (err)
return (err);
err = atu_wait_completion(sc, CMD_STARTUP, NULL);
if (err)
return (err);
err = atu_switch_radio(sc, B_TRUE);
if (err)
return (err);
err = atu_send_mib(sc, MIB_MAC_MGMT_POWER_MODE,
(void *)ATU_POWER_ACTIVE);
if (err)
return (err);
return (0);
}
static int
atu_start_scan(struct atu_softc *sc)
{
struct ieee80211com *ic = &sc->sc_ic;
struct atu_cmd_do_scan scan;
int err;
if (!ATU_RUNNING(sc))
return (EIO);
bzero(&scan, sizeof (scan));
scan.Cmd = CMD_START_SCAN;
scan.Reserved = 0;
scan.Size = sizeof (scan) - 4;
(void) memset(scan.BSSID, 0xff, sizeof (scan.BSSID));
bcopy(ic->ic_des_essid, scan.SSID, ic->ic_des_esslen);
scan.SSID_Len = ic->ic_des_esslen;
scan.ScanType = ATU_SCAN_ACTIVE;
scan.Channel = ieee80211_chan2ieee(ic, ic->ic_curchan);
scan.ProbeDelay = 0;
scan.MinChannelTime = 20;
scan.MaxChannelTime = 40;
scan.InternationalScan = 0;
err = atu_send_command(sc, (uint8_t *)&scan, sizeof (scan));
if (err) {
cmn_err(CE_WARN, "%s: SCAN command failed\n",
sc->sc_name);
return (err);
}
err = atu_wait_completion(sc, CMD_START_SCAN, NULL);
if (err) {
cmn_err(CE_WARN, "%s: SCAN completion failed\n",
sc->sc_name);
return (err);
}
return (0);
}
static int
atu_join(struct atu_softc *sc, struct ieee80211_node *node)
{
struct atu_cmd_join join;
uint8_t status;
int err;
bzero(&join, sizeof (join));
join.Cmd = CMD_JOIN;
join.Reserved = 0x00;
join.Size = sizeof (join) - 4;
bcopy(node->in_bssid, join.bssid, IEEE80211_ADDR_LEN);
bcopy(node->in_essid, join.essid, node->in_esslen);
join.essid_size = node->in_esslen;
if (node->in_capinfo & IEEE80211_CAPINFO_IBSS)
join.bss_type = ATU_MODE_IBSS;
else
join.bss_type = ATU_MODE_STA;
join.channel = ieee80211_chan2ieee(&sc->sc_ic, node->in_chan);
join.timeout = ATU_JOIN_TIMEOUT;
join.reserved = 0x00;
err = atu_send_command(sc, (uint8_t *)&join, sizeof (join));
if (err) {
cmn_err(CE_WARN, "%s: JOIN command failed\n",
sc->sc_name);
return (err);
}
err = atu_wait_completion(sc, CMD_JOIN, &status);
if (err)
return (err);
if (status != STATUS_COMPLETE) {
cmn_err(CE_WARN, "%s: incorrect JOIN state (0x%02x)\n",
sc->sc_name, status);
return (EIO);
}
return (0);
}
static int
atu_newstate(struct ieee80211com *ic, enum ieee80211_state nstate, int arg)
{
struct atu_softc *sc = (struct atu_softc *)ic;
enum ieee80211_state ostate = ic->ic_state;
int err = 0;
ATU_LOCK(sc);
if (sc->sc_scan_timer != 0) {
ATU_UNLOCK(sc);
(void) untimeout(sc->sc_scan_timer);
ATU_LOCK(sc);
sc->sc_scan_timer = 0;
}
ostate = ic->ic_state;
switch (nstate) {
case IEEE80211_S_SCAN:
switch (ostate) {
case IEEE80211_S_SCAN:
case IEEE80211_S_AUTH:
case IEEE80211_S_ASSOC:
case IEEE80211_S_RUN:
ATU_UNLOCK(sc);
sc->sc_newstate(ic, nstate, arg);
ATU_LOCK(sc);
if ((err = atu_start_scan(sc)) != 0) {
ATU_UNLOCK(sc);
ieee80211_cancel_scan(ic);
return (err);
}
sc->sc_scan_timer = timeout(
(void (*) (void*))ieee80211_next_scan,
(void *)&sc->sc_ic, 0);
ATU_UNLOCK(sc);
return (err);
default:
break;
}
break;
case IEEE80211_S_AUTH:
switch (ostate) {
case IEEE80211_S_INIT:
case IEEE80211_S_SCAN:
err = atu_join(sc, ic->ic_bss);
if (err) {
ATU_UNLOCK(sc);
return (err);
}
break;
default:
break;
}
default:
break;
}
ATU_UNLOCK(sc);
err = sc->sc_newstate(ic, nstate, arg);
return (err);
}
static int
atu_open_pipes(struct atu_softc *sc)
{
usb_ep_data_t *ep;
usb_pipe_policy_t policy = {0};
int uret;
ep = usb_lookup_ep_data(sc->sc_dip, sc->sc_udev, 0, 0, 0,
USB_EP_ATTR_BULK, USB_EP_DIR_OUT);
policy.pp_max_async_reqs = ATU_TX_LIST_CNT;
uret = usb_pipe_open(sc->sc_dip, &ep->ep_descr, &policy,
USB_FLAGS_SLEEP, &sc->sc_tx_pipe);
if (uret != USB_SUCCESS)
goto fail;
ep = usb_lookup_ep_data(sc->sc_dip, sc->sc_udev, 0, 0, 0,
USB_EP_ATTR_BULK, USB_EP_DIR_IN);
policy.pp_max_async_reqs = ATU_RX_LIST_CNT + 32;
uret = usb_pipe_open(sc->sc_dip, &ep->ep_descr, &policy,
USB_FLAGS_SLEEP, &sc->sc_rx_pipe);
if (uret != USB_SUCCESS)
goto fail;
return (0);
fail:
if (sc->sc_rx_pipe != NULL) {
usb_pipe_close(sc->sc_dip, sc->sc_rx_pipe,
USB_FLAGS_SLEEP, NULL, 0);
sc->sc_rx_pipe = NULL;
}
if (sc->sc_tx_pipe != NULL) {
usb_pipe_close(sc->sc_dip, sc->sc_tx_pipe,
USB_FLAGS_SLEEP, NULL, 0);
sc->sc_tx_pipe = NULL;
}
return (EIO);
}
static void
atu_close_pipes(struct atu_softc *sc)
{
usb_flags_t flags = USB_FLAGS_SLEEP;
if (sc->sc_rx_pipe != NULL) {
usb_pipe_reset(sc->sc_dip, sc->sc_rx_pipe, flags, NULL, 0);
usb_pipe_close(sc->sc_dip, sc->sc_rx_pipe, flags, NULL, 0);
sc->sc_rx_pipe = NULL;
}
if (sc->sc_tx_pipe != NULL) {
usb_pipe_reset(sc->sc_dip, sc->sc_tx_pipe, flags, NULL, 0);
usb_pipe_close(sc->sc_dip, sc->sc_tx_pipe, flags, NULL, 0);
sc->sc_tx_pipe = NULL;
}
}
static int atu_rx_trigger(struct atu_softc *sc);
/*ARGSUSED*/
static void
atu_rxeof(usb_pipe_handle_t pipe, usb_bulk_req_t *req)
{
struct atu_softc *sc = (struct atu_softc *)req->bulk_client_private;
struct ieee80211com *ic = &sc->sc_ic;
struct ieee80211_node *ni;
struct atu_rx_hdr *h;
struct ieee80211_frame *wh;
mblk_t *mp = req->bulk_data;
int len, pktlen;
req->bulk_data = NULL;
if (req->bulk_completion_reason != USB_CR_OK) {
sc->sc_rx_err++;
goto fail;
}
len = msgdsize(mp);
if (len < ATU_RX_HDRLEN + ATU_MIN_FRAMELEN) {
cmn_err(CE_CONT, "%s: fragment (%d bytes)\n",
sc->sc_name, len);
sc->sc_rx_err++;
goto fail;
}
h = (struct atu_rx_hdr *)mp->b_rptr;
pktlen = h->length - 4;
if (pktlen + ATU_RX_HDRLEN + 4 != len) {
cmn_err(CE_CONT, "%s: jumbo (%d bytes -> %d bytes)\n",
sc->sc_name, len, pktlen);
sc->sc_rx_err++;
goto fail;
}
mp->b_rptr += ATU_RX_HDRLEN;
mp->b_wptr = mp->b_rptr + pktlen;
wh = (struct ieee80211_frame *)mp->b_rptr;
if (wh->i_fc[1] & IEEE80211_FC1_WEP)
wh->i_fc[1] &= ~IEEE80211_FC1_WEP;
ni = ieee80211_find_rxnode(ic, wh);
(void) ieee80211_input(ic, mp, ni, h->rssi, h->rx_time);
ieee80211_free_node(ni);
done:
usb_free_bulk_req(req);
mutex_enter(&sc->sc_rxlock);
sc->rx_queued--;
mutex_exit(&sc->sc_rxlock);
if (ATU_RUNNING(sc))
(void) atu_rx_trigger(sc);
return;
fail:
freemsg(mp);
goto done;
}
/*ARGSUSED*/
static void
atu_txeof(usb_pipe_handle_t pipe, usb_bulk_req_t *req)
{
struct atu_softc *sc = (struct atu_softc *)req->bulk_client_private;
struct ieee80211com *ic = &sc->sc_ic;
if (req->bulk_completion_reason != USB_CR_OK)
ic->ic_stats.is_tx_failed++;
usb_free_bulk_req(req);
mutex_enter(&sc->sc_txlock);
sc->tx_queued--;
if (sc->sc_need_sched) {
sc->sc_need_sched = 0;
mac_tx_update(ic->ic_mach);
}
mutex_exit(&sc->sc_txlock);
}
static int
atu_rx_trigger(struct atu_softc *sc)
{
usb_bulk_req_t *req;
int uret;
req = usb_alloc_bulk_req(sc->sc_dip, ATU_RX_BUFSZ, USB_FLAGS_SLEEP);
if (req == NULL)
return (ENOMEM);
req->bulk_len = ATU_RX_BUFSZ;
req->bulk_client_private = (usb_opaque_t)sc;
req->bulk_timeout = 0;
req->bulk_attributes = USB_ATTRS_SHORT_XFER_OK | USB_ATTRS_AUTOCLEARING;
req->bulk_cb = atu_rxeof;
req->bulk_exc_cb = atu_rxeof;
req->bulk_completion_reason = 0;
req->bulk_cb_flags = 0;
uret = usb_pipe_bulk_xfer(sc->sc_rx_pipe, req, 0);
if (uret != USB_SUCCESS) {
usb_free_bulk_req(req);
return (EIO);
}
mutex_enter(&sc->sc_rxlock);
sc->rx_queued++;
mutex_exit(&sc->sc_rxlock);
return (0);
}
static int
atu_tx_trigger(struct atu_softc *sc, mblk_t *mp)
{
usb_bulk_req_t *req;
int uret;
req = usb_alloc_bulk_req(sc->sc_dip, 0, USB_FLAGS_SLEEP);
if (req == NULL)
return (EIO);
req->bulk_len = msgdsize(mp);
req->bulk_data = mp;
req->bulk_client_private = (usb_opaque_t)sc;
req->bulk_timeout = 10;
req->bulk_attributes = USB_ATTRS_AUTOCLEARING;
req->bulk_cb = atu_txeof;
req->bulk_exc_cb = atu_txeof;
req->bulk_completion_reason = 0;
req->bulk_cb_flags = 0;
uret = usb_pipe_bulk_xfer(sc->sc_tx_pipe, req, 0);
if (uret != USB_SUCCESS) {
req->bulk_data = NULL;
usb_free_bulk_req(req);
return (EIO);
}
mutex_enter(&sc->sc_txlock);
sc->tx_queued++;
mutex_exit(&sc->sc_txlock);
return (0);
}
static int
atu_init_rx_queue(struct atu_softc *sc)
{
int err, i;
mutex_enter(&sc->sc_rxlock);
sc->rx_queued = 0;
mutex_exit(&sc->sc_rxlock);
for (i = 0; i < ATU_RX_LIST_CNT; i++) {
err = atu_rx_trigger(sc);
if (err)
return (err);
}
return (0);
}
static void
atu_init_tx_queue(struct atu_softc *sc)
{
mutex_enter(&sc->sc_txlock);
sc->tx_queued = 0;
mutex_exit(&sc->sc_txlock);
}
static int
atu_send(ieee80211com_t *ic, mblk_t *mp, uint8_t type)
{
struct atu_softc *sc = (struct atu_softc *)ic;
struct ieee80211_node *ni = NULL;
struct atu_tx_hdr *desc;
struct ieee80211_frame *wh;
mblk_t *m;
int pktlen = msgdsize(mp), err = 0;
mutex_enter(&sc->sc_txlock);
if (sc->tx_queued > ATU_TX_LIST_CNT) {
sc->sc_tx_nobuf++;
mutex_exit(&sc->sc_txlock);
err = ENOMEM;
goto fail;
}
mutex_exit(&sc->sc_txlock);
m = allocb(ATU_TX_BUFSZ, BPRI_MED);
if (m == NULL) {
sc->sc_tx_nobuf++;
err = ENOMEM;
goto fail;
}
/* reserve tx header space */
m->b_rptr += ATU_TX_HDRLEN;
m->b_wptr += ATU_TX_HDRLEN;
/* copy and (implicitly) free old data */
mcopymsg(mp, m->b_wptr);
m->b_wptr += pktlen;
wh = (struct ieee80211_frame *)m->b_rptr;
ni = ieee80211_find_txnode(ic, wh->i_addr1);
if (ni == NULL) {
ic->ic_stats.is_tx_failed++;
freemsg(m);
err = ENXIO;
goto fail;
}
if (type == IEEE80211_FC0_TYPE_DATA)
(void) ieee80211_encap(ic, m, ni);
/* full WEP in device, prune WEP fields (IV, KID) */
if (wh->i_fc[1] & IEEE80211_FC1_WEP) {
(void) memmove(m->b_rptr + IEEE80211_WEP_IVLEN
+ IEEE80211_WEP_KIDLEN, m->b_rptr,
sizeof (struct ieee80211_frame));
m->b_rptr += IEEE80211_WEP_IVLEN + IEEE80211_WEP_KIDLEN;
}
pktlen = msgdsize(m);
m->b_rptr -= ATU_TX_HDRLEN;
/* setup tx header */
desc = (struct atu_tx_hdr *)m->b_rptr;
bzero(desc, ATU_TX_HDRLEN);
desc->length = (uint16_t)pktlen;
desc->tx_rate = ATU_DEF_TX_RATE;
err = atu_tx_trigger(sc, m);
if (!err) {
ic->ic_stats.is_tx_frags++;
ic->ic_stats.is_tx_bytes += pktlen;
} else {
ic->ic_stats.is_tx_failed++;
freemsg(m);
}
fail:
if (ni != NULL)
ieee80211_free_node(ni);
return (err);
}
static int
atu_stop(struct atu_softc *sc)
{
sc->sc_flags &= ~ATU_FLAG_RUNNING;
atu_close_pipes(sc);
return (atu_switch_radio(sc, B_FALSE));
}
static int
atu_init(struct atu_softc *sc)
{
int err;
err = atu_stop(sc);
if (err)
return (err);
err = atu_open_pipes(sc);
if (err)
goto fail;
err = atu_config(sc);
if (err) {
cmn_err(CE_WARN, "%s: startup config failed\n",
sc->sc_name);
goto fail;
}
atu_init_tx_queue(sc);
err = atu_init_rx_queue(sc);
if (err) {
cmn_err(CE_WARN, "%s: rx queue init failed\n", sc->sc_name);
goto fail;
}
sc->sc_flags |= ATU_FLAG_RUNNING;
return (0);
fail:
(void) atu_stop(sc);
return (err);
}
static void
atu_watchdog(void *arg)
{
struct atu_softc *sc = arg;
struct ieee80211com *ic = &sc->sc_ic;
ieee80211_stop_watchdog(ic);
ATU_LOCK(sc);
if (!ATU_RUNNING(sc)) {
ATU_UNLOCK(sc);
return;
}
ATU_UNLOCK(sc);
switch (ic->ic_state) {
case IEEE80211_S_AUTH:
case IEEE80211_S_ASSOC:
if (ic->ic_bss->in_fails > 0)
ieee80211_new_state(ic, IEEE80211_S_INIT, -1);
else
ieee80211_watchdog(ic);
break;
}
}
static int
atu_dfu_stage1(void *arg)
{
struct atu_softc *sc = arg;
uint8_t state, *ptr = NULL, status[6];
int block_size, bytes_left = 0, block = 0, err, i, count = 0;
/*
* Uploading firmware is done with the DFU (Device Firmware Upgrade)
* interface. See "Universal Serial Bus - Device Class Specification
* for Device Firmware Upgrade" pdf for details of the protocol.
* Maybe this could be moved to a separate 'firmware driver' once more
* device drivers need it... For now we'll just do it here.
*
* Just for your information, the Atmel's DFU descriptor looks like
* this:
*
* 07 size
* 21 type
* 01 capabilities : only firmware download, *need* reset
* after download
* 13 05 detach timeout : max 1299ms between DFU_DETACH and
* reset
* 00 04 max bytes of firmware per transaction : 1024
*/
for (i = 0; i < sizeof (atu_fw_table) / sizeof (atu_fw_table[0]); i++)
if (sc->sc_radio == atu_fw_table[i].atur_type) {
ptr = atu_fw_table[i].atur_int;
bytes_left = atu_fw_table[i].atur_int_size;
}
state = atu_get_dfu_state(sc);
while (block >= 0 && state > 0) {
switch (state) {
case DFUState_DnLoadSync:
/* get DFU status */
err = atu_usb_request(sc, DFU_GETSTATUS, 0, 0, 6,
status);
if (err) {
cmn_err(CE_WARN, "%s: DFU get status failed\n",
sc->sc_name);
return (err);
}
/* success means state => DnLoadIdle */
state = DFUState_DnLoadIdle;
continue;
case DFUState_DFUIdle:
case DFUState_DnLoadIdle:
if (bytes_left >= DFU_MaxBlockSize)
block_size = DFU_MaxBlockSize;
else
block_size = bytes_left;
err = atu_usb_request(sc, DFU_DNLOAD, block++, 0,
block_size, ptr);
if (err) {
cmn_err(CE_WARN, "%s: DFU download failed\n",
sc->sc_name);
return (err);
}
ptr += block_size;
bytes_left -= block_size;
if (block_size == 0)
block = -1;
break;
case DFUState_DFUError:
cmn_err(CE_WARN, "%s: DFU state error\n", sc->sc_name);
return (EIO);
default:
drv_usecwait(10*1000);
if (++count > 100) {
cmn_err(CE_WARN, "%s: DFU timeout\n",
sc->sc_name);
return (ETIME);
}
break;
}
state = atu_get_dfu_state(sc);
}
if (state != DFUState_ManifestSync)
cmn_err(CE_WARN, "%s: DFU state (%d) != ManifestSync\n",
sc->sc_name, state);
err = atu_usb_request(sc, DFU_GETSTATUS, 0, 0, 6, status);
if (err) {
cmn_err(CE_WARN, "%s: DFU get status failed\n",
sc->sc_name);
return (err);
}
err = atu_usb_request(sc, DFU_REMAP, 0, 0, 0, NULL);
if (err && !(sc->sc_quirk & ATU_QUIRK_NO_REMAP)) {
cmn_err(CE_WARN, "%s: DFU remap failed\n", sc->sc_name);
return (err);
}
/*
* after a lot of trying and measuring I found out the device needs
* about 56 miliseconds after sending the remap command before
* it's ready to communicate again. So we'll wait just a little bit
* longer than that to be sure...
*/
drv_usecwait((56+100)*1000);
return (0);
}
static int
atu_dfu_stage2(void *arg)
{
struct atu_softc *sc = arg;
uint8_t *ptr = NULL;
int block_size, bytes_left = 0, block = 0, err, i;
for (i = 0; i < sizeof (atu_fw_table) / sizeof (atu_fw_table[0]); i++)
if (sc->sc_radio == atu_fw_table[i].atur_type) {
ptr = atu_fw_table[i].atur_ext;
bytes_left = atu_fw_table[i].atur_ext_size;
}
while (bytes_left) {
if (bytes_left > 1024)
block_size = 1024;
else
block_size = bytes_left;
err = atu_usb_request(sc, ATU_VENDOR_DEV_OUT, 0x0e,
0x0802, block, block_size, ptr);
if (err) {
cmn_err(CE_WARN, "%s: stage2 firmware load failed\n",
sc->sc_name);
return (err);
}
ptr += block_size;
block++;
bytes_left -= block_size;
}
err = atu_usb_request(sc, ATU_VENDOR_DEV_OUT, 0x0e, 0x0802,
block, 0, NULL);
if (err) {
cmn_err(CE_WARN, "%s: zero-length block load failed\n",
sc->sc_name);
return (err);
}
/*
* The SMC2662w V.4 seems to require some time to do its thing with
* the stage2 firmware... 20 ms isn't enough, but 21 ms works 100
* times out of 100 tries. We'll wait a bit longer just to be sure
*/
if (sc->sc_quirk & ATU_QUIRK_FW_DELAY)
drv_usecwait((21 + 100) * 1000);
return (0);
}
static int
atu_load_microcode(struct atu_softc *sc, boolean_t attach)
{
usb_dev_reset_lvl_t reset;
uint8_t mode, chan;
int err;
reset = attach ? USB_RESET_LVL_REATTACH : USB_RESET_LVL_DEFAULT;
err = atu_get_opmode(sc, &mode);
if (!err) {
if (mode == ATU_DEV_READY)
return (0);
/*
* Opmode of SMC2662 V.4 does not change after stage2
* firmware download. If succeeded reading the channel
* number, stage2 firmware is already running.
*/
if (sc->sc_radio != RadioIntersil &&
atu_get_mib(sc, MIB_PHY_CHANNEL, &chan) == 0)
return (0);
if (mode == ATU_DEV_STAGE2)
stage2:
return (atu_dfu_stage2(sc));
}
err = atu_dfu_stage1(sc);
if (err)
return (err);
if (usb_reset_device(sc->sc_dip, reset) != USB_SUCCESS)
return (EIO);
if (attach)
return (EAGAIN);
else
goto stage2;
}
static int
atu_disconnect(dev_info_t *dip)
{
struct atu_softc *sc;
struct ieee80211com *ic;
sc = ddi_get_soft_state(atu_soft_state_p, ddi_get_instance(dip));
ic = &sc->sc_ic;
ieee80211_new_state(ic, IEEE80211_S_INIT, -1);
ieee80211_stop_watchdog(ic);
ATU_LOCK(sc);
if (sc->sc_scan_timer != 0) {
ATU_UNLOCK(sc);
(void) untimeout(sc->sc_scan_timer);
ATU_LOCK(sc);
sc->sc_scan_timer = 0;
}
sc->sc_flags &= ~(ATU_FLAG_RUNNING | ATU_FLAG_RADIO_ON);
atu_close_pipes(sc);
ATU_UNLOCK(sc);
return (0);
}
static int
atu_reconnect(dev_info_t *dip)
{
struct atu_softc *sc;
int err;
sc = ddi_get_soft_state(atu_soft_state_p, ddi_get_instance(dip));
if (usb_check_same_device(sc->sc_dip, NULL, USB_LOG_L2, -1,
USB_CHK_BASIC, NULL) != USB_SUCCESS)
return (DDI_FAILURE);
ATU_LOCK(sc);
err = atu_load_microcode(sc, B_FALSE);
if (!err)
err = atu_init(sc);
ATU_UNLOCK(sc);
return (err ? DDI_FAILURE : DDI_SUCCESS);
}
static int
atu_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
struct atu_softc *sc;
struct ieee80211com *ic;
mac_register_t *macp;
wifi_data_t wd = {0};
int instance, i, err;
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
sc = ddi_get_soft_state(atu_soft_state_p,
ddi_get_instance(dip));
if (usb_check_same_device(sc->sc_dip, NULL, USB_LOG_L2, -1,
USB_CHK_BASIC, NULL) != USB_SUCCESS)
return (DDI_SUCCESS);
if (atu_load_microcode(sc, B_FALSE) == 0)
(void) atu_init(sc);
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
instance = ddi_get_instance(dip);
if (ddi_soft_state_zalloc(atu_soft_state_p, instance) != DDI_SUCCESS)
return (DDI_FAILURE);
sc = ddi_get_soft_state(atu_soft_state_p, instance);
ic = &sc->sc_ic;
sc->sc_dip = dip;
(void) snprintf(sc->sc_name, sizeof (sc->sc_name), "%s%d",
"atu", instance);
err = usb_client_attach(dip, USBDRV_VERSION, 0);
if (err != USB_SUCCESS)
goto fail1;
err = usb_get_dev_data(dip, &sc->sc_udev, USB_PARSE_LVL_ALL, 0);
if (err != USB_SUCCESS) {
sc->sc_udev = NULL;
goto fail2;
}
for (i = 0; i < sizeof (atu_dev_table)/sizeof (atu_dev_table[0]); i++) {
struct atu_dev_type *t = &atu_dev_table[i];
if (sc->sc_udev->dev_descr->idVendor == t->atu_vid &&
sc->sc_udev->dev_descr->idProduct == t->atu_pid) {
sc->sc_radio = t->atu_radio;
sc->sc_quirk = t->atu_quirk;
}
}
err = atu_load_microcode(sc, B_TRUE);
if (err == EAGAIN) {
sc->sc_flags |= ATU_FLAG_REATTACH; /* reattaching */
return (DDI_SUCCESS);
} else if (err) {
goto fail2;
}
sc->sc_flags &= ~ATU_FLAG_REATTACH;
/* read device config & MAC address */
err = atu_get_config(sc);
if (err) {
cmn_err(CE_WARN, "%s: read device config failed\n",
sc->sc_name);
goto fail2;
}
mutex_init(&sc->sc_genlock, NULL, MUTEX_DRIVER, NULL);
mutex_init(&sc->sc_txlock, NULL, MUTEX_DRIVER, NULL);
mutex_init(&sc->sc_rxlock, NULL, MUTEX_DRIVER, NULL);
ic->ic_phytype = IEEE80211_T_DS;
ic->ic_opmode = IEEE80211_M_STA;
ic->ic_caps = IEEE80211_C_SHPREAMBLE | IEEE80211_C_WEP;
ic->ic_sup_rates[IEEE80211_MODE_11B] = atu_rateset;
ic->ic_maxrssi = atu_fw_table[sc->sc_radio].max_rssi;
ic->ic_state = IEEE80211_S_INIT;
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_2GHZ |
IEEE80211_CHAN_PASSIVE;
}
ic->ic_xmit = atu_send;
ieee80211_attach(ic);
sc->sc_newstate = ic->ic_newstate;
ic->ic_newstate = atu_newstate;
ic->ic_watchdog = atu_watchdog;
ieee80211_media_init(ic);
ic->ic_def_txkey = 0;
wd.wd_opmode = ic->ic_opmode;
wd.wd_secalloc = WIFI_SEC_NONE;
IEEE80211_ADDR_COPY(wd.wd_bssid, ic->ic_bss->in_bssid);
macp = mac_alloc(MAC_VERSION);
if (macp == NULL)
goto fail3;
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 = &atu_m_callbacks;
macp->m_min_sdu = 0;
macp->m_max_sdu = IEEE80211_MTU;
macp->m_pdata = &wd;
macp->m_pdata_size = sizeof (wd);
err = mac_register(macp, &ic->ic_mach);
mac_free(macp);
if (err)
goto fail3;
err = usb_register_hotplug_cbs(sc->sc_dip, atu_disconnect,
atu_reconnect);
if (err != USB_SUCCESS)
goto fail4;
err = ddi_create_minor_node(dip, sc->sc_name, S_IFCHR,
instance + 1, DDI_NT_NET_WIFI, 0);
if (err != DDI_SUCCESS)
cmn_err(CE_WARN, "%s: minor node creation failed\n",
sc->sc_name);
mac_link_update(ic->ic_mach, LINK_STATE_DOWN);
return (DDI_SUCCESS);
fail4:
(void) mac_unregister(ic->ic_mach);
fail3:
mutex_destroy(&sc->sc_genlock);
mutex_destroy(&sc->sc_rxlock);
mutex_destroy(&sc->sc_txlock);
fail2:
usb_client_detach(sc->sc_dip, sc->sc_udev);
fail1:
ddi_soft_state_free(atu_soft_state_p, instance);
return (DDI_FAILURE);
}
static int
atu_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
struct atu_softc *sc;
int err;
sc = ddi_get_soft_state(atu_soft_state_p, ddi_get_instance(dip));
switch (cmd) {
case DDI_DETACH:
break;
case DDI_SUSPEND:
ieee80211_new_state(&sc->sc_ic, IEEE80211_S_INIT, -1);
ieee80211_stop_watchdog(&sc->sc_ic);
ATU_LOCK(sc);
if (sc->sc_scan_timer != 0) {
ATU_UNLOCK(sc);
(void) untimeout(sc->sc_scan_timer);
ATU_LOCK(sc);
sc->sc_scan_timer = 0;
}
(void) atu_stop(sc);
ATU_UNLOCK(sc);
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
if (!ATU_REATTACH(sc)) {
err = mac_disable(sc->sc_ic.ic_mach);
if (err)
return (DDI_FAILURE);
(void) atu_stop(sc);
usb_unregister_hotplug_cbs(dip);
(void) mac_unregister(sc->sc_ic.ic_mach);
ieee80211_detach(&sc->sc_ic);
mutex_destroy(&sc->sc_genlock);
mutex_destroy(&sc->sc_txlock);
mutex_destroy(&sc->sc_rxlock);
ddi_remove_minor_node(dip, NULL);
}
usb_client_detach(dip, sc->sc_udev);
ddi_soft_state_free(atu_soft_state_p, ddi_get_instance(dip));
return (DDI_SUCCESS);
}
DDI_DEFINE_STREAM_OPS(atu_dev_ops, nulldev, nulldev, atu_attach,
atu_detach, nodev, NULL, D_MP, NULL, ddi_quiesce_not_needed);
static struct modldrv atu_modldrv = {
&mod_driverops,
"atu driver v1.1",
&atu_dev_ops
};
static struct modlinkage modlinkage = {
MODREV_1,
(void *)&atu_modldrv,
NULL
};
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
int
_init(void)
{
int status;
status = ddi_soft_state_init(&atu_soft_state_p,
sizeof (struct atu_softc), 1);
if (status != 0)
return (status);
mac_init_ops(&atu_dev_ops, "atu");
status = mod_install(&modlinkage);
if (status != 0) {
mac_fini_ops(&atu_dev_ops);
ddi_soft_state_fini(&atu_soft_state_p);
}
return (status);
}
int
_fini(void)
{
int status;
status = mod_remove(&modlinkage);
if (status == 0) {
mac_fini_ops(&atu_dev_ops);
ddi_soft_state_fini(&atu_soft_state_p);
}
return (status);
}
static int
atu_m_start(void *arg)
{
struct atu_softc *sc = (struct atu_softc *)arg;
int err;
ATU_LOCK(sc);
err = atu_init(sc);
ATU_UNLOCK(sc);
return (err);
}
static void
atu_m_stop(void *arg)
{
struct atu_softc *sc = (struct atu_softc *)arg;
struct ieee80211com *ic = &sc->sc_ic;
ieee80211_new_state(ic, IEEE80211_S_INIT, -1);
ieee80211_stop_watchdog(ic);
ATU_LOCK(sc);
if (sc->sc_scan_timer != 0) {
ATU_UNLOCK(sc);
(void) untimeout(sc->sc_scan_timer);
ATU_LOCK(sc);
sc->sc_scan_timer = 0;
}
(void) atu_stop(sc);
ATU_UNLOCK(sc);
}
/*ARGSUSED*/
static int
atu_m_unicst(void *arg, const uint8_t *macaddr)
{
return (ENOTSUP);
}
/*ARGSUSED*/
static int
atu_m_multicst(void *arg, boolean_t add, const uint8_t *mca)
{
return (ENOTSUP);
}
/*ARGSUSED*/
static int
atu_m_promisc(void *arg, boolean_t on)
{
return (0);
}
static int
atu_m_setprop(void *arg, const char *name, mac_prop_id_t id, uint_t len,
const void *buf)
{
struct atu_softc *sc = (struct atu_softc *)arg;
struct ieee80211com *ic = &sc->sc_ic;
int err;
err = ieee80211_setprop(ic, name, id, len, buf);
if (err != ENETRESET)
return (err);
if (ic->ic_des_esslen == 0)
return (0);
ATU_LOCK(sc);
if (ATU_RUNNING(sc)) {
if (sc->sc_scan_timer != 0) {
ATU_UNLOCK(sc);
(void) untimeout(sc->sc_scan_timer);
ATU_LOCK(sc);
sc->sc_scan_timer = 0;
}
err = atu_init(sc);
ATU_UNLOCK(sc);
if (err)
return (err);
ieee80211_new_state(ic, IEEE80211_S_SCAN, -1);
ATU_LOCK(sc);
}
ATU_UNLOCK(sc);
return (0);
}
static int
atu_m_getprop(void *arg, const char *name, mac_prop_id_t id,
uint_t length, void *buf)
{
struct atu_softc *sc = (struct atu_softc *)arg;
struct ieee80211com *ic = &sc->sc_ic;
return (ieee80211_getprop(ic, name, id, length, buf));
}
static void
atu_m_propinfo(void *arg, const char *name, mac_prop_id_t id,
mac_prop_info_handle_t mph)
{
struct atu_softc *sc = (struct atu_softc *)arg;
struct ieee80211com *ic = &sc->sc_ic;
ieee80211_propinfo(ic, name, id, mph);
}
static void
atu_m_ioctl(void* arg, queue_t *wq, mblk_t *mp)
{
struct atu_softc *sc = (struct atu_softc *)arg;
struct ieee80211com *ic = &sc->sc_ic;
int err;
err = ieee80211_ioctl(ic, wq, mp);
if (err != ENETRESET || ic->ic_des_esslen == 0)
return;
ATU_LOCK(sc);
if (ATU_RUNNING(sc)) {
if (sc->sc_scan_timer != 0) {
ATU_UNLOCK(sc);
(void) untimeout(sc->sc_scan_timer);
ATU_LOCK(sc);
sc->sc_scan_timer = 0;
}
err = atu_init(sc);
ATU_UNLOCK(sc);
if (err)
return;
ieee80211_new_state(ic, IEEE80211_S_SCAN, -1);
ATU_LOCK(sc);
}
ATU_UNLOCK(sc);
}
static mblk_t *
atu_m_tx(void *arg, mblk_t *mp)
{
struct atu_softc *sc = (struct atu_softc *)arg;
struct ieee80211com *ic = &sc->sc_ic;
mblk_t *next;
if (ic->ic_state != IEEE80211_S_RUN) {
freemsgchain(mp);
return (NULL);
}
while (mp != NULL) {
next = mp->b_next;
mp->b_next = NULL;
if (atu_send(ic, mp, IEEE80211_FC0_TYPE_DATA) == ENOMEM) {
mutex_enter(&sc->sc_txlock);
sc->sc_need_sched = 1;
mutex_exit(&sc->sc_txlock);
mp->b_next = next;
return (mp);
}
mp = next;
}
return (mp);
}
static int
atu_m_stat(void *arg, uint_t stat, uint64_t *val)
{
struct atu_softc *sc = (struct atu_softc *)arg;
ieee80211com_t *ic = &sc->sc_ic;
ieee80211_node_t *in;
ATU_LOCK(sc);
switch (stat) {
case MAC_STAT_IFSPEED:
in = ic->ic_bss;
*val = ((ic->ic_fixed_rate == IEEE80211_FIXED_RATE_NONE) ?
IEEE80211_RATE(in->in_txrate) :
ic->ic_fixed_rate) / 2 * 1000000;
break;
case MAC_STAT_NOXMTBUF:
*val = sc->sc_tx_nobuf;
break;
case MAC_STAT_NORCVBUF:
*val = sc->sc_rx_nobuf;
break;
case MAC_STAT_IERRORS:
*val = sc->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:
*val = ic->ic_stats.is_tx_failed;
break;
case WIFI_STAT_TX_FRAGS:
case WIFI_STAT_MCAST_TX:
case WIFI_STAT_TX_FAILED:
case WIFI_STAT_TX_RETRANS:
case WIFI_STAT_TX_RERETRANS:
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_FCS_ERRORS:
case WIFI_STAT_WEP_ERRORS:
case WIFI_STAT_RX_DUPS:
ATU_UNLOCK(sc);
return (ieee80211_stat(ic, stat, val));
default:
ATU_UNLOCK(sc);
return (ENOTSUP);
}
ATU_UNLOCK(sc);
return (0);
}
static mac_callbacks_t atu_m_callbacks = {
MC_IOCTL | MC_SETPROP | MC_GETPROP | MC_PROPINFO,
atu_m_stat,
atu_m_start,
atu_m_stop,
atu_m_promisc,
atu_m_multicst,
atu_m_unicst,
atu_m_tx,
NULL,
atu_m_ioctl,
NULL,
NULL,
NULL,
atu_m_setprop,
atu_m_getprop,
atu_m_propinfo
};