freebsd-nq/sys/dev/iwi/if_iwi.c
Bernhard Schmidt 82510b7eca The firmware does pad notifications to an even number of bytes (at least
the association notification), the included information though always
contains an elem block with an odd number of bytes. We handle the last
byte as if it might contain a whole elem block, this of course is not
true as one byte is not enough to hold a block, we therefore discard the
complete frame. The solution here is to subtract one from the actual
notification length, this is also what the Linux driver does. With this
change the frames ends exactly where the last elem block ends.

This commit also reverts r214160 which is no longer required and now even
wrong.

MFC after:	1 week
2010-10-23 11:26:22 +00:00

3590 lines
93 KiB
C

/*-
* Copyright (c) 2004, 2005
* Damien Bergamini <damien.bergamini@free.fr>. All rights reserved.
* Copyright (c) 2005-2006 Sam Leffler, Errno Consulting
* Copyright (c) 2007 Andrew Thompson <thompsa@FreeBSD.org>
*
* 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
/*-
* Intel(R) PRO/Wireless 2200BG/2225BG/2915ABG driver
* http://www.intel.com/network/connectivity/products/wireless/prowireless_mobile.htm
*/
#include <sys/param.h>
#include <sys/sysctl.h>
#include <sys/sockio.h>
#include <sys/mbuf.h>
#include <sys/kernel.h>
#include <sys/socket.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/module.h>
#include <sys/bus.h>
#include <sys/endian.h>
#include <sys/proc.h>
#include <sys/mount.h>
#include <sys/namei.h>
#include <sys/linker.h>
#include <sys/firmware.h>
#include <sys/taskqueue.h>
#include <machine/bus.h>
#include <machine/resource.h>
#include <sys/rman.h>
#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
#include <net/bpf.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <net/ethernet.h>
#include <net/if_dl.h>
#include <net/if_media.h>
#include <net/if_types.h>
#include <net80211/ieee80211_var.h>
#include <net80211/ieee80211_radiotap.h>
#include <net80211/ieee80211_input.h>
#include <net80211/ieee80211_regdomain.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/in_var.h>
#include <netinet/ip.h>
#include <netinet/if_ether.h>
#include <dev/iwi/if_iwireg.h>
#include <dev/iwi/if_iwivar.h>
#define IWI_DEBUG
#ifdef IWI_DEBUG
#define DPRINTF(x) do { if (iwi_debug > 0) printf x; } while (0)
#define DPRINTFN(n, x) do { if (iwi_debug >= (n)) printf x; } while (0)
int iwi_debug = 0;
SYSCTL_INT(_debug, OID_AUTO, iwi, CTLFLAG_RW, &iwi_debug, 0, "iwi debug level");
static const char *iwi_fw_states[] = {
"IDLE", /* IWI_FW_IDLE */
"LOADING", /* IWI_FW_LOADING */
"ASSOCIATING", /* IWI_FW_ASSOCIATING */
"DISASSOCIATING", /* IWI_FW_DISASSOCIATING */
"SCANNING", /* IWI_FW_SCANNING */
};
#else
#define DPRINTF(x)
#define DPRINTFN(n, x)
#endif
MODULE_DEPEND(iwi, pci, 1, 1, 1);
MODULE_DEPEND(iwi, wlan, 1, 1, 1);
MODULE_DEPEND(iwi, firmware, 1, 1, 1);
enum {
IWI_LED_TX,
IWI_LED_RX,
IWI_LED_POLL,
};
struct iwi_ident {
uint16_t vendor;
uint16_t device;
const char *name;
};
static const struct iwi_ident iwi_ident_table[] = {
{ 0x8086, 0x4220, "Intel(R) PRO/Wireless 2200BG" },
{ 0x8086, 0x4221, "Intel(R) PRO/Wireless 2225BG" },
{ 0x8086, 0x4223, "Intel(R) PRO/Wireless 2915ABG" },
{ 0x8086, 0x4224, "Intel(R) PRO/Wireless 2915ABG" },
{ 0, 0, NULL }
};
static struct ieee80211vap *iwi_vap_create(struct ieee80211com *,
const char name[IFNAMSIZ], int unit, int opmode, int flags,
const uint8_t bssid[IEEE80211_ADDR_LEN],
const uint8_t mac[IEEE80211_ADDR_LEN]);
static void iwi_vap_delete(struct ieee80211vap *);
static void iwi_dma_map_addr(void *, bus_dma_segment_t *, int, int);
static int iwi_alloc_cmd_ring(struct iwi_softc *, struct iwi_cmd_ring *,
int);
static void iwi_reset_cmd_ring(struct iwi_softc *, struct iwi_cmd_ring *);
static void iwi_free_cmd_ring(struct iwi_softc *, struct iwi_cmd_ring *);
static int iwi_alloc_tx_ring(struct iwi_softc *, struct iwi_tx_ring *,
int, bus_addr_t, bus_addr_t);
static void iwi_reset_tx_ring(struct iwi_softc *, struct iwi_tx_ring *);
static void iwi_free_tx_ring(struct iwi_softc *, struct iwi_tx_ring *);
static int iwi_alloc_rx_ring(struct iwi_softc *, struct iwi_rx_ring *,
int);
static void iwi_reset_rx_ring(struct iwi_softc *, struct iwi_rx_ring *);
static void iwi_free_rx_ring(struct iwi_softc *, struct iwi_rx_ring *);
static struct ieee80211_node *iwi_node_alloc(struct ieee80211vap *,
const uint8_t [IEEE80211_ADDR_LEN]);
static void iwi_node_free(struct ieee80211_node *);
static void iwi_media_status(struct ifnet *, struct ifmediareq *);
static int iwi_newstate(struct ieee80211vap *, enum ieee80211_state, int);
static void iwi_wme_init(struct iwi_softc *);
static int iwi_wme_setparams(struct iwi_softc *, struct ieee80211com *);
static void iwi_update_wme(void *, int);
static int iwi_wme_update(struct ieee80211com *);
static uint16_t iwi_read_prom_word(struct iwi_softc *, uint8_t);
static void iwi_frame_intr(struct iwi_softc *, struct iwi_rx_data *, int,
struct iwi_frame *);
static void iwi_notification_intr(struct iwi_softc *, struct iwi_notif *);
static void iwi_rx_intr(struct iwi_softc *);
static void iwi_tx_intr(struct iwi_softc *, struct iwi_tx_ring *);
static void iwi_intr(void *);
static int iwi_cmd(struct iwi_softc *, uint8_t, void *, uint8_t);
static void iwi_write_ibssnode(struct iwi_softc *, const u_int8_t [], int);
static int iwi_tx_start(struct ifnet *, struct mbuf *,
struct ieee80211_node *, int);
static int iwi_raw_xmit(struct ieee80211_node *, struct mbuf *,
const struct ieee80211_bpf_params *);
static void iwi_start_locked(struct ifnet *);
static void iwi_start(struct ifnet *);
static void iwi_watchdog(void *);
static int iwi_ioctl(struct ifnet *, u_long, caddr_t);
static void iwi_stop_master(struct iwi_softc *);
static int iwi_reset(struct iwi_softc *);
static int iwi_load_ucode(struct iwi_softc *, const struct iwi_fw *);
static int iwi_load_firmware(struct iwi_softc *, const struct iwi_fw *);
static void iwi_release_fw_dma(struct iwi_softc *sc);
static int iwi_config(struct iwi_softc *);
static int iwi_get_firmware(struct iwi_softc *, enum ieee80211_opmode);
static void iwi_put_firmware(struct iwi_softc *);
static void iwi_monitor_scan(void *, int);
static int iwi_scanchan(struct iwi_softc *, unsigned long, int);
static void iwi_scan_start(struct ieee80211com *);
static void iwi_scan_end(struct ieee80211com *);
static void iwi_set_channel(struct ieee80211com *);
static void iwi_scan_curchan(struct ieee80211_scan_state *, unsigned long maxdwell);
static void iwi_scan_mindwell(struct ieee80211_scan_state *);
static int iwi_auth_and_assoc(struct iwi_softc *, struct ieee80211vap *);
static void iwi_disassoc(void *, int);
static int iwi_disassociate(struct iwi_softc *, int quiet);
static void iwi_init_locked(struct iwi_softc *);
static void iwi_init(void *);
static int iwi_init_fw_dma(struct iwi_softc *, int);
static void iwi_stop_locked(void *);
static void iwi_stop(struct iwi_softc *);
static void iwi_restart(void *, int);
static int iwi_getrfkill(struct iwi_softc *);
static void iwi_radio_on(void *, int);
static void iwi_radio_off(void *, int);
static void iwi_sysctlattach(struct iwi_softc *);
static void iwi_led_event(struct iwi_softc *, int);
static void iwi_ledattach(struct iwi_softc *);
static int iwi_probe(device_t);
static int iwi_attach(device_t);
static int iwi_detach(device_t);
static int iwi_shutdown(device_t);
static int iwi_suspend(device_t);
static int iwi_resume(device_t);
static device_method_t iwi_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, iwi_probe),
DEVMETHOD(device_attach, iwi_attach),
DEVMETHOD(device_detach, iwi_detach),
DEVMETHOD(device_shutdown, iwi_shutdown),
DEVMETHOD(device_suspend, iwi_suspend),
DEVMETHOD(device_resume, iwi_resume),
{ 0, 0 }
};
static driver_t iwi_driver = {
"iwi",
iwi_methods,
sizeof (struct iwi_softc)
};
static devclass_t iwi_devclass;
DRIVER_MODULE(iwi, pci, iwi_driver, iwi_devclass, 0, 0);
static __inline uint8_t
MEM_READ_1(struct iwi_softc *sc, uint32_t addr)
{
CSR_WRITE_4(sc, IWI_CSR_INDIRECT_ADDR, addr);
return CSR_READ_1(sc, IWI_CSR_INDIRECT_DATA);
}
static __inline uint32_t
MEM_READ_4(struct iwi_softc *sc, uint32_t addr)
{
CSR_WRITE_4(sc, IWI_CSR_INDIRECT_ADDR, addr);
return CSR_READ_4(sc, IWI_CSR_INDIRECT_DATA);
}
static int
iwi_probe(device_t dev)
{
const struct iwi_ident *ident;
for (ident = iwi_ident_table; ident->name != NULL; ident++) {
if (pci_get_vendor(dev) == ident->vendor &&
pci_get_device(dev) == ident->device) {
device_set_desc(dev, ident->name);
return 0;
}
}
return ENXIO;
}
/* Base Address Register */
#define IWI_PCI_BAR0 0x10
static int
iwi_attach(device_t dev)
{
struct iwi_softc *sc = device_get_softc(dev);
struct ifnet *ifp;
struct ieee80211com *ic;
uint16_t val;
int i, error;
uint8_t bands;
uint8_t macaddr[IEEE80211_ADDR_LEN];
sc->sc_dev = dev;
ifp = sc->sc_ifp = if_alloc(IFT_IEEE80211);
if (ifp == NULL) {
device_printf(dev, "can not if_alloc()\n");
return ENXIO;
}
ic = ifp->if_l2com;
IWI_LOCK_INIT(sc);
sc->sc_unr = new_unrhdr(1, IWI_MAX_IBSSNODE-1, &sc->sc_mtx);
TASK_INIT(&sc->sc_radiontask, 0, iwi_radio_on, sc);
TASK_INIT(&sc->sc_radiofftask, 0, iwi_radio_off, sc);
TASK_INIT(&sc->sc_restarttask, 0, iwi_restart, sc);
TASK_INIT(&sc->sc_disassoctask, 0, iwi_disassoc, sc);
TASK_INIT(&sc->sc_wmetask, 0, iwi_update_wme, sc);
TASK_INIT(&sc->sc_monitortask, 0, iwi_monitor_scan, sc);
callout_init_mtx(&sc->sc_wdtimer, &sc->sc_mtx, 0);
callout_init_mtx(&sc->sc_rftimer, &sc->sc_mtx, 0);
if (pci_get_powerstate(dev) != PCI_POWERSTATE_D0) {
device_printf(dev, "chip is in D%d power mode "
"-- setting to D0\n", pci_get_powerstate(dev));
pci_set_powerstate(dev, PCI_POWERSTATE_D0);
}
pci_write_config(dev, 0x41, 0, 1);
/* enable bus-mastering */
pci_enable_busmaster(dev);
sc->mem_rid = IWI_PCI_BAR0;
sc->mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->mem_rid,
RF_ACTIVE);
if (sc->mem == NULL) {
device_printf(dev, "could not allocate memory resource\n");
goto fail;
}
sc->sc_st = rman_get_bustag(sc->mem);
sc->sc_sh = rman_get_bushandle(sc->mem);
sc->irq_rid = 0;
sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irq_rid,
RF_ACTIVE | RF_SHAREABLE);
if (sc->irq == NULL) {
device_printf(dev, "could not allocate interrupt resource\n");
goto fail;
}
if (iwi_reset(sc) != 0) {
device_printf(dev, "could not reset adapter\n");
goto fail;
}
/*
* Allocate rings.
*/
if (iwi_alloc_cmd_ring(sc, &sc->cmdq, IWI_CMD_RING_COUNT) != 0) {
device_printf(dev, "could not allocate Cmd ring\n");
goto fail;
}
for (i = 0; i < 4; i++) {
error = iwi_alloc_tx_ring(sc, &sc->txq[i], IWI_TX_RING_COUNT,
IWI_CSR_TX1_RIDX + i * 4,
IWI_CSR_TX1_WIDX + i * 4);
if (error != 0) {
device_printf(dev, "could not allocate Tx ring %d\n",
i+i);
goto fail;
}
}
if (iwi_alloc_rx_ring(sc, &sc->rxq, IWI_RX_RING_COUNT) != 0) {
device_printf(dev, "could not allocate Rx ring\n");
goto fail;
}
iwi_wme_init(sc);
ifp->if_softc = sc;
if_initname(ifp, device_get_name(dev), device_get_unit(dev));
ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
ifp->if_init = iwi_init;
ifp->if_ioctl = iwi_ioctl;
ifp->if_start = iwi_start;
IFQ_SET_MAXLEN(&ifp->if_snd, ifqmaxlen);
ifp->if_snd.ifq_drv_maxlen = ifqmaxlen;
IFQ_SET_READY(&ifp->if_snd);
ic->ic_ifp = ifp;
ic->ic_opmode = IEEE80211_M_STA;
ic->ic_phytype = IEEE80211_T_OFDM; /* not only, but not used */
/* set device capabilities */
ic->ic_caps =
IEEE80211_C_STA /* station mode supported */
| IEEE80211_C_IBSS /* IBSS mode supported */
| IEEE80211_C_MONITOR /* monitor mode supported */
| IEEE80211_C_PMGT /* power save supported */
| IEEE80211_C_SHPREAMBLE /* short preamble supported */
| IEEE80211_C_WPA /* 802.11i */
| IEEE80211_C_WME /* 802.11e */
#if 0
| IEEE80211_C_BGSCAN /* capable of bg scanning */
#endif
;
/* read MAC address from EEPROM */
val = iwi_read_prom_word(sc, IWI_EEPROM_MAC + 0);
macaddr[0] = val & 0xff;
macaddr[1] = val >> 8;
val = iwi_read_prom_word(sc, IWI_EEPROM_MAC + 1);
macaddr[2] = val & 0xff;
macaddr[3] = val >> 8;
val = iwi_read_prom_word(sc, IWI_EEPROM_MAC + 2);
macaddr[4] = val & 0xff;
macaddr[5] = val >> 8;
bands = 0;
setbit(&bands, IEEE80211_MODE_11B);
setbit(&bands, IEEE80211_MODE_11G);
if (pci_get_device(dev) >= 0x4223)
setbit(&bands, IEEE80211_MODE_11A);
ieee80211_init_channels(ic, NULL, &bands);
ieee80211_ifattach(ic, macaddr);
/* override default methods */
ic->ic_node_alloc = iwi_node_alloc;
sc->sc_node_free = ic->ic_node_free;
ic->ic_node_free = iwi_node_free;
ic->ic_raw_xmit = iwi_raw_xmit;
ic->ic_scan_start = iwi_scan_start;
ic->ic_scan_end = iwi_scan_end;
ic->ic_set_channel = iwi_set_channel;
ic->ic_scan_curchan = iwi_scan_curchan;
ic->ic_scan_mindwell = iwi_scan_mindwell;
ic->ic_wme.wme_update = iwi_wme_update;
ic->ic_vap_create = iwi_vap_create;
ic->ic_vap_delete = iwi_vap_delete;
ieee80211_radiotap_attach(ic,
&sc->sc_txtap.wt_ihdr, sizeof(sc->sc_txtap),
IWI_TX_RADIOTAP_PRESENT,
&sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap),
IWI_RX_RADIOTAP_PRESENT);
iwi_sysctlattach(sc);
iwi_ledattach(sc);
/*
* Hook our interrupt after all initialization is complete.
*/
error = bus_setup_intr(dev, sc->irq, INTR_TYPE_NET | INTR_MPSAFE,
NULL, iwi_intr, sc, &sc->sc_ih);
if (error != 0) {
device_printf(dev, "could not set up interrupt\n");
goto fail;
}
if (bootverbose)
ieee80211_announce(ic);
return 0;
fail:
/* XXX fix */
iwi_detach(dev);
return ENXIO;
}
static int
iwi_detach(device_t dev)
{
struct iwi_softc *sc = device_get_softc(dev);
struct ifnet *ifp = sc->sc_ifp;
struct ieee80211com *ic = ifp->if_l2com;
/* NB: do early to drain any pending tasks */
ieee80211_draintask(ic, &sc->sc_radiontask);
ieee80211_draintask(ic, &sc->sc_radiofftask);
ieee80211_draintask(ic, &sc->sc_restarttask);
ieee80211_draintask(ic, &sc->sc_disassoctask);
ieee80211_draintask(ic, &sc->sc_monitortask);
iwi_stop(sc);
ieee80211_ifdetach(ic);
iwi_put_firmware(sc);
iwi_release_fw_dma(sc);
iwi_free_cmd_ring(sc, &sc->cmdq);
iwi_free_tx_ring(sc, &sc->txq[0]);
iwi_free_tx_ring(sc, &sc->txq[1]);
iwi_free_tx_ring(sc, &sc->txq[2]);
iwi_free_tx_ring(sc, &sc->txq[3]);
iwi_free_rx_ring(sc, &sc->rxq);
bus_teardown_intr(dev, sc->irq, sc->sc_ih);
bus_release_resource(dev, SYS_RES_IRQ, sc->irq_rid, sc->irq);
bus_release_resource(dev, SYS_RES_MEMORY, sc->mem_rid, sc->mem);
delete_unrhdr(sc->sc_unr);
IWI_LOCK_DESTROY(sc);
if_free(ifp);
return 0;
}
static struct ieee80211vap *
iwi_vap_create(struct ieee80211com *ic,
const char name[IFNAMSIZ], int unit, int opmode, int flags,
const uint8_t bssid[IEEE80211_ADDR_LEN],
const uint8_t mac[IEEE80211_ADDR_LEN])
{
struct ifnet *ifp = ic->ic_ifp;
struct iwi_softc *sc = ifp->if_softc;
struct iwi_vap *ivp;
struct ieee80211vap *vap;
int i;
if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */
return NULL;
/*
* Get firmware image (and possibly dma memory) on mode change.
*/
if (iwi_get_firmware(sc, opmode))
return NULL;
/* allocate DMA memory for mapping firmware image */
i = sc->fw_fw.size;
if (sc->fw_boot.size > i)
i = sc->fw_boot.size;
/* XXX do we dma the ucode as well ? */
if (sc->fw_uc.size > i)
i = sc->fw_uc.size;
if (iwi_init_fw_dma(sc, i))
return NULL;
ivp = (struct iwi_vap *) malloc(sizeof(struct iwi_vap),
M_80211_VAP, M_NOWAIT | M_ZERO);
if (ivp == NULL)
return NULL;
vap = &ivp->iwi_vap;
ieee80211_vap_setup(ic, vap, name, unit, opmode, flags, bssid, mac);
/* override the default, the setting comes from the linux driver */
vap->iv_bmissthreshold = 24;
/* override with driver methods */
ivp->iwi_newstate = vap->iv_newstate;
vap->iv_newstate = iwi_newstate;
/* complete setup */
ieee80211_vap_attach(vap, ieee80211_media_change, iwi_media_status);
ic->ic_opmode = opmode;
return vap;
}
static void
iwi_vap_delete(struct ieee80211vap *vap)
{
struct iwi_vap *ivp = IWI_VAP(vap);
ieee80211_vap_detach(vap);
free(ivp, M_80211_VAP);
}
static void
iwi_dma_map_addr(void *arg, bus_dma_segment_t *segs, int nseg, int error)
{
if (error != 0)
return;
KASSERT(nseg == 1, ("too many DMA segments, %d should be 1", nseg));
*(bus_addr_t *)arg = segs[0].ds_addr;
}
static int
iwi_alloc_cmd_ring(struct iwi_softc *sc, struct iwi_cmd_ring *ring, int count)
{
int error;
ring->count = count;
ring->queued = 0;
ring->cur = ring->next = 0;
error = bus_dma_tag_create(bus_get_dma_tag(sc->sc_dev), 4, 0,
BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL,
count * IWI_CMD_DESC_SIZE, 1, count * IWI_CMD_DESC_SIZE, 0,
NULL, NULL, &ring->desc_dmat);
if (error != 0) {
device_printf(sc->sc_dev, "could not create desc DMA tag\n");
goto fail;
}
error = bus_dmamem_alloc(ring->desc_dmat, (void **)&ring->desc,
BUS_DMA_NOWAIT | BUS_DMA_ZERO, &ring->desc_map);
if (error != 0) {
device_printf(sc->sc_dev, "could not allocate DMA memory\n");
goto fail;
}
error = bus_dmamap_load(ring->desc_dmat, ring->desc_map, ring->desc,
count * IWI_CMD_DESC_SIZE, iwi_dma_map_addr, &ring->physaddr, 0);
if (error != 0) {
device_printf(sc->sc_dev, "could not load desc DMA map\n");
goto fail;
}
return 0;
fail: iwi_free_cmd_ring(sc, ring);
return error;
}
static void
iwi_reset_cmd_ring(struct iwi_softc *sc, struct iwi_cmd_ring *ring)
{
ring->queued = 0;
ring->cur = ring->next = 0;
}
static void
iwi_free_cmd_ring(struct iwi_softc *sc, struct iwi_cmd_ring *ring)
{
if (ring->desc != NULL) {
bus_dmamap_sync(ring->desc_dmat, ring->desc_map,
BUS_DMASYNC_POSTWRITE);
bus_dmamap_unload(ring->desc_dmat, ring->desc_map);
bus_dmamem_free(ring->desc_dmat, ring->desc, ring->desc_map);
}
if (ring->desc_dmat != NULL)
bus_dma_tag_destroy(ring->desc_dmat);
}
static int
iwi_alloc_tx_ring(struct iwi_softc *sc, struct iwi_tx_ring *ring, int count,
bus_addr_t csr_ridx, bus_addr_t csr_widx)
{
int i, error;
ring->count = count;
ring->queued = 0;
ring->cur = ring->next = 0;
ring->csr_ridx = csr_ridx;
ring->csr_widx = csr_widx;
error = bus_dma_tag_create(bus_get_dma_tag(sc->sc_dev), 4, 0,
BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL,
count * IWI_TX_DESC_SIZE, 1, count * IWI_TX_DESC_SIZE, 0, NULL,
NULL, &ring->desc_dmat);
if (error != 0) {
device_printf(sc->sc_dev, "could not create desc DMA tag\n");
goto fail;
}
error = bus_dmamem_alloc(ring->desc_dmat, (void **)&ring->desc,
BUS_DMA_NOWAIT | BUS_DMA_ZERO, &ring->desc_map);
if (error != 0) {
device_printf(sc->sc_dev, "could not allocate DMA memory\n");
goto fail;
}
error = bus_dmamap_load(ring->desc_dmat, ring->desc_map, ring->desc,
count * IWI_TX_DESC_SIZE, iwi_dma_map_addr, &ring->physaddr, 0);
if (error != 0) {
device_printf(sc->sc_dev, "could not load desc DMA map\n");
goto fail;
}
ring->data = malloc(count * sizeof (struct iwi_tx_data), M_DEVBUF,
M_NOWAIT | M_ZERO);
if (ring->data == NULL) {
device_printf(sc->sc_dev, "could not allocate soft data\n");
error = ENOMEM;
goto fail;
}
error = bus_dma_tag_create(bus_get_dma_tag(sc->sc_dev), 1, 0,
BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, MCLBYTES,
IWI_MAX_NSEG, MCLBYTES, 0, NULL, NULL, &ring->data_dmat);
if (error != 0) {
device_printf(sc->sc_dev, "could not create data DMA tag\n");
goto fail;
}
for (i = 0; i < count; i++) {
error = bus_dmamap_create(ring->data_dmat, 0,
&ring->data[i].map);
if (error != 0) {
device_printf(sc->sc_dev, "could not create DMA map\n");
goto fail;
}
}
return 0;
fail: iwi_free_tx_ring(sc, ring);
return error;
}
static void
iwi_reset_tx_ring(struct iwi_softc *sc, struct iwi_tx_ring *ring)
{
struct iwi_tx_data *data;
int i;
for (i = 0; i < ring->count; i++) {
data = &ring->data[i];
if (data->m != NULL) {
bus_dmamap_sync(ring->data_dmat, data->map,
BUS_DMASYNC_POSTWRITE);
bus_dmamap_unload(ring->data_dmat, data->map);
m_freem(data->m);
data->m = NULL;
}
if (data->ni != NULL) {
ieee80211_free_node(data->ni);
data->ni = NULL;
}
}
ring->queued = 0;
ring->cur = ring->next = 0;
}
static void
iwi_free_tx_ring(struct iwi_softc *sc, struct iwi_tx_ring *ring)
{
struct iwi_tx_data *data;
int i;
if (ring->desc != NULL) {
bus_dmamap_sync(ring->desc_dmat, ring->desc_map,
BUS_DMASYNC_POSTWRITE);
bus_dmamap_unload(ring->desc_dmat, ring->desc_map);
bus_dmamem_free(ring->desc_dmat, ring->desc, ring->desc_map);
}
if (ring->desc_dmat != NULL)
bus_dma_tag_destroy(ring->desc_dmat);
if (ring->data != NULL) {
for (i = 0; i < ring->count; i++) {
data = &ring->data[i];
if (data->m != NULL) {
bus_dmamap_sync(ring->data_dmat, data->map,
BUS_DMASYNC_POSTWRITE);
bus_dmamap_unload(ring->data_dmat, data->map);
m_freem(data->m);
}
if (data->ni != NULL)
ieee80211_free_node(data->ni);
if (data->map != NULL)
bus_dmamap_destroy(ring->data_dmat, data->map);
}
free(ring->data, M_DEVBUF);
}
if (ring->data_dmat != NULL)
bus_dma_tag_destroy(ring->data_dmat);
}
static int
iwi_alloc_rx_ring(struct iwi_softc *sc, struct iwi_rx_ring *ring, int count)
{
struct iwi_rx_data *data;
int i, error;
ring->count = count;
ring->cur = 0;
ring->data = malloc(count * sizeof (struct iwi_rx_data), M_DEVBUF,
M_NOWAIT | M_ZERO);
if (ring->data == NULL) {
device_printf(sc->sc_dev, "could not allocate soft data\n");
error = ENOMEM;
goto fail;
}
error = bus_dma_tag_create(bus_get_dma_tag(sc->sc_dev), 1, 0,
BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, MCLBYTES,
1, MCLBYTES, 0, NULL, NULL, &ring->data_dmat);
if (error != 0) {
device_printf(sc->sc_dev, "could not create data DMA tag\n");
goto fail;
}
for (i = 0; i < count; i++) {
data = &ring->data[i];
error = bus_dmamap_create(ring->data_dmat, 0, &data->map);
if (error != 0) {
device_printf(sc->sc_dev, "could not create DMA map\n");
goto fail;
}
data->m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR);
if (data->m == NULL) {
device_printf(sc->sc_dev,
"could not allocate rx mbuf\n");
error = ENOMEM;
goto fail;
}
error = bus_dmamap_load(ring->data_dmat, data->map,
mtod(data->m, void *), MCLBYTES, iwi_dma_map_addr,
&data->physaddr, 0);
if (error != 0) {
device_printf(sc->sc_dev,
"could not load rx buf DMA map");
goto fail;
}
data->reg = IWI_CSR_RX_BASE + i * 4;
}
return 0;
fail: iwi_free_rx_ring(sc, ring);
return error;
}
static void
iwi_reset_rx_ring(struct iwi_softc *sc, struct iwi_rx_ring *ring)
{
ring->cur = 0;
}
static void
iwi_free_rx_ring(struct iwi_softc *sc, struct iwi_rx_ring *ring)
{
struct iwi_rx_data *data;
int i;
if (ring->data != NULL) {
for (i = 0; i < ring->count; i++) {
data = &ring->data[i];
if (data->m != NULL) {
bus_dmamap_sync(ring->data_dmat, data->map,
BUS_DMASYNC_POSTREAD);
bus_dmamap_unload(ring->data_dmat, data->map);
m_freem(data->m);
}
if (data->map != NULL)
bus_dmamap_destroy(ring->data_dmat, data->map);
}
free(ring->data, M_DEVBUF);
}
if (ring->data_dmat != NULL)
bus_dma_tag_destroy(ring->data_dmat);
}
static int
iwi_shutdown(device_t dev)
{
struct iwi_softc *sc = device_get_softc(dev);
iwi_stop(sc);
iwi_put_firmware(sc); /* ??? XXX */
return 0;
}
static int
iwi_suspend(device_t dev)
{
struct iwi_softc *sc = device_get_softc(dev);
iwi_stop(sc);
return 0;
}
static int
iwi_resume(device_t dev)
{
struct iwi_softc *sc = device_get_softc(dev);
struct ifnet *ifp = sc->sc_ifp;
pci_write_config(dev, 0x41, 0, 1);
if (ifp->if_flags & IFF_UP)
iwi_init(sc);
return 0;
}
static struct ieee80211_node *
iwi_node_alloc(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN])
{
struct iwi_node *in;
in = malloc(sizeof (struct iwi_node), M_80211_NODE, M_NOWAIT | M_ZERO);
if (in == NULL)
return NULL;
/* XXX assign sta table entry for adhoc */
in->in_station = -1;
return &in->in_node;
}
static void
iwi_node_free(struct ieee80211_node *ni)
{
struct ieee80211com *ic = ni->ni_ic;
struct iwi_softc *sc = ic->ic_ifp->if_softc;
struct iwi_node *in = (struct iwi_node *)ni;
if (in->in_station != -1) {
DPRINTF(("%s mac %6D station %u\n", __func__,
ni->ni_macaddr, ":", in->in_station));
free_unr(sc->sc_unr, in->in_station);
}
sc->sc_node_free(ni);
}
/*
* Convert h/w rate code to IEEE rate code.
*/
static int
iwi_cvtrate(int iwirate)
{
switch (iwirate) {
case IWI_RATE_DS1: return 2;
case IWI_RATE_DS2: return 4;
case IWI_RATE_DS5: return 11;
case IWI_RATE_DS11: return 22;
case IWI_RATE_OFDM6: return 12;
case IWI_RATE_OFDM9: return 18;
case IWI_RATE_OFDM12: return 24;
case IWI_RATE_OFDM18: return 36;
case IWI_RATE_OFDM24: return 48;
case IWI_RATE_OFDM36: return 72;
case IWI_RATE_OFDM48: return 96;
case IWI_RATE_OFDM54: return 108;
}
return 0;
}
/*
* The firmware automatically adapts the transmit speed. We report its current
* value here.
*/
static void
iwi_media_status(struct ifnet *ifp, struct ifmediareq *imr)
{
struct ieee80211vap *vap = ifp->if_softc;
struct ieee80211com *ic = vap->iv_ic;
struct iwi_softc *sc = ic->ic_ifp->if_softc;
/* read current transmission rate from adapter */
vap->iv_bss->ni_txrate =
iwi_cvtrate(CSR_READ_4(sc, IWI_CSR_CURRENT_TX_RATE));
ieee80211_media_status(ifp, imr);
}
static int
iwi_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg)
{
struct iwi_vap *ivp = IWI_VAP(vap);
struct ieee80211com *ic = vap->iv_ic;
struct ifnet *ifp = ic->ic_ifp;
struct iwi_softc *sc = ifp->if_softc;
IWI_LOCK_DECL;
DPRINTF(("%s: %s -> %s flags 0x%x\n", __func__,
ieee80211_state_name[vap->iv_state],
ieee80211_state_name[nstate], sc->flags));
IEEE80211_UNLOCK(ic);
IWI_LOCK(sc);
switch (nstate) {
case IEEE80211_S_INIT:
/*
* NB: don't try to do this if iwi_stop_master has
* shutdown the firmware and disabled interrupts.
*/
if (vap->iv_state == IEEE80211_S_RUN &&
(sc->flags & IWI_FLAG_FW_INITED))
iwi_disassociate(sc, 0);
break;
case IEEE80211_S_AUTH:
iwi_auth_and_assoc(sc, vap);
break;
case IEEE80211_S_RUN:
if (vap->iv_opmode == IEEE80211_M_IBSS &&
vap->iv_state == IEEE80211_S_SCAN) {
/*
* XXX when joining an ibss network we are called
* with a SCAN -> RUN transition on scan complete.
* Use that to call iwi_auth_and_assoc. On completing
* the join we are then called again with an
* AUTH -> RUN transition and we want to do nothing.
* This is all totally bogus and needs to be redone.
*/
iwi_auth_and_assoc(sc, vap);
} else if (vap->iv_opmode == IEEE80211_M_MONITOR)
ieee80211_runtask(ic, &sc->sc_monitortask);
break;
case IEEE80211_S_ASSOC:
/*
* If we are transitioning from AUTH then just wait
* for the ASSOC status to come back from the firmware.
* Otherwise we need to issue the association request.
*/
if (vap->iv_state == IEEE80211_S_AUTH)
break;
iwi_auth_and_assoc(sc, vap);
break;
default:
break;
}
IWI_UNLOCK(sc);
IEEE80211_LOCK(ic);
return ivp->iwi_newstate(vap, nstate, arg);
}
/*
* WME parameters coming from IEEE 802.11e specification. These values are
* already declared in ieee80211_proto.c, but they are static so they can't
* be reused here.
*/
static const struct wmeParams iwi_wme_cck_params[WME_NUM_AC] = {
{ 0, 3, 5, 7, 0 }, /* WME_AC_BE */
{ 0, 3, 5, 10, 0 }, /* WME_AC_BK */
{ 0, 2, 4, 5, 188 }, /* WME_AC_VI */
{ 0, 2, 3, 4, 102 } /* WME_AC_VO */
};
static const struct wmeParams iwi_wme_ofdm_params[WME_NUM_AC] = {
{ 0, 3, 4, 6, 0 }, /* WME_AC_BE */
{ 0, 3, 4, 10, 0 }, /* WME_AC_BK */
{ 0, 2, 3, 4, 94 }, /* WME_AC_VI */
{ 0, 2, 2, 3, 47 } /* WME_AC_VO */
};
#define IWI_EXP2(v) htole16((1 << (v)) - 1)
#define IWI_USEC(v) htole16(IEEE80211_TXOP_TO_US(v))
static void
iwi_wme_init(struct iwi_softc *sc)
{
const struct wmeParams *wmep;
int ac;
memset(sc->wme, 0, sizeof sc->wme);
for (ac = 0; ac < WME_NUM_AC; ac++) {
/* set WME values for CCK modulation */
wmep = &iwi_wme_cck_params[ac];
sc->wme[1].aifsn[ac] = wmep->wmep_aifsn;
sc->wme[1].cwmin[ac] = IWI_EXP2(wmep->wmep_logcwmin);
sc->wme[1].cwmax[ac] = IWI_EXP2(wmep->wmep_logcwmax);
sc->wme[1].burst[ac] = IWI_USEC(wmep->wmep_txopLimit);
sc->wme[1].acm[ac] = wmep->wmep_acm;
/* set WME values for OFDM modulation */
wmep = &iwi_wme_ofdm_params[ac];
sc->wme[2].aifsn[ac] = wmep->wmep_aifsn;
sc->wme[2].cwmin[ac] = IWI_EXP2(wmep->wmep_logcwmin);
sc->wme[2].cwmax[ac] = IWI_EXP2(wmep->wmep_logcwmax);
sc->wme[2].burst[ac] = IWI_USEC(wmep->wmep_txopLimit);
sc->wme[2].acm[ac] = wmep->wmep_acm;
}
}
static int
iwi_wme_setparams(struct iwi_softc *sc, struct ieee80211com *ic)
{
const struct wmeParams *wmep;
int ac;
for (ac = 0; ac < WME_NUM_AC; ac++) {
/* set WME values for current operating mode */
wmep = &ic->ic_wme.wme_chanParams.cap_wmeParams[ac];
sc->wme[0].aifsn[ac] = wmep->wmep_aifsn;
sc->wme[0].cwmin[ac] = IWI_EXP2(wmep->wmep_logcwmin);
sc->wme[0].cwmax[ac] = IWI_EXP2(wmep->wmep_logcwmax);
sc->wme[0].burst[ac] = IWI_USEC(wmep->wmep_txopLimit);
sc->wme[0].acm[ac] = wmep->wmep_acm;
}
DPRINTF(("Setting WME parameters\n"));
return iwi_cmd(sc, IWI_CMD_SET_WME_PARAMS, sc->wme, sizeof sc->wme);
}
#undef IWI_USEC
#undef IWI_EXP2
static void
iwi_update_wme(void *arg, int npending)
{
struct ieee80211com *ic = arg;
struct iwi_softc *sc = ic->ic_ifp->if_softc;
IWI_LOCK_DECL;
IWI_LOCK(sc);
(void) iwi_wme_setparams(sc, ic);
IWI_UNLOCK(sc);
}
static int
iwi_wme_update(struct ieee80211com *ic)
{
struct iwi_softc *sc = ic->ic_ifp->if_softc;
struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
/*
* We may be called to update the WME parameters in
* the adapter at various places. If we're already
* associated then initiate the request immediately;
* otherwise we assume the params will get sent down
* to the adapter as part of the work iwi_auth_and_assoc
* does.
*/
if (vap->iv_state == IEEE80211_S_RUN)
ieee80211_runtask(ic, &sc->sc_wmetask);
return (0);
}
static int
iwi_wme_setie(struct iwi_softc *sc)
{
struct ieee80211_wme_info wme;
memset(&wme, 0, sizeof wme);
wme.wme_id = IEEE80211_ELEMID_VENDOR;
wme.wme_len = sizeof (struct ieee80211_wme_info) - 2;
wme.wme_oui[0] = 0x00;
wme.wme_oui[1] = 0x50;
wme.wme_oui[2] = 0xf2;
wme.wme_type = WME_OUI_TYPE;
wme.wme_subtype = WME_INFO_OUI_SUBTYPE;
wme.wme_version = WME_VERSION;
wme.wme_info = 0;
DPRINTF(("Setting WME IE (len=%u)\n", wme.wme_len));
return iwi_cmd(sc, IWI_CMD_SET_WMEIE, &wme, sizeof wme);
}
/*
* Read 16 bits at address 'addr' from the serial EEPROM.
*/
static uint16_t
iwi_read_prom_word(struct iwi_softc *sc, uint8_t addr)
{
uint32_t tmp;
uint16_t val;
int n;
/* clock C once before the first command */
IWI_EEPROM_CTL(sc, 0);
IWI_EEPROM_CTL(sc, IWI_EEPROM_S);
IWI_EEPROM_CTL(sc, IWI_EEPROM_S | IWI_EEPROM_C);
IWI_EEPROM_CTL(sc, IWI_EEPROM_S);
/* write start bit (1) */
IWI_EEPROM_CTL(sc, IWI_EEPROM_S | IWI_EEPROM_D);
IWI_EEPROM_CTL(sc, IWI_EEPROM_S | IWI_EEPROM_D | IWI_EEPROM_C);
/* write READ opcode (10) */
IWI_EEPROM_CTL(sc, IWI_EEPROM_S | IWI_EEPROM_D);
IWI_EEPROM_CTL(sc, IWI_EEPROM_S | IWI_EEPROM_D | IWI_EEPROM_C);
IWI_EEPROM_CTL(sc, IWI_EEPROM_S);
IWI_EEPROM_CTL(sc, IWI_EEPROM_S | IWI_EEPROM_C);
/* write address A7-A0 */
for (n = 7; n >= 0; n--) {
IWI_EEPROM_CTL(sc, IWI_EEPROM_S |
(((addr >> n) & 1) << IWI_EEPROM_SHIFT_D));
IWI_EEPROM_CTL(sc, IWI_EEPROM_S |
(((addr >> n) & 1) << IWI_EEPROM_SHIFT_D) | IWI_EEPROM_C);
}
IWI_EEPROM_CTL(sc, IWI_EEPROM_S);
/* read data Q15-Q0 */
val = 0;
for (n = 15; n >= 0; n--) {
IWI_EEPROM_CTL(sc, IWI_EEPROM_S | IWI_EEPROM_C);
IWI_EEPROM_CTL(sc, IWI_EEPROM_S);
tmp = MEM_READ_4(sc, IWI_MEM_EEPROM_CTL);
val |= ((tmp & IWI_EEPROM_Q) >> IWI_EEPROM_SHIFT_Q) << n;
}
IWI_EEPROM_CTL(sc, 0);
/* clear Chip Select and clock C */
IWI_EEPROM_CTL(sc, IWI_EEPROM_S);
IWI_EEPROM_CTL(sc, 0);
IWI_EEPROM_CTL(sc, IWI_EEPROM_C);
return val;
}
static void
iwi_setcurchan(struct iwi_softc *sc, int chan)
{
struct ifnet *ifp = sc->sc_ifp;
struct ieee80211com *ic = ifp->if_l2com;
sc->curchan = chan;
ieee80211_radiotap_chan_change(ic);
}
static void
iwi_frame_intr(struct iwi_softc *sc, struct iwi_rx_data *data, int i,
struct iwi_frame *frame)
{
struct ifnet *ifp = sc->sc_ifp;
struct ieee80211com *ic = ifp->if_l2com;
struct mbuf *mnew, *m;
struct ieee80211_node *ni;
int type, error, framelen;
int8_t rssi, nf;
IWI_LOCK_DECL;
framelen = le16toh(frame->len);
if (framelen < IEEE80211_MIN_LEN || framelen > MCLBYTES) {
/*
* XXX >MCLBYTES is bogus as it means the h/w dma'd
* out of bounds; need to figure out how to limit
* frame size in the firmware
*/
/* XXX stat */
DPRINTFN(1,
("drop rx frame len=%u chan=%u rssi=%u rssi_dbm=%u\n",
le16toh(frame->len), frame->chan, frame->rssi,
frame->rssi_dbm));
return;
}
DPRINTFN(5, ("received frame len=%u chan=%u rssi=%u rssi_dbm=%u\n",
le16toh(frame->len), frame->chan, frame->rssi, frame->rssi_dbm));
if (frame->chan != sc->curchan)
iwi_setcurchan(sc, frame->chan);
/*
* Try to allocate a new mbuf for this ring element and load it before
* processing the current mbuf. If the ring element cannot be loaded,
* drop the received packet and reuse the old mbuf. In the unlikely
* case that the old mbuf can't be reloaded either, explicitly panic.
*/
mnew = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR);
if (mnew == NULL) {
ifp->if_ierrors++;
return;
}
bus_dmamap_unload(sc->rxq.data_dmat, data->map);
error = bus_dmamap_load(sc->rxq.data_dmat, data->map,
mtod(mnew, void *), MCLBYTES, iwi_dma_map_addr, &data->physaddr,
0);
if (error != 0) {
m_freem(mnew);
/* try to reload the old mbuf */
error = bus_dmamap_load(sc->rxq.data_dmat, data->map,
mtod(data->m, void *), MCLBYTES, iwi_dma_map_addr,
&data->physaddr, 0);
if (error != 0) {
/* very unlikely that it will fail... */
panic("%s: could not load old rx mbuf",
device_get_name(sc->sc_dev));
}
ifp->if_ierrors++;
return;
}
/*
* New mbuf successfully loaded, update Rx ring and continue
* processing.
*/
m = data->m;
data->m = mnew;
CSR_WRITE_4(sc, data->reg, data->physaddr);
/* finalize mbuf */
m->m_pkthdr.rcvif = ifp;
m->m_pkthdr.len = m->m_len = sizeof (struct iwi_hdr) +
sizeof (struct iwi_frame) + framelen;
m_adj(m, sizeof (struct iwi_hdr) + sizeof (struct iwi_frame));
rssi = frame->rssi_dbm;
nf = -95;
if (ieee80211_radiotap_active(ic)) {
struct iwi_rx_radiotap_header *tap = &sc->sc_rxtap;
tap->wr_flags = 0;
tap->wr_antsignal = rssi;
tap->wr_antnoise = nf;
tap->wr_rate = iwi_cvtrate(frame->rate);
tap->wr_antenna = frame->antenna;
}
IWI_UNLOCK(sc);
ni = ieee80211_find_rxnode(ic, mtod(m, struct ieee80211_frame_min *));
if (ni != NULL) {
type = ieee80211_input(ni, m, rssi, nf);
ieee80211_free_node(ni);
} else
type = ieee80211_input_all(ic, m, rssi, nf);
IWI_LOCK(sc);
if (sc->sc_softled) {
/*
* Blink for any data frame. Otherwise do a
* heartbeat-style blink when idle. The latter
* is mainly for station mode where we depend on
* periodic beacon frames to trigger the poll event.
*/
if (type == IEEE80211_FC0_TYPE_DATA) {
sc->sc_rxrate = frame->rate;
iwi_led_event(sc, IWI_LED_RX);
} else if (ticks - sc->sc_ledevent >= sc->sc_ledidle)
iwi_led_event(sc, IWI_LED_POLL);
}
}
/*
* Check for an association response frame to see if QoS
* has been negotiated. We parse just enough to figure
* out if we're supposed to use QoS. The proper solution
* is to pass the frame up so ieee80211_input can do the
* work but that's made hard by how things currently are
* done in the driver.
*/
static void
iwi_checkforqos(struct ieee80211vap *vap,
const struct ieee80211_frame *wh, int len)
{
#define SUBTYPE(wh) ((wh)->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK)
const uint8_t *frm, *efrm, *wme;
struct ieee80211_node *ni;
uint16_t capinfo, status, associd;
/* NB: +8 for capinfo, status, associd, and first ie */
if (!(sizeof(*wh)+8 < len && len < IEEE80211_MAX_LEN) ||
SUBTYPE(wh) != IEEE80211_FC0_SUBTYPE_ASSOC_RESP)
return;
/*
* asresp frame format
* [2] capability information
* [2] status
* [2] association ID
* [tlv] supported rates
* [tlv] extended supported rates
* [tlv] WME
*/
frm = (const uint8_t *)&wh[1];
efrm = ((const uint8_t *) wh) + len;
capinfo = le16toh(*(const uint16_t *)frm);
frm += 2;
status = le16toh(*(const uint16_t *)frm);
frm += 2;
associd = le16toh(*(const uint16_t *)frm);
frm += 2;
wme = NULL;
while (frm < efrm) {
IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1], return);
switch (*frm) {
case IEEE80211_ELEMID_VENDOR:
if (iswmeoui(frm))
wme = frm;
break;
}
frm += frm[1] + 2;
}
ni = vap->iv_bss;
ni->ni_capinfo = capinfo;
ni->ni_associd = associd & 0x3fff;
if (wme != NULL)
ni->ni_flags |= IEEE80211_NODE_QOS;
else
ni->ni_flags &= ~IEEE80211_NODE_QOS;
#undef SUBTYPE
}
/*
* Task queue callbacks for iwi_notification_intr used to avoid LOR's.
*/
static void
iwi_notification_intr(struct iwi_softc *sc, struct iwi_notif *notif)
{
struct ifnet *ifp = sc->sc_ifp;
struct ieee80211com *ic = ifp->if_l2com;
struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
struct iwi_notif_scan_channel *chan;
struct iwi_notif_scan_complete *scan;
struct iwi_notif_authentication *auth;
struct iwi_notif_association *assoc;
struct iwi_notif_beacon_state *beacon;
switch (notif->type) {
case IWI_NOTIF_TYPE_SCAN_CHANNEL:
chan = (struct iwi_notif_scan_channel *)(notif + 1);
DPRINTFN(3, ("Scan of channel %u complete (%u)\n",
ieee80211_ieee2mhz(chan->nchan, 0), chan->nchan));
/* Reset the timer, the scan is still going */
sc->sc_state_timer = 3;
break;
case IWI_NOTIF_TYPE_SCAN_COMPLETE:
scan = (struct iwi_notif_scan_complete *)(notif + 1);
DPRINTFN(2, ("Scan completed (%u, %u)\n", scan->nchan,
scan->status));
IWI_STATE_END(sc, IWI_FW_SCANNING);
/*
* Monitor mode works by doing a passive scan to set
* the channel and enable rx. Because we don't want
* to abort a scan lest the firmware crash we scan
* for a short period of time and automatically restart
* the scan when notified the sweep has completed.
*/
if (vap->iv_opmode == IEEE80211_M_MONITOR) {
ieee80211_runtask(ic, &sc->sc_monitortask);
break;
}
if (scan->status == IWI_SCAN_COMPLETED) {
/* NB: don't need to defer, net80211 does it for us */
ieee80211_scan_next(vap);
}
break;
case IWI_NOTIF_TYPE_AUTHENTICATION:
auth = (struct iwi_notif_authentication *)(notif + 1);
switch (auth->state) {
case IWI_AUTH_SUCCESS:
DPRINTFN(2, ("Authentication succeeeded\n"));
ieee80211_new_state(vap, IEEE80211_S_ASSOC, -1);
break;
case IWI_AUTH_FAIL:
/*
* These are delivered as an unsolicited deauth
* (e.g. due to inactivity) or in response to an
* associate request.
*/
sc->flags &= ~IWI_FLAG_ASSOCIATED;
if (vap->iv_state != IEEE80211_S_RUN) {
DPRINTFN(2, ("Authentication failed\n"));
vap->iv_stats.is_rx_auth_fail++;
IWI_STATE_END(sc, IWI_FW_ASSOCIATING);
} else {
DPRINTFN(2, ("Deauthenticated\n"));
vap->iv_stats.is_rx_deauth++;
}
ieee80211_new_state(vap, IEEE80211_S_SCAN, -1);
break;
case IWI_AUTH_SENT_1:
case IWI_AUTH_RECV_2:
case IWI_AUTH_SEQ1_PASS:
break;
case IWI_AUTH_SEQ1_FAIL:
DPRINTFN(2, ("Initial authentication handshake failed; "
"you probably need shared key\n"));
vap->iv_stats.is_rx_auth_fail++;
IWI_STATE_END(sc, IWI_FW_ASSOCIATING);
/* XXX retry shared key when in auto */
break;
default:
device_printf(sc->sc_dev,
"unknown authentication state %u\n", auth->state);
break;
}
break;
case IWI_NOTIF_TYPE_ASSOCIATION:
assoc = (struct iwi_notif_association *)(notif + 1);
switch (assoc->state) {
case IWI_AUTH_SUCCESS:
/* re-association, do nothing */
break;
case IWI_ASSOC_SUCCESS:
DPRINTFN(2, ("Association succeeded\n"));
sc->flags |= IWI_FLAG_ASSOCIATED;
IWI_STATE_END(sc, IWI_FW_ASSOCIATING);
iwi_checkforqos(vap,
(const struct ieee80211_frame *)(assoc+1),
le16toh(notif->len) - sizeof(*assoc) - 1);
ieee80211_new_state(vap, IEEE80211_S_RUN, -1);
break;
case IWI_ASSOC_INIT:
sc->flags &= ~IWI_FLAG_ASSOCIATED;
switch (sc->fw_state) {
case IWI_FW_ASSOCIATING:
DPRINTFN(2, ("Association failed\n"));
IWI_STATE_END(sc, IWI_FW_ASSOCIATING);
ieee80211_new_state(vap, IEEE80211_S_SCAN, -1);
break;
case IWI_FW_DISASSOCIATING:
DPRINTFN(2, ("Dissassociated\n"));
IWI_STATE_END(sc, IWI_FW_DISASSOCIATING);
vap->iv_stats.is_rx_disassoc++;
ieee80211_new_state(vap, IEEE80211_S_SCAN, -1);
break;
}
break;
default:
device_printf(sc->sc_dev,
"unknown association state %u\n", assoc->state);
break;
}
break;
case IWI_NOTIF_TYPE_BEACON:
/* XXX check struct length */
beacon = (struct iwi_notif_beacon_state *)(notif + 1);
DPRINTFN(5, ("Beacon state (%u, %u)\n",
beacon->state, le32toh(beacon->number)));
if (beacon->state == IWI_BEACON_MISS) {
/*
* The firmware notifies us of every beacon miss
* so we need to track the count against the
* configured threshold before notifying the
* 802.11 layer.
* XXX try to roam, drop assoc only on much higher count
*/
if (le32toh(beacon->number) >= vap->iv_bmissthreshold) {
DPRINTF(("Beacon miss: %u >= %u\n",
le32toh(beacon->number),
vap->iv_bmissthreshold));
vap->iv_stats.is_beacon_miss++;
/*
* It's pointless to notify the 802.11 layer
* as it'll try to send a probe request (which
* we'll discard) and then timeout and drop us
* into scan state. Instead tell the firmware
* to disassociate and then on completion we'll
* kick the state machine to scan.
*/
ieee80211_runtask(ic, &sc->sc_disassoctask);
}
}
break;
case IWI_NOTIF_TYPE_CALIBRATION:
case IWI_NOTIF_TYPE_NOISE:
case IWI_NOTIF_TYPE_LINK_QUALITY:
DPRINTFN(5, ("Notification (%u)\n", notif->type));
break;
default:
DPRINTF(("unknown notification type %u flags 0x%x len %u\n",
notif->type, notif->flags, le16toh(notif->len)));
break;
}
}
static void
iwi_rx_intr(struct iwi_softc *sc)
{
struct iwi_rx_data *data;
struct iwi_hdr *hdr;
uint32_t hw;
hw = CSR_READ_4(sc, IWI_CSR_RX_RIDX);
for (; sc->rxq.cur != hw;) {
data = &sc->rxq.data[sc->rxq.cur];
bus_dmamap_sync(sc->rxq.data_dmat, data->map,
BUS_DMASYNC_POSTREAD);
hdr = mtod(data->m, struct iwi_hdr *);
switch (hdr->type) {
case IWI_HDR_TYPE_FRAME:
iwi_frame_intr(sc, data, sc->rxq.cur,
(struct iwi_frame *)(hdr + 1));
break;
case IWI_HDR_TYPE_NOTIF:
iwi_notification_intr(sc,
(struct iwi_notif *)(hdr + 1));
break;
default:
device_printf(sc->sc_dev, "unknown hdr type %u\n",
hdr->type);
}
DPRINTFN(15, ("rx done idx=%u\n", sc->rxq.cur));
sc->rxq.cur = (sc->rxq.cur + 1) % IWI_RX_RING_COUNT;
}
/* tell the firmware what we have processed */
hw = (hw == 0) ? IWI_RX_RING_COUNT - 1 : hw - 1;
CSR_WRITE_4(sc, IWI_CSR_RX_WIDX, hw);
}
static void
iwi_tx_intr(struct iwi_softc *sc, struct iwi_tx_ring *txq)
{
struct ifnet *ifp = sc->sc_ifp;
struct iwi_tx_data *data;
uint32_t hw;
hw = CSR_READ_4(sc, txq->csr_ridx);
for (; txq->next != hw;) {
data = &txq->data[txq->next];
bus_dmamap_sync(txq->data_dmat, data->map,
BUS_DMASYNC_POSTWRITE);
bus_dmamap_unload(txq->data_dmat, data->map);
if (data->m->m_flags & M_TXCB)
ieee80211_process_callback(data->ni, data->m, 0/*XXX*/);
m_freem(data->m);
data->m = NULL;
ieee80211_free_node(data->ni);
data->ni = NULL;
DPRINTFN(15, ("tx done idx=%u\n", txq->next));
ifp->if_opackets++;
txq->queued--;
txq->next = (txq->next + 1) % IWI_TX_RING_COUNT;
}
sc->sc_tx_timer = 0;
ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
if (sc->sc_softled)
iwi_led_event(sc, IWI_LED_TX);
iwi_start_locked(ifp);
}
static void
iwi_fatal_error_intr(struct iwi_softc *sc)
{
struct ifnet *ifp = sc->sc_ifp;
struct ieee80211com *ic = ifp->if_l2com;
struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
device_printf(sc->sc_dev, "firmware error\n");
if (vap != NULL)
ieee80211_cancel_scan(vap);
ieee80211_runtask(ic, &sc->sc_restarttask);
sc->flags &= ~IWI_FLAG_BUSY;
sc->sc_busy_timer = 0;
wakeup(sc);
}
static void
iwi_radio_off_intr(struct iwi_softc *sc)
{
struct ifnet *ifp = sc->sc_ifp;
struct ieee80211com *ic = ifp->if_l2com;
ieee80211_runtask(ic, &sc->sc_radiofftask);
}
static void
iwi_intr(void *arg)
{
struct iwi_softc *sc = arg;
uint32_t r;
IWI_LOCK_DECL;
IWI_LOCK(sc);
if ((r = CSR_READ_4(sc, IWI_CSR_INTR)) == 0 || r == 0xffffffff) {
IWI_UNLOCK(sc);
return;
}
/* acknowledge interrupts */
CSR_WRITE_4(sc, IWI_CSR_INTR, r);
if (r & IWI_INTR_FATAL_ERROR) {
iwi_fatal_error_intr(sc);
goto done;
}
if (r & IWI_INTR_FW_INITED) {
if (!(r & (IWI_INTR_FATAL_ERROR | IWI_INTR_PARITY_ERROR)))
wakeup(sc);
}
if (r & IWI_INTR_RADIO_OFF)
iwi_radio_off_intr(sc);
if (r & IWI_INTR_CMD_DONE) {
sc->flags &= ~IWI_FLAG_BUSY;
sc->sc_busy_timer = 0;
wakeup(sc);
}
if (r & IWI_INTR_TX1_DONE)
iwi_tx_intr(sc, &sc->txq[0]);
if (r & IWI_INTR_TX2_DONE)
iwi_tx_intr(sc, &sc->txq[1]);
if (r & IWI_INTR_TX3_DONE)
iwi_tx_intr(sc, &sc->txq[2]);
if (r & IWI_INTR_TX4_DONE)
iwi_tx_intr(sc, &sc->txq[3]);
if (r & IWI_INTR_RX_DONE)
iwi_rx_intr(sc);
if (r & IWI_INTR_PARITY_ERROR) {
/* XXX rate-limit */
device_printf(sc->sc_dev, "parity error\n");
}
done:
IWI_UNLOCK(sc);
}
static int
iwi_cmd(struct iwi_softc *sc, uint8_t type, void *data, uint8_t len)
{
struct iwi_cmd_desc *desc;
IWI_LOCK_ASSERT(sc);
if (sc->flags & IWI_FLAG_BUSY) {
device_printf(sc->sc_dev, "%s: cmd %d not sent, busy\n",
__func__, type);
return EAGAIN;
}
sc->flags |= IWI_FLAG_BUSY;
sc->sc_busy_timer = 2;
desc = &sc->cmdq.desc[sc->cmdq.cur];
desc->hdr.type = IWI_HDR_TYPE_COMMAND;
desc->hdr.flags = IWI_HDR_FLAG_IRQ;
desc->type = type;
desc->len = len;
memcpy(desc->data, data, len);
bus_dmamap_sync(sc->cmdq.desc_dmat, sc->cmdq.desc_map,
BUS_DMASYNC_PREWRITE);
DPRINTFN(2, ("sending command idx=%u type=%u len=%u\n", sc->cmdq.cur,
type, len));
sc->cmdq.cur = (sc->cmdq.cur + 1) % IWI_CMD_RING_COUNT;
CSR_WRITE_4(sc, IWI_CSR_CMD_WIDX, sc->cmdq.cur);
return msleep(sc, &sc->sc_mtx, 0, "iwicmd", hz);
}
static void
iwi_write_ibssnode(struct iwi_softc *sc,
const u_int8_t addr[IEEE80211_ADDR_LEN], int entry)
{
struct iwi_ibssnode node;
/* write node information into NIC memory */
memset(&node, 0, sizeof node);
IEEE80211_ADDR_COPY(node.bssid, addr);
DPRINTF(("%s mac %6D station %u\n", __func__, node.bssid, ":", entry));
CSR_WRITE_REGION_1(sc,
IWI_CSR_NODE_BASE + entry * sizeof node,
(uint8_t *)&node, sizeof node);
}
static int
iwi_tx_start(struct ifnet *ifp, struct mbuf *m0, struct ieee80211_node *ni,
int ac)
{
struct iwi_softc *sc = ifp->if_softc;
struct ieee80211vap *vap = ni->ni_vap;
struct ieee80211com *ic = ni->ni_ic;
struct iwi_node *in = (struct iwi_node *)ni;
const struct ieee80211_frame *wh;
struct ieee80211_key *k;
const struct chanAccParams *cap;
struct iwi_tx_ring *txq = &sc->txq[ac];
struct iwi_tx_data *data;
struct iwi_tx_desc *desc;
struct mbuf *mnew;
bus_dma_segment_t segs[IWI_MAX_NSEG];
int error, nsegs, hdrlen, i;
int ismcast, flags, xflags, staid;
IWI_LOCK_ASSERT(sc);
wh = mtod(m0, const struct ieee80211_frame *);
/* NB: only data frames use this path */
hdrlen = ieee80211_hdrsize(wh);
ismcast = IEEE80211_IS_MULTICAST(wh->i_addr1);
flags = xflags = 0;
if (!ismcast)
flags |= IWI_DATA_FLAG_NEED_ACK;
if (vap->iv_flags & IEEE80211_F_SHPREAMBLE)
flags |= IWI_DATA_FLAG_SHPREAMBLE;
if (IEEE80211_QOS_HAS_SEQ(wh)) {
xflags |= IWI_DATA_XFLAG_QOS;
cap = &ic->ic_wme.wme_chanParams;
if (!cap->cap_wmeParams[ac].wmep_noackPolicy)
flags &= ~IWI_DATA_FLAG_NEED_ACK;
}
/*
* This is only used in IBSS mode where the firmware expect an index
* in a h/w table instead of a destination address.
*/
if (vap->iv_opmode == IEEE80211_M_IBSS) {
if (!ismcast) {
if (in->in_station == -1) {
in->in_station = alloc_unr(sc->sc_unr);
if (in->in_station == -1) {
/* h/w table is full */
m_freem(m0);
ieee80211_free_node(ni);
ifp->if_oerrors++;
return 0;
}
iwi_write_ibssnode(sc,
ni->ni_macaddr, in->in_station);
}
staid = in->in_station;
} else {
/*
* Multicast addresses have no associated node
* so there will be no station entry. We reserve
* entry 0 for one mcast address and use that.
* If there are many being used this will be
* expensive and we'll need to do a better job
* but for now this handles the broadcast case.
*/
if (!IEEE80211_ADDR_EQ(wh->i_addr1, sc->sc_mcast)) {
IEEE80211_ADDR_COPY(sc->sc_mcast, wh->i_addr1);
iwi_write_ibssnode(sc, sc->sc_mcast, 0);
}
staid = 0;
}
} else
staid = 0;
if (wh->i_fc[1] & IEEE80211_FC1_WEP) {
k = ieee80211_crypto_encap(ni, m0);
if (k == NULL) {
m_freem(m0);
return ENOBUFS;
}
/* packet header may have moved, reset our local pointer */
wh = mtod(m0, struct ieee80211_frame *);
}
if (ieee80211_radiotap_active_vap(vap)) {
struct iwi_tx_radiotap_header *tap = &sc->sc_txtap;
tap->wt_flags = 0;
ieee80211_radiotap_tx(vap, m0);
}
data = &txq->data[txq->cur];
desc = &txq->desc[txq->cur];
/* save and trim IEEE802.11 header */
m_copydata(m0, 0, hdrlen, (caddr_t)&desc->wh);
m_adj(m0, hdrlen);
error = bus_dmamap_load_mbuf_sg(txq->data_dmat, data->map, m0, segs,
&nsegs, 0);
if (error != 0 && error != EFBIG) {
device_printf(sc->sc_dev, "could not map mbuf (error %d)\n",
error);
m_freem(m0);
return error;
}
if (error != 0) {
mnew = m_defrag(m0, M_DONTWAIT);
if (mnew == NULL) {
device_printf(sc->sc_dev,
"could not defragment mbuf\n");
m_freem(m0);
return ENOBUFS;
}
m0 = mnew;
error = bus_dmamap_load_mbuf_sg(txq->data_dmat, data->map,
m0, segs, &nsegs, 0);
if (error != 0) {
device_printf(sc->sc_dev,
"could not map mbuf (error %d)\n", error);
m_freem(m0);
return error;
}
}
data->m = m0;
data->ni = ni;
desc->hdr.type = IWI_HDR_TYPE_DATA;
desc->hdr.flags = IWI_HDR_FLAG_IRQ;
desc->station = staid;
desc->cmd = IWI_DATA_CMD_TX;
desc->len = htole16(m0->m_pkthdr.len);
desc->flags = flags;
desc->xflags = xflags;
#if 0
if (vap->iv_flags & IEEE80211_F_PRIVACY)
desc->wep_txkey = vap->iv_def_txkey;
else
#endif
desc->flags |= IWI_DATA_FLAG_NO_WEP;
desc->nseg = htole32(nsegs);
for (i = 0; i < nsegs; i++) {
desc->seg_addr[i] = htole32(segs[i].ds_addr);
desc->seg_len[i] = htole16(segs[i].ds_len);
}
bus_dmamap_sync(txq->data_dmat, data->map, BUS_DMASYNC_PREWRITE);
bus_dmamap_sync(txq->desc_dmat, txq->desc_map, BUS_DMASYNC_PREWRITE);
DPRINTFN(5, ("sending data frame txq=%u idx=%u len=%u nseg=%u\n",
ac, txq->cur, le16toh(desc->len), nsegs));
txq->queued++;
txq->cur = (txq->cur + 1) % IWI_TX_RING_COUNT;
CSR_WRITE_4(sc, txq->csr_widx, txq->cur);
return 0;
}
static int
iwi_raw_xmit(struct ieee80211_node *ni, struct mbuf *m,
const struct ieee80211_bpf_params *params)
{
/* no support; just discard */
m_freem(m);
ieee80211_free_node(ni);
return 0;
}
static void
iwi_start_locked(struct ifnet *ifp)
{
struct iwi_softc *sc = ifp->if_softc;
struct mbuf *m;
struct ieee80211_node *ni;
int ac;
IWI_LOCK_ASSERT(sc);
if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0)
return;
for (;;) {
IFQ_DRV_DEQUEUE(&ifp->if_snd, m);
if (m == NULL)
break;
ac = M_WME_GETAC(m);
if (sc->txq[ac].queued > IWI_TX_RING_COUNT - 8) {
/* there is no place left in this ring; tail drop */
/* XXX tail drop */
IFQ_DRV_PREPEND(&ifp->if_snd, m);
ifp->if_drv_flags |= IFF_DRV_OACTIVE;
break;
}
ni = (struct ieee80211_node *) m->m_pkthdr.rcvif;
if (iwi_tx_start(ifp, m, ni, ac) != 0) {
ieee80211_free_node(ni);
ifp->if_oerrors++;
break;
}
sc->sc_tx_timer = 5;
}
}
static void
iwi_start(struct ifnet *ifp)
{
struct iwi_softc *sc = ifp->if_softc;
IWI_LOCK_DECL;
IWI_LOCK(sc);
iwi_start_locked(ifp);
IWI_UNLOCK(sc);
}
static void
iwi_watchdog(void *arg)
{
struct iwi_softc *sc = arg;
struct ifnet *ifp = sc->sc_ifp;
struct ieee80211com *ic = ifp->if_l2com;
IWI_LOCK_ASSERT(sc);
if (sc->sc_tx_timer > 0) {
if (--sc->sc_tx_timer == 0) {
if_printf(ifp, "device timeout\n");
ifp->if_oerrors++;
ieee80211_runtask(ic, &sc->sc_restarttask);
}
}
if (sc->sc_state_timer > 0) {
if (--sc->sc_state_timer == 0) {
if_printf(ifp, "firmware stuck in state %d, resetting\n",
sc->fw_state);
if (sc->fw_state == IWI_FW_SCANNING) {
struct ieee80211com *ic = ifp->if_l2com;
ieee80211_cancel_scan(TAILQ_FIRST(&ic->ic_vaps));
}
ieee80211_runtask(ic, &sc->sc_restarttask);
sc->sc_state_timer = 3;
}
}
if (sc->sc_busy_timer > 0) {
if (--sc->sc_busy_timer == 0) {
if_printf(ifp, "firmware command timeout, resetting\n");
ieee80211_runtask(ic, &sc->sc_restarttask);
}
}
callout_reset(&sc->sc_wdtimer, hz, iwi_watchdog, sc);
}
static int
iwi_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
{
struct iwi_softc *sc = ifp->if_softc;
struct ieee80211com *ic = ifp->if_l2com;
struct ifreq *ifr = (struct ifreq *) data;
int error = 0, startall = 0;
IWI_LOCK_DECL;
switch (cmd) {
case SIOCSIFFLAGS:
IWI_LOCK(sc);
if (ifp->if_flags & IFF_UP) {
if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) {
iwi_init_locked(sc);
startall = 1;
}
} else {
if (ifp->if_drv_flags & IFF_DRV_RUNNING)
iwi_stop_locked(sc);
}
IWI_UNLOCK(sc);
if (startall)
ieee80211_start_all(ic);
break;
case SIOCGIFMEDIA:
error = ifmedia_ioctl(ifp, ifr, &ic->ic_media, cmd);
break;
case SIOCGIFADDR:
error = ether_ioctl(ifp, cmd, data);
break;
default:
error = EINVAL;
break;
}
return error;
}
static void
iwi_stop_master(struct iwi_softc *sc)
{
uint32_t tmp;
int ntries;
/* disable interrupts */
CSR_WRITE_4(sc, IWI_CSR_INTR_MASK, 0);
CSR_WRITE_4(sc, IWI_CSR_RST, IWI_RST_STOP_MASTER);
for (ntries = 0; ntries < 5; ntries++) {
if (CSR_READ_4(sc, IWI_CSR_RST) & IWI_RST_MASTER_DISABLED)
break;
DELAY(10);
}
if (ntries == 5)
device_printf(sc->sc_dev, "timeout waiting for master\n");
tmp = CSR_READ_4(sc, IWI_CSR_RST);
CSR_WRITE_4(sc, IWI_CSR_RST, tmp | IWI_RST_PRINCETON_RESET);
sc->flags &= ~IWI_FLAG_FW_INITED;
}
static int
iwi_reset(struct iwi_softc *sc)
{
uint32_t tmp;
int i, ntries;
iwi_stop_master(sc);
tmp = CSR_READ_4(sc, IWI_CSR_CTL);
CSR_WRITE_4(sc, IWI_CSR_CTL, tmp | IWI_CTL_INIT);
CSR_WRITE_4(sc, IWI_CSR_READ_INT, IWI_READ_INT_INIT_HOST);
/* wait for clock stabilization */
for (ntries = 0; ntries < 1000; ntries++) {
if (CSR_READ_4(sc, IWI_CSR_CTL) & IWI_CTL_CLOCK_READY)
break;
DELAY(200);
}
if (ntries == 1000) {
device_printf(sc->sc_dev,
"timeout waiting for clock stabilization\n");
return EIO;
}
tmp = CSR_READ_4(sc, IWI_CSR_RST);
CSR_WRITE_4(sc, IWI_CSR_RST, tmp | IWI_RST_SOFT_RESET);
DELAY(10);
tmp = CSR_READ_4(sc, IWI_CSR_CTL);
CSR_WRITE_4(sc, IWI_CSR_CTL, tmp | IWI_CTL_INIT);
/* clear NIC memory */
CSR_WRITE_4(sc, IWI_CSR_AUTOINC_ADDR, 0);
for (i = 0; i < 0xc000; i++)
CSR_WRITE_4(sc, IWI_CSR_AUTOINC_DATA, 0);
return 0;
}
static const struct iwi_firmware_ohdr *
iwi_setup_ofw(struct iwi_softc *sc, struct iwi_fw *fw)
{
const struct firmware *fp = fw->fp;
const struct iwi_firmware_ohdr *hdr;
if (fp->datasize < sizeof (struct iwi_firmware_ohdr)) {
device_printf(sc->sc_dev, "image '%s' too small\n", fp->name);
return NULL;
}
hdr = (const struct iwi_firmware_ohdr *)fp->data;
if ((IWI_FW_GET_MAJOR(le32toh(hdr->version)) != IWI_FW_REQ_MAJOR) ||
(IWI_FW_GET_MINOR(le32toh(hdr->version)) != IWI_FW_REQ_MINOR)) {
device_printf(sc->sc_dev, "version for '%s' %d.%d != %d.%d\n",
fp->name, IWI_FW_GET_MAJOR(le32toh(hdr->version)),
IWI_FW_GET_MINOR(le32toh(hdr->version)), IWI_FW_REQ_MAJOR,
IWI_FW_REQ_MINOR);
return NULL;
}
fw->data = ((const char *) fp->data) + sizeof(struct iwi_firmware_ohdr);
fw->size = fp->datasize - sizeof(struct iwi_firmware_ohdr);
fw->name = fp->name;
return hdr;
}
static const struct iwi_firmware_ohdr *
iwi_setup_oucode(struct iwi_softc *sc, struct iwi_fw *fw)
{
const struct iwi_firmware_ohdr *hdr;
hdr = iwi_setup_ofw(sc, fw);
if (hdr != NULL && le32toh(hdr->mode) != IWI_FW_MODE_UCODE) {
device_printf(sc->sc_dev, "%s is not a ucode image\n",
fw->name);
hdr = NULL;
}
return hdr;
}
static void
iwi_getfw(struct iwi_fw *fw, const char *fwname,
struct iwi_fw *uc, const char *ucname)
{
if (fw->fp == NULL)
fw->fp = firmware_get(fwname);
/* NB: pre-3.0 ucode is packaged separately */
if (uc->fp == NULL && fw->fp != NULL && fw->fp->version < 300)
uc->fp = firmware_get(ucname);
}
/*
* Get the required firmware images if not already loaded.
* Note that we hold firmware images so long as the device
* is marked up in case we need to reload them on device init.
* This is necessary because we re-init the device sometimes
* from a context where we cannot read from the filesystem
* (e.g. from the taskqueue thread when rfkill is re-enabled).
* XXX return 0 on success, 1 on error.
*
* NB: the order of get'ing and put'ing images here is
* intentional to support handling firmware images bundled
* by operating mode and/or all together in one file with
* the boot firmware as "master".
*/
static int
iwi_get_firmware(struct iwi_softc *sc, enum ieee80211_opmode opmode)
{
const struct iwi_firmware_hdr *hdr;
const struct firmware *fp;
/* invalidate cached firmware on mode change */
if (sc->fw_mode != opmode)
iwi_put_firmware(sc);
switch (opmode) {
case IEEE80211_M_STA:
iwi_getfw(&sc->fw_fw, "iwi_bss", &sc->fw_uc, "iwi_ucode_bss");
break;
case IEEE80211_M_IBSS:
iwi_getfw(&sc->fw_fw, "iwi_ibss", &sc->fw_uc, "iwi_ucode_ibss");
break;
case IEEE80211_M_MONITOR:
iwi_getfw(&sc->fw_fw, "iwi_monitor",
&sc->fw_uc, "iwi_ucode_monitor");
break;
default:
device_printf(sc->sc_dev, "unknown opmode %d\n", opmode);
return EINVAL;
}
fp = sc->fw_fw.fp;
if (fp == NULL) {
device_printf(sc->sc_dev, "could not load firmware\n");
goto bad;
}
if (fp->version < 300) {
/*
* Firmware prior to 3.0 was packaged as separate
* boot, firmware, and ucode images. Verify the
* ucode image was read in, retrieve the boot image
* if needed, and check version stamps for consistency.
* The version stamps in the data are also checked
* above; this is a bit paranoid but is a cheap
* safeguard against mis-packaging.
*/
if (sc->fw_uc.fp == NULL) {
device_printf(sc->sc_dev, "could not load ucode\n");
goto bad;
}
if (sc->fw_boot.fp == NULL) {
sc->fw_boot.fp = firmware_get("iwi_boot");
if (sc->fw_boot.fp == NULL) {
device_printf(sc->sc_dev,
"could not load boot firmware\n");
goto bad;
}
}
if (sc->fw_boot.fp->version != sc->fw_fw.fp->version ||
sc->fw_boot.fp->version != sc->fw_uc.fp->version) {
device_printf(sc->sc_dev,
"firmware version mismatch: "
"'%s' is %d, '%s' is %d, '%s' is %d\n",
sc->fw_boot.fp->name, sc->fw_boot.fp->version,
sc->fw_uc.fp->name, sc->fw_uc.fp->version,
sc->fw_fw.fp->name, sc->fw_fw.fp->version
);
goto bad;
}
/*
* Check and setup each image.
*/
if (iwi_setup_oucode(sc, &sc->fw_uc) == NULL ||
iwi_setup_ofw(sc, &sc->fw_boot) == NULL ||
iwi_setup_ofw(sc, &sc->fw_fw) == NULL)
goto bad;
} else {
/*
* Check and setup combined image.
*/
if (fp->datasize < sizeof(struct iwi_firmware_hdr)) {
device_printf(sc->sc_dev, "image '%s' too small\n",
fp->name);
goto bad;
}
hdr = (const struct iwi_firmware_hdr *)fp->data;
if (fp->datasize < sizeof(*hdr) + le32toh(hdr->bsize) + le32toh(hdr->usize)
+ le32toh(hdr->fsize)) {
device_printf(sc->sc_dev, "image '%s' too small (2)\n",
fp->name);
goto bad;
}
sc->fw_boot.data = ((const char *) fp->data) + sizeof(*hdr);
sc->fw_boot.size = le32toh(hdr->bsize);
sc->fw_boot.name = fp->name;
sc->fw_uc.data = sc->fw_boot.data + sc->fw_boot.size;
sc->fw_uc.size = le32toh(hdr->usize);
sc->fw_uc.name = fp->name;
sc->fw_fw.data = sc->fw_uc.data + sc->fw_uc.size;
sc->fw_fw.size = le32toh(hdr->fsize);
sc->fw_fw.name = fp->name;
}
#if 0
device_printf(sc->sc_dev, "boot %d ucode %d fw %d bytes\n",
sc->fw_boot.size, sc->fw_uc.size, sc->fw_fw.size);
#endif
sc->fw_mode = opmode;
return 0;
bad:
iwi_put_firmware(sc);
return 1;
}
static void
iwi_put_fw(struct iwi_fw *fw)
{
if (fw->fp != NULL) {
firmware_put(fw->fp, FIRMWARE_UNLOAD);
fw->fp = NULL;
}
fw->data = NULL;
fw->size = 0;
fw->name = NULL;
}
/*
* Release any cached firmware images.
*/
static void
iwi_put_firmware(struct iwi_softc *sc)
{
iwi_put_fw(&sc->fw_uc);
iwi_put_fw(&sc->fw_fw);
iwi_put_fw(&sc->fw_boot);
}
static int
iwi_load_ucode(struct iwi_softc *sc, const struct iwi_fw *fw)
{
uint32_t tmp;
const uint16_t *w;
const char *uc = fw->data;
size_t size = fw->size;
int i, ntries, error;
IWI_LOCK_ASSERT(sc);
error = 0;
CSR_WRITE_4(sc, IWI_CSR_RST, CSR_READ_4(sc, IWI_CSR_RST) |
IWI_RST_STOP_MASTER);
for (ntries = 0; ntries < 5; ntries++) {
if (CSR_READ_4(sc, IWI_CSR_RST) & IWI_RST_MASTER_DISABLED)
break;
DELAY(10);
}
if (ntries == 5) {
device_printf(sc->sc_dev, "timeout waiting for master\n");
error = EIO;
goto fail;
}
MEM_WRITE_4(sc, 0x3000e0, 0x80000000);
DELAY(5000);
tmp = CSR_READ_4(sc, IWI_CSR_RST);
tmp &= ~IWI_RST_PRINCETON_RESET;
CSR_WRITE_4(sc, IWI_CSR_RST, tmp);
DELAY(5000);
MEM_WRITE_4(sc, 0x3000e0, 0);
DELAY(1000);
MEM_WRITE_4(sc, IWI_MEM_EEPROM_EVENT, 1);
DELAY(1000);
MEM_WRITE_4(sc, IWI_MEM_EEPROM_EVENT, 0);
DELAY(1000);
MEM_WRITE_1(sc, 0x200000, 0x00);
MEM_WRITE_1(sc, 0x200000, 0x40);
DELAY(1000);
/* write microcode into adapter memory */
for (w = (const uint16_t *)uc; size > 0; w++, size -= 2)
MEM_WRITE_2(sc, 0x200010, htole16(*w));
MEM_WRITE_1(sc, 0x200000, 0x00);
MEM_WRITE_1(sc, 0x200000, 0x80);
/* wait until we get an answer */
for (ntries = 0; ntries < 100; ntries++) {
if (MEM_READ_1(sc, 0x200000) & 1)
break;
DELAY(100);
}
if (ntries == 100) {
device_printf(sc->sc_dev,
"timeout waiting for ucode to initialize\n");
error = EIO;
goto fail;
}
/* read the answer or the firmware will not initialize properly */
for (i = 0; i < 7; i++)
MEM_READ_4(sc, 0x200004);
MEM_WRITE_1(sc, 0x200000, 0x00);
fail:
return error;
}
/* macro to handle unaligned little endian data in firmware image */
#define GETLE32(p) ((p)[0] | (p)[1] << 8 | (p)[2] << 16 | (p)[3] << 24)
static int
iwi_load_firmware(struct iwi_softc *sc, const struct iwi_fw *fw)
{
u_char *p, *end;
uint32_t sentinel, ctl, src, dst, sum, len, mlen, tmp;
int ntries, error;
IWI_LOCK_ASSERT(sc);
/* copy firmware image to DMA memory */
memcpy(sc->fw_virtaddr, fw->data, fw->size);
/* make sure the adapter will get up-to-date values */
bus_dmamap_sync(sc->fw_dmat, sc->fw_map, BUS_DMASYNC_PREWRITE);
/* tell the adapter where the command blocks are stored */
MEM_WRITE_4(sc, 0x3000a0, 0x27000);
/*
* Store command blocks into adapter's internal memory using register
* indirections. The adapter will read the firmware image through DMA
* using information stored in command blocks.
*/
src = sc->fw_physaddr;
p = sc->fw_virtaddr;
end = p + fw->size;
CSR_WRITE_4(sc, IWI_CSR_AUTOINC_ADDR, 0x27000);
while (p < end) {
dst = GETLE32(p); p += 4; src += 4;
len = GETLE32(p); p += 4; src += 4;
p += len;
while (len > 0) {
mlen = min(len, IWI_CB_MAXDATALEN);
ctl = IWI_CB_DEFAULT_CTL | mlen;
sum = ctl ^ src ^ dst;
/* write a command block */
CSR_WRITE_4(sc, IWI_CSR_AUTOINC_DATA, ctl);
CSR_WRITE_4(sc, IWI_CSR_AUTOINC_DATA, src);
CSR_WRITE_4(sc, IWI_CSR_AUTOINC_DATA, dst);
CSR_WRITE_4(sc, IWI_CSR_AUTOINC_DATA, sum);
src += mlen;
dst += mlen;
len -= mlen;
}
}
/* write a fictive final command block (sentinel) */
sentinel = CSR_READ_4(sc, IWI_CSR_AUTOINC_ADDR);
CSR_WRITE_4(sc, IWI_CSR_AUTOINC_DATA, 0);
tmp = CSR_READ_4(sc, IWI_CSR_RST);
tmp &= ~(IWI_RST_MASTER_DISABLED | IWI_RST_STOP_MASTER);
CSR_WRITE_4(sc, IWI_CSR_RST, tmp);
/* tell the adapter to start processing command blocks */
MEM_WRITE_4(sc, 0x3000a4, 0x540100);
/* wait until the adapter reaches the sentinel */
for (ntries = 0; ntries < 400; ntries++) {
if (MEM_READ_4(sc, 0x3000d0) >= sentinel)
break;
DELAY(100);
}
/* sync dma, just in case */
bus_dmamap_sync(sc->fw_dmat, sc->fw_map, BUS_DMASYNC_POSTWRITE);
if (ntries == 400) {
device_printf(sc->sc_dev,
"timeout processing command blocks for %s firmware\n",
fw->name);
return EIO;
}
/* we're done with command blocks processing */
MEM_WRITE_4(sc, 0x3000a4, 0x540c00);
/* allow interrupts so we know when the firmware is ready */
CSR_WRITE_4(sc, IWI_CSR_INTR_MASK, IWI_INTR_MASK);
/* tell the adapter to initialize the firmware */
CSR_WRITE_4(sc, IWI_CSR_RST, 0);
tmp = CSR_READ_4(sc, IWI_CSR_CTL);
CSR_WRITE_4(sc, IWI_CSR_CTL, tmp | IWI_CTL_ALLOW_STANDBY);
/* wait at most one second for firmware initialization to complete */
if ((error = msleep(sc, &sc->sc_mtx, 0, "iwiinit", hz)) != 0) {
device_printf(sc->sc_dev, "timeout waiting for %s firmware "
"initialization to complete\n", fw->name);
}
return error;
}
static int
iwi_setpowermode(struct iwi_softc *sc, struct ieee80211vap *vap)
{
uint32_t data;
if (vap->iv_flags & IEEE80211_F_PMGTON) {
/* XXX set more fine-grained operation */
data = htole32(IWI_POWER_MODE_MAX);
} else
data = htole32(IWI_POWER_MODE_CAM);
DPRINTF(("Setting power mode to %u\n", le32toh(data)));
return iwi_cmd(sc, IWI_CMD_SET_POWER_MODE, &data, sizeof data);
}
static int
iwi_setwepkeys(struct iwi_softc *sc, struct ieee80211vap *vap)
{
struct iwi_wep_key wepkey;
struct ieee80211_key *wk;
int error, i;
for (i = 0; i < IEEE80211_WEP_NKID; i++) {
wk = &vap->iv_nw_keys[i];
wepkey.cmd = IWI_WEP_KEY_CMD_SETKEY;
wepkey.idx = i;
wepkey.len = wk->wk_keylen;
memset(wepkey.key, 0, sizeof wepkey.key);
memcpy(wepkey.key, wk->wk_key, wk->wk_keylen);
DPRINTF(("Setting wep key index %u len %u\n", wepkey.idx,
wepkey.len));
error = iwi_cmd(sc, IWI_CMD_SET_WEP_KEY, &wepkey,
sizeof wepkey);
if (error != 0)
return error;
}
return 0;
}
static int
iwi_config(struct iwi_softc *sc)
{
struct ifnet *ifp = sc->sc_ifp;
struct ieee80211com *ic = ifp->if_l2com;
struct iwi_configuration config;
struct iwi_rateset rs;
struct iwi_txpower power;
uint32_t data;
int error, i;
IWI_LOCK_ASSERT(sc);
DPRINTF(("Setting MAC address to %6D\n", IF_LLADDR(ifp), ":"));
error = iwi_cmd(sc, IWI_CMD_SET_MAC_ADDRESS, IF_LLADDR(ifp),
IEEE80211_ADDR_LEN);
if (error != 0)
return error;
memset(&config, 0, sizeof config);
config.bluetooth_coexistence = sc->bluetooth;
config.silence_threshold = 0x1e;
config.antenna = sc->antenna;
config.multicast_enabled = 1;
config.answer_pbreq = (ic->ic_opmode == IEEE80211_M_IBSS) ? 1 : 0;
config.disable_unicast_decryption = 1;
config.disable_multicast_decryption = 1;
if (ic->ic_opmode == IEEE80211_M_MONITOR) {
config.allow_invalid_frames = 1;
config.allow_beacon_and_probe_resp = 1;
config.allow_mgt = 1;
}
DPRINTF(("Configuring adapter\n"));
error = iwi_cmd(sc, IWI_CMD_SET_CONFIG, &config, sizeof config);
if (error != 0)
return error;
if (ic->ic_opmode == IEEE80211_M_IBSS) {
power.mode = IWI_MODE_11B;
power.nchan = 11;
for (i = 0; i < 11; i++) {
power.chan[i].chan = i + 1;
power.chan[i].power = IWI_TXPOWER_MAX;
}
DPRINTF(("Setting .11b channels tx power\n"));
error = iwi_cmd(sc, IWI_CMD_SET_TX_POWER, &power, sizeof power);
if (error != 0)
return error;
power.mode = IWI_MODE_11G;
DPRINTF(("Setting .11g channels tx power\n"));
error = iwi_cmd(sc, IWI_CMD_SET_TX_POWER, &power, sizeof power);
if (error != 0)
return error;
}
memset(&rs, 0, sizeof rs);
rs.mode = IWI_MODE_11G;
rs.type = IWI_RATESET_TYPE_SUPPORTED;
rs.nrates = ic->ic_sup_rates[IEEE80211_MODE_11G].rs_nrates;
memcpy(rs.rates, ic->ic_sup_rates[IEEE80211_MODE_11G].rs_rates,
rs.nrates);
DPRINTF(("Setting .11bg supported rates (%u)\n", rs.nrates));
error = iwi_cmd(sc, IWI_CMD_SET_RATES, &rs, sizeof rs);
if (error != 0)
return error;
memset(&rs, 0, sizeof rs);
rs.mode = IWI_MODE_11A;
rs.type = IWI_RATESET_TYPE_SUPPORTED;
rs.nrates = ic->ic_sup_rates[IEEE80211_MODE_11A].rs_nrates;
memcpy(rs.rates, ic->ic_sup_rates[IEEE80211_MODE_11A].rs_rates,
rs.nrates);
DPRINTF(("Setting .11a supported rates (%u)\n", rs.nrates));
error = iwi_cmd(sc, IWI_CMD_SET_RATES, &rs, sizeof rs);
if (error != 0)
return error;
data = htole32(arc4random());
DPRINTF(("Setting initialization vector to %u\n", le32toh(data)));
error = iwi_cmd(sc, IWI_CMD_SET_IV, &data, sizeof data);
if (error != 0)
return error;
/* enable adapter */
DPRINTF(("Enabling adapter\n"));
return iwi_cmd(sc, IWI_CMD_ENABLE, NULL, 0);
}
static __inline void
set_scan_type(struct iwi_scan_ext *scan, int ix, int scan_type)
{
uint8_t *st = &scan->scan_type[ix / 2];
if (ix % 2)
*st = (*st & 0xf0) | ((scan_type & 0xf) << 0);
else
*st = (*st & 0x0f) | ((scan_type & 0xf) << 4);
}
static int
scan_type(const struct ieee80211_scan_state *ss,
const struct ieee80211_channel *chan)
{
/* We can only set one essid for a directed scan */
if (ss->ss_nssid != 0)
return IWI_SCAN_TYPE_BDIRECTED;
if ((ss->ss_flags & IEEE80211_SCAN_ACTIVE) &&
(chan->ic_flags & IEEE80211_CHAN_PASSIVE) == 0)
return IWI_SCAN_TYPE_BROADCAST;
return IWI_SCAN_TYPE_PASSIVE;
}
static __inline int
scan_band(const struct ieee80211_channel *c)
{
return IEEE80211_IS_CHAN_5GHZ(c) ? IWI_CHAN_5GHZ : IWI_CHAN_2GHZ;
}
static void
iwi_monitor_scan(void *arg, int npending)
{
struct iwi_softc *sc = arg;
IWI_LOCK_DECL;
IWI_LOCK(sc);
(void) iwi_scanchan(sc, 2000, 0);
IWI_UNLOCK(sc);
}
/*
* Start a scan on the current channel or all channels.
*/
static int
iwi_scanchan(struct iwi_softc *sc, unsigned long maxdwell, int allchan)
{
struct ieee80211com *ic;
struct ieee80211_channel *chan;
struct ieee80211_scan_state *ss;
struct iwi_scan_ext scan;
int error = 0;
IWI_LOCK_ASSERT(sc);
if (sc->fw_state == IWI_FW_SCANNING) {
/*
* This should not happen as we only trigger scan_next after
* completion
*/
DPRINTF(("%s: called too early - still scanning\n", __func__));
return (EBUSY);
}
IWI_STATE_BEGIN(sc, IWI_FW_SCANNING);
ic = sc->sc_ifp->if_l2com;
ss = ic->ic_scan;
memset(&scan, 0, sizeof scan);
scan.full_scan_index = htole32(++sc->sc_scangen);
scan.dwell_time[IWI_SCAN_TYPE_PASSIVE] = htole16(maxdwell);
if (ic->ic_flags_ext & IEEE80211_FEXT_BGSCAN) {
/*
* Use very short dwell times for when we send probe request
* frames. Without this bg scans hang. Ideally this should
* be handled with early-termination as done by net80211 but
* that's not feasible (aborting a scan is problematic).
*/
scan.dwell_time[IWI_SCAN_TYPE_BROADCAST] = htole16(30);
scan.dwell_time[IWI_SCAN_TYPE_BDIRECTED] = htole16(30);
} else {
scan.dwell_time[IWI_SCAN_TYPE_BROADCAST] = htole16(maxdwell);
scan.dwell_time[IWI_SCAN_TYPE_BDIRECTED] = htole16(maxdwell);
}
/* We can only set one essid for a directed scan */
if (ss->ss_nssid != 0) {
error = iwi_cmd(sc, IWI_CMD_SET_ESSID, ss->ss_ssid[0].ssid,
ss->ss_ssid[0].len);
if (error)
return (error);
}
if (allchan) {
int i, next, band, b, bstart;
/*
* Convert scan list to run-length encoded channel list
* the firmware requires (preserving the order setup by
* net80211). The first entry in each run specifies the
* band and the count of items in the run.
*/
next = 0; /* next open slot */
bstart = 0; /* NB: not needed, silence compiler */
band = -1; /* NB: impossible value */
KASSERT(ss->ss_last > 0, ("no channels"));
for (i = 0; i < ss->ss_last; i++) {
chan = ss->ss_chans[i];
b = scan_band(chan);
if (b != band) {
if (band != -1)
scan.channels[bstart] =
(next - bstart) | band;
/* NB: this allocates a slot for the run-len */
band = b, bstart = next++;
}
if (next >= IWI_SCAN_CHANNELS) {
DPRINTF(("truncating scan list\n"));
break;
}
scan.channels[next] = ieee80211_chan2ieee(ic, chan);
set_scan_type(&scan, next, scan_type(ss, chan));
next++;
}
scan.channels[bstart] = (next - bstart) | band;
} else {
/* Scan the current channel only */
chan = ic->ic_curchan;
scan.channels[0] = 1 | scan_band(chan);
scan.channels[1] = ieee80211_chan2ieee(ic, chan);
set_scan_type(&scan, 1, scan_type(ss, chan));
}
#ifdef IWI_DEBUG
if (iwi_debug > 0) {
static const char *scantype[8] =
{ "PSTOP", "PASV", "DIR", "BCAST", "BDIR", "5", "6", "7" };
int i;
printf("Scan request: index %u dwell %d/%d/%d\n"
, le32toh(scan.full_scan_index)
, le16toh(scan.dwell_time[IWI_SCAN_TYPE_PASSIVE])
, le16toh(scan.dwell_time[IWI_SCAN_TYPE_BROADCAST])
, le16toh(scan.dwell_time[IWI_SCAN_TYPE_BDIRECTED])
);
i = 0;
do {
int run = scan.channels[i];
if (run == 0)
break;
printf("Scan %d %s channels:", run & 0x3f,
run & IWI_CHAN_2GHZ ? "2.4GHz" : "5GHz");
for (run &= 0x3f, i++; run > 0; run--, i++) {
uint8_t type = scan.scan_type[i/2];
printf(" %u/%s", scan.channels[i],
scantype[(i & 1 ? type : type>>4) & 7]);
}
printf("\n");
} while (i < IWI_SCAN_CHANNELS);
}
#endif
return (iwi_cmd(sc, IWI_CMD_SCAN_EXT, &scan, sizeof scan));
}
static int
iwi_set_sensitivity(struct iwi_softc *sc, int8_t rssi_dbm)
{
struct iwi_sensitivity sens;
DPRINTF(("Setting sensitivity to %d\n", rssi_dbm));
memset(&sens, 0, sizeof sens);
sens.rssi = htole16(rssi_dbm);
return iwi_cmd(sc, IWI_CMD_SET_SENSITIVITY, &sens, sizeof sens);
}
static int
iwi_auth_and_assoc(struct iwi_softc *sc, struct ieee80211vap *vap)
{
struct ieee80211com *ic = vap->iv_ic;
struct ifnet *ifp = vap->iv_ifp;
struct ieee80211_node *ni = vap->iv_bss;
struct iwi_configuration config;
struct iwi_associate *assoc = &sc->assoc;
struct iwi_rateset rs;
uint16_t capinfo;
uint32_t data;
int error, mode;
IWI_LOCK_ASSERT(sc);
if (sc->flags & IWI_FLAG_ASSOCIATED) {
DPRINTF(("Already associated\n"));
return (-1);
}
IWI_STATE_BEGIN(sc, IWI_FW_ASSOCIATING);
error = 0;
mode = 0;
if (IEEE80211_IS_CHAN_A(ic->ic_curchan))
mode = IWI_MODE_11A;
else if (IEEE80211_IS_CHAN_G(ic->ic_curchan))
mode = IWI_MODE_11G;
if (IEEE80211_IS_CHAN_B(ic->ic_curchan))
mode = IWI_MODE_11B;
if (IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan)) {
memset(&config, 0, sizeof config);
config.bluetooth_coexistence = sc->bluetooth;
config.antenna = sc->antenna;
config.multicast_enabled = 1;
if (mode == IWI_MODE_11G)
config.use_protection = 1;
config.answer_pbreq =
(vap->iv_opmode == IEEE80211_M_IBSS) ? 1 : 0;
config.disable_unicast_decryption = 1;
config.disable_multicast_decryption = 1;
DPRINTF(("Configuring adapter\n"));
error = iwi_cmd(sc, IWI_CMD_SET_CONFIG, &config, sizeof config);
if (error != 0)
goto done;
}
#ifdef IWI_DEBUG
if (iwi_debug > 0) {
printf("Setting ESSID to ");
ieee80211_print_essid(ni->ni_essid, ni->ni_esslen);
printf("\n");
}
#endif
error = iwi_cmd(sc, IWI_CMD_SET_ESSID, ni->ni_essid, ni->ni_esslen);
if (error != 0)
goto done;
error = iwi_setpowermode(sc, vap);
if (error != 0)
goto done;
data = htole32(vap->iv_rtsthreshold);
DPRINTF(("Setting RTS threshold to %u\n", le32toh(data)));
error = iwi_cmd(sc, IWI_CMD_SET_RTS_THRESHOLD, &data, sizeof data);
if (error != 0)
goto done;
data = htole32(vap->iv_fragthreshold);
DPRINTF(("Setting fragmentation threshold to %u\n", le32toh(data)));
error = iwi_cmd(sc, IWI_CMD_SET_FRAG_THRESHOLD, &data, sizeof data);
if (error != 0)
goto done;
/* the rate set has already been "negotiated" */
memset(&rs, 0, sizeof rs);
rs.mode = mode;
rs.type = IWI_RATESET_TYPE_NEGOTIATED;
rs.nrates = ni->ni_rates.rs_nrates;
if (rs.nrates > IWI_RATESET_SIZE) {
DPRINTF(("Truncating negotiated rate set from %u\n",
rs.nrates));
rs.nrates = IWI_RATESET_SIZE;
}
memcpy(rs.rates, ni->ni_rates.rs_rates, rs.nrates);
DPRINTF(("Setting negotiated rates (%u)\n", rs.nrates));
error = iwi_cmd(sc, IWI_CMD_SET_RATES, &rs, sizeof rs);
if (error != 0)
goto done;
memset(assoc, 0, sizeof *assoc);
if ((vap->iv_flags & IEEE80211_F_WME) && ni->ni_ies.wme_ie != NULL) {
/* NB: don't treat WME setup as failure */
if (iwi_wme_setparams(sc, ic) == 0 && iwi_wme_setie(sc) == 0)
assoc->policy |= htole16(IWI_POLICY_WME);
/* XXX complain on failure? */
}
if (vap->iv_appie_wpa != NULL) {
struct ieee80211_appie *ie = vap->iv_appie_wpa;
DPRINTF(("Setting optional IE (len=%u)\n", ie->ie_len));
error = iwi_cmd(sc, IWI_CMD_SET_OPTIE, ie->ie_data, ie->ie_len);
if (error != 0)
goto done;
}
error = iwi_set_sensitivity(sc, ic->ic_node_getrssi(ni));
if (error != 0)
goto done;
assoc->mode = mode;
assoc->chan = ic->ic_curchan->ic_ieee;
/*
* NB: do not arrange for shared key auth w/o privacy
* (i.e. a wep key); it causes a firmware error.
*/
if ((vap->iv_flags & IEEE80211_F_PRIVACY) &&
ni->ni_authmode == IEEE80211_AUTH_SHARED) {
assoc->auth = IWI_AUTH_SHARED;
/*
* It's possible to have privacy marked but no default
* key setup. This typically is due to a user app bug
* but if we blindly grab the key the firmware will
* barf so avoid it for now.
*/
if (vap->iv_def_txkey != IEEE80211_KEYIX_NONE)
assoc->auth |= vap->iv_def_txkey << 4;
error = iwi_setwepkeys(sc, vap);
if (error != 0)
goto done;
}
if (vap->iv_flags & IEEE80211_F_WPA)
assoc->policy |= htole16(IWI_POLICY_WPA);
if (vap->iv_opmode == IEEE80211_M_IBSS && ni->ni_tstamp.tsf == 0)
assoc->type = IWI_HC_IBSS_START;
else
assoc->type = IWI_HC_ASSOC;
memcpy(assoc->tstamp, ni->ni_tstamp.data, 8);
if (vap->iv_opmode == IEEE80211_M_IBSS)
capinfo = IEEE80211_CAPINFO_IBSS;
else
capinfo = IEEE80211_CAPINFO_ESS;
if (vap->iv_flags & IEEE80211_F_PRIVACY)
capinfo |= IEEE80211_CAPINFO_PRIVACY;
if ((ic->ic_flags & IEEE80211_F_SHPREAMBLE) &&
IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan))
capinfo |= IEEE80211_CAPINFO_SHORT_PREAMBLE;
if (ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME)
capinfo |= IEEE80211_CAPINFO_SHORT_SLOTTIME;
assoc->capinfo = htole16(capinfo);
assoc->lintval = htole16(ic->ic_lintval);
assoc->intval = htole16(ni->ni_intval);
IEEE80211_ADDR_COPY(assoc->bssid, ni->ni_bssid);
if (vap->iv_opmode == IEEE80211_M_IBSS)
IEEE80211_ADDR_COPY(assoc->dst, ifp->if_broadcastaddr);
else
IEEE80211_ADDR_COPY(assoc->dst, ni->ni_bssid);
DPRINTF(("%s bssid %6D dst %6D channel %u policy 0x%x "
"auth %u capinfo 0x%x lintval %u bintval %u\n",
assoc->type == IWI_HC_IBSS_START ? "Start" : "Join",
assoc->bssid, ":", assoc->dst, ":",
assoc->chan, le16toh(assoc->policy), assoc->auth,
le16toh(assoc->capinfo), le16toh(assoc->lintval),
le16toh(assoc->intval)));
error = iwi_cmd(sc, IWI_CMD_ASSOCIATE, assoc, sizeof *assoc);
done:
if (error)
IWI_STATE_END(sc, IWI_FW_ASSOCIATING);
return (error);
}
static void
iwi_disassoc(void *arg, int pending)
{
struct iwi_softc *sc = arg;
IWI_LOCK_DECL;
IWI_LOCK(sc);
iwi_disassociate(sc, 0);
IWI_UNLOCK(sc);
}
static int
iwi_disassociate(struct iwi_softc *sc, int quiet)
{
struct iwi_associate *assoc = &sc->assoc;
if ((sc->flags & IWI_FLAG_ASSOCIATED) == 0) {
DPRINTF(("Not associated\n"));
return (-1);
}
IWI_STATE_BEGIN(sc, IWI_FW_DISASSOCIATING);
if (quiet)
assoc->type = IWI_HC_DISASSOC_QUIET;
else
assoc->type = IWI_HC_DISASSOC;
DPRINTF(("Trying to disassociate from %6D channel %u\n",
assoc->bssid, ":", assoc->chan));
return iwi_cmd(sc, IWI_CMD_ASSOCIATE, assoc, sizeof *assoc);
}
/*
* release dma resources for the firmware
*/
static void
iwi_release_fw_dma(struct iwi_softc *sc)
{
if (sc->fw_flags & IWI_FW_HAVE_PHY)
bus_dmamap_unload(sc->fw_dmat, sc->fw_map);
if (sc->fw_flags & IWI_FW_HAVE_MAP)
bus_dmamem_free(sc->fw_dmat, sc->fw_virtaddr, sc->fw_map);
if (sc->fw_flags & IWI_FW_HAVE_DMAT)
bus_dma_tag_destroy(sc->fw_dmat);
sc->fw_flags = 0;
sc->fw_dma_size = 0;
sc->fw_dmat = NULL;
sc->fw_map = NULL;
sc->fw_physaddr = 0;
sc->fw_virtaddr = NULL;
}
/*
* allocate the dma descriptor for the firmware.
* Return 0 on success, 1 on error.
* Must be called unlocked, protected by IWI_FLAG_FW_LOADING.
*/
static int
iwi_init_fw_dma(struct iwi_softc *sc, int size)
{
if (sc->fw_dma_size >= size)
return 0;
if (bus_dma_tag_create(bus_get_dma_tag(sc->sc_dev), 4, 0,
BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL,
size, 1, size, 0, NULL, NULL, &sc->fw_dmat) != 0) {
device_printf(sc->sc_dev,
"could not create firmware DMA tag\n");
goto error;
}
sc->fw_flags |= IWI_FW_HAVE_DMAT;
if (bus_dmamem_alloc(sc->fw_dmat, &sc->fw_virtaddr, 0,
&sc->fw_map) != 0) {
device_printf(sc->sc_dev,
"could not allocate firmware DMA memory\n");
goto error;
}
sc->fw_flags |= IWI_FW_HAVE_MAP;
if (bus_dmamap_load(sc->fw_dmat, sc->fw_map, sc->fw_virtaddr,
size, iwi_dma_map_addr, &sc->fw_physaddr, 0) != 0) {
device_printf(sc->sc_dev, "could not load firmware DMA map\n");
goto error;
}
sc->fw_flags |= IWI_FW_HAVE_PHY;
sc->fw_dma_size = size;
return 0;
error:
iwi_release_fw_dma(sc);
return 1;
}
static void
iwi_init_locked(struct iwi_softc *sc)
{
struct ifnet *ifp = sc->sc_ifp;
struct iwi_rx_data *data;
int i;
IWI_LOCK_ASSERT(sc);
if (sc->fw_state == IWI_FW_LOADING) {
device_printf(sc->sc_dev, "%s: already loading\n", __func__);
return; /* XXX: condvar? */
}
iwi_stop_locked(sc);
IWI_STATE_BEGIN(sc, IWI_FW_LOADING);
if (iwi_reset(sc) != 0) {
device_printf(sc->sc_dev, "could not reset adapter\n");
goto fail;
}
if (iwi_load_firmware(sc, &sc->fw_boot) != 0) {
device_printf(sc->sc_dev,
"could not load boot firmware %s\n", sc->fw_boot.name);
goto fail;
}
if (iwi_load_ucode(sc, &sc->fw_uc) != 0) {
device_printf(sc->sc_dev,
"could not load microcode %s\n", sc->fw_uc.name);
goto fail;
}
iwi_stop_master(sc);
CSR_WRITE_4(sc, IWI_CSR_CMD_BASE, sc->cmdq.physaddr);
CSR_WRITE_4(sc, IWI_CSR_CMD_SIZE, sc->cmdq.count);
CSR_WRITE_4(sc, IWI_CSR_CMD_WIDX, sc->cmdq.cur);
CSR_WRITE_4(sc, IWI_CSR_TX1_BASE, sc->txq[0].physaddr);
CSR_WRITE_4(sc, IWI_CSR_TX1_SIZE, sc->txq[0].count);
CSR_WRITE_4(sc, IWI_CSR_TX1_WIDX, sc->txq[0].cur);
CSR_WRITE_4(sc, IWI_CSR_TX2_BASE, sc->txq[1].physaddr);
CSR_WRITE_4(sc, IWI_CSR_TX2_SIZE, sc->txq[1].count);
CSR_WRITE_4(sc, IWI_CSR_TX2_WIDX, sc->txq[1].cur);
CSR_WRITE_4(sc, IWI_CSR_TX3_BASE, sc->txq[2].physaddr);
CSR_WRITE_4(sc, IWI_CSR_TX3_SIZE, sc->txq[2].count);
CSR_WRITE_4(sc, IWI_CSR_TX3_WIDX, sc->txq[2].cur);
CSR_WRITE_4(sc, IWI_CSR_TX4_BASE, sc->txq[3].physaddr);
CSR_WRITE_4(sc, IWI_CSR_TX4_SIZE, sc->txq[3].count);
CSR_WRITE_4(sc, IWI_CSR_TX4_WIDX, sc->txq[3].cur);
for (i = 0; i < sc->rxq.count; i++) {
data = &sc->rxq.data[i];
CSR_WRITE_4(sc, data->reg, data->physaddr);
}
CSR_WRITE_4(sc, IWI_CSR_RX_WIDX, sc->rxq.count - 1);
if (iwi_load_firmware(sc, &sc->fw_fw) != 0) {
device_printf(sc->sc_dev,
"could not load main firmware %s\n", sc->fw_fw.name);
goto fail;
}
sc->flags |= IWI_FLAG_FW_INITED;
IWI_STATE_END(sc, IWI_FW_LOADING);
if (iwi_config(sc) != 0) {
device_printf(sc->sc_dev, "unable to enable adapter\n");
goto fail2;
}
callout_reset(&sc->sc_wdtimer, hz, iwi_watchdog, sc);
ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
ifp->if_drv_flags |= IFF_DRV_RUNNING;
return;
fail:
IWI_STATE_END(sc, IWI_FW_LOADING);
fail2:
iwi_stop_locked(sc);
}
static void
iwi_init(void *priv)
{
struct iwi_softc *sc = priv;
struct ifnet *ifp = sc->sc_ifp;
struct ieee80211com *ic = ifp->if_l2com;
IWI_LOCK_DECL;
IWI_LOCK(sc);
iwi_init_locked(sc);
IWI_UNLOCK(sc);
if (ifp->if_drv_flags & IFF_DRV_RUNNING)
ieee80211_start_all(ic);
}
static void
iwi_stop_locked(void *priv)
{
struct iwi_softc *sc = priv;
struct ifnet *ifp = sc->sc_ifp;
IWI_LOCK_ASSERT(sc);
ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE);
if (sc->sc_softled) {
callout_stop(&sc->sc_ledtimer);
sc->sc_blinking = 0;
}
callout_stop(&sc->sc_wdtimer);
callout_stop(&sc->sc_rftimer);
iwi_stop_master(sc);
CSR_WRITE_4(sc, IWI_CSR_RST, IWI_RST_SOFT_RESET);
/* reset rings */
iwi_reset_cmd_ring(sc, &sc->cmdq);
iwi_reset_tx_ring(sc, &sc->txq[0]);
iwi_reset_tx_ring(sc, &sc->txq[1]);
iwi_reset_tx_ring(sc, &sc->txq[2]);
iwi_reset_tx_ring(sc, &sc->txq[3]);
iwi_reset_rx_ring(sc, &sc->rxq);
sc->sc_tx_timer = 0;
sc->sc_state_timer = 0;
sc->sc_busy_timer = 0;
sc->flags &= ~(IWI_FLAG_BUSY | IWI_FLAG_ASSOCIATED);
sc->fw_state = IWI_FW_IDLE;
wakeup(sc);
}
static void
iwi_stop(struct iwi_softc *sc)
{
IWI_LOCK_DECL;
IWI_LOCK(sc);
iwi_stop_locked(sc);
IWI_UNLOCK(sc);
}
static void
iwi_restart(void *arg, int npending)
{
struct iwi_softc *sc = arg;
iwi_init(sc);
}
/*
* Return whether or not the radio is enabled in hardware
* (i.e. the rfkill switch is "off").
*/
static int
iwi_getrfkill(struct iwi_softc *sc)
{
return (CSR_READ_4(sc, IWI_CSR_IO) & IWI_IO_RADIO_ENABLED) == 0;
}
static void
iwi_radio_on(void *arg, int pending)
{
struct iwi_softc *sc = arg;
struct ieee80211com *ic = sc->sc_ifp->if_l2com;
device_printf(sc->sc_dev, "radio turned on\n");
iwi_init(sc);
ieee80211_notify_radio(ic, 1);
}
static void
iwi_rfkill_poll(void *arg)
{
struct iwi_softc *sc = arg;
IWI_LOCK_ASSERT(sc);
/*
* Check for a change in rfkill state. We get an
* interrupt when a radio is disabled but not when
* it is enabled so we must poll for the latter.
*/
if (!iwi_getrfkill(sc)) {
struct ifnet *ifp = sc->sc_ifp;
struct ieee80211com *ic = ifp->if_l2com;
ieee80211_runtask(ic, &sc->sc_radiontask);
return;
}
callout_reset(&sc->sc_rftimer, 2*hz, iwi_rfkill_poll, sc);
}
static void
iwi_radio_off(void *arg, int pending)
{
struct iwi_softc *sc = arg;
struct ieee80211com *ic = sc->sc_ifp->if_l2com;
IWI_LOCK_DECL;
device_printf(sc->sc_dev, "radio turned off\n");
ieee80211_notify_radio(ic, 0);
IWI_LOCK(sc);
iwi_stop_locked(sc);
iwi_rfkill_poll(sc);
IWI_UNLOCK(sc);
}
static int
iwi_sysctl_stats(SYSCTL_HANDLER_ARGS)
{
struct iwi_softc *sc = arg1;
uint32_t size, buf[128];
memset(buf, 0, sizeof buf);
if (!(sc->flags & IWI_FLAG_FW_INITED))
return SYSCTL_OUT(req, buf, sizeof buf);
size = min(CSR_READ_4(sc, IWI_CSR_TABLE0_SIZE), 128 - 1);
CSR_READ_REGION_4(sc, IWI_CSR_TABLE0_BASE, &buf[1], size);
return SYSCTL_OUT(req, buf, size);
}
static int
iwi_sysctl_radio(SYSCTL_HANDLER_ARGS)
{
struct iwi_softc *sc = arg1;
int val = !iwi_getrfkill(sc);
return SYSCTL_OUT(req, &val, sizeof val);
}
/*
* Add sysctl knobs.
*/
static void
iwi_sysctlattach(struct iwi_softc *sc)
{
struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(sc->sc_dev);
struct sysctl_oid *tree = device_get_sysctl_tree(sc->sc_dev);
SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "radio",
CTLTYPE_INT | CTLFLAG_RD, sc, 0, iwi_sysctl_radio, "I",
"radio transmitter switch state (0=off, 1=on)");
SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "stats",
CTLTYPE_OPAQUE | CTLFLAG_RD, sc, 0, iwi_sysctl_stats, "S",
"statistics");
sc->bluetooth = 0;
SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "bluetooth",
CTLFLAG_RW, &sc->bluetooth, 0, "bluetooth coexistence");
sc->antenna = IWI_ANTENNA_AUTO;
SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "antenna",
CTLFLAG_RW, &sc->antenna, 0, "antenna (0=auto)");
}
/*
* LED support.
*
* Different cards have different capabilities. Some have three
* led's while others have only one. The linux ipw driver defines
* led's for link state (associated or not), band (11a, 11g, 11b),
* and for link activity. We use one led and vary the blink rate
* according to the tx/rx traffic a la the ath driver.
*/
static __inline uint32_t
iwi_toggle_event(uint32_t r)
{
return r &~ (IWI_RST_STANDBY | IWI_RST_GATE_ODMA |
IWI_RST_GATE_IDMA | IWI_RST_GATE_ADMA);
}
static uint32_t
iwi_read_event(struct iwi_softc *sc)
{
return MEM_READ_4(sc, IWI_MEM_EEPROM_EVENT);
}
static void
iwi_write_event(struct iwi_softc *sc, uint32_t v)
{
MEM_WRITE_4(sc, IWI_MEM_EEPROM_EVENT, v);
}
static void
iwi_led_done(void *arg)
{
struct iwi_softc *sc = arg;
sc->sc_blinking = 0;
}
/*
* Turn the activity LED off: flip the pin and then set a timer so no
* update will happen for the specified duration.
*/
static void
iwi_led_off(void *arg)
{
struct iwi_softc *sc = arg;
uint32_t v;
v = iwi_read_event(sc);
v &= ~sc->sc_ledpin;
iwi_write_event(sc, iwi_toggle_event(v));
callout_reset(&sc->sc_ledtimer, sc->sc_ledoff, iwi_led_done, sc);
}
/*
* Blink the LED according to the specified on/off times.
*/
static void
iwi_led_blink(struct iwi_softc *sc, int on, int off)
{
uint32_t v;
v = iwi_read_event(sc);
v |= sc->sc_ledpin;
iwi_write_event(sc, iwi_toggle_event(v));
sc->sc_blinking = 1;
sc->sc_ledoff = off;
callout_reset(&sc->sc_ledtimer, on, iwi_led_off, sc);
}
static void
iwi_led_event(struct iwi_softc *sc, int event)
{
#define N(a) (sizeof(a)/sizeof(a[0]))
/* NB: on/off times from the Atheros NDIS driver, w/ permission */
static const struct {
u_int rate; /* tx/rx iwi rate */
u_int16_t timeOn; /* LED on time (ms) */
u_int16_t timeOff; /* LED off time (ms) */
} blinkrates[] = {
{ IWI_RATE_OFDM54, 40, 10 },
{ IWI_RATE_OFDM48, 44, 11 },
{ IWI_RATE_OFDM36, 50, 13 },
{ IWI_RATE_OFDM24, 57, 14 },
{ IWI_RATE_OFDM18, 67, 16 },
{ IWI_RATE_OFDM12, 80, 20 },
{ IWI_RATE_DS11, 100, 25 },
{ IWI_RATE_OFDM9, 133, 34 },
{ IWI_RATE_OFDM6, 160, 40 },
{ IWI_RATE_DS5, 200, 50 },
{ 6, 240, 58 }, /* XXX 3Mb/s if it existed */
{ IWI_RATE_DS2, 267, 66 },
{ IWI_RATE_DS1, 400, 100 },
{ 0, 500, 130 }, /* unknown rate/polling */
};
uint32_t txrate;
int j = 0; /* XXX silence compiler */
sc->sc_ledevent = ticks; /* time of last event */
if (sc->sc_blinking) /* don't interrupt active blink */
return;
switch (event) {
case IWI_LED_POLL:
j = N(blinkrates)-1;
break;
case IWI_LED_TX:
/* read current transmission rate from adapter */
txrate = CSR_READ_4(sc, IWI_CSR_CURRENT_TX_RATE);
if (blinkrates[sc->sc_txrix].rate != txrate) {
for (j = 0; j < N(blinkrates)-1; j++)
if (blinkrates[j].rate == txrate)
break;
sc->sc_txrix = j;
} else
j = sc->sc_txrix;
break;
case IWI_LED_RX:
if (blinkrates[sc->sc_rxrix].rate != sc->sc_rxrate) {
for (j = 0; j < N(blinkrates)-1; j++)
if (blinkrates[j].rate == sc->sc_rxrate)
break;
sc->sc_rxrix = j;
} else
j = sc->sc_rxrix;
break;
}
/* XXX beware of overflow */
iwi_led_blink(sc, (blinkrates[j].timeOn * hz) / 1000,
(blinkrates[j].timeOff * hz) / 1000);
#undef N
}
static int
iwi_sysctl_softled(SYSCTL_HANDLER_ARGS)
{
struct iwi_softc *sc = arg1;
int softled = sc->sc_softled;
int error;
error = sysctl_handle_int(oidp, &softled, 0, req);
if (error || !req->newptr)
return error;
softled = (softled != 0);
if (softled != sc->sc_softled) {
if (softled) {
uint32_t v = iwi_read_event(sc);
v &= ~sc->sc_ledpin;
iwi_write_event(sc, iwi_toggle_event(v));
}
sc->sc_softled = softled;
}
return 0;
}
static void
iwi_ledattach(struct iwi_softc *sc)
{
struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(sc->sc_dev);
struct sysctl_oid *tree = device_get_sysctl_tree(sc->sc_dev);
sc->sc_blinking = 0;
sc->sc_ledstate = 1;
sc->sc_ledidle = (2700*hz)/1000; /* 2.7sec */
callout_init_mtx(&sc->sc_ledtimer, &sc->sc_mtx, 0);
SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
"softled", CTLTYPE_INT | CTLFLAG_RW, sc, 0,
iwi_sysctl_softled, "I", "enable/disable software LED support");
SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
"ledpin", CTLFLAG_RW, &sc->sc_ledpin, 0,
"pin setting to turn activity LED on");
SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
"ledidle", CTLFLAG_RW, &sc->sc_ledidle, 0,
"idle time for inactivity LED (ticks)");
/* XXX for debugging */
SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
"nictype", CTLFLAG_RD, &sc->sc_nictype, 0,
"NIC type from EEPROM");
sc->sc_ledpin = IWI_RST_LED_ACTIVITY;
sc->sc_softled = 1;
sc->sc_nictype = (iwi_read_prom_word(sc, IWI_EEPROM_NIC) >> 8) & 0xff;
if (sc->sc_nictype == 1) {
/*
* NB: led's are reversed.
*/
sc->sc_ledpin = IWI_RST_LED_ASSOCIATED;
}
}
static void
iwi_scan_start(struct ieee80211com *ic)
{
/* ignore */
}
static void
iwi_set_channel(struct ieee80211com *ic)
{
struct ifnet *ifp = ic->ic_ifp;
struct iwi_softc *sc = ifp->if_softc;
if (sc->fw_state == IWI_FW_IDLE)
iwi_setcurchan(sc, ic->ic_curchan->ic_ieee);
}
static void
iwi_scan_curchan(struct ieee80211_scan_state *ss, unsigned long maxdwell)
{
struct ieee80211vap *vap = ss->ss_vap;
struct ifnet *ifp = vap->iv_ic->ic_ifp;
struct iwi_softc *sc = ifp->if_softc;
IWI_LOCK_DECL;
IWI_LOCK(sc);
if (iwi_scanchan(sc, maxdwell, 0))
ieee80211_cancel_scan(vap);
IWI_UNLOCK(sc);
}
static void
iwi_scan_mindwell(struct ieee80211_scan_state *ss)
{
/* NB: don't try to abort scan; wait for firmware to finish */
}
static void
iwi_scan_end(struct ieee80211com *ic)
{
struct ifnet *ifp = ic->ic_ifp;
struct iwi_softc *sc = ifp->if_softc;
IWI_LOCK_DECL;
IWI_LOCK(sc);
sc->flags &= ~IWI_FLAG_CHANNEL_SCAN;
/* NB: make sure we're still scanning */
if (sc->fw_state == IWI_FW_SCANNING)
iwi_cmd(sc, IWI_CMD_ABORT_SCAN, NULL, 0);
IWI_UNLOCK(sc);
}