#define IEEE80211_NO_HT
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: if_iwm.c,v 1.93 2025/12/21 16:24:39 mlelstv Exp $");
#include <sys/param.h>
#include <sys/conf.h>
#include <sys/kernel.h>
#include <sys/kmem.h>
#include <sys/mbuf.h>
#include <sys/mutex.h>
#include <sys/proc.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <sys/cpu.h>
#include <sys/bus.h>
#include <sys/workqueue.h>
#include <machine/endian.h>
#include <sys/intr.h>
#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
#include <dev/pci/pcidevs.h>
#include <dev/firmload.h>
#include <net/bpf.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_media.h>
#include <net/if_ether.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <net80211/ieee80211_var.h>
#include <net80211/ieee80211_amrr.h>
#include <net80211/ieee80211_radiotap.h>
#define DEVNAME(_s) device_xname((_s)->sc_dev)
#define IC2IFP(_ic_) ((_ic_)->ic_ifp)
#define le16_to_cpup(_a_) (le16toh(*(const uint16_t *)(_a_)))
#define le32_to_cpup(_a_) (le32toh(*(const uint32_t *)(_a_)))
#ifdef IWM_DEBUG
#define DPRINTF(x) do { if (iwm_debug > 0) printf x; } while (0)
#define DPRINTFN(n, x) do { if (iwm_debug >= (n)) printf x; } while (0)
int iwm_debug = 0;
#else
#define DPRINTF(x) do { ; } while (0)
#define DPRINTFN(n, x) do { ; } while (0)
#endif
#include <dev/pci/if_iwmreg.h>
#include <dev/pci/if_iwmvar.h>
static const u_int8_t etheranyaddr[ETHER_ADDR_LEN] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static const uint8_t iwm_nvm_channels[] = {
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
36, 40, 44, 48, 52, 56, 60, 64,
100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144,
149, 153, 157, 161, 165
};
static const uint8_t iwm_nvm_channels_8000[] = {
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92,
96, 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144,
149, 153, 157, 161, 165, 169, 173, 177, 181
};
#define IWM_NUM_2GHZ_CHANNELS 14
static const struct iwm_rate {
uint16_t rate;
uint8_t plcp;
uint8_t ht_plcp;
} iwm_rates[] = {
{ 2, IWM_RATE_1M_PLCP, IWM_RATE_HT_SISO_MCS_INV_PLCP },
{ 4, IWM_RATE_2M_PLCP, IWM_RATE_HT_SISO_MCS_INV_PLCP },
{ 11, IWM_RATE_5M_PLCP, IWM_RATE_HT_SISO_MCS_INV_PLCP },
{ 22, IWM_RATE_11M_PLCP, IWM_RATE_HT_SISO_MCS_INV_PLCP },
{ 12, IWM_RATE_6M_PLCP, IWM_RATE_HT_SISO_MCS_0_PLCP },
{ 18, IWM_RATE_9M_PLCP, IWM_RATE_HT_SISO_MCS_INV_PLCP },
{ 24, IWM_RATE_12M_PLCP, IWM_RATE_HT_SISO_MCS_1_PLCP },
{ 26, IWM_RATE_INVM_PLCP, IWM_RATE_HT_MIMO2_MCS_0_PLCP },
{ 36, IWM_RATE_18M_PLCP, IWM_RATE_HT_SISO_MCS_2_PLCP },
{ 48, IWM_RATE_24M_PLCP, IWM_RATE_HT_SISO_MCS_3_PLCP },
{ 52, IWM_RATE_INVM_PLCP, IWM_RATE_HT_MIMO2_MCS_1_PLCP },
{ 72, IWM_RATE_36M_PLCP, IWM_RATE_HT_SISO_MCS_4_PLCP },
{ 78, IWM_RATE_INVM_PLCP, IWM_RATE_HT_MIMO2_MCS_2_PLCP },
{ 96, IWM_RATE_48M_PLCP, IWM_RATE_HT_SISO_MCS_5_PLCP },
{ 104, IWM_RATE_INVM_PLCP, IWM_RATE_HT_MIMO2_MCS_3_PLCP },
{ 108, IWM_RATE_54M_PLCP, IWM_RATE_HT_SISO_MCS_6_PLCP },
{ 128, IWM_RATE_INVM_PLCP, IWM_RATE_HT_SISO_MCS_7_PLCP },
{ 156, IWM_RATE_INVM_PLCP, IWM_RATE_HT_MIMO2_MCS_4_PLCP },
{ 208, IWM_RATE_INVM_PLCP, IWM_RATE_HT_MIMO2_MCS_5_PLCP },
{ 234, IWM_RATE_INVM_PLCP, IWM_RATE_HT_MIMO2_MCS_6_PLCP },
{ 260, IWM_RATE_INVM_PLCP, IWM_RATE_HT_MIMO2_MCS_7_PLCP },
};
#define IWM_RIDX_CCK 0
#define IWM_RIDX_OFDM 4
#define IWM_RIDX_MAX (__arraycount(iwm_rates)-1)
#define IWM_RIDX_IS_CCK(_i_) ((_i_) < IWM_RIDX_OFDM)
#define IWM_RIDX_IS_OFDM(_i_) ((_i_) >= IWM_RIDX_OFDM)
#define IWM_RVAL_IS_OFDM(_i_) ((_i_) >= 12 && (_i_) != 22)
#ifndef IEEE80211_NO_HT
static const int iwm_mcs2ridx[] = {
IWM_RATE_MCS_0_INDEX,
IWM_RATE_MCS_1_INDEX,
IWM_RATE_MCS_2_INDEX,
IWM_RATE_MCS_3_INDEX,
IWM_RATE_MCS_4_INDEX,
IWM_RATE_MCS_5_INDEX,
IWM_RATE_MCS_6_INDEX,
IWM_RATE_MCS_7_INDEX,
IWM_RATE_MCS_8_INDEX,
IWM_RATE_MCS_9_INDEX,
IWM_RATE_MCS_10_INDEX,
IWM_RATE_MCS_11_INDEX,
IWM_RATE_MCS_12_INDEX,
IWM_RATE_MCS_13_INDEX,
IWM_RATE_MCS_14_INDEX,
IWM_RATE_MCS_15_INDEX,
};
#endif
struct iwm_nvm_section {
uint16_t length;
uint8_t *data;
};
struct iwm_newstate_state {
struct work ns_wk;
enum ieee80211_state ns_nstate;
int ns_arg;
int ns_generation;
};
#ifndef IEEE80211_NO_HT
static int iwm_is_mimo_ht_plcp(uint8_t);
static int iwm_is_mimo_ht_mcs(int);
#endif
static int iwm_store_cscheme(struct iwm_softc *, uint8_t *, size_t);
static int iwm_firmware_store_section(struct iwm_softc *,
enum iwm_ucode_type, uint8_t *, size_t);
static int iwm_set_default_calib(struct iwm_softc *, const void *);
static void iwm_fw_info_free(struct iwm_fw_info *);
static void iwm_fw_version_str(char *, size_t, uint32_t, uint32_t, uint32_t);
static int iwm_read_firmware(struct iwm_softc *, enum iwm_ucode_type);
static uint32_t iwm_read_prph(struct iwm_softc *, uint32_t);
static void iwm_write_prph(struct iwm_softc *, uint32_t, uint32_t);
static int iwm_read_mem(struct iwm_softc *, uint32_t, void *, int);
static int iwm_write_mem(struct iwm_softc *, uint32_t, const void *, int);
static int iwm_write_mem32(struct iwm_softc *, uint32_t, uint32_t);
static int iwm_poll_bit(struct iwm_softc *, int, uint32_t, uint32_t, int);
static int iwm_nic_lock(struct iwm_softc *);
static void iwm_nic_unlock(struct iwm_softc *);
static int iwm_set_bits_mask_prph(struct iwm_softc *, uint32_t, uint32_t,
uint32_t);
static int iwm_set_bits_prph(struct iwm_softc *, uint32_t, uint32_t);
static int iwm_clear_bits_prph(struct iwm_softc *, uint32_t, uint32_t);
static int iwm_dma_contig_alloc(bus_dma_tag_t, struct iwm_dma_info *,
bus_size_t, bus_size_t);
static void iwm_dma_contig_free(struct iwm_dma_info *);
static int iwm_alloc_rx_ring(struct iwm_softc *, struct iwm_rx_ring *);
static void iwm_disable_rx_dma(struct iwm_softc *);
static void iwm_reset_rx_ring(struct iwm_softc *, struct iwm_rx_ring *);
static void iwm_free_rx_ring(struct iwm_softc *, struct iwm_rx_ring *);
static int iwm_alloc_tx_ring(struct iwm_softc *, struct iwm_tx_ring *,
int);
static void iwm_reset_tx_ring(struct iwm_softc *, struct iwm_tx_ring *);
static void iwm_free_tx_ring(struct iwm_softc *, struct iwm_tx_ring *);
static void iwm_enable_rfkill_int(struct iwm_softc *);
static int iwm_check_rfkill(struct iwm_softc *);
static void iwm_enable_interrupts(struct iwm_softc *);
static void iwm_enable_fwload_interrupt(struct iwm_softc *);
static void iwm_restore_interrupts(struct iwm_softc *);
static void iwm_disable_interrupts(struct iwm_softc *);
static void iwm_ict_reset(struct iwm_softc *);
static int iwm_set_hw_ready(struct iwm_softc *);
static int iwm_prepare_card_hw(struct iwm_softc *);
static void iwm_apm_config(struct iwm_softc *);
static int iwm_apm_init(struct iwm_softc *);
static void iwm_apm_stop(struct iwm_softc *);
static int iwm_allow_mcast(struct iwm_softc *);
static int iwm_start_hw(struct iwm_softc *);
static void iwm_stop_device(struct iwm_softc *);
static void iwm_nic_config(struct iwm_softc *);
static int iwm_nic_rx_init(struct iwm_softc *);
static int iwm_nic_rx_legacy_init(struct iwm_softc *);
static int iwm_nic_tx_init(struct iwm_softc *);
static int iwm_nic_init(struct iwm_softc *);
static int iwm_enable_ac_txq(struct iwm_softc *, int, int);
static int iwm_enable_txq(struct iwm_softc *, int, int, int,
int, uint8_t, uint16_t);
static int iwm_post_alive(struct iwm_softc *);
static struct iwm_phy_db_entry *
iwm_phy_db_get_section(struct iwm_softc *,
enum iwm_phy_db_section_type, uint16_t);
static int iwm_phy_db_set_section(struct iwm_softc *,
struct iwm_calib_res_notif_phy_db *, uint16_t);
static int iwm_is_valid_channel(uint16_t);
static uint8_t iwm_ch_id_to_ch_index(uint16_t);
static uint16_t iwm_channel_id_to_papd(uint16_t);
static uint16_t iwm_channel_id_to_txp(struct iwm_softc *, uint16_t);
static int iwm_phy_db_get_section_data(struct iwm_softc *, uint32_t,
uint8_t **, uint16_t *, uint16_t);
static int iwm_send_phy_db_cmd(struct iwm_softc *, uint16_t, uint16_t,
void *);
static int iwm_phy_db_send_all_channel_groups(struct iwm_softc *,
enum iwm_phy_db_section_type, uint8_t);
static int iwm_send_phy_db_data(struct iwm_softc *);
static int iwm_send_time_event_cmd(struct iwm_softc *,
const struct iwm_time_event_cmd *);
static void iwm_protect_session(struct iwm_softc *, struct iwm_node *,
uint32_t, uint32_t);
static void iwm_unprotect_session(struct iwm_softc *, struct iwm_node *);
static int iwm_nvm_read_chunk(struct iwm_softc *, uint16_t, uint16_t,
uint16_t, uint8_t *, uint16_t *);
static int iwm_nvm_read_section(struct iwm_softc *, uint16_t, uint8_t *,
uint16_t *, size_t);
static void iwm_init_channel_map(struct iwm_softc *, const uint16_t * const,
const uint8_t *, size_t);
static int iwm_mimo_enabled(struct iwm_softc *);
#ifndef IEEE80211_NO_HT
static void iwm_setup_ht_rates(struct iwm_softc *);
static void iwm_htprot_task(void *);
static void iwm_update_htprot(struct ieee80211com *,
struct ieee80211_node *);
static int iwm_ampdu_rx_start(struct ieee80211com *,
struct ieee80211_node *, uint8_t);
static void iwm_ampdu_rx_stop(struct ieee80211com *,
struct ieee80211_node *, uint8_t);
static void iwm_sta_rx_agg(struct iwm_softc *, struct ieee80211_node *,
uint8_t, uint16_t, int);
#if 0
static int iwm_ampdu_tx_start(struct ieee80211com *,
struct ieee80211_node *, uint8_t);
static void iwm_ampdu_tx_stop(struct ieee80211com *,
struct ieee80211_node *, uint8_t);
#endif
static void iwm_ba_task(void *);
#endif
static int iwm_parse_nvm_data(struct iwm_softc *, const uint16_t *,
const uint16_t *, const uint16_t *,
const uint16_t *, const uint16_t *,
const uint16_t *, int);
static void iwm_set_hw_address_8000(struct iwm_softc *,
struct iwm_nvm_data *, const uint16_t *, const uint16_t *);
static int iwm_parse_nvm_sections(struct iwm_softc *,
struct iwm_nvm_section *);
static int iwm_nvm_init(struct iwm_softc *);
static int iwm_firmware_load_sect(struct iwm_softc *, uint32_t,
const uint8_t *, uint32_t);
static int iwm_firmware_load_chunk(struct iwm_softc *, uint32_t,
const uint8_t *, uint32_t);
static int iwm_load_firmware_7000(struct iwm_softc *, enum iwm_ucode_type);
static int iwm_load_cpu_sections_8000(struct iwm_softc *,
struct iwm_fw_sects *, int , int *);
static int iwm_load_firmware_8000(struct iwm_softc *, enum iwm_ucode_type);
static int iwm_load_firmware(struct iwm_softc *, enum iwm_ucode_type);
static int iwm_start_fw(struct iwm_softc *, enum iwm_ucode_type);
static int iwm_send_tx_ant_cfg(struct iwm_softc *, uint8_t);
static int iwm_send_phy_cfg_cmd(struct iwm_softc *);
static int iwm_load_ucode_wait_alive(struct iwm_softc *,
enum iwm_ucode_type);
static int iwm_send_dqa_cmd(struct iwm_softc *);
static int iwm_run_init_mvm_ucode(struct iwm_softc *, int);
#ifdef notyet
static int iwm_config_ltr(struct iwm_softc *);
#endif
static int iwm_rx_addbuf(struct iwm_softc *, int, int);
static int iwm_calc_rssi(struct iwm_softc *, struct iwm_rx_phy_info *);
static int iwm_get_signal_strength(struct iwm_softc *,
struct iwm_rx_phy_info *);
static void iwm_rx_rx_phy_cmd(struct iwm_softc *,
struct iwm_rx_packet *, struct iwm_rx_data *);
static int iwm_get_noise(const struct iwm_statistics_rx_non_phy *);
static void iwm_rx_rx_mpdu(struct iwm_softc *, struct iwm_rx_packet *,
struct iwm_rx_data *);
static void iwm_rx_tx_cmd_single(struct iwm_softc *, struct iwm_rx_packet *, struct iwm_node *);
static void iwm_rx_tx_cmd(struct iwm_softc *, struct iwm_rx_packet *,
struct iwm_rx_data *);
static int iwm_binding_cmd(struct iwm_softc *, struct iwm_node *,
uint32_t);
static void iwm_phy_ctxt_cmd_hdr(struct iwm_softc *, struct iwm_phy_ctxt *,
struct iwm_phy_context_cmd *, uint32_t, uint32_t);
static void iwm_phy_ctxt_cmd_data(struct iwm_softc *,
struct iwm_phy_context_cmd *, struct ieee80211_channel *,
uint8_t, uint8_t);
static int iwm_phy_ctxt_cmd(struct iwm_softc *, struct iwm_phy_ctxt *,
uint8_t, uint8_t, uint32_t, uint32_t);
static int iwm_send_cmd(struct iwm_softc *, struct iwm_host_cmd *);
static int iwm_send_cmd_pdu(struct iwm_softc *, uint32_t, uint32_t,
uint16_t, const void *);
static int iwm_send_cmd_status(struct iwm_softc *, struct iwm_host_cmd *,
uint32_t *);
static int iwm_send_cmd_pdu_status(struct iwm_softc *, uint32_t, uint16_t,
const void *, uint32_t *);
static void iwm_free_resp(struct iwm_softc *, struct iwm_host_cmd *);
static void iwm_cmd_done(struct iwm_softc *, int qid, int idx);
static void iwm_update_sched(struct iwm_softc *, int, int, uint8_t,
uint16_t);
static const struct iwm_rate *
iwm_tx_fill_cmd(struct iwm_softc *, struct iwm_node *,
struct ieee80211_frame *, struct iwm_tx_cmd *);
static int iwm_tx(struct iwm_softc *, struct mbuf *,
struct ieee80211_node *, int);
static void iwm_led_enable(struct iwm_softc *);
static void iwm_led_disable(struct iwm_softc *);
static int iwm_led_is_enabled(struct iwm_softc *);
static void iwm_led_blink_timeout(void *);
static void iwm_led_blink_start(struct iwm_softc *);
static void iwm_led_blink_stop(struct iwm_softc *);
static int iwm_beacon_filter_send_cmd(struct iwm_softc *,
struct iwm_beacon_filter_cmd *);
static void iwm_beacon_filter_set_cqm_params(struct iwm_softc *,
struct iwm_node *, struct iwm_beacon_filter_cmd *);
static int iwm_update_beacon_abort(struct iwm_softc *, struct iwm_node *,
int);
static void iwm_power_build_cmd(struct iwm_softc *, struct iwm_node *,
struct iwm_mac_power_cmd *);
static int iwm_power_mac_update_mode(struct iwm_softc *,
struct iwm_node *);
static int iwm_power_update_device(struct iwm_softc *);
#ifdef notyet
static int iwm_enable_beacon_filter(struct iwm_softc *, struct iwm_node *);
#endif
static int iwm_disable_beacon_filter(struct iwm_softc *);
static int iwm_add_sta_cmd(struct iwm_softc *, struct iwm_node *, int);
static int iwm_add_aux_sta(struct iwm_softc *);
static uint16_t iwm_scan_rx_chain(struct iwm_softc *);
static uint32_t iwm_scan_rate_n_flags(struct iwm_softc *, int, int);
static uint8_t iwm_lmac_scan_fill_channels(struct iwm_softc *,
struct iwm_scan_channel_cfg_lmac *, int);
static int iwm_fill_probe_req(struct iwm_softc *,
struct iwm_scan_probe_req *);
static int iwm_fill_probe_req_v1(struct iwm_softc *,
struct iwm_scan_probe_req_v1 *);
static int iwm_lmac_scan(struct iwm_softc *);
static int iwm_config_umac_scan(struct iwm_softc *);
static int iwm_umac_scan(struct iwm_softc *);
static void iwm_mcc_update(struct iwm_softc *, struct iwm_mcc_chub_notif *);
static uint8_t iwm_ridx2rate(struct ieee80211_rateset *, int);
static void iwm_ack_rates(struct iwm_softc *, struct iwm_node *, int *,
int *);
static void iwm_mac_ctxt_cmd_common(struct iwm_softc *, struct iwm_node *,
struct iwm_mac_ctx_cmd *, uint32_t, int);
static void iwm_mac_ctxt_cmd_fill_sta(struct iwm_softc *, struct iwm_node *,
struct iwm_mac_data_sta *, int);
static int iwm_mac_ctxt_cmd(struct iwm_softc *, struct iwm_node *,
uint32_t, int);
static void iwm_rx_missed_beacons_notif(struct iwm_softc *,
struct iwm_missed_beacons_notif *);
static int iwm_update_quotas(struct iwm_softc *, struct iwm_node *);
static int iwm_scan(struct iwm_softc *);
static int iwm_phy_ctxt_update(struct iwm_softc *, struct iwm_phy_ctxt *,
struct ieee80211_channel *, uint8_t, uint8_t, uint32_t);
static int iwm_auth(struct iwm_softc *);
#ifdef notyet
static int iwm_deauth(struct iwm_softc *);
#endif
static int iwm_run(struct iwm_softc *);
#ifdef notyet
static int iwm_run_stop(struct iwm_softc *);
#endif
static int iwm_assoc(struct iwm_softc *);
static void iwm_calib_timeout(void *);
static int iwm_setrates_task(struct iwm_softc *);
static void iwm_setrates_cb(struct work *, void *);
static int iwm_setrates(struct iwm_node *);
static int iwm_media_change(struct ifnet *);
static int iwm_do_newstate(struct ieee80211com *, enum ieee80211_state,
int);
static void iwm_newstate_cb(struct work *, void *);
static int iwm_newstate(struct ieee80211com *, enum ieee80211_state, int);
static void iwm_endscan(struct iwm_softc *);
static void iwm_fill_sf_command(struct iwm_softc *, struct iwm_sf_cfg_cmd *,
struct ieee80211_node *);
static int iwm_sf_config(struct iwm_softc *, int);
static int iwm_send_bt_init_conf(struct iwm_softc *);
static int iwm_send_soc_conf(struct iwm_softc *);
static int iwm_send_update_mcc_cmd(struct iwm_softc *, const char *);
static int iwm_send_temp_report_ths_cmd(struct iwm_softc *);
static void iwm_tt_tx_backoff(struct iwm_softc *, uint32_t);
static int iwm_init_hw(struct iwm_softc *);
static int iwm_init(struct ifnet *);
static void iwm_start(struct ifnet *);
static void iwm_stop(struct ifnet *, int);
static void iwm_watchdog(struct ifnet *);
static int iwm_ioctl(struct ifnet *, u_long, void *);
static const char *iwm_desc_lookup(uint32_t);
static void iwm_nic_error(struct iwm_softc *);
static void iwm_dump_driver_status(struct iwm_softc *);
static void iwm_nic_umac_error(struct iwm_softc *);
static void iwm_rx_pkt(struct iwm_softc *, struct iwm_rx_data *);
static void iwm_notif_intr(struct iwm_softc *);
static int iwm_intr(void *);
static void iwm_softintr(void *);
static int iwm_preinit(struct iwm_softc *, bool);
static void iwm_attach_hook(device_t);
static void iwm_attach(device_t, device_t, void *);
static int iwm_config_complete(struct iwm_softc *);
#if 0
static void iwm_init_task(void *);
static int iwm_activate(device_t, enum devact);
static void iwm_wakeup(struct iwm_softc *);
#endif
static void iwm_radiotap_attach(struct iwm_softc *);
static int iwm_sysctl_fw_loaded_handler(SYSCTLFN_PROTO);
static int iwm_sysctl_root_num;
static int iwm_lar_disable;
#ifndef IWM_DEFAULT_MCC
#define IWM_DEFAULT_MCC "ZZ"
#endif
static char iwm_default_mcc[3] = IWM_DEFAULT_MCC;
static int
iwm_firmload(struct iwm_softc *sc)
{
struct iwm_fw_info *fw = &sc->sc_fw;
firmware_handle_t fwh;
int err;
if (ISSET(sc->sc_flags, IWM_FLAG_FW_LOADED))
return 0;
err = firmware_open("if_iwm", sc->sc_fwname, &fwh);
if (err) {
aprint_error_dev(sc->sc_dev,
"could not get firmware handle %s\n", sc->sc_fwname);
return err;
}
if (fw->fw_rawdata != NULL && fw->fw_rawsize > 0) {
kmem_free(fw->fw_rawdata, fw->fw_rawsize);
fw->fw_rawdata = NULL;
}
fw->fw_rawsize = firmware_get_size(fwh);
if (fw->fw_rawsize < sizeof(uint32_t)) {
aprint_error_dev(sc->sc_dev,
"firmware too short: %zd bytes\n", fw->fw_rawsize);
err = EINVAL;
goto out;
}
fw->fw_rawdata = kmem_alloc(fw->fw_rawsize, KM_SLEEP);
err = firmware_read(fwh, 0, fw->fw_rawdata, fw->fw_rawsize);
if (err) {
aprint_error_dev(sc->sc_dev,
"could not read firmware %s\n", sc->sc_fwname);
goto out;
}
SET(sc->sc_flags, IWM_FLAG_FW_LOADED);
out:
firmware_close(fwh);
return err;
}
static void
iwm_fix_channel(struct iwm_softc *sc, struct mbuf *m)
{
struct ieee80211com *ic = &sc->sc_ic;
struct ieee80211_frame *wh;
uint8_t subtype;
wh = mtod(m, struct ieee80211_frame *);
if ((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) != IEEE80211_FC0_TYPE_MGT)
return;
subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK;
if (subtype != IEEE80211_FC0_SUBTYPE_BEACON &&
subtype != IEEE80211_FC0_SUBTYPE_PROBE_RESP)
return;
int chan = le32toh(sc->sc_last_phy_info.channel);
if (chan < __arraycount(ic->ic_channels))
ic->ic_curchan = &ic->ic_channels[chan];
}
#ifdef notyet
static uint8_t
iwm_lookup_cmd_ver(struct iwm_softc *sc, uint8_t grp, uint8_t cmd)
{
const struct iwm_fw_cmd_version *entry;
int i;
for (i = 0; i < sc->n_cmd_versions; i++) {
entry = &sc->cmd_versions[i];
if (entry->group == grp && entry->cmd == cmd)
return entry->cmd_ver;
}
return IWM_FW_CMD_VER_UNKNOWN;
}
static int
iwm_is_mimo_ht_plcp(uint8_t ht_plcp)
{
return (ht_plcp != IWM_RATE_HT_SISO_MCS_INV_PLCP &&
(ht_plcp & IWM_RATE_HT_MCS_NSS_MSK));
}
static int
iwm_is_mimo_ht_mcs(int mcs)
{
int ridx = iwm_ht_mcs2ridx[mcs];
return iwm_is_mimo_ht_plcp(iwm_rates[ridx].ht_plcp);
}
#endif
static int
iwm_store_cscheme(struct iwm_softc *sc, uint8_t *data, size_t dlen)
{
struct iwm_fw_cscheme_list *l = (struct iwm_fw_cscheme_list *)data;
if (dlen < sizeof(*l) ||
dlen < sizeof(l->size) + l->size * sizeof(*l->cs))
return EINVAL;
return 0;
}
static int
iwm_firmware_store_section(struct iwm_softc *sc, enum iwm_ucode_type type,
uint8_t *data, size_t dlen)
{
struct iwm_fw_sects *fws;
struct iwm_fw_onesect *fwone;
if (type >= IWM_UCODE_TYPE_MAX)
return EINVAL;
if (dlen < sizeof(uint32_t))
return EINVAL;
fws = &sc->sc_fw.fw_sects[type];
if (fws->fw_count >= IWM_UCODE_SECT_MAX)
return EINVAL;
fwone = &fws->fw_sect[fws->fw_count];
memcpy(&fwone->fws_devoff, data, sizeof(uint32_t));
fwone->fws_data = data + sizeof(uint32_t);
fwone->fws_len = dlen - sizeof(uint32_t);
fwone->fws_alloc = data;
fwone->fws_allocsize = dlen;
fws->fw_count++;
fws->fw_totlen += fwone->fws_len;
return 0;
}
struct iwm_tlv_calib_data {
uint32_t ucode_type;
struct iwm_tlv_calib_ctrl calib;
} __packed;
static int
iwm_set_default_calib(struct iwm_softc *sc, const void *data)
{
const struct iwm_tlv_calib_data *def_calib = data;
uint32_t ucode_type = le32toh(def_calib->ucode_type);
if (ucode_type >= IWM_UCODE_TYPE_MAX) {
DPRINTF(("%s: Wrong ucode_type %u for default calibration.\n",
DEVNAME(sc), ucode_type));
return EINVAL;
}
sc->sc_default_calib[ucode_type].flow_trigger =
def_calib->calib.flow_trigger;
sc->sc_default_calib[ucode_type].event_trigger =
def_calib->calib.event_trigger;
return 0;
}
static void
iwm_fw_info_free(struct iwm_fw_info *fw)
{
kmem_free(fw->fw_rawdata, fw->fw_rawsize);
fw->fw_rawdata = NULL;
fw->fw_rawsize = 0;
memset(fw->fw_sects, 0, sizeof(fw->fw_sects));
}
static void
iwm_fw_version_str(char *buf, size_t bufsize,
uint32_t major, uint32_t minor, uint32_t api)
{
if (major >= 35)
snprintf(buf, bufsize, "%u.%08x.%u", major, minor, api);
else
snprintf(buf, bufsize, "%u.%u.%u", major, minor, api);
}
static int
iwm_read_firmware(struct iwm_softc *sc, enum iwm_ucode_type ucode_type)
{
struct iwm_fw_info *fw = &sc->sc_fw;
struct iwm_tlv_ucode_header *uhdr;
struct iwm_ucode_tlv tlv;
enum iwm_ucode_tlv_type tlv_type;
uint8_t *data;
int err;
size_t len;
if (fw->fw_status == IWM_FW_STATUS_DONE)
return 0;
while (fw->fw_status == IWM_FW_STATUS_INPROGRESS)
tsleep(&sc->sc_fw, 0, "iwmfwp", 0);
fw->fw_status = IWM_FW_STATUS_INPROGRESS;
if (fw->fw_status == IWM_FW_STATUS_DONE)
return 0;
if (fw->fw_rawdata != NULL) {
iwm_fw_info_free(fw);
CLR(sc->sc_flags, IWM_FLAG_FW_LOADED);
}
err = iwm_firmload(sc);
if (err) {
aprint_error_dev(sc->sc_dev,
"could not read firmware %s (error %d)\n",
sc->sc_fwname, err);
goto out;
}
sc->sc_capaflags = 0;
sc->sc_capa_n_scan_channels = IWM_DEFAULT_SCAN_CHANNELS;
memset(sc->sc_enabled_capa, 0, sizeof(sc->sc_enabled_capa));
memset(sc->sc_ucode_api, 0, sizeof(sc->sc_ucode_api));
sc->n_cmd_versions = 0;
uhdr = (void *)fw->fw_rawdata;
if (*(uint32_t *)fw->fw_rawdata != 0
|| le32toh(uhdr->magic) != IWM_TLV_UCODE_MAGIC) {
aprint_error_dev(sc->sc_dev, "invalid firmware %s\n",
sc->sc_fwname);
err = EINVAL;
goto out;
}
iwm_fw_version_str(sc->sc_fwver, sizeof(sc->sc_fwver),
IWM_UCODE_MAJOR(le32toh(uhdr->ver)),
IWM_UCODE_MINOR(le32toh(uhdr->ver)),
IWM_UCODE_API(le32toh(uhdr->ver)));
data = uhdr->data;
len = fw->fw_rawsize - sizeof(*uhdr);
while (len >= sizeof(tlv)) {
size_t tlv_len;
void *tlv_data;
memcpy(&tlv, data, sizeof(tlv));
tlv_len = le32toh(tlv.length);
tlv_type = le32toh(tlv.type);
len -= sizeof(tlv);
data += sizeof(tlv);
tlv_data = data;
if (len < tlv_len) {
aprint_error_dev(sc->sc_dev,
"firmware too short: %zu bytes\n", len);
err = EINVAL;
goto parse_out;
}
switch (tlv_type) {
case IWM_UCODE_TLV_PROBE_MAX_LEN:
if (tlv_len < sizeof(uint32_t)) {
err = EINVAL;
goto parse_out;
}
sc->sc_capa_max_probe_len
= le32toh(*(uint32_t *)tlv_data);
if (sc->sc_capa_max_probe_len >
IWM_SCAN_OFFLOAD_PROBE_REQ_SIZE) {
err = EINVAL;
goto parse_out;
}
break;
case IWM_UCODE_TLV_PAN:
if (tlv_len) {
err = EINVAL;
goto parse_out;
}
sc->sc_capaflags |= IWM_UCODE_TLV_FLAGS_PAN;
break;
case IWM_UCODE_TLV_FLAGS:
if (tlv_len < sizeof(uint32_t)) {
err = EINVAL;
goto parse_out;
}
if (tlv_len % sizeof(uint32_t)) {
err = EINVAL;
goto parse_out;
}
sc->sc_capaflags = le32toh(*(uint32_t *)tlv_data);
break;
case IWM_UCODE_TLV_CSCHEME:
err = iwm_store_cscheme(sc, tlv_data, tlv_len);
if (err)
goto parse_out;
break;
case IWM_UCODE_TLV_NUM_OF_CPU: {
uint32_t num_cpu;
if (tlv_len != sizeof(uint32_t)) {
err = EINVAL;
goto parse_out;
}
num_cpu = le32toh(*(uint32_t *)tlv_data);
if (num_cpu < 1 || num_cpu > 2) {
err = EINVAL;
goto parse_out;
}
break;
}
case IWM_UCODE_TLV_SEC_RT:
err = iwm_firmware_store_section(sc,
IWM_UCODE_TYPE_REGULAR, tlv_data, tlv_len);
if (err)
goto parse_out;
break;
case IWM_UCODE_TLV_SEC_INIT:
err = iwm_firmware_store_section(sc,
IWM_UCODE_TYPE_INIT, tlv_data, tlv_len);
if (err)
goto parse_out;
break;
case IWM_UCODE_TLV_SEC_WOWLAN:
err = iwm_firmware_store_section(sc,
IWM_UCODE_TYPE_WOW, tlv_data, tlv_len);
if (err)
goto parse_out;
break;
case IWM_UCODE_TLV_DEF_CALIB:
if (tlv_len != sizeof(struct iwm_tlv_calib_data)) {
err = EINVAL;
goto parse_out;
}
err = iwm_set_default_calib(sc, tlv_data);
if (err)
goto parse_out;
break;
case IWM_UCODE_TLV_PHY_SKU:
if (tlv_len != sizeof(uint32_t)) {
err = EINVAL;
goto parse_out;
}
sc->sc_fw_phy_config = le32toh(*(uint32_t *)tlv_data);
break;
case IWM_UCODE_TLV_API_CHANGES_SET: {
struct iwm_ucode_api *api;
uint32_t idx, bits;
int i;
if (tlv_len != sizeof(*api)) {
err = EINVAL;
goto parse_out;
}
api = (struct iwm_ucode_api *)tlv_data;
idx = le32toh(api->api_index);
bits = le32toh(api->api_flags);
if (idx >= howmany(IWM_NUM_UCODE_TLV_API, 32)) {
err = EINVAL;
goto parse_out;
}
for (i = 0; i < 32; i++) {
if (!ISSET(bits, __BIT(i)))
continue;
setbit(sc->sc_ucode_api, i + (32 * idx));
}
break;
}
case IWM_UCODE_TLV_ENABLED_CAPABILITIES: {
struct iwm_ucode_capa *capa;
uint32_t idx, bits;
int i;
if (tlv_len != sizeof(*capa)) {
err = EINVAL;
goto parse_out;
}
capa = (struct iwm_ucode_capa *)tlv_data;
idx = le32toh(capa->api_index);
bits = le32toh(capa->api_capa);
if (idx >= howmany(IWM_NUM_UCODE_TLV_CAPA, 32)) {
err = EINVAL;
goto parse_out;
}
for (i = 0; i < 32; i++) {
if (!ISSET(bits, __BIT(i)))
continue;
setbit(sc->sc_enabled_capa, i + (32 * idx));
}
break;
}
case IWM_UCODE_TLV_CMD_VERSIONS:
if (tlv_len % sizeof(struct iwm_fw_cmd_version)) {
tlv_len /= sizeof(struct iwm_fw_cmd_version);
tlv_len *= sizeof(struct iwm_fw_cmd_version);
}
if (sc->n_cmd_versions != 0) {
err = EINVAL;
goto parse_out;
}
if (tlv_len > sizeof(sc->cmd_versions)) {
err = EINVAL;
goto parse_out;
}
memcpy(&sc->cmd_versions[0], tlv_data, tlv_len);
sc->n_cmd_versions = tlv_len / sizeof(struct iwm_fw_cmd_version);
break;
case IWM_UCODE_TLV_SDIO_ADMA_ADDR:
case IWM_UCODE_TLV_FW_GSCAN_CAPA:
case IWM_UCODE_TLV_FW_MEM_SEG:
break;
case IWM_UCODE_TLV_SEC_RT_USNIFFER:
err = iwm_firmware_store_section(sc,
IWM_UCODE_TYPE_REGULAR_USNIFFER, tlv_data,
tlv_len);
if (err)
goto parse_out;
break;
case IWM_UCODE_TLV_PAGING: {
uint32_t paging_mem_size;
if (tlv_len != sizeof(paging_mem_size)) {
err = EINVAL;
goto parse_out;
}
paging_mem_size = le32toh(*(uint32_t *)tlv_data);
if (paging_mem_size > IWM_MAX_PAGING_IMAGE_SIZE) {
err = EINVAL;
goto parse_out;
}
if (paging_mem_size & (IWM_FW_PAGING_SIZE - 1)) {
err = EINVAL;
goto parse_out;
}
fw->fw_sects[IWM_UCODE_TYPE_REGULAR].paging_mem_size =
paging_mem_size;
fw->fw_sects[IWM_UCODE_TYPE_REGULAR_USNIFFER].paging_mem_size =
paging_mem_size;
break;
}
case IWM_UCODE_TLV_N_SCAN_CHANNELS:
if (tlv_len != sizeof(uint32_t)) {
err = EINVAL;
goto parse_out;
}
sc->sc_capa_n_scan_channels =
le32toh(*(uint32_t *)tlv_data);
if (sc->sc_capa_n_scan_channels > IWM_MAX_SCAN_CHANNELS) {
err = ERANGE;
goto parse_out;
}
break;
case IWM_UCODE_TLV_FW_VERSION:
if (tlv_len != sizeof(uint32_t) * 3) {
err = EINVAL;
goto parse_out;
}
iwm_fw_version_str(sc->sc_fwver, sizeof(sc->sc_fwver),
le32toh(((uint32_t *)tlv_data)[0]),
le32toh(((uint32_t *)tlv_data)[1]),
le32toh(((uint32_t *)tlv_data)[2]));
break;
default:
DPRINTF(("%s: unknown firmware section %d, abort\n",
DEVNAME(sc), tlv_type));
err = EINVAL;
goto parse_out;
}
if (roundup(tlv_len, 4) > len)
break;
len -= roundup(tlv_len, 4);
data += roundup(tlv_len, 4);
}
KASSERT(err == 0);
parse_out:
if (err) {
aprint_error_dev(sc->sc_dev, "firmware parse error %d, "
"section type %d\n", err, tlv_type);
}
if (!(sc->sc_capaflags & IWM_UCODE_TLV_FLAGS_PM_CMD_SUPPORT)) {
aprint_error_dev(sc->sc_dev,
"device uses unsupported power ops\n");
err = ENOTSUP;
}
out:
if (err) {
fw->fw_status = IWM_FW_STATUS_NONE;
if (fw->fw_rawdata != NULL) {
iwm_fw_info_free(fw);
CLR(sc->sc_flags, IWM_FLAG_FW_LOADED);
}
} else {
fw->fw_status = IWM_FW_STATUS_DONE;
}
wakeup(&sc->sc_fw);
return err;
}
static uint32_t
iwm_read_prph(struct iwm_softc *sc, uint32_t addr)
{
IWM_WRITE(sc,
IWM_HBUS_TARG_PRPH_RADDR, ((addr & 0x000fffff) | (3 << 24)));
IWM_BARRIER_READ_WRITE(sc);
return IWM_READ(sc, IWM_HBUS_TARG_PRPH_RDAT);
}
static void
iwm_write_prph(struct iwm_softc *sc, uint32_t addr, uint32_t val)
{
IWM_WRITE(sc,
IWM_HBUS_TARG_PRPH_WADDR, ((addr & 0x000fffff) | (3 << 24)));
IWM_BARRIER_WRITE(sc);
IWM_WRITE(sc, IWM_HBUS_TARG_PRPH_WDAT, val);
}
static int
iwm_read_mem(struct iwm_softc *sc, uint32_t addr, void *buf, int dwords)
{
int offs;
uint32_t *vals = buf;
if (iwm_nic_lock(sc)) {
IWM_WRITE(sc, IWM_HBUS_TARG_MEM_RADDR, addr);
for (offs = 0; offs < dwords; offs++)
vals[offs] = IWM_READ(sc, IWM_HBUS_TARG_MEM_RDAT);
iwm_nic_unlock(sc);
return 0;
}
return EBUSY;
}
static int
iwm_write_mem(struct iwm_softc *sc, uint32_t addr, const void *buf, int dwords)
{
int offs;
const uint32_t *vals = buf;
if (iwm_nic_lock(sc)) {
IWM_WRITE(sc, IWM_HBUS_TARG_MEM_WADDR, addr);
for (offs = 0; offs < dwords; offs++) {
uint32_t val = vals ? vals[offs] : 0;
IWM_WRITE(sc, IWM_HBUS_TARG_MEM_WDAT, val);
}
iwm_nic_unlock(sc);
return 0;
}
return EBUSY;
}
static int
iwm_write_mem32(struct iwm_softc *sc, uint32_t addr, uint32_t val)
{
return iwm_write_mem(sc, addr, &val, 1);
}
static int
iwm_poll_bit(struct iwm_softc *sc, int reg, uint32_t bits, uint32_t mask,
int timo)
{
for (;;) {
if ((IWM_READ(sc, reg) & mask) == (bits & mask)) {
return 1;
}
if (timo < 10) {
return 0;
}
timo -= 10;
DELAY(10);
}
}
static int
iwm_nic_lock(struct iwm_softc *sc)
{
mutex_enter(&sc->sc_nic_mtx);
if (sc->sc_nic_locks++ > 0) {
mutex_exit(&sc->sc_nic_mtx);
return 1;
}
IWM_SETBITS(sc, IWM_CSR_GP_CNTRL,
IWM_CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ);
mutex_exit(&sc->sc_nic_mtx);
if (sc->sc_device_family >= IWM_DEVICE_FAMILY_8000)
DELAY(2);
if (iwm_poll_bit(sc, IWM_CSR_GP_CNTRL,
IWM_CSR_GP_CNTRL_REG_VAL_MAC_ACCESS_EN,
IWM_CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY
| IWM_CSR_GP_CNTRL_REG_FLAG_GOING_TO_SLEEP, 150000)) {
return 1;
}
mutex_enter(&sc->sc_nic_mtx);
IWM_CLRBITS(sc, IWM_CSR_GP_CNTRL,
IWM_CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ);
--sc->sc_nic_locks;
mutex_exit(&sc->sc_nic_mtx);
device_printf(sc->sc_dev, "acquiring device failed\n");
return 0;
}
static void
iwm_nic_unlock(struct iwm_softc *sc)
{
int err = 1;
mutex_enter(&sc->sc_nic_mtx);
if (sc->sc_nic_locks > 0) {
if (--sc->sc_nic_locks == 0) {
IWM_CLRBITS(sc, IWM_CSR_GP_CNTRL,
IWM_CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ);
}
err = 0;
}
mutex_exit(&sc->sc_nic_mtx);
if (err)
device_printf(sc->sc_dev, "NIC already unlocked\n");
}
static int
iwm_set_bits_mask_prph(struct iwm_softc *sc, uint32_t reg, uint32_t bits,
uint32_t mask)
{
uint32_t val;
if (iwm_nic_lock(sc)) {
val = iwm_read_prph(sc, reg) & mask;
val |= bits;
iwm_write_prph(sc, reg, val);
iwm_nic_unlock(sc);
return 0;
}
return EBUSY;
}
static int
iwm_set_bits_prph(struct iwm_softc *sc, uint32_t reg, uint32_t bits)
{
return iwm_set_bits_mask_prph(sc, reg, bits, ~0);
}
static int
iwm_clear_bits_prph(struct iwm_softc *sc, uint32_t reg, uint32_t bits)
{
return iwm_set_bits_mask_prph(sc, reg, 0, ~bits);
}
static int
iwm_dma_contig_alloc(bus_dma_tag_t tag, struct iwm_dma_info *dma,
bus_size_t size, bus_size_t alignment)
{
int nsegs, err;
void *va;
dma->tag = tag;
dma->size = size;
err = bus_dmamap_create(tag, size, 1, size, 0, BUS_DMA_NOWAIT,
&dma->map);
if (err)
goto fail;
err = bus_dmamem_alloc(tag, size, alignment, 0, &dma->seg, 1, &nsegs,
BUS_DMA_NOWAIT);
if (err)
goto fail;
err = bus_dmamem_map(tag, &dma->seg, 1, size, &va, BUS_DMA_NOWAIT);
if (err)
goto fail;
dma->vaddr = va;
err = bus_dmamap_load(tag, dma->map, dma->vaddr, size, NULL,
BUS_DMA_NOWAIT);
if (err)
goto fail;
memset(dma->vaddr, 0, size);
bus_dmamap_sync(tag, dma->map, 0, size, BUS_DMASYNC_PREWRITE);
dma->paddr = dma->map->dm_segs[0].ds_addr;
return 0;
fail: iwm_dma_contig_free(dma);
return err;
}
static void
iwm_dma_contig_free(struct iwm_dma_info *dma)
{
if (dma->map != NULL) {
if (dma->vaddr != NULL) {
bus_dmamap_sync(dma->tag, dma->map, 0, dma->size,
BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
bus_dmamap_unload(dma->tag, dma->map);
bus_dmamem_unmap(dma->tag, dma->vaddr, dma->size);
bus_dmamem_free(dma->tag, &dma->seg, 1);
dma->vaddr = NULL;
}
bus_dmamap_destroy(dma->tag, dma->map);
dma->map = NULL;
}
}
static int
iwm_alloc_rx_ring(struct iwm_softc *sc, struct iwm_rx_ring *ring)
{
bus_size_t size;
int i, err;
ring->cur = 0;
size = IWM_RX_RING_COUNT * sizeof(uint32_t);
err = iwm_dma_contig_alloc(sc->sc_dmat, &ring->desc_dma, size, 256);
if (err) {
aprint_error_dev(sc->sc_dev,
"could not allocate RX ring DMA memory\n");
goto fail;
}
ring->desc = ring->desc_dma.vaddr;
err = iwm_dma_contig_alloc(sc->sc_dmat, &ring->stat_dma,
sizeof(*ring->stat), 16);
if (err) {
aprint_error_dev(sc->sc_dev,
"could not allocate RX status DMA memory\n");
goto fail;
}
ring->stat = ring->stat_dma.vaddr;
for (i = 0; i < IWM_RX_RING_COUNT; i++) {
struct iwm_rx_data *data = &ring->data[i];
memset(data, 0, sizeof(*data));
err = bus_dmamap_create(sc->sc_dmat, IWM_RBUF_SIZE, 1,
IWM_RBUF_SIZE, 0, BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW,
&data->map);
if (err) {
aprint_error_dev(sc->sc_dev,
"could not create RX buf DMA map\n");
goto fail;
}
err = iwm_rx_addbuf(sc, IWM_RBUF_SIZE, i);
if (err)
goto fail;
}
return 0;
fail: iwm_free_rx_ring(sc, ring);
return err;
}
static void
iwm_disable_rx_dma(struct iwm_softc *sc)
{
int ntries;
if (iwm_nic_lock(sc)) {
IWM_WRITE(sc, IWM_FH_MEM_RCSR_CHNL0_CONFIG_REG, 0);
for (ntries = 0; ntries < 1000; ntries++) {
if (IWM_READ(sc, IWM_FH_MEM_RSSR_RX_STATUS_REG) &
IWM_FH_RSSR_CHNL0_RX_STATUS_CHNL_IDLE)
break;
DELAY(10);
}
iwm_nic_unlock(sc);
}
}
void
iwm_reset_rx_ring(struct iwm_softc *sc, struct iwm_rx_ring *ring)
{
ring->cur = 0;
bus_dmamap_sync(sc->sc_dmat, ring->stat_dma.map, 0,
ring->stat_dma.size, BUS_DMASYNC_PREWRITE);
memset(ring->stat, 0, sizeof(*ring->stat));
bus_dmamap_sync(sc->sc_dmat, ring->stat_dma.map, 0,
ring->stat_dma.size, BUS_DMASYNC_POSTWRITE);
}
static void
iwm_free_rx_ring(struct iwm_softc *sc, struct iwm_rx_ring *ring)
{
int i;
iwm_dma_contig_free(&ring->desc_dma);
iwm_dma_contig_free(&ring->stat_dma);
for (i = 0; i < IWM_RX_RING_COUNT; i++) {
struct iwm_rx_data *data = &ring->data[i];
if (data->m != NULL) {
bus_size_t sz = data->m->m_pkthdr.len;
bus_dmamap_sync(sc->sc_dmat, data->map, 0,
sz, BUS_DMASYNC_POSTREAD);
bus_dmamap_unload(sc->sc_dmat, data->map);
m_freem(data->m);
data->m = NULL;
}
if (data->map != NULL) {
bus_dmamap_destroy(sc->sc_dmat, data->map);
data->map = NULL;
}
}
}
static int
iwm_alloc_tx_ring(struct iwm_softc *sc, struct iwm_tx_ring *ring, int qid)
{
bus_addr_t paddr;
bus_size_t size;
int i, err, nsegs;
ring->qid = qid;
ring->queued = 0;
ring->cur = 0;
size = IWM_TX_RING_COUNT * sizeof (struct iwm_tfd);
err = iwm_dma_contig_alloc(sc->sc_dmat, &ring->desc_dma, size, 256);
if (err) {
aprint_error_dev(sc->sc_dev,
"could not allocate TX ring DMA memory\n");
goto fail;
}
ring->desc = ring->desc_dma.vaddr;
if (qid > IWM_LAST_AGG_TX_QUEUE)
return 0;
size = IWM_TX_RING_COUNT * sizeof(struct iwm_device_cmd);
err = iwm_dma_contig_alloc(sc->sc_dmat, &ring->cmd_dma, size, 4);
if (err) {
aprint_error_dev(sc->sc_dev,
"could not allocate TX cmd DMA memory\n");
goto fail;
}
ring->cmd = ring->cmd_dma.vaddr;
paddr = ring->cmd_dma.paddr;
for (i = 0; i < IWM_TX_RING_COUNT; i++) {
struct iwm_tx_data *data = &ring->data[i];
size_t mapsize;
data->cmd_paddr = paddr;
data->scratch_paddr = paddr + sizeof(struct iwm_cmd_header)
+ offsetof(struct iwm_tx_cmd, scratch);
paddr += sizeof(struct iwm_device_cmd);
if (qid == IWM_CMD_QUEUE || qid == IWM_DQA_CMD_QUEUE) {
mapsize = IWM_RBUF_SIZE;
nsegs = 1;
} else {
mapsize = MCLBYTES;
nsegs = IWM_NUM_OF_TBS - 2;
}
err = bus_dmamap_create(sc->sc_dmat, mapsize,
nsegs, mapsize, 0, BUS_DMA_NOWAIT,
&data->map);
if (err) {
aprint_error_dev(sc->sc_dev,
"could not create TX buf DMA map\n");
goto fail;
}
}
KASSERT(paddr == ring->cmd_dma.paddr + size);
return 0;
fail: iwm_free_tx_ring(sc, ring);
return err;
}
static void
iwm_reset_tx_ring(struct iwm_softc *sc, struct iwm_tx_ring *ring)
{
int i;
for (i = 0; i < IWM_TX_RING_COUNT; i++) {
struct iwm_tx_data *data = &ring->data[i];
if (data->m != NULL) {
bus_dmamap_sync(sc->sc_dmat, data->map, 0,
data->map->dm_mapsize, BUS_DMASYNC_POSTWRITE);
bus_dmamap_unload(sc->sc_dmat, data->map);
m_freem(data->m);
data->m = NULL;
}
}
memset(ring->desc, 0, ring->desc_dma.size);
bus_dmamap_sync(sc->sc_dmat, ring->desc_dma.map, 0,
ring->desc_dma.size, BUS_DMASYNC_PREWRITE);
sc->qfullmsk &= ~(1 << ring->qid);
if (sc->sc_device_family == IWM_DEVICE_FAMILY_7000) {
if (ring->qid == sc->cmdqid && ring->queued > 0) {
iwm_nic_unlock(sc);
}
}
ring->queued = 0;
ring->cur = 0;
}
static void
iwm_free_tx_ring(struct iwm_softc *sc, struct iwm_tx_ring *ring)
{
int i;
iwm_dma_contig_free(&ring->desc_dma);
iwm_dma_contig_free(&ring->cmd_dma);
for (i = 0; i < IWM_TX_RING_COUNT; i++) {
struct iwm_tx_data *data = &ring->data[i];
if (data->m != NULL) {
bus_dmamap_sync(sc->sc_dmat, data->map, 0,
data->map->dm_mapsize, BUS_DMASYNC_POSTWRITE);
bus_dmamap_unload(sc->sc_dmat, data->map);
m_freem(data->m);
data->m = NULL;
}
if (data->map != NULL) {
bus_dmamap_destroy(sc->sc_dmat, data->map);
data->map = NULL;
}
}
}
static void
iwm_enable_rfkill_int(struct iwm_softc *sc)
{
sc->sc_intmask = IWM_CSR_INT_BIT_RF_KILL;
IWM_WRITE(sc, IWM_CSR_INT_MASK, sc->sc_intmask);
}
static int
iwm_check_rfkill(struct iwm_softc *sc)
{
uint32_t v;
int rv;
v = IWM_READ(sc, IWM_CSR_GP_CNTRL);
rv = (v & IWM_CSR_GP_CNTRL_REG_FLAG_HW_RF_KILL_SW) == 0;
if (rv) {
sc->sc_flags |= IWM_FLAG_RFKILL;
} else {
sc->sc_flags &= ~IWM_FLAG_RFKILL;
}
return rv;
}
static void
iwm_enable_interrupts(struct iwm_softc *sc)
{
sc->sc_intmask = IWM_CSR_INI_SET_MASK;
IWM_WRITE(sc, IWM_CSR_INT_MASK, sc->sc_intmask);
}
static void
iwm_enable_fwload_interrupt(struct iwm_softc *sc)
{
sc->sc_intmask = IWM_CSR_INT_BIT_FH_TX;
IWM_WRITE(sc, IWM_CSR_INT_MASK, sc->sc_intmask);
}
static void
iwm_restore_interrupts(struct iwm_softc *sc)
{
IWM_WRITE(sc, IWM_CSR_INT_MASK, sc->sc_intmask);
}
static void
iwm_disable_interrupts(struct iwm_softc *sc)
{
IWM_WRITE(sc, IWM_CSR_INT_MASK, 0);
IWM_WRITE(sc, IWM_CSR_INT, ~0);
IWM_WRITE(sc, IWM_CSR_FH_INT_STATUS, ~0);
}
static void
iwm_ict_reset(struct iwm_softc *sc)
{
iwm_disable_interrupts(sc);
memset(sc->ict_dma.vaddr, 0, IWM_ICT_SIZE);
bus_dmamap_sync(sc->sc_dmat, sc->ict_dma.map, 0, sc->ict_dma.size,
BUS_DMASYNC_PREWRITE);
sc->ict_cur = 0;
IWM_WRITE(sc, IWM_CSR_DRAM_INT_TBL_REG,
IWM_CSR_DRAM_INT_TBL_ENABLE
| IWM_CSR_DRAM_INIT_TBL_WRAP_CHECK
| IWM_CSR_DRAM_INIT_TBL_WRITE_POINTER
| sc->ict_dma.paddr >> IWM_ICT_PADDR_SHIFT);
sc->sc_flags |= IWM_FLAG_USE_ICT;
IWM_WRITE(sc, IWM_CSR_INT, ~0);
iwm_enable_interrupts(sc);
}
#define IWM_HW_READY_TIMEOUT 50
static int
iwm_set_hw_ready(struct iwm_softc *sc)
{
int ready;
IWM_SETBITS(sc, IWM_CSR_HW_IF_CONFIG_REG,
IWM_CSR_HW_IF_CONFIG_REG_BIT_NIC_READY);
ready = iwm_poll_bit(sc, IWM_CSR_HW_IF_CONFIG_REG,
IWM_CSR_HW_IF_CONFIG_REG_BIT_NIC_READY,
IWM_CSR_HW_IF_CONFIG_REG_BIT_NIC_READY,
IWM_HW_READY_TIMEOUT);
if (ready)
IWM_SETBITS(sc, IWM_CSR_MBOX_SET_REG,
IWM_CSR_MBOX_SET_REG_OS_ALIVE);
return ready;
}
#undef IWM_HW_READY_TIMEOUT
static int
iwm_prepare_card_hw(struct iwm_softc *sc)
{
int t = 0;
int ntries;
if (iwm_set_hw_ready(sc))
return 0;
IWM_SETBITS(sc, IWM_CSR_DBG_LINK_PWR_MGMT_REG,
IWM_CSR_RESET_LINK_PWR_MGMT_DISABLED);
DELAY(1000);
for (ntries = 0; ntries < 10; ntries++) {
IWM_SETBITS(sc, IWM_CSR_HW_IF_CONFIG_REG,
IWM_CSR_HW_IF_CONFIG_REG_PREPARE);
do {
if (iwm_set_hw_ready(sc))
return 0;
DELAY(200);
t += 200;
} while (t < 150000);
DELAY(25000);
}
return ETIMEDOUT;
}
static void
iwm_apm_config(struct iwm_softc *sc)
{
pcireg_t reg;
reg = pci_conf_read(sc->sc_pct, sc->sc_pcitag,
sc->sc_cap_off + PCIE_LCSR);
if (reg & PCIE_LCSR_ASPM_L1) {
IWM_SETBITS(sc, IWM_CSR_GIO_REG,
IWM_CSR_GIO_REG_VAL_L0S_ENABLED);
} else {
IWM_CLRBITS(sc, IWM_CSR_GIO_REG,
IWM_CSR_GIO_REG_VAL_L0S_ENABLED);
}
#ifdef notyet
reg = pci_conf_read(sc->sc_pct, sc->sc_pcitag,
sc->sc_cap_off + PCIE_DCSR2);
sc->sc_ltr_enabled = (reg & PCIE_DCSR2_LTR_MEC) ? 1 : 0;
DPRINTF(("%s: L1 %sabled - LTR %sabled\n",
DEVNAME(sc),
(lctl & PCI_PCIE_LCSR_ASPM_L1) ? "En" : "Dis",
sc->sc_ltr_enabled ? "En" : "Dis"));
#endif
}
static int
iwm_apm_init(struct iwm_softc *sc)
{
int err = 0;
if (sc->sc_device_family < IWM_DEVICE_FAMILY_8000) {
IWM_SETBITS(sc, IWM_CSR_GIO_CHICKEN_BITS,
IWM_CSR_GIO_CHICKEN_BITS_REG_BIT_DIS_L0S_EXIT_TIMER);
}
IWM_SETBITS(sc, IWM_CSR_GIO_CHICKEN_BITS,
IWM_CSR_GIO_CHICKEN_BITS_REG_BIT_L1A_NO_L0S_RX);
IWM_SETBITS(sc, IWM_CSR_DBG_HPET_MEM_REG, IWM_CSR_DBG_HPET_MEM_REG_VAL);
IWM_SETBITS(sc, IWM_CSR_HW_IF_CONFIG_REG,
IWM_CSR_HW_IF_CONFIG_REG_BIT_HAP_WAKE_L1A);
iwm_apm_config(sc);
#if 0
if (trans->cfg->base_params->pll_cfg_val)
IWM_SETBITS(trans, IWM_CSR_ANA_PLL_CFG,
trans->cfg->base_params->pll_cfg_val);
#endif
IWM_SETBITS(sc, IWM_CSR_GP_CNTRL, IWM_CSR_GP_CNTRL_REG_FLAG_INIT_DONE);
if (!iwm_poll_bit(sc, IWM_CSR_GP_CNTRL,
IWM_CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY,
IWM_CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY, 25000)) {
aprint_error_dev(sc->sc_dev,
"timeout waiting for clock stabilization\n");
err = ETIMEDOUT;
goto out;
}
if (sc->host_interrupt_operation_mode) {
if (iwm_nic_lock(sc)) {
iwm_read_prph(sc, IWM_OSC_CLK);
iwm_read_prph(sc, IWM_OSC_CLK);
iwm_nic_unlock(sc);
}
err = iwm_set_bits_prph(sc, IWM_OSC_CLK,
IWM_OSC_CLK_FORCE_CONTROL);
if (err)
goto out;
if (iwm_nic_lock(sc)) {
iwm_read_prph(sc, IWM_OSC_CLK);
iwm_read_prph(sc, IWM_OSC_CLK);
iwm_nic_unlock(sc);
}
}
if (sc->sc_device_family == IWM_DEVICE_FAMILY_7000) {
if (iwm_nic_lock(sc)) {
iwm_write_prph(sc, IWM_APMG_CLK_EN_REG,
IWM_APMG_CLK_VAL_DMA_CLK_RQT);
iwm_nic_unlock(sc);
}
DELAY(20);
err = iwm_set_bits_prph(sc, IWM_APMG_PCIDEV_STT_REG,
IWM_APMG_PCIDEV_STT_VAL_L1_ACT_DIS);
if (err)
goto out;
if (iwm_nic_lock(sc)) {
iwm_write_prph(sc, IWM_APMG_RTC_INT_STT_REG,
IWM_APMG_RTC_INT_STT_RFKILL);
iwm_nic_unlock(sc);
}
}
out:
if (err)
aprint_error_dev(sc->sc_dev, "apm init error %d\n", err);
return err;
}
static void
iwm_apm_stop(struct iwm_softc *sc)
{
IWM_SETBITS(sc, IWM_CSR_DBG_LINK_PWR_MGMT_REG,
IWM_CSR_RESET_LINK_PWR_MGMT_DISABLED);
IWM_SETBITS(sc, IWM_CSR_HW_IF_CONFIG_REG,
IWM_CSR_HW_IF_CONFIG_REG_PREPARE |
IWM_CSR_HW_IF_CONFIG_REG_ENABLE_PME);
DELAY(1000);
IWM_CLRBITS(sc, IWM_CSR_DBG_LINK_PWR_MGMT_REG,
IWM_CSR_RESET_LINK_PWR_MGMT_DISABLED);
DELAY(5000);
IWM_SETBITS(sc, IWM_CSR_RESET, IWM_CSR_RESET_REG_FLAG_STOP_MASTER);
if (!iwm_poll_bit(sc, IWM_CSR_RESET,
IWM_CSR_RESET_REG_FLAG_MASTER_DISABLED,
IWM_CSR_RESET_REG_FLAG_MASTER_DISABLED, 100))
aprint_error_dev(sc->sc_dev, "timeout waiting for master\n");
DPRINTF(("iwm apm stop\n"));
IWM_CLRBITS(sc, IWM_CSR_GP_CNTRL,
IWM_CSR_GP_CNTRL_REG_FLAG_INIT_DONE);
}
static int
iwm_start_hw(struct iwm_softc *sc)
{
int err;
err = iwm_prepare_card_hw(sc);
if (err)
return err;
IWM_WRITE(sc, IWM_CSR_RESET, IWM_CSR_RESET_REG_FLAG_SW_RESET);
DELAY(5000);
err = iwm_apm_init(sc);
if (err)
return err;
iwm_enable_rfkill_int(sc);
iwm_check_rfkill(sc);
return 0;
}
static void
iwm_stop_device(struct iwm_softc *sc)
{
int chnl, ntries;
int qid;
iwm_disable_interrupts(sc);
sc->sc_flags &= ~IWM_FLAG_USE_ICT;
if (iwm_nic_lock(sc)) {
iwm_write_prph(sc, IWM_SCD_TXFACT, 0);
for (chnl = 0; chnl < IWM_FH_TCSR_CHNL_NUM; chnl++) {
IWM_WRITE(sc,
IWM_FH_TCSR_CHNL_TX_CONFIG_REG(chnl), 0);
for (ntries = 0; ntries < 200; ntries++) {
uint32_t r;
r = IWM_READ(sc, IWM_FH_TSSR_TX_STATUS_REG);
if (r & IWM_FH_TSSR_TX_STATUS_REG_MSK_CHNL_IDLE(
chnl))
break;
DELAY(20);
}
}
iwm_nic_unlock(sc);
}
iwm_disable_rx_dma(sc);
iwm_reset_rx_ring(sc, &sc->rxq);
for (qid = 0; qid < __arraycount(sc->txq); qid++)
iwm_reset_tx_ring(sc, &sc->txq[qid]);
if (sc->sc_device_family == IWM_DEVICE_FAMILY_7000) {
if (iwm_nic_lock(sc)) {
iwm_write_prph(sc, IWM_APMG_CLK_DIS_REG,
IWM_APMG_CLK_VAL_DMA_CLK_RQT);
iwm_nic_unlock(sc);
}
DELAY(5);
}
IWM_CLRBITS(sc, IWM_CSR_GP_CNTRL,
IWM_CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ);
if (sc->sc_nic_locks > 0)
device_printf(sc->sc_dev, "%d active NIC locks cleared\n",
sc->sc_nic_locks);
sc->sc_nic_locks = 0;
iwm_apm_stop(sc);
IWM_WRITE(sc, IWM_CSR_RESET, IWM_CSR_RESET_REG_FLAG_SW_RESET);
DELAY(5000);
iwm_disable_interrupts(sc);
iwm_enable_rfkill_int(sc);
iwm_check_rfkill(sc);
iwm_prepare_card_hw(sc);
}
static void
iwm_nic_config(struct iwm_softc *sc)
{
uint8_t radio_cfg_type, radio_cfg_step, radio_cfg_dash;
uint32_t mask, val, reg_val = 0;
radio_cfg_type = (sc->sc_fw_phy_config & IWM_FW_PHY_CFG_RADIO_TYPE) >>
IWM_FW_PHY_CFG_RADIO_TYPE_POS;
radio_cfg_step = (sc->sc_fw_phy_config & IWM_FW_PHY_CFG_RADIO_STEP) >>
IWM_FW_PHY_CFG_RADIO_STEP_POS;
radio_cfg_dash = (sc->sc_fw_phy_config & IWM_FW_PHY_CFG_RADIO_DASH) >>
IWM_FW_PHY_CFG_RADIO_DASH_POS;
reg_val |= IWM_CSR_HW_REV_STEP(sc->sc_hw_rev) <<
IWM_CSR_HW_IF_CONFIG_REG_POS_MAC_STEP;
reg_val |= IWM_CSR_HW_REV_DASH(sc->sc_hw_rev) <<
IWM_CSR_HW_IF_CONFIG_REG_POS_MAC_DASH;
reg_val |= radio_cfg_type << IWM_CSR_HW_IF_CONFIG_REG_POS_PHY_TYPE;
reg_val |= radio_cfg_step << IWM_CSR_HW_IF_CONFIG_REG_POS_PHY_STEP;
reg_val |= radio_cfg_dash << IWM_CSR_HW_IF_CONFIG_REG_POS_PHY_DASH;
mask = IWM_CSR_HW_IF_CONFIG_REG_MSK_MAC_DASH |
IWM_CSR_HW_IF_CONFIG_REG_MSK_MAC_STEP |
IWM_CSR_HW_IF_CONFIG_REG_MSK_PHY_STEP |
IWM_CSR_HW_IF_CONFIG_REG_MSK_PHY_DASH |
IWM_CSR_HW_IF_CONFIG_REG_MSK_PHY_TYPE |
IWM_CSR_HW_IF_CONFIG_REG_BIT_RADIO_SI |
IWM_CSR_HW_IF_CONFIG_REG_BIT_MAC_SI;
val = IWM_READ(sc, IWM_CSR_HW_IF_CONFIG_REG);
val &= ~mask;
val |= reg_val;
IWM_WRITE(sc, IWM_CSR_HW_IF_CONFIG_REG, val);
DPRINTF(("Radio type=0x%x-0x%x-0x%x\n", radio_cfg_type,
radio_cfg_step, radio_cfg_dash));
if (sc->sc_device_family == IWM_DEVICE_FAMILY_7000) {
iwm_set_bits_mask_prph(sc, IWM_APMG_PS_CTRL_REG,
IWM_APMG_PS_CTRL_EARLY_PWR_OFF_RESET_DIS,
~IWM_APMG_PS_CTRL_EARLY_PWR_OFF_RESET_DIS);
}
}
static int
iwm_nic_rx_init(struct iwm_softc *sc)
{
return iwm_nic_rx_legacy_init(sc);
}
static int
iwm_nic_rx_legacy_init(struct iwm_softc *sc)
{
memset(sc->rxq.stat, 0, sizeof(*sc->rxq.stat));
bus_dmamap_sync(sc->sc_dmat, sc->rxq.stat_dma.map,
0, sc->rxq.stat_dma.size,
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
iwm_disable_rx_dma(sc);
if (!iwm_nic_lock(sc))
return EBUSY;
IWM_WRITE(sc, IWM_FH_MEM_RCSR_CHNL0_RBDCB_WPTR, 0);
IWM_WRITE(sc, IWM_FH_MEM_RCSR_CHNL0_FLUSH_RB_REQ, 0);
IWM_WRITE(sc, IWM_FH_RSCSR_CHNL0_RDPTR, 0);
IWM_WRITE(sc, IWM_FH_RSCSR_CHNL0_RBDCB_WPTR_REG, 0);
IWM_WRITE(sc,
IWM_FH_RSCSR_CHNL0_RBDCB_BASE_REG, sc->rxq.desc_dma.paddr >> 8);
IWM_WRITE(sc,
IWM_FH_RSCSR_CHNL0_STTS_WPTR_REG, sc->rxq.stat_dma.paddr >> 4);
IWM_WRITE(sc, IWM_FH_MEM_RCSR_CHNL0_CONFIG_REG,
IWM_FH_RCSR_RX_CONFIG_CHNL_EN_ENABLE_VAL |
IWM_FH_RCSR_CHNL0_RX_IGNORE_RXF_EMPTY |
IWM_FH_RCSR_CHNL0_RX_CONFIG_IRQ_DEST_INT_HOST_VAL |
IWM_FH_RCSR_CHNL0_RX_CONFIG_SINGLE_FRAME_MSK |
(IWM_RX_RB_TIMEOUT << IWM_FH_RCSR_RX_CONFIG_REG_IRQ_RBTH_POS) |
IWM_FH_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_4K |
IWM_RX_QUEUE_SIZE_LOG << IWM_FH_RCSR_RX_CONFIG_RBDCB_SIZE_POS);
IWM_WRITE_1(sc, IWM_CSR_INT_COALESCING, IWM_HOST_INT_TIMEOUT_DEF);
if (sc->host_interrupt_operation_mode)
IWM_SETBITS(sc, IWM_CSR_INT_COALESCING, IWM_HOST_INT_OPER_MODE);
iwm_nic_unlock(sc);
IWM_WRITE(sc, IWM_FH_RSCSR_CHNL0_WPTR, 8);
return 0;
}
static int
iwm_nic_tx_init(struct iwm_softc *sc)
{
int qid, err;
if (!iwm_nic_lock(sc))
return EBUSY;
iwm_write_prph(sc, IWM_SCD_TXFACT, 0);
IWM_WRITE(sc, IWM_FH_KW_MEM_ADDR_REG, sc->kw_dma.paddr >> 4);
for (qid = 0; qid < __arraycount(sc->txq); qid++) {
struct iwm_tx_ring *txq = &sc->txq[qid];
IWM_WRITE(sc, IWM_FH_MEM_CBBC_QUEUE(qid),
txq->desc_dma.paddr >> 8);
DPRINTF(("loading ring %d descriptors (%p) at %"PRIxMAX"\n",
qid, txq->desc, (uintmax_t)(txq->desc_dma.paddr >> 8)));
}
err = iwm_set_bits_prph(sc, IWM_SCD_GP_CTRL,
IWM_SCD_GP_CTRL_AUTO_ACTIVE_MODE |
IWM_SCD_GP_CTRL_ENABLE_31_QUEUES);
iwm_nic_unlock(sc);
return err;
}
static int
iwm_nic_init(struct iwm_softc *sc)
{
int err;
iwm_apm_init(sc);
if (sc->sc_device_family == IWM_DEVICE_FAMILY_7000) {
iwm_set_bits_mask_prph(sc, IWM_APMG_PS_CTRL_REG,
IWM_APMG_PS_CTRL_VAL_PWR_SRC_VMAIN,
~IWM_APMG_PS_CTRL_MSK_PWR_SRC);
}
iwm_nic_config(sc);
err = iwm_nic_rx_init(sc);
if (err)
return err;
err = iwm_nic_tx_init(sc);
if (err)
return err;
DPRINTF(("shadow registers enabled\n"));
IWM_SETBITS(sc, IWM_CSR_MAC_SHADOW_REG_CTRL, 0x800fffff);
return 0;
}
static const uint8_t iwm_ac_to_tx_fifo[] = {
IWM_TX_FIFO_BE,
IWM_TX_FIFO_BK,
IWM_TX_FIFO_VI,
IWM_TX_FIFO_VO,
};
static int
iwm_enable_ac_txq(struct iwm_softc *sc, int qid, int fifo)
{
int err;
IWM_WRITE(sc, IWM_HBUS_TARG_WRPTR, qid << 8 | 0);
iwm_write_prph(sc, IWM_SCD_QUEUE_STATUS_BITS(qid),
(0 << IWM_SCD_QUEUE_STTS_REG_POS_ACTIVE)
| (1 << IWM_SCD_QUEUE_STTS_REG_POS_SCD_ACT_EN));
err = iwm_clear_bits_prph(sc, IWM_SCD_AGGR_SEL, (1 << qid));
if (err) {
return err;
}
iwm_write_prph(sc, IWM_SCD_QUEUE_RDPTR(qid), 0);
iwm_write_mem32(sc,
sc->sched_base + IWM_SCD_CONTEXT_QUEUE_OFFSET(qid), 0);
iwm_write_mem32(sc,
sc->sched_base + IWM_SCD_CONTEXT_QUEUE_OFFSET(qid) +
sizeof(uint32_t),
((IWM_FRAME_LIMIT << IWM_SCD_QUEUE_CTX_REG2_WIN_SIZE_POS) &
IWM_SCD_QUEUE_CTX_REG2_WIN_SIZE_MSK) |
((IWM_FRAME_LIMIT
<< IWM_SCD_QUEUE_CTX_REG2_FRAME_LIMIT_POS) &
IWM_SCD_QUEUE_CTX_REG2_FRAME_LIMIT_MSK));
iwm_write_prph(sc, IWM_SCD_QUEUE_STATUS_BITS(qid),
(1 << IWM_SCD_QUEUE_STTS_REG_POS_ACTIVE) |
(fifo << IWM_SCD_QUEUE_STTS_REG_POS_TXF) |
(1 << IWM_SCD_QUEUE_STTS_REG_POS_WSL) |
IWM_SCD_QUEUE_STTS_REG_MSK);
if (qid == sc->cmdqid) {
iwm_write_prph(sc, IWM_SCD_EN_CTRL,
iwm_read_prph(sc, IWM_SCD_EN_CTRL) | (1 << qid));
}
return 0;
}
static int
iwm_enable_txq(struct iwm_softc *sc, int sta_id, int qid, int fifo,
int aggregate, uint8_t tid, uint16_t ssn)
{
struct iwm_tx_ring *ring = &sc->txq[qid];
struct iwm_scd_txq_cfg_cmd cmd;
int err, idx, scd_bug;
#ifdef notyet
scd_bug = !sc->sc_mqrx_supported &&
!((ssn - ring->cur) & 0x3f) &&
(ssn != ring->cur);
#else
scd_bug = !((ssn - ring->cur) & 0x3f) &&
(ssn != ring->cur);
#endif
if (scd_bug)
ssn = (ssn + 1) & 0xfff;
idx = IWM_AGG_SSN_TO_TXQ_IDX(ssn);
IWM_WRITE(sc, IWM_HBUS_TARG_WRPTR, qid << 8 | idx);
ring->cur = idx;
memset(&cmd, 0, sizeof(cmd));
cmd.tid = tid;
cmd.scd_queue = qid;
cmd.enable = 1;
cmd.sta_id = sta_id;
cmd.tx_fifo = fifo;
cmd.aggregate = aggregate;
cmd.ssn = htole16(ssn);
cmd.window = IWM_FRAME_LIMIT;
err = iwm_send_cmd_pdu(sc, IWM_SCD_QUEUE_CFG, 0,
sizeof(cmd), &cmd);
if (err)
return err;
return 0;
}
static int
iwm_post_alive(struct iwm_softc *sc)
{
int nwords;
int err, chnl;
uint32_t base;
if (!iwm_nic_lock(sc))
return EBUSY;
base = iwm_read_prph(sc, IWM_SCD_SRAM_BASE_ADDR);
if (sc->sched_base != base) {
DPRINTF(("%s: sched addr mismatch: 0x%08x != 0x%08x\n",
DEVNAME(sc), sc->sched_base, base));
sc->sched_base = base;
}
iwm_ict_reset(sc);
iwm_nic_unlock(sc);
nwords = (IWM_SCD_TRANS_TBL_MEM_UPPER_BOUND -
IWM_SCD_CONTEXT_MEM_LOWER_BOUND)
/ sizeof(uint32_t);
err = iwm_write_mem(sc,
sc->sched_base + IWM_SCD_CONTEXT_MEM_LOWER_BOUND,
NULL, nwords);
if (err)
return err;
if (!iwm_nic_lock(sc))
return EBUSY;
iwm_write_prph(sc, IWM_SCD_DRAM_BASE_ADDR, sc->sched_dma.paddr >> 10);
iwm_write_prph(sc, IWM_SCD_CHAINEXT_EN, 0);
err = iwm_enable_ac_txq(sc, sc->cmdqid, IWM_TX_FIFO_CMD);
if (err) {
iwm_nic_unlock(sc);
return err;
}
iwm_write_prph(sc, IWM_SCD_TXFACT, 0xff);
for (chnl = 0; chnl < IWM_FH_TCSR_CHNL_NUM; chnl++) {
IWM_WRITE(sc, IWM_FH_TCSR_CHNL_TX_CONFIG_REG(chnl),
IWM_FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_ENABLE |
IWM_FH_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_ENABLE);
}
IWM_SETBITS(sc, IWM_FH_TX_CHICKEN_BITS_REG,
IWM_FH_TX_CHICKEN_BITS_SCD_AUTO_RETRY_EN);
iwm_nic_unlock(sc);
if (sc->sc_device_family < IWM_DEVICE_FAMILY_8000) {
err = iwm_clear_bits_prph(sc, IWM_APMG_PCIDEV_STT_REG,
IWM_APMG_PCIDEV_STT_VAL_L1_ACT_DIS);
}
return err;
}
static struct iwm_phy_db_entry *
iwm_phy_db_get_section(struct iwm_softc *sc, enum iwm_phy_db_section_type type,
uint16_t chg_id)
{
struct iwm_phy_db *phy_db = &sc->sc_phy_db;
if (type >= IWM_PHY_DB_MAX)
return NULL;
switch (type) {
case IWM_PHY_DB_CFG:
return &phy_db->cfg;
case IWM_PHY_DB_CALIB_NCH:
return &phy_db->calib_nch;
case IWM_PHY_DB_CALIB_CHG_PAPD:
if (chg_id >= IWM_NUM_PAPD_CH_GROUPS)
return NULL;
return &phy_db->calib_ch_group_papd[chg_id];
case IWM_PHY_DB_CALIB_CHG_TXP:
if (chg_id >= IWM_NUM_TXP_CH_GROUPS)
return NULL;
return &phy_db->calib_ch_group_txp[chg_id];
default:
return NULL;
}
return NULL;
}
static int
iwm_phy_db_set_section(struct iwm_softc *sc,
struct iwm_calib_res_notif_phy_db *phy_db_notif, uint16_t size)
{
struct iwm_phy_db_entry *entry;
enum iwm_phy_db_section_type type = le16toh(phy_db_notif->type);
uint16_t chg_id = 0;
if (type == IWM_PHY_DB_CALIB_CHG_PAPD ||
type == IWM_PHY_DB_CALIB_CHG_TXP)
chg_id = le16toh(*(uint16_t *)phy_db_notif->data);
entry = iwm_phy_db_get_section(sc, type, chg_id);
if (!entry)
return EINVAL;
if (entry->data)
kmem_intr_free(entry->data, entry->size);
entry->data = kmem_intr_alloc(size, KM_NOSLEEP);
if (!entry->data) {
entry->size = 0;
return ENOMEM;
}
memcpy(entry->data, phy_db_notif->data, size);
entry->size = size;
DPRINTFN(10, ("%s(%d): [PHYDB]SET: Type %d, Size: %d, data: %p\n",
__func__, __LINE__, type, size, entry->data));
return 0;
}
static int
iwm_is_valid_channel(uint16_t ch_id)
{
if (ch_id <= 14 ||
(36 <= ch_id && ch_id <= 64 && ch_id % 4 == 0) ||
(100 <= ch_id && ch_id <= 140 && ch_id % 4 == 0) ||
(145 <= ch_id && ch_id <= 165 && ch_id % 4 == 1))
return 1;
return 0;
}
static uint8_t
iwm_ch_id_to_ch_index(uint16_t ch_id)
{
if (!iwm_is_valid_channel(ch_id))
return 0xff;
if (ch_id <= 14)
return ch_id - 1;
if (ch_id <= 64)
return (ch_id + 20) / 4;
if (ch_id <= 140)
return (ch_id - 12) / 4;
return (ch_id - 13) / 4;
}
static uint16_t
iwm_channel_id_to_papd(uint16_t ch_id)
{
if (!iwm_is_valid_channel(ch_id))
return 0xff;
if (1 <= ch_id && ch_id <= 14)
return 0;
if (36 <= ch_id && ch_id <= 64)
return 1;
if (100 <= ch_id && ch_id <= 140)
return 2;
return 3;
}
static uint16_t
iwm_channel_id_to_txp(struct iwm_softc *sc, uint16_t ch_id)
{
struct iwm_phy_db *phy_db = &sc->sc_phy_db;
struct iwm_phy_db_chg_txp *txp_chg;
int i;
uint8_t ch_index = iwm_ch_id_to_ch_index(ch_id);
if (ch_index == 0xff)
return 0xff;
for (i = 0; i < IWM_NUM_TXP_CH_GROUPS; i++) {
txp_chg = (void *)phy_db->calib_ch_group_txp[i].data;
if (!txp_chg)
return 0xff;
if (le16toh(txp_chg->max_channel_idx) >= ch_index)
return i;
}
return 0xff;
}
static int
iwm_phy_db_get_section_data(struct iwm_softc *sc, uint32_t type, uint8_t **data,
uint16_t *size, uint16_t ch_id)
{
struct iwm_phy_db_entry *entry;
uint16_t ch_group_id = 0;
if (type == IWM_PHY_DB_CALIB_CHG_PAPD)
ch_group_id = iwm_channel_id_to_papd(ch_id);
else if (type == IWM_PHY_DB_CALIB_CHG_TXP)
ch_group_id = iwm_channel_id_to_txp(sc, ch_id);
entry = iwm_phy_db_get_section(sc, type, ch_group_id);
if (!entry)
return EINVAL;
*data = entry->data;
*size = entry->size;
DPRINTFN(10, ("%s(%d): [PHYDB] GET: Type %d , Size: %d\n",
__func__, __LINE__, type, *size));
return 0;
}
static int
iwm_send_phy_db_cmd(struct iwm_softc *sc, uint16_t type, uint16_t length,
void *data)
{
struct iwm_phy_db_cmd phy_db_cmd;
struct iwm_host_cmd cmd = {
.id = IWM_PHY_DB_CMD,
.flags = IWM_CMD_ASYNC,
};
DPRINTFN(10, ("Sending PHY-DB hcmd of type %d, of length %d\n",
type, length));
phy_db_cmd.type = le16toh(type);
phy_db_cmd.length = le16toh(length);
cmd.data[0] = &phy_db_cmd;
cmd.len[0] = sizeof(struct iwm_phy_db_cmd);
cmd.data[1] = data;
cmd.len[1] = length;
return iwm_send_cmd(sc, &cmd);
}
static int
iwm_phy_db_send_all_channel_groups(struct iwm_softc *sc,
enum iwm_phy_db_section_type type, uint8_t max_ch_groups)
{
uint16_t i;
int err;
struct iwm_phy_db_entry *entry;
for (i = 0; i < max_ch_groups; i++) {
entry = iwm_phy_db_get_section(sc, type, i);
if (!entry)
return EINVAL;
if (!entry->size)
continue;
err = iwm_send_phy_db_cmd(sc, type, entry->size, entry->data);
if (err) {
DPRINTF(("%s: Can't SEND phy_db section %d (%d), "
"err %d\n", DEVNAME(sc), type, i, err));
return err;
}
DPRINTFN(10, ("%s: Sent PHY_DB HCMD, type = %d num = %d\n",
DEVNAME(sc), type, i));
DELAY(1000);
}
return 0;
}
static int
iwm_send_phy_db_data(struct iwm_softc *sc)
{
uint8_t *data = NULL;
uint16_t size = 0;
int err;
err = iwm_phy_db_get_section_data(sc, IWM_PHY_DB_CFG, &data, &size, 0);
if (err)
return err;
err = iwm_send_phy_db_cmd(sc, IWM_PHY_DB_CFG, size, data);
if (err)
return err;
err = iwm_phy_db_get_section_data(sc, IWM_PHY_DB_CALIB_NCH,
&data, &size, 0);
if (err)
return err;
err = iwm_send_phy_db_cmd(sc, IWM_PHY_DB_CALIB_NCH, size, data);
if (err)
return err;
err = iwm_phy_db_send_all_channel_groups(sc,
IWM_PHY_DB_CALIB_CHG_PAPD, IWM_NUM_PAPD_CH_GROUPS);
if (err)
return err;
err = iwm_phy_db_send_all_channel_groups(sc,
IWM_PHY_DB_CALIB_CHG_TXP, IWM_NUM_TXP_CH_GROUPS);
if (err)
return err;
return 0;
}
static int
iwm_send_time_event_cmd(struct iwm_softc *sc,
const struct iwm_time_event_cmd *cmd)
{
struct iwm_rx_packet *pkt;
struct iwm_time_event_resp *resp;
struct iwm_host_cmd hcmd = {
.id = IWM_TIME_EVENT_CMD,
.flags = IWM_CMD_WANT_RESP,
.resp_pkt_len = sizeof(*pkt) + sizeof(*resp),
};
uint32_t resp_len;
int err;
hcmd.data[0] = cmd;
hcmd.len[0] = sizeof(*cmd);
err = iwm_send_cmd(sc, &hcmd);
if (err)
return err;
pkt = hcmd.resp_pkt;
if (!pkt || (pkt->hdr.flags & IWM_CMD_FAILED_MSK)) {
err = EIO;
goto out;
}
resp_len = iwm_rx_packet_payload_len(pkt);
if (resp_len != sizeof(*resp)) {
err = EIO;
goto out;
}
resp = (void *)pkt->data;
if (le32toh(resp->status) == 0)
sc->sc_time_event_uid = le32toh(resp->unique_id);
else
err = EIO;
out:
iwm_free_resp(sc, &hcmd);
return err;
}
static void
iwm_protect_session(struct iwm_softc *sc, struct iwm_node *in,
uint32_t duration, uint32_t max_delay)
{
struct iwm_time_event_cmd time_cmd;
if (ISSET(sc->sc_flags, IWM_FLAG_TE_ACTIVE))
return;
memset(&time_cmd, 0, sizeof(time_cmd));
time_cmd.action = htole32(IWM_FW_CTXT_ACTION_ADD);
time_cmd.id_and_color =
htole32(IWM_FW_CMD_ID_AND_COLOR(in->in_id, in->in_color));
time_cmd.id = htole32(IWM_TE_BSS_STA_AGGRESSIVE_ASSOC);
time_cmd.apply_time = htole32(0);
time_cmd.max_frags = IWM_TE_V2_FRAG_NONE;
time_cmd.max_delay = htole32(max_delay);
time_cmd.interval = htole32(1);
time_cmd.duration = htole32(duration);
time_cmd.repeat = 1;
time_cmd.policy
= htole16(IWM_TE_V2_NOTIF_HOST_EVENT_START |
IWM_TE_V2_NOTIF_HOST_EVENT_END |
IWM_TE_V2_START_IMMEDIATELY);
if (iwm_send_time_event_cmd(sc, &time_cmd) == 0)
SET(sc->sc_flags, IWM_FLAG_TE_ACTIVE);
DELAY(100);
}
static void
iwm_unprotect_session(struct iwm_softc *sc, struct iwm_node *in)
{
struct iwm_time_event_cmd time_cmd;
if ((sc->sc_flags & IWM_FLAG_TE_ACTIVE) == 0)
return;
memset(&time_cmd, 0, sizeof(time_cmd));
time_cmd.action = htole32(IWM_FW_CTXT_ACTION_REMOVE);
time_cmd.id_and_color =
htole32(IWM_FW_CMD_ID_AND_COLOR(in->in_id, in->in_color));
time_cmd.id = htole32(sc->sc_time_event_uid);
if (iwm_send_time_event_cmd(sc, &time_cmd) == 0)
CLR(sc->sc_flags, IWM_FLAG_TE_ACTIVE);
DELAY(100);
}
static const int iwm_nvm_to_read[] = {
IWM_NVM_SECTION_TYPE_HW,
IWM_NVM_SECTION_TYPE_SW,
IWM_NVM_SECTION_TYPE_REGULATORY,
IWM_NVM_SECTION_TYPE_CALIBRATION,
IWM_NVM_SECTION_TYPE_PRODUCTION,
IWM_NVM_SECTION_TYPE_REGULATORY_SDP,
IWM_NVM_SECTION_TYPE_HW_8000,
IWM_NVM_SECTION_TYPE_MAC_OVERRIDE,
IWM_NVM_SECTION_TYPE_PHY_SKU,
};
#define IWM_NVM_DEFAULT_CHUNK_SIZE (2*1024)
#define IWM_MAX_NVM_SECTION_SIZE_7000 (16 * 512 * sizeof(uint16_t))
#define IWM_MAX_NVM_SECTION_SIZE_8000 (32 * 512 * sizeof(uint16_t))
#define IWM_NVM_WRITE_OPCODE 1
#define IWM_NVM_READ_OPCODE 0
static int
iwm_nvm_read_chunk(struct iwm_softc *sc, uint16_t section, uint16_t offset,
uint16_t length, uint8_t *data, uint16_t *len)
{
offset = 0;
struct iwm_nvm_access_cmd nvm_access_cmd = {
.offset = htole16(offset),
.length = htole16(length),
.type = htole16(section),
.op_code = IWM_NVM_READ_OPCODE,
};
struct iwm_nvm_access_resp *nvm_resp;
struct iwm_rx_packet *pkt;
struct iwm_host_cmd cmd = {
.id = IWM_NVM_ACCESS_CMD,
.flags = (IWM_CMD_WANT_RESP | IWM_CMD_SEND_IN_RFKILL),
.resp_pkt_len = IWM_CMD_RESP_MAX,
.data = { &nvm_access_cmd, },
};
int err, offset_read;
size_t bytes_read;
uint8_t *resp_data;
cmd.len[0] = sizeof(struct iwm_nvm_access_cmd);
err = iwm_send_cmd(sc, &cmd);
if (err) {
DPRINTF(("%s: Could not send NVM_ACCESS command (error=%d)\n",
DEVNAME(sc), err));
return err;
}
pkt = cmd.resp_pkt;
if (pkt->hdr.flags & IWM_CMD_FAILED_MSK) {
err = EIO;
goto exit;
}
nvm_resp = (void *)pkt->data;
if (nvm_resp == NULL) {
err = EIO;
goto exit;
}
err = le16toh(nvm_resp->status);
bytes_read = le16toh(nvm_resp->length);
offset_read = le16toh(nvm_resp->offset);
resp_data = nvm_resp->data;
if (err) {
err = EINVAL;
goto exit;
}
if (offset_read != offset) {
err = EINVAL;
goto exit;
}
if (bytes_read > length) {
err = EINVAL;
goto exit;
}
memcpy(data + offset, resp_data, bytes_read);
*len = bytes_read;
exit:
iwm_free_resp(sc, &cmd);
return err;
}
static int
iwm_nvm_read_section(struct iwm_softc *sc, uint16_t section, uint8_t *data,
uint16_t *len, size_t max_len)
{
uint16_t chunklen, seglen;
int err;
chunklen = seglen = IWM_NVM_DEFAULT_CHUNK_SIZE;
*len = 0;
while (seglen == chunklen && *len < max_len) {
err = iwm_nvm_read_chunk(sc, section, *len, chunklen, data,
&seglen);
if (err) {
DPRINTF(("%s: Cannot read NVM from section %d "
"offset %d, length %d\n",
DEVNAME(sc), section, *len, chunklen));
return err;
}
*len += seglen;
}
DPRINTFN(4, ("NVM section %d read completed\n", section));
return 0;
}
static uint8_t
iwm_fw_valid_tx_ant(struct iwm_softc *sc)
{
uint8_t tx_ant;
tx_ant = ((sc->sc_fw_phy_config & IWM_FW_PHY_CFG_TX_CHAIN)
>> IWM_FW_PHY_CFG_TX_CHAIN_POS);
if (sc->sc_nvm.valid_tx_ant)
tx_ant &= sc->sc_nvm.valid_tx_ant;
return tx_ant;
}
static uint8_t
iwm_fw_valid_rx_ant(struct iwm_softc *sc)
{
uint8_t rx_ant;
rx_ant = ((sc->sc_fw_phy_config & IWM_FW_PHY_CFG_RX_CHAIN)
>> IWM_FW_PHY_CFG_RX_CHAIN_POS);
if (sc->sc_nvm.valid_rx_ant)
rx_ant &= sc->sc_nvm.valid_rx_ant;
return rx_ant;
}
static void
iwm_init_channel_map(struct iwm_softc *sc, const uint16_t * const nvm_ch_flags,
const uint8_t *nvm_channels, size_t nchan)
{
struct ieee80211com *ic = &sc->sc_ic;
struct iwm_nvm_data *data = &sc->sc_nvm;
int ch_idx;
struct ieee80211_channel *channel;
uint16_t ch_flags;
int is_5ghz;
int flags, hw_value;
for (ch_idx = 0; ch_idx < nchan; ch_idx++) {
ch_flags = le16_to_cpup(nvm_ch_flags + ch_idx);
aprint_debug_dev(sc->sc_dev,
"Ch. %d: %svalid %cibss %s %cradar %cdfs"
" %cwide %c40MHz %c80MHz %c160MHz\n",
nvm_channels[ch_idx],
ch_flags & IWM_NVM_CHANNEL_VALID ? "" : "in",
ch_flags & IWM_NVM_CHANNEL_IBSS ? '+' : '-',
ch_flags & IWM_NVM_CHANNEL_ACTIVE ? "active" : "passive",
ch_flags & IWM_NVM_CHANNEL_RADAR ? '+' : '-',
ch_flags & IWM_NVM_CHANNEL_DFS ? '+' : '-',
ch_flags & IWM_NVM_CHANNEL_WIDE ? '+' : '-',
ch_flags & IWM_NVM_CHANNEL_40MHZ ? '+' : '-',
ch_flags & IWM_NVM_CHANNEL_80MHZ ? '+' : '-',
ch_flags & IWM_NVM_CHANNEL_160MHZ ? '+' : '-');
if (ch_idx >= IWM_NUM_2GHZ_CHANNELS &&
!data->sku_cap_band_52GHz_enable)
ch_flags &= ~IWM_NVM_CHANNEL_VALID;
if (!(ch_flags & IWM_NVM_CHANNEL_VALID)) {
DPRINTF(("Ch. %d Flags %x [%sGHz] - No traffic\n",
nvm_channels[ch_idx], ch_flags,
(ch_idx >= IWM_NUM_2GHZ_CHANNELS) ? "5" : "2.4"));
continue;
}
hw_value = nvm_channels[ch_idx];
channel = &ic->ic_channels[hw_value];
is_5ghz = ch_idx >= IWM_NUM_2GHZ_CHANNELS;
if (!is_5ghz) {
flags = IEEE80211_CHAN_2GHZ;
channel->ic_flags
= IEEE80211_CHAN_CCK
| IEEE80211_CHAN_OFDM
| IEEE80211_CHAN_DYN
| IEEE80211_CHAN_2GHZ;
} else {
flags = IEEE80211_CHAN_5GHZ;
channel->ic_flags =
IEEE80211_CHAN_A;
}
channel->ic_freq = ieee80211_ieee2mhz(hw_value, flags);
if (!(ch_flags & IWM_NVM_CHANNEL_ACTIVE))
channel->ic_flags |= IEEE80211_CHAN_PASSIVE;
#ifndef IEEE80211_NO_HT
if (data->sku_cap_11n_enable)
channel->ic_flags |= IEEE80211_CHAN_HT;
#endif
}
}
static int
iwm_mimo_enabled(struct iwm_softc *sc)
{
#ifndef IEEE80211_NO_HT
struct ieee80211com *ic = &sc->sc_ic;
return !sc->sc_nvm.sku_cap_mimo_disable &&
(ic->ic_userflags & IEEE80211_F_NOMIMO) == 0;
#else
return 0;
#endif
}
#ifndef IEEE80211_NO_HT
static void
iwm_setup_ht_rates(struct iwm_softc *sc)
{
struct ieee80211com *ic = &sc->sc_ic;
ic->ic_tx_mcs_set = IEEE80211_TX_MCS_SET_DEFINED;
ic->ic_sup_mcs[0] = 0xff;
#ifdef notyet
if (sc->sc_nvm.sku_cap_mimo_disable)
return;
if (iwm_fw_valid_rx_ant(sc) > 1)
ic->ic_sup_mcs[1] = 0xff;
if (iwm_fw_valid_rx_ant(sc) > 2)
ic->ic_sup_mcs[2] = 0xff;
#endif
}
#define IWM_MAX_RX_BA_SESSIONS 16
static void
iwm_sta_rx_agg(struct iwm_softc *sc, struct ieee80211_node *ni, uint8_t tid,
uint16_t ssn, int start)
{
struct ieee80211com *ic = &sc->sc_ic;
struct iwm_add_sta_cmd cmd;
struct iwm_node *in = (struct iwm_node *)ni;
int err, s;
uint32_t status;
size_t cmdsize;
s = splnet();
if (start && sc->sc_rx_ba_sessions >= IWM_MAX_RX_BA_SESSIONS) {
ieee80211_addba_req_refuse(ic, ni, tid);
splx(s);
return;
}
memset(&cmd, 0, sizeof(cmd));
cmd.sta_id = IWM_STATION_ID;
cmd.mac_id_n_color
= htole32(IWM_FW_CMD_ID_AND_COLOR(in->in_id, in->in_color));
cmd.add_modify = IWM_STA_MODE_MODIFY;
if (start) {
cmd.add_immediate_ba_tid = (uint8_t)tid;
cmd.add_immediate_ba_ssn = ssn;
} else {
cmd.remove_immediate_ba_tid = (uint8_t)tid;
}
cmd.modify_mask = start ? IWM_STA_MODIFY_ADD_BA_TID :
IWM_STA_MODIFY_REMOVE_BA_TID;
status = IWM_ADD_STA_SUCCESS;
if (isset(sc->sc_ucode_api, IWM_UCODE_TLV_API_STA_TYPE))
cmdsize = sizeof(cmd);
else
cmdsize = sizeof(struct iwm_add_sta_cmd_v7);
err = iwm_send_cmd_pdu_status(sc, IWM_ADD_STA, cmdsize, &cmd,
&status);
if (!err && (status & IWM_ADD_STA_STATUS_MASK) != IWM_ADD_STA_SUCCESS)
err = EIO;
if (err) {
if (start)
ieee80211_addba_req_refuse(ic, ni, tid);
splx(s);
return err;
}
if (start) {
sc->sc_rx_ba_sessions++;
ieee80211_addba_req_accept(ic, ni, tid);
} else if (sc->sc_rx_ba_sessions > 0)
sc->sc_rx_ba_sessions--;
splx(s);
}
static void
iwm_htprot_task(void *arg)
{
struct iwm_softc *sc = arg;
struct ieee80211com *ic = &sc->sc_ic;
struct iwm_node *in = (struct iwm_node *)ic->ic_bss;
int err;
err = iwm_mac_ctxt_cmd(sc, in, IWM_FW_CTXT_ACTION_MODIFY, 1);
if (err)
aprint_error_dev(sc->sc_dev,
"could not change HT protection: error %d\n", err);
}
static void
iwm_update_htprot(struct ieee80211com *ic, struct ieee80211_node *ni)
{
struct iwm_softc *sc = ic->ic_softc;
task_add(systq, &sc->htprot_task);
}
static void
iwm_ba_task(void *arg)
{
struct iwm_softc *sc = arg;
struct ieee80211com *ic = &sc->sc_ic;
struct ieee80211_node *ni = ic->ic_bss;
if (sc->ba_start)
iwm_sta_rx_agg(sc, ni, sc->ba_tid, sc->ba_ssn, 1);
else
iwm_sta_rx_agg(sc, ni, sc->ba_tid, 0, 0);
}
static int
iwm_ampdu_rx_start(struct ieee80211com *ic, struct ieee80211_node *ni,
uint8_t tid)
{
struct ieee80211_rx_ba *ba = &ni->ni_rx_ba[tid];
struct iwm_softc *sc = IC2IFP(ic)->if_softc;
if (sc->sc_rx_ba_sessions >= IWM_MAX_RX_BA_SESSIONS)
return ENOSPC;
sc->ba_start = 1;
sc->ba_tid = tid;
sc->ba_ssn = htole16(ba->ba_winstart);
task_add(systq, &sc->ba_task);
return EBUSY;
}
static void
iwm_ampdu_rx_stop(struct ieee80211com *ic, struct ieee80211_node *ni,
uint8_t tid)
{
struct iwm_softc *sc = IC2IFP(ic)->if_softc;
sc->ba_start = 0;
sc->ba_tid = tid;
task_add(systq, &sc->ba_task);
}
#endif
static void
iwm_free_fw_paging(struct iwm_softc *sc)
{
int i;
if (sc->fw_paging_db[0].fw_paging_block.vaddr == NULL)
return;
for (i = 0; i < IWM_NUM_OF_FW_PAGING_BLOCKS; i++) {
iwm_dma_contig_free(&sc->fw_paging_db[i].fw_paging_block);
}
memset(sc->fw_paging_db, 0, sizeof(sc->fw_paging_db));
}
static int
iwm_fill_paging_mem(struct iwm_softc *sc, const struct iwm_fw_sects *fws)
{
int sec_idx, idx;
uint32_t offset = 0;
for (sec_idx = 0; sec_idx < IWM_UCODE_SECT_MAX; sec_idx++) {
if (fws->fw_sect[sec_idx].fws_devoff ==
IWM_PAGING_SEPARATOR_SECTION) {
sec_idx++;
break;
}
}
if (sec_idx >= __arraycount(fws->fw_sect) - 1) {
aprint_error_dev(sc->sc_dev,
"Paging: Missing CSS and/or paging sections\n");
iwm_free_fw_paging(sc);
return EINVAL;
}
DPRINTF(("%s: Paging: load paging CSS to FW, sec = %d\n", DEVNAME(sc),
sec_idx));
memcpy(sc->fw_paging_db[0].fw_paging_block.vaddr,
fws->fw_sect[sec_idx].fws_data, sc->fw_paging_db[0].fw_paging_size);
DPRINTF(("%s: Paging: copied %d CSS bytes to first block\n",
DEVNAME(sc), sc->fw_paging_db[0].fw_paging_size));
sec_idx++;
for (idx = 1; idx < sc->num_of_paging_blk; idx++) {
memcpy(sc->fw_paging_db[idx].fw_paging_block.vaddr,
(const char *)fws->fw_sect[sec_idx].fws_data + offset,
sc->fw_paging_db[idx].fw_paging_size);
DPRINTF(("%s: Paging: copied %d paging bytes to block %d\n",
DEVNAME(sc), sc->fw_paging_db[idx].fw_paging_size, idx));
offset += sc->fw_paging_db[idx].fw_paging_size;
}
if (sc->num_of_pages_in_last_blk > 0) {
memcpy(sc->fw_paging_db[idx].fw_paging_block.vaddr,
(const char *)fws->fw_sect[sec_idx].fws_data + offset,
IWM_FW_PAGING_SIZE * sc->num_of_pages_in_last_blk);
DPRINTF(("%s: Paging: copied %d pages in the last block %d\n",
DEVNAME(sc), sc->num_of_pages_in_last_blk, idx));
}
return 0;
}
static int
iwm_alloc_fw_paging_mem(struct iwm_softc *sc, const struct iwm_fw_sects *fws)
{
int blk_idx = 0;
int error, num_of_pages;
bus_dmamap_t dmap;
if (sc->fw_paging_db[0].fw_paging_block.vaddr != NULL) {
int i;
bus_dmamap_sync(sc->sc_dmat,
sc->fw_paging_db[0].fw_paging_block.map,
0, IWM_FW_PAGING_SIZE,
BUS_DMASYNC_POSTWRITE | BUS_DMASYNC_POSTREAD);
for (i = 0; i < sc->num_of_paging_blk + 1; i++) {
dmap = sc->fw_paging_db[i].fw_paging_block.map;
bus_dmamap_sync(sc->sc_dmat, dmap, 0, dmap->dm_mapsize,
BUS_DMASYNC_POSTWRITE | BUS_DMASYNC_POSTREAD);
}
return 0;
}
CTASSERT(__BIT(IWM_BLOCK_2_EXP_SIZE) == IWM_PAGING_BLOCK_SIZE);
num_of_pages = fws->paging_mem_size / IWM_FW_PAGING_SIZE;
sc->num_of_paging_blk =
((num_of_pages - 1) / IWM_NUM_OF_PAGE_PER_GROUP) + 1;
sc->num_of_pages_in_last_blk =
num_of_pages -
IWM_NUM_OF_PAGE_PER_GROUP * (sc->num_of_paging_blk - 1);
DPRINTF(("%s: Paging: allocating mem for %d paging blocks, "
"each block holds 8 pages, last block holds %d pages\n",
DEVNAME(sc), sc->num_of_paging_blk, sc->num_of_pages_in_last_blk));
error = iwm_dma_contig_alloc(sc->sc_dmat,
&sc->fw_paging_db[blk_idx].fw_paging_block, IWM_FW_PAGING_SIZE,
4096);
if (error) {
iwm_free_fw_paging(sc);
return ENOMEM;
}
sc->fw_paging_db[blk_idx].fw_paging_size = IWM_FW_PAGING_SIZE;
DPRINTF(("%s: Paging: allocated 4K(CSS) bytes for firmware paging.\n",
DEVNAME(sc)));
for (blk_idx = 1; blk_idx < sc->num_of_paging_blk + 1; blk_idx++) {
error = iwm_dma_contig_alloc(sc->sc_dmat,
&sc->fw_paging_db[blk_idx].fw_paging_block,
IWM_PAGING_BLOCK_SIZE, 4096);
if (error) {
iwm_free_fw_paging(sc);
return ENOMEM;
}
sc->fw_paging_db[blk_idx].fw_paging_size =
IWM_PAGING_BLOCK_SIZE;
DPRINTF(("%s: Paging: allocated 32K bytes for firmware "
"paging.\n", DEVNAME(sc)));
}
return 0;
}
static int
iwm_save_fw_paging(struct iwm_softc *sc, const struct iwm_fw_sects *fws)
{
int err;
err = iwm_alloc_fw_paging_mem(sc, fws);
if (err)
return err;
return iwm_fill_paging_mem(sc, fws);
}
static bool
iwm_has_new_tx_api(struct iwm_softc *sc)
{
return false;
}
static int
iwm_send_paging_cmd(struct iwm_softc *sc, const struct iwm_fw_sects *fws)
{
struct iwm_fw_paging_cmd fw_paging_cmd = {
.flags = htole32(IWM_PAGING_CMD_IS_SECURED |
IWM_PAGING_CMD_IS_ENABLED |
(sc->num_of_pages_in_last_blk <<
IWM_PAGING_CMD_NUM_OF_PAGES_IN_LAST_GRP_POS)),
.block_size = htole32(IWM_BLOCK_2_EXP_SIZE),
.block_num = htole32(sc->num_of_paging_blk),
};
size_t size = sizeof(fw_paging_cmd);
int blk_idx;
bus_dmamap_t dmap;
if (!iwm_has_new_tx_api(sc))
size -= (sizeof(uint64_t) - sizeof(uint32_t)) *
IWM_NUM_OF_FW_PAGING_BLOCKS;
for (blk_idx = 0; blk_idx < sc->num_of_paging_blk + 1; blk_idx++) {
bus_addr_t dev_phy_addr =
sc->fw_paging_db[blk_idx].fw_paging_block.paddr;
if (iwm_has_new_tx_api(sc)) {
fw_paging_cmd.device_phy_addr.addr64[blk_idx] =
htole64(dev_phy_addr);
} else {
dev_phy_addr = dev_phy_addr >> IWM_PAGE_2_EXP_SIZE;
fw_paging_cmd.device_phy_addr.addr32[blk_idx] =
htole32(dev_phy_addr);
}
dmap = sc->fw_paging_db[blk_idx].fw_paging_block.map;
bus_dmamap_sync(sc->sc_dmat, dmap, 0,
blk_idx == 0 ? IWM_FW_PAGING_SIZE : IWM_PAGING_BLOCK_SIZE,
BUS_DMASYNC_PREWRITE | BUS_DMASYNC_PREREAD);
}
return iwm_send_cmd_pdu(sc,
iwm_cmd_id(IWM_FW_PAGING_BLOCK_CMD, IWM_LONG_GROUP, 0),
0, size, &fw_paging_cmd);
}
static void
iwm_set_hw_address_8000(struct iwm_softc *sc, struct iwm_nvm_data *data,
const uint16_t *mac_override, const uint16_t *nvm_hw)
{
static const uint8_t reserved_mac[ETHER_ADDR_LEN] = {
0x02, 0xcc, 0xaa, 0xff, 0xee, 0x00
};
const uint8_t *hw_addr;
if (mac_override) {
hw_addr = (const uint8_t *)(mac_override +
IWM_MAC_ADDRESS_OVERRIDE_8000);
memcpy(data->hw_addr, hw_addr, ETHER_ADDR_LEN);
if (memcmp(reserved_mac, hw_addr, ETHER_ADDR_LEN) != 0 &&
(memcmp(etherbroadcastaddr, data->hw_addr,
sizeof(etherbroadcastaddr)) != 0) &&
(memcmp(etheranyaddr, data->hw_addr,
sizeof(etheranyaddr)) != 0) &&
!ETHER_IS_MULTICAST(data->hw_addr))
return;
}
if (nvm_hw) {
uint32_t mac_addr0, mac_addr1;
if (!iwm_nic_lock(sc))
goto out;
mac_addr0 = htole32(iwm_read_prph(sc, IWM_WFMP_MAC_ADDR_0));
mac_addr1 = htole32(iwm_read_prph(sc, IWM_WFMP_MAC_ADDR_1));
iwm_nic_unlock(sc);
hw_addr = (const uint8_t *)&mac_addr0;
data->hw_addr[0] = hw_addr[3];
data->hw_addr[1] = hw_addr[2];
data->hw_addr[2] = hw_addr[1];
data->hw_addr[3] = hw_addr[0];
hw_addr = (const uint8_t *)&mac_addr1;
data->hw_addr[4] = hw_addr[1];
data->hw_addr[5] = hw_addr[0];
return;
}
out:
aprint_error_dev(sc->sc_dev, "mac address not found\n");
memset(data->hw_addr, 0, sizeof(data->hw_addr));
}
static int
iwm_parse_nvm_data(struct iwm_softc *sc, const uint16_t *nvm_hw,
const uint16_t *nvm_sw, const uint16_t *nvm_calib,
const uint16_t *mac_override, const uint16_t *phy_sku,
const uint16_t *regulatory, int n_regulatory)
{
struct iwm_nvm_data *data = &sc->sc_nvm;
uint8_t hw_addr[ETHER_ADDR_LEN];
uint32_t sku;
if (sc->sc_device_family == IWM_DEVICE_FAMILY_7000) {
uint16_t radio_cfg = le16_to_cpup(nvm_sw + IWM_RADIO_CFG);
data->radio_cfg_type = IWM_NVM_RF_CFG_TYPE_MSK(radio_cfg);
data->radio_cfg_step = IWM_NVM_RF_CFG_STEP_MSK(radio_cfg);
data->radio_cfg_dash = IWM_NVM_RF_CFG_DASH_MSK(radio_cfg);
data->radio_cfg_pnum = IWM_NVM_RF_CFG_PNUM_MSK(radio_cfg);
data->nvm_version = le16_to_cpup(nvm_sw + IWM_NVM_VERSION);
sku = le16_to_cpup(nvm_sw + IWM_SKU);
} else {
uint32_t radio_cfg = le32_to_cpup(phy_sku + IWM_RADIO_CFG_8000);
data->radio_cfg_type = IWM_NVM_RF_CFG_TYPE_MSK_8000(radio_cfg);
data->radio_cfg_step = IWM_NVM_RF_CFG_STEP_MSK_8000(radio_cfg);
data->radio_cfg_dash = IWM_NVM_RF_CFG_DASH_MSK_8000(radio_cfg);
data->radio_cfg_pnum = IWM_NVM_RF_CFG_PNUM_MSK_8000(radio_cfg);
data->valid_tx_ant = IWM_NVM_RF_CFG_TX_ANT_MSK_8000(radio_cfg);
data->valid_rx_ant = IWM_NVM_RF_CFG_RX_ANT_MSK_8000(radio_cfg);
data->nvm_version = le16_to_cpup(nvm_sw + IWM_NVM_VERSION_8000);
sku = le32_to_cpup(phy_sku + IWM_SKU_8000);
}
data->sku_cap_band_24GHz_enable = sku & IWM_NVM_SKU_CAP_BAND_24GHZ;
data->sku_cap_band_52GHz_enable = sku & IWM_NVM_SKU_CAP_BAND_52GHZ;
data->sku_cap_11n_enable = sku & IWM_NVM_SKU_CAP_11N_ENABLE;
data->sku_cap_11ac_enable = sku & IWM_NVM_SKU_CAP_11AC_ENABLE;
data->sku_cap_mimo_disable = sku & IWM_NVM_SKU_CAP_MIMO_DISABLE;
if (sc->sc_device_family >= IWM_DEVICE_FAMILY_8000) {
#if 0
uint16_t lar_offset = data->nvm_version < 0xE39 ?
IWM_NVM_LAR_OFFSET_8000_OLD :
IWM_NVM_LAR_OFFSET_8000;
uint16_t lar_config;
lar_config = le16_to_cpup(regulatory + lar_offset);
data->lar_enabled = !!(lar_config &
IWM_NVM_LAR_ENABLED_8000);
#else
data->lar_enabled = 0;
#endif
data->n_hw_addrs = le16_to_cpup(nvm_sw + IWM_N_HW_ADDRS_8000);
} else
data->n_hw_addrs = le16_to_cpup(nvm_sw + IWM_N_HW_ADDRS);
if (sc->sc_device_family == IWM_DEVICE_FAMILY_7000) {
memcpy(hw_addr, nvm_hw + IWM_HW_ADDR, ETHER_ADDR_LEN);
data->hw_addr[0] = hw_addr[1];
data->hw_addr[1] = hw_addr[0];
data->hw_addr[2] = hw_addr[3];
data->hw_addr[3] = hw_addr[2];
data->hw_addr[4] = hw_addr[5];
data->hw_addr[5] = hw_addr[4];
} else
iwm_set_hw_address_8000(sc, data, mac_override, nvm_hw);
if (sc->sc_device_family == IWM_DEVICE_FAMILY_7000) {
if (sc->nvm_type == IWM_NVM_SDP) {
iwm_init_channel_map(sc, regulatory, iwm_nvm_channels,
MIN(n_regulatory, __arraycount(iwm_nvm_channels)));
} else {
iwm_init_channel_map(sc, &nvm_sw[IWM_NVM_CHANNELS],
iwm_nvm_channels, __arraycount(iwm_nvm_channels));
}
} else
iwm_init_channel_map(sc, ®ulatory[IWM_NVM_CHANNELS_8000],
iwm_nvm_channels_8000,
MIN(n_regulatory, __arraycount(iwm_nvm_channels_8000)));
data->calib_version = 255;
return 0;
}
static int
iwm_parse_nvm_sections(struct iwm_softc *sc, struct iwm_nvm_section *sections)
{
const uint16_t *hw, *sw, *calib, *mac_override = NULL, *phy_sku = NULL;
const uint16_t *regulatory = NULL;
int n_regulatory = 0;
if (sc->sc_device_family == IWM_DEVICE_FAMILY_7000) {
if (!sections[IWM_NVM_SECTION_TYPE_SW].data ||
!sections[IWM_NVM_SECTION_TYPE_HW].data) {
return ENOENT;
}
hw = (const uint16_t *) sections[IWM_NVM_SECTION_TYPE_HW].data;
if (sc->nvm_type == IWM_NVM_SDP) {
if (!sections[IWM_NVM_SECTION_TYPE_REGULATORY_SDP].data)
return ENOENT;
regulatory = (const uint16_t *)
sections[IWM_NVM_SECTION_TYPE_REGULATORY_SDP].data;
n_regulatory =
sections[IWM_NVM_SECTION_TYPE_REGULATORY_SDP].length
;
}
} else if (sc->sc_device_family == IWM_DEVICE_FAMILY_8000) {
if (!sections[IWM_NVM_SECTION_TYPE_SW].data ||
!sections[IWM_NVM_SECTION_TYPE_REGULATORY].data) {
return ENOENT;
}
if (!sections[IWM_NVM_SECTION_TYPE_HW_8000].data &&
!sections[IWM_NVM_SECTION_TYPE_MAC_OVERRIDE].data) {
return ENOENT;
}
if (!sections[IWM_NVM_SECTION_TYPE_PHY_SKU].data) {
return ENOENT;
}
regulatory = (const uint16_t *)
sections[IWM_NVM_SECTION_TYPE_REGULATORY].data;
n_regulatory = sections[IWM_NVM_SECTION_TYPE_REGULATORY].length;
hw = (const uint16_t *)
sections[IWM_NVM_SECTION_TYPE_HW_8000].data;
mac_override =
(const uint16_t *)
sections[IWM_NVM_SECTION_TYPE_MAC_OVERRIDE].data;
phy_sku = (const uint16_t *)
sections[IWM_NVM_SECTION_TYPE_PHY_SKU].data;
} else {
panic("unknown device family %d\n", sc->sc_device_family);
}
sw = (const uint16_t *)sections[IWM_NVM_SECTION_TYPE_SW].data;
calib = (const uint16_t *)
sections[IWM_NVM_SECTION_TYPE_CALIBRATION].data;
return iwm_parse_nvm_data(sc, hw, sw, calib, mac_override,
phy_sku, regulatory, n_regulatory);
}
static int
iwm_nvm_init(struct iwm_softc *sc)
{
struct iwm_nvm_section nvm_sections[IWM_NVM_NUM_OF_SECTIONS];
int i, section, err;
uint16_t len;
uint8_t *buf;
const size_t bufsz = (sc->sc_device_family == IWM_DEVICE_FAMILY_8000) ?
IWM_MAX_NVM_SECTION_SIZE_8000 : IWM_MAX_NVM_SECTION_SIZE_7000;
DPRINTF(("Read NVM\n"));
memset(nvm_sections, 0, sizeof(nvm_sections));
buf = kmem_alloc(bufsz, KM_SLEEP);
for (i = 0; i < __arraycount(iwm_nvm_to_read); i++) {
section = iwm_nvm_to_read[i];
KASSERT(section < IWM_NVM_NUM_OF_SECTIONS);
err = iwm_nvm_read_section(sc, section, buf, &len, bufsz);
if (err) {
err = 0;
continue;
}
nvm_sections[section].data = kmem_alloc(len, KM_SLEEP);
memcpy(nvm_sections[section].data, buf, len);
nvm_sections[section].length = len;
}
kmem_free(buf, bufsz);
err = iwm_parse_nvm_sections(sc, nvm_sections);
for (i = 0; i < IWM_NVM_NUM_OF_SECTIONS; i++) {
if (nvm_sections[i].data != NULL)
kmem_free(nvm_sections[i].data, nvm_sections[i].length);
}
return err;
}
static int
iwm_firmware_load_sect(struct iwm_softc *sc, uint32_t dst_addr,
const uint8_t *section, uint32_t byte_cnt)
{
int err = EINVAL;
uint32_t chunk_sz, offset;
chunk_sz = MIN(IWM_FH_MEM_TB_MAX_LENGTH, byte_cnt);
for (offset = 0; offset < byte_cnt; offset += chunk_sz) {
uint32_t addr, len;
const uint8_t *data;
addr = dst_addr + offset;
len = MIN(chunk_sz, byte_cnt - offset);
data = section + offset;
err = iwm_firmware_load_chunk(sc, addr, data, len);
if (err)
break;
}
return err;
}
static int
iwm_firmware_load_chunk(struct iwm_softc *sc, uint32_t dst_addr,
const uint8_t *section, uint32_t byte_cnt)
{
struct iwm_dma_info *dma = &sc->fw_dma;
int err;
memcpy(dma->vaddr, section, byte_cnt);
bus_dmamap_sync(sc->sc_dmat, dma->map, 0, byte_cnt,
BUS_DMASYNC_PREWRITE);
if (dst_addr >= IWM_FW_MEM_EXTENDED_START &&
dst_addr <= IWM_FW_MEM_EXTENDED_END) {
err = iwm_set_bits_prph(sc, IWM_LMPM_CHICK,
IWM_LMPM_CHICK_EXTENDED_ADDR_SPACE);
if (err)
goto done;
}
sc->sc_fw_chunk_done = 0;
if (!iwm_nic_lock(sc)) {
err = EBUSY;
goto done;
}
IWM_WRITE(sc, IWM_FH_TCSR_CHNL_TX_CONFIG_REG(IWM_FH_SRVC_CHNL),
IWM_FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_PAUSE);
IWM_WRITE(sc, IWM_FH_SRVC_CHNL_SRAM_ADDR_REG(IWM_FH_SRVC_CHNL),
dst_addr);
IWM_WRITE(sc, IWM_FH_TFDIB_CTRL0_REG(IWM_FH_SRVC_CHNL),
dma->paddr & IWM_FH_MEM_TFDIB_DRAM_ADDR_LSB_MSK);
IWM_WRITE(sc, IWM_FH_TFDIB_CTRL1_REG(IWM_FH_SRVC_CHNL),
(iwm_get_dma_hi_addr(dma->paddr)
<< IWM_FH_MEM_TFDIB_REG1_ADDR_BITSHIFT) | byte_cnt);
IWM_WRITE(sc, IWM_FH_TCSR_CHNL_TX_BUF_STS_REG(IWM_FH_SRVC_CHNL),
1 << IWM_FH_TCSR_CHNL_TX_BUF_STS_REG_POS_TB_NUM |
1 << IWM_FH_TCSR_CHNL_TX_BUF_STS_REG_POS_TB_IDX |
IWM_FH_TCSR_CHNL_TX_BUF_STS_REG_VAL_TFDB_VALID);
IWM_WRITE(sc, IWM_FH_TCSR_CHNL_TX_CONFIG_REG(IWM_FH_SRVC_CHNL),
IWM_FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_ENABLE |
IWM_FH_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_DISABLE |
IWM_FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_HOST_ENDTFD);
iwm_nic_unlock(sc);
err = 0;
while (!sc->sc_fw_chunk_done) {
err = tsleep(&sc->sc_fw, 0, "iwmfw", mstohz(1000));
if (err)
break;
}
if (!sc->sc_fw_chunk_done) {
DPRINTF(("%s: fw chunk addr 0x%x len %d failed to load\n",
DEVNAME(sc), dst_addr, byte_cnt));
}
if (dst_addr >= IWM_FW_MEM_EXTENDED_START &&
dst_addr <= IWM_FW_MEM_EXTENDED_END) {
int err2 = iwm_clear_bits_prph(sc, IWM_LMPM_CHICK,
IWM_LMPM_CHICK_EXTENDED_ADDR_SPACE);
if (!err)
err = err2;
}
done:
bus_dmamap_sync(sc->sc_dmat, dma->map, 0, byte_cnt,
BUS_DMASYNC_POSTWRITE);
return err;
}
static int
iwm_load_firmware_7000(struct iwm_softc *sc, enum iwm_ucode_type ucode_type)
{
struct iwm_fw_sects *fws;
int err, i;
void *data;
uint32_t dlen;
uint32_t offset;
fws = &sc->sc_fw.fw_sects[ucode_type];
for (i = 0; i < fws->fw_count; i++) {
data = fws->fw_sect[i].fws_data;
dlen = fws->fw_sect[i].fws_len;
offset = fws->fw_sect[i].fws_devoff;
if (dlen > sc->sc_fwdmasegsz) {
err = EFBIG;
} else
err = iwm_firmware_load_sect(sc, offset, data, dlen);
if (err) {
printf("%s: could not load firmware chunk %u of %u"
" (error %d)\n",DEVNAME(sc), i, fws->fw_count, err);
return err;
}
}
iwm_enable_interrupts(sc);
IWM_WRITE(sc, IWM_CSR_RESET, 0);
return 0;
}
static int
iwm_load_cpu_sections_8000(struct iwm_softc *sc, struct iwm_fw_sects *fws,
int cpu, int *first_ucode_section)
{
int shift_param;
int i, err = 0, sec_num = 0x1;
uint32_t val, last_read_idx = 0;
void *data;
uint32_t dlen;
uint32_t offset;
if (cpu == 1) {
shift_param = 0;
*first_ucode_section = 0;
} else {
shift_param = 16;
(*first_ucode_section)++;
}
for (i = *first_ucode_section; i < IWM_UCODE_SECT_MAX; i++) {
last_read_idx = i;
data = fws->fw_sect[i].fws_data;
dlen = fws->fw_sect[i].fws_len;
offset = fws->fw_sect[i].fws_devoff;
if (!data || offset == IWM_CPU1_CPU2_SEPARATOR_SECTION ||
offset == IWM_PAGING_SEPARATOR_SECTION)
break;
if (dlen > sc->sc_fwdmasegsz) {
err = EFBIG;
} else
err = iwm_firmware_load_sect(sc, offset, data, dlen);
if (err) {
DPRINTF(("%s: could not load firmware chunk %d "
"(error %d)\n", DEVNAME(sc), i, err));
return err;
}
if (iwm_nic_lock(sc)) {
val = IWM_READ(sc, IWM_FH_UCODE_LOAD_STATUS);
val = val | (sec_num << shift_param);
IWM_WRITE(sc, IWM_FH_UCODE_LOAD_STATUS, val);
sec_num = (sec_num << 1) | 0x1;
iwm_nic_unlock(sc);
} else {
err = EBUSY;
DPRINTF(("%s: could not load firmware chunk %d "
"(error %d)\n", DEVNAME(sc), i, err));
return err;
}
}
*first_ucode_section = last_read_idx;
if (iwm_nic_lock(sc)) {
if (cpu == 1)
IWM_WRITE(sc, IWM_FH_UCODE_LOAD_STATUS, 0xFFFF);
else
IWM_WRITE(sc, IWM_FH_UCODE_LOAD_STATUS, 0xFFFFFFFF);
iwm_nic_unlock(sc);
} else {
err = EBUSY;
DPRINTF(("%s: could not finalize firmware loading (error %d)\n",
DEVNAME(sc), err));
return err;
}
return 0;
}
static int
iwm_load_firmware_8000(struct iwm_softc *sc, enum iwm_ucode_type ucode_type)
{
struct iwm_fw_sects *fws;
int err = 0;
int first_ucode_section;
fws = &sc->sc_fw.fw_sects[ucode_type];
if (iwm_nic_lock(sc)) {
iwm_write_prph(sc, IWM_RELEASE_CPU_RESET,
IWM_RELEASE_CPU_RESET_BIT);
iwm_nic_unlock(sc);
}
err = iwm_load_cpu_sections_8000(sc, fws, 1, &first_ucode_section);
if (err)
return err;
err = iwm_load_cpu_sections_8000(sc, fws, 2, &first_ucode_section);
if (err)
return err;
iwm_enable_interrupts(sc);
return 0;
}
static int
iwm_load_firmware(struct iwm_softc *sc, enum iwm_ucode_type ucode_type)
{
int err, w;
sc->sc_uc.uc_intr = 0;
if (sc->sc_device_family == IWM_DEVICE_FAMILY_8000)
err = iwm_load_firmware_8000(sc, ucode_type);
else
err = iwm_load_firmware_7000(sc, ucode_type);
if (err)
return err;
for (w = 0; !sc->sc_uc.uc_intr && w < 10; w++)
err = tsleep(&sc->sc_uc, 0, "iwmuc", mstohz(100));
if (err || !sc->sc_uc.uc_ok) {
aprint_error_dev(sc->sc_dev,
"could not load firmware (error %d, ok %d)\n",
err, sc->sc_uc.uc_ok);
if (sc->sc_device_family == IWM_DEVICE_FAMILY_8000) {
aprint_error_dev(sc->sc_dev, "cpu1 status: 0x%x\n",
iwm_read_prph(sc, IWM_SB_CPU_1_STATUS));
aprint_error_dev(sc->sc_dev, "cpu2 status: 0x%x\n",
iwm_read_prph(sc, IWM_SB_CPU_2_STATUS));
}
}
return err;
}
static int
iwm_start_fw(struct iwm_softc *sc, enum iwm_ucode_type ucode_type)
{
int err;
IWM_WRITE(sc, IWM_CSR_INT, ~0);
err = iwm_nic_init(sc);
if (err) {
aprint_error_dev(sc->sc_dev, "Unable to init nic\n");
return err;
}
IWM_WRITE(sc, IWM_CSR_UCODE_DRV_GP1_CLR, IWM_CSR_UCODE_SW_BIT_RFKILL);
IWM_WRITE(sc, IWM_CSR_UCODE_DRV_GP1_CLR,
IWM_CSR_UCODE_DRV_GP1_BIT_CMD_BLOCKED);
IWM_WRITE(sc, IWM_CSR_INT, ~0);
iwm_enable_fwload_interrupt(sc);
IWM_WRITE(sc, IWM_CSR_UCODE_DRV_GP1_CLR, IWM_CSR_UCODE_SW_BIT_RFKILL);
IWM_WRITE(sc, IWM_CSR_UCODE_DRV_GP1_CLR, IWM_CSR_UCODE_SW_BIT_RFKILL);
return iwm_load_firmware(sc, ucode_type);
}
static int
iwm_send_tx_ant_cfg(struct iwm_softc *sc, uint8_t valid_tx_ant)
{
struct iwm_tx_ant_cfg_cmd tx_ant_cmd = {
.valid = htole32(valid_tx_ant),
};
return iwm_send_cmd_pdu(sc, IWM_TX_ANT_CONFIGURATION_CMD, 0,
sizeof(tx_ant_cmd), &tx_ant_cmd);
}
static int
iwm_send_phy_cfg_cmd(struct iwm_softc *sc)
{
struct iwm_phy_cfg_cmd phy_cfg_cmd;
enum iwm_ucode_type ucode_type = sc->sc_uc_current;
phy_cfg_cmd.phy_cfg = htole32(sc->sc_fw_phy_config);
phy_cfg_cmd.calib_control.event_trigger =
sc->sc_default_calib[ucode_type].event_trigger;
phy_cfg_cmd.calib_control.flow_trigger =
sc->sc_default_calib[ucode_type].flow_trigger;
DPRINTFN(10, ("Sending Phy CFG command: 0x%x\n", phy_cfg_cmd.phy_cfg));
return iwm_send_cmd_pdu(sc, IWM_PHY_CONFIGURATION_CMD, 0,
sizeof(phy_cfg_cmd), &phy_cfg_cmd);
}
static int
iwm_send_dqa_cmd(struct iwm_softc *sc)
{
struct iwm_dqa_enable_cmd dqa_cmd = {
.cmd_queue = htole32(IWM_DQA_CMD_QUEUE),
};
uint32_t cmd_id;
cmd_id = iwm_cmd_id(IWM_DQA_ENABLE_CMD, IWM_DATA_PATH_GROUP, 0);
return iwm_send_cmd_pdu(sc, cmd_id, 0, sizeof(dqa_cmd), &dqa_cmd);
}
static int
iwm_load_ucode_wait_alive(struct iwm_softc *sc, enum iwm_ucode_type ucode_type)
{
struct iwm_fw_sects *fws;
enum iwm_ucode_type old_type = sc->sc_uc_current;
int err;
err = iwm_read_firmware(sc, ucode_type);
if (err)
return err;
if (isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_DQA_SUPPORT))
sc->cmdqid = IWM_DQA_CMD_QUEUE;
else
sc->cmdqid = IWM_CMD_QUEUE;
sc->sc_uc_current = ucode_type;
err = iwm_start_fw(sc, ucode_type);
if (err) {
sc->sc_uc_current = old_type;
return err;
}
err = iwm_post_alive(sc);
if (err)
return err;
fws = &sc->sc_fw.fw_sects[ucode_type];
if (fws->paging_mem_size) {
err = iwm_save_fw_paging(sc, fws);
if (err)
return err;
err = iwm_send_paging_cmd(sc, fws);
if (err) {
iwm_free_fw_paging(sc);
return err;
}
}
return 0;
}
static int
iwm_run_init_mvm_ucode(struct iwm_softc *sc, int justnvm)
{
const int wait_flags = (IWM_INIT_COMPLETE | IWM_CALIB_COMPLETE);
int err, s;
if ((sc->sc_flags & IWM_FLAG_RFKILL) && !justnvm) {
aprint_error_dev(sc->sc_dev,
"radio is disabled by hardware switch\n");
return EPERM;
}
s = splnet();
sc->sc_init_complete = 0;
err = iwm_load_ucode_wait_alive(sc, IWM_UCODE_TYPE_INIT);
if (err) {
DPRINTF(("%s: failed to load init firmware\n", DEVNAME(sc)));
splx(s);
return err;
}
if (sc->sc_device_family < IWM_DEVICE_FAMILY_8000) {
err = iwm_send_bt_init_conf(sc);
if (err) {
splx(s);
return err;
}
}
if (justnvm) {
err = iwm_nvm_init(sc);
if (err) {
aprint_error_dev(sc->sc_dev, "failed to read nvm\n");
splx(s);
return err;
}
if (IEEE80211_ADDR_EQ(etheranyaddr, sc->sc_ic.ic_myaddr))
IEEE80211_ADDR_COPY(&sc->sc_ic.ic_myaddr,
&sc->sc_nvm.hw_addr);
splx(s);
return 0;
}
err = iwm_sf_config(sc, IWM_SF_INIT_OFF);
if (err) {
splx(s);
return err;
}
err = iwm_send_tx_ant_cfg(sc, iwm_fw_valid_tx_ant(sc));
if (err) {
splx(s);
return err;
}
err = iwm_send_phy_cfg_cmd(sc);
if (err) {
splx(s);
return err;
}
while ((sc->sc_init_complete & wait_flags) != wait_flags) {
err = tsleep(&sc->sc_init_complete, 0, "iwminit", mstohz(2000));
if (err)
break;
}
splx(s);
return err;
}
#ifdef notyet
static int
iwm_config_ltr(struct iwm_softc *sc)
{
struct iwm_ltr_config_cmd cmd = {
.flags = htole32(IWM_LTR_CFG_FLAG_FEATURE_ENABLE),
};
if (!sc->sc_ltr_enabled)
return 0;
return iwm_send_cmd_pdu(sc, IWM_LTR_CONFIG, 0, sizeof(cmd), &cmd);
}
#endif
static int
iwm_rx_addbuf(struct iwm_softc *sc, int size, int idx)
{
struct iwm_rx_ring *ring = &sc->rxq;
struct iwm_rx_data *data = &ring->data[idx];
struct mbuf *m;
int err;
int fatal = 0;
m = m_gethdr(M_DONTWAIT, MT_DATA);
if (m == NULL)
return ENOBUFS;
if (size <= MCLBYTES) {
MCLGET(m, M_DONTWAIT);
} else {
MEXTMALLOC(m, size, M_DONTWAIT);
}
if ((m->m_flags & M_EXT) == 0) {
m_freem(m);
return ENOBUFS;
}
if (data->m != NULL) {
bus_dmamap_unload(sc->sc_dmat, data->map);
fatal = 1;
}
m->m_len = m->m_pkthdr.len = size;
err = bus_dmamap_load_mbuf(sc->sc_dmat, data->map, m,
BUS_DMA_READ|BUS_DMA_NOWAIT);
if (err) {
if (fatal)
panic("iwm: could not load RX mbuf");
m_freem(m);
return err;
}
data->m = m;
bus_dmamap_sync(sc->sc_dmat, data->map, 0, size, BUS_DMASYNC_PREREAD);
ring->desc[idx] = htole32(data->map->dm_segs[0].ds_addr >> 8);
bus_dmamap_sync(sc->sc_dmat, ring->desc_dma.map,
idx * sizeof(uint32_t), sizeof(uint32_t), BUS_DMASYNC_PREWRITE);
return 0;
}
#define IWM_RSSI_OFFSET 50
static int
iwm_calc_rssi(struct iwm_softc *sc, struct iwm_rx_phy_info *phy_info)
{
int rssi_a, rssi_b, rssi_a_dbm, rssi_b_dbm, max_rssi_dbm;
uint32_t agc_a, agc_b;
uint32_t val;
val = le32toh(phy_info->non_cfg_phy[IWM_RX_INFO_AGC_IDX]);
agc_a = (val & IWM_OFDM_AGC_A_MSK) >> IWM_OFDM_AGC_A_POS;
agc_b = (val & IWM_OFDM_AGC_B_MSK) >> IWM_OFDM_AGC_B_POS;
val = le32toh(phy_info->non_cfg_phy[IWM_RX_INFO_RSSI_AB_IDX]);
rssi_a = (val & IWM_OFDM_RSSI_INBAND_A_MSK) >> IWM_OFDM_RSSI_A_POS;
rssi_b = (val & IWM_OFDM_RSSI_INBAND_B_MSK) >> IWM_OFDM_RSSI_B_POS;
rssi_a_dbm = rssi_a - IWM_RSSI_OFFSET - agc_a;
rssi_b_dbm = rssi_b - IWM_RSSI_OFFSET - agc_b;
max_rssi_dbm = MAX(rssi_a_dbm, rssi_b_dbm);
DPRINTF(("Rssi In A %d B %d Max %d AGCA %d AGCB %d\n",
rssi_a_dbm, rssi_b_dbm, max_rssi_dbm, agc_a, agc_b));
return max_rssi_dbm;
}
static int
iwm_get_signal_strength(struct iwm_softc *sc, struct iwm_rx_phy_info *phy_info)
{
int energy_a, energy_b, energy_c, max_energy;
uint32_t val;
val = le32toh(phy_info->non_cfg_phy[IWM_RX_INFO_ENERGY_ANT_ABC_IDX]);
energy_a = (val & IWM_RX_INFO_ENERGY_ANT_A_MSK) >>
IWM_RX_INFO_ENERGY_ANT_A_POS;
energy_a = energy_a ? -energy_a : -256;
energy_b = (val & IWM_RX_INFO_ENERGY_ANT_B_MSK) >>
IWM_RX_INFO_ENERGY_ANT_B_POS;
energy_b = energy_b ? -energy_b : -256;
energy_c = (val & IWM_RX_INFO_ENERGY_ANT_C_MSK) >>
IWM_RX_INFO_ENERGY_ANT_C_POS;
energy_c = energy_c ? -energy_c : -256;
max_energy = MAX(energy_a, energy_b);
max_energy = MAX(max_energy, energy_c);
DPRINTFN(12, ("energy In A %d B %d C %d, and max %d\n",
energy_a, energy_b, energy_c, max_energy));
return max_energy;
}
static void
iwm_rx_rx_phy_cmd(struct iwm_softc *sc, struct iwm_rx_packet *pkt,
struct iwm_rx_data *data)
{
struct iwm_rx_phy_info *phy_info = (void *)pkt->data;
DPRINTFN(20, ("received PHY stats\n"));
bus_dmamap_sync(sc->sc_dmat, data->map, sizeof(*pkt),
sizeof(*phy_info), BUS_DMASYNC_POSTREAD);
memcpy(&sc->sc_last_phy_info, phy_info, sizeof(sc->sc_last_phy_info));
}
static int
iwm_get_noise(const struct iwm_statistics_rx_non_phy *stats)
{
int i, total, nbant, noise;
total = nbant = noise = 0;
for (i = 0; i < 3; i++) {
noise = le32toh(stats->beacon_silence_rssi[i]) & 0xff;
if (noise) {
total += noise;
nbant++;
}
}
return (nbant == 0) ? -127 : (total / nbant) - 107;
}
static void
iwm_rx_rx_mpdu(struct iwm_softc *sc, struct iwm_rx_packet *pkt,
struct iwm_rx_data *data)
{
struct ieee80211com *ic = &sc->sc_ic;
struct ieee80211_frame *wh;
struct ieee80211_node *ni;
struct ieee80211_channel *c = NULL;
struct mbuf *m;
struct iwm_rx_phy_info *phy_info;
struct iwm_rx_mpdu_res_start *rx_res;
int device_timestamp;
uint32_t len;
uint32_t rx_pkt_status;
int rssi;
int s;
bus_dmamap_sync(sc->sc_dmat, data->map, 0, IWM_RBUF_SIZE,
BUS_DMASYNC_POSTREAD);
phy_info = &sc->sc_last_phy_info;
rx_res = (struct iwm_rx_mpdu_res_start *)pkt->data;
wh = (struct ieee80211_frame *)(pkt->data + sizeof(*rx_res));
len = le16toh(rx_res->byte_count);
rx_pkt_status = le32toh(*(uint32_t *)(pkt->data +
sizeof(*rx_res) + len));
m = data->m;
m->m_data = pkt->data + sizeof(*rx_res);
m->m_pkthdr.len = m->m_len = len;
if (__predict_false(phy_info->cfg_phy_cnt > 20)) {
DPRINTF(("dsp size out of range [0,20]: %d\n",
phy_info->cfg_phy_cnt));
return;
}
if (!(rx_pkt_status & IWM_RX_MPDU_RES_STATUS_CRC_OK) ||
!(rx_pkt_status & IWM_RX_MPDU_RES_STATUS_OVERRUN_OK)) {
DPRINTF(("Bad CRC or FIFO: 0x%08X.\n", rx_pkt_status));
return;
}
device_timestamp = le32toh(phy_info->system_timestamp);
if (sc->sc_capaflags & IWM_UCODE_TLV_FLAGS_RX_ENERGY_API) {
rssi = iwm_get_signal_strength(sc, phy_info);
} else {
rssi = iwm_calc_rssi(sc, phy_info);
}
rssi = -rssi;
if (ic->ic_state == IEEE80211_S_SCAN)
iwm_fix_channel(sc, m);
if (iwm_rx_addbuf(sc, IWM_RBUF_SIZE, sc->rxq.cur) != 0)
return;
m_set_rcvif(m, IC2IFP(ic));
if (le32toh(phy_info->channel) < __arraycount(ic->ic_channels))
c = &ic->ic_channels[le32toh(phy_info->channel)];
s = splnet();
ni = ieee80211_find_rxnode(ic, (struct ieee80211_frame_min *)wh);
if (c)
ni->ni_chan = c;
if (__predict_false(sc->sc_drvbpf != NULL)) {
struct iwm_rx_radiotap_header *tap = &sc->sc_rxtap;
tap->wr_flags = 0;
if (phy_info->phy_flags & htole16(IWM_PHY_INFO_FLAG_SHPREAMBLE))
tap->wr_flags |= IEEE80211_RADIOTAP_F_SHORTPRE;
tap->wr_chan_freq =
htole16(ic->ic_channels[phy_info->channel].ic_freq);
tap->wr_chan_flags =
htole16(ic->ic_channels[phy_info->channel].ic_flags);
tap->wr_dbm_antsignal = (int8_t)rssi;
tap->wr_dbm_antnoise = (int8_t)sc->sc_noise;
tap->wr_tsft = phy_info->system_timestamp;
if (phy_info->phy_flags &
htole16(IWM_RX_RES_PHY_FLAGS_OFDM_HT)) {
uint8_t mcs = (phy_info->rate_n_flags &
htole32(IWM_RATE_HT_MCS_RATE_CODE_MSK |
IWM_RATE_HT_MCS_NSS_MSK));
tap->wr_rate = (0x80 | mcs);
} else {
uint8_t rate = (phy_info->rate_n_flags &
htole32(IWM_RATE_LEGACY_RATE_MSK));
switch (rate) {
case 10: tap->wr_rate = 2; break;
case 20: tap->wr_rate = 4; break;
case 55: tap->wr_rate = 11; break;
case 110: tap->wr_rate = 22; break;
case 0xd: tap->wr_rate = 12; break;
case 0xf: tap->wr_rate = 18; break;
case 0x5: tap->wr_rate = 24; break;
case 0x7: tap->wr_rate = 36; break;
case 0x9: tap->wr_rate = 48; break;
case 0xb: tap->wr_rate = 72; break;
case 0x1: tap->wr_rate = 96; break;
case 0x3: tap->wr_rate = 108; break;
default: tap->wr_rate = 0;
}
}
bpf_mtap2(sc->sc_drvbpf, tap, sc->sc_rxtap_len, m, BPF_D_IN);
}
ieee80211_input(ic, m, ni, rssi, device_timestamp);
ieee80211_free_node(ni);
splx(s);
}
static void
iwm_rx_tx_cmd_single(struct iwm_softc *sc, struct iwm_rx_packet *pkt,
struct iwm_node *in)
{
struct ieee80211com *ic = &sc->sc_ic;
struct ifnet *ifp = IC2IFP(ic);
struct iwm_tx_resp *tx_resp = (void *)pkt->data;
int status = le16toh(tx_resp->status.status) & IWM_TX_STATUS_MSK;
int failack = tx_resp->failure_frame;
KASSERT(tx_resp->frame_count == 1);
in->in_amn.amn_txcnt++;
if (failack > 0) {
in->in_amn.amn_retrycnt++;
}
if (status != IWM_TX_STATUS_SUCCESS &&
status != IWM_TX_STATUS_DIRECT_DONE)
if_statinc(ifp, if_oerrors);
else
if_statinc(ifp, if_opackets);
}
static void
iwm_rx_tx_cmd(struct iwm_softc *sc, struct iwm_rx_packet *pkt,
struct iwm_rx_data *data)
{
struct ieee80211com *ic = &sc->sc_ic;
struct ifnet *ifp = IC2IFP(ic);
struct iwm_cmd_header *cmd_hdr = &pkt->hdr;
int idx = cmd_hdr->idx;
int qid = cmd_hdr->qid;
struct iwm_tx_ring *ring = &sc->txq[qid];
struct iwm_tx_data *txd = &ring->data[idx];
struct iwm_node *in = txd->in;
int s;
s = splnet();
if (txd->done) {
DPRINTF(("%s: got tx interrupt that's already been handled!\n",
DEVNAME(sc)));
splx(s);
return;
}
bus_dmamap_sync(sc->sc_dmat, data->map, 0, IWM_RBUF_SIZE,
BUS_DMASYNC_POSTREAD);
sc->sc_tx_timer = 0;
iwm_rx_tx_cmd_single(sc, pkt, in);
bus_dmamap_sync(sc->sc_dmat, txd->map, 0, txd->map->dm_mapsize,
BUS_DMASYNC_POSTWRITE);
bus_dmamap_unload(sc->sc_dmat, txd->map);
m_freem(txd->m);
DPRINTFN(8, ("free txd %p, in %p\n", txd, txd->in));
KASSERT(txd->done == 0);
txd->done = 1;
KASSERT(txd->in);
txd->m = NULL;
txd->in = NULL;
ieee80211_free_node(&in->in_ni);
if (--ring->queued < IWM_TX_RING_LOMARK) {
sc->qfullmsk &= ~(1 << qid);
if (sc->qfullmsk == 0 && (ifp->if_flags & IFF_OACTIVE)) {
ifp->if_flags &= ~IFF_OACTIVE;
KASSERT(KERNEL_LOCKED_P());
iwm_start(ifp);
}
}
splx(s);
}
static int
iwm_binding_cmd(struct iwm_softc *sc, struct iwm_node *in, uint32_t action)
{
struct iwm_binding_cmd cmd;
struct iwm_phy_ctxt *phyctxt = in->in_phyctxt;
uint32_t mac_id = IWM_FW_CMD_ID_AND_COLOR(in->in_id, in->in_color);
int i, err, active = (sc->sc_flags & IWM_FLAG_BINDING_ACTIVE);
uint32_t status;
size_t cmdsize;
if (action == IWM_FW_CTXT_ACTION_ADD && active) {
device_printf(sc->sc_dev, "binding already added\n");
return EINVAL;
}
if (action == IWM_FW_CTXT_ACTION_REMOVE && !active) {
device_printf(sc->sc_dev, "binding already removed\n");
return EINVAL;
}
if (phyctxt == NULL)
return EINVAL;
memset(&cmd, 0, sizeof(cmd));
cmd.id_and_color
= htole32(IWM_FW_CMD_ID_AND_COLOR(phyctxt->id, phyctxt->color));
cmd.action = htole32(action);
cmd.phy = htole32(IWM_FW_CMD_ID_AND_COLOR(phyctxt->id, phyctxt->color));
cmd.macs[0] = htole32(mac_id);
for (i = 1; i < IWM_MAX_MACS_IN_BINDING; i++)
cmd.macs[i] = htole32(IWM_FW_CTXT_INVALID);
if (IEEE80211_IS_CHAN_2GHZ(phyctxt->channel) ||
isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_CDB_SUPPORT))
cmd.lmac_id = htole32(IWM_LMAC_24G_INDEX);
else
cmd.lmac_id = htole32(IWM_LMAC_5G_INDEX);
if (isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_BINDING_CDB_SUPPORT))
cmdsize = sizeof(cmd);
else
cmdsize = sizeof(struct iwm_binding_cmd_v1);
status = 0;
err = iwm_send_cmd_pdu_status(sc, IWM_BINDING_CONTEXT_CMD, cmdsize, &cmd,
&status);
if (err == 0 && status != 0)
err = EIO;
return err;
}
static void
iwm_phy_ctxt_cmd_hdr(struct iwm_softc *sc, struct iwm_phy_ctxt *ctxt,
struct iwm_phy_context_cmd *cmd, uint32_t action, uint32_t apply_time)
{
memset(cmd, 0, sizeof(struct iwm_phy_context_cmd));
cmd->id_and_color = htole32(IWM_FW_CMD_ID_AND_COLOR(ctxt->id,
ctxt->color));
cmd->action = htole32(action);
cmd->apply_time = htole32(apply_time);
}
static void
iwm_phy_ctxt_cmd_data(struct iwm_softc *sc, struct iwm_phy_context_cmd *cmd,
struct ieee80211_channel *chan, uint8_t chains_static,
uint8_t chains_dynamic)
{
struct ieee80211com *ic = &sc->sc_ic;
uint8_t active_cnt, idle_cnt;
cmd->ci.band = IEEE80211_IS_CHAN_2GHZ(chan) ?
IWM_PHY_BAND_24 : IWM_PHY_BAND_5;
cmd->ci.channel = ieee80211_chan2ieee(ic, chan);
cmd->ci.width = IWM_PHY_VHT_CHANNEL_MODE20;
cmd->ci.ctrl_pos = IWM_PHY_VHT_CTRL_POS_1_BELOW;
idle_cnt = chains_static;
active_cnt = chains_dynamic;
cmd->rxchain_info = htole32(iwm_fw_valid_rx_ant(sc) <<
IWM_PHY_RX_CHAIN_VALID_POS);
cmd->rxchain_info |= htole32(idle_cnt << IWM_PHY_RX_CHAIN_CNT_POS);
cmd->rxchain_info |= htole32(active_cnt <<
IWM_PHY_RX_CHAIN_MIMO_CNT_POS);
cmd->txchain_info = htole32(iwm_fw_valid_tx_ant(sc));
}
static int
iwm_phy_ctxt_cmd(struct iwm_softc *sc, struct iwm_phy_ctxt *ctxt, uint8_t chains_static, uint8_t chains_dynamic, uint32_t action,
uint32_t apply_time)
{
struct iwm_phy_context_cmd cmd;
iwm_phy_ctxt_cmd_hdr(sc, ctxt, &cmd, action, apply_time);
iwm_phy_ctxt_cmd_data(sc, &cmd, ctxt->channel,
chains_static, chains_dynamic);
return iwm_send_cmd_pdu(sc, IWM_PHY_CONTEXT_CMD, 0,
sizeof(struct iwm_phy_context_cmd), &cmd);
}
static int
iwm_send_cmd(struct iwm_softc *sc, struct iwm_host_cmd *hcmd)
{
struct iwm_tx_ring *ring = &sc->txq[sc->cmdqid];
struct iwm_tfd *desc;
struct iwm_tx_data *txdata;
struct iwm_device_cmd *cmd;
struct mbuf *m;
bus_addr_t paddr;
uint32_t addr_lo;
int err = 0, i, paylen, off, s;
int idx, code, async, group_id;
size_t hdrlen, datasz;
uint8_t *data;
int generation = sc->sc_generation;
code = hcmd->id;
async = hcmd->flags & IWM_CMD_ASYNC;
idx = ring->cur;
for (i = 0, paylen = 0; i < __arraycount(hcmd->len); i++) {
paylen += hcmd->len[i];
}
hcmd->resp_pkt = NULL;
if (hcmd->flags & IWM_CMD_WANT_RESP) {
uint8_t *resp_buf;
KASSERT(!async);
KASSERT(hcmd->resp_pkt_len >= sizeof(struct iwm_rx_packet));
KASSERT(hcmd->resp_pkt_len <= IWM_CMD_RESP_MAX);
if (sc->sc_cmd_resp_pkt[idx] != NULL)
return ENOSPC;
resp_buf = kmem_zalloc(hcmd->resp_pkt_len, KM_NOSLEEP);
if (resp_buf == NULL)
return ENOMEM;
sc->sc_cmd_resp_pkt[idx] = resp_buf;
sc->sc_cmd_resp_len[idx] = hcmd->resp_pkt_len;
} else {
sc->sc_cmd_resp_pkt[idx] = NULL;
sc->sc_cmd_resp_len[idx] = 0;
}
s = splnet();
desc = &ring->desc[idx];
txdata = &ring->data[idx];
group_id = iwm_cmd_groupid(code);
if (group_id != 0) {
hdrlen = sizeof(cmd->hdr_wide);
datasz = sizeof(cmd->data_wide);
} else {
hdrlen = sizeof(cmd->hdr);
datasz = sizeof(cmd->data);
}
if (paylen > datasz) {
size_t totlen = hdrlen + paylen;
if (paylen > IWM_MAX_CMD_PAYLOAD_SIZE) {
device_printf(sc->sc_dev,
"firmware command too long (%zd bytes)\n", totlen);
err = EINVAL;
goto out;
}
m = m_gethdr(M_DONTWAIT, MT_DATA);
if (m == NULL) {
device_printf(sc->sc_dev,
"could not get fw cmd mbuf header\n");
err = ENOMEM;
goto out;
}
MEXTMALLOC(m, IWM_RBUF_SIZE, M_DONTWAIT);
if (!(m->m_flags & M_EXT)) {
device_printf(sc->sc_dev,
"could not get fw cmd mbuf (%zd bytes)\n", totlen);
m_freem(m);
err = ENOMEM;
goto out;
}
cmd = mtod(m, struct iwm_device_cmd *);
err = bus_dmamap_load(sc->sc_dmat, txdata->map, cmd,
totlen, NULL, BUS_DMA_NOWAIT | BUS_DMA_WRITE);
if (err) {
device_printf(sc->sc_dev,
"could not load fw cmd mbuf (%zd bytes)\n", totlen);
m_freem(m);
goto out;
}
txdata->m = m;
paddr = txdata->map->dm_segs[0].ds_addr;
} else {
cmd = &ring->cmd[idx];
paddr = txdata->cmd_paddr;
}
if (group_id != 0) {
cmd->hdr_wide.opcode = iwm_cmd_opcode(code);
cmd->hdr_wide.group_id = group_id;
cmd->hdr_wide.qid = ring->qid;
cmd->hdr_wide.idx = idx;
cmd->hdr_wide.length = htole16(paylen);
cmd->hdr_wide.version = iwm_cmd_version(code);
data = cmd->data_wide;
} else {
cmd->hdr.code = code;
cmd->hdr.flags = 0;
cmd->hdr.qid = ring->qid;
cmd->hdr.idx = idx;
data = cmd->data;
}
for (i = 0, off = 0; i < __arraycount(hcmd->data); i++) {
if (hcmd->len[i] == 0)
continue;
memcpy(data + off, hcmd->data[i], hcmd->len[i]);
off += hcmd->len[i];
}
KASSERT(off == paylen);
addr_lo = htole32((uint32_t)paddr);
memcpy(&desc->tbs[0].lo, &addr_lo, sizeof(uint32_t));
desc->tbs[0].hi_n_len = htole16(iwm_get_dma_hi_addr(paddr)
| ((hdrlen + paylen) << 4));
desc->num_tbs = 1;
DPRINTFN(8, ("iwm_send_cmd 0x%x size=%zu %s\n",
code, hdrlen + paylen, async ? " (async)" : ""));
if (paylen > datasz) {
bus_dmamap_sync(sc->sc_dmat, txdata->map, 0,
hdrlen + paylen, BUS_DMASYNC_PREWRITE);
} else {
bus_dmamap_sync(sc->sc_dmat, ring->cmd_dma.map,
(uint8_t *)cmd - (uint8_t *)ring->cmd_dma.vaddr,
hdrlen + paylen, BUS_DMASYNC_PREWRITE);
}
bus_dmamap_sync(sc->sc_dmat, ring->desc_dma.map,
(uint8_t *)desc - (uint8_t *)ring->desc_dma.vaddr,
sizeof(*desc), BUS_DMASYNC_PREWRITE);
if (sc->sc_device_family == IWM_DEVICE_FAMILY_7000) {
if (ring->queued == 0 && !iwm_nic_lock(sc)) {
err = EBUSY;
goto out;
}
}
iwm_update_sched(sc, ring->qid, ring->cur, 0, 0);
DPRINTF(("sending command 0x%x qid %d, idx %d\n",
code, ring->qid, ring->cur));
ring->queued++;
ring->cur = (ring->cur + 1) % IWM_TX_RING_COUNT;
IWM_WRITE(sc, IWM_HBUS_TARG_WRPTR, ring->qid << 8 | ring->cur);
if (!async) {
err = tsleep(desc, PCATCH, "iwmcmd", mstohz(2000));
if (err == 0) {
if (generation != sc->sc_generation) {
err = ENXIO;
goto out;
}
hcmd->resp_pkt = (void *)sc->sc_cmd_resp_pkt[idx];
sc->sc_cmd_resp_pkt[idx] = NULL;
sc->sc_cmd_resp_len[idx] = 0;
} else if (generation == sc->sc_generation) {
if (sc->sc_cmd_resp_pkt[idx] != NULL) {
KASSERT(sc->sc_cmd_resp_len[idx] > 0);
kmem_free(sc->sc_cmd_resp_pkt[idx],
sc->sc_cmd_resp_len[idx]);
}
sc->sc_cmd_resp_pkt[idx] = NULL;
sc->sc_cmd_resp_len[idx] = 0;
}
}
out:
splx(s);
return err;
}
static int
iwm_send_cmd_pdu(struct iwm_softc *sc, uint32_t id, uint32_t flags,
uint16_t len, const void *data)
{
struct iwm_host_cmd cmd = {
.id = id,
.len = { len, },
.data = { data, },
.flags = flags,
};
return iwm_send_cmd(sc, &cmd);
}
static int
iwm_send_cmd_status(struct iwm_softc *sc, struct iwm_host_cmd *cmd,
uint32_t *status)
{
struct iwm_rx_packet *pkt;
struct iwm_cmd_response *resp;
int err, resp_len;
KASSERT((cmd->flags & IWM_CMD_WANT_RESP) == 0);
cmd->flags |= IWM_CMD_WANT_RESP;
cmd->resp_pkt_len = sizeof(*pkt) + sizeof(*resp);
err = iwm_send_cmd(sc, cmd);
if (err)
return err;
pkt = cmd->resp_pkt;
if (pkt == NULL || (pkt->hdr.flags & IWM_CMD_FAILED_MSK))
return EIO;
resp_len = iwm_rx_packet_payload_len(pkt);
if (resp_len != sizeof(*resp)) {
iwm_free_resp(sc, cmd);
return EIO;
}
resp = (void *)pkt->data;
*status = le32toh(resp->status);
iwm_free_resp(sc, cmd);
return err;
}
static int
iwm_send_cmd_pdu_status(struct iwm_softc *sc, uint32_t id, uint16_t len,
const void *data, uint32_t *status)
{
struct iwm_host_cmd cmd = {
.id = id,
.len = { len, },
.data = { data, },
};
return iwm_send_cmd_status(sc, &cmd, status);
}
static void
iwm_free_resp(struct iwm_softc *sc, struct iwm_host_cmd *hcmd)
{
KASSERT(hcmd != NULL);
KASSERT((hcmd->flags & IWM_CMD_WANT_RESP) == IWM_CMD_WANT_RESP);
if (hcmd->resp_pkt != NULL) {
KASSERT(hcmd->resp_pkt_len > 0);
kmem_free(hcmd->resp_pkt, hcmd->resp_pkt_len);
}
hcmd->resp_pkt = NULL;
}
static void
iwm_cmd_done(struct iwm_softc *sc, int qid, int idx)
{
struct iwm_tx_ring *ring = &sc->txq[sc->cmdqid];
struct iwm_tx_data *data;
if (qid != sc->cmdqid) {
return;
}
data = &ring->data[idx];
if (data->m != NULL) {
bus_dmamap_sync(sc->sc_dmat, data->map, 0,
data->map->dm_mapsize, BUS_DMASYNC_POSTWRITE);
bus_dmamap_unload(sc->sc_dmat, data->map);
m_freem(data->m);
data->m = NULL;
}
wakeup(&ring->desc[idx]);
if (ring->queued == 0) {
device_printf(sc->sc_dev, "cmd_done with empty ring\n");
} else if (--ring->queued == 0) {
if (sc->sc_device_family == IWM_DEVICE_FAMILY_7000) {
iwm_nic_unlock(sc);
}
}
}
void
iwm_update_sched(struct iwm_softc *sc, int qid, int idx, uint8_t sta_id,
uint16_t len)
{
struct iwm_agn_scd_bc_tbl *scd_bc_tbl;
uint16_t val;
scd_bc_tbl = sc->sched_dma.vaddr;
len += IWM_TX_CRC_SIZE + IWM_TX_DELIMITER_SIZE;
if (sc->sc_capaflags & IWM_UCODE_TLV_FLAGS_DW_BC_TABLE)
len = roundup(len, 4) / 4;
val = htole16(sta_id << 12 | len);
bus_dmamap_sync(sc->sc_dmat, sc->sched_dma.map,
0, sc->sched_dma.size, BUS_DMASYNC_PREWRITE);
scd_bc_tbl[qid].tfd_offset[idx] = val;
if (idx < IWM_TFD_QUEUE_SIZE_BC_DUP)
scd_bc_tbl[qid].tfd_offset[IWM_TFD_QUEUE_SIZE_MAX + idx] = val;
bus_dmamap_sync(sc->sc_dmat, sc->sched_dma.map,
0, sc->sched_dma.size, BUS_DMASYNC_POSTWRITE);
}
static const struct iwm_rate *
iwm_tx_fill_cmd(struct iwm_softc *sc, struct iwm_node *in,
struct ieee80211_frame *wh, struct iwm_tx_cmd *tx)
{
struct ieee80211com *ic = &sc->sc_ic;
struct ieee80211_node *ni = &in->in_ni;
const struct iwm_rate *rinfo;
int type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK;
int ridx, rate_flags, i, ind;
int nrates = ni->ni_rates.rs_nrates;
tx->rts_retry_limit = IWM_RTS_DFAULT_RETRY_LIMIT;
tx->data_retry_limit = IWM_DEFAULT_TX_RETRY;
if (IEEE80211_IS_MULTICAST(wh->i_addr1) ||
type != IEEE80211_FC0_TYPE_DATA) {
ridx = (IEEE80211_IS_CHAN_5GHZ(ni->ni_chan)) ?
IWM_RIDX_OFDM : IWM_RIDX_CCK;
tx->data_retry_limit = IWM_MGMT_DFAULT_RETRY_LIMIT;
#ifndef IEEE80211_NO_HT
} else if (ic->ic_fixed_mcs != -1) {
ridx = sc->sc_fixed_ridx;
#endif
} else if (ic->ic_fixed_rate != -1) {
ridx = sc->sc_fixed_ridx;
} else {
tx->initial_rate_index = 0;
tx->tx_flags |= htole32(IWM_TX_CMD_FLG_STA_RATE);
DPRINTFN(12, ("start with txrate %d\n",
tx->initial_rate_index));
#ifndef IEEE80211_NO_HT
if (ni->ni_flags & IEEE80211_NODE_HT) {
ridx = iwm_mcs2ridx[ni->ni_txmcs];
return &iwm_rates[ridx];
}
#endif
ridx = (IEEE80211_IS_CHAN_5GHZ(ni->ni_chan)) ?
IWM_RIDX_OFDM : IWM_RIDX_CCK;
for (i = 0; i < nrates; i++) {
if (iwm_rates[i].rate == (ni->ni_txrate &
IEEE80211_RATE_VAL)) {
ridx = i;
break;
}
}
return &iwm_rates[ridx];
}
rinfo = &iwm_rates[ridx];
for (i = 0, ind = sc->sc_mgmt_last_antenna;
i < IWM_RATE_MCS_ANT_NUM; i++) {
ind = (ind + 1) % IWM_RATE_MCS_ANT_NUM;
if (iwm_fw_valid_tx_ant(sc) & (1 << ind)) {
sc->sc_mgmt_last_antenna = ind;
break;
}
}
rate_flags = (1 << sc->sc_mgmt_last_antenna) << IWM_RATE_MCS_ANT_POS;
if (IWM_RIDX_IS_CCK(ridx))
rate_flags |= IWM_RATE_MCS_CCK_MSK;
#ifndef IEEE80211_NO_HT
if ((ni->ni_flags & IEEE80211_NODE_HT) &&
rinfo->ht_plcp != IWM_RATE_HT_SISO_MCS_INV_PLCP) {
rate_flags |= IWM_RATE_MCS_HT_MSK;
tx->rate_n_flags = htole32(rate_flags | rinfo->ht_plcp);
} else
#endif
tx->rate_n_flags = htole32(rate_flags | rinfo->plcp);
return rinfo;
}
#define TB0_SIZE 16
static int
iwm_tx(struct iwm_softc *sc, struct mbuf *m, struct ieee80211_node *ni, int ac)
{
struct ieee80211com *ic = &sc->sc_ic;
struct iwm_node *in = (struct iwm_node *)ni;
struct iwm_tx_ring *ring;
struct iwm_tx_data *data;
struct iwm_tfd *desc;
struct iwm_device_cmd *cmd;
struct iwm_tx_cmd *tx;
struct ieee80211_frame *wh;
struct ieee80211_key *k = NULL;
struct mbuf *m1;
const struct iwm_rate *rinfo;
uint32_t flags;
u_int hdrlen;
bus_dma_segment_t *seg;
uint8_t tid, type, subtype;
int i, totlen, err, pad;
int qid;
#ifndef IEEE80211_NO_HT
int hasqos;
#endif
wh = mtod(m, struct ieee80211_frame *);
type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK;
subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK;
hdrlen = ieee80211_anyhdrsize(wh);
#ifndef IEEE80211_NO_HT
hasqos = ieee80211_has_qos(wh);
#endif
if (type == IEEE80211_FC0_TYPE_DATA)
tid = IWM_TID_NON_QOS;
else
tid = IWM_MAX_TID_COUNT;
if (ic->ic_opmode == IEEE80211_M_MONITOR) {
if (isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_DQA_SUPPORT))
qid = IWM_DQA_INJECT_MONITOR_QUEUE;
else
qid = IWM_AUX_QUEUE;
} else if (isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_DQA_SUPPORT))
qid = IWM_DQA_MIN_MGMT_QUEUE + ac;
else
qid = ac;
#ifndef IEEE80211_NO_HT
if (hasqos) {
struct ieee80211_tx_ba *ba;
uint16_t qos = ieee80211_get_qos(wh);
int qostid = qos & IEEE80211_QOS_TID;
int agg_qid = IWM_FIRST_AGG_TX_QUEUE + qostid;
ba = &ni->ni_tx_ba[qostid];
if (!IEEE80211_IS_MULTICAST(wh->i_addr1) &&
type == IEEE80211_FC0_TYPE_DATA &&
subtype != IEEE80211_FC0_SUBTYPE_NODATA &&
(sc->tx_ba_queue_mask & (1 << agg_qid)) &&
ba->ba_state == IEEE80211_BA_AGREED) {
qid = agg_qid;
tid = qostid;
ac = ieee80211_up_to_ac(ic, qostid);
}
}
#endif
ring = &sc->txq[qid];
if (ring == NULL) {
device_printf(sc->sc_dev, "no ring data for queue %d\n", qid);
m_freem(m);
return EFAULT;
}
desc = &ring->desc[ring->cur];
memset(desc, 0, sizeof(*desc));
data = &ring->data[ring->cur];
cmd = &ring->cmd[ring->cur];
cmd->hdr.code = IWM_TX_CMD;
cmd->hdr.flags = 0;
cmd->hdr.qid = ring->qid;
cmd->hdr.idx = ring->cur;
tx = (void *)cmd->data;
memset(tx, 0, sizeof(*tx));
rinfo = iwm_tx_fill_cmd(sc, in, wh, tx);
if (__predict_false(sc->sc_drvbpf != NULL)) {
struct iwm_tx_radiotap_header *tap = &sc->sc_txtap;
tap->wt_flags = 0;
tap->wt_chan_freq = htole16(ni->ni_chan->ic_freq);
tap->wt_chan_flags = htole16(ni->ni_chan->ic_flags);
#ifndef IEEE80211_NO_HT
if ((ni->ni_flags & IEEE80211_NODE_HT) &&
!IEEE80211_IS_MULTICAST(wh->i_addr1) &&
type == IEEE80211_FC0_TYPE_DATA &&
rinfo->plcp == IWM_RATE_INVM_PLCP) {
tap->wt_rate = (0x80 | rinfo->ht_plcp);
} else
#endif
tap->wt_rate = rinfo->rate;
tap->wt_hwqueue = ac;
if (wh->i_fc[1] & IEEE80211_FC1_WEP)
tap->wt_flags |= IEEE80211_RADIOTAP_F_WEP;
bpf_mtap2(sc->sc_drvbpf, tap, sc->sc_txtap_len, m, BPF_D_OUT);
}
if (wh->i_fc[1] & IEEE80211_FC1_WEP) {
k = ieee80211_crypto_encap(ic, ni, m);
if (k == NULL) {
m_freem(m);
return ENOBUFS;
}
wh = mtod(m, struct ieee80211_frame *);
}
totlen = m->m_pkthdr.len;
flags = 0;
if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) {
flags |= IWM_TX_CMD_FLG_ACK;
}
if (type == IEEE80211_FC0_TYPE_DATA &&
!IEEE80211_IS_MULTICAST(wh->i_addr1) &&
(totlen + IEEE80211_CRC_LEN > ic->ic_rtsthreshold ||
(ic->ic_flags & IEEE80211_F_USEPROT)))
flags |= IWM_TX_CMD_FLG_PROT_REQUIRE;
if (IEEE80211_IS_MULTICAST(wh->i_addr1) ||
type != IEEE80211_FC0_TYPE_DATA)
tx->sta_id = IWM_AUX_STA_ID;
else
tx->sta_id = IWM_STATION_ID;
if (type == IEEE80211_FC0_TYPE_MGT) {
if (subtype == IEEE80211_FC0_SUBTYPE_ASSOC_REQ ||
subtype == IEEE80211_FC0_SUBTYPE_REASSOC_REQ)
tx->pm_frame_timeout = htole16(IWM_PM_FRAME_ASSOC);
else
tx->pm_frame_timeout = htole16(IWM_PM_FRAME_MGMT);
} else {
#ifndef IEEE80211_NO_HT
if (type == IEEE80211_FC0_TYPE_CTL &&
subtype == IEEE80211_FC0_SUBTYPE_BAR) {
struct ieee80211_frame_min *mwh;
uint8_t *barfrm;
uint16_t ctl;
mwh = mtod(m, struct ieee80211_frame_min *);
barfrm = (uint8_t *)&mwh[1];
ctl = barfrm[1] << 8 | barfrm[0];
tid = (ctl & IEEE80211_BA_TID_INFO_MASK) >>
IEEE80211_BA_TID_INFO_SHIFT;
flags |= IWM_TX_CMD_FLG_ACK | IWM_TX_CMD_FLG_BAR;
tx->data_retry_limit = IWM_BAR_DEFAULT_RETRY_LIMIT;
}
#endif
tx->pm_frame_timeout = htole16(IWM_PM_FRAME_NONE);
}
if (hdrlen & 3) {
flags |= IWM_TX_CMD_FLG_MH_PAD;
pad = 4 - (hdrlen & 3);
} else
pad = 0;
tx->driver_txop = 0;
tx->next_frame_len = 0;
tx->len = htole16(totlen);
tx->tid_tspec = tid;
tx->life_time = htole32(IWM_TX_CMD_LIFE_TIME_INFINITE);
tx->dram_lsb_ptr = htole32(data->scratch_paddr);
tx->dram_msb_ptr = iwm_get_dma_hi_addr(data->scratch_paddr);
memcpy(tx + 1, wh, hdrlen);
flags |= IWM_TX_CMD_FLG_BT_DIS | IWM_TX_CMD_FLG_SEQ_CTL;
tx->sec_ctl = 0;
tx->tx_flags |= htole32(flags);
m_adj(m, hdrlen);
err = bus_dmamap_load_mbuf(sc->sc_dmat, data->map, m,
BUS_DMA_NOWAIT | BUS_DMA_WRITE);
if (err) {
if (err != EFBIG) {
aprint_error_dev(sc->sc_dev,
"can't map mbuf (error %d)\n", err);
m_freem(m);
return err;
}
MGETHDR(m1, M_DONTWAIT, MT_DATA);
if (m1 == NULL) {
m_freem(m);
return ENOBUFS;
}
MCLAIM(m1, &sc->sc_ec.ec_rx_mowner);
if (m->m_pkthdr.len > MHLEN) {
MCLGET(m1, M_DONTWAIT);
if (!(m1->m_flags & M_EXT)) {
m_freem(m);
m_freem(m1);
return ENOBUFS;
}
}
m_copydata(m, 0, m->m_pkthdr.len, mtod(m1, void *));
m1->m_pkthdr.len = m1->m_len = m->m_pkthdr.len;
m_freem(m);
m = m1;
err = bus_dmamap_load_mbuf(sc->sc_dmat, data->map, m,
BUS_DMA_NOWAIT | BUS_DMA_WRITE);
if (err) {
aprint_error_dev(sc->sc_dev,
"can't map mbuf (error %d)\n", err);
m_freem(m);
return err;
}
}
data->m = m;
data->in = in;
data->done = 0;
#ifndef IEEE80211_NO_HT
data->txmcs = ni->ni_txmcs;
data->txrate = ni->ni_txrate;
#ifdef notyet
data->ampdu_txmcs = ni->ni_txmcs;
data->ampdu_txnss = ni->ni_vht_ss;
#endif
#endif
DPRINTFN(8, ("sending txd %p, in %p\n", data, data->in));
KASSERT(data->in != NULL);
DPRINTFN(8, ("sending data: qid=%d idx=%d len=%d nsegs=%d type=%d "
"subtype=%x tx_flags=%08x init_rateidx=%08x rate_n_flags=%08x\n",
ring->qid, ring->cur, totlen, data->map->dm_nsegs, type,
(wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK) >> 4,
le32toh(tx->tx_flags), le32toh(tx->initial_rate_index),
le32toh(tx->rate_n_flags)));
desc->num_tbs = 2 + data->map->dm_nsegs;
desc->tbs[0].lo = htole32(data->cmd_paddr);
desc->tbs[0].hi_n_len = htole16(iwm_get_dma_hi_addr(data->cmd_paddr) |
(TB0_SIZE << 4));
desc->tbs[1].lo = htole32(data->cmd_paddr + TB0_SIZE);
desc->tbs[1].hi_n_len = htole16(iwm_get_dma_hi_addr(data->cmd_paddr) |
((sizeof(struct iwm_cmd_header) + sizeof(*tx)
+ hdrlen + pad - TB0_SIZE) << 4));
seg = data->map->dm_segs;
for (i = 0; i < data->map->dm_nsegs; i++, seg++) {
desc->tbs[i+2].lo = htole32(seg->ds_addr);
desc->tbs[i+2].hi_n_len =
htole16(iwm_get_dma_hi_addr(seg->ds_addr)
| ((seg->ds_len) << 4));
}
bus_dmamap_sync(sc->sc_dmat, data->map, 0, m->m_pkthdr.len,
BUS_DMASYNC_PREWRITE);
bus_dmamap_sync(sc->sc_dmat, ring->cmd_dma.map,
(uint8_t *)cmd - (uint8_t *)ring->cmd, sizeof(*cmd),
BUS_DMASYNC_PREWRITE);
bus_dmamap_sync(sc->sc_dmat, ring->desc_dma.map,
(uint8_t *)desc - (uint8_t *)ring->desc, sizeof(*desc),
BUS_DMASYNC_PREWRITE);
iwm_update_sched(sc, ring->qid, ring->cur, tx->sta_id, totlen);
ring->cur = (ring->cur + 1) % IWM_TX_RING_COUNT;
IWM_WRITE(sc, IWM_HBUS_TARG_WRPTR, ring->qid << 8 | ring->cur);
if (++ring->queued > IWM_TX_RING_HIMARK) {
sc->qfullmsk |= 1 << ring->qid;
}
return 0;
}
#if 0
static int
iwm_flush_tx_path(struct iwm_softc *sc, int tfd_msk, int sync)
{
struct iwm_tx_path_flush_cmd flush_cmd = {
.queues_ctl = htole32(tfd_msk),
.flush_ctl = htole16(IWM_DUMP_TX_FIFO_FLUSH),
};
int err;
err = iwm_send_cmd_pdu(sc, IWM_TXPATH_FLUSH, sync ? 0 : IWM_CMD_ASYNC,
sizeof(flush_cmd), &flush_cmd);
if (err)
aprint_error_dev(sc->sc_dev, "Flushing tx queue failed: %d\n",
err);
return err;
}
#endif
static void
iwm_led_enable(struct iwm_softc *sc)
{
IWM_WRITE(sc, IWM_CSR_LED_REG, IWM_CSR_LED_REG_TURN_ON);
}
static void
iwm_led_disable(struct iwm_softc *sc)
{
IWM_WRITE(sc, IWM_CSR_LED_REG, IWM_CSR_LED_REG_TURN_OFF);
}
static int
iwm_led_is_enabled(struct iwm_softc *sc)
{
return (IWM_READ(sc, IWM_CSR_LED_REG) == IWM_CSR_LED_REG_TURN_ON);
}
static void
iwm_led_blink_timeout(void *arg)
{
struct iwm_softc *sc = arg;
if (iwm_led_is_enabled(sc))
iwm_led_disable(sc);
else
iwm_led_enable(sc);
callout_schedule(&sc->sc_led_blink_to, mstohz(200));
}
static void
iwm_led_blink_start(struct iwm_softc *sc)
{
callout_schedule(&sc->sc_led_blink_to, mstohz(200));
}
static void
iwm_led_blink_stop(struct iwm_softc *sc)
{
callout_stop(&sc->sc_led_blink_to);
iwm_led_disable(sc);
}
#define IWM_POWER_KEEP_ALIVE_PERIOD_SEC 25
static int
iwm_beacon_filter_send_cmd(struct iwm_softc *sc,
struct iwm_beacon_filter_cmd *cmd)
{
return iwm_send_cmd_pdu(sc, IWM_REPLY_BEACON_FILTERING_CMD,
0, sizeof(struct iwm_beacon_filter_cmd), cmd);
}
static void
iwm_beacon_filter_set_cqm_params(struct iwm_softc *sc, struct iwm_node *in,
struct iwm_beacon_filter_cmd *cmd)
{
cmd->ba_enable_beacon_abort = htole32(sc->sc_bf.ba_enabled);
}
static int
iwm_update_beacon_abort(struct iwm_softc *sc, struct iwm_node *in, int enable)
{
struct iwm_beacon_filter_cmd cmd = {
IWM_BF_CMD_CONFIG_DEFAULTS,
.bf_enable_beacon_filter = htole32(1),
.ba_enable_beacon_abort = htole32(enable),
};
if (!sc->sc_bf.bf_enabled)
return 0;
sc->sc_bf.ba_enabled = enable;
iwm_beacon_filter_set_cqm_params(sc, in, &cmd);
return iwm_beacon_filter_send_cmd(sc, &cmd);
}
static void
iwm_power_build_cmd(struct iwm_softc *sc, struct iwm_node *in,
struct iwm_mac_power_cmd *cmd)
{
struct ieee80211_node *ni = &in->in_ni;
int dtim_period, dtim_msec, keep_alive;
cmd->id_and_color = htole32(IWM_FW_CMD_ID_AND_COLOR(in->in_id,
in->in_color));
if (ni->ni_dtim_period)
dtim_period = ni->ni_dtim_period;
else
dtim_period = 1;
dtim_msec = dtim_period * ni->ni_intval;
keep_alive = MAX(3 * dtim_msec, 1000 * IWM_POWER_KEEP_ALIVE_PERIOD_SEC);
keep_alive = roundup(keep_alive, 1000) / 1000;
cmd->keep_alive_seconds = htole16(keep_alive);
#ifdef notyet
cmd->flags = htole16(IWM_POWER_FLAGS_POWER_SAVE_ENA_MSK);
cmd->rx_data_timeout = IWM_DEFAULT_PS_RX_DATA_TIMEOUT;
cmd->tx_data_timeout = IWM_DEFAULT_PS_TX_DATA_TIMEOUT;
#endif
}
static int
iwm_power_mac_update_mode(struct iwm_softc *sc, struct iwm_node *in)
{
int err;
int ba_enable;
struct iwm_mac_power_cmd cmd;
memset(&cmd, 0, sizeof(cmd));
iwm_power_build_cmd(sc, in, &cmd);
err = iwm_send_cmd_pdu(sc, IWM_MAC_PM_POWER_TABLE, 0,
sizeof(cmd), &cmd);
if (err)
return err;
ba_enable = !!(cmd.flags &
htole16(IWM_POWER_FLAGS_POWER_MANAGEMENT_ENA_MSK));
return iwm_update_beacon_abort(sc, in, ba_enable);
}
static int
iwm_power_update_device(struct iwm_softc *sc)
{
struct iwm_device_power_cmd cmd = { };
struct ieee80211com *ic = &sc->sc_ic;
if (!(sc->sc_capaflags & IWM_UCODE_TLV_FLAGS_DEVICE_PS_CMD))
return 0;
if (ic->ic_opmode != IEEE80211_M_MONITOR)
cmd.flags = htole16(IWM_DEVICE_POWER_FLAGS_POWER_SAVE_ENA_MSK);
DPRINTF(("Sending device power command with flags = 0x%X\n",
cmd.flags));
return iwm_send_cmd_pdu(sc, IWM_POWER_TABLE_CMD, 0, sizeof(cmd), &cmd);
}
#ifdef notyet
static int
iwm_enable_beacon_filter(struct iwm_softc *sc, struct iwm_node *in)
{
struct iwm_beacon_filter_cmd cmd = {
IWM_BF_CMD_CONFIG_DEFAULTS,
.bf_enable_beacon_filter = htole32(1),
};
int err;
iwm_beacon_filter_set_cqm_params(sc, in, &cmd);
err = iwm_beacon_filter_send_cmd(sc, &cmd);
if (err == 0)
sc->sc_bf.bf_enabled = 1;
return err;
}
#endif
static int
iwm_disable_beacon_filter(struct iwm_softc *sc)
{
struct iwm_beacon_filter_cmd cmd;
int err;
memset(&cmd, 0, sizeof(cmd));
if ((sc->sc_capaflags & IWM_UCODE_TLV_FLAGS_BF_UPDATED) == 0)
return 0;
err = iwm_beacon_filter_send_cmd(sc, &cmd);
if (err == 0)
sc->sc_bf.bf_enabled = 0;
return err;
}
static int
iwm_add_sta_cmd(struct iwm_softc *sc, struct iwm_node *in, int update)
{
struct iwm_add_sta_cmd add_sta_cmd;
int err;
uint32_t status;
size_t cmdsize;
struct ieee80211com *ic = &sc->sc_ic;
memset(&add_sta_cmd, 0, sizeof(add_sta_cmd));
if (ic->ic_opmode == IEEE80211_M_MONITOR)
add_sta_cmd.sta_id = IWM_MONITOR_STA_ID;
else
add_sta_cmd.sta_id = IWM_STATION_ID;
if (isset(sc->sc_ucode_api, IWM_UCODE_TLV_API_STA_TYPE)) {
if (ic->ic_opmode == IEEE80211_M_MONITOR)
add_sta_cmd.station_type = IWM_STA_GENERAL_PURPOSE;
else
add_sta_cmd.station_type = IWM_STA_LINK;
}
add_sta_cmd.mac_id_n_color
= htole32(IWM_FW_CMD_ID_AND_COLOR(in->in_id, in->in_color));
if (ic->ic_opmode == IEEE80211_M_MONITOR) {
int qid;
IEEE80211_ADDR_COPY(&add_sta_cmd.addr, etheranyaddr);
if (isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_DQA_SUPPORT))
qid = IWM_DQA_INJECT_MONITOR_QUEUE;
else
qid = IWM_AUX_QUEUE;
in->tfd_queue_msk |= (1 << qid);
} else {
int ac;
for (ac = 0; ac < WME_NUM_AC; ac++) {
int qid = ac;
if (isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_DQA_SUPPORT))
qid += IWM_DQA_MIN_MGMT_QUEUE;
in->tfd_queue_msk |= (1 << qid);
}
}
if (!update) {
if (ic->ic_opmode == IEEE80211_M_MONITOR) {
IEEE80211_ADDR_COPY(&add_sta_cmd.addr,
etherbroadcastaddr);
} else {
IEEE80211_ADDR_COPY(&add_sta_cmd.addr,
in->in_macaddr);
}
}
add_sta_cmd.add_modify = update ? 1 : 0;
add_sta_cmd.station_flags_msk
|= htole32(IWM_STA_FLG_FAT_EN_MSK | IWM_STA_FLG_MIMO_EN_MSK);
if (update) {
add_sta_cmd.modify_mask |= (IWM_STA_MODIFY_QUEUES |
IWM_STA_MODIFY_TID_DISABLE_TX);
}
add_sta_cmd.tid_disable_tx = htole16(0xffff);
add_sta_cmd.tfd_queue_msk = htole32(in->tfd_queue_msk);
#ifndef IEEE80211_NO_HT
if (in->in_ni.ni_flags & IEEE80211_NODE_HT) {
add_sta_cmd.station_flags_msk
|= htole32(IWM_STA_FLG_MAX_AGG_SIZE_MSK |
IWM_STA_FLG_AGG_MPDU_DENS_MSK);
add_sta_cmd.station_flags
|= htole32(IWM_STA_FLG_MAX_AGG_SIZE_64K);
switch (ic->ic_ampdu_params & IEEE80211_AMPDU_PARAM_SS) {
case IEEE80211_AMPDU_PARAM_SS_2:
add_sta_cmd.station_flags
|= htole32(IWM_STA_FLG_AGG_MPDU_DENS_2US);
break;
case IEEE80211_AMPDU_PARAM_SS_4:
add_sta_cmd.station_flags
|= htole32(IWM_STA_FLG_AGG_MPDU_DENS_4US);
break;
case IEEE80211_AMPDU_PARAM_SS_8:
add_sta_cmd.station_flags
|= htole32(IWM_STA_FLG_AGG_MPDU_DENS_8US);
break;
case IEEE80211_AMPDU_PARAM_SS_16:
add_sta_cmd.station_flags
|= htole32(IWM_STA_FLG_AGG_MPDU_DENS_16US);
break;
default:
break;
}
}
#endif
status = IWM_ADD_STA_SUCCESS;
if (isset(sc->sc_ucode_api, IWM_UCODE_TLV_API_STA_TYPE))
cmdsize = sizeof(add_sta_cmd);
else
cmdsize = sizeof(struct iwm_add_sta_cmd_v7);
err = iwm_send_cmd_pdu_status(sc, IWM_ADD_STA, cmdsize,
&add_sta_cmd, &status);
if (err == 0 && (status & IWM_ADD_STA_STATUS_MASK) != IWM_ADD_STA_SUCCESS)
err = EIO;
return err;
}
static int
iwm_add_aux_sta(struct iwm_softc *sc)
{
struct iwm_add_sta_cmd cmd;
int err, qid;
uint32_t status;
size_t cmdsize;
if (isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_DQA_SUPPORT)) {
qid = IWM_DQA_AUX_QUEUE;
err = iwm_enable_txq(sc, IWM_AUX_STA_ID, qid,
IWM_TX_FIFO_MCAST, 0, IWM_MAX_TID_COUNT, 0);
} else {
qid = IWM_AUX_QUEUE;
err = iwm_enable_ac_txq(sc, qid, IWM_TX_FIFO_MCAST);
}
if (err)
return err;
memset(&cmd, 0, sizeof(cmd));
cmd.sta_id = IWM_AUX_STA_ID;
if (isset(sc->sc_ucode_api, IWM_UCODE_TLV_API_STA_TYPE))
cmd.station_type = IWM_STA_AUX_ACTIVITY;
cmd.mac_id_n_color =
htole32(IWM_FW_CMD_ID_AND_COLOR(IWM_MAC_INDEX_AUX, 0));
cmd.tfd_queue_msk = htole32(1 << qid);
cmd.tid_disable_tx = htole16(0xffff);
status = IWM_ADD_STA_SUCCESS;
if (isset(sc->sc_ucode_api, IWM_UCODE_TLV_API_STA_TYPE))
cmdsize = sizeof(cmd);
else
cmdsize = sizeof(struct iwm_add_sta_cmd_v7);
err = iwm_send_cmd_pdu_status(sc, IWM_ADD_STA, cmdsize, &cmd,
&status);
if (err == 0 &&
(status & IWM_ADD_STA_STATUS_MASK) != IWM_ADD_STA_SUCCESS)
err = EIO;
return err;
}
#define IWM_PLCP_QUIET_THRESH 1
#define IWM_ACTIVE_QUIET_TIME 10
#define LONG_OUT_TIME_PERIOD 600
#define SHORT_OUT_TIME_PERIOD 200
#define SUSPEND_TIME_PERIOD 100
static uint16_t
iwm_scan_rx_chain(struct iwm_softc *sc)
{
uint16_t rx_chain;
uint8_t rx_ant;
rx_ant = iwm_fw_valid_rx_ant(sc);
rx_chain = rx_ant << IWM_PHY_RX_CHAIN_VALID_POS;
rx_chain |= rx_ant << IWM_PHY_RX_CHAIN_FORCE_MIMO_SEL_POS;
rx_chain |= rx_ant << IWM_PHY_RX_CHAIN_FORCE_SEL_POS;
rx_chain |= 0x1 << IWM_PHY_RX_CHAIN_DRIVER_FORCE_POS;
return htole16(rx_chain);
}
static uint32_t
iwm_scan_rate_n_flags(struct iwm_softc *sc, int flags, int no_cck)
{
uint32_t tx_ant;
int i, ind;
for (i = 0, ind = sc->sc_scan_last_antenna;
i < IWM_RATE_MCS_ANT_NUM; i++) {
ind = (ind + 1) % IWM_RATE_MCS_ANT_NUM;
if (iwm_fw_valid_tx_ant(sc) & (1 << ind)) {
sc->sc_scan_last_antenna = ind;
break;
}
}
tx_ant = (1 << sc->sc_scan_last_antenna) << IWM_RATE_MCS_ANT_POS;
if ((flags & IEEE80211_CHAN_2GHZ) && !no_cck)
return htole32(IWM_RATE_1M_PLCP | IWM_RATE_MCS_CCK_MSK |
tx_ant);
else
return htole32(IWM_RATE_6M_PLCP | tx_ant);
}
static uint8_t
iwm_lmac_scan_fill_channels(struct iwm_softc *sc,
struct iwm_scan_channel_cfg_lmac *chan, int n_ssids)
{
struct ieee80211com *ic = &sc->sc_ic;
struct ieee80211_channel *c;
uint8_t nchan;
for (nchan = 0, c = &ic->ic_channels[1];
c <= &ic->ic_channels[IEEE80211_CHAN_MAX] &&
nchan < sc->sc_capa_n_scan_channels;
c++) {
if (c->ic_flags == 0)
continue;
chan->channel_num = htole16(ieee80211_mhz2ieee(c->ic_freq, 0));
chan->iter_count = htole16(1);
chan->iter_interval = htole32(0);
chan->flags = htole32(IWM_UNIFIED_SCAN_CHANNEL_PARTIAL);
chan->flags |= htole32(IWM_SCAN_CHANNEL_NSSIDS(n_ssids));
if (!IEEE80211_IS_CHAN_PASSIVE(c) && n_ssids != 0)
chan->flags |= htole32(IWM_SCAN_CHANNEL_TYPE_ACTIVE);
chan++;
nchan++;
}
return nchan;
}
static uint8_t
iwm_umac_scan_fill_channels(struct iwm_softc *sc,
struct iwm_scan_channel_cfg_umac *chan, int n_ssids)
{
struct ieee80211com *ic = &sc->sc_ic;
struct ieee80211_channel *c;
uint8_t nchan;
for (nchan = 0, c = &ic->ic_channels[1];
c <= &ic->ic_channels[IEEE80211_CHAN_MAX] &&
nchan < sc->sc_capa_n_scan_channels;
c++) {
if (c->ic_flags == 0)
continue;
chan->channel_num = ieee80211_mhz2ieee(c->ic_freq, 0);
chan->iter_count = 1;
chan->iter_interval = htole16(0);
chan->flags = htole32(IWM_SCAN_CHANNEL_UMAC_NSSIDS(n_ssids));
chan++;
nchan++;
}
return nchan;
}
static int
iwm_fill_probe_req_v1(struct iwm_softc *sc, struct iwm_scan_probe_req_v1 *preq1)
{
struct iwm_scan_probe_req preq2;
int err, i;
err = iwm_fill_probe_req(sc, &preq2);
if (err)
return err;
preq1->mac_header = preq2.mac_header;
for (i=0; i<__arraycount(preq1->band_data); i++)
preq1->band_data[i] = preq2.band_data[i];
preq1->common_data = preq2.common_data;
memcpy(preq1->buf, preq2.buf, sizeof(preq1->buf));
return 0;
}
static int
iwm_fill_probe_req(struct iwm_softc *sc, struct iwm_scan_probe_req *preq)
{
struct ieee80211com *ic = &sc->sc_ic;
struct ieee80211_frame *wh = (struct ieee80211_frame *)preq->buf;
struct ieee80211_rateset *rs;
size_t remain = sizeof(preq->buf);
uint8_t *frm, *pos;
memset(preq, 0, sizeof(*preq));
KASSERT(ic->ic_des_esslen < sizeof(ic->ic_des_essid));
if (remain < sizeof(*wh) + 2 + ic->ic_des_esslen)
return ENOBUFS;
wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_MGT |
IEEE80211_FC0_SUBTYPE_PROBE_REQ;
wh->i_fc[1] = IEEE80211_FC1_DIR_NODS;
IEEE80211_ADDR_COPY(wh->i_addr1, etherbroadcastaddr);
IEEE80211_ADDR_COPY(wh->i_addr2, ic->ic_myaddr);
IEEE80211_ADDR_COPY(wh->i_addr3, etherbroadcastaddr);
*(uint16_t *)&wh->i_dur[0] = 0;
*(uint16_t *)&wh->i_seq[0] = 0;
frm = (uint8_t *)(wh + 1);
frm = ieee80211_add_ssid(frm, ic->ic_des_essid, ic->ic_des_esslen);
preq->mac_header.offset = 0;
preq->mac_header.len = htole16(frm - (uint8_t *)wh);
remain -= frm - (uint8_t *)wh;
rs = &ic->ic_sup_rates[IEEE80211_MODE_11G];
if (rs->rs_nrates > IEEE80211_RATE_SIZE) {
if (remain < 4 + rs->rs_nrates)
return ENOBUFS;
} else if (remain < 2 + rs->rs_nrates)
return ENOBUFS;
preq->band_data[0].offset = htole16(frm - (uint8_t *)wh);
pos = frm;
frm = ieee80211_add_rates(frm, rs);
if (rs->rs_nrates > IEEE80211_RATE_SIZE)
frm = ieee80211_add_xrates(frm, rs);
preq->band_data[0].len = htole16(frm - pos);
remain -= frm - pos;
if (isset(sc->sc_enabled_capa,
IWM_UCODE_TLV_CAPA_DS_PARAM_SET_IE_SUPPORT)) {
if (remain < 3)
return ENOBUFS;
*frm++ = IEEE80211_ELEMID_DSPARMS;
*frm++ = 1;
*frm++ = 0;
remain -= 3;
}
if (sc->sc_nvm.sku_cap_band_52GHz_enable) {
rs = &ic->ic_sup_rates[IEEE80211_MODE_11A];
if (rs->rs_nrates > IEEE80211_RATE_SIZE) {
if (remain < 4 + rs->rs_nrates)
return ENOBUFS;
} else if (remain < 2 + rs->rs_nrates)
return ENOBUFS;
preq->band_data[1].offset = htole16(frm - (uint8_t *)wh);
pos = frm;
frm = ieee80211_add_rates(frm, rs);
if (rs->rs_nrates > IEEE80211_RATE_SIZE)
frm = ieee80211_add_xrates(frm, rs);
preq->band_data[1].len = htole16(frm - pos);
remain -= frm - pos;
}
#ifndef IEEE80211_NO_HT
preq->common_data.offset = htole16(frm - (uint8_t *)wh);
pos = frm;
if (ic->ic_flags & IEEE80211_F_HTON) {
if (remain < 28)
return ENOBUFS;
frm = ieee80211_add_htcaps(frm, ic);
}
#endif
preq->common_data.len = htole16(frm - pos);
return 0;
}
static int
iwm_lmac_scan(struct iwm_softc *sc)
{
struct ieee80211com *ic = &sc->sc_ic;
struct iwm_host_cmd hcmd = {
.id = IWM_SCAN_OFFLOAD_REQUEST_CMD,
.len = { 0, },
.data = { NULL, },
.flags = 0,
};
struct iwm_scan_req_lmac *req;
size_t req_len;
int err;
DPRINTF(("%s: %s\n", DEVNAME(sc), __func__));
req_len = sizeof(struct iwm_scan_req_lmac) +
(sizeof(struct iwm_scan_channel_cfg_lmac) *
sc->sc_capa_n_scan_channels) + sizeof(struct iwm_scan_probe_req_v1);
if (req_len > IWM_MAX_CMD_PAYLOAD_SIZE)
return ENOMEM;
req = kmem_zalloc(req_len, KM_SLEEP);
hcmd.len[0] = (uint16_t)req_len;
hcmd.data[0] = (void *)req;
req->active_dwell = 10;
req->passive_dwell = 110;
req->fragmented_dwell = 44;
req->extended_dwell = 90;
req->max_out_time = 0;
req->suspend_time = 0;
req->scan_prio = htole32(IWM_SCAN_PRIORITY_HIGH);
req->rx_chain_select = iwm_scan_rx_chain(sc);
req->iter_num = htole32(1);
req->delay = 0;
req->scan_flags = htole32(IWM_LMAC_SCAN_FLAG_PASS_ALL |
IWM_LMAC_SCAN_FLAG_ITER_COMPLETE |
IWM_LMAC_SCAN_FLAG_EXTENDED_DWELL);
if (ic->ic_des_esslen == 0)
req->scan_flags |= htole32(IWM_LMAC_SCAN_FLAG_PASSIVE);
else
req->scan_flags |= htole32(IWM_LMAC_SCAN_FLAG_PRE_CONNECTION);
if (isset(sc->sc_enabled_capa,
IWM_UCODE_TLV_CAPA_DS_PARAM_SET_IE_SUPPORT))
req->scan_flags |= htole32(IWM_LMAC_SCAN_FLAGS_RRM_ENABLED);
req->flags = htole32(IWM_PHY_BAND_24);
if (sc->sc_nvm.sku_cap_band_52GHz_enable)
req->flags |= htole32(IWM_PHY_BAND_5);
req->filter_flags =
htole32(IWM_MAC_FILTER_ACCEPT_GRP | IWM_MAC_FILTER_IN_BEACON);
req->tx_cmd[0].tx_flags = htole32(IWM_TX_CMD_FLG_SEQ_CTL |
IWM_TX_CMD_FLG_BT_DIS);
req->tx_cmd[0].rate_n_flags =
iwm_scan_rate_n_flags(sc, IEEE80211_CHAN_2GHZ, 1);
req->tx_cmd[0].sta_id = IWM_AUX_STA_ID;
req->tx_cmd[1].tx_flags = htole32(IWM_TX_CMD_FLG_SEQ_CTL |
IWM_TX_CMD_FLG_BT_DIS);
req->tx_cmd[1].rate_n_flags =
iwm_scan_rate_n_flags(sc, IEEE80211_CHAN_5GHZ, 1);
req->tx_cmd[1].sta_id = IWM_AUX_STA_ID;
if (ic->ic_des_esslen != 0) {
req->direct_scan[0].id = IEEE80211_ELEMID_SSID;
req->direct_scan[0].len = ic->ic_des_esslen;
memcpy(req->direct_scan[0].ssid, ic->ic_des_essid,
ic->ic_des_esslen);
}
req->n_channels = iwm_lmac_scan_fill_channels(sc,
(struct iwm_scan_channel_cfg_lmac *)req->data,
ic->ic_des_esslen != 0);
err = iwm_fill_probe_req_v1(sc,
(struct iwm_scan_probe_req_v1 *)(req->data +
(sizeof(struct iwm_scan_channel_cfg_lmac) *
sc->sc_capa_n_scan_channels)));
if (err) {
kmem_free(req, req_len);
return err;
}
req->schedule[0].iterations = 1;
req->schedule[0].full_scan_mul = 1;
req->channel_opt[0].non_ebs_ratio = 1;
req->channel_opt[1].non_ebs_ratio = 1;
err = iwm_send_cmd(sc, &hcmd);
kmem_free(req, req_len);
return err;
}
static int
iwm_config_umac_scan(struct iwm_softc *sc)
{
struct ieee80211com *ic = &sc->sc_ic;
struct iwm_scan_config *scan_config;
int err, nchan;
size_t cmd_size;
struct ieee80211_channel *c;
struct iwm_host_cmd hcmd = {
.id = iwm_cmd_id(IWM_SCAN_CFG_CMD, IWM_LONG_GROUP, 0),
.flags = 0,
};
static const uint32_t rates = (IWM_SCAN_CONFIG_RATE_1M |
IWM_SCAN_CONFIG_RATE_2M | IWM_SCAN_CONFIG_RATE_5M |
IWM_SCAN_CONFIG_RATE_11M | IWM_SCAN_CONFIG_RATE_6M |
IWM_SCAN_CONFIG_RATE_9M | IWM_SCAN_CONFIG_RATE_12M |
IWM_SCAN_CONFIG_RATE_18M | IWM_SCAN_CONFIG_RATE_24M |
IWM_SCAN_CONFIG_RATE_36M | IWM_SCAN_CONFIG_RATE_48M |
IWM_SCAN_CONFIG_RATE_54M);
cmd_size = sizeof(*scan_config) + sc->sc_capa_n_scan_channels;
scan_config = kmem_zalloc(cmd_size, KM_SLEEP);
scan_config->tx_chains = htole32(iwm_fw_valid_tx_ant(sc));
scan_config->rx_chains = htole32(iwm_fw_valid_rx_ant(sc));
scan_config->legacy_rates = htole32(rates |
IWM_SCAN_CONFIG_SUPPORTED_RATE(rates));
scan_config->dwell_active = 10;
scan_config->dwell_passive = 110;
scan_config->dwell_fragmented = 44;
scan_config->dwell_extended = 90;
scan_config->out_of_channel_time = htole32(0);
scan_config->suspend_time = htole32(0);
IEEE80211_ADDR_COPY(scan_config->mac_addr, sc->sc_ic.ic_myaddr);
scan_config->bcast_sta_id = IWM_AUX_STA_ID;
#if 1
scan_config->channel_flags = IWM_CHANNEL_FLAG_EBS |
IWM_CHANNEL_FLAG_ACCURATE_EBS | IWM_CHANNEL_FLAG_EBS_ADD |
IWM_CHANNEL_FLAG_PRE_SCAN_PASSIVE2ACTIVE;
#else
scan_config->channel_flags = 0;
#endif
for (c = &ic->ic_channels[1], nchan = 0;
c <= &ic->ic_channels[IEEE80211_CHAN_MAX] &&
nchan < sc->sc_capa_n_scan_channels; c++) {
if (c->ic_flags == 0)
continue;
scan_config->channel_array[nchan++] =
ieee80211_mhz2ieee(c->ic_freq, 0);
}
scan_config->flags = htole32(IWM_SCAN_CONFIG_FLAG_ACTIVATE |
IWM_SCAN_CONFIG_FLAG_ALLOW_CHUB_REQS |
IWM_SCAN_CONFIG_FLAG_SET_TX_CHAINS |
IWM_SCAN_CONFIG_FLAG_SET_RX_CHAINS |
IWM_SCAN_CONFIG_FLAG_SET_AUX_STA_ID |
IWM_SCAN_CONFIG_FLAG_SET_ALL_TIMES |
IWM_SCAN_CONFIG_FLAG_SET_LEGACY_RATES |
IWM_SCAN_CONFIG_FLAG_SET_MAC_ADDR |
IWM_SCAN_CONFIG_FLAG_SET_CHANNEL_FLAGS|
IWM_SCAN_CONFIG_N_CHANNELS(nchan) |
IWM_SCAN_CONFIG_FLAG_CLEAR_FRAGMENTED);
hcmd.data[0] = scan_config;
hcmd.len[0] = cmd_size;
err = iwm_send_cmd(sc, &hcmd);
kmem_free(scan_config, cmd_size);
return err;
}
static int
iwm_umac_scan_size(struct iwm_softc *sc)
{
int base_size = IWM_SCAN_REQ_UMAC_SIZE_V1;
int tail_size;
if (isset(sc->sc_ucode_api, IWM_UCODE_TLV_API_ADAPTIVE_DWELL_V2))
base_size = IWM_SCAN_REQ_UMAC_SIZE_V8;
else if (isset(sc->sc_ucode_api, IWM_UCODE_TLV_API_ADAPTIVE_DWELL))
base_size = IWM_SCAN_REQ_UMAC_SIZE_V7;
if (isset(sc->sc_ucode_api, IWM_UCODE_TLV_API_SCAN_EXT_CHAN_VER))
tail_size = sizeof(struct iwm_scan_req_umac_tail_v2);
else
tail_size = sizeof(struct iwm_scan_req_umac_tail_v1);
return base_size + sizeof(struct iwm_scan_channel_cfg_umac) *
sc->sc_capa_n_scan_channels + tail_size;
}
static struct iwm_scan_umac_chan_param *
iwm_get_scan_req_umac_chan_param(struct iwm_softc *sc,
struct iwm_scan_req_umac *req)
{
if (isset(sc->sc_ucode_api, IWM_UCODE_TLV_API_ADAPTIVE_DWELL_V2))
return &req->v8.channel;
if (isset(sc->sc_ucode_api, IWM_UCODE_TLV_API_ADAPTIVE_DWELL))
return &req->v7.channel;
return &req->v1.channel;
}
static void *
iwm_get_scan_req_umac_data(struct iwm_softc *sc, struct iwm_scan_req_umac *req)
{
if (isset(sc->sc_ucode_api, IWM_UCODE_TLV_API_ADAPTIVE_DWELL_V2))
return (void *)&req->v8.data;
if (isset(sc->sc_ucode_api, IWM_UCODE_TLV_API_ADAPTIVE_DWELL))
return (void *)&req->v7.data;
return (void *)&req->v1.data;
}
#define IWM_SCAN_ADWELL_MAX_BUDGET_FULL_SCAN 300
#define IWM_SCAN_ADWELL_MAX_BUDGET_DIRECTED_SCAN 100
#define IWM_SCAN_ADWELL_DEFAULT_HB_N_APS 8
#define IWM_SCAN_ADWELL_DEFAULT_LB_N_APS 2
#define IWM_SCAN_ADWELL_DEFAULT_N_APS_SOCIAL 10
static int
iwm_umac_scan(struct iwm_softc *sc)
{
struct ieee80211com *ic = &sc->sc_ic;
struct iwm_host_cmd hcmd = {
.id = iwm_cmd_id(IWM_SCAN_REQ_UMAC, IWM_LONG_GROUP, 0),
.len = { 0, },
.data = { NULL, },
.flags = 0,
};
struct iwm_scan_req_umac *req;
void *cmd_data, *tail_data;
struct iwm_scan_req_umac_tail_v2 *tail;
struct iwm_scan_req_umac_tail_v1 *tailv1;
struct iwm_scan_umac_chan_param *chanparam;
size_t req_len;
int err;
DPRINTF(("%s: %s\n", DEVNAME(sc), __func__));
req_len = iwm_umac_scan_size(sc);
if (req_len < IWM_SCAN_REQ_UMAC_SIZE_V1 + sizeof(*tailv1) ||
req_len > IWM_MAX_CMD_PAYLOAD_SIZE)
return ERANGE;
req = kmem_zalloc(req_len, KM_SLEEP);
hcmd.len[0] = (uint16_t)req_len;
hcmd.data[0] = (void *)req;
if (isset(sc->sc_ucode_api, IWM_UCODE_TLV_API_ADAPTIVE_DWELL)) {
req->v7.adwell_default_n_aps_social =
IWM_SCAN_ADWELL_DEFAULT_N_APS_SOCIAL;
req->v7.adwell_default_n_aps =
IWM_SCAN_ADWELL_DEFAULT_LB_N_APS;
if (ic->ic_des_esslen != 0)
req->v7.adwell_max_budget =
htole16(IWM_SCAN_ADWELL_MAX_BUDGET_DIRECTED_SCAN);
else
req->v7.adwell_max_budget =
htole16(IWM_SCAN_ADWELL_MAX_BUDGET_FULL_SCAN);
req->v7.scan_priority = htole32(IWM_SCAN_PRIORITY_HIGH);
req->v7.max_out_time[IWM_SCAN_LB_LMAC_IDX] = 0;
req->v7.suspend_time[IWM_SCAN_LB_LMAC_IDX] = 0;
if (isset(sc->sc_ucode_api, IWM_UCODE_TLV_API_ADAPTIVE_DWELL_V2)) {
req->v8.active_dwell[IWM_SCAN_LB_LMAC_IDX] = 10;
req->v8.passive_dwell[IWM_SCAN_LB_LMAC_IDX] = 110;
} else {
req->v7.active_dwell = 10;
req->v7.passive_dwell = 110;
req->v7.fragmented_dwell = 44;
}
} else {
req->v1.active_dwell = 10;
req->v1.passive_dwell = 110;
req->v1.fragmented_dwell = 44;
req->v1.extended_dwell = 90;
req->v1.scan_priority = htole32(IWM_SCAN_PRIORITY_HIGH);
}
req->ooc_priority = htole32(IWM_SCAN_PRIORITY_HIGH);
cmd_data = iwm_get_scan_req_umac_data(sc, req);
chanparam = iwm_get_scan_req_umac_chan_param(sc, req);
chanparam->count = iwm_umac_scan_fill_channels(sc,
(struct iwm_scan_channel_cfg_umac *)cmd_data,
ic->ic_des_esslen != 0);
chanparam->flags = 0;
tail_data = (char *)cmd_data
+ sizeof(struct iwm_scan_channel_cfg_umac) * sc->sc_capa_n_scan_channels;
tail = (struct iwm_scan_req_umac_tail_v2 *)tail_data;
tailv1 = (struct iwm_scan_req_umac_tail_v1 *)tail_data;
req->general_flags = htole32(IWM_UMAC_SCAN_GEN_FLAGS_PASS_ALL |
IWM_UMAC_SCAN_GEN_FLAGS_ITER_COMPLETE |
IWM_UMAC_SCAN_GEN_FLAGS_EXTENDED_DWELL);
if (ic->ic_des_esslen != 0) {
tailv1->direct_scan[0].id = IEEE80211_ELEMID_SSID;
tailv1->direct_scan[0].len = ic->ic_des_esslen;
memcpy(tailv1->direct_scan[0].ssid, ic->ic_des_essid,
ic->ic_des_esslen);
req->general_flags |=
htole32(IWM_UMAC_SCAN_GEN_FLAGS_PRE_CONNECT);
} else
req->general_flags |= htole32(IWM_UMAC_SCAN_GEN_FLAGS_PASSIVE);
if (isset(sc->sc_enabled_capa,
IWM_UCODE_TLV_CAPA_DS_PARAM_SET_IE_SUPPORT))
req->general_flags |=
htole32(IWM_UMAC_SCAN_GEN_FLAGS_RRM_ENABLED);
if (isset(sc->sc_ucode_api, IWM_UCODE_TLV_API_SCAN_EXT_CHAN_VER))
err = iwm_fill_probe_req(sc, &tail->preq);
else
err = iwm_fill_probe_req_v1(sc, &tailv1->preq);
if (err) {
kmem_free(req, req_len);
return err;
}
tailv1->schedule[0].interval = 0;
tailv1->schedule[0].iter_count = 1;
err = iwm_send_cmd(sc, &hcmd);
kmem_free(req, req_len);
return err;
}
void
iwm_mcc_update(struct iwm_softc *sc, struct iwm_mcc_chub_notif *notif)
{
struct ieee80211com *ic = &sc->sc_ic;
struct ifnet *ifp = IC2IFP(ic);
char alpha2[3];
snprintf(alpha2, sizeof(alpha2), "%c%c",
(le16toh(notif->mcc) & 0xff00) >> 8, le16toh(notif->mcc) & 0xff);
if (ifp->if_flags & IFF_DEBUG) {
printf("%s: firmware has detected regulatory domain '%s' "
"(0x%x)\n", DEVNAME(sc), alpha2, le16toh(notif->mcc));
}
}
static uint8_t
iwm_ridx2rate(struct ieee80211_rateset *rs, int ridx)
{
int i;
uint8_t rval;
for (i = 0; i < rs->rs_nrates; i++) {
rval = (rs->rs_rates[i] & IEEE80211_RATE_VAL);
if (rval == iwm_rates[ridx].rate)
return rs->rs_rates[i];
}
return 0;
}
static void
iwm_ack_rates(struct iwm_softc *sc, struct iwm_node *in, int *cck_rates,
int *ofdm_rates)
{
struct ieee80211_node *ni = &in->in_ni;
struct ieee80211_rateset *rs = &ni->ni_rates;
int lowest_present_ofdm = -1;
int lowest_present_cck = -1;
uint8_t cck = 0;
uint8_t ofdm = 0;
int i;
if (ni->ni_chan == IEEE80211_CHAN_ANYC ||
IEEE80211_IS_CHAN_2GHZ(ni->ni_chan)) {
for (i = IWM_FIRST_CCK_RATE; i < IWM_FIRST_OFDM_RATE; i++) {
if ((iwm_ridx2rate(rs, i) & IEEE80211_RATE_BASIC) == 0)
continue;
cck |= (1 << i);
if (lowest_present_cck == -1 || lowest_present_cck > i)
lowest_present_cck = i;
}
}
for (i = IWM_FIRST_OFDM_RATE; i <= IWM_LAST_NON_HT_RATE; i++) {
if ((iwm_ridx2rate(rs, i) & IEEE80211_RATE_BASIC) == 0)
continue;
ofdm |= (1 << (i - IWM_FIRST_OFDM_RATE));
if (lowest_present_ofdm == -1 || lowest_present_ofdm > i)
lowest_present_ofdm = i;
}
if (IWM_RATE_24M_INDEX < lowest_present_ofdm)
ofdm |= IWM_RATE_BIT_MSK(24) >> IWM_FIRST_OFDM_RATE;
if (IWM_RATE_12M_INDEX < lowest_present_ofdm)
ofdm |= IWM_RATE_BIT_MSK(12) >> IWM_FIRST_OFDM_RATE;
ofdm |= IWM_RATE_BIT_MSK(6) >> IWM_FIRST_OFDM_RATE;
if (IWM_RATE_11M_INDEX < lowest_present_cck)
cck |= IWM_RATE_BIT_MSK(11) >> IWM_FIRST_CCK_RATE;
if (IWM_RATE_5M_INDEX < lowest_present_cck)
cck |= IWM_RATE_BIT_MSK(5) >> IWM_FIRST_CCK_RATE;
if (IWM_RATE_2M_INDEX < lowest_present_cck)
cck |= IWM_RATE_BIT_MSK(2) >> IWM_FIRST_CCK_RATE;
cck |= IWM_RATE_BIT_MSK(1) >> IWM_FIRST_CCK_RATE;
*cck_rates = cck;
*ofdm_rates = ofdm;
}
static void
iwm_mac_ctxt_cmd_common(struct iwm_softc *sc, struct iwm_node *in,
struct iwm_mac_ctx_cmd *cmd, uint32_t action, int assoc)
{
#define IWM_EXP2(x) ((1 << (x)) - 1)
struct ieee80211com *ic = &sc->sc_ic;
struct ieee80211_node *ni = ic->ic_bss;
int cck_ack_rates, ofdm_ack_rates;
int i;
cmd->id_and_color = htole32(IWM_FW_CMD_ID_AND_COLOR(in->in_id,
in->in_color));
cmd->action = htole32(action);
if (ic->ic_opmode == IEEE80211_M_MONITOR)
cmd->mac_type = htole32(IWM_FW_MAC_TYPE_LISTENER);
else if (ic->ic_opmode == IEEE80211_M_STA)
cmd->mac_type = htole32(IWM_FW_MAC_TYPE_BSS_STA);
else
panic("unsupported operating mode %d", ic->ic_opmode);
cmd->tsf_id = htole32(IWM_TSF_ID_A);
IEEE80211_ADDR_COPY(cmd->node_addr, ic->ic_myaddr);
if (ic->ic_opmode == IEEE80211_M_MONITOR) {
IEEE80211_ADDR_COPY(cmd->bssid_addr, etherbroadcastaddr);
return;
}
IEEE80211_ADDR_COPY(cmd->bssid_addr, in->in_macaddr);
iwm_ack_rates(sc, in, &cck_ack_rates, &ofdm_ack_rates);
cmd->cck_rates = htole32(cck_ack_rates);
cmd->ofdm_rates = htole32(ofdm_ack_rates);
cmd->cck_short_preamble
= htole32((ic->ic_flags & IEEE80211_F_SHPREAMBLE)
? IWM_MAC_FLG_SHORT_PREAMBLE : 0);
cmd->short_slot
= htole32((ic->ic_flags & IEEE80211_F_SHSLOT)
? IWM_MAC_FLG_SHORT_SLOT : 0);
for (i = 0; i < WME_NUM_AC; i++) {
struct wmeParams *wmep = &ic->ic_wme.wme_params[i];
int txf = iwm_ac_to_tx_fifo[i];
cmd->ac[txf].cw_min = htole16(IWM_EXP2(wmep->wmep_logcwmin));
cmd->ac[txf].cw_max = htole16(IWM_EXP2(wmep->wmep_logcwmax));
cmd->ac[txf].aifsn = wmep->wmep_aifsn;
cmd->ac[txf].fifos_mask = (1 << txf);
cmd->ac[txf].edca_txop = htole16(wmep->wmep_txopLimit * 32);
}
if (ni->ni_flags & IEEE80211_NODE_QOS)
cmd->qos_flags |= htole32(IWM_MAC_QOS_FLG_UPDATE_EDCA);
#ifndef IEEE80211_NO_HT
if (ni->ni_flags & IEEE80211_NODE_HT) {
enum ieee80211_htprot htprot =
(ni->ni_htop1 & IEEE80211_HTOP1_PROT_MASK);
switch (htprot) {
case IEEE80211_HTPROT_NONE:
break;
case IEEE80211_HTPROT_NONMEMBER:
case IEEE80211_HTPROT_NONHT_MIXED:
cmd->protection_flags |=
htole32(IWM_MAC_PROT_FLG_HT_PROT);
case IEEE80211_HTPROT_20MHZ:
cmd->protection_flags |=
htole32(IWM_MAC_PROT_FLG_HT_PROT |
IWM_MAC_PROT_FLG_FAT_PROT);
break;
default:
break;
}
cmd->qos_flags |= htole32(IWM_MAC_QOS_FLG_TGN);
}
#endif
if (ic->ic_flags & IEEE80211_F_USEPROT)
cmd->protection_flags |= htole32(IWM_MAC_PROT_FLG_TGG_PROTECT);
cmd->filter_flags = htole32(IWM_MAC_FILTER_ACCEPT_GRP);
#undef IWM_EXP2
}
static void
iwm_mac_ctxt_cmd_fill_sta(struct iwm_softc *sc, struct iwm_node *in,
struct iwm_mac_data_sta *sta, int assoc)
{
struct ieee80211_node *ni = &in->in_ni;
uint32_t dtim_off;
uint64_t tsf;
dtim_off = ni->ni_dtim_count * ni->ni_intval * IEEE80211_DUR_TU;
tsf = le64toh(ni->ni_tstamp.tsf);
sta->is_assoc = htole32(assoc);
sta->dtim_time = htole32(ni->ni_rstamp + dtim_off);
sta->dtim_tsf = htole64(tsf + dtim_off);
sta->bi = htole32(ni->ni_intval);
sta->bi_reciprocal = htole32(iwm_reciprocal(ni->ni_intval));
sta->dtim_interval = htole32(ni->ni_intval * ni->ni_dtim_period);
sta->dtim_reciprocal = htole32(iwm_reciprocal(sta->dtim_interval));
sta->listen_interval = htole32(10);
sta->assoc_id = htole32(ni->ni_associd);
sta->assoc_beacon_arrive_time = htole32(ni->ni_rstamp);
}
static int
iwm_mac_ctxt_cmd(struct iwm_softc *sc, struct iwm_node *in, uint32_t action,
int assoc)
{
struct ieee80211_node *ni = &in->in_ni;
struct iwm_mac_ctx_cmd cmd;
memset(&cmd, 0, sizeof(cmd));
iwm_mac_ctxt_cmd_common(sc, in, &cmd, action, assoc);
if (!assoc || !ni->ni_associd || !ni->ni_dtim_period)
cmd.filter_flags |= htole32(IWM_MAC_FILTER_IN_BEACON);
else
iwm_mac_ctxt_cmd_fill_sta(sc, in, &cmd.sta, assoc);
return iwm_send_cmd_pdu(sc, IWM_MAC_CONTEXT_CMD, 0, sizeof(cmd), &cmd);
}
#define IWM_MISSED_BEACONS_THRESHOLD 8
static void
iwm_rx_missed_beacons_notif(struct iwm_softc *sc,
struct iwm_missed_beacons_notif *mb)
{
int s;
DPRINTF(("missed bcn mac_id=%u, consecutive=%u (%u, %u, %u)\n",
le32toh(mb->mac_id),
le32toh(mb->consec_missed_beacons),
le32toh(mb->consec_missed_beacons_since_last_rx),
le32toh(mb->num_recvd_beacons),
le32toh(mb->num_expected_beacons)));
if (le32toh(mb->consec_missed_beacons_since_last_rx) >
IWM_MISSED_BEACONS_THRESHOLD) {
s = splnet();
ieee80211_beacon_miss(&sc->sc_ic);
splx(s);
}
}
static int
iwm_update_quotas(struct iwm_softc *sc, struct iwm_node *in)
{
struct iwm_time_quota_cmd cmd;
int i, idx, num_active_macs, quota, quota_rem;
int colors[IWM_MAX_BINDINGS] = { -1, -1, -1, -1, };
int n_ifs[IWM_MAX_BINDINGS] = {0, };
uint16_t id;
memset(&cmd, 0, sizeof(cmd));
if (in) {
id = in->in_phyctxt->id;
KASSERT(id < IWM_MAX_BINDINGS);
colors[id] = in->in_phyctxt->color;
if (1)
n_ifs[id] = 1;
}
num_active_macs = 0;
for (i = 0; i < IWM_MAX_BINDINGS; i++) {
cmd.quotas[i].id_and_color = htole32(IWM_FW_CTXT_INVALID);
num_active_macs += n_ifs[i];
}
quota = 0;
quota_rem = 0;
if (num_active_macs) {
quota = IWM_MAX_QUOTA / num_active_macs;
quota_rem = IWM_MAX_QUOTA % num_active_macs;
}
for (idx = 0, i = 0; i < IWM_MAX_BINDINGS; i++) {
if (colors[i] < 0)
continue;
cmd.quotas[idx].id_and_color =
htole32(IWM_FW_CMD_ID_AND_COLOR(i, colors[i]));
if (n_ifs[i] <= 0) {
cmd.quotas[idx].quota = htole32(0);
cmd.quotas[idx].max_duration = htole32(0);
} else {
cmd.quotas[idx].quota = htole32(quota * n_ifs[i]);
cmd.quotas[idx].max_duration = htole32(0);
}
idx++;
}
cmd.quotas[0].quota = htole32(le32toh(cmd.quotas[0].quota) + quota_rem);
return iwm_send_cmd_pdu(sc, IWM_TIME_QUOTA_CMD, 0, sizeof(cmd), &cmd);
}
static int
iwm_scan(struct iwm_softc *sc)
{
struct ieee80211com *ic = &sc->sc_ic;
int err;
#ifdef notyet
if (sc->sc_flags & IWM_FLAG_BGSCAN) {
err = iwm_scan_abort(sc);
if (err) {
device_printf(sc->sc_dev,
"could not abort background scan\n");
return err;
}
}
#endif
if (isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_UMAC_SCAN))
err = iwm_umac_scan(sc);
else
err = iwm_lmac_scan(sc);
if (err) {
device_printf(sc->sc_dev, "could not initiate scan (error = %d)\n", err);
return err;
}
if (IFM_SUBTYPE(ic->ic_media.ifm_cur->ifm_media) == IFM_AUTO)
ieee80211_setmode(ic, IEEE80211_MODE_AUTO);
return 0;
}
static int
iwm_phy_ctxt_update(struct iwm_softc *sc, struct iwm_phy_ctxt *phyctxt,
struct ieee80211_channel *chan, uint8_t chains_static,
uint8_t chains_dynamic, uint32_t apply_time)
{
uint16_t band_flags = (IEEE80211_CHAN_2GHZ | IEEE80211_CHAN_5GHZ);
int err;
if (isset(sc->sc_enabled_capa,
IWM_UCODE_TLV_CAPA_BINDING_CDB_SUPPORT) &&
(phyctxt->channel->ic_flags & band_flags) !=
(chan->ic_flags & band_flags)) {
err = iwm_phy_ctxt_cmd(sc, phyctxt, chains_static,
chains_dynamic, IWM_FW_CTXT_ACTION_REMOVE, apply_time);
if (err) {
device_printf(sc->sc_dev,
"could not remove PHY context (error %d)\n", err);
return err;
}
phyctxt->channel = chan;
err = iwm_phy_ctxt_cmd(sc, phyctxt, chains_static,
chains_dynamic, IWM_FW_CTXT_ACTION_ADD, apply_time);
if (err) {
device_printf(sc->sc_dev,
"could not add PHY context (error %d)\n", err);
return err;
}
} else {
phyctxt->channel = chan;
err = iwm_phy_ctxt_cmd(sc, phyctxt, chains_static,
chains_dynamic, IWM_FW_CTXT_ACTION_MODIFY, apply_time);
if (err) {
device_printf(sc->sc_dev,
"could not update PHY context (error %d)\n", err);
return err;
}
}
return 0;
}
static int
iwm_auth(struct iwm_softc *sc)
{
struct ieee80211com *ic = &sc->sc_ic;
struct iwm_node *in = (struct iwm_node *)ic->ic_bss;
uint32_t duration;
int generation = sc->sc_generation, err;
if (ic->ic_opmode == IEEE80211_M_MONITOR) {
err = iwm_phy_ctxt_update(sc, &sc->sc_phyctxt[0],
ic->ic_ibss_chan, 1, 1, 0);
if (err)
return err;
} else {
err = iwm_phy_ctxt_update(sc, &sc->sc_phyctxt[0],
in->in_ni.ni_chan, 1, 1, 0);
if (err)
return err;
}
in->in_phyctxt = &sc->sc_phyctxt[0];
IEEE80211_ADDR_COPY(in->in_macaddr, in->in_ni.ni_macaddr);
iwm_setrates(in);
err = iwm_mac_ctxt_cmd(sc, in, IWM_FW_CTXT_ACTION_ADD, 0);
if (err) {
aprint_error_dev(sc->sc_dev,
"could not add MAC context (error %d)\n", err);
return err;
}
sc->sc_flags |= IWM_FLAG_MAC_ACTIVE;
err = iwm_binding_cmd(sc, in, IWM_FW_CTXT_ACTION_ADD);
if (err) {
aprint_error_dev(sc->sc_dev,
"could not add binding (error %d)\n", err);
goto rm_mac_ctxt;
}
sc->sc_flags |= IWM_FLAG_BINDING_ACTIVE;
in->tid_disable_ampdu = 0xffff;
err = iwm_add_sta_cmd(sc, in, 0);
if (err) {
aprint_error_dev(sc->sc_dev,
"could not add sta (error %d)\n", err);
goto rm_binding;
}
sc->sc_flags |= IWM_FLAG_STA_ACTIVE;
if (ic->ic_opmode == IEEE80211_M_MONITOR)
return 0;
if (in->in_ni.ni_intval)
duration = in->in_ni.ni_intval * 5;
else
duration = IEEE80211_DUR_TU;
iwm_protect_session(sc, in, duration, in->in_ni.ni_intval / 2);
return 0;
rm_binding:
if (generation == sc->sc_generation) {
iwm_binding_cmd(sc, in, IWM_FW_CTXT_ACTION_REMOVE);
sc->sc_flags &= ~IWM_FLAG_BINDING_ACTIVE;
}
rm_mac_ctxt:
if (generation == sc->sc_generation) {
iwm_mac_ctxt_cmd(sc, in, IWM_FW_CTXT_ACTION_REMOVE, 0);
sc->sc_flags &= ~IWM_FLAG_MAC_ACTIVE;
}
return err;
}
#ifdef notyet
static int
iwm_deauth(struct iwm_softc *sc)
{
struct ieee80211com *ic = &sc->sc_ic;
struct iwm_node *in = (void *)ic->ic_bss;
int err;
splassert(IPL_NET);
iwm_unprotect_session(sc, in);
if (sc->sc_flags & IWM_FLAG_STA_ACTIVE) {
err = iwm_flush_sta(sc, in);
if (err)
return err;
err = iwm_rm_sta_cmd(sc, in);
if (err) {
printf("%s: could not remove STA (error %d)\n",
DEVNAME(sc), err);
return err;
}
in->tid_disable_ampdu = 0xffff;
sc->sc_flags &= ~IWM_FLAG_STA_ACTIVE;
sc->sc_rx_ba_sessions = 0;
sc->ba_rx.start_tidmask = 0;
sc->ba_rx.stop_tidmask = 0;
sc->tx_ba_queue_mask = 0;
sc->ba_tx.start_tidmask = 0;
sc->ba_tx.stop_tidmask = 0;
}
if (sc->sc_flags & IWM_FLAG_BINDING_ACTIVE) {
err = iwm_binding_cmd(sc, in, IWM_FW_CTXT_ACTION_REMOVE);
if (err) {
printf("%s: could not remove binding (error %d)\n",
DEVNAME(sc), err);
return err;
}
sc->sc_flags &= ~IWM_FLAG_BINDING_ACTIVE;
}
if (sc->sc_flags & IWM_FLAG_MAC_ACTIVE) {
err = iwm_mac_ctxt_cmd(sc, in, IWM_FW_CTXT_ACTION_REMOVE, 0);
if (err) {
printf("%s: could not remove MAC context (error %d)\n",
DEVNAME(sc), err);
return err;
}
sc->sc_flags &= ~IWM_FLAG_MAC_ACTIVE;
}
err = iwm_phy_ctxt_update(sc, &sc->sc_phyctxt[0],
&ic->ic_channels[1], 1, 1, 0);
if (err)
return err;
return 0;
}
#endif
static int
iwm_run(struct iwm_softc *sc)
{
struct ieee80211com *ic = &sc->sc_ic;
struct iwm_node *in = (void *)ic->ic_bss;
#ifndef IEEE80211_NO_HT
struct ieee80211_node *ni = &in->in_ni;
#endif
int err;
if (ic->ic_opmode == IEEE80211_M_MONITOR) {
err = iwm_auth(sc);
if (err)
return err;
}
if (ic->ic_opmode == IEEE80211_M_MONITOR) {
uint8_t chains = iwm_mimo_enabled(sc) ? 2 : 1;
err = iwm_phy_ctxt_update(sc, in->in_phyctxt,
in->in_phyctxt->channel, chains, chains, 0);
if (err) {
printf("%s: failed to update PHY\n", DEVNAME(sc));
return err;
}
}
#ifndef IEEE80211_NO_HT
else if (ni->ni_flags & IEEE80211_NODE_HT) {
uint8_t chains = iwm_mimo_enabled(sc) ? 2 : 1;
uint8_t sco, vht_chan_width;
if (IEEE80211_CHAN_40MHZ_ALLOWED(in->in_ni.ni_chan) &&
ieee80211_node_supports_ht_chan40(ni))
sco = (ni->ni_htop0 & IEEE80211_HTOP0_SCO_MASK);
else
sco = IEEE80211_HTOP0_SCO_SCN;
if ((ni->ni_flags & IEEE80211_NODE_VHT) &&
IEEE80211_CHAN_80MHZ_ALLOWED(in->in_ni.ni_chan) &&
ieee80211_node_supports_vht_chan80(ni))
vht_chan_width = IEEE80211_VHTOP0_CHAN_WIDTH_80;
else
vht_chan_width = IEEE80211_VHTOP0_CHAN_WIDTH_HT;
err = iwm_phy_ctxt_update(sc, in->in_phyctxt,
in->in_phyctxt->channel, chains, chains, 0);
if (err) {
printf("%s: failed to update PHY\n", DEVNAME(sc));
return err;
}
}
#endif
err = iwm_add_sta_cmd(sc, in, 1);
if (err) {
printf("%s: could not update STA (error %d)\n",
DEVNAME(sc), err);
return err;
}
err = iwm_mac_ctxt_cmd(sc, in, IWM_FW_CTXT_ACTION_MODIFY, 1);
if (err) {
printf("%s: failed to update MAC\n", DEVNAME(sc));
return err;
}
iwm_unprotect_session(sc, in);
err = iwm_sf_config(sc, IWM_SF_FULL_ON);
if (err) {
printf("%s: could not set sf full on (error %d)\n",
DEVNAME(sc), err);
return err;
}
err = iwm_allow_mcast(sc);
if (err) {
printf("%s: could not allow mcast (error %d)\n",
DEVNAME(sc), err);
return err;
}
err = iwm_power_update_device(sc);
if (err) {
printf("%s: could not send power command (error %d)\n",
DEVNAME(sc), err);
return err;
}
#ifdef notyet
err = iwm_enable_beacon_filter(sc, in);
if (err) {
printf("%s: could not enable beacon filter\n",
DEVNAME(sc));
return err;
}
#endif
err = iwm_power_mac_update_mode(sc, in);
if (err) {
printf("%s: could not update MAC power (error %d)\n",
DEVNAME(sc), err);
return err;
}
if (!isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_DYNAMIC_QUOTA)) {
err = iwm_update_quotas(sc, in);
if (err) {
printf("%s: could not update quotas (error %d)\n",
DEVNAME(sc), err);
return err;
}
}
ieee80211_amrr_node_init(&sc->sc_amrr, &in->in_amn);
#ifndef IEEE80211_NO_HT
ieee80211_ra_node_init(&in->in_rn);
ieee80211_ra_vht_node_init(&in->in_rn_vht);
#endif
if (ic->ic_opmode == IEEE80211_M_MONITOR) {
iwm_led_blink_start(sc);
return 0;
}
in->in_ni.ni_txrate = 0;
#ifndef IEEE80211_NO_HT
in->in_ni.ni_txmcs = 0;
in->in_ni.ni_vht_ss = 1;
#endif
iwm_setrates(in);
callout_schedule(&sc->sc_calib_to, mstohz(500));
iwm_led_enable(sc);
return 0;
}
#ifdef notyet
static int
iwm_run_stop(struct iwm_softc *sc)
{
struct ieee80211com *ic = &sc->sc_ic;
struct iwm_node *in = (void *)ic->ic_bss;
#ifndef IEEE80211_NO_HT
struct ieee80211_node *ni = &in->in_ni;
int i, tid;
#endif
int err;
#ifndef IEEE80211_NO_HT
for (i = 0; i < __arraycount(sc->sc_rxba_data); i++) {
struct iwm_rxba_data *rxba = &sc->sc_rxba_data[i];
if (rxba->baid == IWM_RX_REORDER_DATA_INVALID_BAID)
continue;
err = iwm_sta_rx_agg(sc, ni, rxba->tid, 0, 0, 0, 0);
if (err)
return err;
iwm_clear_reorder_buffer(sc, rxba);
if (sc->sc_rx_ba_sessions > 0)
sc->sc_rx_ba_sessions--;
}
for (tid = 0; tid < IWM_MAX_TID_COUNT; tid++) {
int qid = IWM_FIRST_AGG_TX_QUEUE + tid;
if ((sc->tx_ba_queue_mask & (1 << qid)) == 0)
continue;
err = iwm_sta_tx_agg(sc, ni, tid, 0, 0, 0);
if (err)
return err;
err = iwm_disable_txq(sc, IWM_STATION_ID, qid, tid);
if (err)
return err;
in->tfd_queue_msk &= ~(1 << qid);
}
ieee80211_ba_del(ni);
#endif
if (ic->ic_opmode == IEEE80211_M_MONITOR)
iwm_led_blink_stop(sc);
err = iwm_sf_config(sc, IWM_SF_INIT_OFF);
if (err)
return err;
iwm_disable_beacon_filter(sc);
if (!isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_DYNAMIC_QUOTA)) {
err = iwm_update_quotas(sc, in);
if (err) {
printf("%s: could not update quotas (error %d)\n",
DEVNAME(sc), err);
return err;
}
}
err = iwm_mac_ctxt_cmd(sc, in, IWM_FW_CTXT_ACTION_MODIFY, 0);
if (err) {
printf("%s: failed to update MAC\n", DEVNAME(sc));
return err;
}
#ifndef IEEE80211_NO_HT
if (in->in_ni.ni_flags & IEEE80211_NODE_HT) {
err = iwm_phy_ctxt_update(sc, in->in_phyctxt,
in->in_phyctxt->channel, 1, 1, 0);
if (err) {
printf("%s: failed to update PHY\n", DEVNAME(sc));
return err;
}
}
#endif
return 0;
}
#endif
static int
iwm_assoc(struct iwm_softc *sc)
{
#if 0
struct ieee80211com *ic = &sc->sc_ic;
struct iwm_node *in = (struct iwm_node *)ic->ic_bss;
int err;
err = iwm_add_sta_cmd(sc, in, 1);
if (err)
return err;
#endif
return 0;
}
static struct ieee80211_node *
iwm_node_alloc(struct ieee80211_node_table *nt)
{
return malloc(sizeof(struct iwm_node), M_80211_NODE, M_NOWAIT | M_ZERO);
}
static void
iwm_calib_timeout(void *arg)
{
struct iwm_softc *sc = arg;
struct ieee80211com *ic = &sc->sc_ic;
struct iwm_node *in = (struct iwm_node *)ic->ic_bss;
struct ieee80211_node *ni = &in->in_ni;
int otxrate;
int s;
s = splnet();
if ((ic->ic_fixed_rate == -1
#ifndef IEEE80211_NO_HT
|| ic->ic_fixed_mcs == -1
#endif
) &&
ic->ic_opmode == IEEE80211_M_STA && ic->ic_bss) {
#ifndef IEEE80211_NO_HT
if (ni->ni_flags & IEEE80211_NODE_HT)
otxrate = ni->ni_txmcs;
else
#endif
otxrate = ni->ni_txrate;
ieee80211_amrr_choose(&sc->sc_amrr, &in->in_ni, &in->in_amn);
#ifndef IEEE80211_NO_HT
if ((ni->ni_flags & IEEE80211_NODE_HT) &&
otxrate != ni->ni_txmcs)
iwm_setrates_task(sc);
else
#endif
if (otxrate != ni->ni_txrate)
iwm_setrates_task(sc);
}
splx(s);
callout_schedule(&sc->sc_calib_to, mstohz(500));
}
static int
iwm_setrates_task(struct iwm_softc *sc)
{
struct work *wk;
wk = kmem_intr_alloc(sizeof(*wk), KM_NOSLEEP);
if (!wk) {
DPRINTF(("%s: allocating setrates cb mem failed\n", DEVNAME(sc)));
return ENOMEM;
}
workqueue_enqueue(sc->sc_setratewq, wk, NULL);
return 0;
}
static void
iwm_setrates_cb(struct work *wk, void *arg)
{
struct iwm_softc *sc = arg;
struct ieee80211com *ic = &sc->sc_ic;
struct iwm_node *in = (struct iwm_node *)ic->ic_bss;
int s;
kmem_intr_free(wk, sizeof(*wk));
s = splnet();
iwm_setrates(in);
splx(s);
}
static int
iwm_setrates(struct iwm_node *in)
{
struct ieee80211_node *ni = &in->in_ni;
struct ieee80211com *ic = ni->ni_ic;
struct iwm_softc *sc = IC2IFP(ic)->if_softc;
struct iwm_lq_cmd *lq = &in->in_lq;
struct ieee80211_rateset *rs = &ni->ni_rates;
int i, j, ridx, ridx_min, tab = 0;
#ifndef IEEE80211_NO_HT
int sgi_ok;
#endif
struct iwm_host_cmd cmd = {
.id = IWM_LQ_CMD,
.len = { sizeof(in->in_lq), },
};
memset(lq, 0, sizeof(*lq));
lq->sta_id = IWM_STATION_ID;
if (ic->ic_flags & IEEE80211_F_USEPROT)
lq->flags |= IWM_LQ_FLAG_USE_RTS_MSK;
#ifndef IEEE80211_NO_HT
sgi_ok = ((ni->ni_flags & IEEE80211_NODE_HT) &&
(ni->ni_htcaps & IEEE80211_HTCAP_SGI20));
#endif
j = 0;
ridx_min = (IEEE80211_IS_CHAN_5GHZ(ni->ni_chan)) ?
IWM_RIDX_OFDM : IWM_RIDX_CCK;
for (ridx = IWM_RIDX_MAX; ridx >= ridx_min; ridx--) {
if (j >= __arraycount(lq->rs_table))
break;
tab = 0;
#ifndef IEEE80211_NO_HT
if ((ni->ni_flags & IEEE80211_NODE_HT) &&
iwm_rates[ridx].ht_plcp != IWM_RATE_HT_SISO_MCS_INV_PLCP) {
for (i = ni->ni_txmcs; i >= 0; i--) {
if (isclr(ni->ni_rxmcs, i))
continue;
if (ridx == iwm_mcs2ridx[i]) {
tab = iwm_rates[ridx].ht_plcp;
tab |= IWM_RATE_MCS_HT_MSK;
if (sgi_ok)
tab |= IWM_RATE_MCS_SGI_MSK;
break;
}
}
}
#endif
if (tab == 0 && iwm_rates[ridx].plcp != IWM_RATE_INVM_PLCP) {
for (i = ni->ni_txrate; i >= 0; i--) {
if (iwm_rates[ridx].rate == (rs->rs_rates[i] &
IEEE80211_RATE_VAL)) {
tab = iwm_rates[ridx].plcp;
break;
}
}
}
if (tab == 0)
continue;
tab |= 1 << IWM_RATE_MCS_ANT_POS;
if (IWM_RIDX_IS_CCK(ridx))
tab |= IWM_RATE_MCS_CCK_MSK;
DPRINTFN(2, ("station rate %d %x\n", i, tab));
lq->rs_table[j++] = htole32(tab);
}
i = j > 0 ? j - 1 : 0;
while (j < __arraycount(lq->rs_table))
lq->rs_table[j++] = lq->rs_table[i];
lq->single_stream_ant_msk = IWM_ANT_A;
lq->dual_stream_ant_msk = IWM_ANT_AB;
lq->agg_time_limit = htole16(4000);
lq->agg_disable_start_th = 3;
#ifdef notyet
lq->agg_frame_cnt_limit = 0x3f;
#else
lq->agg_frame_cnt_limit = 1;
#endif
cmd.data[0] = &in->in_lq;
return iwm_send_cmd(sc, &cmd);
}
static int
iwm_media_change(struct ifnet *ifp)
{
struct iwm_softc *sc = ifp->if_softc;
struct ieee80211com *ic = &sc->sc_ic;
uint8_t rate, ridx;
int err;
err = ieee80211_media_change(ifp);
if (err != ENETRESET)
return err;
#ifndef IEEE80211_NO_HT
if (ic->ic_fixed_mcs != -1)
sc->sc_fixed_ridx = iwm_mcs2ridx[ic->ic_fixed_mcs];
else
#endif
if (ic->ic_fixed_rate != -1) {
rate = ic->ic_sup_rates[ic->ic_curmode].
rs_rates[ic->ic_fixed_rate] & IEEE80211_RATE_VAL;
for (ridx = 0; ridx <= IWM_RIDX_MAX; ridx++)
if (iwm_rates[ridx].rate == rate)
break;
sc->sc_fixed_ridx = ridx;
}
if ((ifp->if_flags & (IFF_UP | IFF_RUNNING)) ==
(IFF_UP | IFF_RUNNING)) {
iwm_stop(ifp, 0);
err = iwm_init(ifp);
}
return err;
}
static int
iwm_do_newstate(struct ieee80211com *ic, enum ieee80211_state nstate, int arg)
{
struct ifnet *ifp = IC2IFP(ic);
struct iwm_softc *sc = ifp->if_softc;
enum ieee80211_state ostate = ic->ic_state;
int err;
DPRINTF(("switching state %s->%s\n", ieee80211_state_name[ostate],
ieee80211_state_name[nstate]));
if (ostate == IEEE80211_S_SCAN && nstate != ostate)
iwm_led_blink_stop(sc);
if (ostate == IEEE80211_S_RUN && nstate != ostate)
iwm_disable_beacon_filter(sc);
if (ostate > IEEE80211_S_SCAN && nstate < ostate) {
if (nstate != IEEE80211_S_INIT) {
DPRINTF(("Force transition to INIT; MGT=%d\n", arg));
sc->sc_newstate(ic, IEEE80211_S_INIT, -1);
iwm_stop(ifp, 1);
iwm_init(ifp);
return 0;
}
}
switch (nstate) {
case IEEE80211_S_INIT:
break;
case IEEE80211_S_SCAN:
if (!ISSET(sc->sc_flags, IWM_FLAG_SCANNING)) {
err = iwm_scan(sc);
if (err)
return err;
SET(sc->sc_flags, IWM_FLAG_SCANNING);
}
iwm_led_blink_start(sc);
ic->ic_state = nstate;
return 0;
case IEEE80211_S_AUTH:
err = iwm_auth(sc);
if (err) {
DPRINTF(("%s: could not move to auth state: %d\n",
DEVNAME(sc), err));
return err;
}
break;
case IEEE80211_S_ASSOC:
err = iwm_assoc(sc);
if (err) {
DPRINTF(("%s: failed to associate: %d\n", DEVNAME(sc),
err));
return err;
}
break;
case IEEE80211_S_RUN:
err = iwm_run(sc);
if (err) {
DPRINTF(("%s: failed to run: %d\n", DEVNAME(sc),
err));
return err;
}
break;
default:
break;
}
return sc->sc_newstate(ic, nstate, arg);
}
static void
iwm_newstate_cb(struct work *wk, void *v)
{
struct iwm_softc *sc = v;
struct ieee80211com *ic = &sc->sc_ic;
struct iwm_newstate_state *iwmns = (struct iwm_newstate_state *)wk;
enum ieee80211_state nstate = iwmns->ns_nstate;
int generation = iwmns->ns_generation;
int arg = iwmns->ns_arg;
kmem_intr_free(iwmns, sizeof(*iwmns));
if (ISSET(sc->sc_flags, IWM_FLAG_STOPPED))
return;
DPRINTF(("Prepare to switch state %d->%d\n", ic->ic_state, nstate));
if (sc->sc_generation != generation) {
DPRINTF(("newstate_cb: someone pulled the plug meanwhile\n"));
if (nstate == IEEE80211_S_INIT) {
DPRINTF(("newstate_cb: nstate == IEEE80211_S_INIT: "
"calling sc_newstate()\n"));
(void) sc->sc_newstate(ic, nstate, arg);
}
} else
(void) iwm_do_newstate(ic, nstate, arg);
}
static int
iwm_newstate(struct ieee80211com *ic, enum ieee80211_state nstate, int arg)
{
struct iwm_newstate_state *iwmns;
struct ifnet *ifp = IC2IFP(ic);
struct iwm_softc *sc = ifp->if_softc;
callout_stop(&sc->sc_calib_to);
iwmns = kmem_intr_alloc(sizeof(*iwmns), KM_NOSLEEP);
if (!iwmns) {
DPRINTF(("%s: allocating state cb mem failed\n", DEVNAME(sc)));
return ENOMEM;
}
iwmns->ns_nstate = nstate;
iwmns->ns_arg = arg;
iwmns->ns_generation = sc->sc_generation;
workqueue_enqueue(sc->sc_nswq, &iwmns->ns_wk, NULL);
return 0;
}
static void
iwm_endscan(struct iwm_softc *sc)
{
struct ieee80211com *ic = &sc->sc_ic;
int s;
DPRINTF(("%s: scan ended\n", DEVNAME(sc)));
s = splnet();
if (ic->ic_state == IEEE80211_S_SCAN)
ieee80211_end_scan(ic);
CLR(sc->sc_flags, IWM_FLAG_SCANNING);
splx(s);
}
static const uint32_t
iwm_sf_full_timeout_def[IWM_SF_NUM_SCENARIO][IWM_SF_NUM_TIMEOUT_TYPES] = {
{
htole32(IWM_SF_SINGLE_UNICAST_AGING_TIMER_DEF),
htole32(IWM_SF_SINGLE_UNICAST_IDLE_TIMER_DEF)
},
{
htole32(IWM_SF_AGG_UNICAST_AGING_TIMER_DEF),
htole32(IWM_SF_AGG_UNICAST_IDLE_TIMER_DEF)
},
{
htole32(IWM_SF_MCAST_AGING_TIMER_DEF),
htole32(IWM_SF_MCAST_IDLE_TIMER_DEF)
},
{
htole32(IWM_SF_BA_AGING_TIMER_DEF),
htole32(IWM_SF_BA_IDLE_TIMER_DEF)
},
{
htole32(IWM_SF_TX_RE_AGING_TIMER_DEF),
htole32(IWM_SF_TX_RE_IDLE_TIMER_DEF)
},
};
static const uint32_t
iwm_sf_full_timeout[IWM_SF_NUM_SCENARIO][IWM_SF_NUM_TIMEOUT_TYPES] = {
{
htole32(IWM_SF_SINGLE_UNICAST_AGING_TIMER),
htole32(IWM_SF_SINGLE_UNICAST_IDLE_TIMER)
},
{
htole32(IWM_SF_AGG_UNICAST_AGING_TIMER),
htole32(IWM_SF_AGG_UNICAST_IDLE_TIMER)
},
{
htole32(IWM_SF_MCAST_AGING_TIMER),
htole32(IWM_SF_MCAST_IDLE_TIMER)
},
{
htole32(IWM_SF_BA_AGING_TIMER),
htole32(IWM_SF_BA_IDLE_TIMER)
},
{
htole32(IWM_SF_TX_RE_AGING_TIMER),
htole32(IWM_SF_TX_RE_IDLE_TIMER)
},
};
static void
iwm_fill_sf_command(struct iwm_softc *sc, struct iwm_sf_cfg_cmd *sf_cmd,
struct ieee80211_node *ni)
{
int i, j, watermark;
sf_cmd->watermark[IWM_SF_LONG_DELAY_ON] = htole32(IWM_SF_W_MARK_SCAN);
if (ni) {
#ifndef IEEE80211_NO_HT
if (ni->ni_flags & IEEE80211_NODE_HT) {
#ifdef notyet
if (ni->ni_rxmcs[2] != 0)
watermark = IWM_SF_W_MARK_MIMO3;
else if (ni->ni_rxmcs[1] != 0)
watermark = IWM_SF_W_MARK_MIMO2;
else
#endif
watermark = IWM_SF_W_MARK_SISO;
} else
#endif
watermark = IWM_SF_W_MARK_LEGACY;
} else {
watermark = IWM_SF_W_MARK_MIMO2;
}
sf_cmd->watermark[IWM_SF_FULL_ON] = htole32(watermark);
for (i = 0; i < IWM_SF_NUM_SCENARIO; i++) {
for (j = 0; j < IWM_SF_NUM_TIMEOUT_TYPES; j++) {
sf_cmd->long_delay_timeouts[i][j] =
htole32(IWM_SF_LONG_DELAY_AGING_TIMER);
}
}
if (ni) {
memcpy(sf_cmd->full_on_timeouts, iwm_sf_full_timeout,
sizeof(iwm_sf_full_timeout));
} else {
memcpy(sf_cmd->full_on_timeouts, iwm_sf_full_timeout_def,
sizeof(iwm_sf_full_timeout_def));
}
}
static int
iwm_sf_config(struct iwm_softc *sc, int new_state)
{
struct ieee80211com *ic = &sc->sc_ic;
struct iwm_sf_cfg_cmd sf_cmd = {
.state = htole32(IWM_SF_FULL_ON),
};
#if 0
if (sc->sc_device_family == IWM_DEVICE_FAMILY_8000)
sf_cmd.state |= htole32(IWM_SF_CFG_DUMMY_NOTIF_OFF);
#endif
switch (new_state) {
case IWM_SF_UNINIT:
case IWM_SF_INIT_OFF:
iwm_fill_sf_command(sc, &sf_cmd, NULL);
break;
case IWM_SF_FULL_ON:
iwm_fill_sf_command(sc, &sf_cmd, ic->ic_bss);
break;
default:
return EINVAL;
}
return iwm_send_cmd_pdu(sc, IWM_REPLY_SF_CFG_CMD, IWM_CMD_ASYNC,
sizeof(sf_cmd), &sf_cmd);
}
static int
iwm_send_bt_init_conf(struct iwm_softc *sc)
{
struct iwm_bt_coex_cmd bt_cmd;
bt_cmd.mode = htole32(IWM_BT_COEX_WIFI);
bt_cmd.enabled_modules = htole32(IWM_BT_COEX_HIGH_BAND_RET);
return iwm_send_cmd_pdu(sc, IWM_BT_CONFIG, 0, sizeof(bt_cmd),
&bt_cmd);
}
static bool
iwm_is_lar_supported(struct iwm_softc *sc)
{
bool nvm_lar = sc->sc_nvm.lar_enabled;
bool tlv_lar = isset(sc->sc_enabled_capa,
IWM_UCODE_TLV_CAPA_LAR_SUPPORT);
if (iwm_lar_disable)
return false;
if (sc->sc_device_family == IWM_DEVICE_FAMILY_8000)
return nvm_lar && tlv_lar;
else
return tlv_lar;
}
static int
iwm_send_soc_conf(struct iwm_softc *sc)
{
struct iwm_soc_configuration_cmd cmd;
int err;
uint32_t cmd_id, flags = 0;
memset(&cmd, 0, sizeof(cmd));
#ifdef notyet
if (!sc->sc_integrated) {
flags = IWM_SOC_CONFIG_CMD_FLAGS_DISCRETE;
} else {
uint8_t scan_cmd_ver;
if (sc->sc_ltr_delay != IWM_SOC_FLAGS_LTR_APPLY_DELAY_NONE)
flags |= (sc->sc_ltr_delay &
IWM_SOC_FLAGS_LTR_APPLY_DELAY_MASK);
scan_cmd_ver = iwm_lookup_cmd_ver(sc, IWM_LONG_GROUP,
IWM_SCAN_REQ_UMAC);
if (scan_cmd_ver != IWM_FW_CMD_VER_UNKNOWN &&
scan_cmd_ver >= 2 && sc->sc_low_latency_xtal)
flags |= IWM_SOC_CONFIG_CMD_FLAGS_LOW_LATENCY;
}
cmd.latency = htole32(sc->sc_xtal_latency);
#else
flags = IWM_SOC_CONFIG_CMD_FLAGS_DISCRETE;
#endif
cmd.flags = htole32(flags);
cmd_id = iwm_cmd_id(IWM_SOC_CONFIGURATION_CMD, IWM_SYSTEM_GROUP, 0);
err = iwm_send_cmd_pdu(sc, cmd_id, 0, sizeof(cmd), &cmd);
if (err)
printf("%s: failed to set soc latency: %d\n", DEVNAME(sc), err);
return err;
}
static int
iwm_send_update_mcc_cmd(struct iwm_softc *sc, const char *alpha2)
{
struct iwm_mcc_update_cmd mcc_cmd;
struct iwm_host_cmd hcmd = {
.id = IWM_MCC_UPDATE_CMD,
.flags = IWM_CMD_WANT_RESP,
.resp_pkt_len = IWM_CMD_RESP_MAX,
.data = { &mcc_cmd },
};
struct iwm_rx_packet *pkt;
size_t resp_len;
int err;
int resp_v3 = isset(sc->sc_enabled_capa,
IWM_UCODE_TLV_CAPA_LAR_SUPPORT_V3);
if (!iwm_is_lar_supported(sc)) {
DPRINTF(("%s: no LAR support\n", __func__));
return 0;
}
memset(&mcc_cmd, 0, sizeof(mcc_cmd));
mcc_cmd.mcc = htole16(alpha2[0] << 8 | alpha2[1]);
if (isset(sc->sc_ucode_api, IWM_UCODE_TLV_API_WIFI_MCC_UPDATE) ||
isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_LAR_MULTI_MCC))
mcc_cmd.source_id = IWM_MCC_SOURCE_GET_CURRENT;
else
mcc_cmd.source_id = IWM_MCC_SOURCE_OLD_FW;
if (resp_v3)
hcmd.len[0] = sizeof(struct iwm_mcc_update_cmd);
else
hcmd.len[0] = sizeof(struct iwm_mcc_update_cmd_v1);
err = iwm_send_cmd(sc, &hcmd);
if (err)
return err;
pkt = hcmd.resp_pkt;
if (!pkt || (pkt->hdr.flags & IWM_CMD_FAILED_MSK)) {
err = EIO;
goto out;
}
if (resp_v3) {
struct iwm_mcc_update_resp_v3 *resp;
resp_len = iwm_rx_packet_payload_len(pkt);
if (resp_len < sizeof(*resp)) {
err = EIO;
goto out;
}
resp = (void *)pkt->data;
if (resp_len != sizeof(*resp) +
resp->n_channels * sizeof(resp->channels[0])) {
err = EIO;
goto out;
}
} else {
struct iwm_mcc_update_resp_v1 *resp_v1;
resp_len = iwm_rx_packet_payload_len(pkt);
if (resp_len < sizeof(*resp_v1)) {
err = EIO;
goto out;
}
resp_v1 = (void *)pkt->data;
if (resp_len != sizeof(*resp_v1) +
resp_v1->n_channels * sizeof(resp_v1->channels[0])) {
err = EIO;
goto out;
}
}
out:
iwm_free_resp(sc, &hcmd);
return err;
}
static int
iwm_send_temp_report_ths_cmd(struct iwm_softc *sc)
{
struct iwm_temp_report_ths_cmd cmd;
int err;
memset(&cmd, 0, sizeof(cmd));
err = iwm_send_cmd_pdu(sc,
IWM_WIDE_ID(IWM_PHY_OPS_GROUP, IWM_TEMP_REPORTING_THRESHOLDS_CMD),
0, sizeof(cmd), &cmd);
if (err)
printf("%s: TEMP_REPORT_THS_CMD command failed (error %d)\n",
DEVNAME(sc), err);
return err;
}
static void
iwm_tt_tx_backoff(struct iwm_softc *sc, uint32_t backoff)
{
struct iwm_host_cmd cmd = {
.id = IWM_REPLY_THERMAL_MNG_BACKOFF,
.len = { sizeof(uint32_t), },
.data = { &backoff, },
};
iwm_send_cmd(sc, &cmd);
}
static int
iwm_init_hw(struct iwm_softc *sc)
{
struct ieee80211com *ic = &sc->sc_ic;
int err, i, ac, qid, s;
err = iwm_run_init_mvm_ucode(sc, 0);
if (err)
return err;
iwm_stop_device(sc);
err = iwm_start_hw(sc);
if (err) {
aprint_error_dev(sc->sc_dev, "could not initialize hardware\n");
return err;
}
s = splnet();
err = iwm_load_ucode_wait_alive(sc, IWM_UCODE_TYPE_REGULAR);
if (err) {
aprint_error_dev(sc->sc_dev,
"could not load firmware (error %d)\n", err);
splx(s);
return err;
}
if (!iwm_nic_lock(sc)) {
splx(s);
return EBUSY;
}
err = iwm_send_tx_ant_cfg(sc, iwm_fw_valid_tx_ant(sc));
if (err) {
aprint_error_dev(sc->sc_dev,
"could not init tx ant config (error %d)\n", err);
goto err;
}
err = iwm_send_phy_db_data(sc);
if (err) {
aprint_error_dev(sc->sc_dev,
"could not init phy db (error %d)\n", err);
goto err;
}
err = iwm_send_phy_cfg_cmd(sc);
if (err) {
aprint_error_dev(sc->sc_dev,
"could not send phy config (error %d)\n", err);
goto err;
}
err = iwm_send_bt_init_conf(sc);
if (err) {
aprint_error_dev(sc->sc_dev,
"could not init bt coex (error %d)\n", err);
goto err;
}
if (isset(sc->sc_enabled_capa,
IWM_UCODE_TLV_CAPA_SOC_LATENCY_SUPPORT)) {
err = iwm_send_soc_conf(sc);
if (err)
goto err;
}
if (isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_DQA_SUPPORT)) {
err = iwm_send_dqa_cmd(sc);
if (err)
goto err;
}
err = iwm_add_aux_sta(sc);
if (err) {
aprint_error_dev(sc->sc_dev,
"could not add aux station (error %d)\n", err);
goto err;
}
for (i = 0; i < IWM_NUM_PHY_CTX; i++) {
sc->sc_phyctxt[i].channel = &ic->ic_channels[1];
err = iwm_phy_ctxt_cmd(sc, &sc->sc_phyctxt[i], 1, 1,
IWM_FW_CTXT_ACTION_ADD, 0);
if (err) {
aprint_error_dev(sc->sc_dev,
"could not add phy context %d (error %d)\n",
i, err);
goto err;
}
}
if (sc->sc_device_family == IWM_DEVICE_FAMILY_7000)
iwm_tt_tx_backoff(sc, 0);
#ifdef notyet
err = iwm_config_ltr(sc);
if (err) {
aprint_error_dev(sc->sc_dev,
"PCIe LTR confguration failed (error %d)\n", err);
}
#endif
if (isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_CT_KILL_BY_FW)) {
err = iwm_send_temp_report_ths_cmd(sc);
if (err)
goto err;
}
err = iwm_power_update_device(sc);
if (err) {
aprint_error_dev(sc->sc_dev,
"could send power command (error %d)\n", err);
goto err;
}
if (isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_LAR_SUPPORT)) {
err = iwm_send_update_mcc_cmd(sc, iwm_default_mcc);
if (err) {
aprint_error_dev(sc->sc_dev,
"could not init LAR (error %d)\n", err);
goto err;
}
}
if (isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_UMAC_SCAN)) {
err = iwm_config_umac_scan(sc);
if (err) {
aprint_error_dev(sc->sc_dev,
"could not configure scan (error %d)\n", err);
goto err;
}
}
if (ic->ic_opmode == IEEE80211_M_MONITOR) {
if (isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_DQA_SUPPORT))
qid = IWM_DQA_INJECT_MONITOR_QUEUE;
else
qid = IWM_AUX_QUEUE;
err = iwm_enable_txq(sc, IWM_MONITOR_STA_ID, qid,
iwm_ac_to_tx_fifo[WME_AC_BE], 0, IWM_MAX_TID_COUNT, 0);
if (err) {
aprint_error_dev(sc->sc_dev,
"could not enable monitor inject Tx queue "
"(error %d)\n", err);
goto err;
}
} else {
for (ac = 0; ac < WME_NUM_AC; ac++) {
if (isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_DQA_SUPPORT))
qid = ac + IWM_DQA_MIN_MGMT_QUEUE;
else
qid = ac;
err = iwm_enable_txq(sc, IWM_STATION_ID, qid,
iwm_ac_to_tx_fifo[ac], 0, IWM_TID_NON_QOS, 0);
if (err) {
aprint_error_dev(sc->sc_dev,
"could not enable Tx queue %d (error %d)\n",
ac, err);
goto err;
}
}
}
err = iwm_disable_beacon_filter(sc);
if (err) {
aprint_error_dev(sc->sc_dev,
"could not disable beacon filter (error %d)\n", err);
goto err;
}
err:
iwm_nic_unlock(sc);
splx(s);
return err;
}
static int
iwm_allow_mcast(struct iwm_softc *sc)
{
struct ieee80211com *ic = &sc->sc_ic;
struct iwm_node *in = (struct iwm_node *)ic->ic_bss;
struct iwm_mcast_filter_cmd *cmd;
size_t size;
int err;
size = roundup(sizeof(*cmd), 4);
cmd = kmem_intr_zalloc(size, KM_NOSLEEP);
if (cmd == NULL)
return ENOMEM;
cmd->filter_own = 1;
cmd->port_id = 0;
cmd->count = 0;
cmd->pass_all = 1;
IEEE80211_ADDR_COPY(cmd->bssid, in->in_macaddr);
err = iwm_send_cmd_pdu(sc, IWM_MCAST_FILTER_CMD,
0, size, cmd);
kmem_intr_free(cmd, size);
return err;
}
static int
iwm_init(struct ifnet *ifp)
{
struct iwm_softc *sc = ifp->if_softc;
struct ieee80211com *ic = &sc->sc_ic;
int err, generation;
generation = ++sc->sc_generation;
err = iwm_preinit(sc, false);
if (err)
return err;
err = iwm_start_hw(sc);
if (err) {
device_printf(sc->sc_dev, "could not initialize hardware\n");
return err;
}
CLR(sc->sc_flags, IWM_FLAG_STOPPED);
err = iwm_init_hw(sc);
if (err) {
if (generation == sc->sc_generation)
iwm_stop_device(sc);
return err;
}
ifp->if_flags &= ~IFF_OACTIVE;
ifp->if_flags |= IFF_RUNNING;
if (ic->ic_opmode == IEEE80211_M_MONITOR) {
ieee80211_new_state(ic, IEEE80211_S_RUN, -1);
return 0;
}
ieee80211_begin_scan(&sc->sc_ic, 0);
return 0;
}
static void
iwm_start(struct ifnet *ifp)
{
struct iwm_softc *sc = ifp->if_softc;
struct ieee80211com *ic = &sc->sc_ic;
struct ieee80211_node *ni;
struct ether_header *eh;
struct mbuf *m;
int ac;
if ((ifp->if_flags & (IFF_RUNNING | IFF_OACTIVE)) != IFF_RUNNING)
return;
for (;;) {
if (sc->qfullmsk != 0) {
ifp->if_flags |= IFF_OACTIVE;
break;
}
IF_DEQUEUE(&ic->ic_mgtq, m);
if (m) {
ni = M_GETCTX(m, struct ieee80211_node *);
M_CLEARCTX(m);
ac = WME_AC_BE;
goto sendit;
}
if (ic->ic_state != IEEE80211_S_RUN) {
break;
}
IFQ_DEQUEUE(&ifp->if_snd, m);
if (m == NULL)
break;
if (m->m_len < sizeof (*eh) &&
(m = m_pullup(m, sizeof (*eh))) == NULL) {
if_statinc(ifp, if_oerrors);
continue;
}
eh = mtod(m, struct ether_header *);
ni = ieee80211_find_txnode(ic, eh->ether_dhost);
if (ni == NULL) {
m_freem(m);
if_statinc(ifp, if_oerrors);
continue;
}
if (ieee80211_classify(ic, m, ni) != 0) {
m_freem(m);
ieee80211_free_node(ni);
if_statinc(ifp, if_oerrors);
continue;
}
ac = (eh->ether_type != htons(ETHERTYPE_PAE)) ?
M_WME_GETAC(m) : WME_AC_BE;
bpf_mtap(ifp, m, BPF_D_OUT);
if ((m = ieee80211_encap(ic, m, ni)) == NULL) {
ieee80211_free_node(ni);
if_statinc(ifp, if_oerrors);
continue;
}
sendit:
bpf_mtap3(ic->ic_rawbpf, m, BPF_D_OUT);
if (iwm_tx(sc, m, ni, ac) != 0) {
ieee80211_free_node(ni);
if_statinc(ifp, if_oerrors);
continue;
}
if (ifp->if_flags & IFF_UP) {
sc->sc_tx_timer = 15;
ifp->if_timer = 1;
}
}
}
static void
iwm_stop(struct ifnet *ifp, int disable)
{
struct iwm_softc *sc = ifp->if_softc;
struct ieee80211com *ic = &sc->sc_ic;
struct iwm_node *in = (struct iwm_node *)ic->ic_bss;
int i, s;
s = splnet();
sc->sc_flags &= ~IWM_FLAG_HW_INITED;
sc->sc_flags |= IWM_FLAG_STOPPED;
iwm_stop_device(sc);
sc->sc_generation++;
for (i = 0; i < __arraycount(sc->sc_cmd_resp_pkt); i++) {
if (sc->sc_cmd_resp_pkt[i] != NULL) {
KASSERT(sc->sc_cmd_resp_len[i] > 0);
kmem_free(sc->sc_cmd_resp_pkt[i],
sc->sc_cmd_resp_len[i]);
}
sc->sc_cmd_resp_pkt[i] = NULL;
sc->sc_cmd_resp_len[i] = 0;
}
ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE);
if (in) {
in->in_phyctxt = NULL;
in->tid_disable_ampdu = 0xffff;
in->tfd_queue_msk = 0;
IEEE80211_ADDR_COPY(in->in_macaddr, etheranyaddr);
}
sc->sc_flags &= ~(IWM_FLAG_SCANNING | IWM_FLAG_BGSCAN);
sc->sc_flags &= ~IWM_FLAG_MAC_ACTIVE;
sc->sc_flags &= ~IWM_FLAG_BINDING_ACTIVE;
sc->sc_flags &= ~IWM_FLAG_STA_ACTIVE;
sc->sc_flags &= ~IWM_FLAG_TE_ACTIVE;
#ifdef notyet
sc->sc_flags &= ~IWM_FLAG_HW_ERR;
sc->sc_flags &= ~IWM_FLAG_SHUTDOWN;
#endif
sc->sc_flags &= ~IWM_FLAG_TXFLUSH;
if (ic->ic_state != IEEE80211_S_INIT)
ieee80211_new_state(ic, IEEE80211_S_INIT, -1);
iwm_led_blink_stop(sc);
ifp->if_timer = sc->sc_tx_timer = 0;
splx(s);
}
static void
iwm_watchdog(struct ifnet *ifp)
{
struct iwm_softc *sc = ifp->if_softc;
ifp->if_timer = 0;
if (sc->sc_tx_timer > 0) {
if (--sc->sc_tx_timer == 0) {
aprint_error_dev(sc->sc_dev, "device timeout\n");
#ifdef IWM_DEBUG
iwm_nic_error(sc);
iwm_dump_driver_status(sc);
#endif
ifp->if_flags &= ~IFF_UP;
iwm_stop(ifp, 1);
if_statinc(ifp, if_oerrors);
return;
}
ifp->if_timer = 1;
}
ieee80211_watchdog(&sc->sc_ic);
}
static int
iwm_ioctl(struct ifnet *ifp, u_long cmd, void *data)
{
struct iwm_softc *sc = ifp->if_softc;
struct ieee80211com *ic = &sc->sc_ic;
const struct sockaddr *sa;
int s, err = 0;
s = splnet();
switch (cmd) {
case SIOCSIFADDR:
ifp->if_flags |= IFF_UP;
case SIOCSIFFLAGS:
err = ifioctl_common(ifp, cmd, data);
if (err)
break;
if (ifp->if_flags & IFF_UP) {
if (!(ifp->if_flags & IFF_RUNNING)) {
sc->sc_fw.fw_status = IWM_FW_STATUS_NONE;
err = iwm_init(ifp);
}
} else {
if (ifp->if_flags & IFF_RUNNING) {
iwm_stop(ifp, 1);
}
}
break;
case SIOCADDMULTI:
case SIOCDELMULTI:
sa = ifreq_getaddr(SIOCADDMULTI, (struct ifreq *)data);
err = (cmd == SIOCADDMULTI) ?
ether_addmulti(sa, &sc->sc_ec) :
ether_delmulti(sa, &sc->sc_ec);
if (err == ENETRESET)
err = 0;
break;
default:
err = ieee80211_ioctl(ic, cmd, data);
break;
}
if (err == ENETRESET) {
err = 0;
if ((ifp->if_flags & (IFF_UP | IFF_RUNNING)) ==
(IFF_UP | IFF_RUNNING)) {
iwm_stop(ifp, 0);
err = iwm_init(ifp);
}
}
splx(s);
return err;
}
struct iwm_error_event_table {
uint32_t valid;
uint32_t error_id;
uint32_t trm_hw_status0;
uint32_t trm_hw_status1;
uint32_t blink2;
uint32_t ilink1;
uint32_t ilink2;
uint32_t data1;
uint32_t data2;
uint32_t data3;
uint32_t bcon_time;
uint32_t tsf_low;
uint32_t tsf_hi;
uint32_t gp1;
uint32_t gp2;
uint32_t fw_rev_type;
uint32_t major;
uint32_t minor;
uint32_t hw_ver;
uint32_t brd_ver;
uint32_t log_pc;
uint32_t frame_ptr;
uint32_t stack_ptr;
uint32_t hcmd;
uint32_t isr0;
uint32_t isr1;
uint32_t isr2;
uint32_t isr3;
uint32_t isr4;
uint32_t last_cmd_id;
uint32_t wait_event;
uint32_t l2p_control;
uint32_t l2p_duration;
uint32_t l2p_mhvalid;
uint32_t l2p_addr_match;
uint32_t lmpm_pmg_sel;
uint32_t u_timestamp;
uint32_t flow_handler;
} __packed ;
struct iwm_umac_error_event_table {
uint32_t valid;
uint32_t error_id;
uint32_t blink1;
uint32_t blink2;
uint32_t ilink1;
uint32_t ilink2;
uint32_t data1;
uint32_t data2;
uint32_t data3;
uint32_t umac_major;
uint32_t umac_minor;
uint32_t frame_pointer;
uint32_t stack_pointer;
uint32_t cmd_header;
uint32_t nic_isr_pref;
} __packed;
#define ERROR_START_OFFSET (1 * sizeof(uint32_t))
#define ERROR_ELEM_SIZE (7 * sizeof(uint32_t))
#define IWM_FW_SYSASSERT_CPU_MASK 0xf0000000
static const struct {
const char *name;
uint8_t num;
} advanced_lookup[] = {
{ "NMI_INTERRUPT_WDG", 0x34 },
{ "SYSASSERT", 0x35 },
{ "UCODE_VERSION_MISMATCH", 0x37 },
{ "BAD_COMMAND", 0x38 },
{ "BAD_COMMAND", 0x39 },
{ "NMI_INTERRUPT_DATA_ACTION_PT", 0x3C },
{ "FATAL_ERROR", 0x3D },
{ "NMI_TRM_HW_ERR", 0x46 },
{ "NMI_INTERRUPT_TRM", 0x4C },
{ "NMI_INTERRUPT_BREAK_POINT", 0x54 },
{ "NMI_INTERRUPT_WDG_RXF_FULL", 0x5C },
{ "NMI_INTERRUPT_WDG_NO_RBD_RXF_FULL", 0x64 },
{ "NMI_INTERRUPT_HOST", 0x66 },
{ "NMI_INTERRUPT_LMAC_FATAL", 0x70 },
{ "NMI_INTERRUPT_UMAC_FATAL", 0x71 },
{ "NMI_INTERRUPT_OTHER_LMAC_FATAL", 0x73 },
{ "NMI_INTERRUPT_ACTION_PT", 0x7C },
{ "NMI_INTERRUPT_UNKNOWN", 0x84 },
{ "NMI_INTERRUPT_INST_ACTION_PT", 0x86 },
{ "ADVANCED_SYSASSERT", 0 },
};
static const char *
iwm_desc_lookup(uint32_t num)
{
int i;
for (i = 0; i < __arraycount(advanced_lookup) - 1; i++)
if (advanced_lookup[i].num ==
(num & ~IWM_FW_SYSASSERT_CPU_MASK))
return advanced_lookup[i].name;
return advanced_lookup[i].name;
}
static void
iwm_nic_error(struct iwm_softc *sc)
{
struct iwm_error_event_table t;
uint32_t base;
device_printf(sc->sc_dev, "dumping device error log\n");
base = sc->sc_uc.uc_error_event_table;
if (base < 0x800000) {
device_printf(sc->sc_dev,
"Invalid error log pointer 0x%08x\n", base);
return;
}
if (iwm_read_mem(sc, base, &t, sizeof(t)/sizeof(uint32_t))) {
device_printf(sc->sc_dev, "reading errlog failed\n");
return;
}
if (!t.valid) {
device_printf(sc->sc_dev, "errlog not found, skipping\n");
return;
}
if (ERROR_START_OFFSET <= t.valid * ERROR_ELEM_SIZE) {
device_printf(sc->sc_dev, "Start Error Log Dump:\n");
device_printf(sc->sc_dev, "Status: 0x%x, count: %d\n",
sc->sc_flags, t.valid);
}
device_printf(sc->sc_dev, "%08X | %-28s\n", t.error_id,
iwm_desc_lookup(t.error_id));
device_printf(sc->sc_dev, "%08X | trm_hw_status0\n",
t.trm_hw_status0);
device_printf(sc->sc_dev, "%08X | trm_hw_status1\n",
t.trm_hw_status1);
device_printf(sc->sc_dev, "%08X | branchlink2\n", t.blink2);
device_printf(sc->sc_dev, "%08X | interruptlink1\n", t.ilink1);
device_printf(sc->sc_dev, "%08X | interruptlink2\n", t.ilink2);
device_printf(sc->sc_dev, "%08X | data1\n", t.data1);
device_printf(sc->sc_dev, "%08X | data2\n", t.data2);
device_printf(sc->sc_dev, "%08X | data3\n", t.data3);
device_printf(sc->sc_dev, "%08X | beacon time\n", t.bcon_time);
device_printf(sc->sc_dev, "%08X | tsf low\n", t.tsf_low);
device_printf(sc->sc_dev, "%08X | tsf hi\n", t.tsf_hi);
device_printf(sc->sc_dev, "%08X | time gp1\n", t.gp1);
device_printf(sc->sc_dev, "%08X | time gp2\n", t.gp2);
device_printf(sc->sc_dev, "%08X | uCode revision type\n",
t.fw_rev_type);
device_printf(sc->sc_dev, "%08X | uCode version major\n",
t.major);
device_printf(sc->sc_dev, "%08X | uCode version minor\n",
t.minor);
device_printf(sc->sc_dev, "%08X | hw version\n", t.hw_ver);
device_printf(sc->sc_dev, "%08X | board version\n", t.brd_ver);
device_printf(sc->sc_dev, "%08X | hcmd\n", t.hcmd);
device_printf(sc->sc_dev, "%08X | isr0\n", t.isr0);
device_printf(sc->sc_dev, "%08X | isr1\n", t.isr1);
device_printf(sc->sc_dev, "%08X | isr2\n", t.isr2);
device_printf(sc->sc_dev, "%08X | isr3\n", t.isr3);
device_printf(sc->sc_dev, "%08X | isr4\n", t.isr4);
device_printf(sc->sc_dev, "%08X | last cmd Id\n", t.last_cmd_id);
device_printf(sc->sc_dev, "%08X | wait_event\n", t.wait_event);
device_printf(sc->sc_dev, "%08X | l2p_control\n", t.l2p_control);
device_printf(sc->sc_dev, "%08X | l2p_duration\n", t.l2p_duration);
device_printf(sc->sc_dev, "%08X | l2p_mhvalid\n", t.l2p_mhvalid);
device_printf(sc->sc_dev, "%08X | l2p_addr_match\n",
t.l2p_addr_match);
device_printf(sc->sc_dev, "%08X | lmpm_pmg_sel\n", t.lmpm_pmg_sel);
device_printf(sc->sc_dev, "%08X | timestamp\n", t.u_timestamp);
device_printf(sc->sc_dev, "%08X | flow_handler\n", t.flow_handler);
if (sc->sc_uc.uc_umac_error_event_table)
iwm_nic_umac_error(sc);
}
static void
iwm_nic_umac_error(struct iwm_softc *sc)
{
struct iwm_umac_error_event_table t;
uint32_t base;
base = sc->sc_uc.uc_umac_error_event_table;
if (base < 0x800000) {
device_printf(sc->sc_dev,
"Invalid error log pointer 0x%08x\n", base);
return;
}
if (iwm_read_mem(sc, base, &t, sizeof(t)/sizeof(uint32_t))) {
device_printf(sc->sc_dev, "reading errlog failed\n");
return;
}
if (ERROR_START_OFFSET <= t.valid * ERROR_ELEM_SIZE) {
device_printf(sc->sc_dev, "Start UMAC Error Log Dump:\n");
device_printf(sc->sc_dev, "Status: 0x%x, count: %d\n",
sc->sc_flags, t.valid);
}
device_printf(sc->sc_dev, "0x%08X | %s\n", t.error_id,
iwm_desc_lookup(t.error_id));
device_printf(sc->sc_dev, "0x%08X | umac branchlink1\n", t.blink1);
device_printf(sc->sc_dev, "0x%08X | umac branchlink2\n", t.blink2);
device_printf(sc->sc_dev, "0x%08X | umac interruptlink1\n",
t.ilink1);
device_printf(sc->sc_dev, "0x%08X | umac interruptlink2\n",
t.ilink2);
device_printf(sc->sc_dev, "0x%08X | umac data1\n", t.data1);
device_printf(sc->sc_dev, "0x%08X | umac data2\n", t.data2);
device_printf(sc->sc_dev, "0x%08X | umac data3\n", t.data3);
device_printf(sc->sc_dev, "0x%08X | umac major\n", t.umac_major);
device_printf(sc->sc_dev, "0x%08X | umac minor\n", t.umac_minor);
device_printf(sc->sc_dev, "0x%08X | frame pointer\n",
t.frame_pointer);
device_printf(sc->sc_dev, "0x%08X | stack pointer\n",
t.stack_pointer);
device_printf(sc->sc_dev, "0x%08X | last host cmd\n", t.cmd_header);
device_printf(sc->sc_dev, "0x%08X | isr status reg\n",
t.nic_isr_pref);
}
static void
iwm_dump_driver_status(struct iwm_softc *sc)
{
int i;
printf("driver status:\n");
for (i = 0; i < IWM_MAX_QUEUES; i++) {
struct iwm_tx_ring *ring = &sc->txq[i];
printf(" tx ring %2d: qid=%-2d cur=%-3d "
"queued=%-3d\n",
i, ring->qid, ring->cur, ring->queued);
}
printf(" rx ring: cur=%d\n", sc->rxq.cur);
printf(" 802.11 state %s\n",
ieee80211_state_name[sc->sc_ic.ic_state]);
}
#define SYNC_RESP_STRUCT(_var_, _pkt_) \
do { \
bus_dmamap_sync(sc->sc_dmat, data->map, sizeof(*(_pkt_)), \
sizeof(*(_var_)), BUS_DMASYNC_POSTREAD); \
_var_ = (void *)((_pkt_)+1); \
} while (0)
#define SYNC_RESP(_len_, _pkt_) \
do { \
bus_dmamap_sync(sc->sc_dmat, data->map, sizeof(*(_pkt_)), \
(_len_), BUS_DMASYNC_POSTREAD); \
} while (0)
static void
iwm_rx_pkt(struct iwm_softc *sc, struct iwm_rx_data *data)
{
struct iwm_rx_packet *pkt;
int qid, idx, code;
int handled = 1;
bus_dmamap_sync(sc->sc_dmat, data->map, 0, IWM_RBUF_SIZE,
BUS_DMASYNC_POSTREAD);
pkt = mtod(data->m, struct iwm_rx_packet *);
qid = pkt->hdr.qid;
idx = pkt->hdr.idx;
code = IWM_WIDE_ID(pkt->hdr.flags, pkt->hdr.code);
if (((qid & ~0x80) == 0 && idx == 0 && code == 0) ||
pkt->len_n_flags == htole32(IWM_FH_RSCSR_FRAME_INVALID))
return;
switch (code) {
case IWM_REPLY_RX_PHY_CMD:
iwm_rx_rx_phy_cmd(sc, pkt, data);
break;
case IWM_REPLY_RX_MPDU_CMD:
iwm_rx_rx_mpdu(sc, pkt, data);
break;
case IWM_TX_CMD:
iwm_rx_tx_cmd(sc, pkt, data);
break;
#ifdef notyet
case IWM_BA_NOTIF:
iwm_rx_compressed_ba(sc, pkt);
break;
#endif
case IWM_MISSED_BEACONS_NOTIFICATION: {
struct iwm_missed_beacons_notif *mb;
SYNC_RESP_STRUCT(mb, pkt);
iwm_rx_missed_beacons_notif(sc, mb);
break;
}
case IWM_MFUART_LOAD_NOTIFICATION:
break;
case IWM_ALIVE: {
struct iwm_alive_resp_v1 *resp1;
struct iwm_alive_resp_v2 *resp2;
struct iwm_alive_resp_v3 *resp3;
if (iwm_rx_packet_payload_len(pkt) == sizeof(*resp1)) {
SYNC_RESP_STRUCT(resp1, pkt);
sc->sc_uc.uc_error_event_table
= le32toh(resp1->error_event_table_ptr);
sc->sc_uc.uc_log_event_table
= le32toh(resp1->log_event_table_ptr);
sc->sched_base = le32toh(resp1->scd_base_ptr);
if (resp1->status == IWM_ALIVE_STATUS_OK)
sc->sc_uc.uc_ok = 1;
else
sc->sc_uc.uc_ok = 0;
}
if (iwm_rx_packet_payload_len(pkt) == sizeof(*resp2)) {
SYNC_RESP_STRUCT(resp2, pkt);
sc->sc_uc.uc_error_event_table
= le32toh(resp2->error_event_table_ptr);
sc->sc_uc.uc_log_event_table
= le32toh(resp2->log_event_table_ptr);
sc->sched_base = le32toh(resp2->scd_base_ptr);
sc->sc_uc.uc_umac_error_event_table
= le32toh(resp2->error_info_addr);
if (resp2->status == IWM_ALIVE_STATUS_OK)
sc->sc_uc.uc_ok = 1;
else
sc->sc_uc.uc_ok = 0;
}
if (iwm_rx_packet_payload_len(pkt) == sizeof(*resp3)) {
SYNC_RESP_STRUCT(resp3, pkt);
sc->sc_uc.uc_error_event_table
= le32toh(resp3->error_event_table_ptr);
sc->sc_uc.uc_log_event_table
= le32toh(resp3->log_event_table_ptr);
sc->sched_base = le32toh(resp3->scd_base_ptr);
sc->sc_uc.uc_umac_error_event_table
= le32toh(resp3->error_info_addr);
if (resp3->status == IWM_ALIVE_STATUS_OK)
sc->sc_uc.uc_ok = 1;
else
sc->sc_uc.uc_ok = 0;
}
sc->sc_uc.uc_intr = 1;
wakeup(&sc->sc_uc);
break;
}
case IWM_CALIB_RES_NOTIF_PHY_DB: {
struct iwm_calib_res_notif_phy_db *phy_db_notif;
SYNC_RESP_STRUCT(phy_db_notif, pkt);
uint16_t size = le16toh(phy_db_notif->length);
bus_dmamap_sync(sc->sc_dmat, data->map,
sizeof(*pkt) + sizeof(*phy_db_notif),
size, BUS_DMASYNC_POSTREAD);
iwm_phy_db_set_section(sc, phy_db_notif, size);
sc->sc_init_complete |= IWM_CALIB_COMPLETE;
wakeup(&sc->sc_init_complete);
break;
}
case IWM_STATISTICS_NOTIFICATION: {
struct iwm_notif_statistics *stats;
SYNC_RESP_STRUCT(stats, pkt);
memcpy(&sc->sc_stats, stats, sizeof(sc->sc_stats));
sc->sc_noise = iwm_get_noise(&stats->rx.general);
break;
}
case IWM_MCC_CHUB_UPDATE_CMD: {
struct iwm_mcc_chub_notif *notif;
SYNC_RESP_STRUCT(notif, pkt);
iwm_mcc_update(sc, notif);
break;
}
case IWM_DTS_MEASUREMENT_NOTIFICATION:
case IWM_WIDE_ID(IWM_PHY_OPS_GROUP,
IWM_DTS_MEASUREMENT_NOTIF_WIDE):
case IWM_WIDE_ID(IWM_PHY_OPS_GROUP,
IWM_TEMP_REPORTING_THRESHOLDS_CMD):
break;
#if notyet
case IWM_WIDE_ID(IWM_PHY_OPS_GROUP,
IWM_CT_KILL_NOTIFICATION): {
struct iwm_ct_kill_notif *notif;
SYNC_RESP_STRUCT(notif, pkt);
printf("%s: device at critical temperature (%u degC), "
"stopping device\n",
DEVNAME(sc), le16toh(notif->temperature));
sc->sc_flags |= IWM_FLAG_HW_ERR;
task_add(systq, &sc->init_task);
break;
}
#endif
case IWM_ADD_STA_KEY:
case IWM_PHY_CONFIGURATION_CMD:
case IWM_TX_ANT_CONFIGURATION_CMD:
case IWM_ADD_STA:
case IWM_MAC_CONTEXT_CMD:
case IWM_REPLY_SF_CFG_CMD:
case IWM_POWER_TABLE_CMD:
case IWM_LTR_CONFIG:
case IWM_PHY_CONTEXT_CMD:
case IWM_BINDING_CONTEXT_CMD:
case IWM_WIDE_ID(IWM_LONG_GROUP, IWM_SCAN_CFG_CMD):
case IWM_WIDE_ID(IWM_LONG_GROUP, IWM_SCAN_REQ_UMAC):
case IWM_WIDE_ID(IWM_LONG_GROUP, IWM_SCAN_ABORT_UMAC):
case IWM_SCAN_OFFLOAD_REQUEST_CMD:
case IWM_SCAN_OFFLOAD_ABORT_CMD:
case IWM_REPLY_BEACON_FILTERING_CMD:
case IWM_MAC_PM_POWER_TABLE:
case IWM_TIME_QUOTA_CMD:
case IWM_REMOVE_STA:
case IWM_TXPATH_FLUSH:
case IWM_LQ_CMD:
case IWM_WIDE_ID(IWM_LONG_GROUP,
IWM_FW_PAGING_BLOCK_CMD):
case IWM_BT_CONFIG:
case IWM_REPLY_THERMAL_MNG_BACKOFF:
case IWM_NVM_ACCESS_CMD:
case IWM_MCC_UPDATE_CMD:
case IWM_TIME_EVENT_CMD: {
size_t pkt_len;
if (sc->sc_cmd_resp_pkt[idx] == NULL)
break;
pkt_len = sizeof(pkt->len_n_flags) +
iwm_rx_packet_len(pkt);
if ((pkt->hdr.flags & IWM_CMD_FAILED_MSK) ||
pkt_len < sizeof(*pkt) ||
pkt_len > sc->sc_cmd_resp_len[idx]) {
kmem_free(sc->sc_cmd_resp_pkt[idx],
sc->sc_cmd_resp_len[idx]);
sc->sc_cmd_resp_pkt[idx] = NULL;
sc->sc_cmd_resp_len[idx] = 0;
break;
}
SYNC_RESP(pkt_len - sizeof(*pkt), pkt);
memcpy(sc->sc_cmd_resp_pkt[idx], pkt, pkt_len);
break;
}
case IWM_PHY_DB_CMD:
break;
case IWM_INIT_COMPLETE_NOTIF:
sc->sc_init_complete |= IWM_INIT_COMPLETE;
wakeup(&sc->sc_init_complete);
break;
case IWM_SCAN_OFFLOAD_COMPLETE: {
struct iwm_periodic_scan_complete *notif;
SYNC_RESP_STRUCT(notif, pkt);
break;
}
case IWM_SCAN_ITERATION_COMPLETE: {
struct iwm_lmac_scan_complete_notif *notif;
SYNC_RESP_STRUCT(notif, pkt);
if (ISSET(sc->sc_flags, IWM_FLAG_SCANNING))
iwm_endscan(sc);
break;
}
case IWM_SCAN_COMPLETE_UMAC: {
struct iwm_umac_scan_complete *notif;
SYNC_RESP_STRUCT(notif, pkt);
if (ISSET(sc->sc_flags, IWM_FLAG_SCANNING))
iwm_endscan(sc);
break;
}
case IWM_SCAN_ITERATION_COMPLETE_UMAC: {
struct iwm_umac_scan_iter_complete_notif *notif;
SYNC_RESP_STRUCT(notif, pkt);
if (ISSET(sc->sc_flags, IWM_FLAG_SCANNING))
iwm_endscan(sc);
break;
}
case IWM_REPLY_ERROR: {
struct iwm_error_resp *resp;
SYNC_RESP_STRUCT(resp, pkt);
device_printf(sc->sc_dev, "firmware error 0x%x, cmd 0x%x\n",
le32toh(resp->error_type), resp->cmd_id);
break;
}
case IWM_TIME_EVENT_NOTIFICATION: {
struct iwm_time_event_notif *notif;
uint32_t action;
SYNC_RESP_STRUCT(notif, pkt);
if (sc->sc_time_event_uid != le32toh(notif->unique_id))
break;
action = le32toh(notif->action);
if (action & IWM_TE_V2_NOTIF_HOST_EVENT_END)
CLR(sc->sc_flags, IWM_FLAG_TE_ACTIVE);
break;
}
case IWM_WIDE_ID(IWM_SYSTEM_GROUP,
IWM_FSEQ_VER_MISMATCH_NOTIFICATION):
break;
case IWM_DEBUG_LOG_MSG:
break;
case IWM_MCAST_FILTER_CMD:
break;
case IWM_SCD_QUEUE_CFG: {
struct iwm_scd_txq_cfg_rsp *rsp;
SYNC_RESP_STRUCT(rsp, pkt);
break;
}
case IWM_WIDE_ID(IWM_DATA_PATH_GROUP, IWM_DQA_ENABLE_CMD):
break;
case IWM_WIDE_ID(IWM_SYSTEM_GROUP, IWM_SOC_CONFIGURATION_CMD):
break;
default:
handled = 0;
device_printf(sc->sc_dev,
"unhandled firmware response 0x%x/0x%x rx ring %d[%d]\n",
code, pkt->len_n_flags, (qid & ~0x80), idx);
break;
}
if (handled && !(qid & (1 << 7))) {
iwm_cmd_done(sc, qid, idx);
}
}
static void
iwm_notif_intr(struct iwm_softc *sc)
{
uint32_t wreg;
uint16_t hw;
int count;
bus_dmamap_sync(sc->sc_dmat, sc->rxq.stat_dma.map,
0, sc->rxq.stat_dma.size, BUS_DMASYNC_POSTREAD);
#if notyet
if (sc->sc_mqrx_supported) {
count = IWM_RX_MQ_RING_COUNT;
wreg = IWM_RFH_Q0_FRBDCB_WIDX_TRG;
} else
#endif
{
count = IWM_RX_RING_COUNT;
wreg = IWM_FH_RSCSR_CHNL0_WPTR;
}
hw = le16toh(sc->rxq.stat->closed_rb_num) & 0xfff;
hw &= (count - 1);
while (sc->rxq.cur != hw) {
struct iwm_rx_data *data = &sc->rxq.data[sc->rxq.cur];
iwm_rx_pkt(sc, data);
sc->rxq.cur = (sc->rxq.cur + 1) % count;
}
hw = (hw == 0) ? count - 1 : hw - 1;
IWM_WRITE(sc, wreg, hw & ~7);
}
static int
iwm_intr(void *arg)
{
struct iwm_softc *sc = arg;
IWM_WRITE(sc, IWM_CSR_INT_MASK, 0);
softint_schedule(sc->sc_soft_ih);
return 1;
}
static void
iwm_softintr(void *arg)
{
struct iwm_softc *sc = arg;
struct ifnet *ifp = IC2IFP(&sc->sc_ic);
uint32_t r1, r2;
int s;
if (__predict_true(sc->sc_flags & IWM_FLAG_USE_ICT)) {
uint32_t *ict = sc->ict_dma.vaddr;
int tmp;
bus_dmamap_sync(sc->sc_dmat, sc->ict_dma.map,
0, sc->ict_dma.size, BUS_DMASYNC_POSTREAD);
tmp = htole32(ict[sc->ict_cur]);
if (tmp == 0)
goto out_ena;
r1 = r2 = 0;
while (tmp) {
r1 |= tmp;
ict[sc->ict_cur] = 0;
sc->ict_cur = (sc->ict_cur + 1) % IWM_ICT_COUNT;
tmp = htole32(ict[sc->ict_cur]);
}
bus_dmamap_sync(sc->sc_dmat, sc->ict_dma.map,
0, sc->ict_dma.size, BUS_DMASYNC_PREWRITE);
if (r1 == 0xffffffff)
r1 = 0;
if (r1 & 0xc0000)
r1 |= 0x8000;
r1 = (0xff & r1) | ((0xff00 & r1) << 16);
} else {
r1 = IWM_READ(sc, IWM_CSR_INT);
r2 = IWM_READ(sc, IWM_CSR_FH_INT_STATUS);
}
if (r1 == 0 && r2 == 0) {
goto out_ena;
}
if (r1 == 0xffffffff || (r1 & 0xfffffff0) == 0xa5a5a5a0)
return;
IWM_WRITE(sc, IWM_CSR_INT, r1 | ~sc->sc_intmask);
if (r1 & IWM_CSR_INT_BIT_RF_KILL) {
if (iwm_check_rfkill(sc) && ifp != NULL &&
(ifp->if_flags & IFF_UP))
goto fatal;
}
if (r1 & IWM_CSR_INT_BIT_SW_ERR) {
iwm_nic_error(sc);
iwm_dump_driver_status(sc);
aprint_error_dev(sc->sc_dev, "fatal firmware error\n");
fatal:
if (ifp != NULL) {
s = splnet();
ifp->if_flags &= ~IFF_UP;
iwm_stop(ifp, 1);
splx(s);
}
return;
}
if (r1 & IWM_CSR_INT_BIT_HW_ERR) {
aprint_error_dev(sc->sc_dev,
"hardware error, stopping device\n");
goto fatal;
}
if (r1 & IWM_CSR_INT_BIT_FH_TX) {
IWM_WRITE(sc, IWM_CSR_FH_INT_STATUS, IWM_CSR_FH_INT_TX_MASK);
sc->sc_fw_chunk_done = 1;
wakeup(&sc->sc_fw);
}
if (r1 & (IWM_CSR_INT_BIT_FH_RX | IWM_CSR_INT_BIT_SW_RX |
IWM_CSR_INT_BIT_RX_PERIODIC)) {
if (r1 & (IWM_CSR_INT_BIT_FH_RX | IWM_CSR_INT_BIT_SW_RX)) {
IWM_WRITE(sc, IWM_CSR_FH_INT_STATUS, IWM_CSR_FH_INT_RX_MASK);
}
if (r1 & IWM_CSR_INT_BIT_RX_PERIODIC) {
IWM_WRITE(sc, IWM_CSR_INT, IWM_CSR_INT_BIT_RX_PERIODIC);
}
IWM_WRITE_1(sc, IWM_CSR_INT_PERIODIC_REG, IWM_CSR_INT_PERIODIC_DIS);
if (r1 & (IWM_CSR_INT_BIT_FH_RX | IWM_CSR_INT_BIT_SW_RX))
IWM_WRITE_1(sc, IWM_CSR_INT_PERIODIC_REG,
IWM_CSR_INT_PERIODIC_ENA);
iwm_notif_intr(sc);
}
out_ena:
iwm_restore_interrupts(sc);
}
static const pci_product_id_t iwm_devices[] = {
PCI_PRODUCT_INTEL_WIFI_LINK_7260_1,
PCI_PRODUCT_INTEL_WIFI_LINK_7260_2,
PCI_PRODUCT_INTEL_WIFI_LINK_3160_1,
PCI_PRODUCT_INTEL_WIFI_LINK_3160_2,
PCI_PRODUCT_INTEL_WIFI_LINK_7265_1,
PCI_PRODUCT_INTEL_WIFI_LINK_7265_2,
PCI_PRODUCT_INTEL_WIFI_LINK_3165_1,
PCI_PRODUCT_INTEL_WIFI_LINK_3165_2,
PCI_PRODUCT_INTEL_WIFI_LINK_3168,
PCI_PRODUCT_INTEL_WIFI_LINK_8260_1,
PCI_PRODUCT_INTEL_WIFI_LINK_8260_2,
PCI_PRODUCT_INTEL_WIFI_LINK_4165_1,
PCI_PRODUCT_INTEL_WIFI_LINK_4165_2,
PCI_PRODUCT_INTEL_WIFI_LINK_8265,
};
static int
iwm_match(device_t parent, cfdata_t match __unused, void *aux)
{
struct pci_attach_args *pa = aux;
if (PCI_VENDOR(pa->pa_id) != PCI_VENDOR_INTEL)
return 0;
for (size_t i = 0; i < __arraycount(iwm_devices); i++)
if (PCI_PRODUCT(pa->pa_id) == iwm_devices[i])
return 1;
return 0;
}
static int
iwm_preinit(struct iwm_softc *sc, bool printme)
{
int err;
err = iwm_prepare_card_hw(sc);
if (err) {
aprint_error_dev(sc->sc_dev, "could not prepare hardware\n");
return err;
}
if (sc->attached) {
return 0;
}
err = iwm_start_hw(sc);
if (err) {
aprint_error_dev(sc->sc_dev, "could not initialize hardware\n");
return err;
}
err = iwm_run_init_mvm_ucode(sc, 1);
iwm_stop_device(sc);
if (err)
return err;
sc->attached = 1;
if (printme)
aprint_normal_dev(sc->sc_dev,
"hw rev 0x%x, fw ver %s, address %s\n",
sc->sc_hw_rev & IWM_CSR_HW_REV_TYPE_MSK, sc->sc_fwver,
ether_sprintf(sc->sc_nvm.hw_addr));
return 0;
}
static void
iwm_attach_hook(device_t dev)
{
struct iwm_softc *sc = device_private(dev);
iwm_config_complete(sc);
}
static void
iwm_attach(device_t parent, device_t self, void *aux)
{
struct iwm_softc *sc = device_private(self);
struct pci_attach_args *pa = aux;
pcireg_t reg, memtype;
char intrbuf[PCI_INTRSTR_LEN];
const char *intrstr;
int err;
int txq_i;
const struct sysctlnode *node;
sc->sc_dev = self;
sc->sc_pct = pa->pa_pc;
sc->sc_pcitag = pa->pa_tag;
sc->sc_dmat = pa->pa_dmat;
sc->sc_pciid = pa->pa_id;
pci_aprint_devinfo(pa, NULL);
mutex_init(&sc->sc_nic_mtx, MUTEX_DEFAULT, IPL_NET);
if (workqueue_create(&sc->sc_nswq, "iwmns",
iwm_newstate_cb, sc, PRI_NONE, IPL_NET, 0))
panic("%s: could not create workqueue: newstate",
device_xname(self));
sc->sc_soft_ih = softint_establish(SOFTINT_NET, iwm_softintr, sc);
if (sc->sc_soft_ih == NULL)
panic("%s: could not establish softint", device_xname(self));
err = pci_get_capability(sc->sc_pct, sc->sc_pcitag,
PCI_CAP_PCIEXPRESS, &sc->sc_cap_off, NULL);
if (err == 0) {
aprint_error_dev(self,
"PCIe capability structure not found!\n");
return;
}
reg = pci_conf_read(sc->sc_pct, sc->sc_pcitag, 0x40);
pci_conf_write(sc->sc_pct, sc->sc_pcitag, 0x40, reg & ~0xff00);
reg = pci_conf_read(sc->sc_pct, sc->sc_pcitag, PCI_COMMAND_STATUS_REG);
reg |= PCI_COMMAND_MASTER_ENABLE;
pci_conf_write(sc->sc_pct, sc->sc_pcitag, PCI_COMMAND_STATUS_REG, reg);
memtype = pci_mapreg_type(pa->pa_pc, pa->pa_tag, PCI_MAPREG_START);
err = pci_mapreg_map(pa, PCI_MAPREG_START, memtype, 0,
&sc->sc_st, &sc->sc_sh, NULL, &sc->sc_sz);
if (err) {
aprint_error_dev(self, "can't map mem space\n");
return;
}
err = pci_intr_alloc(pa, &sc->sc_pihp, NULL, 0);
if (err) {
aprint_error_dev(self, "can't allocate interrupt\n");
return;
}
reg = pci_conf_read(sc->sc_pct, sc->sc_pcitag, PCI_COMMAND_STATUS_REG);
if (pci_intr_type(sc->sc_pct, sc->sc_pihp[0]) == PCI_INTR_TYPE_INTX)
CLR(reg, PCI_COMMAND_INTERRUPT_DISABLE);
else
SET(reg, PCI_COMMAND_INTERRUPT_DISABLE);
pci_conf_write(sc->sc_pct, sc->sc_pcitag, PCI_COMMAND_STATUS_REG, reg);
intrstr = pci_intr_string(sc->sc_pct, sc->sc_pihp[0], intrbuf,
sizeof(intrbuf));
sc->sc_ih = pci_intr_establish_xname(sc->sc_pct, sc->sc_pihp[0],
IPL_NET, iwm_intr, sc, device_xname(self));
if (sc->sc_ih == NULL) {
aprint_error_dev(self, "can't establish interrupt");
if (intrstr != NULL)
aprint_error(" at %s", intrstr);
aprint_error("\n");
return;
}
aprint_normal_dev(self, "interrupting at %s\n", intrstr);
sc->sc_hw_rev = IWM_READ(sc, IWM_CSR_HW_REV);
switch (PCI_PRODUCT(sc->sc_pciid)) {
case PCI_PRODUCT_INTEL_WIFI_LINK_3160_1:
case PCI_PRODUCT_INTEL_WIFI_LINK_3160_2:
sc->sc_fwname = "iwlwifi-3160-17.ucode";
sc->host_interrupt_operation_mode = 1;
sc->apmg_wake_up_wa = 1;
sc->sc_device_family = IWM_DEVICE_FAMILY_7000;
sc->sc_fwdmasegsz = IWM_FWDMASEGSZ;
sc->nvm_type = IWM_NVM;
break;
case PCI_PRODUCT_INTEL_WIFI_LINK_3165_1:
case PCI_PRODUCT_INTEL_WIFI_LINK_3165_2:
#if 0
sc->sc_fwname = "iwlwifi-7265D-29.ucode";
#else
sc->sc_fwname = "iwlwifi-7265D-22.ucode";
#endif
sc->host_interrupt_operation_mode = 0;
sc->apmg_wake_up_wa = 1;
sc->sc_device_family = IWM_DEVICE_FAMILY_7000;
sc->sc_fwdmasegsz = IWM_FWDMASEGSZ;
sc->nvm_type = IWM_NVM;
break;
case PCI_PRODUCT_INTEL_WIFI_LINK_3168:
sc->sc_fwname = "iwlwifi-3168-29.ucode";
sc->host_interrupt_operation_mode = 0;
sc->apmg_wake_up_wa = 1;
sc->sc_device_family = IWM_DEVICE_FAMILY_7000;
sc->sc_fwdmasegsz = IWM_FWDMASEGSZ;
sc->nvm_type = IWM_NVM_SDP;
break;
case PCI_PRODUCT_INTEL_WIFI_LINK_7260_1:
case PCI_PRODUCT_INTEL_WIFI_LINK_7260_2:
sc->sc_fwname = "iwlwifi-7260-17.ucode";
sc->host_interrupt_operation_mode = 1;
sc->apmg_wake_up_wa = 1;
sc->sc_device_family = IWM_DEVICE_FAMILY_7000;
sc->sc_fwdmasegsz = IWM_FWDMASEGSZ;
sc->nvm_type = IWM_NVM;
break;
case PCI_PRODUCT_INTEL_WIFI_LINK_7265_1:
case PCI_PRODUCT_INTEL_WIFI_LINK_7265_2:
sc->sc_fwname = (sc->sc_hw_rev & IWM_CSR_HW_REV_TYPE_MSK) ==
IWM_CSR_HW_REV_TYPE_7265D ?
#if 0
"iwlwifi-7265D-29.ucode": "iwlwifi-7265-17.ucode";
#else
"iwlwifi-7265D-22.ucode": "iwlwifi-7265-17.ucode";
#endif
sc->host_interrupt_operation_mode = 0;
sc->apmg_wake_up_wa = 1;
sc->sc_device_family = IWM_DEVICE_FAMILY_7000;
sc->sc_fwdmasegsz = IWM_FWDMASEGSZ;
sc->nvm_type = IWM_NVM;
break;
case PCI_PRODUCT_INTEL_WIFI_LINK_8260_1:
case PCI_PRODUCT_INTEL_WIFI_LINK_8260_2:
case PCI_PRODUCT_INTEL_WIFI_LINK_4165_1:
case PCI_PRODUCT_INTEL_WIFI_LINK_4165_2:
#if 0
sc->sc_fwname = "iwlwifi-8000C-36.ucode";
#else
sc->sc_fwname = "iwlwifi-8000C-22.ucode";
#endif
sc->host_interrupt_operation_mode = 0;
sc->apmg_wake_up_wa = 0;
sc->sc_device_family = IWM_DEVICE_FAMILY_8000;
sc->sc_fwdmasegsz = IWM_FWDMASEGSZ_8000;
sc->nvm_type = IWM_NVM_EXT;
break;
case PCI_PRODUCT_INTEL_WIFI_LINK_8265:
#if 0
sc->sc_fwname = "iwlwifi-8265-36.ucode";
#else
sc->sc_fwname = "iwlwifi-8265-22.ucode";
#endif
sc->host_interrupt_operation_mode = 0;
sc->apmg_wake_up_wa = 0;
sc->sc_device_family = IWM_DEVICE_FAMILY_8000;
sc->sc_fwdmasegsz = IWM_FWDMASEGSZ_8000;
sc->nvm_type = IWM_NVM_EXT;
break;
default:
aprint_error_dev(self, "unknown product %#x",
PCI_PRODUCT(sc->sc_pciid));
return;
}
DPRINTF(("%s: firmware=%s\n", DEVNAME(sc), sc->sc_fwname));
if (sc->sc_device_family == IWM_DEVICE_FAMILY_8000)
sc->sc_hw_rev = (sc->sc_hw_rev & 0xfff0) |
(IWM_CSR_HW_REV_STEP(sc->sc_hw_rev << 2) << 2);
if (iwm_prepare_card_hw(sc) != 0) {
aprint_error_dev(sc->sc_dev, "could not initialize hardware\n");
return;
}
if (sc->sc_device_family == IWM_DEVICE_FAMILY_8000) {
uint32_t hw_step;
IWM_SETBITS(sc, IWM_CSR_GP_CNTRL,
IWM_CSR_GP_CNTRL_REG_FLAG_INIT_DONE);
DELAY(2);
err = iwm_poll_bit(sc, IWM_CSR_GP_CNTRL,
IWM_CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY,
IWM_CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY,
25000);
if (!err) {
aprint_error_dev(sc->sc_dev,
"failed to wake up the nic\n");
return;
}
if (iwm_nic_lock(sc)) {
hw_step = iwm_read_prph(sc, IWM_WFPM_CTRL_REG);
hw_step |= IWM_ENABLE_WFPM;
iwm_write_prph(sc, IWM_WFPM_CTRL_REG, hw_step);
hw_step = iwm_read_prph(sc, IWM_AUX_MISC_REG);
hw_step = (hw_step >> IWM_HW_STEP_LOCATION_BITS) & 0xF;
if (hw_step == 0x3)
sc->sc_hw_rev = (sc->sc_hw_rev & 0xFFFFFFF3) |
(IWM_SILICON_C_STEP << 2);
iwm_nic_unlock(sc);
} else {
aprint_error_dev(sc->sc_dev,
"failed to lock the nic\n");
return;
}
}
err = iwm_dma_contig_alloc(sc->sc_dmat, &sc->fw_dma, sc->sc_fwdmasegsz,
16);
if (err) {
aprint_error_dev(sc->sc_dev,
"could not allocate memory for firmware\n");
return;
}
err = iwm_dma_contig_alloc(sc->sc_dmat, &sc->kw_dma, 4096, 4096);
if (err) {
aprint_error_dev(sc->sc_dev,
"could not allocate keep warm page\n");
goto fail1;
}
err = iwm_dma_contig_alloc(sc->sc_dmat, &sc->ict_dma, IWM_ICT_SIZE,
1 << IWM_ICT_PADDR_SHIFT);
if (err) {
aprint_error_dev(sc->sc_dev, "could not allocate ICT table\n");
goto fail2;
}
err = iwm_dma_contig_alloc(sc->sc_dmat, &sc->sched_dma,
__arraycount(sc->txq) * sizeof(struct iwm_agn_scd_bc_tbl), 1024);
if (err) {
aprint_error_dev(sc->sc_dev,
"could not allocate TX scheduler rings\n");
goto fail3;
}
for (txq_i = 0; txq_i < __arraycount(sc->txq); txq_i++) {
err = iwm_alloc_tx_ring(sc, &sc->txq[txq_i], txq_i);
if (err) {
aprint_error_dev(sc->sc_dev,
"could not allocate TX ring %d\n", txq_i);
goto fail4;
}
}
err = iwm_alloc_rx_ring(sc, &sc->rxq);
if (err) {
aprint_error_dev(sc->sc_dev, "could not allocate RX ring\n");
goto fail5;
}
IWM_WRITE(sc, IWM_CSR_INT, 0xffffffff);
if ((err = sysctl_createv(&sc->sc_clog, 0, NULL, &node,
0, CTLTYPE_NODE, device_xname(sc->sc_dev),
SYSCTL_DESCR("iwm per-controller controls"),
NULL, 0, NULL, 0,
CTL_HW, iwm_sysctl_root_num, CTL_CREATE,
CTL_EOL)) != 0) {
aprint_normal_dev(sc->sc_dev,
"couldn't create iwm per-controller sysctl node\n");
}
if (err == 0) {
int iwm_nodenum = node->sysctl_num;
if ((err = sysctl_createv(&sc->sc_clog, 0, NULL, &node,
CTLFLAG_READWRITE, CTLTYPE_INT, "fw_loaded",
SYSCTL_DESCR("Reload firmware"),
iwm_sysctl_fw_loaded_handler, 0, (void *)sc, 0,
CTL_HW, iwm_sysctl_root_num, iwm_nodenum, CTL_CREATE,
CTL_EOL)) != 0) {
aprint_normal_dev(sc->sc_dev,
"couldn't create load_fw sysctl node\n");
}
}
callout_init(&sc->sc_calib_to, 0);
callout_setfunc(&sc->sc_calib_to, iwm_calib_timeout, sc);
callout_init(&sc->sc_led_blink_to, 0);
callout_setfunc(&sc->sc_led_blink_to, iwm_led_blink_timeout, sc);
if (workqueue_create(&sc->sc_setratewq, "iwmsr",
iwm_setrates_cb, sc, PRI_NONE, IPL_NET, 0))
panic("%s: could not create workqueue: setrates",
device_xname(self));
#ifndef IEEE80211_NO_HT
if (workqueue_create(&sc->sc_bawq, "iwmba",
iwm_ba_cb, sc, PRI_NONE, IPL_NET, 0))
panic("%s: could not create workqueue: blockack",
device_xname(self));
if (workqueue_create(&sc->sc_htprowq, "iwmhtpro",
iwm_htprot_cb, sc, PRI_NONE, IPL_NET, 0))
panic("%s: could not create workqueue: htprot",
device_xname(self));
#endif
config_mountroot(self, iwm_attach_hook);
return;
fail5: while (--txq_i >= 0)
iwm_free_tx_ring(sc, &sc->txq[txq_i]);
fail4: iwm_dma_contig_free(&sc->sched_dma);
fail3: if (sc->ict_dma.vaddr != NULL)
iwm_dma_contig_free(&sc->ict_dma);
fail2: iwm_dma_contig_free(&sc->kw_dma);
fail1: iwm_dma_contig_free(&sc->fw_dma);
}
static int
iwm_config_complete(struct iwm_softc *sc)
{
device_t self = sc->sc_dev;
struct ieee80211com *ic = &sc->sc_ic;
struct ifnet *ifp = &sc->sc_ec.ec_if;
int err;
KASSERT(!ISSET(sc->sc_flags, IWM_FLAG_ATTACHED));
err = iwm_preinit(sc, true);
if (err)
return err;
ic->ic_ifp = ifp;
ic->ic_phytype = IEEE80211_T_OFDM;
ic->ic_opmode = IEEE80211_M_STA;
ic->ic_state = IEEE80211_S_INIT;
ic->ic_caps =
IEEE80211_C_WEP |
IEEE80211_C_WPA |
#ifdef notyet
IEEE80211_C_SCANALL |
IEEE80211_C_SCANALLBAND |
#endif
IEEE80211_C_SHSLOT |
IEEE80211_C_SHPREAMBLE;
#ifndef IEEE80211_NO_HT
ic->ic_htcaps = IEEE80211_HTCAP_SGI20;
ic->ic_htxcaps = 0;
ic->ic_txbfcaps = 0;
ic->ic_aselcaps = 0;
ic->ic_ampdu_params = (IEEE80211_AMPDU_PARAM_SS_4 | 0x3 );
#endif
ic->ic_sup_rates[IEEE80211_MODE_11B] = ieee80211_std_rateset_11b;
ic->ic_sup_rates[IEEE80211_MODE_11G] = ieee80211_std_rateset_11g;
if (sc->sc_nvm.sku_cap_band_52GHz_enable)
ic->ic_sup_rates[IEEE80211_MODE_11A] = ieee80211_std_rateset_11a;
#ifndef IEEE80211_NO_HT
if (sc->sc_nvm.sku_cap_11n_enable)
iwm_setup_ht_rates(sc);
#endif
for (int i = 0; i < __arraycount(sc->sc_phyctxt); i++) {
sc->sc_phyctxt[i].id = i;
}
sc->sc_amrr.amrr_min_success_threshold = 1;
sc->sc_amrr.amrr_max_success_threshold = 15;
ic->ic_ibss_chan = &ic->ic_channels[1];
#if 0
ic->ic_max_rssi = IWM_MAX_DBM - IWM_MIN_DBM;
#endif
ifp->if_softc = sc;
ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
ifp->if_init = iwm_init;
ifp->if_stop = iwm_stop;
ifp->if_ioctl = iwm_ioctl;
ifp->if_start = iwm_start;
ifp->if_watchdog = iwm_watchdog;
IFQ_SET_READY(&ifp->if_snd);
memcpy(ifp->if_xname, DEVNAME(sc), IFNAMSIZ);
if_initialize(ifp);
ieee80211_ifattach(ic);
ifp->if_percpuq = if_percpuq_create(ifp);
if_register(ifp);
ic->ic_node_alloc = iwm_node_alloc;
sc->sc_newstate = ic->ic_newstate;
ic->ic_newstate = iwm_newstate;
mutex_init(&sc->sc_media_mtx, MUTEX_DEFAULT, IPL_SOFTNET);
ieee80211_media_init_with_lock(ic,
iwm_media_change, ieee80211_media_status, &sc->sc_media_mtx);
ieee80211_announce(ic);
iwm_radiotap_attach(sc);
if (pmf_device_register(self, NULL, NULL))
pmf_class_network_register(self, ifp);
else
aprint_error_dev(self, "couldn't establish power handler\n");
sc->sc_flags |= IWM_FLAG_ATTACHED;
return 0;
}
void
iwm_radiotap_attach(struct iwm_softc *sc)
{
struct ifnet *ifp = IC2IFP(&sc->sc_ic);
bpf_attach2(ifp, DLT_IEEE802_11_RADIO,
sizeof (struct ieee80211_frame) + IEEE80211_RADIOTAP_HDRLEN,
&sc->sc_drvbpf);
sc->sc_rxtap_len = sizeof sc->sc_rxtapu;
sc->sc_rxtap.wr_ihdr.it_len = htole16(sc->sc_rxtap_len);
sc->sc_rxtap.wr_ihdr.it_present = htole32(IWM_RX_RADIOTAP_PRESENT);
sc->sc_txtap_len = sizeof sc->sc_txtapu;
sc->sc_txtap.wt_ihdr.it_len = htole16(sc->sc_txtap_len);
sc->sc_txtap.wt_ihdr.it_present = htole32(IWM_TX_RADIOTAP_PRESENT);
}
#if 0
static void
iwm_init_task(void *arg)
{
struct iwm_softc *sc = arg;
struct ifnet *ifp = IC2IFP(&sc->sc_ic);
int s;
rw_enter_write(&sc->ioctl_rwl);
s = splnet();
iwm_stop(ifp, 0);
if ((ifp->if_flags & (IFF_UP | IFF_RUNNING)) == IFF_UP)
iwm_init(ifp);
splx(s);
rw_exit(&sc->ioctl_rwl);
}
static void
iwm_wakeup(struct iwm_softc *sc)
{
pcireg_t reg;
reg = pci_conf_read(sc->sc_pct, sc->sc_pcitag, 0x40);
pci_conf_write(sc->sc_pct, sc->sc_pcitag, 0x40, reg & ~0xff00);
iwm_init_task(sc);
}
static int
iwm_activate(device_t self, enum devact act)
{
struct iwm_softc *sc = device_private(self);
struct ifnet *ifp = IC2IFP(&sc->sc_ic);
switch (act) {
case DVACT_DEACTIVATE:
if (ifp->if_flags & IFF_RUNNING)
iwm_stop(ifp, 1);
return 0;
default:
return EOPNOTSUPP;
}
}
#endif
CFATTACH_DECL_NEW(iwm, sizeof(struct iwm_softc), iwm_match, iwm_attach,
NULL, NULL);
static int
iwm_sysctl_fw_loaded_handler(SYSCTLFN_ARGS)
{
struct sysctlnode node;
struct iwm_softc *sc;
int err, t;
node = *rnode;
sc = node.sysctl_data;
t = ISSET(sc->sc_flags, IWM_FLAG_FW_LOADED) ? 1 : 0;
node.sysctl_data = &t;
err = sysctl_lookup(SYSCTLFN_CALL(&node));
if (err || newp == NULL)
return err;
if (t == 0)
CLR(sc->sc_flags, IWM_FLAG_FW_LOADED);
return 0;
}
SYSCTL_SETUP(sysctl_iwm, "sysctl iwm(4) subtree setup")
{
const struct sysctlnode *rnode;
#ifdef IWM_DEBUG
const struct sysctlnode *cnode;
#endif
int rc;
if ((rc = sysctl_createv(clog, 0, NULL, &rnode,
CTLFLAG_PERMANENT, CTLTYPE_NODE, "iwm",
SYSCTL_DESCR("iwm global controls"),
NULL, 0, NULL, 0, CTL_HW, CTL_CREATE, CTL_EOL)) != 0)
goto err;
iwm_sysctl_root_num = rnode->sysctl_num;
#ifdef IWM_DEBUG
if ((rc = sysctl_createv(clog, 0, &rnode, &cnode,
CTLFLAG_PERMANENT|CTLFLAG_READWRITE, CTLTYPE_INT,
"debug", SYSCTL_DESCR("Enable debugging output"),
NULL, 0, &iwm_debug, 0, CTL_CREATE, CTL_EOL)) != 0)
goto err;
#endif
return;
err:
aprint_error("%s: sysctl_createv failed (rc = %d)\n", __func__, rc);
}