| /* $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; |
| } |
| |
| |