blob: 9e07f2b8e29b1b747340072dffb2d7f475ae5bd4 [file] [log] [blame]
/* $NetBSD: if_iwn.c,v 1.78 2016/06/10 13:27:14 ozaki-r Exp $ */
/* $OpenBSD: if_iwn.c,v 1.135 2014/09/10 07:22:09 dcoppa Exp $ */
/*-
* Copyright (c) 2007-2010 Damien Bergamini <damien.bergamini@free.fr>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
* Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
*/
/*
* Driver for Intel WiFi Link 4965 and 100/1000/2000/5000/6000 Series 802.11
* network adapters.
*/
/*
* TODO:
* - turn tunables into driver properties
*/
#undef IWN_HWCRYPTO /* XXX does not even compile yet */
#include <sys/modctl.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <sys/sockio.h>
#include <sys/proc.h>
#include <sys/socket.h>
#include <sys/systm.h>
#include <sys/mutex.h>
#include <sys/conf.h>
#include <sys/pci.h>
#include <sys/pcie.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/in_var.h>
#include <netinet/ip.h>
#include <sys/dlpi.h>
#include <sys/mac_provider.h>
#include <sys/mac_wifi.h>
#include <sys/net80211.h>
#include <sys/firmload.h>
#include <sys/queue.h>
#include <sys/strsun.h>
#include <sys/strsubr.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <sys/kstat.h>
#include <sys/sdt.h>
#include "if_iwncompat.h"
#include "if_iwnreg.h"
#include "if_iwnvar.h"
#include <inet/wifi_ioctl.h>
#ifdef DEBUG
#define IWN_DEBUG
#endif
/*
* regs access attributes
*/
static ddi_device_acc_attr_t iwn_reg_accattr = {
.devacc_attr_version = DDI_DEVICE_ATTR_V0,
.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC,
.devacc_attr_dataorder = DDI_STRICTORDER_ACC,
.devacc_attr_access = DDI_DEFAULT_ACC
};
/*
* DMA access attributes for descriptor
*/
static ddi_device_acc_attr_t iwn_dma_descattr = {
.devacc_attr_version = DDI_DEVICE_ATTR_V0,
.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC,
.devacc_attr_dataorder = DDI_STRICTORDER_ACC,
.devacc_attr_access = DDI_DEFAULT_ACC
};
/*
* DMA access attributes
*/
static ddi_device_acc_attr_t iwn_dma_accattr = {
.devacc_attr_version = DDI_DEVICE_ATTR_V0,
.devacc_attr_endian_flags = DDI_NEVERSWAP_ACC,
.devacc_attr_dataorder = DDI_STRICTORDER_ACC,
.devacc_attr_access = DDI_DEFAULT_ACC
};
/*
* Supported rates for 802.11a/b/g modes (in 500Kbps unit).
*/
static const struct ieee80211_rateset iwn_rateset_11a =
{ 8, { 12, 18, 24, 36, 48, 72, 96, 108 } };
static const struct ieee80211_rateset iwn_rateset_11b =
{ 4, { 2, 4, 11, 22 } };
static const struct ieee80211_rateset iwn_rateset_11g =
{ 12, { 2, 4, 11, 22, 12, 18, 24, 36, 48, 72, 96, 108 } };
static void iwn_kstat_create(struct iwn_softc *, const char *, size_t,
kstat_t **, void **);
static void iwn_kstat_free(kstat_t *, void *, size_t);
static void iwn_kstat_init(struct iwn_softc *);
static void iwn_kstat_init_2000(struct iwn_softc *);
static void iwn_kstat_init_4965(struct iwn_softc *);
static void iwn_kstat_init_6000(struct iwn_softc *);
static void iwn_intr_teardown(struct iwn_softc *);
static int iwn_intr_add(struct iwn_softc *, int);
static int iwn_intr_setup(struct iwn_softc *);
static int iwn_attach(dev_info_t *, ddi_attach_cmd_t);
static int iwn4965_attach(struct iwn_softc *);
static int iwn5000_attach(struct iwn_softc *, uint16_t);
static int iwn_detach(dev_info_t *, ddi_detach_cmd_t);
static int iwn_quiesce(dev_info_t *);
static int iwn_nic_lock(struct iwn_softc *);
static int iwn_eeprom_lock(struct iwn_softc *);
static int iwn_init_otprom(struct iwn_softc *);
static int iwn_read_prom_data(struct iwn_softc *, uint32_t, void *, int);
static int iwn_dma_contig_alloc(struct iwn_softc *, struct iwn_dma_info *,
uint_t, uint_t, void **, ddi_device_acc_attr_t *, uint_t);
static void iwn_dma_contig_free(struct iwn_dma_info *);
static int iwn_alloc_sched(struct iwn_softc *);
static void iwn_free_sched(struct iwn_softc *);
static int iwn_alloc_kw(struct iwn_softc *);
static void iwn_free_kw(struct iwn_softc *);
static int iwn_alloc_ict(struct iwn_softc *);
static void iwn_free_ict(struct iwn_softc *);
static int iwn_alloc_fwmem(struct iwn_softc *);
static void iwn_free_fwmem(struct iwn_softc *);
static int iwn_alloc_rx_ring(struct iwn_softc *, struct iwn_rx_ring *);
static void iwn_reset_rx_ring(struct iwn_softc *, struct iwn_rx_ring *);
static void iwn_free_rx_ring(struct iwn_softc *, struct iwn_rx_ring *);
static int iwn_alloc_tx_ring(struct iwn_softc *, struct iwn_tx_ring *,
int);
static void iwn_reset_tx_ring(struct iwn_softc *, struct iwn_tx_ring *);
static void iwn_free_tx_ring(struct iwn_softc *, struct iwn_tx_ring *);
static void iwn5000_ict_reset(struct iwn_softc *);
static int iwn_read_eeprom(struct iwn_softc *);
static void iwn4965_read_eeprom(struct iwn_softc *);
#ifdef IWN_DEBUG
static void iwn4965_print_power_group(struct iwn_softc *, int);
#endif
static void iwn5000_read_eeprom(struct iwn_softc *);
static void iwn_read_eeprom_channels(struct iwn_softc *, int, uint32_t);
static void iwn_read_eeprom_enhinfo(struct iwn_softc *);
static struct ieee80211_node *iwn_node_alloc(ieee80211com_t *);
static void iwn_node_free(ieee80211_node_t *);
static void iwn_newassoc(struct ieee80211_node *, int);
static int iwn_newstate(struct ieee80211com *, enum ieee80211_state, int);
static void iwn_iter_func(void *, struct ieee80211_node *);
static void iwn_calib_timeout(void *);
static void iwn_rx_phy(struct iwn_softc *, struct iwn_rx_desc *,
struct iwn_rx_data *);
static void iwn_rx_done(struct iwn_softc *, struct iwn_rx_desc *,
struct iwn_rx_data *);
#ifndef IEEE80211_NO_HT
static void iwn_rx_compressed_ba(struct iwn_softc *, struct iwn_rx_desc *,
struct iwn_rx_data *);
#endif
static void iwn5000_rx_calib_results(struct iwn_softc *,
struct iwn_rx_desc *, struct iwn_rx_data *);
static void iwn_rx_statistics(struct iwn_softc *, struct iwn_rx_desc *,
struct iwn_rx_data *);
static void iwn4965_tx_done(struct iwn_softc *, struct iwn_rx_desc *,
struct iwn_rx_data *);
static void iwn5000_tx_done(struct iwn_softc *, struct iwn_rx_desc *,
struct iwn_rx_data *);
static void iwn_tx_done(struct iwn_softc *, struct iwn_rx_desc *, int,
uint8_t);
static void iwn_cmd_done(struct iwn_softc *, struct iwn_rx_desc *);
static void iwn_notif_intr(struct iwn_softc *);
static void iwn_wakeup_intr(struct iwn_softc *);
static void iwn_fatal_intr(struct iwn_softc *);
static uint_t iwn_intr(caddr_t, caddr_t);
static void iwn4965_update_sched(struct iwn_softc *, int, int, uint8_t,
uint16_t);
static void iwn5000_update_sched(struct iwn_softc *, int, int, uint8_t,
uint16_t);
#ifdef notyet
static void iwn5000_reset_sched(struct iwn_softc *, int, int);
#endif
static int iwn_send(ieee80211com_t *, mblk_t *, uint8_t);
static void iwn_watchdog(void *);
static int iwn_cmd(struct iwn_softc *, uint8_t, void *, int, int);
static int iwn4965_add_node(struct iwn_softc *, struct iwn_node_info *,
int);
static int iwn5000_add_node(struct iwn_softc *, struct iwn_node_info *,
int);
static int iwn_set_link_quality(struct iwn_softc *,
struct ieee80211_node *);
static int iwn_add_broadcast_node(struct iwn_softc *, int);
static void iwn_set_led(struct iwn_softc *, uint8_t, uint8_t, uint8_t);
static int iwn_set_critical_temp(struct iwn_softc *);
static int iwn_set_timing(struct iwn_softc *, struct ieee80211_node *);
static void iwn4965_power_calibration(struct iwn_softc *, int);
static int iwn4965_set_txpower(struct iwn_softc *, int);
static int iwn5000_set_txpower(struct iwn_softc *, int);
static int iwn4965_get_rssi(const struct iwn_rx_stat *);
static int iwn5000_get_rssi(const struct iwn_rx_stat *);
static int iwn_get_noise(const struct iwn_rx_general_stats *);
static int iwn4965_get_temperature(struct iwn_softc *);
static int iwn5000_get_temperature(struct iwn_softc *);
static int iwn_init_sensitivity(struct iwn_softc *);
static void iwn_collect_noise(struct iwn_softc *,
const struct iwn_rx_general_stats *);
static int iwn4965_init_gains(struct iwn_softc *);
static int iwn5000_init_gains(struct iwn_softc *);
static int iwn4965_set_gains(struct iwn_softc *);
static int iwn5000_set_gains(struct iwn_softc *);
static void iwn_tune_sensitivity(struct iwn_softc *,
const struct iwn_rx_stats *);
static int iwn_send_sensitivity(struct iwn_softc *);
static int iwn_set_pslevel(struct iwn_softc *, int, int, int);
static int iwn5000_runtime_calib(struct iwn_softc *);
static int iwn_config_bt_coex_bluetooth(struct iwn_softc *);
static int iwn_config_bt_coex_prio_table(struct iwn_softc *);
static int iwn_config_bt_coex_adv1(struct iwn_softc *);
static int iwn_config_bt_coex_adv2(struct iwn_softc *);
static int iwn_config(struct iwn_softc *);
static uint16_t iwn_get_active_dwell_time(struct iwn_softc *, uint16_t,
uint8_t);
static uint16_t iwn_limit_dwell(struct iwn_softc *, uint16_t);
static uint16_t iwn_get_passive_dwell_time(struct iwn_softc *, uint16_t);
static int iwn_scan(struct iwn_softc *, uint16_t);
static int iwn_auth(struct iwn_softc *);
static int iwn_run(struct iwn_softc *);
#ifdef IWN_HWCRYPTO
static int iwn_set_key(struct ieee80211com *, struct ieee80211_node *,
struct ieee80211_key *);
static void iwn_delete_key(struct ieee80211com *, struct ieee80211_node *,
struct ieee80211_key *);
#endif
static int iwn_wme_update(struct ieee80211com *);
#ifndef IEEE80211_NO_HT
static int iwn_ampdu_rx_start(struct ieee80211com *,
struct ieee80211_node *, uint8_t);
static void iwn_ampdu_rx_stop(struct ieee80211com *,
struct ieee80211_node *, uint8_t);
static int iwn_ampdu_tx_start(struct ieee80211com *,
struct ieee80211_node *, uint8_t);
static void iwn_ampdu_tx_stop(struct ieee80211com *,
struct ieee80211_node *, uint8_t);
static void iwn4965_ampdu_tx_start(struct iwn_softc *,
struct ieee80211_node *, uint8_t, uint16_t);
static void iwn4965_ampdu_tx_stop(struct iwn_softc *,
uint8_t, uint16_t);
static void iwn5000_ampdu_tx_start(struct iwn_softc *,
struct ieee80211_node *, uint8_t, uint16_t);
static void iwn5000_ampdu_tx_stop(struct iwn_softc *,
uint8_t, uint16_t);
#endif
static int iwn5000_query_calibration(struct iwn_softc *);
static int iwn5000_send_calibration(struct iwn_softc *);
static int iwn5000_send_wimax_coex(struct iwn_softc *);
static int iwn6000_temp_offset_calib(struct iwn_softc *);
static int iwn2000_temp_offset_calib(struct iwn_softc *);
static int iwn4965_post_alive(struct iwn_softc *);
static int iwn5000_post_alive(struct iwn_softc *);
static int iwn4965_load_bootcode(struct iwn_softc *, const uint8_t *,
int);
static int iwn4965_load_firmware(struct iwn_softc *);
static int iwn5000_load_firmware_section(struct iwn_softc *, uint32_t,
const uint8_t *, int);
static int iwn5000_load_firmware(struct iwn_softc *);
static int iwn_read_firmware_leg(struct iwn_softc *,
struct iwn_fw_info *);
static int iwn_read_firmware_tlv(struct iwn_softc *,
struct iwn_fw_info *, uint16_t);
static int iwn_read_firmware(struct iwn_softc *);
static int iwn_clock_wait(struct iwn_softc *);
static int iwn_apm_init(struct iwn_softc *);
static void iwn_apm_stop_master(struct iwn_softc *);
static void iwn_apm_stop(struct iwn_softc *);
static int iwn4965_nic_config(struct iwn_softc *);
static int iwn5000_nic_config(struct iwn_softc *);
static int iwn_hw_prepare(struct iwn_softc *);
static int iwn_hw_init(struct iwn_softc *);
static void iwn_hw_stop(struct iwn_softc *, boolean_t);
static int iwn_init(struct iwn_softc *);
static void iwn_abort_scan(void *);
static void iwn_periodic(void *);
static int iwn_fast_recover(struct iwn_softc *);
static uint8_t *ieee80211_add_ssid(uint8_t *, const uint8_t *, uint32_t);
static uint8_t *ieee80211_add_rates(uint8_t *,
const struct ieee80211_rateset *);
static uint8_t *ieee80211_add_xrates(uint8_t *,
const struct ieee80211_rateset *);
static void iwn_fix_channel(struct iwn_softc *, mblk_t *,
struct iwn_rx_stat *);
#ifdef IWN_DEBUG
#define IWN_DBG(...) iwn_dbg("?" __VA_ARGS__)
static int iwn_dbg_print = 0;
static void
iwn_dbg(const char *fmt, ...)
{
va_list ap;
if (iwn_dbg_print != 0) {
va_start(ap, fmt);
vcmn_err(CE_CONT, fmt, ap);
va_end(ap);
}
}
#else
#define IWN_DBG(...)
#endif
/*
* tunables
*/
/*
* enable 5GHz scanning
*/
int iwn_enable_5ghz = 1;
/*
* If more than 50 consecutive beacons are missed,
* we've probably lost our connection.
* If more than 5 consecutive beacons are missed,
* reinitialize the sensitivity state machine.
*/
int iwn_beacons_missed_disconnect = 50;
int iwn_beacons_missed_sensitivity = 5;
/*
* iwn_periodic interval, in units of msec
*/
int iwn_periodic_interval = 100;
/*
* scan timeout in sec
*/
int iwn_scan_timeout = 20;
static ether_addr_t etherbroadcastaddr = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
static void *iwn_state = NULL;
/*
* Mac Call Back entries
*/
static int iwn_m_stat(void *, uint_t, uint64_t *);
static int iwn_m_start(void *);
static void iwn_m_stop(void *);
static int iwn_m_unicst(void *, const uint8_t *);
static int iwn_m_multicst(void *, boolean_t, const uint8_t *);
static int iwn_m_promisc(void *, boolean_t);
static mblk_t *iwn_m_tx(void *, mblk_t *);
static void iwn_m_ioctl(void *, queue_t *, mblk_t *);
static int iwn_m_setprop(void *, const char *, mac_prop_id_t, uint_t,
const void *);
static int iwn_m_getprop(void *, const char *, mac_prop_id_t, uint_t,
void *);
static void iwn_m_propinfo(void *, const char *, mac_prop_id_t,
mac_prop_info_handle_t);
mac_callbacks_t iwn_m_callbacks = {
.mc_callbacks = MC_IOCTL | MC_SETPROP | MC_GETPROP | MC_PROPINFO,
.mc_getstat = iwn_m_stat,
.mc_start = iwn_m_start,
.mc_stop = iwn_m_stop,
.mc_setpromisc = iwn_m_promisc,
.mc_multicst = iwn_m_multicst,
.mc_unicst = iwn_m_unicst,
.mc_tx = iwn_m_tx,
.mc_reserved = NULL,
.mc_ioctl = iwn_m_ioctl,
.mc_getcapab = NULL,
.mc_open = NULL,
.mc_close = NULL,
.mc_setprop = iwn_m_setprop,
.mc_getprop = iwn_m_getprop,
.mc_propinfo = iwn_m_propinfo
};
static inline uint32_t
iwn_read(struct iwn_softc *sc, int reg)
{
/*LINTED: E_PTR_BAD_CAST_ALIGN*/
return (ddi_get32(sc->sc_regh, (uint32_t *)(sc->sc_base + reg)));
}
static inline void
iwn_write(struct iwn_softc *sc, int reg, uint32_t val)
{
/*LINTED: E_PTR_BAD_CAST_ALIGN*/
ddi_put32(sc->sc_regh, (uint32_t *)(sc->sc_base + reg), val);
}
static inline void
iwn_write_1(struct iwn_softc *sc, int reg, uint8_t val)
{
ddi_put8(sc->sc_regh, (uint8_t *)(sc->sc_base + reg), val);
}
static void
iwn_kstat_create(struct iwn_softc *sc, const char *name, size_t size,
kstat_t **ks, void **data)
{
*ks = kstat_create(ddi_driver_name(sc->sc_dip),
ddi_get_instance(sc->sc_dip), name, "misc", KSTAT_TYPE_NAMED,
size / sizeof (kstat_named_t), 0);
if (*ks == NULL)
*data = kmem_zalloc(size, KM_SLEEP);
else
*data = (*ks)->ks_data;
}
static void
iwn_kstat_free(kstat_t *ks, void *data, size_t size)
{
if (ks != NULL)
kstat_delete(ks);
else if (data != NULL)
kmem_free(data, size);
}
static void
iwn_kstat_init(struct iwn_softc *sc)
{
if (sc->sc_ks_misc != NULL)
sc->sc_ks_misc->ks_lock = &sc->sc_mtx;
if (sc->sc_ks_ant != NULL)
sc->sc_ks_ant->ks_lock = &sc->sc_mtx;
if (sc->sc_ks_sens != NULL)
sc->sc_ks_sens->ks_lock = &sc->sc_mtx;
if (sc->sc_ks_timing != NULL)
sc->sc_ks_timing->ks_lock = &sc->sc_mtx;
if (sc->sc_ks_edca != NULL)
sc->sc_ks_edca->ks_lock = &sc->sc_mtx;
kstat_named_init(&sc->sc_misc->temp,
"temperature", KSTAT_DATA_ULONG);
kstat_named_init(&sc->sc_misc->crit_temp,
"critical temperature", KSTAT_DATA_ULONG);
kstat_named_init(&sc->sc_misc->pslevel,
"power saving level", KSTAT_DATA_ULONG);
kstat_named_init(&sc->sc_misc->noise,
"noise", KSTAT_DATA_LONG);
kstat_named_init(&sc->sc_ant->tx_ant,
"TX mask", KSTAT_DATA_ULONG);
kstat_named_init(&sc->sc_ant->rx_ant,
"RX mask", KSTAT_DATA_ULONG);
kstat_named_init(&sc->sc_ant->conn_ant,
"connected mask", KSTAT_DATA_ULONG);
kstat_named_init(&sc->sc_ant->gain[0],
"gain A", KSTAT_DATA_ULONG);
kstat_named_init(&sc->sc_ant->gain[1],
"gain B", KSTAT_DATA_ULONG);
kstat_named_init(&sc->sc_ant->gain[2],
"gain C", KSTAT_DATA_ULONG);
kstat_named_init(&sc->sc_sens->ofdm_x1,
"OFDM X1", KSTAT_DATA_ULONG);
kstat_named_init(&sc->sc_sens->ofdm_mrc_x1,
"OFDM MRC X1", KSTAT_DATA_ULONG);
kstat_named_init(&sc->sc_sens->ofdm_x4,
"OFDM X4", KSTAT_DATA_ULONG);
kstat_named_init(&sc->sc_sens->ofdm_mrc_x4,
"OFDM MRC X4", KSTAT_DATA_ULONG);
kstat_named_init(&sc->sc_sens->cck_x4,
"CCK X4", KSTAT_DATA_ULONG);
kstat_named_init(&sc->sc_sens->cck_mrc_x4,
"CCK MRC X4", KSTAT_DATA_ULONG);
kstat_named_init(&sc->sc_sens->energy_cck,
"energy CCK", KSTAT_DATA_ULONG);
kstat_named_init(&sc->sc_timing->bintval,
"bintval", KSTAT_DATA_ULONG);
kstat_named_init(&sc->sc_timing->tstamp,
"timestamp", KSTAT_DATA_ULONGLONG);
kstat_named_init(&sc->sc_timing->init,
"init", KSTAT_DATA_ULONG);
kstat_named_init(&sc->sc_edca->ac[0].cwmin,
"background cwmin", KSTAT_DATA_ULONG);
kstat_named_init(&sc->sc_edca->ac[0].cwmax,
"background cwmax", KSTAT_DATA_ULONG);
kstat_named_init(&sc->sc_edca->ac[0].aifsn,
"background aifsn", KSTAT_DATA_ULONG);
kstat_named_init(&sc->sc_edca->ac[0].txop,
"background txop", KSTAT_DATA_ULONG);
kstat_named_init(&sc->sc_edca->ac[1].cwmin,
"best effort cwmin", KSTAT_DATA_ULONG);
kstat_named_init(&sc->sc_edca->ac[1].cwmax,
"best effort cwmax", KSTAT_DATA_ULONG);
kstat_named_init(&sc->sc_edca->ac[1].aifsn,
"best effort aifsn", KSTAT_DATA_ULONG);
kstat_named_init(&sc->sc_edca->ac[1].txop,
"best effort txop", KSTAT_DATA_ULONG);
kstat_named_init(&sc->sc_edca->ac[2].cwmin,
"video cwmin", KSTAT_DATA_ULONG);
kstat_named_init(&sc->sc_edca->ac[2].cwmax,
"video cwmax", KSTAT_DATA_ULONG);
kstat_named_init(&sc->sc_edca->ac[2].aifsn,
"video aifsn", KSTAT_DATA_ULONG);
kstat_named_init(&sc->sc_edca->ac[2].txop,
"video txop", KSTAT_DATA_ULONG);
kstat_named_init(&sc->sc_edca->ac[3].cwmin,
"voice cwmin", KSTAT_DATA_ULONG);
kstat_named_init(&sc->sc_edca->ac[3].cwmax,
"voice cwmax", KSTAT_DATA_ULONG);
kstat_named_init(&sc->sc_edca->ac[3].aifsn,
"voice aifsn", KSTAT_DATA_ULONG);
kstat_named_init(&sc->sc_edca->ac[3].txop,
"voice txop", KSTAT_DATA_ULONG);
}
static void
iwn_kstat_init_2000(struct iwn_softc *sc)
{
if (sc->sc_ks_toff != NULL)
sc->sc_ks_toff->ks_lock = &sc->sc_mtx;
kstat_named_init(&sc->sc_toff.t2000->toff_lo,
"temperature offset low", KSTAT_DATA_LONG);
kstat_named_init(&sc->sc_toff.t2000->toff_hi,
"temperature offset high", KSTAT_DATA_LONG);
kstat_named_init(&sc->sc_toff.t2000->volt,
"reference voltage", KSTAT_DATA_LONG);
}
static void
iwn_kstat_init_4965(struct iwn_softc *sc)
{
int i, r;
if (sc->sc_ks_txpower != NULL)
sc->sc_ks_txpower->ks_lock = &sc->sc_mtx;
kstat_named_init(&sc->sc_txpower->vdiff,
"voltage comp", KSTAT_DATA_LONG);
kstat_named_init(&sc->sc_txpower->chan,
"channel", KSTAT_DATA_LONG);
kstat_named_init(&sc->sc_txpower->group,
"attenuation group", KSTAT_DATA_LONG);
kstat_named_init(&sc->sc_txpower->subband,
"sub-band", KSTAT_DATA_LONG);
for (i = 0; i != 2; i++) {
char tmp[KSTAT_STRLEN];
(void) snprintf(tmp, KSTAT_STRLEN - 1, "Ant %d power", i);
kstat_named_init(&sc->sc_txpower->txchain[i].power,
tmp, KSTAT_DATA_LONG);
(void) snprintf(tmp, KSTAT_STRLEN - 1, "Ant %d gain", i);
kstat_named_init(&sc->sc_txpower->txchain[i].gain,
tmp, KSTAT_DATA_LONG);
(void) snprintf(tmp, KSTAT_STRLEN - 1, "Ant %d temperature", i);
kstat_named_init(&sc->sc_txpower->txchain[i].temp,
tmp, KSTAT_DATA_LONG);
(void) snprintf(tmp, KSTAT_STRLEN - 1,
"Ant %d temperature compensation", i);
kstat_named_init(&sc->sc_txpower->txchain[i].tcomp,
tmp, KSTAT_DATA_LONG);
for (r = 0; r <= IWN_RIDX_MAX; r++) {
(void) snprintf(tmp, KSTAT_STRLEN - 1,
"Ant %d Rate %d RF gain", i, r);
kstat_named_init(
&sc->sc_txpower->txchain[i].rate[r].rf_gain,
tmp, KSTAT_DATA_LONG);
(void) snprintf(tmp, KSTAT_STRLEN - 1,
"Ant %d Rate %d DSP gain", i, r);
kstat_named_init(
&sc->sc_txpower->txchain[0].rate[0].dsp_gain,
tmp, KSTAT_DATA_LONG);
}
}
}
static void
iwn_kstat_init_6000(struct iwn_softc *sc)
{
if (sc->sc_ks_toff != NULL)
sc->sc_ks_toff->ks_lock = &sc->sc_mtx;
kstat_named_init(&sc->sc_toff.t6000->toff,
"temperature offset", KSTAT_DATA_LONG);
}
static void
iwn_intr_teardown(struct iwn_softc *sc)
{
if (sc->sc_intr_htable != NULL) {
if ((sc->sc_intr_cap & DDI_INTR_FLAG_BLOCK) != 0) {
(void) ddi_intr_block_disable(sc->sc_intr_htable,
sc->sc_intr_count);
} else {
(void) ddi_intr_disable(sc->sc_intr_htable[0]);
}
(void) ddi_intr_remove_handler(sc->sc_intr_htable[0]);
(void) ddi_intr_free(sc->sc_intr_htable[0]);
sc->sc_intr_htable[0] = NULL;
kmem_free(sc->sc_intr_htable, sc->sc_intr_size);
sc->sc_intr_size = 0;
sc->sc_intr_htable = NULL;
}
}
static int
iwn_intr_add(struct iwn_softc *sc, int intr_type)
{
int ni, na;
int ret;
char *func;
if (ddi_intr_get_nintrs(sc->sc_dip, intr_type, &ni) != DDI_SUCCESS)
return (DDI_FAILURE);
if (ddi_intr_get_navail(sc->sc_dip, intr_type, &na) != DDI_SUCCESS)
return (DDI_FAILURE);
sc->sc_intr_size = sizeof (ddi_intr_handle_t);
sc->sc_intr_htable = kmem_zalloc(sc->sc_intr_size, KM_SLEEP);
ret = ddi_intr_alloc(sc->sc_dip, sc->sc_intr_htable, intr_type, 0, 1,
&sc->sc_intr_count, DDI_INTR_ALLOC_STRICT);
if (ret != DDI_SUCCESS) {
dev_err(sc->sc_dip, CE_WARN, "!ddi_intr_alloc() failed");
return (DDI_FAILURE);
}
ret = ddi_intr_get_pri(sc->sc_intr_htable[0], &sc->sc_intr_pri);
if (ret != DDI_SUCCESS) {
dev_err(sc->sc_dip, CE_WARN, "!ddi_intr_get_pri() failed");
return (DDI_FAILURE);
}
ret = ddi_intr_add_handler(sc->sc_intr_htable[0], iwn_intr, (caddr_t)sc,
NULL);
if (ret != DDI_SUCCESS) {
dev_err(sc->sc_dip, CE_WARN, "!ddi_intr_add_handler() failed");
return (DDI_FAILURE);
}
ret = ddi_intr_get_cap(sc->sc_intr_htable[0], &sc->sc_intr_cap);
if (ret != DDI_SUCCESS) {
dev_err(sc->sc_dip, CE_WARN, "!ddi_intr_get_cap() failed");
return (DDI_FAILURE);
}
if ((sc->sc_intr_cap & DDI_INTR_FLAG_BLOCK) != 0) {
ret = ddi_intr_block_enable(sc->sc_intr_htable,
sc->sc_intr_count);
func = "ddi_intr_enable_block";
} else {
ret = ddi_intr_enable(sc->sc_intr_htable[0]);
func = "ddi_intr_enable";
}
if (ret != DDI_SUCCESS) {
dev_err(sc->sc_dip, CE_WARN, "!%s() failed", func);
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
static int
iwn_intr_setup(struct iwn_softc *sc)
{
int intr_type;
int ret;
ret = ddi_intr_get_supported_types(sc->sc_dip, &intr_type);
if (ret != DDI_SUCCESS) {
dev_err(sc->sc_dip, CE_WARN,
"!ddi_intr_get_supported_types() failed");
return (DDI_FAILURE);
}
if ((intr_type & DDI_INTR_TYPE_MSIX)) {
if (iwn_intr_add(sc, DDI_INTR_TYPE_MSIX) == DDI_SUCCESS)
return (DDI_SUCCESS);
iwn_intr_teardown(sc);
}
if ((intr_type & DDI_INTR_TYPE_MSI)) {
if (iwn_intr_add(sc, DDI_INTR_TYPE_MSI) == DDI_SUCCESS)
return (DDI_SUCCESS);
iwn_intr_teardown(sc);
}
if ((intr_type & DDI_INTR_TYPE_FIXED)) {
if (iwn_intr_add(sc, DDI_INTR_TYPE_FIXED) == DDI_SUCCESS)
return (DDI_SUCCESS);
iwn_intr_teardown(sc);
}
dev_err(sc->sc_dip, CE_WARN, "!iwn_intr_setup() failed");
return (DDI_FAILURE);
}
static int
iwn_pci_get_capability(ddi_acc_handle_t pcih, int cap, int *cap_off)
{
uint8_t ptr;
uint8_t val;
for (ptr = pci_config_get8(pcih, PCI_CONF_CAP_PTR);
ptr != 0 && ptr != 0xff;
ptr = pci_config_get8(pcih, ptr + PCI_CAP_NEXT_PTR)) {
val = pci_config_get8(pcih, ptr + PCIE_CAP_ID);
if (val == 0xff)
return (DDI_FAILURE);
if (cap != val)
continue;
*cap_off = ptr;
return (DDI_SUCCESS);
}
return (DDI_FAILURE);
}
static int
iwn_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
int instance;
struct iwn_softc *sc;
struct ieee80211com *ic;
char strbuf[32];
wifi_data_t wd = { 0 };
mac_register_t *macp;
uint32_t reg;
int i, error;
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
instance = ddi_get_instance(dip);
sc = ddi_get_soft_state(iwn_state,
instance);
ASSERT(sc != NULL);
if (sc->sc_flags & IWN_FLAG_RUNNING) {
(void) iwn_init(sc);
}
sc->sc_flags &= ~IWN_FLAG_SUSPEND;
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
instance = ddi_get_instance(dip);
if (ddi_soft_state_zalloc(iwn_state, instance) != DDI_SUCCESS) {
dev_err(dip, CE_WARN, "!ddi_soft_state_zalloc() failed");
return (DDI_FAILURE);
}
sc = ddi_get_soft_state(iwn_state, instance);
ddi_set_driver_private(dip, (caddr_t)sc);
ic = &sc->sc_ic;
sc->sc_dip = dip;
iwn_kstat_create(sc, "hw_state", sizeof (struct iwn_ks_misc),
&sc->sc_ks_misc, (void **)&sc->sc_misc);
iwn_kstat_create(sc, "antennas", sizeof (struct iwn_ks_ant),
&sc->sc_ks_ant, (void **)&sc->sc_ant);
iwn_kstat_create(sc, "sensitivity", sizeof (struct iwn_ks_sens),
&sc->sc_ks_sens, (void **)&sc->sc_sens);
iwn_kstat_create(sc, "timing", sizeof (struct iwn_ks_timing),
&sc->sc_ks_timing, (void **)&sc->sc_timing);
iwn_kstat_create(sc, "edca", sizeof (struct iwn_ks_edca),
&sc->sc_ks_edca, (void **)&sc->sc_edca);
if (pci_config_setup(dip, &sc->sc_pcih) != DDI_SUCCESS) {
dev_err(sc->sc_dip, CE_WARN, "!pci_config_setup() failed");
goto fail_pci_config;
}
/*
* Get the offset of the PCI Express Capability Structure in PCI
* Configuration Space.
*/
error = iwn_pci_get_capability(sc->sc_pcih, PCI_CAP_ID_PCI_E,
&sc->sc_cap_off);
if (error != DDI_SUCCESS) {
dev_err(sc->sc_dip, CE_WARN,
"!PCIe capability structure not found!");
goto fail_pci_capab;
}
/* Clear device-specific "PCI retry timeout" register (41h). */
reg = pci_config_get8(sc->sc_pcih, 0x41);
if (reg)
pci_config_put8(sc->sc_pcih, 0x41, 0);
error = ddi_regs_map_setup(dip, 1, &sc->sc_base, 0, 0, &iwn_reg_accattr,
&sc->sc_regh);
if (error != DDI_SUCCESS) {
dev_err(sc->sc_dip, CE_WARN, "!ddi_regs_map_setup() failed");
goto fail_regs_map;
}
/* Clear pending interrupts. */
IWN_WRITE(sc, IWN_INT, 0xffffffff);
/* Disable all interrupts. */
IWN_WRITE(sc, IWN_INT_MASK, 0);
/* Install interrupt handler. */
if (iwn_intr_setup(sc) != DDI_SUCCESS)
goto fail_intr;
mutex_init(&sc->sc_mtx, NULL, MUTEX_DRIVER,
DDI_INTR_PRI(sc->sc_intr_pri));
mutex_init(&sc->sc_tx_mtx, NULL, MUTEX_DRIVER,
DDI_INTR_PRI(sc->sc_intr_pri));
mutex_init(&sc->sc_mt_mtx, NULL, MUTEX_DRIVER,
DDI_INTR_PRI(sc->sc_intr_pri));
cv_init(&sc->sc_cmd_cv, NULL, CV_DRIVER, NULL);
cv_init(&sc->sc_scan_cv, NULL, CV_DRIVER, NULL);
cv_init(&sc->sc_fhdma_cv, NULL, CV_DRIVER, NULL);
cv_init(&sc->sc_alive_cv, NULL, CV_DRIVER, NULL);
cv_init(&sc->sc_calib_cv, NULL, CV_DRIVER, NULL);
iwn_kstat_init(sc);
/* Read hardware revision and attach. */
sc->hw_type =
(IWN_READ(sc, IWN_HW_REV) & IWN_HW_REV_TYPE_MASK)
>> IWN_HW_REV_TYPE_SHIFT;
if (sc->hw_type == IWN_HW_REV_TYPE_4965)
error = iwn4965_attach(sc);
else
error = iwn5000_attach(sc, sc->sc_devid);
if (error != 0) {
dev_err(sc->sc_dip, CE_WARN, "!could not attach device");
goto fail_hw;
}
if ((error = iwn_hw_prepare(sc)) != 0) {
dev_err(sc->sc_dip, CE_WARN, "!hardware not ready");
goto fail_hw;
}
/* Read MAC address, channels, etc from EEPROM. */
if ((error = iwn_read_eeprom(sc)) != 0) {
dev_err(sc->sc_dip, CE_WARN, "!could not read EEPROM");
goto fail_hw;
}
/* Allocate DMA memory for firmware transfers. */
if ((error = iwn_alloc_fwmem(sc)) != 0) {
dev_err(sc->sc_dip, CE_WARN,
"!could not allocate memory for firmware");
goto fail_fwmem;
}
/* Allocate "Keep Warm" page. */
if ((error = iwn_alloc_kw(sc)) != 0) {
dev_err(sc->sc_dip, CE_WARN,
"!could not allocate keep warm page");
goto fail_kw;
}
/* Allocate ICT table for 5000 Series. */
if (sc->hw_type != IWN_HW_REV_TYPE_4965 &&
(error = iwn_alloc_ict(sc)) != 0) {
dev_err(sc->sc_dip, CE_WARN, "!could not allocate ICT table");
goto fail_ict;
}
/* Allocate TX scheduler "rings". */
if ((error = iwn_alloc_sched(sc)) != 0) {
dev_err(sc->sc_dip, CE_WARN,
"!could not allocate TX scheduler rings");
goto fail_sched;
}
/* Allocate TX rings (16 on 4965AGN, 20 on >=5000). */
for (i = 0; i < sc->ntxqs; i++) {
if ((error = iwn_alloc_tx_ring(sc, &sc->txq[i], i)) != 0) {
dev_err(sc->sc_dip, CE_WARN,
"!could not allocate TX ring %d", i);
while (--i >= 0)
iwn_free_tx_ring(sc, &sc->txq[i]);
goto fail_txring;
}
}
/* Allocate RX ring. */
if ((error = iwn_alloc_rx_ring(sc, &sc->rxq)) != 0) {
dev_err(sc->sc_dip, CE_WARN, "!could not allocate RX ring");
goto fail_rxring;
}
/* Clear pending interrupts. */
IWN_WRITE(sc, IWN_INT, 0xffffffff);
/* Count the number of available chains. */
sc->ntxchains =
((sc->txchainmask >> 2) & 1) +
((sc->txchainmask >> 1) & 1) +
((sc->txchainmask >> 0) & 1);
sc->nrxchains =
((sc->rxchainmask >> 2) & 1) +
((sc->rxchainmask >> 1) & 1) +
((sc->rxchainmask >> 0) & 1);
dev_err(sc->sc_dip, CE_CONT, "!MIMO %dT%dR, %s, address %s",
sc->ntxchains, sc->nrxchains, sc->eeprom_domain,
ieee80211_macaddr_sprintf(ic->ic_macaddr));
sc->sc_ant->tx_ant.value.ul = sc->txchainmask;
sc->sc_ant->rx_ant.value.ul = sc->rxchainmask;
ic->ic_phytype = IEEE80211_T_OFDM; /* not only, but not used */
ic->ic_opmode = IEEE80211_M_STA; /* default to BSS mode */
ic->ic_state = IEEE80211_S_INIT;
/* Set device capabilities. */
/* XXX OpenBSD has IEEE80211_C_WEP, IEEE80211_C_RSN,
* and IEEE80211_C_PMGT too. */
ic->ic_caps =
IEEE80211_C_IBSS | /* IBSS mode support */
IEEE80211_C_WPA | /* 802.11i */
IEEE80211_C_MONITOR | /* monitor mode supported */
IEEE80211_C_TXPMGT | /* tx power management */
IEEE80211_C_SHSLOT | /* short slot time supported */
IEEE80211_C_SHPREAMBLE | /* short preamble supported */
IEEE80211_C_WME; /* 802.11e */
#ifndef IEEE80211_NO_HT
if (sc->sc_flags & IWN_FLAG_HAS_11N) {
/* Set HT capabilities. */
ic->ic_htcaps =
#if IWN_RBUF_SIZE == 8192
IEEE80211_HTCAP_AMSDU7935 |
#endif
IEEE80211_HTCAP_CBW20_40 |
IEEE80211_HTCAP_SGI20 |
IEEE80211_HTCAP_SGI40;
if (sc->hw_type != IWN_HW_REV_TYPE_4965)
ic->ic_htcaps |= IEEE80211_HTCAP_GF;
if (sc->hw_type == IWN_HW_REV_TYPE_6050)
ic->ic_htcaps |= IEEE80211_HTCAP_SMPS_DYN;
else
ic->ic_htcaps |= IEEE80211_HTCAP_SMPS_DIS;
}
#endif /* !IEEE80211_NO_HT */
/* Set supported legacy rates. */
ic->ic_sup_rates[IEEE80211_MODE_11B] = iwn_rateset_11b;
ic->ic_sup_rates[IEEE80211_MODE_11G] = iwn_rateset_11g;
if (sc->sc_flags & IWN_FLAG_HAS_5GHZ) {
ic->ic_sup_rates[IEEE80211_MODE_11A] = iwn_rateset_11a;
}
#ifndef IEEE80211_NO_HT
if (sc->sc_flags & IWN_FLAG_HAS_11N) {
/* Set supported HT rates. */
ic->ic_sup_mcs[0] = 0xff; /* MCS 0-7 */
if (sc->nrxchains > 1)
ic->ic_sup_mcs[1] = 0xff; /* MCS 7-15 */
if (sc->nrxchains > 2)
ic->ic_sup_mcs[2] = 0xff; /* MCS 16-23 */
}
#endif
/* IBSS channel undefined for now. */
ic->ic_ibss_chan = &ic->ic_sup_channels[0];
ic->ic_node_newassoc = iwn_newassoc;
ic->ic_xmit = iwn_send;
#ifdef IWN_HWCRYPTO
ic->ic_crypto.cs_key_set = iwn_set_key;
ic->ic_crypto.cs_key_delete = iwn_delete_key;
#endif
ic->ic_wme.wme_update = iwn_wme_update;
#ifndef IEEE80211_NO_HT
ic->ic_ampdu_rx_start = iwn_ampdu_rx_start;
ic->ic_ampdu_rx_stop = iwn_ampdu_rx_stop;
ic->ic_ampdu_tx_start = iwn_ampdu_tx_start;
ic->ic_ampdu_tx_stop = iwn_ampdu_tx_stop;
#endif
/*
* attach to 802.11 module
*/
ieee80211_attach(ic);
ieee80211_register_door(ic, ddi_driver_name(dip), ddi_get_instance(dip));
/* Override 802.11 state transition machine. */
sc->sc_newstate = ic->ic_newstate;
ic->ic_newstate = iwn_newstate;
ic->ic_watchdog = iwn_watchdog;
ic->ic_node_alloc = iwn_node_alloc;
ic->ic_node_free = iwn_node_free;
ieee80211_media_init(ic);
/*
* initialize default tx key
*/
ic->ic_def_txkey = 0;
sc->amrr.amrr_min_success_threshold = 1;
sc->amrr.amrr_max_success_threshold = 15;
/*
* Initialize pointer to device specific functions
*/
wd.wd_secalloc = WIFI_SEC_NONE;
wd.wd_opmode = ic->ic_opmode;
IEEE80211_ADDR_COPY(wd.wd_bssid, ic->ic_macaddr);
/*
* create relation to GLD
*/
macp = mac_alloc(MAC_VERSION);
if (NULL == macp) {
dev_err(sc->sc_dip, CE_WARN, "!mac_alloc() failed");
goto fail_mac_alloc;
}
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 = &iwn_m_callbacks;
macp->m_min_sdu = 0;
macp->m_max_sdu = IEEE80211_MTU;
macp->m_pdata = &wd;
macp->m_pdata_size = sizeof (wd);
/*
* Register the macp to mac
*/
error = mac_register(macp, &ic->ic_mach);
mac_free(macp);
if (error != DDI_SUCCESS) {
dev_err(sc->sc_dip, CE_WARN, "!mac_register() failed");
goto fail_mac_alloc;
}
/*
* Create minor node of type DDI_NT_NET_WIFI
*/
(void) snprintf(strbuf, sizeof (strbuf), "iwn%d", instance);
error = ddi_create_minor_node(dip, strbuf, S_IFCHR,
instance + 1, DDI_NT_NET_WIFI, 0);
if (error != DDI_SUCCESS) {
dev_err(sc->sc_dip, CE_WARN, "!ddi_create_minor_node() failed");
goto fail_minor;
}
/*
* Notify link is down now
*/
mac_link_update(ic->ic_mach, LINK_STATE_DOWN);
sc->sc_periodic = ddi_periodic_add(iwn_periodic, sc,
iwn_periodic_interval * MICROSEC, 0);
if (sc->sc_ks_misc)
kstat_install(sc->sc_ks_misc);
if (sc->sc_ks_ant)
kstat_install(sc->sc_ks_ant);
if (sc->sc_ks_sens)
kstat_install(sc->sc_ks_sens);
if (sc->sc_ks_timing)
kstat_install(sc->sc_ks_timing);
if (sc->sc_ks_edca)
kstat_install(sc->sc_ks_edca);
if (sc->sc_ks_txpower)
kstat_install(sc->sc_ks_txpower);
if (sc->sc_ks_toff)
kstat_install(sc->sc_ks_toff);
sc->sc_flags |= IWN_FLAG_ATTACHED;
return (DDI_SUCCESS);
/* Free allocated memory if something failed during attachment. */
fail_minor:
mac_unregister(ic->ic_mach);
fail_mac_alloc:
ieee80211_detach(ic);
iwn_free_rx_ring(sc, &sc->rxq);
fail_rxring:
for (i = 0; i < sc->ntxqs; i++)
iwn_free_tx_ring(sc, &sc->txq[i]);
fail_txring:
iwn_free_sched(sc);
fail_sched:
if (sc->ict != NULL)
iwn_free_ict(sc);
fail_ict:
iwn_free_kw(sc);
fail_kw:
iwn_free_fwmem(sc);
fail_fwmem:
fail_hw:
iwn_intr_teardown(sc);
iwn_kstat_free(sc->sc_ks_txpower, sc->sc_txpower,
sizeof (struct iwn_ks_txpower));
if (sc->hw_type == IWN_HW_REV_TYPE_6005)
iwn_kstat_free(sc->sc_ks_toff, sc->sc_toff.t6000,
sizeof (struct iwn_ks_toff_6000));
else
iwn_kstat_free(sc->sc_ks_toff, sc->sc_toff.t2000,
sizeof (struct iwn_ks_toff_2000));
fail_intr:
ddi_regs_map_free(&sc->sc_regh);
fail_regs_map:
fail_pci_capab:
pci_config_teardown(&sc->sc_pcih);
fail_pci_config:
iwn_kstat_free(sc->sc_ks_misc, sc->sc_misc,
sizeof (struct iwn_ks_misc));
iwn_kstat_free(sc->sc_ks_ant, sc->sc_ant,
sizeof (struct iwn_ks_ant));
iwn_kstat_free(sc->sc_ks_sens, sc->sc_sens,
sizeof (struct iwn_ks_sens));
iwn_kstat_free(sc->sc_ks_timing, sc->sc_timing,
sizeof (struct iwn_ks_timing));
iwn_kstat_free(sc->sc_ks_edca, sc->sc_edca,
sizeof (struct iwn_ks_edca));
ddi_soft_state_free(iwn_state, instance);
return (DDI_FAILURE);
}
int
iwn4965_attach(struct iwn_softc *sc)
{
struct iwn_ops *ops = &sc->ops;
ops->load_firmware = iwn4965_load_firmware;
ops->read_eeprom = iwn4965_read_eeprom;
ops->post_alive = iwn4965_post_alive;
ops->nic_config = iwn4965_nic_config;
ops->config_bt_coex = iwn_config_bt_coex_bluetooth;
ops->update_sched = iwn4965_update_sched;
ops->get_temperature = iwn4965_get_temperature;
ops->get_rssi = iwn4965_get_rssi;
ops->set_txpower = iwn4965_set_txpower;
ops->init_gains = iwn4965_init_gains;
ops->set_gains = iwn4965_set_gains;
ops->add_node = iwn4965_add_node;
ops->tx_done = iwn4965_tx_done;
#ifndef IEEE80211_NO_HT
ops->ampdu_tx_start = iwn4965_ampdu_tx_start;
ops->ampdu_tx_stop = iwn4965_ampdu_tx_stop;
#endif
sc->ntxqs = IWN4965_NTXQUEUES;
sc->ndmachnls = IWN4965_NDMACHNLS;
sc->broadcast_id = IWN4965_ID_BROADCAST;
sc->rxonsz = IWN4965_RXONSZ;
sc->schedsz = IWN4965_SCHEDSZ;
sc->fw_text_maxsz = IWN4965_FW_TEXT_MAXSZ;
sc->fw_data_maxsz = IWN4965_FW_DATA_MAXSZ;
sc->fwsz = IWN4965_FWSZ;
sc->sched_txfact_addr = IWN4965_SCHED_TXFACT;
sc->limits = &iwn4965_sensitivity_limits;
sc->fwname = "iwlwifi-4965-2.ucode";
/* Override chains masks, ROM is known to be broken. */
sc->txchainmask = IWN_ANT_AB;
sc->rxchainmask = IWN_ANT_ABC;
iwn_kstat_create(sc, "txpower", sizeof (struct iwn_ks_txpower),
&sc->sc_ks_txpower, (void **)&sc->sc_txpower);
iwn_kstat_init_4965(sc);
return 0;
}
int
iwn5000_attach(struct iwn_softc *sc, uint16_t pid)
{
struct iwn_ops *ops = &sc->ops;
ops->load_firmware = iwn5000_load_firmware;
ops->read_eeprom = iwn5000_read_eeprom;
ops->post_alive = iwn5000_post_alive;
ops->nic_config = iwn5000_nic_config;
ops->config_bt_coex = iwn_config_bt_coex_bluetooth;
ops->update_sched = iwn5000_update_sched;
ops->get_temperature = iwn5000_get_temperature;
ops->get_rssi = iwn5000_get_rssi;
ops->set_txpower = iwn5000_set_txpower;
ops->init_gains = iwn5000_init_gains;
ops->set_gains = iwn5000_set_gains;
ops->add_node = iwn5000_add_node;
ops->tx_done = iwn5000_tx_done;
#ifndef IEEE80211_NO_HT
ops->ampdu_tx_start = iwn5000_ampdu_tx_start;
ops->ampdu_tx_stop = iwn5000_ampdu_tx_stop;
#endif
sc->ntxqs = IWN5000_NTXQUEUES;
sc->ndmachnls = IWN5000_NDMACHNLS;
sc->broadcast_id = IWN5000_ID_BROADCAST;
sc->rxonsz = IWN5000_RXONSZ;
sc->schedsz = IWN5000_SCHEDSZ;
sc->fw_text_maxsz = IWN5000_FW_TEXT_MAXSZ;
sc->fw_data_maxsz = IWN5000_FW_DATA_MAXSZ;
sc->fwsz = IWN5000_FWSZ;
sc->sched_txfact_addr = IWN5000_SCHED_TXFACT;
switch (sc->hw_type) {
case IWN_HW_REV_TYPE_5100:
sc->limits = &iwn5000_sensitivity_limits;
sc->fwname = "iwlwifi-5000-2.ucode";
/* Override chains masks, ROM is known to be broken. */
sc->txchainmask = IWN_ANT_B;
sc->rxchainmask = IWN_ANT_AB;
break;
case IWN_HW_REV_TYPE_5150:
sc->limits = &iwn5150_sensitivity_limits;
sc->fwname = "iwlwifi-5150-2.ucode";
break;
case IWN_HW_REV_TYPE_5300:
case IWN_HW_REV_TYPE_5350:
sc->limits = &iwn5000_sensitivity_limits;
sc->fwname = "iwlwifi-5000-2.ucode";
break;
case IWN_HW_REV_TYPE_1000:
sc->limits = &iwn1000_sensitivity_limits;
if (pid == PCI_PRODUCT_INTEL_WIFI_LINK_100_1 ||
pid == PCI_PRODUCT_INTEL_WIFI_LINK_100_2)
sc->fwname = "iwlwifi-100-5.ucode";
else
sc->fwname = "iwlwifi-1000-3.ucode";
break;
case IWN_HW_REV_TYPE_6000:
sc->limits = &iwn6000_sensitivity_limits;
sc->fwname = "iwlwifi-6000-4.ucode";
if (pid == PCI_PRODUCT_INTEL_WIFI_LINK_6000_IPA_1 ||
pid == PCI_PRODUCT_INTEL_WIFI_LINK_6000_IPA_2) {
sc->sc_flags |= IWN_FLAG_INTERNAL_PA;
/* Override chains masks, ROM is known to be broken. */
sc->txchainmask = IWN_ANT_BC;
sc->rxchainmask = IWN_ANT_BC;
}
break;
case IWN_HW_REV_TYPE_6050:
sc->limits = &iwn6000_sensitivity_limits;
sc->fwname = "iwlwifi-6050-5.ucode";
break;
case IWN_HW_REV_TYPE_6005:
sc->limits = &iwn6000_sensitivity_limits;
/* Type 6030 cards return IWN_HW_REV_TYPE_6005 */
if (pid == PCI_PRODUCT_INTEL_WIFI_LINK_1030_1 ||
pid == PCI_PRODUCT_INTEL_WIFI_LINK_1030_2 ||
pid == PCI_PRODUCT_INTEL_WIFI_LINK_6230_1 ||
pid == PCI_PRODUCT_INTEL_WIFI_LINK_6230_2 ||
pid == PCI_PRODUCT_INTEL_WIFI_LINK_6235 ||
pid == PCI_PRODUCT_INTEL_WIFI_LINK_6235_2) {
sc->fwname = "iwlwifi-6000g2b-6.ucode";
ops->config_bt_coex = iwn_config_bt_coex_adv1;
}
else
sc->fwname = "iwlwifi-6000g2a-6.ucode";
iwn_kstat_create(sc, "temp_offset",
sizeof (struct iwn_ks_toff_6000),
&sc->sc_ks_toff, (void **)&sc->sc_toff.t6000);
iwn_kstat_init_6000(sc);
break;
case IWN_HW_REV_TYPE_2030:
sc->limits = &iwn2000_sensitivity_limits;
sc->fwname = "iwlwifi-2030-6.ucode";
ops->config_bt_coex = iwn_config_bt_coex_adv2;
iwn_kstat_create(sc, "temp_offset",
sizeof (struct iwn_ks_toff_2000),
&sc->sc_ks_toff, (void **)&sc->sc_toff.t2000);
iwn_kstat_init_2000(sc);
break;
case IWN_HW_REV_TYPE_2000:
sc->limits = &iwn2000_sensitivity_limits;
sc->fwname = "iwlwifi-2000-6.ucode";
iwn_kstat_create(sc, "temp_offset",
sizeof (struct iwn_ks_toff_2000),
&sc->sc_ks_toff, (void **)&sc->sc_toff.t2000);
iwn_kstat_init_2000(sc);
break;
case IWN_HW_REV_TYPE_135:
sc->limits = &iwn2000_sensitivity_limits;
sc->fwname = "iwlwifi-135-6.ucode";
ops->config_bt_coex = iwn_config_bt_coex_adv2;
iwn_kstat_create(sc, "temp_offset",
sizeof (struct iwn_ks_toff_2000),
&sc->sc_ks_toff, (void **)&sc->sc_toff.t2000);
iwn_kstat_init_2000(sc);
break;
case IWN_HW_REV_TYPE_105:
sc->limits = &iwn2000_sensitivity_limits;
sc->fwname = "iwlwifi-105-6.ucode";
iwn_kstat_create(sc, "temp_offset",
sizeof (struct iwn_ks_toff_2000),
&sc->sc_ks_toff, (void **)&sc->sc_toff.t2000);
iwn_kstat_init_2000(sc);
break;
default:
dev_err(sc->sc_dip, CE_WARN, "!adapter type %d not supported",
sc->hw_type);
return ENOTSUP;
}
return 0;
}
static int
iwn_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
struct iwn_softc *sc = ddi_get_driver_private(dip);
ieee80211com_t *ic = &sc->sc_ic;
int qid, error;
switch (cmd) {
case DDI_DETACH:
break;
case DDI_SUSPEND:
sc->sc_flags &= ~IWN_FLAG_HW_ERR_RECOVER;
sc->sc_flags &= ~IWN_FLAG_RATE_AUTO_CTL;
sc->sc_flags |= IWN_FLAG_SUSPEND;
if (sc->sc_flags & IWN_FLAG_RUNNING) {
iwn_hw_stop(sc, B_TRUE);
ieee80211_new_state(ic, IEEE80211_S_INIT, -1);
}
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
if (!(sc->sc_flags & IWN_FLAG_ATTACHED)) {
return (DDI_FAILURE);
}
error = mac_disable(ic->ic_mach);
if (error != DDI_SUCCESS)
return (error);
mutex_enter(&sc->sc_mtx);
sc->sc_flags |= IWN_FLAG_STOP_CALIB_TO;
mutex_exit(&sc->sc_mtx);
if (sc->calib_to != 0)
(void) untimeout(sc->calib_to);
sc->calib_to = 0;
if (sc->scan_to != 0)
(void) untimeout(sc->scan_to);
sc->scan_to = 0;
ddi_periodic_delete(sc->sc_periodic);
/*
* stop chipset
*/
iwn_hw_stop(sc, B_TRUE);
/*
* Unregister from GLD
*/
(void) mac_unregister(ic->ic_mach);
ieee80211_detach(ic);
/* Uninstall interrupt handler. */
iwn_intr_teardown(sc);
/* Free DMA resources. */
mutex_enter(&sc->sc_mtx);
iwn_free_rx_ring(sc, &sc->rxq);
for (qid = 0; qid < sc->ntxqs; qid++)
iwn_free_tx_ring(sc, &sc->txq[qid]);
iwn_free_sched(sc);
iwn_free_kw(sc);
if (sc->ict != NULL)
iwn_free_ict(sc);
iwn_free_fwmem(sc);
mutex_exit(&sc->sc_mtx);
iwn_kstat_free(sc->sc_ks_misc, sc->sc_misc,
sizeof (struct iwn_ks_misc));
iwn_kstat_free(sc->sc_ks_ant, sc->sc_ant,
sizeof (struct iwn_ks_ant));
iwn_kstat_free(sc->sc_ks_sens, sc->sc_sens,
sizeof (struct iwn_ks_sens));
iwn_kstat_free(sc->sc_ks_timing, sc->sc_timing,
sizeof (struct iwn_ks_timing));
iwn_kstat_free(sc->sc_ks_edca, sc->sc_edca,
sizeof (struct iwn_ks_edca));
iwn_kstat_free(sc->sc_ks_txpower, sc->sc_txpower,
sizeof (struct iwn_ks_txpower));
if (sc->hw_type == IWN_HW_REV_TYPE_6005)
iwn_kstat_free(sc->sc_ks_toff, sc->sc_toff.t6000,
sizeof (struct iwn_ks_toff_6000));
else
iwn_kstat_free(sc->sc_ks_toff, sc->sc_toff.t2000,
sizeof (struct iwn_ks_toff_2000));
ddi_regs_map_free(&sc->sc_regh);
pci_config_teardown(&sc->sc_pcih);
ddi_remove_minor_node(dip, NULL);
ddi_soft_state_free(iwn_state, ddi_get_instance(dip));
return 0;
}
static int
iwn_quiesce(dev_info_t *dip)
{
struct iwn_softc *sc;
sc = ddi_get_soft_state(iwn_state, ddi_get_instance(dip));
if (sc == NULL)
return (DDI_FAILURE);
#ifdef IWN_DEBUG
/* bypass any messages */
iwn_dbg_print = 0;
#endif
/*
* No more blocking is allowed while we are in the
* quiesce(9E) entry point.
*/
sc->sc_flags |= IWN_FLAG_QUIESCED;
/*
* Disable and mask all interrupts.
*/
iwn_hw_stop(sc, B_FALSE);
return (DDI_SUCCESS);
}
static int
iwn_nic_lock(struct iwn_softc *sc)
{
int ntries;
/* Request exclusive access to NIC. */
IWN_SETBITS(sc, IWN_GP_CNTRL, IWN_GP_CNTRL_MAC_ACCESS_REQ);
/* Spin until we actually get the lock. */
for (ntries = 0; ntries < 1000; ntries++) {
if ((IWN_READ(sc, IWN_GP_CNTRL) &
(IWN_GP_CNTRL_MAC_ACCESS_ENA | IWN_GP_CNTRL_SLEEP)) ==
IWN_GP_CNTRL_MAC_ACCESS_ENA)
return 0;
DELAY(10);
}
return ETIMEDOUT;
}
static __inline void
iwn_nic_unlock(struct iwn_softc *sc)
{
IWN_CLRBITS(sc, IWN_GP_CNTRL, IWN_GP_CNTRL_MAC_ACCESS_REQ);
}
static __inline uint32_t
iwn_prph_read(struct iwn_softc *sc, uint32_t addr)
{
IWN_WRITE(sc, IWN_PRPH_RADDR, IWN_PRPH_DWORD | addr);
IWN_BARRIER_READ_WRITE(sc);
return IWN_READ(sc, IWN_PRPH_RDATA);
}
static __inline void
iwn_prph_write(struct iwn_softc *sc, uint32_t addr, uint32_t data)
{
IWN_WRITE(sc, IWN_PRPH_WADDR, IWN_PRPH_DWORD | addr);
IWN_BARRIER_WRITE(sc);
IWN_WRITE(sc, IWN_PRPH_WDATA, data);
}
static __inline void
iwn_prph_setbits(struct iwn_softc *sc, uint32_t addr, uint32_t mask)
{
iwn_prph_write(sc, addr, iwn_prph_read(sc, addr) | mask);
}
static __inline void
iwn_prph_clrbits(struct iwn_softc *sc, uint32_t addr, uint32_t mask)
{
iwn_prph_write(sc, addr, iwn_prph_read(sc, addr) & ~mask);
}
static __inline void
iwn_prph_write_region_4(struct iwn_softc *sc, uint32_t addr,
const uint32_t *data, int count)
{
for (; count > 0; count--, data++, addr += 4)
iwn_prph_write(sc, addr, *data);
}
static __inline uint32_t
iwn_mem_read(struct iwn_softc *sc, uint32_t addr)
{
IWN_WRITE(sc, IWN_MEM_RADDR, addr);
IWN_BARRIER_READ_WRITE(sc);
return IWN_READ(sc, IWN_MEM_RDATA);
}
static __inline void
iwn_mem_write(struct iwn_softc *sc, uint32_t addr, uint32_t data)
{
IWN_WRITE(sc, IWN_MEM_WADDR, addr);
IWN_BARRIER_WRITE(sc);
IWN_WRITE(sc, IWN_MEM_WDATA, data);
}
#ifndef IEEE80211_NO_HT
static __inline void
iwn_mem_write_2(struct iwn_softc *sc, uint32_t addr, uint16_t data)
{
uint32_t tmp;
tmp = iwn_mem_read(sc, addr & ~3);
if (addr & 3)
tmp = (tmp & 0x0000ffff) | data << 16;
else
tmp = (tmp & 0xffff0000) | data;
iwn_mem_write(sc, addr & ~3, tmp);
}
#endif
static __inline void
iwn_mem_read_region_4(struct iwn_softc *sc, uint32_t addr, uint32_t *data,
int count)
{
for (; count > 0; count--, addr += 4)
*data++ = iwn_mem_read(sc, addr);
}
static __inline void
iwn_mem_set_region_4(struct iwn_softc *sc, uint32_t addr, uint32_t val,
int count)
{
for (; count > 0; count--, addr += 4)
iwn_mem_write(sc, addr, val);
}
static int
iwn_eeprom_lock(struct iwn_softc *sc)
{
int i, ntries;
for (i = 0; i < 100; i++) {
/* Request exclusive access to EEPROM. */
IWN_SETBITS(sc, IWN_HW_IF_CONFIG,
IWN_HW_IF_CONFIG_EEPROM_LOCKED);
/* Spin until we actually get the lock. */
for (ntries = 0; ntries < 100; ntries++) {
if (IWN_READ(sc, IWN_HW_IF_CONFIG) &
IWN_HW_IF_CONFIG_EEPROM_LOCKED)
return 0;
DELAY(10);
}
}
return ETIMEDOUT;
}
static __inline void
iwn_eeprom_unlock(struct iwn_softc *sc)
{
IWN_CLRBITS(sc, IWN_HW_IF_CONFIG, IWN_HW_IF_CONFIG_EEPROM_LOCKED);
}
/*
* Initialize access by host to One Time Programmable ROM.
* NB: This kind of ROM can be found on 1000 or 6000 Series only.
*/
static int
iwn_init_otprom(struct iwn_softc *sc)
{
uint16_t prev = 0, base, next;
int count, error;
/* Wait for clock stabilization before accessing prph. */
if ((error = iwn_clock_wait(sc)) != 0)
return error;
if ((error = iwn_nic_lock(sc)) != 0)
return error;
iwn_prph_setbits(sc, IWN_APMG_PS, IWN_APMG_PS_RESET_REQ);
DELAY(5);
iwn_prph_clrbits(sc, IWN_APMG_PS, IWN_APMG_PS_RESET_REQ);
iwn_nic_unlock(sc);
/* Set auto clock gate disable bit for HW with OTP shadow RAM. */
if (sc->hw_type != IWN_HW_REV_TYPE_1000) {
IWN_SETBITS(sc, IWN_DBG_LINK_PWR_MGMT,
IWN_RESET_LINK_PWR_MGMT_DIS);
}
IWN_CLRBITS(sc, IWN_EEPROM_GP, IWN_EEPROM_GP_IF_OWNER);
/* Clear ECC status. */
IWN_SETBITS(sc, IWN_OTP_GP,
IWN_OTP_GP_ECC_CORR_STTS | IWN_OTP_GP_ECC_UNCORR_STTS);
/*
* Find the block before last block (contains the EEPROM image)
* for HW without OTP shadow RAM.
*/
if (sc->hw_type == IWN_HW_REV_TYPE_1000) {
/* Switch to absolute addressing mode. */
IWN_CLRBITS(sc, IWN_OTP_GP, IWN_OTP_GP_RELATIVE_ACCESS);
base = 0;
for (count = 0; count < IWN1000_OTP_NBLOCKS; count++) {
error = iwn_read_prom_data(sc, base, &next, 2);
if (error != 0)
return error;
if (next == 0) /* End of linked-list. */
break;
prev = base;
base = le16toh(next);
}
if (count == 0 || count == IWN1000_OTP_NBLOCKS)
return EIO;
/* Skip "next" word. */
sc->prom_base = prev + 1;
}
return 0;
}
static int
iwn_read_prom_data(struct iwn_softc *sc, uint32_t addr, void *data, int count)
{
uint8_t *out = data;
uint32_t val, tmp;
int ntries;
addr += sc->prom_base;
for (; count > 0; count -= 2, addr++) {
IWN_WRITE(sc, IWN_EEPROM, addr << 2);
for (ntries = 0; ntries < 10; ntries++) {
val = IWN_READ(sc, IWN_EEPROM);
if (val & IWN_EEPROM_READ_VALID)
break;
DELAY(5);
}
if (ntries == 10) {
dev_err(sc->sc_dip, CE_WARN,
"!timeout reading ROM at 0x%x", addr);
return ETIMEDOUT;
}
if (sc->sc_flags & IWN_FLAG_HAS_OTPROM) {
/* OTPROM, check for ECC errors. */
tmp = IWN_READ(sc, IWN_OTP_GP);
if (tmp & IWN_OTP_GP_ECC_UNCORR_STTS) {
dev_err(sc->sc_dip, CE_WARN,
"!OTPROM ECC error at 0x%x", addr);
return EIO;
}
if (tmp & IWN_OTP_GP_ECC_CORR_STTS) {
/* Correctable ECC error, clear bit. */
IWN_SETBITS(sc, IWN_OTP_GP,
IWN_OTP_GP_ECC_CORR_STTS);
}
}
*out++ = val >> 16;
if (count > 1)
*out++ = val >> 24;
}
return 0;
}
static int
iwn_dma_contig_alloc(struct iwn_softc *sc, struct iwn_dma_info *dma,
uint_t size, uint_t flags, void **kvap, ddi_device_acc_attr_t *acc_attr,
uint_t align)
{
ddi_dma_attr_t dma_attr = {
.dma_attr_version = DMA_ATTR_V0,
.dma_attr_addr_lo = 0,
.dma_attr_addr_hi = 0xfffffffffULL,
.dma_attr_count_max = 0xfffffffffULL,
.dma_attr_align = align,
.dma_attr_burstsizes = 0x7ff,
.dma_attr_minxfer = 1,
.dma_attr_maxxfer = 0xfffffffffULL,
.dma_attr_seg = 0xfffffffffULL,
.dma_attr_sgllen = 1,
.dma_attr_granular = 1,
.dma_attr_flags = 0,
};
int error;
error = ddi_dma_alloc_handle(sc->sc_dip, &dma_attr, DDI_DMA_SLEEP, NULL,
&dma->dma_hdl);
if (error != DDI_SUCCESS) {
dev_err(sc->sc_dip, CE_WARN,
"ddi_dma_alloc_handle() failed, error = %d", error);
goto fail;
}
error = ddi_dma_mem_alloc(dma->dma_hdl, size, acc_attr,
flags & (DDI_DMA_CONSISTENT | DDI_DMA_STREAMING), DDI_DMA_SLEEP, 0,
&dma->vaddr, &dma->length, &dma->acc_hdl);
if (error != DDI_SUCCESS) {
dev_err(sc->sc_dip, CE_WARN,
"ddi_dma_mem_alloc() failed, error = %d", error);
goto fail2;
}
bzero(dma->vaddr, dma->length);
error = ddi_dma_addr_bind_handle(dma->dma_hdl, NULL, dma->vaddr,
dma->length, flags, DDI_DMA_SLEEP, NULL, &dma->cookie,
&dma->ncookies);
if (error != DDI_DMA_MAPPED) {
dma->ncookies = 0;
dev_err(sc->sc_dip, CE_WARN,
"ddi_dma_addr_bind_handle() failed, error = %d", error);
goto fail3;
}
dma->size = size;
dma->paddr = dma->cookie.dmac_laddress;
if (kvap != NULL)
*kvap = (void *)dma->vaddr;
return (DDI_SUCCESS);
fail3:
ddi_dma_mem_free(&dma->acc_hdl);
fail2:
ddi_dma_free_handle(&dma->dma_hdl);
fail:
bzero(dma, sizeof (struct iwn_dma_info));
return (DDI_FAILURE);
}
static void
iwn_dma_contig_free(struct iwn_dma_info *dma)
{
if (dma->dma_hdl != NULL) {
if (dma->ncookies)
(void) ddi_dma_unbind_handle(dma->dma_hdl);
ddi_dma_free_handle(&dma->dma_hdl);
}
if (dma->acc_hdl != NULL)
ddi_dma_mem_free(&dma->acc_hdl);
bzero(dma, sizeof (struct iwn_dma_info));
}
static int
iwn_alloc_sched(struct iwn_softc *sc)
{
/* TX scheduler rings must be aligned on a 1KB boundary. */
return iwn_dma_contig_alloc(sc, &sc->sched_dma, sc->schedsz,
DDI_DMA_CONSISTENT | DDI_DMA_RDWR, (void **)&sc->sched,
&iwn_dma_accattr, 1024);
}
static void
iwn_free_sched(struct iwn_softc *sc)
{
iwn_dma_contig_free(&sc->sched_dma);
}
static int
iwn_alloc_kw(struct iwn_softc *sc)
{
/* "Keep Warm" page must be aligned on a 4KB boundary. */
return iwn_dma_contig_alloc(sc, &sc->kw_dma, IWN_KW_SIZE,
DDI_DMA_CONSISTENT | DDI_DMA_RDWR, NULL, &iwn_dma_accattr, 4096);
}
static void
iwn_free_kw(struct iwn_softc *sc)
{
iwn_dma_contig_free(&sc->kw_dma);
}
static int
iwn_alloc_ict(struct iwn_softc *sc)
{
/* ICT table must be aligned on a 4KB boundary. */
return iwn_dma_contig_alloc(sc, &sc->ict_dma, IWN_ICT_SIZE,
DDI_DMA_CONSISTENT | DDI_DMA_RDWR, (void **)&sc->ict,
&iwn_dma_descattr, 4096);
}
static void
iwn_free_ict(struct iwn_softc *sc)
{
iwn_dma_contig_free(&sc->ict_dma);
}
static int
iwn_alloc_fwmem(struct iwn_softc *sc)
{
/* Must be aligned on a 16-byte boundary. */
return iwn_dma_contig_alloc(sc, &sc->fw_dma, sc->fwsz,
DDI_DMA_CONSISTENT | DDI_DMA_RDWR, NULL, &iwn_dma_accattr, 16);
}
static void
iwn_free_fwmem(struct iwn_softc *sc)
{
iwn_dma_contig_free(&sc->fw_dma);
}
static int
iwn_alloc_rx_ring(struct iwn_softc *sc, struct iwn_rx_ring *ring)
{
size_t size;
int i, error;
ring->cur = 0;
/* Allocate RX descriptors (256-byte aligned). */
size = IWN_RX_RING_COUNT * sizeof (uint32_t);
error = iwn_dma_contig_alloc(sc, &ring->desc_dma, size,
DDI_DMA_CONSISTENT | DDI_DMA_RDWR, (void **)&ring->desc,
&iwn_dma_descattr, 256);
if (error != DDI_SUCCESS) {
dev_err(sc->sc_dip, CE_WARN,
"!could not allocate RX ring DMA memory");
goto fail;
}
/* Allocate RX status area (16-byte aligned). */
error = iwn_dma_contig_alloc(sc, &ring->stat_dma,
sizeof (struct iwn_rx_status), DDI_DMA_CONSISTENT | DDI_DMA_RDWR,
(void **)&ring->stat, &iwn_dma_descattr, 16);
if (error != DDI_SUCCESS) {
dev_err(sc->sc_dip, CE_WARN,
"!could not allocate RX status DMA memory");
goto fail;
}
/*
* Allocate and map RX buffers.
*/
for (i = 0; i < IWN_RX_RING_COUNT; i++) {
struct iwn_rx_data *data = &ring->data[i];
error = iwn_dma_contig_alloc(sc, &data->dma_data, IWN_RBUF_SIZE,
DDI_DMA_CONSISTENT | DDI_DMA_READ, NULL, &iwn_dma_accattr,
256);
if (error != DDI_SUCCESS) {
dev_err(sc->sc_dip, CE_WARN,
"!could not create RX buf DMA map");
goto fail;
}
/* Set physical address of RX buffer (256-byte aligned). */
ring->desc[i] = htole32(data->dma_data.paddr >> 8);
}
(void) ddi_dma_sync(ring->desc_dma.dma_hdl, 0, 0, DDI_DMA_SYNC_FORDEV);
return 0;
fail: iwn_free_rx_ring(sc, ring);
return error;
}
static void
iwn_reset_rx_ring(struct iwn_softc *sc, struct iwn_rx_ring *ring)
{
int ntries;
if (iwn_nic_lock(sc) == 0) {
IWN_WRITE(sc, IWN_FH_RX_CONFIG, 0);
for (ntries = 0; ntries < 1000; ntries++) {
if (IWN_READ(sc, IWN_FH_RX_STATUS) &
IWN_FH_RX_STATUS_IDLE)
break;
DELAY(10);
}
iwn_nic_unlock(sc);
}
ring->cur = 0;
sc->last_rx_valid = 0;
}
static void
iwn_free_rx_ring(struct iwn_softc *sc, struct iwn_rx_ring *ring)
{
_NOTE(ARGUNUSED(sc));
int i;
iwn_dma_contig_free(&ring->desc_dma);
iwn_dma_contig_free(&ring->stat_dma);
for (i = 0; i < IWN_RX_RING_COUNT; i++) {
struct iwn_rx_data *data = &ring->data[i];
if (data->dma_data.dma_hdl)
iwn_dma_contig_free(&data->dma_data);
}
}
static int
iwn_alloc_tx_ring(struct iwn_softc *sc, struct iwn_tx_ring *ring, int qid)
{
uintptr_t paddr;
size_t size;
int i, error;
ring->qid = qid;
ring->queued = 0;
ring->cur = 0;
/* Allocate TX descriptors (256-byte aligned). */
size = IWN_TX_RING_COUNT * sizeof (struct iwn_tx_desc);
error = iwn_dma_contig_alloc(sc, &ring->desc_dma, size,
DDI_DMA_CONSISTENT | DDI_DMA_WRITE, (void **)&ring->desc,
&iwn_dma_descattr, 256);
if (error != DDI_SUCCESS) {
dev_err(sc->sc_dip, CE_WARN,
"!could not allocate TX ring DMA memory");
goto fail;
}
/*
* We only use rings 0 through 4 (4 EDCA + cmd) so there is no need
* to allocate commands space for other rings.
* XXX Do we really need to allocate descriptors for other rings?
*/
if (qid > 4)
return 0;
size = IWN_TX_RING_COUNT * sizeof (struct iwn_tx_cmd);
error = iwn_dma_contig_alloc(sc, &ring->cmd_dma, size,
DDI_DMA_CONSISTENT | DDI_DMA_WRITE, (void **)&ring->cmd,
&iwn_dma_accattr, 4);
if (error != DDI_SUCCESS) {
dev_err(sc->sc_dip, CE_WARN,
"!could not allocate TX cmd DMA memory");
goto fail;
}
paddr = ring->cmd_dma.paddr;
for (i = 0; i < IWN_TX_RING_COUNT; i++) {
struct iwn_tx_data *data = &ring->data[i];
data->cmd_paddr = paddr;
data->scratch_paddr = paddr + 12;
paddr += sizeof (struct iwn_tx_cmd);
error = iwn_dma_contig_alloc(sc, &data->dma_data, IWN_TBUF_SIZE,
DDI_DMA_CONSISTENT | DDI_DMA_WRITE, NULL, &iwn_dma_accattr,
256);
if (error != DDI_SUCCESS) {
dev_err(sc->sc_dip, CE_WARN,
"!could not create TX buf DMA map");
goto fail;
}
}
return 0;
fail: iwn_free_tx_ring(sc, ring);
return error;
}
static void
iwn_reset_tx_ring(struct iwn_softc *sc, struct iwn_tx_ring *ring)
{
int i;
if (ring->qid < 4)
for (i = 0; i < IWN_TX_RING_COUNT; i++) {
struct iwn_tx_data *data = &ring->data[i];
(void) ddi_dma_sync(data->dma_data.dma_hdl, 0, 0,
DDI_DMA_SYNC_FORDEV);
}
/* Clear TX descriptors. */
memset(ring->desc, 0, ring->desc_dma.size);
(void) ddi_dma_sync(ring->desc_dma.dma_hdl, 0, 0, DDI_DMA_SYNC_FORDEV);
sc->qfullmsk &= ~(1 << ring->qid);
ring->queued = 0;
ring->cur = 0;
}
static void
iwn_free_tx_ring(struct iwn_softc *sc, struct iwn_tx_ring *ring)
{
_NOTE(ARGUNUSED(sc));
int i;
iwn_dma_contig_free(&ring->desc_dma);
iwn_dma_contig_free(&ring->cmd_dma);
for (i = 0; i < IWN_TX_RING_COUNT; i++) {
struct iwn_tx_data *data = &ring->data[i];
if (data->dma_data.dma_hdl)
iwn_dma_contig_free(&data->dma_data);
}
}
static void
iwn5000_ict_reset(struct iwn_softc *sc)
{
/* Disable interrupts. */
IWN_WRITE(sc, IWN_INT_MASK, 0);
/* Reset ICT table. */
memset(sc->ict, 0, IWN_ICT_SIZE);
sc->ict_cur = 0;
/* Set physical address of ICT table (4KB aligned). */
IWN_WRITE(sc, IWN_DRAM_INT_TBL, IWN_DRAM_INT_TBL_ENABLE |
IWN_DRAM_INT_TBL_WRAP_CHECK | sc->ict_dma.paddr >> 12);
/* Enable periodic RX interrupt. */
sc->int_mask |= IWN_INT_RX_PERIODIC;
/* Switch to ICT interrupt mode in driver. */
sc->sc_flags |= IWN_FLAG_USE_ICT;
/* Re-enable interrupts. */
IWN_WRITE(sc, IWN_INT, 0xffffffff);
IWN_WRITE(sc, IWN_INT_MASK, sc->int_mask);
}
static int
iwn_read_eeprom(struct iwn_softc *sc)
{
struct iwn_ops *ops = &sc->ops;
struct ieee80211com *ic = &sc->sc_ic;
uint16_t val;
int error;
/* Check whether adapter has an EEPROM or an OTPROM. */
if (sc->hw_type >= IWN_HW_REV_TYPE_1000 &&
(IWN_READ(sc, IWN_OTP_GP) & IWN_OTP_GP_DEV_SEL_OTP))
sc->sc_flags |= IWN_FLAG_HAS_OTPROM;
IWN_DBG("%s found",
(sc->sc_flags & IWN_FLAG_HAS_OTPROM) ? "OTPROM" : "EEPROM");
/* Adapter has to be powered on for EEPROM access to work. */
if ((error = iwn_apm_init(sc)) != 0) {
dev_err(sc->sc_dip, CE_WARN,
"!could not power ON adapter");
return error;
}
if ((IWN_READ(sc, IWN_EEPROM_GP) & 0x7) == 0) {
dev_err(sc->sc_dip, CE_WARN,
"!bad ROM signature");
return EIO;
}
if ((error = iwn_eeprom_lock(sc)) != 0) {
dev_err(sc->sc_dip, CE_WARN,
"!could not lock ROM (error=%d)", error);
return error;
}
if (sc->sc_flags & IWN_FLAG_HAS_OTPROM) {
if ((error = iwn_init_otprom(sc)) != 0) {
dev_err(sc->sc_dip, CE_WARN,
"!could not initialize OTPROM");
return error;
}
}
iwn_read_prom_data(sc, IWN_EEPROM_SKU_CAP, &val, 2);
IWN_DBG("SKU capabilities=0x%04x", le16toh(val));
/* Check if HT support is bonded out. */
if (val & htole16(IWN_EEPROM_SKU_CAP_11N))
sc->sc_flags |= IWN_FLAG_HAS_11N;
iwn_read_prom_data(sc, IWN_EEPROM_RFCFG, &val, 2);
sc->rfcfg = le16toh(val);
IWN_DBG("radio config=0x%04x", sc->rfcfg);
/* Read Tx/Rx chains from ROM unless it's known to be broken. */
if (sc->txchainmask == 0)
sc->txchainmask = IWN_RFCFG_TXANTMSK(sc->rfcfg);
if (sc->rxchainmask == 0)
sc->rxchainmask = IWN_RFCFG_RXANTMSK(sc->rfcfg);
/* Read MAC address. */
iwn_read_prom_data(sc, IWN_EEPROM_MAC, ic->ic_macaddr, 6);
/* Read adapter-specific information from EEPROM. */
ops->read_eeprom(sc);
iwn_apm_stop(sc); /* Power OFF adapter. */
iwn_eeprom_unlock(sc);
return 0;
}
static void
iwn4965_read_eeprom(struct iwn_softc *sc)
{
uint32_t addr;
uint16_t val;
int i;
/* Read regulatory domain (4 ASCII characters). */
iwn_read_prom_data(sc, IWN4965_EEPROM_DOMAIN, sc->eeprom_domain, 4);
/* Read the list of authorized channels (20MHz ones only). */
for (i = 0; i < 5; i++) {
addr = iwn4965_regulatory_bands[i];
iwn_read_eeprom_channels(sc, i, addr);
}
/* Read maximum allowed TX power for 2GHz and 5GHz bands. */
iwn_read_prom_data(sc, IWN4965_EEPROM_MAXPOW, &val, 2);
sc->maxpwr2GHz = val & 0xff;
sc->maxpwr5GHz = val >> 8;
/* Check that EEPROM values are within valid range. */
if (sc->maxpwr5GHz < 20 || sc->maxpwr5GHz > 50)
sc->maxpwr5GHz = 38;
if (sc->maxpwr2GHz < 20 || sc->maxpwr2GHz > 50)
sc->maxpwr2GHz = 38;
IWN_DBG("maxpwr 2GHz=%d 5GHz=%d", sc->maxpwr2GHz, sc->maxpwr5GHz);
/* Read samples for each TX power group. */
iwn_read_prom_data(sc, IWN4965_EEPROM_BANDS, sc->bands,
sizeof sc->bands);
/* Read voltage at which samples were taken. */
iwn_read_prom_data(sc, IWN4965_EEPROM_VOLTAGE, &val, 2);
sc->eeprom_voltage = (int16_t)le16toh(val);
IWN_DBG("voltage=%d (in 0.3V)", sc->eeprom_voltage);
#ifdef IWN_DEBUG
/* Print samples. */
if (iwn_dbg_print != 0) {
for (i = 0; i < IWN_NBANDS; i++)
iwn4965_print_power_group(sc, i);
}
#endif
}
#ifdef IWN_DEBUG
static void
iwn4965_print_power_group(struct iwn_softc *sc, int i)
{
struct iwn4965_eeprom_band *band = &sc->bands[i];
struct iwn4965_eeprom_chan_samples *chans = band->chans;
int j, c;
dev_err(sc->sc_dip, CE_CONT, "!===band %d===", i);
dev_err(sc->sc_dip, CE_CONT, "!chan lo=%d, chan hi=%d", band->lo,
band->hi);
dev_err(sc->sc_dip, CE_CONT, "!chan1 num=%d", chans[0].num);
for (c = 0; c < 2; c++) {
for (j = 0; j < IWN_NSAMPLES; j++) {
dev_err(sc->sc_dip, CE_CONT, "!chain %d, sample %d: "
"temp=%d gain=%d power=%d pa_det=%d", c, j,
chans[0].samples[c][j].temp,
chans[0].samples[c][j].gain,
chans[0].samples[c][j].power,
chans[0].samples[c][j].pa_det);
}
}
dev_err(sc->sc_dip, CE_CONT, "!chan2 num=%d", chans[1].num);
for (c = 0; c < 2; c++) {
for (j = 0; j < IWN_NSAMPLES; j++) {
dev_err(sc->sc_dip, CE_CONT, "!chain %d, sample %d: "
"temp=%d gain=%d power=%d pa_det=%d", c, j,
chans[1].samples[c][j].temp,
chans[1].samples[c][j].gain,
chans[1].samples[c][j].power,
chans[1].samples[c][j].pa_det);
}
}
}
#endif
static void
iwn5000_read_eeprom(struct iwn_softc *sc)
{
struct iwn5000_eeprom_calib_hdr hdr;
int32_t volt;
uint32_t base, addr;
uint16_t val;
int i;
/* Read regulatory domain (4 ASCII characters). */
iwn_read_prom_data(sc, IWN5000_EEPROM_REG, &val, 2);
base = le16toh(val);
iwn_read_prom_data(sc, base + IWN5000_EEPROM_DOMAIN,
sc->eeprom_domain, 4);
/* Read the list of authorized channels (20MHz ones only). */
for (i = 0; i < 5; i++) {
addr = base + iwn5000_regulatory_bands[i];
iwn_read_eeprom_channels(sc, i, addr);
}
/* Read enhanced TX power information for 6000 Series. */
if (sc->hw_type >= IWN_HW_REV_TYPE_6000)
iwn_read_eeprom_enhinfo(sc);
iwn_read_prom_data(sc, IWN5000_EEPROM_CAL, &val, 2);
base = le16toh(val);
iwn_read_prom_data(sc, base, &hdr, sizeof hdr);
IWN_DBG("calib version=%u pa type=%u voltage=%u",
hdr.version, hdr.pa_type, le16toh(hdr.volt));
sc->calib_ver = hdr.version;
if (sc->hw_type == IWN_HW_REV_TYPE_2030 ||
sc->hw_type == IWN_HW_REV_TYPE_2000 ||
sc->hw_type == IWN_HW_REV_TYPE_135 ||
sc->hw_type == IWN_HW_REV_TYPE_105) {
sc->eeprom_voltage = le16toh(hdr.volt);
iwn_read_prom_data(sc, base + IWN5000_EEPROM_TEMP, &val, 2);
sc->eeprom_temp = le16toh(val);
iwn_read_prom_data(sc, base + IWN2000_EEPROM_RAWTEMP, &val, 2);
sc->eeprom_rawtemp = le16toh(val);
}
if (sc->hw_type == IWN_HW_REV_TYPE_5150) {
/* Compute temperature offset. */
iwn_read_prom_data(sc, base + IWN5000_EEPROM_TEMP, &val, 2);
sc->eeprom_temp = le16toh(val);
iwn_read_prom_data(sc, base + IWN5000_EEPROM_VOLT, &val, 2);
volt = le16toh(val);
sc->temp_off = sc->eeprom_temp - (volt / -5);
IWN_DBG("temp=%d volt=%d offset=%dK",
sc->eeprom_temp, volt, sc->temp_off);
} else {
/* Read crystal calibration. */
iwn_read_prom_data(sc, base + IWN5000_EEPROM_CRYSTAL,
&sc->eeprom_crystal, sizeof (uint32_t));
IWN_DBG("crystal calibration 0x%08x",
le32toh(sc->eeprom_crystal));
}
}
static void
iwn_read_eeprom_channels(struct iwn_softc *sc, int n, uint32_t addr)
{
struct ieee80211com *ic = &sc->sc_ic;
const struct iwn_chan_band *band = &iwn_bands[n];
struct iwn_eeprom_chan channels[IWN_MAX_CHAN_PER_BAND];
uint8_t chan;
int i;
iwn_read_prom_data(sc, addr, channels,
band->nchan * sizeof (struct iwn_eeprom_chan));
for (i = 0; i < band->nchan; i++) {
if (!(channels[i].flags & IWN_EEPROM_CHAN_VALID))
continue;
chan = band->chan[i];
if (n == 0) { /* 2GHz band */
ic->ic_sup_channels[chan].ich_freq =
ieee80211_ieee2mhz(chan, IEEE80211_CHAN_2GHZ);
ic->ic_sup_channels[chan].ich_flags =
IEEE80211_CHAN_CCK | IEEE80211_CHAN_OFDM |
IEEE80211_CHAN_DYN | IEEE80211_CHAN_2GHZ;
} else { /* 5GHz band */
/*
* Some adapters support channels 7, 8, 11 and 12
* both in the 2GHz and 4.9GHz bands.
* Because of limitations in our net80211 layer,
* we don't support them in the 4.9GHz band.
*/
if (chan <= 14)
continue;
ic->ic_sup_channels[chan].ich_freq =
ieee80211_ieee2mhz(chan, IEEE80211_CHAN_5GHZ);
ic->ic_sup_channels[chan].ich_flags =
IEEE80211_CHAN_5GHZ | IEEE80211_CHAN_OFDM;
/* We have at least one valid 5GHz channel. */
sc->sc_flags |= IWN_FLAG_HAS_5GHZ;
}
/* Is active scan allowed on this channel? */
if (!(channels[i].flags & IWN_EEPROM_CHAN_ACTIVE)) {
ic->ic_sup_channels[chan].ich_flags |=
IEEE80211_CHAN_PASSIVE;
}
/* Save maximum allowed TX power for this channel. */
sc->maxpwr[chan] = channels[i].maxpwr;
IWN_DBG("adding chan %d flags=0x%x maxpwr=%d",
chan, channels[i].flags, sc->maxpwr[chan]);
}
}
static void
iwn_read_eeprom_enhinfo(struct iwn_softc *sc)
{
struct iwn_eeprom_enhinfo enhinfo[35];
uint16_t val, base;
int8_t maxpwr;
int i;
iwn_read_prom_data(sc, IWN5000_EEPROM_REG, &val, 2);
base = le16toh(val);
iwn_read_prom_data(sc, base + IWN6000_EEPROM_ENHINFO,
enhinfo, sizeof enhinfo);
memset(sc->enh_maxpwr, 0, sizeof sc->enh_maxpwr);
for (i = 0; i < __arraycount(enhinfo); i++) {
if (enhinfo[i].chan == 0 || enhinfo[i].reserved != 0)
continue; /* Skip invalid entries. */
maxpwr = 0;
if (sc->txchainmask & IWN_ANT_A)
maxpwr = MAX(maxpwr, enhinfo[i].chain[0]);
if (sc->txchainmask & IWN_ANT_B)
maxpwr = MAX(maxpwr, enhinfo[i].chain[1]);
if (sc->txchainmask & IWN_ANT_C)
maxpwr = MAX(maxpwr, enhinfo[i].chain[2]);
if (sc->ntxchains == 2)
maxpwr = MAX(maxpwr, enhinfo[i].mimo2);
else if (sc->ntxchains == 3)
maxpwr = MAX(maxpwr, enhinfo[i].mimo3);
maxpwr /= 2; /* Convert half-dBm to dBm. */
IWN_DBG("enhinfo %d, maxpwr=%d", i, maxpwr);
sc->enh_maxpwr[i] = maxpwr;
}
}
static struct ieee80211_node *
iwn_node_alloc(ieee80211com_t *ic)
{
_NOTE(ARGUNUSED(ic));
return (kmem_zalloc(sizeof (struct iwn_node), KM_NOSLEEP));
}
static void
iwn_node_free(ieee80211_node_t *in)
{
ASSERT(in != NULL);
ASSERT(in->in_ic != NULL);
if (in->in_wpa_ie != NULL)
ieee80211_free(in->in_wpa_ie);
if (in->in_wme_ie != NULL)
ieee80211_free(in->in_wme_ie);
if (in->in_htcap_ie != NULL)
ieee80211_free(in->in_htcap_ie);
kmem_free(in, sizeof (struct iwn_node));
}
static void
iwn_newassoc(struct ieee80211_node *ni, int isnew)
{
_NOTE(ARGUNUSED(isnew));
struct iwn_softc *sc = (struct iwn_softc *)&ni->in_ic;
struct iwn_node *wn = (void *)ni;
uint8_t rate, ridx;
int i;
ieee80211_amrr_node_init(&sc->amrr, &wn->amn);
/*
* Select a medium rate and depend on AMRR to raise/lower it.
*/
ni->in_txrate = ni->in_rates.ir_nrates / 2;
for (i = 0; i < ni->in_rates.ir_nrates; i++) {
rate = ni->in_rates.ir_rates[i] & IEEE80211_RATE_VAL;
/* Map 802.11 rate to HW rate index. */
for (ridx = 0; ridx <= IWN_RIDX_MAX; ridx++)
if (iwn_rates[ridx].rate == rate)
break;
wn->ridx[i] = ridx;
}
}
static int
iwn_newstate(struct ieee80211com *ic, enum ieee80211_state nstate, int arg)
{
struct iwn_softc *sc = (struct iwn_softc *)ic;
enum ieee80211_state ostate;
int error;
mutex_enter(&sc->sc_mtx);
sc->sc_flags |= IWN_FLAG_STOP_CALIB_TO;
mutex_exit(&sc->sc_mtx);
(void) untimeout(sc->calib_to);
sc->calib_to = 0;
mutex_enter(&sc->sc_mtx);
ostate = ic->ic_state;
DTRACE_PROBE5(new__state, int, sc->sc_flags,
enum ieee80211_state, ostate,
const char *, ieee80211_state_name[ostate],
enum ieee80211_state, nstate,
const char *, ieee80211_state_name[nstate]);
if ((sc->sc_flags & IWN_FLAG_RADIO_OFF) && nstate != IEEE80211_S_INIT) {
mutex_exit(&sc->sc_mtx);
return (IWN_FAIL);
}
if (!(sc->sc_flags & IWN_FLAG_HW_INITED) &&
nstate != IEEE80211_S_INIT) {
mutex_exit(&sc->sc_mtx);
return (IWN_FAIL);
}
switch (nstate) {
case IEEE80211_S_SCAN:
/* XXX Do not abort a running scan. */
if (sc->sc_flags & IWN_FLAG_SCANNING) {
if (ostate != nstate)
dev_err(sc->sc_dip, CE_WARN, "!scan request(%d)"
" while scanning(%d) ignored", nstate,
ostate);
mutex_exit(&sc->sc_mtx);
return (0);
}
bcopy(&sc->rxon, &sc->rxon_save, sizeof (sc->rxon));
sc->sc_ostate = ostate;
/* XXX Not sure if call and flags are needed. */
ieee80211_node_table_reset(&ic->ic_scan);
ic->ic_flags |= IEEE80211_F_SCAN | IEEE80211_F_ASCAN;
sc->sc_flags |= IWN_FLAG_SCANNING_2GHZ;
/* Make the link LED blink while we're scanning. */
iwn_set_led(sc, IWN_LED_LINK, 10, 10);
ic->ic_state = nstate;
error = iwn_scan(sc, IEEE80211_CHAN_2GHZ);
if (error != 0) {
dev_err(sc->sc_dip, CE_WARN,
"!could not initiate scan");
sc->sc_flags &= ~IWN_FLAG_SCANNING;
mutex_exit(&sc->sc_mtx);
return (error);
}
mutex_exit(&sc->sc_mtx);
sc->scan_to = timeout(iwn_abort_scan, sc, iwn_scan_timeout *
drv_usectohz(MICROSEC));
return (error);
case IEEE80211_S_ASSOC:
if (ostate != IEEE80211_S_RUN) {
mutex_exit(&sc->sc_mtx);
break;
}
/* FALLTHROUGH */
case IEEE80211_S_AUTH:
/* Reset state to handle reassociations correctly. */
sc->rxon.associd = 0;
sc->rxon.filter &= ~htole32(IWN_FILTER_BSS);
sc->calib.state = IWN_CALIB_STATE_INIT;
if ((error = iwn_auth(sc)) != 0) {
mutex_exit(&sc->sc_mtx);
dev_err(sc->sc_dip, CE_WARN,
"!could not move to auth state");
return error;
}
mutex_exit(&sc->sc_mtx);
break;
case IEEE80211_S_RUN:
if ((error = iwn_run(sc)) != 0) {
mutex_exit(&sc->sc_mtx);
dev_err(sc->sc_dip, CE_WARN,
"!could not move to run state");
return error;
}
mutex_exit(&sc->sc_mtx);
break;
case IEEE80211_S_INIT:
sc->sc_flags &= ~IWN_FLAG_SCANNING;
sc->calib.state = IWN_CALIB_STATE_INIT;
/*
* set LED off after init
*/
iwn_set_led(sc, IWN_LED_LINK, 1, 0);
cv_signal(&sc->sc_scan_cv);
mutex_exit(&sc->sc_mtx);
if (sc->scan_to != 0)
(void) untimeout(sc->scan_to);
sc->scan_to = 0;
break;
}
error = sc->sc_newstate(ic, nstate, arg);
if (nstate == IEEE80211_S_RUN)
ieee80211_start_watchdog(ic, 1);
return (error);
}
static void
iwn_iter_func(void *arg, struct ieee80211_node *ni)
{
struct iwn_softc *sc = arg;
struct iwn_node *wn = (struct iwn_node *)ni;
ieee80211_amrr_choose(&sc->amrr, ni, &wn->amn);
}
static void
iwn_calib_timeout(void *arg)
{
struct iwn_softc *sc = arg;
struct ieee80211com *ic = &sc->sc_ic;
mutex_enter(&sc->sc_mtx);
if (ic->ic_fixed_rate == IEEE80211_FIXED_RATE_NONE) {
if (ic->ic_opmode == IEEE80211_M_STA)
iwn_iter_func(sc, ic->ic_bss);
else
ieee80211_iterate_nodes(&ic->ic_sta, iwn_iter_func, sc);
}
/* Force automatic TX power calibration every 60 secs. */
if (++sc->calib_cnt >= 120) {
uint32_t flags = 0;
DTRACE_PROBE(get__statistics);
(void)iwn_cmd(sc, IWN_CMD_GET_STATISTICS, &flags,
sizeof flags, 1);
sc->calib_cnt = 0;
}