/*-
 * SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) AND ISC
 */

/*	$OpenBSD: if_iwx.c,v 1.175 2023/07/05 15:07:28 stsp Exp $	*/

/*
 *
 * Copyright (c) 2025 The FreeBSD Foundation
 *
 * Portions of this software were developed by Tom Jones <thj@FreeBSD.org>
 * under sponsorship from the FreeBSD Foundation.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 */

/*-
 * Copyright (c) 2024 Future Crew, LLC
 *   Author: Mikhail Pchelin <misha@FreeBSD.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * Copyright (c) 2014, 2016 genua gmbh <info@genua.de>
 *   Author: Stefan Sperling <stsp@openbsd.org>
 * Copyright (c) 2014 Fixup Software Ltd.
 * Copyright (c) 2017, 2019, 2020 Stefan Sperling <stsp@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*-
 * Based on BSD-licensed source modules in the Linux iwlwifi driver,
 * which were used as the reference documentation for this implementation.
 *
 ******************************************************************************
 *
 * This file is provided under a dual BSD/GPLv2 license.  When using or
 * redistributing this file, you may do so under either license.
 *
 * GPL LICENSE SUMMARY
 *
 * Copyright(c) 2017 Intel Deutschland GmbH
 * Copyright(c) 2018 - 2019 Intel Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * BSD LICENSE
 *
 * Copyright(c) 2017 Intel Deutschland GmbH
 * Copyright(c) 2018 - 2019 Intel Corporation
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *  * Neither the name Intel Corporation nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *****************************************************************************
 */

/*-
 * Copyright (c) 2007-2010 Damien Bergamini <damien.bergamini@free.fr>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/param.h>
#include <sys/bus.h>
#include <sys/module.h>
#include <sys/conf.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/mutex.h>
#include <sys/proc.h>
#include <sys/rman.h>
#include <sys/rwlock.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/systm.h>
#include <sys/endian.h>
#include <sys/linker.h>
#include <sys/firmware.h>
#include <sys/epoch.h>
#include <sys/kdb.h>

#include <machine/_inttypes.h>
#include <machine/bus.h>
#include <machine/endian.h>
#include <machine/resource.h>

#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>

#include <net/bpf.h>

#include <net/if.h>
#include <net/if_var.h>
#include <net/if_dl.h>
#include <net/if_media.h>

#include <netinet/in.h>
#include <netinet/if_ether.h>

#include <net80211/ieee80211_var.h>
#include <net80211/ieee80211_radiotap.h>
#include <net80211/ieee80211_regdomain.h>
#include <net80211/ieee80211_ratectl.h>
#include <net80211/ieee80211_vht.h>

int iwx_himark = 224;
int iwx_lomark = 192;

#define IWX_FBSD_RSP_V3 3
#define IWX_FBSD_RSP_V4 4

#define DEVNAME(_sc)    (device_get_nameunit((_sc)->sc_dev))
#define IC2IFP(ic) (((struct ieee80211vap *)TAILQ_FIRST(&(ic)->ic_vaps))->iv_ifp)

#define le16_to_cpup(_a_) (le16toh(*(const uint16_t *)(_a_)))
#define le32_to_cpup(_a_) (le32toh(*(const uint32_t *)(_a_)))

#include <dev/iwx/if_iwxreg.h>
#include <dev/iwx/if_iwxvar.h>

#include <dev/iwx/if_iwx_debug.h>

#define	PCI_CFG_RETRY_TIMEOUT	0x41

#define PCI_VENDOR_INTEL		0x8086
#define	PCI_PRODUCT_INTEL_WL_22500_1	0x2723		/* Wi-Fi 6 AX200 */
#define	PCI_PRODUCT_INTEL_WL_22500_2	0x02f0		/* Wi-Fi 6 AX201 */
#define	PCI_PRODUCT_INTEL_WL_22500_3	0xa0f0		/* Wi-Fi 6 AX201 */
#define	PCI_PRODUCT_INTEL_WL_22500_4	0x34f0		/* Wi-Fi 6 AX201 */
#define	PCI_PRODUCT_INTEL_WL_22500_5	0x06f0		/* Wi-Fi 6 AX201 */
#define	PCI_PRODUCT_INTEL_WL_22500_6	0x43f0		/* Wi-Fi 6 AX201 */
#define	PCI_PRODUCT_INTEL_WL_22500_7	0x3df0		/* Wi-Fi 6 AX201 */
#define	PCI_PRODUCT_INTEL_WL_22500_8	0x4df0		/* Wi-Fi 6 AX201 */
#define	PCI_PRODUCT_INTEL_WL_22500_9	0x2725		/* Wi-Fi 6 AX210 */
#define	PCI_PRODUCT_INTEL_WL_22500_10	0x2726		/* Wi-Fi 6 AX211 */
#define	PCI_PRODUCT_INTEL_WL_22500_11	0x51f0		/* Wi-Fi 6 AX211 */
#define	PCI_PRODUCT_INTEL_WL_22500_12	0x7a70		/* Wi-Fi 6 AX211 */
#define	PCI_PRODUCT_INTEL_WL_22500_13	0x7af0		/* Wi-Fi 6 AX211 */
#define	PCI_PRODUCT_INTEL_WL_22500_14	0x7e40		/* Wi-Fi 6 AX210 */
#define	PCI_PRODUCT_INTEL_WL_22500_15	0x7f70		/* Wi-Fi 6 AX211 */
#define	PCI_PRODUCT_INTEL_WL_22500_16	0x54f0		/* Wi-Fi 6 AX211 */
#define	PCI_PRODUCT_INTEL_WL_22500_17	0x51f1		/* Wi-Fi 6 AX211 */

static const struct iwx_devices {
	uint16_t		device;
	char			*name;
} iwx_devices[] = {
	{ PCI_PRODUCT_INTEL_WL_22500_1,		"Wi-Fi 6 AX200"	},
	{ PCI_PRODUCT_INTEL_WL_22500_2,		"Wi-Fi 6 AX201"	},
	{ PCI_PRODUCT_INTEL_WL_22500_3,		"Wi-Fi 6 AX201"	},
	{ PCI_PRODUCT_INTEL_WL_22500_4,		"Wi-Fi 6 AX201"	},
	{ PCI_PRODUCT_INTEL_WL_22500_5,		"Wi-Fi 6 AX201"	},
	{ PCI_PRODUCT_INTEL_WL_22500_6,		"Wi-Fi 6 AX201"	},
	{ PCI_PRODUCT_INTEL_WL_22500_7,		"Wi-Fi 6 AX201"	},
	{ PCI_PRODUCT_INTEL_WL_22500_8,		"Wi-Fi 6 AX201"	},
	{ PCI_PRODUCT_INTEL_WL_22500_9,		"Wi-Fi 6 AX210"	},
	{ PCI_PRODUCT_INTEL_WL_22500_10,	"Wi-Fi 6 AX211"	},
	{ PCI_PRODUCT_INTEL_WL_22500_11,	"Wi-Fi 6 AX211"	},
	{ PCI_PRODUCT_INTEL_WL_22500_12,	"Wi-Fi 6 AX211"	},
	{ PCI_PRODUCT_INTEL_WL_22500_13,	"Wi-Fi 6 AX211"	},
	{ PCI_PRODUCT_INTEL_WL_22500_14,	"Wi-Fi 6 AX210"	},
	{ PCI_PRODUCT_INTEL_WL_22500_15,	"Wi-Fi 6 AX211"	},
	{ PCI_PRODUCT_INTEL_WL_22500_16,	"Wi-Fi 6 AX211"	},
	{ PCI_PRODUCT_INTEL_WL_22500_17,	"Wi-Fi 6 AX211"	},
};

static const uint8_t iwx_nvm_channels_8000[] = {
	/* 2.4 GHz */
	1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
	/* 5 GHz */
	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
};

static const uint8_t iwx_nvm_channels_uhb[] = {
	/* 2.4 GHz */
	1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
	/* 5 GHz */
	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,
	/* 6-7 GHz */
	1, 5, 9, 13, 17, 21, 25, 29, 33, 37, 41, 45, 49, 53, 57, 61, 65, 69,
	73, 77, 81, 85, 89, 93, 97, 101, 105, 109, 113, 117, 121, 125, 129,
	133, 137, 141, 145, 149, 153, 157, 161, 165, 169, 173, 177, 181, 185,
	189, 193, 197, 201, 205, 209, 213, 217, 221, 225, 229, 233
};

#define IWX_NUM_2GHZ_CHANNELS	14
#define IWX_NUM_5GHZ_CHANNELS	37

const struct iwx_rate {
	uint16_t rate;
	uint8_t plcp;
	uint8_t ht_plcp;
} iwx_rates[] = {
		/* Legacy */		/* HT */
	{   2,	IWX_RATE_1M_PLCP,	IWX_RATE_HT_SISO_MCS_INV_PLCP  },
	{   4,	IWX_RATE_2M_PLCP,	IWX_RATE_HT_SISO_MCS_INV_PLCP },
	{  11,	IWX_RATE_5M_PLCP,	IWX_RATE_HT_SISO_MCS_INV_PLCP  },
	{  22,	IWX_RATE_11M_PLCP,	IWX_RATE_HT_SISO_MCS_INV_PLCP },
	{  12,	IWX_RATE_6M_PLCP,	IWX_RATE_HT_SISO_MCS_0_PLCP },
	{  18,	IWX_RATE_9M_PLCP,	IWX_RATE_HT_SISO_MCS_INV_PLCP  },
	{  24,	IWX_RATE_12M_PLCP,	IWX_RATE_HT_SISO_MCS_1_PLCP },
	{  26,	IWX_RATE_INVM_PLCP,	IWX_RATE_HT_MIMO2_MCS_8_PLCP },
	{  36,	IWX_RATE_18M_PLCP,	IWX_RATE_HT_SISO_MCS_2_PLCP },
	{  48,	IWX_RATE_24M_PLCP,	IWX_RATE_HT_SISO_MCS_3_PLCP },
	{  52,	IWX_RATE_INVM_PLCP,	IWX_RATE_HT_MIMO2_MCS_9_PLCP },
	{  72,	IWX_RATE_36M_PLCP,	IWX_RATE_HT_SISO_MCS_4_PLCP },
	{  78,	IWX_RATE_INVM_PLCP,	IWX_RATE_HT_MIMO2_MCS_10_PLCP },
	{  96,	IWX_RATE_48M_PLCP,	IWX_RATE_HT_SISO_MCS_5_PLCP },
	{ 104,	IWX_RATE_INVM_PLCP,	IWX_RATE_HT_MIMO2_MCS_11_PLCP },
	{ 108,	IWX_RATE_54M_PLCP,	IWX_RATE_HT_SISO_MCS_6_PLCP },
	{ 128,	IWX_RATE_INVM_PLCP,	IWX_RATE_HT_SISO_MCS_7_PLCP },
	{ 156,	IWX_RATE_INVM_PLCP,	IWX_RATE_HT_MIMO2_MCS_12_PLCP },
	{ 208,	IWX_RATE_INVM_PLCP,	IWX_RATE_HT_MIMO2_MCS_13_PLCP },
	{ 234,	IWX_RATE_INVM_PLCP,	IWX_RATE_HT_MIMO2_MCS_14_PLCP },
	{ 260,	IWX_RATE_INVM_PLCP,	IWX_RATE_HT_MIMO2_MCS_15_PLCP },
};
#define IWX_RIDX_CCK	0
#define IWX_RIDX_OFDM	4
#define IWX_RIDX_MAX	(nitems(iwx_rates)-1)
#define IWX_RIDX_IS_CCK(_i_) ((_i_) < IWX_RIDX_OFDM)
#define IWX_RIDX_IS_OFDM(_i_) ((_i_) >= IWX_RIDX_OFDM)
#define IWX_RVAL_IS_OFDM(_i_) ((_i_) >= 12 && (_i_) != 22)

/* Convert an MCS index into an iwx_rates[] index. */
const int iwx_mcs2ridx[] = {
	IWX_RATE_MCS_0_INDEX,
	IWX_RATE_MCS_1_INDEX,
	IWX_RATE_MCS_2_INDEX,
	IWX_RATE_MCS_3_INDEX,
	IWX_RATE_MCS_4_INDEX,
	IWX_RATE_MCS_5_INDEX,
	IWX_RATE_MCS_6_INDEX,
	IWX_RATE_MCS_7_INDEX,
	IWX_RATE_MCS_8_INDEX,
	IWX_RATE_MCS_9_INDEX,
	IWX_RATE_MCS_10_INDEX,
	IWX_RATE_MCS_11_INDEX,
	IWX_RATE_MCS_12_INDEX,
	IWX_RATE_MCS_13_INDEX,
	IWX_RATE_MCS_14_INDEX,
	IWX_RATE_MCS_15_INDEX,
};

static uint8_t	iwx_lookup_cmd_ver(struct iwx_softc *, uint8_t, uint8_t);
static uint8_t	iwx_lookup_notif_ver(struct iwx_softc *, uint8_t, uint8_t);
static int	iwx_store_cscheme(struct iwx_softc *, const uint8_t *, size_t);
#if 0
static int	iwx_alloc_fw_monitor_block(struct iwx_softc *, uint8_t, uint8_t);
static int	iwx_alloc_fw_monitor(struct iwx_softc *, uint8_t);
#endif
static int	iwx_apply_debug_destination(struct iwx_softc *);
static void	iwx_set_ltr(struct iwx_softc *);
static int	iwx_ctxt_info_init(struct iwx_softc *, const struct iwx_fw_sects *);
static int	iwx_ctxt_info_gen3_init(struct iwx_softc *,
	    const struct iwx_fw_sects *);
static void	iwx_ctxt_info_free_fw_img(struct iwx_softc *);
static void	iwx_ctxt_info_free_paging(struct iwx_softc *);
static int	iwx_init_fw_sec(struct iwx_softc *, const struct iwx_fw_sects *,
	    struct iwx_context_info_dram *);
static void	iwx_fw_version_str(char *, size_t, uint32_t, uint32_t, uint32_t);
static int	iwx_firmware_store_section(struct iwx_softc *, enum iwx_ucode_type,
	    const uint8_t *, size_t);
static int	iwx_set_default_calib(struct iwx_softc *, const void *);
static void	iwx_fw_info_free(struct iwx_fw_info *);
static int	iwx_read_firmware(struct iwx_softc *);
static uint32_t iwx_prph_addr_mask(struct iwx_softc *);
static uint32_t iwx_read_prph_unlocked(struct iwx_softc *, uint32_t);
static uint32_t iwx_read_prph(struct iwx_softc *, uint32_t);
static void	iwx_write_prph_unlocked(struct iwx_softc *, uint32_t, uint32_t);
static void	iwx_write_prph(struct iwx_softc *, uint32_t, uint32_t);
static uint32_t iwx_read_umac_prph(struct iwx_softc *, uint32_t);
static void	iwx_write_umac_prph(struct iwx_softc *, uint32_t, uint32_t);
static int	iwx_read_mem(struct iwx_softc *, uint32_t, void *, int);
static int	iwx_poll_bit(struct iwx_softc *, int, uint32_t, uint32_t, int);
static int	iwx_nic_lock(struct iwx_softc *);
static void	iwx_nic_assert_locked(struct iwx_softc *);
static void	iwx_nic_unlock(struct iwx_softc *);
static int	iwx_set_bits_mask_prph(struct iwx_softc *, uint32_t, uint32_t,
	    uint32_t);
static int	iwx_set_bits_prph(struct iwx_softc *, uint32_t, uint32_t);
static int	iwx_clear_bits_prph(struct iwx_softc *, uint32_t, uint32_t);
static void iwx_dma_map_addr(void *, bus_dma_segment_t *, int, int);
static int iwx_dma_contig_alloc(bus_dma_tag_t, struct iwx_dma_info *,
    bus_size_t, bus_size_t);
static void iwx_dma_contig_free(struct iwx_dma_info *);
static int iwx_alloc_rx_ring(struct iwx_softc *, struct iwx_rx_ring *);
static void	iwx_disable_rx_dma(struct iwx_softc *);
static void	iwx_reset_rx_ring(struct iwx_softc *, struct iwx_rx_ring *);
static void	iwx_free_rx_ring(struct iwx_softc *, struct iwx_rx_ring *);
static int iwx_alloc_tx_ring(struct iwx_softc *, struct iwx_tx_ring *, int);
static void	iwx_reset_tx_ring(struct iwx_softc *, struct iwx_tx_ring *);
static void	iwx_free_tx_ring(struct iwx_softc *, struct iwx_tx_ring *);
static void	iwx_enable_rfkill_int(struct iwx_softc *);
static int	iwx_check_rfkill(struct iwx_softc *);
static void	iwx_enable_interrupts(struct iwx_softc *);
static void	iwx_enable_fwload_interrupt(struct iwx_softc *);
#if 0
static void	iwx_restore_interrupts(struct iwx_softc *);
#endif
static void	iwx_disable_interrupts(struct iwx_softc *);
static void	iwx_ict_reset(struct iwx_softc *);
static int	iwx_set_hw_ready(struct iwx_softc *);
static int	iwx_prepare_card_hw(struct iwx_softc *);
static int	iwx_force_power_gating(struct iwx_softc *);
static void	iwx_apm_config(struct iwx_softc *);
static int	iwx_apm_init(struct iwx_softc *);
static void	iwx_apm_stop(struct iwx_softc *);
static int	iwx_allow_mcast(struct iwx_softc *);
static void	iwx_init_msix_hw(struct iwx_softc *);
static void	iwx_conf_msix_hw(struct iwx_softc *, int);
static int	iwx_clear_persistence_bit(struct iwx_softc *);
static int	iwx_start_hw(struct iwx_softc *);
static void	iwx_stop_device(struct iwx_softc *);
static void	iwx_nic_config(struct iwx_softc *);
static int	iwx_nic_rx_init(struct iwx_softc *);
static int	iwx_nic_init(struct iwx_softc *);
static int	iwx_enable_txq(struct iwx_softc *, int, int, int, int);
static int	iwx_disable_txq(struct iwx_softc *sc, int, int, uint8_t);
static void	iwx_post_alive(struct iwx_softc *);
static int	iwx_schedule_session_protection(struct iwx_softc *,
    struct iwx_node *, uint32_t);
static void	iwx_unprotect_session(struct iwx_softc *, struct iwx_node *);
static void	iwx_init_channel_map(struct ieee80211com *, int, int *,
    struct ieee80211_channel[]);
static int	iwx_mimo_enabled(struct iwx_softc *);
static void	iwx_init_reorder_buffer(struct iwx_reorder_buffer *, uint16_t,
	    uint16_t);
static void	iwx_clear_reorder_buffer(struct iwx_softc *, struct iwx_rxba_data *);
static void	iwx_sta_rx_agg(struct iwx_softc *, struct ieee80211_node *, uint8_t,
	    uint16_t, uint16_t, int, int);
static void	iwx_sta_tx_agg_start(struct iwx_softc *,
    struct ieee80211_node *, uint8_t);
static void	iwx_ba_rx_task(void *, int);
static void	iwx_ba_tx_task(void *, int);
static void	iwx_set_mac_addr_from_csr(struct iwx_softc *, struct iwx_nvm_data *);
static int	iwx_is_valid_mac_addr(const uint8_t *);
static void	iwx_flip_hw_address(uint32_t, uint32_t, uint8_t *);
static int	iwx_nvm_get(struct iwx_softc *);
static int	iwx_load_firmware(struct iwx_softc *);
static int	iwx_start_fw(struct iwx_softc *);
static int	iwx_pnvm_handle_section(struct iwx_softc *, const uint8_t *, size_t);
static int	iwx_pnvm_parse(struct iwx_softc *, const uint8_t *, size_t);
static void	iwx_ctxt_info_gen3_set_pnvm(struct iwx_softc *);
static int	iwx_load_pnvm(struct iwx_softc *);
static int	iwx_send_tx_ant_cfg(struct iwx_softc *, uint8_t);
static int	iwx_send_phy_cfg_cmd(struct iwx_softc *);
static int	iwx_load_ucode_wait_alive(struct iwx_softc *);
static int	iwx_send_dqa_cmd(struct iwx_softc *);
static int	iwx_run_init_mvm_ucode(struct iwx_softc *, int);
static int	iwx_config_ltr(struct iwx_softc *);
static void 	iwx_update_rx_desc(struct iwx_softc *, struct iwx_rx_ring *, int, bus_dma_segment_t *);
static int 	iwx_rx_addbuf(struct iwx_softc *, int, int);
static int	iwx_rxmq_get_signal_strength(struct iwx_softc *, struct iwx_rx_mpdu_desc *);
static void	iwx_rx_rx_phy_cmd(struct iwx_softc *, struct iwx_rx_packet *,
    struct iwx_rx_data *);
static int	iwx_get_noise(const struct iwx_statistics_rx_non_phy *);
static int	iwx_rx_hwdecrypt(struct iwx_softc *, struct mbuf *, uint32_t);
#if 0
int	iwx_ccmp_decap(struct iwx_softc *, struct mbuf *,
	    struct ieee80211_node *, struct ieee80211_rxinfo *);
#endif
static void	iwx_rx_frame(struct iwx_softc *, struct mbuf *, int, uint32_t,
    int, int, uint32_t, uint8_t);
static void	iwx_clear_tx_desc(struct iwx_softc *, struct iwx_tx_ring *, int);
static void	iwx_txd_done(struct iwx_softc *, struct iwx_tx_ring *,
    struct iwx_tx_data *);
static void	iwx_txq_advance(struct iwx_softc *, struct iwx_tx_ring *, uint16_t);
static void	iwx_rx_tx_cmd(struct iwx_softc *, struct iwx_rx_packet *,
	    struct iwx_rx_data *);
static void	iwx_clear_oactive(struct iwx_softc *, struct iwx_tx_ring *);
static void	iwx_rx_bmiss(struct iwx_softc *, struct iwx_rx_packet *,
    struct iwx_rx_data *);
static int	iwx_binding_cmd(struct iwx_softc *, struct iwx_node *, uint32_t);
static uint8_t	iwx_get_vht_ctrl_pos(struct ieee80211com *, struct ieee80211_channel *);
static int	iwx_phy_ctxt_cmd_uhb_v3_v4(struct iwx_softc *,
    struct iwx_phy_ctxt *, uint8_t, uint8_t, uint32_t, uint8_t, uint8_t, int);
#if 0
static int	iwx_phy_ctxt_cmd_v3_v4(struct iwx_softc *, struct iwx_phy_ctxt *,
    uint8_t, uint8_t, uint32_t, uint8_t, uint8_t, int);
#endif
static int	iwx_phy_ctxt_cmd(struct iwx_softc *, struct iwx_phy_ctxt *,
    uint8_t, uint8_t, uint32_t, uint32_t, uint8_t, uint8_t);
static int	iwx_send_cmd(struct iwx_softc *, struct iwx_host_cmd *);
static int	iwx_send_cmd_pdu(struct iwx_softc *, uint32_t, uint32_t, uint16_t,
	    const void *);
static int	iwx_send_cmd_status(struct iwx_softc *, struct iwx_host_cmd *,
	    uint32_t *);
static int	iwx_send_cmd_pdu_status(struct iwx_softc *, uint32_t, uint16_t,
	    const void *, uint32_t *);
static void	iwx_free_resp(struct iwx_softc *, struct iwx_host_cmd *);
static void	iwx_cmd_done(struct iwx_softc *, int, int, int);
static uint32_t iwx_fw_rateidx_ofdm(uint8_t);
static uint32_t iwx_fw_rateidx_cck(uint8_t);
static const struct iwx_rate *iwx_tx_fill_cmd(struct iwx_softc *,
    struct iwx_node *, struct ieee80211_frame *, uint16_t *, uint32_t *,
    struct mbuf *);
static void	iwx_tx_update_byte_tbl(struct iwx_softc *, struct iwx_tx_ring *, int,
	    uint16_t, uint16_t);
static int	iwx_tx(struct iwx_softc *, struct mbuf *,
    struct ieee80211_node *);
static int	iwx_flush_sta_tids(struct iwx_softc *, int, uint16_t);
static int	iwx_drain_sta(struct iwx_softc *sc, struct iwx_node *, int);
static int	iwx_flush_sta(struct iwx_softc *, struct iwx_node *);
static int	iwx_beacon_filter_send_cmd(struct iwx_softc *,
	    struct iwx_beacon_filter_cmd *);
static int	iwx_update_beacon_abort(struct iwx_softc *, struct iwx_node *,
    int);
static void	iwx_power_build_cmd(struct iwx_softc *, struct iwx_node *,
	    struct iwx_mac_power_cmd *);
static int	iwx_power_mac_update_mode(struct iwx_softc *, struct iwx_node *);
static int	iwx_power_update_device(struct iwx_softc *);
#if 0
static int	iwx_enable_beacon_filter(struct iwx_softc *, struct iwx_node *);
#endif
static int	iwx_disable_beacon_filter(struct iwx_softc *);
static int	iwx_add_sta_cmd(struct iwx_softc *, struct iwx_node *, int);
static int	iwx_rm_sta_cmd(struct iwx_softc *, struct iwx_node *);
static int	iwx_rm_sta(struct iwx_softc *, struct iwx_node *);
static int	iwx_fill_probe_req(struct iwx_softc *,
    struct iwx_scan_probe_req *);
static int	iwx_config_umac_scan_reduced(struct iwx_softc *);
static uint16_t iwx_scan_umac_flags_v2(struct iwx_softc *, int);
static void	iwx_scan_umac_dwell_v10(struct iwx_softc *,
	    struct iwx_scan_general_params_v10 *, int);
static void	iwx_scan_umac_fill_general_p_v10(struct iwx_softc *,
	    struct iwx_scan_general_params_v10 *, uint16_t, int);
static void	iwx_scan_umac_fill_ch_p_v6(struct iwx_softc *,
	    struct iwx_scan_channel_params_v6 *, uint32_t, int);
static int	iwx_umac_scan_v14(struct iwx_softc *, int);
static void	iwx_mcc_update(struct iwx_softc *, struct iwx_mcc_chub_notif *);
static uint8_t	iwx_ridx2rate(struct ieee80211_rateset *, int);
static int	iwx_rval2ridx(int);
static void	iwx_ack_rates(struct iwx_softc *, struct iwx_node *, int *,
    int *);
static void	iwx_mac_ctxt_cmd_common(struct iwx_softc *, struct iwx_node *,
	    struct iwx_mac_ctx_cmd *, uint32_t);
static void	iwx_mac_ctxt_cmd_fill_sta(struct iwx_softc *, struct iwx_node *,
	    struct iwx_mac_data_sta *, int);
static int	iwx_mac_ctxt_cmd(struct iwx_softc *, struct iwx_node *,
    uint32_t, int);
static int	iwx_clear_statistics(struct iwx_softc *);
static int	iwx_scan(struct iwx_softc *);
static int	iwx_bgscan(struct ieee80211com *);
static int	iwx_enable_mgmt_queue(struct iwx_softc *);
static int	iwx_disable_mgmt_queue(struct iwx_softc *);
static int	iwx_rs_rval2idx(uint8_t);
static uint16_t iwx_rs_ht_rates(struct iwx_softc *, struct ieee80211_node *,
    int);
static uint16_t iwx_rs_vht_rates(struct iwx_softc *, struct ieee80211_node *, int);
static int	iwx_rs_init_v3(struct iwx_softc *, struct iwx_node *);
static int	iwx_rs_init_v4(struct iwx_softc *, struct iwx_node *);
static int	iwx_rs_init(struct iwx_softc *, struct iwx_node *);
static int	iwx_phy_send_rlc(struct iwx_softc *, struct iwx_phy_ctxt *,
	    uint8_t, uint8_t);
static int	iwx_phy_ctxt_update(struct iwx_softc *, struct iwx_phy_ctxt *,
	    struct ieee80211_channel *, uint8_t, uint8_t, uint32_t, uint8_t,
	    uint8_t);
static int	iwx_auth(struct ieee80211vap *, struct iwx_softc *);
static int	iwx_deauth(struct iwx_softc *);
static int	iwx_run(struct ieee80211vap *, struct iwx_softc *);
static int	iwx_run_stop(struct iwx_softc *);
static struct ieee80211_node * iwx_node_alloc(struct ieee80211vap *,
    const uint8_t[IEEE80211_ADDR_LEN]);
#if 0
int	iwx_set_key(struct ieee80211com *, struct ieee80211_node *,
	    struct ieee80211_key *);
void	iwx_setkey_task(void *);
void	iwx_delete_key(struct ieee80211com *,
	    struct ieee80211_node *, struct ieee80211_key *);
#endif
static int	iwx_newstate(struct ieee80211vap *, enum ieee80211_state, int);
static void	iwx_endscan(struct iwx_softc *);
static void	iwx_fill_sf_command(struct iwx_softc *, struct iwx_sf_cfg_cmd *,
	    struct ieee80211_node *);
static int	iwx_sf_config(struct iwx_softc *, int);
static int	iwx_send_bt_init_conf(struct iwx_softc *);
static int	iwx_send_soc_conf(struct iwx_softc *);
static int	iwx_send_update_mcc_cmd(struct iwx_softc *, const char *);
static int	iwx_send_temp_report_ths_cmd(struct iwx_softc *);
static int	iwx_init_hw(struct iwx_softc *);
static int	iwx_init(struct iwx_softc *);
static void	iwx_stop(struct iwx_softc *);
static void	iwx_watchdog(void *);
static const char *iwx_desc_lookup(uint32_t);
static void	iwx_nic_error(struct iwx_softc *);
static void	iwx_dump_driver_status(struct iwx_softc *);
static void	iwx_nic_umac_error(struct iwx_softc *);
static void	iwx_rx_mpdu_mq(struct iwx_softc *, struct mbuf *, void *, size_t);
static int	iwx_rx_pkt_valid(struct iwx_rx_packet *);
static void	iwx_rx_pkt(struct iwx_softc *, struct iwx_rx_data *,
	    struct mbuf *);
static void	iwx_notif_intr(struct iwx_softc *);
#if 0
/* XXX-THJ - I don't have hardware for this */
static int	iwx_intr(void *);
#endif
static void	iwx_intr_msix(void *);
static int	iwx_preinit(struct iwx_softc *);
static void	iwx_attach_hook(void *);
static const struct iwx_device_cfg *iwx_find_device_cfg(struct iwx_softc *);
static int	iwx_probe(device_t);
static int	iwx_attach(device_t);
static int	iwx_detach(device_t);

/* FreeBSD specific glue */
u_int8_t etherbroadcastaddr[ETHER_ADDR_LEN] =
    { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };

u_int8_t etheranyaddr[ETHER_ADDR_LEN] =
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

#if IWX_DEBUG
#define DPRINTF(x)	do { if (sc->sc_debug == IWX_DEBUG_ANY) { printf x; } } while (0)
#else
#define DPRINTF(x)	do { ; } while (0)
#endif

/* FreeBSD specific functions */
static struct	ieee80211vap * iwx_vap_create(struct ieee80211com *,
    const char[IFNAMSIZ], int, enum ieee80211_opmode, int,
    const uint8_t[IEEE80211_ADDR_LEN], const uint8_t[IEEE80211_ADDR_LEN]);
static void	iwx_vap_delete(struct ieee80211vap *);
static void	iwx_parent(struct ieee80211com *);
static void	iwx_scan_start(struct ieee80211com *);
static void	iwx_scan_end(struct ieee80211com *);
static void	iwx_update_mcast(struct ieee80211com *ic);
static void	iwx_scan_curchan(struct ieee80211_scan_state *, unsigned long);
static void	iwx_scan_mindwell(struct ieee80211_scan_state *);
static void	iwx_set_channel(struct ieee80211com *);
static void	iwx_endscan_cb(void *, int );
static int	iwx_wme_update(struct ieee80211com *);
static int	iwx_raw_xmit(struct ieee80211_node *, struct mbuf *,
    const struct ieee80211_bpf_params *);
static int	iwx_transmit(struct ieee80211com *, struct mbuf *);
static void	iwx_start(struct iwx_softc *);
static int	iwx_ampdu_rx_start(struct ieee80211_node *,
    struct ieee80211_rx_ampdu *, int, int, int);
static void	iwx_ampdu_rx_stop(struct ieee80211_node *,
    struct ieee80211_rx_ampdu *);
static int	iwx_addba_request(struct ieee80211_node *,
    struct ieee80211_tx_ampdu *, int, int, int);
static int	iwx_addba_response(struct ieee80211_node *,
    struct ieee80211_tx_ampdu *, int, int, int);
static void	iwx_key_update_begin(struct ieee80211vap *);
static void	iwx_key_update_end(struct ieee80211vap *);
static int	iwx_key_alloc(struct ieee80211vap *, struct ieee80211_key *,
    ieee80211_keyix *,ieee80211_keyix *);
static int	iwx_key_set(struct ieee80211vap *, const struct ieee80211_key *);
static int	iwx_key_delete(struct ieee80211vap *,
    const struct ieee80211_key *);
static int	iwx_suspend(device_t);
static int	iwx_resume(device_t);
static void	iwx_radiotap_attach(struct iwx_softc *);

/* OpenBSD compat defines */
#define IEEE80211_HTOP0_SCO_SCN 0
#define IEEE80211_VHTOP0_CHAN_WIDTH_HT 0
#define IEEE80211_VHTOP0_CHAN_WIDTH_80 1

#define IEEE80211_HT_RATESET_SISO 0
#define IEEE80211_HT_RATESET_MIMO2 2

const struct ieee80211_rateset ieee80211_std_rateset_11a =
	{ 8, { 12, 18, 24, 36, 48, 72, 96, 108 } };

const struct ieee80211_rateset ieee80211_std_rateset_11b =
	{ 4, { 2, 4, 11, 22 } };

const struct ieee80211_rateset ieee80211_std_rateset_11g =
	{ 12, { 2, 4, 11, 22, 12, 18, 24, 36, 48, 72, 96, 108 } };

inline int
ieee80211_has_addr4(const struct ieee80211_frame *wh)
{
	return (wh->i_fc[1] & IEEE80211_FC1_DIR_MASK) ==
	    IEEE80211_FC1_DIR_DSTODS;
}

static uint8_t
iwx_lookup_cmd_ver(struct iwx_softc *sc, uint8_t grp, uint8_t cmd)
{
	const struct iwx_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 IWX_FW_CMD_VER_UNKNOWN;
}

uint8_t
iwx_lookup_notif_ver(struct iwx_softc *sc, uint8_t grp, uint8_t cmd)
{
	const struct iwx_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->notif_ver;
	}

	return IWX_FW_CMD_VER_UNKNOWN;
}

static int
iwx_store_cscheme(struct iwx_softc *sc, const uint8_t *data, size_t dlen)
{
	const struct iwx_fw_cscheme_list *l = (const void *)data;

	if (dlen < sizeof(*l) ||
	    dlen < sizeof(l->size) + l->size * sizeof(*l->cs))
		return EINVAL;

	/* we don't actually store anything for now, always use s/w crypto */

	return 0;
}

static int
iwx_ctxt_info_alloc_dma(struct iwx_softc *sc,
    const struct iwx_fw_onesect *sec, struct iwx_dma_info *dram)
{
	int err = iwx_dma_contig_alloc(sc->sc_dmat, dram, sec->fws_len, 1);
	if (err) {
		printf("%s: could not allocate context info DMA memory\n",
		    DEVNAME(sc));
		return err;
	}

	memcpy(dram->vaddr, sec->fws_data, sec->fws_len);

	return 0;
}

static void
iwx_ctxt_info_free_paging(struct iwx_softc *sc)
{
	struct iwx_self_init_dram *dram = &sc->init_dram;
	int i;

	if (!dram->paging)
		return;

	/* free paging*/
	for (i = 0; i < dram->paging_cnt; i++)
		iwx_dma_contig_free(&dram->paging[i]);

	free(dram->paging, M_DEVBUF);
	dram->paging_cnt = 0;
	dram->paging = NULL;
}

static int
iwx_get_num_sections(const struct iwx_fw_sects *fws, int start)
{
	int i = 0;

	while (start < fws->fw_count &&
	       fws->fw_sect[start].fws_devoff != IWX_CPU1_CPU2_SEPARATOR_SECTION &&
	       fws->fw_sect[start].fws_devoff != IWX_PAGING_SEPARATOR_SECTION) {
		start++;
		i++;
	}

	return i;
}

static int
iwx_init_fw_sec(struct iwx_softc *sc, const struct iwx_fw_sects *fws,
    struct iwx_context_info_dram *ctxt_dram)
{
	struct iwx_self_init_dram *dram = &sc->init_dram;
	int i, ret, fw_cnt = 0;

	KASSERT(dram->paging == NULL, ("iwx_init_fw_sec"));

	dram->lmac_cnt = iwx_get_num_sections(fws, 0);
	/* add 1 due to separator */
	dram->umac_cnt = iwx_get_num_sections(fws, dram->lmac_cnt + 1);
	/* add 2 due to separators */
	dram->paging_cnt = iwx_get_num_sections(fws,
	    dram->lmac_cnt + dram->umac_cnt + 2);

	IWX_UNLOCK(sc);
	dram->fw = mallocarray(dram->umac_cnt + dram->lmac_cnt,
	    sizeof(*dram->fw), M_DEVBUF,  M_ZERO | M_NOWAIT);
	if (!dram->fw) {
		printf("%s: could not allocate memory for firmware sections\n",
		    DEVNAME(sc));
		IWX_LOCK(sc);
		return ENOMEM;
	}

	dram->paging = mallocarray(dram->paging_cnt, sizeof(*dram->paging),
	    M_DEVBUF, M_ZERO | M_WAITOK);
	IWX_LOCK(sc);
	if (!dram->paging) {
		printf("%s: could not allocate memory for firmware paging\n",
		    DEVNAME(sc));
		return ENOMEM;
	}

	/* initialize lmac sections */
	for (i = 0; i < dram->lmac_cnt; i++) {
		ret = iwx_ctxt_info_alloc_dma(sc, &fws->fw_sect[i],
						   &dram->fw[fw_cnt]);
		if (ret)
			return ret;
		ctxt_dram->lmac_img[i] =
			htole64(dram->fw[fw_cnt].paddr);
		IWX_DPRINTF(sc, IWX_DEBUG_FIRMWARE_TLV,
		    "%s: firmware LMAC section %d at 0x%llx size %lld\n",
		    __func__, i,
		    (unsigned long long)dram->fw[fw_cnt].paddr,
		    (unsigned long long)dram->fw[fw_cnt].size);
		fw_cnt++;
	}

	/* initialize umac sections */
	for (i = 0; i < dram->umac_cnt; i++) {
		/* access FW with +1 to make up for lmac separator */
		ret = iwx_ctxt_info_alloc_dma(sc,
		    &fws->fw_sect[fw_cnt + 1], &dram->fw[fw_cnt]);
		if (ret)
			return ret;
		ctxt_dram->umac_img[i] =
			htole64(dram->fw[fw_cnt].paddr);
		IWX_DPRINTF(sc, IWX_DEBUG_FIRMWARE_TLV,
		    "%s: firmware UMAC section %d at 0x%llx size %lld\n",
		    __func__, i,
		    (unsigned long long)dram->fw[fw_cnt].paddr,
		    (unsigned long long)dram->fw[fw_cnt].size);
		fw_cnt++;
	}

	/*
	 * Initialize paging.
	 * Paging memory isn't stored in dram->fw as the umac and lmac - it is
	 * stored separately.
	 * This is since the timing of its release is different -
	 * while fw memory can be released on alive, the paging memory can be
	 * freed only when the device goes down.
	 * Given that, the logic here in accessing the fw image is a bit
	 * different - fw_cnt isn't changing so loop counter is added to it.
	 */
	for (i = 0; i < dram->paging_cnt; i++) {
		/* access FW with +2 to make up for lmac & umac separators */
		int fw_idx = fw_cnt + i + 2;

		ret = iwx_ctxt_info_alloc_dma(sc,
		    &fws->fw_sect[fw_idx], &dram->paging[i]);
		if (ret)
			return ret;

		ctxt_dram->virtual_img[i] = htole64(dram->paging[i].paddr);
		IWX_DPRINTF(sc, IWX_DEBUG_FIRMWARE_TLV,
		    "%s: firmware paging section %d at 0x%llx size %lld\n",
		    __func__, i,
		    (unsigned long long)dram->paging[i].paddr,
		    (unsigned long long)dram->paging[i].size);
	}

	return 0;
}

static void
iwx_fw_version_str(char *buf, size_t bufsize,
    uint32_t major, uint32_t minor, uint32_t api)
{
	/*
	 * Starting with major version 35 the Linux driver prints the minor
	 * version in hexadecimal.
	 */
	if (major >= 35)
		snprintf(buf, bufsize, "%u.%08x.%u", major, minor, api);
	else
		snprintf(buf, bufsize, "%u.%u.%u", major, minor, api);
}
#if 0
static int
iwx_alloc_fw_monitor_block(struct iwx_softc *sc, uint8_t max_power,
    uint8_t min_power)
{
	struct iwx_dma_info *fw_mon = &sc->fw_mon;
	uint32_t size = 0;
	uint8_t power;
	int err;

	if (fw_mon->size)
		return 0;

	for (power = max_power; power >= min_power; power--) {
		size = (1 << power);

		err = iwx_dma_contig_alloc(sc->sc_dmat, fw_mon, size, 0);
		if (err)
			continue;

		IWX_DPRINTF(sc, IWX_DEBUG_FIRMWARE_TLV,
		    "%s: allocated 0x%08x bytes for firmware monitor.\n",
		    DEVNAME(sc), size);
		break;
	}

	if (err) {
		fw_mon->size = 0;
		return err;
	}

	if (power != max_power)
		IWX_DPRINTF(sc, IWX_DEBUG_FIRMWARE_TLV,
		    "%s: Sorry - debug buffer is only %luK while you requested %luK\n",
		    DEVNAME(sc), (unsigned long)(1 << (power - 10)),
		    (unsigned long)(1 << (max_power - 10)));

	return 0;
}

static int
iwx_alloc_fw_monitor(struct iwx_softc *sc, uint8_t max_power)
{
	if (!max_power) {
		/* default max_power is maximum */
		max_power = 26;
	} else {
		max_power += 11;
	}

	if (max_power > 26) {
		 IWX_DPRINTF(sc, IWX_DEBUG_FIRMWARE_TLV,
		     "%s: External buffer size for monitor is too big %d, "
		     "check the FW TLV\n", DEVNAME(sc), max_power);
		return 0;
	}

	if (sc->fw_mon.size)
		return 0;

	return iwx_alloc_fw_monitor_block(sc, max_power, 11);
}
#endif

static int
iwx_apply_debug_destination(struct iwx_softc *sc)
{
#if 0
	struct iwx_fw_dbg_dest_tlv_v1 *dest_v1;
	int i, err;
	uint8_t mon_mode, size_power, base_shift, end_shift;
	uint32_t base_reg, end_reg;

	dest_v1 = sc->sc_fw.dbg_dest_tlv_v1;
	mon_mode = dest_v1->monitor_mode;
	size_power = dest_v1->size_power;
	base_reg = le32toh(dest_v1->base_reg);
	end_reg = le32toh(dest_v1->end_reg);
	base_shift = dest_v1->base_shift;
	end_shift = dest_v1->end_shift;

	DPRINTF(("%s: applying debug destination %d\n", DEVNAME(sc), mon_mode));

	if (mon_mode == EXTERNAL_MODE) {
		err = iwx_alloc_fw_monitor(sc, size_power);
		if (err)
			return err;
	}

	if (!iwx_nic_lock(sc))
		return EBUSY;

	for (i = 0; i < sc->sc_fw.n_dest_reg; i++) {
		uint32_t addr, val;
		uint8_t op;

		addr = le32toh(dest_v1->reg_ops[i].addr);
		val = le32toh(dest_v1->reg_ops[i].val);
		op = dest_v1->reg_ops[i].op;

		DPRINTF(("%s: op=%u addr=%u val=%u\n", __func__, op, addr, val));
		switch (op) {
		case CSR_ASSIGN:
			IWX_WRITE(sc, addr, val);
			break;
		case CSR_SETBIT:
			IWX_SETBITS(sc, addr, (1 << val));
			break;
		case CSR_CLEARBIT:
			IWX_CLRBITS(sc, addr, (1 << val));
			break;
		case PRPH_ASSIGN:
			iwx_write_prph(sc, addr, val);
			break;
		case PRPH_SETBIT:
			err = iwx_set_bits_prph(sc, addr, (1 << val));
			if (err)
				return err;
			break;
		case PRPH_CLEARBIT:
			err = iwx_clear_bits_prph(sc, addr, (1 << val));
			if (err)
				return err;
			break;
		case PRPH_BLOCKBIT:
			if (iwx_read_prph(sc, addr) & (1 << val))
				goto monitor;
			break;
		default:
			DPRINTF(("%s: FW debug - unknown OP %d\n",
			    DEVNAME(sc), op));
			break;
		}
	}

monitor:
	if (mon_mode == EXTERNAL_MODE && sc->fw_mon.size) {
		iwx_write_prph(sc, le32toh(base_reg),
		    sc->fw_mon.paddr >> base_shift);
		iwx_write_prph(sc, end_reg,
		    (sc->fw_mon.paddr + sc->fw_mon.size - 256)
		    >> end_shift);
	}

	iwx_nic_unlock(sc);
	return 0;
#else
	return 0;
#endif
}

static void
iwx_set_ltr(struct iwx_softc *sc)
{
	uint32_t ltr_val = IWX_CSR_LTR_LONG_VAL_AD_NO_SNOOP_REQ |
	    ((IWX_CSR_LTR_LONG_VAL_AD_SCALE_USEC <<
	    IWX_CSR_LTR_LONG_VAL_AD_NO_SNOOP_SCALE_SHIFT) &
	    IWX_CSR_LTR_LONG_VAL_AD_NO_SNOOP_SCALE_MASK) |
	    ((250 << IWX_CSR_LTR_LONG_VAL_AD_NO_SNOOP_VAL_SHIFT) &
	    IWX_CSR_LTR_LONG_VAL_AD_NO_SNOOP_VAL_MASK) |
	    IWX_CSR_LTR_LONG_VAL_AD_SNOOP_REQ |
	    ((IWX_CSR_LTR_LONG_VAL_AD_SCALE_USEC <<
	    IWX_CSR_LTR_LONG_VAL_AD_SNOOP_SCALE_SHIFT) &
	    IWX_CSR_LTR_LONG_VAL_AD_SNOOP_SCALE_MASK) |
	    (250 & IWX_CSR_LTR_LONG_VAL_AD_SNOOP_VAL);

	/*
	 * To workaround hardware latency issues during the boot process,
	 * initialize the LTR to ~250 usec (see ltr_val above).
	 * The firmware initializes this again later (to a smaller value).
	 */
	if (!sc->sc_integrated) {
		IWX_WRITE(sc, IWX_CSR_LTR_LONG_VAL_AD, ltr_val);
	} else if (sc->sc_integrated &&
		   sc->sc_device_family == IWX_DEVICE_FAMILY_22000) {
		iwx_write_prph(sc, IWX_HPM_MAC_LTR_CSR,
		    IWX_HPM_MAC_LRT_ENABLE_ALL);
		iwx_write_prph(sc, IWX_HPM_UMAC_LTR, ltr_val);
	}
}

int
iwx_ctxt_info_init(struct iwx_softc *sc, const struct iwx_fw_sects *fws)
{
	struct iwx_context_info *ctxt_info;
	struct iwx_context_info_rbd_cfg *rx_cfg;
	uint32_t control_flags = 0;
	uint64_t paddr;
	int err;

	ctxt_info = sc->ctxt_info_dma.vaddr;
	memset(ctxt_info, 0, sizeof(*ctxt_info));

	ctxt_info->version.version = 0;
	ctxt_info->version.mac_id =
		htole16((uint16_t)IWX_READ(sc, IWX_CSR_HW_REV));
	/* size is in DWs */
	ctxt_info->version.size = htole16(sizeof(*ctxt_info) / 4);

	KASSERT(IWX_RX_QUEUE_CB_SIZE(IWX_MQ_RX_TABLE_SIZE) < 0xF,
	    ("IWX_RX_QUEUE_CB_SIZE exceeds rate table size"));

	control_flags = IWX_CTXT_INFO_TFD_FORMAT_LONG |
			(IWX_RX_QUEUE_CB_SIZE(IWX_MQ_RX_TABLE_SIZE) <<
			 IWX_CTXT_INFO_RB_CB_SIZE_POS) |
			(IWX_CTXT_INFO_RB_SIZE_4K << IWX_CTXT_INFO_RB_SIZE_POS);
	ctxt_info->control.control_flags = htole32(control_flags);

	/* initialize RX default queue */
	rx_cfg = &ctxt_info->rbd_cfg;
	rx_cfg->free_rbd_addr = htole64(sc->rxq.free_desc_dma.paddr);
	rx_cfg->used_rbd_addr = htole64(sc->rxq.used_desc_dma.paddr);
	rx_cfg->status_wr_ptr = htole64(sc->rxq.stat_dma.paddr);

	/* initialize TX command queue */
	ctxt_info->hcmd_cfg.cmd_queue_addr =
	    htole64(sc->txq[IWX_DQA_CMD_QUEUE].desc_dma.paddr);
	ctxt_info->hcmd_cfg.cmd_queue_size =
		IWX_TFD_QUEUE_CB_SIZE(IWX_TX_RING_COUNT);

	/* allocate ucode sections in dram and set addresses */
	err = iwx_init_fw_sec(sc, fws, &ctxt_info->dram);
	if (err) {
		iwx_ctxt_info_free_fw_img(sc);
		return err;
	}

	/* Configure debug, if exists */
	if (sc->sc_fw.dbg_dest_tlv_v1) {
#if 1
		err = iwx_apply_debug_destination(sc);
		if (err) {
			iwx_ctxt_info_free_fw_img(sc);
			return err;
		}
#endif
	}

	/*
	 * Write the context info DMA base address. The device expects a
	 * 64-bit address but a simple bus_space_write_8 to this register
	 * won't work on some devices, such as the AX201.
	 */
	paddr = sc->ctxt_info_dma.paddr;
	IWX_WRITE(sc, IWX_CSR_CTXT_INFO_BA, paddr & 0xffffffff);
	IWX_WRITE(sc, IWX_CSR_CTXT_INFO_BA + 4, paddr >> 32);

	/* kick FW self load */
	if (!iwx_nic_lock(sc)) {
		iwx_ctxt_info_free_fw_img(sc);
		return EBUSY;
	}

	iwx_set_ltr(sc);
	iwx_write_prph(sc, IWX_UREG_CPU_INIT_RUN, 1);
	iwx_nic_unlock(sc);

	/* Context info will be released upon alive or failure to get one */

	return 0;
}

static int
iwx_ctxt_info_gen3_init(struct iwx_softc *sc, const struct iwx_fw_sects *fws)
{
	struct iwx_context_info_gen3 *ctxt_info_gen3;
	struct iwx_prph_scratch *prph_scratch;
	struct iwx_prph_scratch_ctrl_cfg *prph_sc_ctrl;
	uint16_t cb_size;
	uint32_t control_flags, scratch_size;
	uint64_t paddr;
	int err;

	if (sc->sc_fw.iml == NULL || sc->sc_fw.iml_len == 0) {
		printf("%s: no image loader found in firmware file\n",
		    DEVNAME(sc));
		iwx_ctxt_info_free_fw_img(sc);
		return EINVAL;
	}

	err = iwx_dma_contig_alloc(sc->sc_dmat, &sc->iml_dma,
	    sc->sc_fw.iml_len, 1);
	if (err) {
		printf("%s: could not allocate DMA memory for "
		    "firmware image loader\n", DEVNAME(sc));
		iwx_ctxt_info_free_fw_img(sc);
		return ENOMEM;
	}

	prph_scratch = sc->prph_scratch_dma.vaddr;
	memset(prph_scratch, 0, sizeof(*prph_scratch));
	prph_sc_ctrl = &prph_scratch->ctrl_cfg;
	prph_sc_ctrl->version.version = 0;
	prph_sc_ctrl->version.mac_id = htole16(IWX_READ(sc, IWX_CSR_HW_REV));
	prph_sc_ctrl->version.size = htole16(sizeof(*prph_scratch) / 4);

	control_flags = IWX_PRPH_SCRATCH_RB_SIZE_4K |
	    IWX_PRPH_SCRATCH_MTR_MODE |
	    (IWX_PRPH_MTR_FORMAT_256B & IWX_PRPH_SCRATCH_MTR_FORMAT);
	if (sc->sc_imr_enabled)
		control_flags |= IWX_PRPH_SCRATCH_IMR_DEBUG_EN;
	prph_sc_ctrl->control.control_flags = htole32(control_flags);

	/* initialize RX default queue */
	prph_sc_ctrl->rbd_cfg.free_rbd_addr =
	    htole64(sc->rxq.free_desc_dma.paddr);

	/* allocate ucode sections in dram and set addresses */
	err = iwx_init_fw_sec(sc, fws, &prph_scratch->dram);
	if (err) {
		iwx_dma_contig_free(&sc->iml_dma);
		iwx_ctxt_info_free_fw_img(sc);
		return err;
	}

	ctxt_info_gen3 = sc->ctxt_info_dma.vaddr;
	memset(ctxt_info_gen3, 0, sizeof(*ctxt_info_gen3));
	ctxt_info_gen3->prph_info_base_addr = htole64(sc->prph_info_dma.paddr);
	ctxt_info_gen3->prph_scratch_base_addr =
	    htole64(sc->prph_scratch_dma.paddr);
	scratch_size = sizeof(*prph_scratch);
	ctxt_info_gen3->prph_scratch_size = htole32(scratch_size);
	ctxt_info_gen3->cr_head_idx_arr_base_addr =
	    htole64(sc->rxq.stat_dma.paddr);
	ctxt_info_gen3->tr_tail_idx_arr_base_addr =
	    htole64(sc->prph_info_dma.paddr + PAGE_SIZE / 2);
	ctxt_info_gen3->cr_tail_idx_arr_base_addr =
	    htole64(sc->prph_info_dma.paddr + 3 * PAGE_SIZE / 4);
	ctxt_info_gen3->mtr_base_addr =
	    htole64(sc->txq[IWX_DQA_CMD_QUEUE].desc_dma.paddr);
	ctxt_info_gen3->mcr_base_addr = htole64(sc->rxq.used_desc_dma.paddr);
	cb_size = IWX_TFD_QUEUE_CB_SIZE(IWX_TX_RING_COUNT);
	ctxt_info_gen3->mtr_size = htole16(cb_size);
	cb_size = IWX_RX_QUEUE_CB_SIZE(IWX_MQ_RX_TABLE_SIZE);
	ctxt_info_gen3->mcr_size = htole16(cb_size);

	memcpy(sc->iml_dma.vaddr, sc->sc_fw.iml, sc->sc_fw.iml_len);

	paddr = sc->ctxt_info_dma.paddr;
	IWX_WRITE(sc, IWX_CSR_CTXT_INFO_ADDR, paddr & 0xffffffff);
	IWX_WRITE(sc, IWX_CSR_CTXT_INFO_ADDR + 4, paddr >> 32);

	paddr = sc->iml_dma.paddr;
	IWX_WRITE(sc, IWX_CSR_IML_DATA_ADDR, paddr & 0xffffffff);
	IWX_WRITE(sc, IWX_CSR_IML_DATA_ADDR + 4, paddr >> 32);
	IWX_WRITE(sc, IWX_CSR_IML_SIZE_ADDR, sc->sc_fw.iml_len);

	IWX_SETBITS(sc, IWX_CSR_CTXT_INFO_BOOT_CTRL,
		    IWX_CSR_AUTO_FUNC_BOOT_ENA);

	IWX_DPRINTF(sc, IWX_DEBUG_FIRMWARE_TLV,
	    "%s:%d kicking fw to get going\n", __func__, __LINE__);

	/* kick FW self load */
	if (!iwx_nic_lock(sc)) {
		iwx_dma_contig_free(&sc->iml_dma);
		iwx_ctxt_info_free_fw_img(sc);
		return EBUSY;
	}
	iwx_set_ltr(sc);
	iwx_write_umac_prph(sc, IWX_UREG_CPU_INIT_RUN, 1);
	iwx_nic_unlock(sc);

	/* Context info will be released upon alive or failure to get one */
	return 0;
}

static void
iwx_ctxt_info_free_fw_img(struct iwx_softc *sc)
{
	struct iwx_self_init_dram *dram = &sc->init_dram;
	int i;

	if (!dram->fw)
		return;

	for (i = 0; i < dram->lmac_cnt + dram->umac_cnt; i++)
		iwx_dma_contig_free(&dram->fw[i]);

	free(dram->fw, M_DEVBUF);
	dram->lmac_cnt = 0;
	dram->umac_cnt = 0;
	dram->fw = NULL;
}

static int
iwx_firmware_store_section(struct iwx_softc *sc, enum iwx_ucode_type type,
    const uint8_t *data, size_t dlen)
{
	struct iwx_fw_sects *fws;
	struct iwx_fw_onesect *fwone;

	if (type >= IWX_UCODE_TYPE_MAX)
		return EINVAL;
	if (dlen < sizeof(uint32_t))
		return EINVAL;

	fws = &sc->sc_fw.fw_sects[type];
	IWX_DPRINTF(sc, IWX_DEBUG_FIRMWARE_TLV,
	    "%s: ucode type %d section %d\n", DEVNAME(sc), type, fws->fw_count);
	if (fws->fw_count >= IWX_UCODE_SECT_MAX)
		return EINVAL;

	fwone = &fws->fw_sect[fws->fw_count];

	/* first 32bit are device load offset */
	memcpy(&fwone->fws_devoff, data, sizeof(uint32_t));

	/* rest is data */
	fwone->fws_data = data + sizeof(uint32_t);
	fwone->fws_len = dlen - sizeof(uint32_t);

	fws->fw_count++;
	fws->fw_totlen += fwone->fws_len;

	return 0;
}

#define IWX_DEFAULT_SCAN_CHANNELS	40
/* Newer firmware might support more channels. Raise this value if needed. */
#define IWX_MAX_SCAN_CHANNELS		67 /* as of iwx-cc-a0-62 firmware */

struct iwx_tlv_calib_data {
	uint32_t ucode_type;
	struct iwx_tlv_calib_ctrl calib;
} __packed;

static int
iwx_set_default_calib(struct iwx_softc *sc, const void *data)
{
	const struct iwx_tlv_calib_data *def_calib = data;
	uint32_t ucode_type = le32toh(def_calib->ucode_type);

	if (ucode_type >= IWX_UCODE_TYPE_MAX)
		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
iwx_fw_info_free(struct iwx_fw_info *fw)
{
	free(fw->fw_rawdata, M_DEVBUF);
	fw->fw_rawdata = NULL;
	fw->fw_rawsize = 0;
	/* don't touch fw->fw_status */
	memset(fw->fw_sects, 0, sizeof(fw->fw_sects));
	free(fw->iml, M_DEVBUF);
	fw->iml = NULL;
	fw->iml_len = 0;
}

#define IWX_FW_ADDR_CACHE_CONTROL 0xC0000000

static int
iwx_read_firmware(struct iwx_softc *sc)
{
	struct iwx_fw_info *fw = &sc->sc_fw;
	const struct iwx_tlv_ucode_header *uhdr;
	struct iwx_ucode_tlv tlv;
	uint32_t tlv_type;
	const uint8_t *data;
	int err = 0;
	size_t len;
	const struct firmware *fwp;

	if (fw->fw_status == IWX_FW_STATUS_DONE)
		return 0;

	fw->fw_status = IWX_FW_STATUS_INPROGRESS;
	fwp = firmware_get(sc->sc_fwname);
	sc->sc_fwp = fwp;

	if (fwp == NULL) {
		printf("%s: could not read firmware %s\n",
		    DEVNAME(sc), sc->sc_fwname);
		err = ENOENT;
		goto out;
	}

	IWX_DPRINTF(sc, IWX_DEBUG_FW, "%s:%d %s: using firmware %s\n",
		__func__, __LINE__, DEVNAME(sc), sc->sc_fwname);


	sc->sc_capaflags = 0;
	sc->sc_capa_n_scan_channels = IWX_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 = (const void *)(fwp->data);
	if (*(const uint32_t *)fwp->data != 0
	    || le32toh(uhdr->magic) != IWX_TLV_UCODE_MAGIC) {
		printf("%s: invalid firmware %s\n",
		    DEVNAME(sc), sc->sc_fwname);
		err = EINVAL;
		goto out;
	}

	iwx_fw_version_str(sc->sc_fwver, sizeof(sc->sc_fwver),
	    IWX_UCODE_MAJOR(le32toh(uhdr->ver)),
	    IWX_UCODE_MINOR(le32toh(uhdr->ver)),
	    IWX_UCODE_API(le32toh(uhdr->ver)));

	data = uhdr->data;
	len = fwp->datasize - sizeof(*uhdr);

	while (len >= sizeof(tlv)) {
		size_t tlv_len;
		const 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) {
			printf("%s: firmware too short: %zu bytes\n",
			    DEVNAME(sc), len);
			err = EINVAL;
			goto parse_out;
		}

		switch (tlv_type) {
		case IWX_UCODE_TLV_PROBE_MAX_LEN:
			if (tlv_len < sizeof(uint32_t)) {
				err = EINVAL;
				goto parse_out;
			}
			sc->sc_capa_max_probe_len
			    = le32toh(*(const uint32_t *)tlv_data);
			if (sc->sc_capa_max_probe_len >
			    IWX_SCAN_OFFLOAD_PROBE_REQ_SIZE) {
				err = EINVAL;
				goto parse_out;
			}
			break;
		case IWX_UCODE_TLV_PAN:
			if (tlv_len) {
				err = EINVAL;
				goto parse_out;
			}
			sc->sc_capaflags |= IWX_UCODE_TLV_FLAGS_PAN;
			break;
		case IWX_UCODE_TLV_FLAGS:
			if (tlv_len < sizeof(uint32_t)) {
				err = EINVAL;
				goto parse_out;
			}
			/*
			 * Apparently there can be many flags, but Linux driver
			 * parses only the first one, and so do we.
			 *
			 * XXX: why does this override IWX_UCODE_TLV_PAN?
			 * Intentional or a bug?  Observations from
			 * current firmware file:
			 *  1) TLV_PAN is parsed first
			 *  2) TLV_FLAGS contains TLV_FLAGS_PAN
			 * ==> this resets TLV_PAN to itself... hnnnk
			 */
			sc->sc_capaflags = le32toh(*(const uint32_t *)tlv_data);
			break;
		case IWX_UCODE_TLV_CSCHEME:
			err = iwx_store_cscheme(sc, tlv_data, tlv_len);
			if (err)
				goto parse_out;
			break;
		case IWX_UCODE_TLV_NUM_OF_CPU: {
			uint32_t num_cpu;
			if (tlv_len != sizeof(uint32_t)) {
				err = EINVAL;
				goto parse_out;
			}
			num_cpu = le32toh(*(const uint32_t *)tlv_data);
			if (num_cpu < 1 || num_cpu > 2) {
				err = EINVAL;
				goto parse_out;
			}
			break;
		}
		case IWX_UCODE_TLV_SEC_RT:
			err = iwx_firmware_store_section(sc,
			    IWX_UCODE_TYPE_REGULAR, tlv_data, tlv_len);
			if (err)
				goto parse_out;
			break;
		case IWX_UCODE_TLV_SEC_INIT:
			err = iwx_firmware_store_section(sc,
			    IWX_UCODE_TYPE_INIT, tlv_data, tlv_len);
			if (err)
				goto parse_out;
			break;
		case IWX_UCODE_TLV_SEC_WOWLAN:
			err = iwx_firmware_store_section(sc,
			    IWX_UCODE_TYPE_WOW, tlv_data, tlv_len);
			if (err)
				goto parse_out;
			break;
		case IWX_UCODE_TLV_DEF_CALIB:
			if (tlv_len != sizeof(struct iwx_tlv_calib_data)) {
				err = EINVAL;
				goto parse_out;
			}
			err = iwx_set_default_calib(sc, tlv_data);
			if (err)
				goto parse_out;
			break;
		case IWX_UCODE_TLV_PHY_SKU:
			if (tlv_len != sizeof(uint32_t)) {
				err = EINVAL;
				goto parse_out;
			}
			sc->sc_fw_phy_config = le32toh(*(const uint32_t *)tlv_data);
			break;

		case IWX_UCODE_TLV_API_CHANGES_SET: {
			const struct iwx_ucode_api *api;
			int idx, i;
			if (tlv_len != sizeof(*api)) {
				err = EINVAL;
				goto parse_out;
			}
			api = (const struct iwx_ucode_api *)tlv_data;
			idx = le32toh(api->api_index);
			if (idx >= howmany(IWX_NUM_UCODE_TLV_API, 32)) {
				err = EINVAL;
				goto parse_out;
			}
			for (i = 0; i < 32; i++) {
				if ((le32toh(api->api_flags) & (1 << i)) == 0)
					continue;
				setbit(sc->sc_ucode_api, i + (32 * idx));
			}
			break;
		}

		case IWX_UCODE_TLV_ENABLED_CAPABILITIES: {
			const struct iwx_ucode_capa *capa;
			int idx, i;
			if (tlv_len != sizeof(*capa)) {
				err = EINVAL;
				goto parse_out;
			}
			capa = (const struct iwx_ucode_capa *)tlv_data;
			idx = le32toh(capa->api_index);
			if (idx >= howmany(IWX_NUM_UCODE_TLV_CAPA, 32)) {
				goto parse_out;
			}
			for (i = 0; i < 32; i++) {
				if ((le32toh(capa->api_capa) & (1 << i)) == 0)
					continue;
				setbit(sc->sc_enabled_capa, i + (32 * idx));
			}
			break;
		}

		case IWX_UCODE_TLV_SDIO_ADMA_ADDR:
		case IWX_UCODE_TLV_FW_GSCAN_CAPA:
			/* ignore, not used by current driver */
			break;

		case IWX_UCODE_TLV_SEC_RT_USNIFFER:
			err = iwx_firmware_store_section(sc,
			    IWX_UCODE_TYPE_REGULAR_USNIFFER, tlv_data,
			    tlv_len);
			if (err)
				goto parse_out;
			break;

		case IWX_UCODE_TLV_PAGING:
			if (tlv_len != sizeof(uint32_t)) {
				err = EINVAL;
				goto parse_out;
			}
			break;

		case IWX_UCODE_TLV_N_SCAN_CHANNELS:
			if (tlv_len != sizeof(uint32_t)) {
				err = EINVAL;
				goto parse_out;
			}
			sc->sc_capa_n_scan_channels =
			  le32toh(*(const uint32_t *)tlv_data);
			if (sc->sc_capa_n_scan_channels > IWX_MAX_SCAN_CHANNELS) {
				err = ERANGE;
				goto parse_out;
			}
			break;

		case IWX_UCODE_TLV_FW_VERSION:
			if (tlv_len != sizeof(uint32_t) * 3) {
				err = EINVAL;
				goto parse_out;
			}

			iwx_fw_version_str(sc->sc_fwver, sizeof(sc->sc_fwver),
			    le32toh(((const uint32_t *)tlv_data)[0]),
			    le32toh(((const uint32_t *)tlv_data)[1]),
			    le32toh(((const uint32_t *)tlv_data)[2]));
			break;

		case IWX_UCODE_TLV_FW_DBG_DEST: {
			const struct iwx_fw_dbg_dest_tlv_v1 *dest_v1 = NULL;

			fw->dbg_dest_ver = (const uint8_t *)tlv_data;
			if (*fw->dbg_dest_ver != 0) {
				err = EINVAL;
				goto parse_out;
			}

			if (fw->dbg_dest_tlv_init)
				break;
			fw->dbg_dest_tlv_init = true;

			dest_v1 = (const void *)tlv_data;
			fw->dbg_dest_tlv_v1 = dest_v1;
			fw->n_dest_reg = tlv_len -
			    offsetof(struct iwx_fw_dbg_dest_tlv_v1, reg_ops);
			fw->n_dest_reg /= sizeof(dest_v1->reg_ops[0]);
			IWX_DPRINTF(sc, IWX_DEBUG_FIRMWARE_TLV,
			    "%s: found debug dest; n_dest_reg=%d\n",
			    __func__, fw->n_dest_reg);
			break;
		}

		case IWX_UCODE_TLV_FW_DBG_CONF: {
			const struct iwx_fw_dbg_conf_tlv *conf = (const void *)tlv_data;

			if (!fw->dbg_dest_tlv_init ||
			    conf->id >= nitems(fw->dbg_conf_tlv) ||
			    fw->dbg_conf_tlv[conf->id] != NULL)
				break;

			IWX_DPRINTF(sc, IWX_DEBUG_FIRMWARE_TLV,
			    "Found debug configuration: %d\n", conf->id);
			fw->dbg_conf_tlv[conf->id] = conf;
			fw->dbg_conf_tlv_len[conf->id] = tlv_len;
			break;
		}

		case IWX_UCODE_TLV_UMAC_DEBUG_ADDRS: {
			const struct iwx_umac_debug_addrs *dbg_ptrs =
				(const void *)tlv_data;

			if (tlv_len != sizeof(*dbg_ptrs)) {
				err = EINVAL;
				goto parse_out;
			}
			if (sc->sc_device_family < IWX_DEVICE_FAMILY_22000)
				break;
			sc->sc_uc.uc_umac_error_event_table =
				le32toh(dbg_ptrs->error_info_addr) &
				~IWX_FW_ADDR_CACHE_CONTROL;
			sc->sc_uc.error_event_table_tlv_status |=
				IWX_ERROR_EVENT_TABLE_UMAC;
			break;
		}

		case IWX_UCODE_TLV_LMAC_DEBUG_ADDRS: {
			const struct iwx_lmac_debug_addrs *dbg_ptrs =
				(const void *)tlv_data;

			if (tlv_len != sizeof(*dbg_ptrs)) {
				err = EINVAL;
				goto parse_out;
			}
			if (sc->sc_device_family < IWX_DEVICE_FAMILY_22000)
				break;
			sc->sc_uc.uc_lmac_error_event_table[0] =
				le32toh(dbg_ptrs->error_event_table_ptr) &
				~IWX_FW_ADDR_CACHE_CONTROL;
			sc->sc_uc.error_event_table_tlv_status |=
				IWX_ERROR_EVENT_TABLE_LMAC1;
			break;
		}

		case IWX_UCODE_TLV_FW_MEM_SEG:
			break;

		case IWX_UCODE_TLV_IML:
			if (sc->sc_fw.iml != NULL) {
				free(fw->iml, M_DEVBUF);
				fw->iml_len = 0;
			}
			sc->sc_fw.iml = malloc(tlv_len, M_DEVBUF,
			    M_WAITOK | M_ZERO);
			if (sc->sc_fw.iml == NULL) {
				err = ENOMEM;
				goto parse_out;
			}
			memcpy(sc->sc_fw.iml, tlv_data, tlv_len);
			sc->sc_fw.iml_len = tlv_len;
			break;

		case IWX_UCODE_TLV_CMD_VERSIONS:
			if (tlv_len % sizeof(struct iwx_fw_cmd_version)) {
				tlv_len /= sizeof(struct iwx_fw_cmd_version);
				tlv_len *= sizeof(struct iwx_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 iwx_fw_cmd_version);
			break;

		case IWX_UCODE_TLV_FW_RECOVERY_INFO:
			break;

		case IWX_UCODE_TLV_FW_FSEQ_VERSION:
		case IWX_UCODE_TLV_PHY_INTEGRATION_VERSION:
		case IWX_UCODE_TLV_FW_NUM_STATIONS:
		case IWX_UCODE_TLV_FW_NUM_BEACONS:
			break;

		/* undocumented TLVs found in iwx-cc-a0-46 image */
		case 58:
		case 0x1000003:
		case 0x1000004:
			break;

		/* undocumented TLVs found in iwx-cc-a0-48 image */
		case 0x1000000:
		case 0x1000002:
			break;

		case IWX_UCODE_TLV_TYPE_DEBUG_INFO:
		case IWX_UCODE_TLV_TYPE_BUFFER_ALLOCATION:
		case IWX_UCODE_TLV_TYPE_HCMD:
		case IWX_UCODE_TLV_TYPE_REGIONS:
		case IWX_UCODE_TLV_TYPE_TRIGGERS:
		case IWX_UCODE_TLV_TYPE_CONF_SET:
		case IWX_UCODE_TLV_SEC_TABLE_ADDR:
		case IWX_UCODE_TLV_D3_KEK_KCK_ADDR:
		case IWX_UCODE_TLV_CURRENT_PC:
			break;

		/* undocumented TLV found in iwx-cc-a0-67 image */
		case 0x100000b:
			break;

		/* undocumented TLV found in iwx-ty-a0-gf-a0-73 image */
		case 0x101:
			break;

		/* undocumented TLV found in iwx-ty-a0-gf-a0-77 image */
		case 0x100000c:
			break;

		/* undocumented TLV found in iwx-ty-a0-gf-a0-89 image */
		case 69:
			break;

		default:
			err = EINVAL;
			goto parse_out;
		}

		/*
		 * Check for size_t overflow and ignore missing padding at
		 * end of firmware file.
		 */
		if (roundup(tlv_len, 4) > len)
			break;

		len -= roundup(tlv_len, 4);
		data += roundup(tlv_len, 4);
	}

	KASSERT(err == 0, ("unhandled fw parse error"));

parse_out:
	if (err) {
		printf("%s: firmware parse error %d, "
		    "section type %d\n", DEVNAME(sc), err, tlv_type);
	}

out:
	if (err) {
		fw->fw_status = IWX_FW_STATUS_NONE;
		if (fw->fw_rawdata != NULL)
			iwx_fw_info_free(fw);
	} else
		fw->fw_status = IWX_FW_STATUS_DONE;
	return err;
}

static uint32_t
iwx_prph_addr_mask(struct iwx_softc *sc)
{
	if (sc->sc_device_family >= IWX_DEVICE_FAMILY_AX210)
		return 0x00ffffff;
	else
		return 0x000fffff;
}

static uint32_t
iwx_read_prph_unlocked(struct iwx_softc *sc, uint32_t addr)
{
	uint32_t mask = iwx_prph_addr_mask(sc);
	IWX_WRITE(sc, IWX_HBUS_TARG_PRPH_RADDR, ((addr & mask) | (3 << 24)));
	IWX_BARRIER_READ_WRITE(sc);
	return IWX_READ(sc, IWX_HBUS_TARG_PRPH_RDAT);
}

uint32_t
iwx_read_prph(struct iwx_softc *sc, uint32_t addr)
{
	iwx_nic_assert_locked(sc);
	return iwx_read_prph_unlocked(sc, addr);
}

static void
iwx_write_prph_unlocked(struct iwx_softc *sc, uint32_t addr, uint32_t val)
{
	uint32_t mask = iwx_prph_addr_mask(sc);
	IWX_WRITE(sc, IWX_HBUS_TARG_PRPH_WADDR, ((addr & mask) | (3 << 24)));
	IWX_BARRIER_WRITE(sc);
	IWX_WRITE(sc, IWX_HBUS_TARG_PRPH_WDAT, val);
}

static void
iwx_write_prph(struct iwx_softc *sc, uint32_t addr, uint32_t val)
{
	iwx_nic_assert_locked(sc);
	iwx_write_prph_unlocked(sc, addr, val);
}

static uint32_t
iwx_read_umac_prph(struct iwx_softc *sc, uint32_t addr)
{
	return iwx_read_prph(sc, addr + sc->sc_umac_prph_offset);
}

static void
iwx_write_umac_prph(struct iwx_softc *sc, uint32_t addr, uint32_t val)
{
	iwx_write_prph(sc, addr + sc->sc_umac_prph_offset, val);
}

static int
iwx_read_mem(struct iwx_softc *sc, uint32_t addr, void *buf, int dwords)
{
	int offs, err = 0;
	uint32_t *vals = buf;

	if (iwx_nic_lock(sc)) {
		IWX_WRITE(sc, IWX_HBUS_TARG_MEM_RADDR, addr);
		for (offs = 0; offs < dwords; offs++)
			vals[offs] = le32toh(IWX_READ(sc, IWX_HBUS_TARG_MEM_RDAT));
		iwx_nic_unlock(sc);
	} else {
		err = EBUSY;
	}
	return err;
}

static int
iwx_poll_bit(struct iwx_softc *sc, int reg, uint32_t bits, uint32_t mask,
    int timo)
{
	for (;;) {
		if ((IWX_READ(sc, reg) & mask) == (bits & mask)) {
			return 1;
		}
		if (timo < 10) {
			return 0;
		}
		timo -= 10;
		DELAY(10);
	}
}

static int
iwx_nic_lock(struct iwx_softc *sc)
{
	if (sc->sc_nic_locks > 0) {
		iwx_nic_assert_locked(sc);
		sc->sc_nic_locks++;
		return 1; /* already locked */
	}

	IWX_SETBITS(sc, IWX_CSR_GP_CNTRL,
	    IWX_CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ);

	DELAY(2);

	if (iwx_poll_bit(sc, IWX_CSR_GP_CNTRL,
	    IWX_CSR_GP_CNTRL_REG_VAL_MAC_ACCESS_EN,
	    IWX_CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY
	     | IWX_CSR_GP_CNTRL_REG_FLAG_GOING_TO_SLEEP, 150000)) {
		sc->sc_nic_locks++;
		return 1;
	}

	printf("%s: acquiring device failed\n", DEVNAME(sc));
	return 0;
}

static void
iwx_nic_assert_locked(struct iwx_softc *sc)
{
	if (sc->sc_nic_locks <= 0)
		panic("%s: nic locks counter %d", DEVNAME(sc), sc->sc_nic_locks);
}

static void
iwx_nic_unlock(struct iwx_softc *sc)
{
	if (sc->sc_nic_locks > 0) {
		if (--sc->sc_nic_locks == 0)
			IWX_CLRBITS(sc, IWX_CSR_GP_CNTRL,
			    IWX_CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ);
	} else
		printf("%s: NIC already unlocked\n", DEVNAME(sc));
}

static int
iwx_set_bits_mask_prph(struct iwx_softc *sc, uint32_t reg, uint32_t bits,
    uint32_t mask)
{
	uint32_t val;

	if (iwx_nic_lock(sc)) {
		val = iwx_read_prph(sc, reg) & mask;
		val |= bits;
		iwx_write_prph(sc, reg, val);
		iwx_nic_unlock(sc);
		return 0;
	}
	return EBUSY;
}

static int
iwx_set_bits_prph(struct iwx_softc *sc, uint32_t reg, uint32_t bits)
{
	return iwx_set_bits_mask_prph(sc, reg, bits, ~0);
}

static int
iwx_clear_bits_prph(struct iwx_softc *sc, uint32_t reg, uint32_t bits)
{
	return iwx_set_bits_mask_prph(sc, reg, 0, ~bits);
}

static void
iwx_dma_map_addr(void *arg, bus_dma_segment_t *segs, int nsegs, int error)
{
        if (error != 0)
                return;
	KASSERT(nsegs == 1, ("too many DMA segments, %d should be 1", nsegs));
	*(bus_addr_t *)arg = segs[0].ds_addr;
}

static int
iwx_dma_contig_alloc(bus_dma_tag_t tag, struct iwx_dma_info *dma,
    bus_size_t size, bus_size_t alignment)
{
	int error;

	dma->tag = NULL;
	dma->map = NULL;
	dma->size = size;
	dma->vaddr = NULL;

	error = bus_dma_tag_create(tag, alignment,
            0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, size,
            1, size, 0, NULL, NULL, &dma->tag);
        if (error != 0)
                goto fail;

        error = bus_dmamem_alloc(dma->tag, (void **)&dma->vaddr,
            BUS_DMA_NOWAIT | BUS_DMA_ZERO | BUS_DMA_COHERENT, &dma->map);
        if (error != 0)
                goto fail;

        error = bus_dmamap_load(dma->tag, dma->map, dma->vaddr, size,
            iwx_dma_map_addr, &dma->paddr, BUS_DMA_NOWAIT);
        if (error != 0) {
		bus_dmamem_free(dma->tag, dma->vaddr, dma->map);
		dma->vaddr = NULL;
		goto fail;
	}

	bus_dmamap_sync(dma->tag, dma->map, BUS_DMASYNC_PREWRITE);

	return 0;

fail:
	iwx_dma_contig_free(dma);
	return error;
}

static void
iwx_dma_contig_free(struct iwx_dma_info *dma)
{
	if (dma->vaddr != NULL) {
		bus_dmamap_sync(dma->tag, dma->map,
		    BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
		bus_dmamap_unload(dma->tag, dma->map);
		bus_dmamem_free(dma->tag, dma->vaddr, dma->map);
		dma->vaddr = NULL;
	}
	if (dma->tag != NULL) {
		bus_dma_tag_destroy(dma->tag);
		dma->tag = NULL;
	}
}

static int
iwx_alloc_rx_ring(struct iwx_softc *sc, struct iwx_rx_ring *ring)
{
	bus_size_t size;
	int i, err;

	ring->cur = 0;

	/* Allocate RX descriptors (256-byte aligned). */
	if (sc->sc_device_family >= IWX_DEVICE_FAMILY_AX210)
		size = sizeof(struct iwx_rx_transfer_desc);
	else
		size = sizeof(uint64_t);
	err = iwx_dma_contig_alloc(sc->sc_dmat, &ring->free_desc_dma,
	    size * IWX_RX_MQ_RING_COUNT, 256);
	if (err) {
		device_printf(sc->sc_dev,
		    "could not allocate RX ring DMA memory\n");
		goto fail;
	}
	ring->desc = ring->free_desc_dma.vaddr;

	/* Allocate RX status area (16-byte aligned). */
	if (sc->sc_device_family >= IWX_DEVICE_FAMILY_AX210)
		size = sizeof(uint16_t);
	else
		size = sizeof(*ring->stat);
	err = iwx_dma_contig_alloc(sc->sc_dmat, &ring->stat_dma, size, 16);
	if (err) {
		device_printf(sc->sc_dev,
		    "could not allocate RX status DMA memory\n");
		goto fail;
	}
	ring->stat = ring->stat_dma.vaddr;

	if (sc->sc_device_family >= IWX_DEVICE_FAMILY_AX210)
		size = sizeof(struct iwx_rx_completion_desc);
	else
		size = sizeof(uint32_t);
	err = iwx_dma_contig_alloc(sc->sc_dmat, &ring->used_desc_dma,
	    size * IWX_RX_MQ_RING_COUNT, 256);
	if (err) {
		device_printf(sc->sc_dev,
		    "could not allocate RX ring DMA memory\n");
		goto fail;
	}

	err = bus_dma_tag_create(sc->sc_dmat, 1, 0, BUS_SPACE_MAXADDR_32BIT,
	    BUS_SPACE_MAXADDR, NULL, NULL, IWX_RBUF_SIZE, 1, IWX_RBUF_SIZE,
	    0, NULL, NULL, &ring->data_dmat);

	for (i = 0; i < IWX_RX_MQ_RING_COUNT; i++) {
		struct iwx_rx_data *data = &ring->data[i];

		memset(data, 0, sizeof(*data));
		err = bus_dmamap_create(ring->data_dmat, 0, &data->map);
		if (err) {
			device_printf(sc->sc_dev,
			    "could not create RX buf DMA map\n");
			goto fail;
		}

		err = iwx_rx_addbuf(sc, IWX_RBUF_SIZE, i);
		if (err)
			goto fail;
	}
	return 0;

fail:	iwx_free_rx_ring(sc, ring);
	return err;
}

static void
iwx_disable_rx_dma(struct iwx_softc *sc)
{
	int ntries;

	if (iwx_nic_lock(sc)) {
		if (sc->sc_device_family >= IWX_DEVICE_FAMILY_AX210)
			iwx_write_umac_prph(sc, IWX_RFH_RXF_DMA_CFG_GEN3, 0);
		else
			iwx_write_prph(sc, IWX_RFH_RXF_DMA_CFG, 0);
		for (ntries = 0; ntries < 1000; ntries++) {
			if (sc->sc_device_family >= IWX_DEVICE_FAMILY_AX210) {
				if (iwx_read_umac_prph(sc,
				    IWX_RFH_GEN_STATUS_GEN3) & IWX_RXF_DMA_IDLE)
					break;
			} else {
				if (iwx_read_prph(sc, IWX_RFH_GEN_STATUS) &
				    IWX_RXF_DMA_IDLE)
					break;
			}
			DELAY(10);
		}
		iwx_nic_unlock(sc);
	}
}

static void
iwx_reset_rx_ring(struct iwx_softc *sc, struct iwx_rx_ring *ring)
{
	ring->cur = 0;
	bus_dmamap_sync(sc->sc_dmat, ring->stat_dma.map,
	    BUS_DMASYNC_PREWRITE);
	if (sc->sc_device_family >= IWX_DEVICE_FAMILY_AX210) {
		uint16_t *status = sc->rxq.stat_dma.vaddr;
		*status = 0;
	} else
		memset(ring->stat, 0, sizeof(*ring->stat));
	bus_dmamap_sync(sc->sc_dmat, ring->stat_dma.map,
	    BUS_DMASYNC_POSTWRITE);

}

static void
iwx_free_rx_ring(struct iwx_softc *sc, struct iwx_rx_ring *ring)
{
	int i;

	iwx_dma_contig_free(&ring->free_desc_dma);
	iwx_dma_contig_free(&ring->stat_dma);
	iwx_dma_contig_free(&ring->used_desc_dma);

	for (i = 0; i < IWX_RX_MQ_RING_COUNT; i++) {
		struct iwx_rx_data *data = &ring->data[i];
		if (data->m != NULL) {
			bus_dmamap_sync(ring->data_dmat, data->map,
			    BUS_DMASYNC_POSTREAD);
			bus_dmamap_unload(ring->data_dmat, data->map);
			m_freem(data->m);
			data->m = NULL;
		}
		if (data->map != NULL) {
			bus_dmamap_destroy(ring->data_dmat, data->map);
			data->map = NULL;
		}
	}
	if (ring->data_dmat != NULL) {
		bus_dma_tag_destroy(ring->data_dmat);
		ring->data_dmat = NULL;
	}
}

static int
iwx_alloc_tx_ring(struct iwx_softc *sc, struct iwx_tx_ring *ring, int qid)
{
	bus_addr_t paddr;
	bus_size_t size;
	int i, err;
	size_t bc_tbl_size;
	bus_size_t bc_align;
	size_t mapsize;

	ring->qid = qid;
	ring->queued = 0;
	ring->cur = 0;
	ring->cur_hw = 0;
	ring->tail = 0;
	ring->tail_hw = 0;

	/* Allocate TX descriptors (256-byte aligned). */
	size = IWX_TX_RING_COUNT * sizeof(struct iwx_tfh_tfd);
	err = iwx_dma_contig_alloc(sc->sc_dmat, &ring->desc_dma, size, 256);
	if (err) {
		device_printf(sc->sc_dev,
		    "could not allocate TX ring DMA memory\n");
		goto fail;
	}
	ring->desc = ring->desc_dma.vaddr;

	/*
	 * The hardware supports up to 512 Tx rings which is more
	 * than we currently need.
	 *
	 * In DQA mode we use 1 command queue + 1 default queue for
	 * management, control, and non-QoS data frames.
	 * The command is queue sc->txq[0], our default queue is sc->txq[1].
	 *
	 * Tx aggregation requires additional queues, one queue per TID for
	 * which aggregation is enabled. We map TID 0-7 to sc->txq[2:9].
	 * Firmware may assign its own internal IDs for these queues
	 * depending on which TID gets aggregation enabled first.
	 * The driver maintains a table mapping driver-side queue IDs
	 * to firmware-side queue IDs.
	 */

	if (sc->sc_device_family >= IWX_DEVICE_FAMILY_AX210) {
		bc_tbl_size = sizeof(struct iwx_gen3_bc_tbl_entry) *
		    IWX_TFD_QUEUE_BC_SIZE_GEN3_AX210;
		bc_align = 128;
	} else {
		bc_tbl_size = sizeof(struct iwx_agn_scd_bc_tbl);
		bc_align = 64;
	}
	err = iwx_dma_contig_alloc(sc->sc_dmat, &ring->bc_tbl, bc_tbl_size,
	    bc_align);
	if (err) {
		device_printf(sc->sc_dev,
		    "could not allocate byte count table DMA memory\n");
		goto fail;
	}

	size = IWX_TX_RING_COUNT * sizeof(struct iwx_device_cmd);
	err = iwx_dma_contig_alloc(sc->sc_dmat, &ring->cmd_dma, size,
	    IWX_FIRST_TB_SIZE_ALIGN);
	if (err) {
		device_printf(sc->sc_dev,
		    "could not allocate cmd DMA memory\n");
		goto fail;
	}
	ring->cmd = ring->cmd_dma.vaddr;

	/* FW commands may require more mapped space than packets. */
	if (qid == IWX_DQA_CMD_QUEUE)
		mapsize = (sizeof(struct iwx_cmd_header) +
		    IWX_MAX_CMD_PAYLOAD_SIZE);
	else
		mapsize = MCLBYTES;
	err = bus_dma_tag_create(sc->sc_dmat, 1, 0, BUS_SPACE_MAXADDR_32BIT,
	    BUS_SPACE_MAXADDR, NULL, NULL, mapsize, IWX_TFH_NUM_TBS - 2,
	    mapsize, 0, NULL, NULL, &ring->data_dmat);

	paddr = ring->cmd_dma.paddr;
	for (i = 0; i < IWX_TX_RING_COUNT; i++) {
		struct iwx_tx_data *data = &ring->data[i];

		data->cmd_paddr = paddr;
		paddr += sizeof(struct iwx_device_cmd);

		err = bus_dmamap_create(ring->data_dmat, 0, &data->map);
		if (err) {
			device_printf(sc->sc_dev,
			    "could not create TX buf DMA map\n");
			goto fail;
		}
	}
	KASSERT(paddr == ring->cmd_dma.paddr + size, ("bad paddr in txr alloc"));
	return 0;

fail:
	return err;
}

static void
iwx_reset_tx_ring(struct iwx_softc *sc, struct iwx_tx_ring *ring)
{
	int i;

	for (i = 0; i < IWX_TX_RING_COUNT; i++) {
		struct iwx_tx_data *data = &ring->data[i];

		if (data->m != NULL) {
			bus_dmamap_sync(ring->data_dmat, data->map,
			    BUS_DMASYNC_POSTWRITE);
			bus_dmamap_unload(ring->data_dmat, data->map);
			m_freem(data->m);
			data->m = NULL;
		}
	}

	/* Clear byte count table. */
	memset(ring->bc_tbl.vaddr, 0, ring->bc_tbl.size);

	/* Clear TX descriptors. */
	memset(ring->desc, 0, ring->desc_dma.size);
	bus_dmamap_sync(ring->desc_dma.tag, ring->desc_dma.map,
	    BUS_DMASYNC_PREWRITE);
	sc->qfullmsk &= ~(1 << ring->qid);
	sc->qenablemsk &= ~(1 << ring->qid);
	for (i = 0; i < nitems(sc->aggqid); i++) {
		if (sc->aggqid[i] == ring->qid) {
			sc->aggqid[i] = 0;
			break;
		}
	}
	ring->queued = 0;
	ring->cur = 0;
	ring->cur_hw = 0;
	ring->tail = 0;
	ring->tail_hw = 0;
	ring->tid = 0;
}

static void
iwx_free_tx_ring(struct iwx_softc *sc, struct iwx_tx_ring *ring)
{
	int i;

	iwx_dma_contig_free(&ring->desc_dma);
	iwx_dma_contig_free(&ring->cmd_dma);
	iwx_dma_contig_free(&ring->bc_tbl);

	for (i = 0; i < IWX_TX_RING_COUNT; i++) {
		struct iwx_tx_data *data = &ring->data[i];

		if (data->m != NULL) {
			bus_dmamap_sync(ring->data_dmat, data->map,
			    BUS_DMASYNC_POSTWRITE);
			bus_dmamap_unload(ring->data_dmat, data->map);
			m_freem(data->m);
			data->m = NULL;
		}
		if (data->map != NULL) {
			bus_dmamap_destroy(ring->data_dmat, data->map);
			data->map = NULL;
		}
	}
	if (ring->data_dmat != NULL) {
		bus_dma_tag_destroy(ring->data_dmat);
		ring->data_dmat = NULL;
	}
}

static void
iwx_enable_rfkill_int(struct iwx_softc *sc)
{
	if (!sc->sc_msix) {
		sc->sc_intmask = IWX_CSR_INT_BIT_RF_KILL;
		IWX_WRITE(sc, IWX_CSR_INT_MASK, sc->sc_intmask);
	} else {
		IWX_WRITE(sc, IWX_CSR_MSIX_FH_INT_MASK_AD,
		    sc->sc_fh_init_mask);
		IWX_WRITE(sc, IWX_CSR_MSIX_HW_INT_MASK_AD,
		    ~IWX_MSIX_HW_INT_CAUSES_REG_RF_KILL);
		sc->sc_hw_mask = IWX_MSIX_HW_INT_CAUSES_REG_RF_KILL;
	}

	IWX_SETBITS(sc, IWX_CSR_GP_CNTRL,
	    IWX_CSR_GP_CNTRL_REG_FLAG_RFKILL_WAKE_L1A_EN);
}

static int
iwx_check_rfkill(struct iwx_softc *sc)
{
	uint32_t v;
	int rv;

	/*
	 * "documentation" is not really helpful here:
	 *  27:	HW_RF_KILL_SW
	 *	Indicates state of (platform's) hardware RF-Kill switch
	 *
	 * But apparently when it's off, it's on ...
	 */
	v = IWX_READ(sc, IWX_CSR_GP_CNTRL);
	rv = (v & IWX_CSR_GP_CNTRL_REG_FLAG_HW_RF_KILL_SW) == 0;
	if (rv) {
		sc->sc_flags |= IWX_FLAG_RFKILL;
	} else {
		sc->sc_flags &= ~IWX_FLAG_RFKILL;
	}

	return rv;
}

static void
iwx_enable_interrupts(struct iwx_softc *sc)
{
	if (!sc->sc_msix) {
		sc->sc_intmask = IWX_CSR_INI_SET_MASK;
		IWX_WRITE(sc, IWX_CSR_INT_MASK, sc->sc_intmask);
	} else {
		/*
		 * fh/hw_mask keeps all the unmasked causes.
		 * Unlike msi, in msix cause is enabled when it is unset.
		 */
		sc->sc_hw_mask = sc->sc_hw_init_mask;
		sc->sc_fh_mask = sc->sc_fh_init_mask;
		IWX_WRITE(sc, IWX_CSR_MSIX_FH_INT_MASK_AD,
		    ~sc->sc_fh_mask);
		IWX_WRITE(sc, IWX_CSR_MSIX_HW_INT_MASK_AD,
		    ~sc->sc_hw_mask);
	}
}

static void
iwx_enable_fwload_interrupt(struct iwx_softc *sc)
{
	if (!sc->sc_msix) {
		sc->sc_intmask = IWX_CSR_INT_BIT_ALIVE | IWX_CSR_INT_BIT_FH_RX;
		IWX_WRITE(sc, IWX_CSR_INT_MASK, sc->sc_intmask);
	} else {
		IWX_WRITE(sc, IWX_CSR_MSIX_HW_INT_MASK_AD,
		    ~IWX_MSIX_HW_INT_CAUSES_REG_ALIVE);
		sc->sc_hw_mask = IWX_MSIX_HW_INT_CAUSES_REG_ALIVE;
		/*
		 * Leave all the FH causes enabled to get the ALIVE
		 * notification.
		 */
		IWX_WRITE(sc, IWX_CSR_MSIX_FH_INT_MASK_AD,
		    ~sc->sc_fh_init_mask);
		sc->sc_fh_mask = sc->sc_fh_init_mask;
	}
}

#if 0
static void
iwx_restore_interrupts(struct iwx_softc *sc)
{
	IWX_WRITE(sc, IWX_CSR_INT_MASK, sc->sc_intmask);
}
#endif

static void
iwx_disable_interrupts(struct iwx_softc *sc)
{
	if (!sc->sc_msix) {
		IWX_WRITE(sc, IWX_CSR_INT_MASK, 0);

		/* acknowledge all interrupts */
		IWX_WRITE(sc, IWX_CSR_INT, ~0);
		IWX_WRITE(sc, IWX_CSR_FH_INT_STATUS, ~0);
	} else {
		IWX_WRITE(sc, IWX_CSR_MSIX_FH_INT_MASK_AD,
		    sc->sc_fh_init_mask);
		IWX_WRITE(sc, IWX_CSR_MSIX_HW_INT_MASK_AD,
		    sc->sc_hw_init_mask);
	}
}

static void
iwx_ict_reset(struct iwx_softc *sc)
{
	iwx_disable_interrupts(sc);

	memset(sc->ict_dma.vaddr, 0, IWX_ICT_SIZE);
	sc->ict_cur = 0;

	/* Set physical address of ICT (4KB aligned). */
	IWX_WRITE(sc, IWX_CSR_DRAM_INT_TBL_REG,
	    IWX_CSR_DRAM_INT_TBL_ENABLE
	    | IWX_CSR_DRAM_INIT_TBL_WRAP_CHECK
	    | IWX_CSR_DRAM_INIT_TBL_WRITE_POINTER
	    | sc->ict_dma.paddr >> IWX_ICT_PADDR_SHIFT);

	/* Switch to ICT interrupt mode in driver. */
	sc->sc_flags |= IWX_FLAG_USE_ICT;

	IWX_WRITE(sc, IWX_CSR_INT, ~0);
	iwx_enable_interrupts(sc);
}

#define IWX_HW_READY_TIMEOUT 50
static int
iwx_set_hw_ready(struct iwx_softc *sc)
{
	int ready;

	IWX_SETBITS(sc, IWX_CSR_HW_IF_CONFIG_REG,
	    IWX_CSR_HW_IF_CONFIG_REG_BIT_NIC_READY);

	ready = iwx_poll_bit(sc, IWX_CSR_HW_IF_CONFIG_REG,
	    IWX_CSR_HW_IF_CONFIG_REG_BIT_NIC_READY,
	    IWX_CSR_HW_IF_CONFIG_REG_BIT_NIC_READY,
	    IWX_HW_READY_TIMEOUT);
	if (ready)
		IWX_SETBITS(sc, IWX_CSR_MBOX_SET_REG,
		    IWX_CSR_MBOX_SET_REG_OS_ALIVE);

	DPRINTF(("%s: ready=%d\n", __func__, ready));
	return ready;
}
#undef IWX_HW_READY_TIMEOUT

static int
iwx_prepare_card_hw(struct iwx_softc *sc)
{
	int t = 0;
	int ntries;

	if (iwx_set_hw_ready(sc))
		return 0;

	IWX_SETBITS(sc, IWX_CSR_DBG_LINK_PWR_MGMT_REG,
	    IWX_CSR_RESET_LINK_PWR_MGMT_DISABLED);
	DELAY(1000);

	for (ntries = 0; ntries < 10; ntries++) {
		/* If HW is not ready, prepare the conditions to check again */
		IWX_SETBITS(sc, IWX_CSR_HW_IF_CONFIG_REG,
		    IWX_CSR_HW_IF_CONFIG_REG_PREPARE);

		do {
			if (iwx_set_hw_ready(sc))
				return 0;
			DELAY(200);
			t += 200;
		} while (t < 150000);
		DELAY(25000);
	}

	return ETIMEDOUT;
}

static int
iwx_force_power_gating(struct iwx_softc *sc)
{
	int err;

	err = iwx_set_bits_prph(sc, IWX_HPM_HIPM_GEN_CFG,
	    IWX_HPM_HIPM_GEN_CFG_CR_FORCE_ACTIVE);
	if (err)
		return err;
	DELAY(20);
	err = iwx_set_bits_prph(sc, IWX_HPM_HIPM_GEN_CFG,
	    IWX_HPM_HIPM_GEN_CFG_CR_PG_EN |
	    IWX_HPM_HIPM_GEN_CFG_CR_SLP_EN);
	if (err)
		return err;
	DELAY(20);
	err = iwx_clear_bits_prph(sc, IWX_HPM_HIPM_GEN_CFG,
	    IWX_HPM_HIPM_GEN_CFG_CR_FORCE_ACTIVE);
	return err;
}

static void
iwx_apm_config(struct iwx_softc *sc)
{
	uint16_t lctl, cap;
	int pcie_ptr;
	int error;

	/*
	 * L0S states have been found to be unstable with our devices
	 * and in newer hardware they are not officially supported at
	 * all, so we must always set the L0S_DISABLED bit.
	 */
	IWX_SETBITS(sc, IWX_CSR_GIO_REG, IWX_CSR_GIO_REG_VAL_L0S_DISABLED);

	error = pci_find_cap(sc->sc_dev, PCIY_EXPRESS, &pcie_ptr);
	if (error != 0) {
		printf("can't fill pcie_ptr\n");
		return;
	}

	lctl = pci_read_config(sc->sc_dev, pcie_ptr + PCIER_LINK_CTL,
	    sizeof(lctl));
#define PCI_PCIE_LCSR_ASPM_L0S 0x00000001
	sc->sc_pm_support = !(lctl & PCI_PCIE_LCSR_ASPM_L0S);
#define PCI_PCIE_DCSR2 0x28
	cap = pci_read_config(sc->sc_dev, pcie_ptr + PCI_PCIE_DCSR2,
	    sizeof(lctl));
#define PCI_PCIE_DCSR2_LTREN 0x00000400
	sc->sc_ltr_enabled = (cap & PCI_PCIE_DCSR2_LTREN) ? 1 : 0;
#define PCI_PCIE_LCSR_ASPM_L1 0x00000002
	DPRINTF(("%s: L1 %sabled - LTR %sabled\n",
	    DEVNAME(sc),
	    (lctl & PCI_PCIE_LCSR_ASPM_L1) ? "En" : "Dis",
	    sc->sc_ltr_enabled ? "En" : "Dis"));
#undef PCI_PCIE_LCSR_ASPM_L0S
#undef PCI_PCIE_DCSR2
#undef PCI_PCIE_DCSR2_LTREN
#undef PCI_PCIE_LCSR_ASPM_L1
}

/*
 * Start up NIC's basic functionality after it has been reset
 * e.g. after platform boot or shutdown.
 * NOTE:  This does not load uCode nor start the embedded processor
 */
static int
iwx_apm_init(struct iwx_softc *sc)
{
	int err = 0;

	/*
	 * Disable L0s without affecting L1;
	 *  don't wait for ICH L0s (ICH bug W/A)
	 */
	IWX_SETBITS(sc, IWX_CSR_GIO_CHICKEN_BITS,
	    IWX_CSR_GIO_CHICKEN_BITS_REG_BIT_L1A_NO_L0S_RX);

	/* Set FH wait threshold to maximum (HW error during stress W/A) */
	IWX_SETBITS(sc, IWX_CSR_DBG_HPET_MEM_REG, IWX_CSR_DBG_HPET_MEM_REG_VAL);

	/*
	 * Enable HAP INTA (interrupt from management bus) to
	 * wake device's PCI Express link L1a -> L0s
	 */
	IWX_SETBITS(sc, IWX_CSR_HW_IF_CONFIG_REG,
	    IWX_CSR_HW_IF_CONFIG_REG_BIT_HAP_WAKE_L1A);

	iwx_apm_config(sc);

	/*
	 * Set "initialization complete" bit to move adapter from
	 * D0U* --> D0A* (powered-up active) state.
	 */
	IWX_SETBITS(sc, IWX_CSR_GP_CNTRL, IWX_CSR_GP_CNTRL_REG_FLAG_INIT_DONE);

	/*
	 * Wait for clock stabilization; once stabilized, access to
	 * device-internal resources is supported, e.g. iwx_write_prph()
	 * and accesses to uCode SRAM.
	 */
	if (!iwx_poll_bit(sc, IWX_CSR_GP_CNTRL,
	    IWX_CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY,
	    IWX_CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY, 25000)) {
		printf("%s: timeout waiting for clock stabilization\n",
		    DEVNAME(sc));
		err = ETIMEDOUT;
		goto out;
	}
 out:
	if (err)
		printf("%s: apm init error %d\n", DEVNAME(sc), err);
	return err;
}

static void
iwx_apm_stop(struct iwx_softc *sc)
{
	IWX_SETBITS(sc, IWX_CSR_DBG_LINK_PWR_MGMT_REG,
	    IWX_CSR_RESET_LINK_PWR_MGMT_DISABLED);
	IWX_SETBITS(sc, IWX_CSR_HW_IF_CONFIG_REG,
	    IWX_CSR_HW_IF_CONFIG_REG_PREPARE |
	    IWX_CSR_HW_IF_CONFIG_REG_ENABLE_PME);
	DELAY(1000);
	IWX_CLRBITS(sc, IWX_CSR_DBG_LINK_PWR_MGMT_REG,
	    IWX_CSR_RESET_LINK_PWR_MGMT_DISABLED);
	DELAY(5000);

	/* stop device's busmaster DMA activity */
	IWX_SETBITS(sc, IWX_CSR_RESET, IWX_CSR_RESET_REG_FLAG_STOP_MASTER);

	if (!iwx_poll_bit(sc, IWX_CSR_RESET,
	    IWX_CSR_RESET_REG_FLAG_MASTER_DISABLED,
	    IWX_CSR_RESET_REG_FLAG_MASTER_DISABLED, 100))
		printf("%s: timeout waiting for bus master\n", DEVNAME(sc));

	/*
	 * Clear "initialization complete" bit to move adapter from
	 * D0A* (powered-up Active) --> D0U* (Uninitialized) state.
	 */
	IWX_CLRBITS(sc, IWX_CSR_GP_CNTRL,
	    IWX_CSR_GP_CNTRL_REG_FLAG_INIT_DONE);
}

static void
iwx_init_msix_hw(struct iwx_softc *sc)
{
	iwx_conf_msix_hw(sc, 0);

	if (!sc->sc_msix)
		return;

	sc->sc_fh_init_mask = ~IWX_READ(sc, IWX_CSR_MSIX_FH_INT_MASK_AD);
	sc->sc_fh_mask = sc->sc_fh_init_mask;
	sc->sc_hw_init_mask = ~IWX_READ(sc, IWX_CSR_MSIX_HW_INT_MASK_AD);
	sc->sc_hw_mask = sc->sc_hw_init_mask;
}

static void
iwx_conf_msix_hw(struct iwx_softc *sc, int stopped)
{
	int vector = 0;

	if (!sc->sc_msix) {
		/* Newer chips default to MSIX. */
		if (!stopped && iwx_nic_lock(sc)) {
			iwx_write_umac_prph(sc, IWX_UREG_CHICK,
			    IWX_UREG_CHICK_MSI_ENABLE);
			iwx_nic_unlock(sc);
		}
		return;
	}

	if (!stopped && iwx_nic_lock(sc)) {
		iwx_write_umac_prph(sc, IWX_UREG_CHICK,
		    IWX_UREG_CHICK_MSIX_ENABLE);
		iwx_nic_unlock(sc);
	}

	/* Disable all interrupts */
	IWX_WRITE(sc, IWX_CSR_MSIX_FH_INT_MASK_AD, ~0);
	IWX_WRITE(sc, IWX_CSR_MSIX_HW_INT_MASK_AD, ~0);

	/* Map fallback-queue (command/mgmt) to a single vector */
	IWX_WRITE_1(sc, IWX_CSR_MSIX_RX_IVAR(0),
	    vector | IWX_MSIX_NON_AUTO_CLEAR_CAUSE);
	/* Map RSS queue (data) to the same vector */
	IWX_WRITE_1(sc, IWX_CSR_MSIX_RX_IVAR(1),
	    vector | IWX_MSIX_NON_AUTO_CLEAR_CAUSE);

	/* Enable the RX queues cause interrupts */
	IWX_CLRBITS(sc, IWX_CSR_MSIX_FH_INT_MASK_AD,
	    IWX_MSIX_FH_INT_CAUSES_Q0 | IWX_MSIX_FH_INT_CAUSES_Q1);

	/* Map non-RX causes to the same vector */
	IWX_WRITE_1(sc, IWX_CSR_MSIX_IVAR(IWX_MSIX_IVAR_CAUSE_D2S_CH0_NUM),
	    vector | IWX_MSIX_NON_AUTO_CLEAR_CAUSE);
	IWX_WRITE_1(sc, IWX_CSR_MSIX_IVAR(IWX_MSIX_IVAR_CAUSE_D2S_CH1_NUM),
	    vector | IWX_MSIX_NON_AUTO_CLEAR_CAUSE);
	IWX_WRITE_1(sc, IWX_CSR_MSIX_IVAR(IWX_MSIX_IVAR_CAUSE_S2D),
	    vector | IWX_MSIX_NON_AUTO_CLEAR_CAUSE);
	IWX_WRITE_1(sc, IWX_CSR_MSIX_IVAR(IWX_MSIX_IVAR_CAUSE_FH_ERR),
	    vector | IWX_MSIX_NON_AUTO_CLEAR_CAUSE);
	IWX_WRITE_1(sc, IWX_CSR_MSIX_IVAR(IWX_MSIX_IVAR_CAUSE_REG_ALIVE),
	    vector | IWX_MSIX_NON_AUTO_CLEAR_CAUSE);
	IWX_WRITE_1(sc, IWX_CSR_MSIX_IVAR(IWX_MSIX_IVAR_CAUSE_REG_WAKEUP),
	    vector | IWX_MSIX_NON_AUTO_CLEAR_CAUSE);
	IWX_WRITE_1(sc, IWX_CSR_MSIX_IVAR(IWX_MSIX_IVAR_CAUSE_REG_RESET_DONE),
	    vector | IWX_MSIX_NON_AUTO_CLEAR_CAUSE);
	IWX_WRITE_1(sc, IWX_CSR_MSIX_IVAR(IWX_MSIX_IVAR_CAUSE_REG_CT_KILL),
	    vector | IWX_MSIX_NON_AUTO_CLEAR_CAUSE);
	IWX_WRITE_1(sc, IWX_CSR_MSIX_IVAR(IWX_MSIX_IVAR_CAUSE_REG_RF_KILL),
	    vector | IWX_MSIX_NON_AUTO_CLEAR_CAUSE);
	IWX_WRITE_1(sc, IWX_CSR_MSIX_IVAR(IWX_MSIX_IVAR_CAUSE_REG_PERIODIC),
	    vector | IWX_MSIX_NON_AUTO_CLEAR_CAUSE);
	IWX_WRITE_1(sc, IWX_CSR_MSIX_IVAR(IWX_MSIX_IVAR_CAUSE_REG_SW_ERR),
	    vector | IWX_MSIX_NON_AUTO_CLEAR_CAUSE);
	IWX_WRITE_1(sc, IWX_CSR_MSIX_IVAR(IWX_MSIX_IVAR_CAUSE_REG_SCD),
	    vector | IWX_MSIX_NON_AUTO_CLEAR_CAUSE);
	IWX_WRITE_1(sc, IWX_CSR_MSIX_IVAR(IWX_MSIX_IVAR_CAUSE_REG_FH_TX),
	    vector | IWX_MSIX_NON_AUTO_CLEAR_CAUSE);
	IWX_WRITE_1(sc, IWX_CSR_MSIX_IVAR(IWX_MSIX_IVAR_CAUSE_REG_HW_ERR),
	    vector | IWX_MSIX_NON_AUTO_CLEAR_CAUSE);
	IWX_WRITE_1(sc, IWX_CSR_MSIX_IVAR(IWX_MSIX_IVAR_CAUSE_REG_HAP),
	    vector | IWX_MSIX_NON_AUTO_CLEAR_CAUSE);

	/* Enable non-RX causes interrupts */
	IWX_CLRBITS(sc, IWX_CSR_MSIX_FH_INT_MASK_AD,
	    IWX_MSIX_FH_INT_CAUSES_D2S_CH0_NUM |
	    IWX_MSIX_FH_INT_CAUSES_D2S_CH1_NUM |
	    IWX_MSIX_FH_INT_CAUSES_S2D |
	    IWX_MSIX_FH_INT_CAUSES_FH_ERR);
	IWX_CLRBITS(sc, IWX_CSR_MSIX_HW_INT_MASK_AD,
	    IWX_MSIX_HW_INT_CAUSES_REG_ALIVE |
	    IWX_MSIX_HW_INT_CAUSES_REG_WAKEUP |
	    IWX_MSIX_HW_INT_CAUSES_REG_RESET_DONE |
	    IWX_MSIX_HW_INT_CAUSES_REG_CT_KILL |
	    IWX_MSIX_HW_INT_CAUSES_REG_RF_KILL |
	    IWX_MSIX_HW_INT_CAUSES_REG_PERIODIC |
	    IWX_MSIX_HW_INT_CAUSES_REG_SW_ERR |
	    IWX_MSIX_HW_INT_CAUSES_REG_SCD |
	    IWX_MSIX_HW_INT_CAUSES_REG_FH_TX |
	    IWX_MSIX_HW_INT_CAUSES_REG_HW_ERR |
	    IWX_MSIX_HW_INT_CAUSES_REG_HAP);
}

static int
iwx_clear_persistence_bit(struct iwx_softc *sc)
{
	uint32_t hpm, wprot;

	hpm = iwx_read_prph_unlocked(sc, IWX_HPM_DEBUG);
	if (hpm != 0xa5a5a5a0 && (hpm & IWX_PERSISTENCE_BIT)) {
		wprot = iwx_read_prph_unlocked(sc, IWX_PREG_PRPH_WPROT_22000);
		if (wprot & IWX_PREG_WFPM_ACCESS) {
			printf("%s: cannot clear persistence bit\n",
			    DEVNAME(sc));
			return EPERM;
		}
		iwx_write_prph_unlocked(sc, IWX_HPM_DEBUG,
		    hpm & ~IWX_PERSISTENCE_BIT);
	}

	return 0;
}

static int
iwx_start_hw(struct iwx_softc *sc)
{
	int err;

	err = iwx_prepare_card_hw(sc);
	if (err)
		return err;

	if (sc->sc_device_family == IWX_DEVICE_FAMILY_22000) {
		err = iwx_clear_persistence_bit(sc);
		if (err)
			return err;
	}

	/* Reset the entire device */
	IWX_SETBITS(sc, IWX_CSR_RESET, IWX_CSR_RESET_REG_FLAG_SW_RESET);
	DELAY(5000);

	if (sc->sc_device_family == IWX_DEVICE_FAMILY_22000 &&
	    sc->sc_integrated) {
		IWX_SETBITS(sc, IWX_CSR_GP_CNTRL,
		    IWX_CSR_GP_CNTRL_REG_FLAG_INIT_DONE);
		DELAY(20);
		if (!iwx_poll_bit(sc, IWX_CSR_GP_CNTRL,
		    IWX_CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY,
		    IWX_CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY, 25000)) {
			printf("%s: timeout waiting for clock stabilization\n",
			    DEVNAME(sc));
			return ETIMEDOUT;
		}

		err = iwx_force_power_gating(sc);
		if (err)
			return err;

		/* Reset the entire device */
		IWX_SETBITS(sc, IWX_CSR_RESET, IWX_CSR_RESET_REG_FLAG_SW_RESET);
		DELAY(5000);
	}

	err = iwx_apm_init(sc);
	if (err)
		return err;

	iwx_init_msix_hw(sc);

	iwx_enable_rfkill_int(sc);
	iwx_check_rfkill(sc);

	return 0;
}

static void
iwx_stop_device(struct iwx_softc *sc)
{
	int i;

	iwx_disable_interrupts(sc);
	sc->sc_flags &= ~IWX_FLAG_USE_ICT;

	iwx_disable_rx_dma(sc);
	iwx_reset_rx_ring(sc, &sc->rxq);
	for (i = 0; i < nitems(sc->txq); i++)
		iwx_reset_tx_ring(sc, &sc->txq[i]);
#if 0
	/* XXX-THJ: Tidy up BA state on stop */
	for (i = 0; i < IEEE80211_NUM_TID; i++) {
		struct ieee80211_tx_ba *ba = &ni->ni_tx_ba[i];
		if (ba->ba_state != IEEE80211_BA_AGREED)
			continue;
		ieee80211_delba_request(ic, ni, 0, 1, i);
	}
#endif
	/* Make sure (redundant) we've released our request to stay awake */
	IWX_CLRBITS(sc, IWX_CSR_GP_CNTRL,
	    IWX_CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ);
	if (sc->sc_nic_locks > 0)
		printf("%s: %d active NIC locks forcefully cleared\n",
		    DEVNAME(sc), sc->sc_nic_locks);
	sc->sc_nic_locks = 0;

	/* Stop the device, and put it in low power state */
	iwx_apm_stop(sc);

	/* Reset the on-board processor. */
	IWX_SETBITS(sc, IWX_CSR_RESET, IWX_CSR_RESET_REG_FLAG_SW_RESET);
	DELAY(5000);

	/*
	 * Upon stop, the IVAR table gets erased, so msi-x won't
	 * work. This causes a bug in RF-KILL flows, since the interrupt
	 * that enables radio won't fire on the correct irq, and the
	 * driver won't be able to handle the interrupt.
	 * Configure the IVAR table again after reset.
	 */
	iwx_conf_msix_hw(sc, 1);

	/*
	 * Upon stop, the APM issues an interrupt if HW RF kill is set.
	 * Clear the interrupt again.
	 */
	iwx_disable_interrupts(sc);

	/* Even though we stop the HW we still want the RF kill interrupt. */
	iwx_enable_rfkill_int(sc);
	iwx_check_rfkill(sc);

	iwx_prepare_card_hw(sc);

	iwx_ctxt_info_free_paging(sc);
	iwx_dma_contig_free(&sc->pnvm_dma);
}

static void
iwx_nic_config(struct iwx_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 & IWX_FW_PHY_CFG_RADIO_TYPE) >>
	    IWX_FW_PHY_CFG_RADIO_TYPE_POS;
	radio_cfg_step = (sc->sc_fw_phy_config & IWX_FW_PHY_CFG_RADIO_STEP) >>
	    IWX_FW_PHY_CFG_RADIO_STEP_POS;
	radio_cfg_dash = (sc->sc_fw_phy_config & IWX_FW_PHY_CFG_RADIO_DASH) >>
	    IWX_FW_PHY_CFG_RADIO_DASH_POS;

	reg_val |= IWX_CSR_HW_REV_STEP(sc->sc_hw_rev) <<
	    IWX_CSR_HW_IF_CONFIG_REG_POS_MAC_STEP;
	reg_val |= IWX_CSR_HW_REV_DASH(sc->sc_hw_rev) <<
	    IWX_CSR_HW_IF_CONFIG_REG_POS_MAC_DASH;

	/* radio configuration */
	reg_val |= radio_cfg_type << IWX_CSR_HW_IF_CONFIG_REG_POS_PHY_TYPE;
	reg_val |= radio_cfg_step << IWX_CSR_HW_IF_CONFIG_REG_POS_PHY_STEP;
	reg_val |= radio_cfg_dash << IWX_CSR_HW_IF_CONFIG_REG_POS_PHY_DASH;

	mask = IWX_CSR_HW_IF_CONFIG_REG_MSK_MAC_DASH |
	    IWX_CSR_HW_IF_CONFIG_REG_MSK_MAC_STEP |
	    IWX_CSR_HW_IF_CONFIG_REG_MSK_PHY_STEP |
	    IWX_CSR_HW_IF_CONFIG_REG_MSK_PHY_DASH |
	    IWX_CSR_HW_IF_CONFIG_REG_MSK_PHY_TYPE |
	    IWX_CSR_HW_IF_CONFIG_REG_BIT_RADIO_SI |
	    IWX_CSR_HW_IF_CONFIG_REG_BIT_MAC_SI;

	val = IWX_READ(sc, IWX_CSR_HW_IF_CONFIG_REG);
	val &= ~mask;
	val |= reg_val;
	IWX_WRITE(sc, IWX_CSR_HW_IF_CONFIG_REG, val);
}

static int
iwx_nic_rx_init(struct iwx_softc *sc)
{
	IWX_WRITE_1(sc, IWX_CSR_INT_COALESCING, IWX_HOST_INT_TIMEOUT_DEF);

	/*
	 * We don't configure the RFH; the firmware will do that.
	 * Rx descriptors are set when firmware sends an ALIVE interrupt.
	 */
	return 0;
}

static int
iwx_nic_init(struct iwx_softc *sc)
{
	int err;

	iwx_apm_init(sc);
	if (sc->sc_device_family < IWX_DEVICE_FAMILY_AX210)
		iwx_nic_config(sc);

	err = iwx_nic_rx_init(sc);
	if (err)
		return err;

	IWX_SETBITS(sc, IWX_CSR_MAC_SHADOW_REG_CTRL, 0x800fffff);

	return 0;
}

/* Map ieee80211_edca_ac categories to firmware Tx FIFO. */
const uint8_t iwx_ac_to_tx_fifo[] = {
	IWX_GEN2_EDCA_TX_FIFO_BE,
	IWX_GEN2_EDCA_TX_FIFO_BK,
	IWX_GEN2_EDCA_TX_FIFO_VI,
	IWX_GEN2_EDCA_TX_FIFO_VO,
};

static int
iwx_enable_txq(struct iwx_softc *sc, int sta_id, int qid, int tid,
    int num_slots)
{
	struct iwx_rx_packet *pkt;
	struct iwx_tx_queue_cfg_rsp *resp;
	struct iwx_tx_queue_cfg_cmd cmd_v0;
	struct iwx_scd_queue_cfg_cmd cmd_v3;
	struct iwx_host_cmd hcmd = {
		.flags = IWX_CMD_WANT_RESP,
		.resp_pkt_len = sizeof(*pkt) + sizeof(*resp),
	};
	struct iwx_tx_ring *ring = &sc->txq[qid];
	int err, fwqid, cmd_ver;
	uint32_t wr_idx;
	size_t resp_len;

	DPRINTF(("%s: tid=%i\n", __func__, tid));
	DPRINTF(("%s: qid=%i\n", __func__, qid));
	iwx_reset_tx_ring(sc, ring);

	cmd_ver = iwx_lookup_cmd_ver(sc, IWX_DATA_PATH_GROUP,
	    IWX_SCD_QUEUE_CONFIG_CMD);
	if (cmd_ver == 0 || cmd_ver == IWX_FW_CMD_VER_UNKNOWN) {
		memset(&cmd_v0, 0, sizeof(cmd_v0));
		cmd_v0.sta_id = sta_id;
		cmd_v0.tid = tid;
		cmd_v0.flags = htole16(IWX_TX_QUEUE_CFG_ENABLE_QUEUE);
		cmd_v0.cb_size = htole32(IWX_TFD_QUEUE_CB_SIZE(num_slots));
		cmd_v0.byte_cnt_addr = htole64(ring->bc_tbl.paddr);
		cmd_v0.tfdq_addr = htole64(ring->desc_dma.paddr);
		hcmd.id = IWX_SCD_QUEUE_CFG;
		hcmd.data[0] = &cmd_v0;
		hcmd.len[0] = sizeof(cmd_v0);
	} else if (cmd_ver == 3) {
		memset(&cmd_v3, 0, sizeof(cmd_v3));
		cmd_v3.operation = htole32(IWX_SCD_QUEUE_ADD);
		cmd_v3.u.add.tfdq_dram_addr = htole64(ring->desc_dma.paddr);
		cmd_v3.u.add.bc_dram_addr = htole64(ring->bc_tbl.paddr);
		cmd_v3.u.add.cb_size = htole32(IWX_TFD_QUEUE_CB_SIZE(num_slots));
		cmd_v3.u.add.flags = htole32(0);
		cmd_v3.u.add.sta_mask = htole32(1 << sta_id);
		cmd_v3.u.add.tid = tid;
		hcmd.id = IWX_WIDE_ID(IWX_DATA_PATH_GROUP,
		    IWX_SCD_QUEUE_CONFIG_CMD);
		hcmd.data[0] = &cmd_v3;
		hcmd.len[0] = sizeof(cmd_v3);
	} else {
		printf("%s: unsupported SCD_QUEUE_CFG command version %d\n",
		    DEVNAME(sc), cmd_ver);
		return ENOTSUP;
	}

	err = iwx_send_cmd(sc, &hcmd);
	if (err)
		return err;

	pkt = hcmd.resp_pkt;
	if (!pkt || (pkt->hdr.flags & IWX_CMD_FAILED_MSK)) {
		err = EIO;
		goto out;
	}

	resp_len = iwx_rx_packet_payload_len(pkt);
	if (resp_len != sizeof(*resp)) {
		err = EIO;
		goto out;
	}

	resp = (void *)pkt->data;
	fwqid = le16toh(resp->queue_number);
	wr_idx = le16toh(resp->write_pointer);

	/* Unlike iwlwifi, we do not support dynamic queue ID assignment. */
	if (fwqid != qid) {
		DPRINTF(("%s: === fwqid != qid\n", __func__));
		err = EIO;
		goto out;
	}

	if (wr_idx != ring->cur_hw) {
		DPRINTF(("%s: === (wr_idx != ring->cur_hw)\n", __func__));
		err = EIO;
		goto out;
	}

	sc->qenablemsk |= (1 << qid);
	ring->tid = tid;
out:
	iwx_free_resp(sc, &hcmd);
	return err;
}

static int
iwx_disable_txq(struct iwx_softc *sc, int sta_id, int qid, uint8_t tid)
{
	struct iwx_rx_packet *pkt;
	struct iwx_tx_queue_cfg_rsp *resp;
	struct iwx_tx_queue_cfg_cmd cmd_v0;
	struct iwx_scd_queue_cfg_cmd cmd_v3;
	struct iwx_host_cmd hcmd = {
		.flags = IWX_CMD_WANT_RESP,
		.resp_pkt_len = sizeof(*pkt) + sizeof(*resp),
	};
	struct iwx_tx_ring *ring = &sc->txq[qid];
	int err, cmd_ver;

	cmd_ver = iwx_lookup_cmd_ver(sc, IWX_DATA_PATH_GROUP,
	    IWX_SCD_QUEUE_CONFIG_CMD);
	if (cmd_ver == 0 || cmd_ver == IWX_FW_CMD_VER_UNKNOWN) {
		memset(&cmd_v0, 0, sizeof(cmd_v0));
		cmd_v0.sta_id = sta_id;
		cmd_v0.tid = tid;
		cmd_v0.flags = htole16(0); /* clear "queue enabled" flag */
		cmd_v0.cb_size = htole32(0);
		cmd_v0.byte_cnt_addr = htole64(0);
		cmd_v0.tfdq_addr = htole64(0);
		hcmd.id = IWX_SCD_QUEUE_CFG;
		hcmd.data[0] = &cmd_v0;
		hcmd.len[0] = sizeof(cmd_v0);
	} else if (cmd_ver == 3) {
		memset(&cmd_v3, 0, sizeof(cmd_v3));
		cmd_v3.operation = htole32(IWX_SCD_QUEUE_REMOVE);
		cmd_v3.u.remove.sta_mask = htole32(1 << sta_id);
		cmd_v3.u.remove.tid = tid;
		hcmd.id = IWX_WIDE_ID(IWX_DATA_PATH_GROUP,
		    IWX_SCD_QUEUE_CONFIG_CMD);
		hcmd.data[0] = &cmd_v3;
		hcmd.len[0] = sizeof(cmd_v3);
	} else {
		printf("%s: unsupported SCD_QUEUE_CFG command version %d\n",
		    DEVNAME(sc), cmd_ver);
		return ENOTSUP;
	}

	err = iwx_send_cmd(sc, &hcmd);
	if (err)
		return err;

	pkt = hcmd.resp_pkt;
	if (!pkt || (pkt->hdr.flags & IWX_CMD_FAILED_MSK)) {
		err = EIO;
		goto out;
	}

	sc->qenablemsk &= ~(1 << qid);
	iwx_reset_tx_ring(sc, ring);
out:
	iwx_free_resp(sc, &hcmd);
	return err;
}

static void
iwx_post_alive(struct iwx_softc *sc)
{
	int txcmd_ver;

	iwx_ict_reset(sc);

	txcmd_ver = iwx_lookup_notif_ver(sc, IWX_LONG_GROUP, IWX_TX_CMD) ;
	if (txcmd_ver != IWX_FW_CMD_VER_UNKNOWN && txcmd_ver > 6)
		sc->sc_rate_n_flags_version = 2;
	else
		sc->sc_rate_n_flags_version = 1;

	txcmd_ver = iwx_lookup_cmd_ver(sc, IWX_LONG_GROUP, IWX_TX_CMD);
}

static int
iwx_schedule_session_protection(struct iwx_softc *sc, struct iwx_node *in,
    uint32_t duration_tu)
{

	struct iwx_session_prot_cmd cmd = {
		.id_and_color = htole32(IWX_FW_CMD_ID_AND_COLOR(in->in_id,
		    in->in_color)),
		.action = htole32(IWX_FW_CTXT_ACTION_ADD),
		.conf_id = htole32(IWX_SESSION_PROTECT_CONF_ASSOC),
		.duration_tu = htole32(duration_tu),
	};
	uint32_t cmd_id;
	int err;

	cmd_id = iwx_cmd_id(IWX_SESSION_PROTECTION_CMD, IWX_MAC_CONF_GROUP, 0);
	err = iwx_send_cmd_pdu(sc, cmd_id, 0, sizeof(cmd), &cmd);
	if (!err)
		sc->sc_flags |= IWX_FLAG_TE_ACTIVE;
	return err;
}

static void
iwx_unprotect_session(struct iwx_softc *sc, struct iwx_node *in)
{
	struct iwx_session_prot_cmd cmd = {
		.id_and_color = htole32(IWX_FW_CMD_ID_AND_COLOR(in->in_id,
		    in->in_color)),
		.action = htole32(IWX_FW_CTXT_ACTION_REMOVE),
		.conf_id = htole32(IWX_SESSION_PROTECT_CONF_ASSOC),
		.duration_tu = 0,
	};
	uint32_t cmd_id;

	/* Do nothing if the time event has already ended. */
	if ((sc->sc_flags & IWX_FLAG_TE_ACTIVE) == 0)
		return;

	cmd_id = iwx_cmd_id(IWX_SESSION_PROTECTION_CMD, IWX_MAC_CONF_GROUP, 0);
	if (iwx_send_cmd_pdu(sc, cmd_id, 0, sizeof(cmd), &cmd) == 0)
		sc->sc_flags &= ~IWX_FLAG_TE_ACTIVE;
}

/*
 * NVM read access and content parsing.  We do not support
 * external NVM or writing NVM.
 */

static uint8_t
iwx_fw_valid_tx_ant(struct iwx_softc *sc)
{
	uint8_t tx_ant;

	tx_ant = ((sc->sc_fw_phy_config & IWX_FW_PHY_CFG_TX_CHAIN)
	    >> IWX_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
iwx_fw_valid_rx_ant(struct iwx_softc *sc)
{
	uint8_t rx_ant;

	rx_ant = ((sc->sc_fw_phy_config & IWX_FW_PHY_CFG_RX_CHAIN)
	    >> IWX_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
iwx_init_channel_map(struct ieee80211com *ic, int maxchans, int *nchans,
    struct ieee80211_channel chans[])
{
	struct iwx_softc *sc = ic->ic_softc;
	struct iwx_nvm_data *data = &sc->sc_nvm;
	uint8_t bands[IEEE80211_MODE_BYTES];
	const uint8_t *nvm_channels;
	uint32_t ch_flags;
	int ch_idx, nchan;

	if (sc->sc_uhb_supported) {
		nchan = nitems(iwx_nvm_channels_uhb);
		nvm_channels = iwx_nvm_channels_uhb;
	} else {
		nchan = nitems(iwx_nvm_channels_8000);
		nvm_channels = iwx_nvm_channels_8000;
	}

	/* 2.4Ghz; 1-13: 11b/g channels. */
	if (!data->sku_cap_band_24GHz_enable)
		goto band_5;

	memset(bands, 0, sizeof(bands));
	setbit(bands, IEEE80211_MODE_11B);
	setbit(bands, IEEE80211_MODE_11G);
	setbit(bands, IEEE80211_MODE_11NG);
	for (ch_idx = 0;
	    ch_idx < IWX_NUM_2GHZ_CHANNELS && ch_idx < nchan;
	    ch_idx++) {

		uint32_t nflags = 0;
		int cflags = 0;

		if (sc->sc_rsp_vers == IWX_FBSD_RSP_V4) {
			ch_flags = le32_to_cpup(
			    sc->sc_rsp_info.rsp_v4.regulatory.channel_profile + ch_idx);
		} else {
			ch_flags = le16_to_cpup(
			    sc->sc_rsp_info.rsp_v3.regulatory.channel_profile + ch_idx);
		}
		if ((ch_flags & IWX_NVM_CHANNEL_VALID) == 0)
			continue;

	          if ((ch_flags & IWX_NVM_CHANNEL_40MHZ) != 0)
                  cflags |= NET80211_CBW_FLAG_HT40;

		/* XXX-BZ nflags RADAR/DFS/INDOOR */

		/* error = */ ieee80211_add_channel_cbw(chans, maxchans, nchans,
		nvm_channels[ch_idx],
		ieee80211_ieee2mhz(nvm_channels[ch_idx], IEEE80211_CHAN_B),
		/* max_power IWL_DEFAULT_MAX_TX_POWER */ 22,
		nflags, bands, cflags);
	}

band_5:
	/* 5Ghz */
	if (!data->sku_cap_band_52GHz_enable)
		goto band_6;


	memset(bands, 0, sizeof(bands));
	setbit(bands, IEEE80211_MODE_11A);
	setbit(bands, IEEE80211_MODE_11NA);
	setbit(bands, IEEE80211_MODE_VHT_5GHZ);

	for (ch_idx = IWX_NUM_2GHZ_CHANNELS;
	    ch_idx < (IWX_NUM_2GHZ_CHANNELS + IWX_NUM_5GHZ_CHANNELS) && ch_idx < nchan;
	    ch_idx++) {
		uint32_t nflags = 0;
		int cflags = 0;

		if (sc->sc_rsp_vers == IWX_FBSD_RSP_V4)
			ch_flags = le32_to_cpup(
			    sc->sc_rsp_info.rsp_v4.regulatory.channel_profile + ch_idx);
		else
			ch_flags = le16_to_cpup(
			    sc->sc_rsp_info.rsp_v3.regulatory.channel_profile + ch_idx);

		if ((ch_flags & IWX_NVM_CHANNEL_VALID) == 0)
		continue;

		if ((ch_flags & IWX_NVM_CHANNEL_40MHZ) != 0)
			cflags |= NET80211_CBW_FLAG_HT40;
		if ((ch_flags & IWX_NVM_CHANNEL_80MHZ) != 0)
			cflags |= NET80211_CBW_FLAG_VHT80;
		if ((ch_flags & IWX_NVM_CHANNEL_160MHZ) != 0)
			cflags |= NET80211_CBW_FLAG_VHT160;

		/* XXX-BZ nflags RADAR/DFS/INDOOR */

		/* error = */ ieee80211_add_channel_cbw(chans, maxchans, nchans,
		nvm_channels[ch_idx],
		ieee80211_ieee2mhz(nvm_channels[ch_idx], IEEE80211_CHAN_A),
		/* max_power IWL_DEFAULT_MAX_TX_POWER */ 22,
		nflags, bands, cflags);
	}
band_6:
	/* 6GHz one day ... */
	return;
}

static int
iwx_mimo_enabled(struct iwx_softc *sc)
{

	return !sc->sc_nvm.sku_cap_mimo_disable;
}

static void
iwx_init_reorder_buffer(struct iwx_reorder_buffer *reorder_buf,
    uint16_t ssn, uint16_t buf_size)
{
	reorder_buf->head_sn = ssn;
	reorder_buf->num_stored = 0;
	reorder_buf->buf_size = buf_size;
	reorder_buf->last_amsdu = 0;
	reorder_buf->last_sub_index = 0;
	reorder_buf->removed = 0;
	reorder_buf->valid = 0;
	reorder_buf->consec_oldsn_drops = 0;
	reorder_buf->consec_oldsn_ampdu_gp2 = 0;
	reorder_buf->consec_oldsn_prev_drop = 0;
}

static void
iwx_clear_reorder_buffer(struct iwx_softc *sc, struct iwx_rxba_data *rxba)
{
	struct iwx_reorder_buffer *reorder_buf = &rxba->reorder_buf;

	reorder_buf->removed = 1;
	rxba->baid = IWX_RX_REORDER_DATA_INVALID_BAID;
}

#define IWX_MAX_RX_BA_SESSIONS 16

static struct iwx_rxba_data *
iwx_find_rxba_data(struct iwx_softc *sc, uint8_t tid)
{
	int i;

	for (i = 0; i < nitems(sc->sc_rxba_data); i++) {
		if (sc->sc_rxba_data[i].baid ==
		    IWX_RX_REORDER_DATA_INVALID_BAID)
			continue;
		if (sc->sc_rxba_data[i].tid == tid)
			return &sc->sc_rxba_data[i];
	}

	return NULL;
}

static int
iwx_sta_rx_agg_baid_cfg_cmd(struct iwx_softc *sc, struct ieee80211_node *ni,
    uint8_t tid, uint16_t ssn, uint16_t winsize, int timeout_val, int start,
    uint8_t *baid)
{
	struct iwx_rx_baid_cfg_cmd cmd;
	uint32_t new_baid = 0;
	int err;

	IWX_ASSERT_LOCKED(sc);

	memset(&cmd, 0, sizeof(cmd));

	if (start) {
		cmd.action = IWX_RX_BAID_ACTION_ADD;
		cmd.alloc.sta_id_mask = htole32(1 << IWX_STATION_ID);
		cmd.alloc.tid = tid;
		cmd.alloc.ssn = htole16(ssn);
		cmd.alloc.win_size = htole16(winsize);
	} else {
		struct iwx_rxba_data *rxba;

		rxba = iwx_find_rxba_data(sc, tid);
		if (rxba == NULL)
			return ENOENT;
		*baid = rxba->baid;

		cmd.action = IWX_RX_BAID_ACTION_REMOVE;
		if (iwx_lookup_cmd_ver(sc, IWX_DATA_PATH_GROUP,
		    IWX_RX_BAID_ALLOCATION_CONFIG_CMD) == 1) {
			cmd.remove_v1.baid = rxba->baid;
		} else {
			cmd.remove.sta_id_mask = htole32(1 << IWX_STATION_ID);
			cmd.remove.tid = tid;
		}
	}

	err = iwx_send_cmd_pdu_status(sc, IWX_WIDE_ID(IWX_DATA_PATH_GROUP,
	    IWX_RX_BAID_ALLOCATION_CONFIG_CMD), sizeof(cmd), &cmd, &new_baid);
	if (err)
		return err;

	if (start) {
		if (new_baid >= nitems(sc->sc_rxba_data))
			return ERANGE;
		*baid = new_baid;
	}

	return 0;
}

static void
iwx_sta_rx_agg(struct iwx_softc *sc, struct ieee80211_node *ni, uint8_t tid,
    uint16_t ssn, uint16_t winsize, int timeout_val, int start)
{
	int err;
	struct iwx_rxba_data *rxba = NULL;
	uint8_t baid = 0;

	if (start && sc->sc_rx_ba_sessions >= IWX_MAX_RX_BA_SESSIONS) {
		return;
	}

	if (isset(sc->sc_enabled_capa, IWX_UCODE_TLV_CAPA_BAID_ML_SUPPORT)) {
		err = iwx_sta_rx_agg_baid_cfg_cmd(sc, ni, tid, ssn, winsize,
		    timeout_val, start, &baid);
	} else {
		panic("sta_rx_agg unsupported hw");
	}
	if (err) {
		DPRINTF(("%s: iwx_sta_rx_agg_sta err=%i\n", __func__, err));
		return;
	} else
		DPRINTF(("%s: iwx_sta_rx_agg_sta success\n", __func__));

	rxba = &sc->sc_rxba_data[baid];

	/* Deaggregation is done in hardware. */
	if (start) {
		if (rxba->baid != IWX_RX_REORDER_DATA_INVALID_BAID) {
			return;
		}
		rxba->sta_id = IWX_STATION_ID;
		rxba->tid = tid;
		rxba->baid = baid;
		rxba->timeout = timeout_val;
		getmicrouptime(&rxba->last_rx);
		iwx_init_reorder_buffer(&rxba->reorder_buf, ssn,
		    winsize);
		if (timeout_val != 0) {
			DPRINTF(("%s: timeout_val != 0\n", __func__));
			return;
		}
	} else
		iwx_clear_reorder_buffer(sc, rxba);

	if (start) {
		sc->sc_rx_ba_sessions++;
	} else if (sc->sc_rx_ba_sessions > 0)
		sc->sc_rx_ba_sessions--;
}

static void
iwx_sta_tx_agg_start(struct iwx_softc *sc, struct ieee80211_node *ni,
    uint8_t tid)
{
	int err, qid;

	qid = sc->aggqid[tid];
	if (qid == 0) {
		/* Firmware should pick the next unused Tx queue. */
		qid = fls(sc->qenablemsk);
	}

	DPRINTF(("%s: qid=%i\n", __func__, qid));

	/*
	 * Simply enable the queue.
	 * Firmware handles Tx Ba session setup and teardown.
	 */
	if ((sc->qenablemsk & (1 << qid)) == 0) {
		if (!iwx_nic_lock(sc)) {
			return;
		}
		err = iwx_enable_txq(sc, IWX_STATION_ID, qid, tid,
		    IWX_TX_RING_COUNT);
		iwx_nic_unlock(sc);
		if (err) {
			printf("%s: could not enable Tx queue %d "
			    "(error %d)\n", DEVNAME(sc), qid, err);
			return;
		}
	}
	ni->ni_tx_ampdu[tid].txa_flags = IEEE80211_AGGR_RUNNING;
	DPRINTF(("%s: will set sc->aggqid[%i]=%i\n", __func__, tid, qid));
	sc->aggqid[tid] = qid;
}

static void
iwx_ba_rx_task(void *arg, int npending __unused)
{
	struct iwx_softc *sc = arg;
	struct ieee80211com *ic = &sc->sc_ic;
	struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
	struct ieee80211_node *ni = vap->iv_bss;
	int tid;

	IWX_LOCK(sc);
	for (tid = 0; tid < IWX_MAX_TID_COUNT; tid++) {
		if (sc->sc_flags & IWX_FLAG_SHUTDOWN)
			break;
		if (sc->ba_rx.start_tidmask & (1 << tid)) {
			struct iwx_rx_ba *ba = &sc->ni_rx_ba[tid];
			DPRINTF(("%s: ba->ba_flags=%x\n", __func__,
			    ba->ba_flags));
			if (ba->ba_flags == IWX_BA_DONE) {
				DPRINTF(("%s: ampdu for tid %i already added\n",
				    __func__, tid));
				break;
			}

			DPRINTF(("%s: ampdu rx start for tid %i\n", __func__,
			    tid));
			iwx_sta_rx_agg(sc, ni, tid, ba->ba_winstart,
			    ba->ba_winsize, ba->ba_timeout_val, 1);
			sc->ba_rx.start_tidmask &= ~(1 << tid);
			ba->ba_flags = IWX_BA_DONE;
		} else if (sc->ba_rx.stop_tidmask & (1 << tid)) {
			iwx_sta_rx_agg(sc, ni, tid, 0, 0, 0, 0);
			sc->ba_rx.stop_tidmask &= ~(1 << tid);
		}
	}
	IWX_UNLOCK(sc);
}

static void
iwx_ba_tx_task(void *arg, int npending __unused)
{
	struct iwx_softc *sc = arg;
	struct ieee80211com *ic = &sc->sc_ic;
	struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
	struct ieee80211_node *ni = vap->iv_bss;
	int tid;

	IWX_LOCK(sc);
	for (tid = 0; tid < IWX_MAX_TID_COUNT; tid++) {
		if (sc->sc_flags & IWX_FLAG_SHUTDOWN)
			break;
		if (sc->ba_tx.start_tidmask & (1 << tid)) {
			DPRINTF(("%s: ampdu tx start for tid %i\n", __func__,
			    tid));
			iwx_sta_tx_agg_start(sc, ni, tid);
			sc->ba_tx.start_tidmask &= ~(1 << tid);
			sc->sc_flags |= IWX_FLAG_AMPDUTX;
		}
	}

	IWX_UNLOCK(sc);
}

static void
iwx_set_mac_addr_from_csr(struct iwx_softc *sc, struct iwx_nvm_data *data)
{
	uint32_t mac_addr0, mac_addr1;

	memset(data->hw_addr, 0, sizeof(data->hw_addr));

	if (!iwx_nic_lock(sc))
		return;

	mac_addr0 = htole32(IWX_READ(sc, IWX_CSR_MAC_ADDR0_STRAP(sc)));
	mac_addr1 = htole32(IWX_READ(sc, IWX_CSR_MAC_ADDR1_STRAP(sc)));

	iwx_flip_hw_address(mac_addr0, mac_addr1, data->hw_addr);

	/* If OEM fused a valid address, use it instead of the one in OTP. */
	if (iwx_is_valid_mac_addr(data->hw_addr)) {
		iwx_nic_unlock(sc);
		return;
	}

	mac_addr0 = htole32(IWX_READ(sc, IWX_CSR_MAC_ADDR0_OTP(sc)));
	mac_addr1 = htole32(IWX_READ(sc, IWX_CSR_MAC_ADDR1_OTP(sc)));

	iwx_flip_hw_address(mac_addr0, mac_addr1, data->hw_addr);

	iwx_nic_unlock(sc);
}

static int
iwx_is_valid_mac_addr(const uint8_t *addr)
{
	static const uint8_t reserved_mac[] = {
		0x02, 0xcc, 0xaa, 0xff, 0xee, 0x00
	};

	return (memcmp(reserved_mac, addr, ETHER_ADDR_LEN) != 0 &&
	    memcmp(etherbroadcastaddr, addr, sizeof(etherbroadcastaddr)) != 0 &&
	    memcmp(etheranyaddr, addr, sizeof(etheranyaddr)) != 0 &&
	    !ETHER_IS_MULTICAST(addr));
}

static void
iwx_flip_hw_address(uint32_t mac_addr0, uint32_t mac_addr1, uint8_t *dest)
{
	const uint8_t *hw_addr;

	hw_addr = (const uint8_t *)&mac_addr0;
	dest[0] = hw_addr[3];
	dest[1] = hw_addr[2];
	dest[2] = hw_addr[1];
	dest[3] = hw_addr[0];

	hw_addr = (const uint8_t *)&mac_addr1;
	dest[4] = hw_addr[1];
	dest[5] = hw_addr[0];
}

static int
iwx_nvm_get(struct iwx_softc *sc)
{
	struct iwx_nvm_get_info cmd = {};
	struct iwx_nvm_data *nvm = &sc->sc_nvm;
	struct iwx_host_cmd hcmd = {
		.flags = IWX_CMD_WANT_RESP | IWX_CMD_SEND_IN_RFKILL,
		.data = { &cmd, },
		.len = { sizeof(cmd) },
		.id = IWX_WIDE_ID(IWX_REGULATORY_AND_NVM_GROUP,
		    IWX_NVM_GET_INFO)
	};
	int err = 0;
	uint32_t mac_flags;
	/*
	 * All the values in iwx_nvm_get_info_rsp v4 are the same as
	 * in v3, except for the channel profile part of the
	 * regulatory.  So we can just access the new struct, with the
	 * exception of the latter.
	 */
	struct iwx_nvm_get_info_rsp *rsp;
	struct iwx_nvm_get_info_rsp_v3 *rsp_v3;
	int v4 = isset(sc->sc_ucode_api, IWX_UCODE_TLV_API_REGULATORY_NVM_INFO);
	size_t resp_len = v4 ? sizeof(*rsp) : sizeof(*rsp_v3);

	hcmd.resp_pkt_len = sizeof(struct iwx_rx_packet) + resp_len;
	err = iwx_send_cmd(sc, &hcmd);
	if (err) {
		printf("%s: failed to send cmd (error %d)", __func__, err);
		return err;
	}

	if (iwx_rx_packet_payload_len(hcmd.resp_pkt) != resp_len) {
		printf("%s: iwx_rx_packet_payload_len=%d\n", __func__,
		    iwx_rx_packet_payload_len(hcmd.resp_pkt));
		printf("%s: resp_len=%zu\n", __func__, resp_len);
		err = EIO;
		goto out;
	}

	memset(nvm, 0, sizeof(*nvm));

	iwx_set_mac_addr_from_csr(sc, nvm);
	if (!iwx_is_valid_mac_addr(nvm->hw_addr)) {
		printf("%s: no valid mac address was found\n", DEVNAME(sc));
		err = EINVAL;
		goto out;
	}

	rsp = (void *)hcmd.resp_pkt->data;

	/* Initialize general data */
	nvm->nvm_version = le16toh(rsp->general.nvm_version);
	nvm->n_hw_addrs = rsp->general.n_hw_addrs;

	/* Initialize MAC sku data */
	mac_flags = le32toh(rsp->mac_sku.mac_sku_flags);
	nvm->sku_cap_11ac_enable =
		!!(mac_flags & IWX_NVM_MAC_SKU_FLAGS_802_11AC_ENABLED);
	nvm->sku_cap_11n_enable =
		!!(mac_flags & IWX_NVM_MAC_SKU_FLAGS_802_11N_ENABLED);
	nvm->sku_cap_11ax_enable =
		!!(mac_flags & IWX_NVM_MAC_SKU_FLAGS_802_11AX_ENABLED);
	nvm->sku_cap_band_24GHz_enable =
		!!(mac_flags & IWX_NVM_MAC_SKU_FLAGS_BAND_2_4_ENABLED);
	nvm->sku_cap_band_52GHz_enable =
		!!(mac_flags & IWX_NVM_MAC_SKU_FLAGS_BAND_5_2_ENABLED);
	nvm->sku_cap_mimo_disable =
		!!(mac_flags & IWX_NVM_MAC_SKU_FLAGS_MIMO_DISABLED);

	/* Initialize PHY sku data */
	nvm->valid_tx_ant = (uint8_t)le32toh(rsp->phy_sku.tx_chains);
	nvm->valid_rx_ant = (uint8_t)le32toh(rsp->phy_sku.rx_chains);

	if (le32toh(rsp->regulatory.lar_enabled) &&
	    isset(sc->sc_enabled_capa, IWX_UCODE_TLV_CAPA_LAR_SUPPORT)) {
		nvm->lar_enabled = 1;
	}

	memcpy(&sc->sc_rsp_info, rsp, resp_len);
	if (v4) {
		sc->sc_rsp_vers = IWX_FBSD_RSP_V4;
	} else {
		sc->sc_rsp_vers = IWX_FBSD_RSP_V3;
	}
out:
	iwx_free_resp(sc, &hcmd);
	return err;
}

static int
iwx_load_firmware(struct iwx_softc *sc)
{
	struct iwx_fw_sects *fws;
	int err;

	IWX_ASSERT_LOCKED(sc)

	sc->sc_uc.uc_intr = 0;
	sc->sc_uc.uc_ok = 0;

	fws = &sc->sc_fw.fw_sects[IWX_UCODE_TYPE_REGULAR];
	if (sc->sc_device_family >= IWX_DEVICE_FAMILY_AX210)
		err = iwx_ctxt_info_gen3_init(sc, fws);
	else
		err = iwx_ctxt_info_init(sc, fws);
	if (err) {
		printf("%s: could not init context info\n", DEVNAME(sc));
		return err;
	}

	/* wait for the firmware to load */
	err = msleep(&sc->sc_uc, &sc->sc_mtx, 0, "iwxuc", hz);
	if (err || !sc->sc_uc.uc_ok) {
		printf("%s: firmware upload failed, %d\n", DEVNAME(sc), err);
		iwx_ctxt_info_free_paging(sc);
	}

	iwx_dma_contig_free(&sc->iml_dma);
	iwx_ctxt_info_free_fw_img(sc);

	if (!sc->sc_uc.uc_ok)
		return EINVAL;

	return err;
}

static int
iwx_start_fw(struct iwx_softc *sc)
{
	int err;

	IWX_WRITE(sc, IWX_CSR_INT, ~0);

	iwx_disable_interrupts(sc);

	/* make sure rfkill handshake bits are cleared */
	IWX_WRITE(sc, IWX_CSR_UCODE_DRV_GP1_CLR, IWX_CSR_UCODE_SW_BIT_RFKILL);
	IWX_WRITE(sc, IWX_CSR_UCODE_DRV_GP1_CLR,
	    IWX_CSR_UCODE_DRV_GP1_BIT_CMD_BLOCKED);

	/* clear (again), then enable firmware load interrupt */
	IWX_WRITE(sc, IWX_CSR_INT, ~0);

	err = iwx_nic_init(sc);
	if (err) {
		printf("%s: unable to init nic\n", DEVNAME(sc));
		return err;
	}

	iwx_enable_fwload_interrupt(sc);

	return iwx_load_firmware(sc);
}

static int
iwx_pnvm_handle_section(struct iwx_softc *sc, const uint8_t *data,
    size_t len)
{
	const struct iwx_ucode_tlv *tlv;
	uint32_t sha1 = 0;
	uint16_t mac_type = 0, rf_id = 0;
	uint8_t *pnvm_data = NULL, *tmp;
	int hw_match = 0;
	uint32_t size = 0;
	int err;

	while (len >= sizeof(*tlv)) {
		uint32_t tlv_len, tlv_type;

		len -= sizeof(*tlv);
		tlv = (const void *)data;

		tlv_len = le32toh(tlv->length);
		tlv_type = le32toh(tlv->type);

		if (len < tlv_len) {
			printf("%s: invalid TLV len: %zd/%u\n",
			    DEVNAME(sc), len, tlv_len);
			err = EINVAL;
			goto out;
		}

		data += sizeof(*tlv);

		switch (tlv_type) {
		case IWX_UCODE_TLV_PNVM_VERSION:
			if (tlv_len < sizeof(uint32_t))
				break;

			sha1 = le32_to_cpup((const uint32_t *)data);
			break;
		case IWX_UCODE_TLV_HW_TYPE:
			if (tlv_len < 2 * sizeof(uint16_t))
				break;

			if (hw_match)
				break;

			mac_type = le16_to_cpup((const uint16_t *)data);
			rf_id = le16_to_cpup((const uint16_t *)(data +
			    sizeof(uint16_t)));

			if (mac_type == IWX_CSR_HW_REV_TYPE(sc->sc_hw_rev) &&
			    rf_id == IWX_CSR_HW_RFID_TYPE(sc->sc_hw_rf_id))
				hw_match = 1;
			break;
		case IWX_UCODE_TLV_SEC_RT: {
			const struct iwx_pnvm_section *section;
			uint32_t data_len;

			section = (const void *)data;
			data_len = tlv_len - sizeof(*section);

			/* TODO: remove, this is a deprecated separator */
			if (le32_to_cpup((const uint32_t *)data) == 0xddddeeee)
				break;

			tmp = malloc(size + data_len, M_DEVBUF,
			    M_WAITOK | M_ZERO);
			if (tmp == NULL) {
				err = ENOMEM;
				goto out;
			}
			// XXX:misha pnvm_data is NULL and size is 0 at first pass
			memcpy(tmp, pnvm_data, size);
			memcpy(tmp + size, section->data, data_len);
			free(pnvm_data, M_DEVBUF);
			pnvm_data = tmp;
			size += data_len;
			break;
		}
		case IWX_UCODE_TLV_PNVM_SKU:
			/* New PNVM section started, stop parsing. */
			goto done;
		default:
			break;
		}

		if (roundup(tlv_len, 4) > len)
			break;
		len -= roundup(tlv_len, 4);
		data += roundup(tlv_len, 4);
	}
done:
	if (!hw_match || size == 0) {
		err = ENOENT;
		goto out;
	}

	err = iwx_dma_contig_alloc(sc->sc_dmat, &sc->pnvm_dma, size, 1);
	if (err) {
		printf("%s: could not allocate DMA memory for PNVM\n",
		    DEVNAME(sc));
		err = ENOMEM;
		goto out;
	}
	memcpy(sc->pnvm_dma.vaddr, pnvm_data, size);
	iwx_ctxt_info_gen3_set_pnvm(sc);
	sc->sc_pnvm_ver = sha1;
out:
	free(pnvm_data, M_DEVBUF);
	return err;
}

static int
iwx_pnvm_parse(struct iwx_softc *sc, const uint8_t *data, size_t len)
{
	const struct iwx_ucode_tlv *tlv;

	while (len >= sizeof(*tlv)) {
		uint32_t tlv_len, tlv_type;

		len -= sizeof(*tlv);
		tlv = (const void *)data;

		tlv_len = le32toh(tlv->length);
		tlv_type = le32toh(tlv->type);

		if (len < tlv_len || roundup(tlv_len, 4) > len)
			return EINVAL;

		if (tlv_type == IWX_UCODE_TLV_PNVM_SKU) {
			const struct iwx_sku_id *sku_id =
				(const void *)(data + sizeof(*tlv));

			data += sizeof(*tlv) + roundup(tlv_len, 4);
			len -= roundup(tlv_len, 4);

			if (sc->sc_sku_id[0] == le32toh(sku_id->data[0]) &&
			    sc->sc_sku_id[1] == le32toh(sku_id->data[1]) &&
			    sc->sc_sku_id[2] == le32toh(sku_id->data[2]) &&
			    iwx_pnvm_handle_section(sc, data, len) == 0)
				return 0;
		} else {
			data += sizeof(*tlv) + roundup(tlv_len, 4);
			len -= roundup(tlv_len, 4);
		}
	}

	return ENOENT;
}

/* Make AX210 firmware loading context point at PNVM image in DMA memory. */
static void
iwx_ctxt_info_gen3_set_pnvm(struct iwx_softc *sc)
{
	struct iwx_prph_scratch *prph_scratch;
	struct iwx_prph_scratch_ctrl_cfg *prph_sc_ctrl;

	prph_scratch = sc->prph_scratch_dma.vaddr;
	prph_sc_ctrl = &prph_scratch->ctrl_cfg;

	prph_sc_ctrl->pnvm_cfg.pnvm_base_addr = htole64(sc->pnvm_dma.paddr);
	prph_sc_ctrl->pnvm_cfg.pnvm_size = htole32(sc->pnvm_dma.size);

	bus_dmamap_sync(sc->sc_dmat, sc->pnvm_dma.map, BUS_DMASYNC_PREWRITE);
}

/*
 * Load platform-NVM (non-volatile-memory) data from the filesystem.
 * This data apparently contains regulatory information and affects device
 * channel configuration.
 * The SKU of AX210 devices tells us which PNVM file section is needed.
 * Pre-AX210 devices store NVM data onboard.
 */
static int
iwx_load_pnvm(struct iwx_softc *sc)
{
	const int wait_flags = IWX_PNVM_COMPLETE;
	int err = 0;
	const struct firmware *pnvm;

	if (sc->sc_sku_id[0] == 0 &&
	    sc->sc_sku_id[1] == 0 &&
	    sc->sc_sku_id[2] == 0)
		return 0;

	if (sc->sc_pnvm_name) {
		if (sc->pnvm_dma.vaddr == NULL) {
			IWX_UNLOCK(sc);
			pnvm = firmware_get(sc->sc_pnvm_name);
			if (pnvm == NULL) {
				printf("%s: could not read %s (error %d)\n",
				    DEVNAME(sc), sc->sc_pnvm_name, err);
				IWX_LOCK(sc);
				return EINVAL;
			}
			sc->sc_pnvm = pnvm;

			err = iwx_pnvm_parse(sc, pnvm->data, pnvm->datasize);
			IWX_LOCK(sc);
			if (err && err != ENOENT) {
				return EINVAL;
			}
		} else
			iwx_ctxt_info_gen3_set_pnvm(sc);
	}

	if (!iwx_nic_lock(sc)) {
		return EBUSY;
	}

	/*
	 * If we don't have a platform NVM file simply ask firmware
	 * to proceed without it.
	 */

	iwx_write_umac_prph(sc, IWX_UREG_DOORBELL_TO_ISR6,
	    IWX_UREG_DOORBELL_TO_ISR6_PNVM);

	/* Wait for the pnvm complete notification from firmware. */
	while ((sc->sc_init_complete & wait_flags) != wait_flags) {
		err = msleep(&sc->sc_init_complete, &sc->sc_mtx, 0, "iwxinit", 2 * hz);
		if (err)
			break;
	}

	iwx_nic_unlock(sc);

	return err;
}

static int
iwx_send_tx_ant_cfg(struct iwx_softc *sc, uint8_t valid_tx_ant)
{
	struct iwx_tx_ant_cfg_cmd tx_ant_cmd = {
		.valid = htole32(valid_tx_ant),
	};

	return iwx_send_cmd_pdu(sc, IWX_TX_ANT_CONFIGURATION_CMD,
	    0, sizeof(tx_ant_cmd), &tx_ant_cmd);
}

static int
iwx_send_phy_cfg_cmd(struct iwx_softc *sc)
{
	struct iwx_phy_cfg_cmd phy_cfg_cmd;

	phy_cfg_cmd.phy_cfg = htole32(sc->sc_fw_phy_config);
	phy_cfg_cmd.calib_control.event_trigger =
	    sc->sc_default_calib[IWX_UCODE_TYPE_REGULAR].event_trigger;
	phy_cfg_cmd.calib_control.flow_trigger =
	    sc->sc_default_calib[IWX_UCODE_TYPE_REGULAR].flow_trigger;

	return iwx_send_cmd_pdu(sc, IWX_PHY_CONFIGURATION_CMD, 0,
	    sizeof(phy_cfg_cmd), &phy_cfg_cmd);
}

static int
iwx_send_dqa_cmd(struct iwx_softc *sc)
{
	struct iwx_dqa_enable_cmd dqa_cmd = {
		.cmd_queue = htole32(IWX_DQA_CMD_QUEUE),
	};
	uint32_t cmd_id;

	cmd_id = iwx_cmd_id(IWX_DQA_ENABLE_CMD, IWX_DATA_PATH_GROUP, 0);
	return iwx_send_cmd_pdu(sc, cmd_id, 0, sizeof(dqa_cmd), &dqa_cmd);
}

static int
iwx_load_ucode_wait_alive(struct iwx_softc *sc)
{
	int err;

	IWX_UNLOCK(sc);
	err = iwx_read_firmware(sc);
	IWX_LOCK(sc);
	if (err)
		return err;

	err = iwx_start_fw(sc);
	if (err)
		return err;

	if (sc->sc_device_family >= IWX_DEVICE_FAMILY_AX210) {
		err = iwx_load_pnvm(sc);
		if (err)
			return err;
	}

	iwx_post_alive(sc);

	return 0;
}

static int
iwx_run_init_mvm_ucode(struct iwx_softc *sc, int readnvm)
{
	const int wait_flags = IWX_INIT_COMPLETE;
	struct iwx_nvm_access_complete_cmd nvm_complete = {};
	struct iwx_init_extended_cfg_cmd init_cfg = {
		.init_flags = htole32(IWX_INIT_NVM),
	};

	int err;

	if ((sc->sc_flags & IWX_FLAG_RFKILL) && !readnvm) {
		printf("%s: radio is disabled by hardware switch\n",
		    DEVNAME(sc));
		return EPERM;
	}

	sc->sc_init_complete = 0;
	err = iwx_load_ucode_wait_alive(sc);
	if (err) {
		IWX_DPRINTF(sc, IWX_DEBUG_FIRMWARE_TLV,
		    "%s: failed to load init firmware\n", DEVNAME(sc));
		return err;
	} else {
		IWX_DPRINTF(sc, IWX_DEBUG_FIRMWARE_TLV,
		    "%s: successfully loaded init firmware\n", __func__);
	}

	/*
	 * Send init config command to mark that we are sending NVM
	 * access commands
	 */
	err = iwx_send_cmd_pdu(sc, IWX_WIDE_ID(IWX_SYSTEM_GROUP,
	    IWX_INIT_EXTENDED_CFG_CMD), 0, sizeof(init_cfg), &init_cfg);
	if (err) {
		printf("%s: IWX_INIT_EXTENDED_CFG_CMD error=%d\n", __func__,
		    err);
		return err;
	}

	err = iwx_send_cmd_pdu(sc, IWX_WIDE_ID(IWX_REGULATORY_AND_NVM_GROUP,
	    IWX_NVM_ACCESS_COMPLETE), 0, sizeof(nvm_complete), &nvm_complete);
	if (err) {
		return err;
	}

	/* Wait for the init complete notification from the firmware. */
	while ((sc->sc_init_complete & wait_flags) != wait_flags) {
		err = msleep(&sc->sc_init_complete, &sc->sc_mtx, 0, "iwxinit", 2 * hz);
		if (err) {
			DPRINTF(("%s: will return err=%d\n", __func__, err));
			return err;
		} else {
			DPRINTF(("%s: sc_init_complete == IWX_INIT_COMPLETE\n",
			    __func__));
		}
	}

	if (readnvm) {
		err = iwx_nvm_get(sc);
		DPRINTF(("%s: err=%d\n", __func__, err));
		if (err) {
			printf("%s: failed to read nvm (error %d)\n",
			    DEVNAME(sc), err);
			return err;
		} else {
			DPRINTF(("%s: successfully read nvm\n", DEVNAME(sc)));
		}
		IEEE80211_ADDR_COPY(sc->sc_ic.ic_macaddr, sc->sc_nvm.hw_addr);
	}
	return 0;
}

static int
iwx_config_ltr(struct iwx_softc *sc)
{
	struct iwx_ltr_config_cmd cmd = {
		.flags = htole32(IWX_LTR_CFG_FLAG_FEATURE_ENABLE),
	};

	if (!sc->sc_ltr_enabled)
		return 0;

	return iwx_send_cmd_pdu(sc, IWX_LTR_CONFIG, 0, sizeof(cmd), &cmd);
}

static void
iwx_update_rx_desc(struct iwx_softc *sc, struct iwx_rx_ring *ring, int idx,
    bus_dma_segment_t *seg)
{
	struct iwx_rx_data *data = &ring->data[idx];

	if (sc->sc_device_family >= IWX_DEVICE_FAMILY_AX210) {
		struct iwx_rx_transfer_desc *desc = ring->desc;
		desc[idx].rbid = htole16(idx & 0xffff);
		desc[idx].addr = htole64((*seg).ds_addr);
		bus_dmamap_sync(ring->data_dmat, data->map,
		    BUS_DMASYNC_PREWRITE);
	} else {
		((uint64_t *)ring->desc)[idx] =
		    htole64((*seg).ds_addr);
		bus_dmamap_sync(ring->data_dmat, data->map,
		    BUS_DMASYNC_PREWRITE);
	}
}

static int
iwx_rx_addbuf(struct iwx_softc *sc, int size, int idx)
{
	struct iwx_rx_ring *ring = &sc->rxq;
	struct iwx_rx_data *data = &ring->data[idx];
	struct mbuf *m;
	int err;
	int fatal = 0;
	bus_dma_segment_t seg;
	int nsegs;

	m = m_getjcl(M_NOWAIT, MT_DATA, M_PKTHDR, IWX_RBUF_SIZE);
	if (m == NULL)
		return ENOBUFS;

	if (data->m != NULL) {
		bus_dmamap_unload(ring->data_dmat, data->map);
		fatal = 1;
	}

	m->m_len = m->m_pkthdr.len = m->m_ext.ext_size;
	err = bus_dmamap_load_mbuf_sg(ring->data_dmat, data->map, m, &seg,
	    &nsegs, BUS_DMA_NOWAIT);
	if (err) {
		/* XXX */
		if (fatal)
			panic("could not load RX mbuf");
		m_freem(m);
		return err;
	}
	data->m = m;
	bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_PREREAD);

	/* Update RX descriptor. */
	iwx_update_rx_desc(sc, ring, idx, &seg);
	return 0;
}

static int
iwx_rxmq_get_signal_strength(struct iwx_softc *sc,
    struct iwx_rx_mpdu_desc *desc)
{
	int energy_a, energy_b;

	if (sc->sc_device_family >= IWX_DEVICE_FAMILY_AX210) {
		energy_a = desc->v3.energy_a;
		energy_b = desc->v3.energy_b;
	} else {
		energy_a = desc->v1.energy_a;
		energy_b = desc->v1.energy_b;
	}
	energy_a = energy_a ? -energy_a : -256;
	energy_b = energy_b ? -energy_b : -256;
	return MAX(energy_a, energy_b);
}

static int
iwx_rxmq_get_chains(struct iwx_softc *sc,
    struct iwx_rx_mpdu_desc *desc)
{

	if (sc->sc_device_family >= IWX_DEVICE_FAMILY_AX210)
		return ((desc->v3.rate_n_flags & IWX_RATE_MCS_ANT_AB_MSK) >>
		    IWX_RATE_MCS_ANT_POS);
	else
		return ((desc->v1.rate_n_flags & IWX_RATE_MCS_ANT_AB_MSK) >>
		    IWX_RATE_MCS_ANT_POS);
}

static void
iwx_rx_rx_phy_cmd(struct iwx_softc *sc, struct iwx_rx_packet *pkt,
    struct iwx_rx_data *data)
{
	struct iwx_rx_phy_info *phy_info = (void *)pkt->data;
	struct iwx_cmd_header *cmd_hdr = &pkt->hdr;
	int qid = cmd_hdr->qid;
	struct iwx_tx_ring *ring = &sc->txq[qid];

	bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_PREREAD);
	memcpy(&sc->sc_last_phy_info, phy_info, sizeof(sc->sc_last_phy_info));
}

/*
 * Retrieve the average noise (in dBm) among receivers.
 */
static int
iwx_get_noise(const struct iwx_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++;
		}
	}

	/* There should be at least one antenna but check anyway. */
	return (nbant == 0) ? -127 : (total / nbant) - 107;
}

#if 0
int
iwx_ccmp_decap(struct iwx_softc *sc, struct mbuf *m, struct ieee80211_node *ni,
    struct ieee80211_rxinfo *rxi)
{
	struct ieee80211com *ic = &sc->sc_ic;
	struct ieee80211_key *k;
	struct ieee80211_frame *wh;
	uint64_t pn, *prsc;
	uint8_t *ivp;
	uint8_t tid;
	int hdrlen, hasqos;

	wh = mtod(m, struct ieee80211_frame *);
	hdrlen = ieee80211_get_hdrlen(wh);
	ivp = (uint8_t *)wh + hdrlen;

	/* find key for decryption */
	k = ieee80211_get_rxkey(ic, m, ni);
	if (k == NULL || k->k_cipher != IEEE80211_CIPHER_CCMP)
		return 1;

	/* Check that ExtIV bit is be set. */
	if (!(ivp[3] & IEEE80211_WEP_EXTIV))
		return 1;

	hasqos = ieee80211_has_qos(wh);
	tid = hasqos ? ieee80211_get_qos(wh) & IEEE80211_QOS_TID : 0;
	prsc = &k->k_rsc[tid];

	/* Extract the 48-bit PN from the CCMP header. */
	pn = (uint64_t)ivp[0]       |
	     (uint64_t)ivp[1] <<  8 |
	     (uint64_t)ivp[4] << 16 |
	     (uint64_t)ivp[5] << 24 |
	     (uint64_t)ivp[6] << 32 |
	     (uint64_t)ivp[7] << 40;
	if (rxi->rxi_flags & IEEE80211_RXI_HWDEC_SAME_PN) {
		if (pn < *prsc) {
			ic->ic_stats.is_ccmp_replays++;
			return 1;
		}
	} else if (pn <= *prsc) {
		ic->ic_stats.is_ccmp_replays++;
		return 1;
	}
	/* Last seen packet number is updated in ieee80211_inputm(). */

	/*
	 * Some firmware versions strip the MIC, and some don't. It is not
	 * clear which of the capability flags could tell us what to expect.
	 * For now, keep things simple and just leave the MIC in place if
	 * it is present.
	 *
	 * The IV will be stripped by ieee80211_inputm().
	 */
	return 0;
}
#endif

static int
iwx_rx_hwdecrypt(struct iwx_softc *sc, struct mbuf *m, uint32_t rx_pkt_status)
{
	struct ieee80211_frame *wh;
	int ret = 0;
	uint8_t type, subtype;

	wh = mtod(m, struct ieee80211_frame *);

	type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK;
	if (type == IEEE80211_FC0_TYPE_CTL) {
		return 0;
	}

	subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK;
	if (IEEE80211_QOS_HAS_SEQ(wh) && (subtype & IEEE80211_FC0_SUBTYPE_NODATA)) {
		return 0;
	}


	if (((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) !=
	    IEEE80211_FC0_TYPE_CTL)
	    && (wh->i_fc[1] & IEEE80211_FC1_PROTECTED)) {
		if ((rx_pkt_status & IWX_RX_MPDU_RES_STATUS_SEC_ENC_MSK) !=
		    IWX_RX_MPDU_RES_STATUS_SEC_CCM_ENC) {
			DPRINTF(("%s: not IWX_RX_MPDU_RES_STATUS_SEC_CCM_ENC\n", __func__));
			ret = 1;
			goto out;
		}
		/* Check whether decryption was successful or not. */
		if ((rx_pkt_status &
		    (IWX_RX_MPDU_RES_STATUS_DEC_DONE |
		    IWX_RX_MPDU_RES_STATUS_MIC_OK)) !=
		    (IWX_RX_MPDU_RES_STATUS_DEC_DONE |
		    IWX_RX_MPDU_RES_STATUS_MIC_OK)) {
			DPRINTF(("%s: not IWX_RX_MPDU_RES_STATUS_MIC_OK\n", __func__));
			ret = 1;
			goto out;
		}
	} 
	out:
	return ret;
}

static void
iwx_rx_frame(struct iwx_softc *sc, struct mbuf *m, int chanidx,
    uint32_t rx_pkt_status, int is_shortpre, int rate_n_flags,
    uint32_t device_timestamp, uint8_t rssi)
{
	struct ieee80211com *ic = &sc->sc_ic;
	struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
	struct ieee80211_frame *wh;
	struct ieee80211_node *ni;

	/*
	 * We need to turn the hardware provided channel index into a channel
	 * and then find it in our ic_channels array
	 */
	if (chanidx < 0 || chanidx >= nitems(ic->ic_channels)) {
		/*
		 * OpenBSD points this at the ibss chan, which it defaults to
		 * channel 1 and then never touches again. Skip a step.
		 */
		printf("iwx: %s:%d controlling chanidx to 1 (%d)\n", __func__, __LINE__, chanidx);
		chanidx = 1;
	}

	int channel = chanidx;
	for (int i = 0; i < ic->ic_nchans; i++) {
		if (ic->ic_channels[i].ic_ieee == channel) {
			chanidx = i;
		}
	}
	ic->ic_curchan = &ic->ic_channels[chanidx];

	wh = mtod(m, struct ieee80211_frame *);
	ni = ieee80211_find_rxnode(ic, (struct ieee80211_frame_min *)wh);

#if 0	/* XXX hw decrypt */
	if ((rxi->rxi_flags & IEEE80211_RXI_HWDEC) &&
	    iwx_ccmp_decap(sc, m, ni, rxi) != 0) {
		m_freem(m);
		ieee80211_release_node(ic, ni);
		return;
	}
#endif
	if (ieee80211_radiotap_active_vap(vap)) {
		struct iwx_rx_radiotap_header *tap = &sc->sc_rxtap;
		uint16_t chan_flags;
		int have_legacy_rate = 1;
		uint8_t mcs, rate;

		tap->wr_flags = 0;
		if (is_shortpre)
			tap->wr_flags |= IEEE80211_RADIOTAP_F_SHORTPRE;
		tap->wr_chan_freq =
		    htole16(ic->ic_channels[chanidx].ic_freq);
		chan_flags = ic->ic_channels[chanidx].ic_flags;
#if 0
		if (ic->ic_curmode != IEEE80211_MODE_11N &&
		    ic->ic_curmode != IEEE80211_MODE_11AC) {
			chan_flags &= ~IEEE80211_CHAN_HT;
			chan_flags &= ~IEEE80211_CHAN_40MHZ;
		}
		if (ic->ic_curmode != IEEE80211_MODE_11AC)
			chan_flags &= ~IEEE80211_CHAN_VHT;
#else
		chan_flags &= ~IEEE80211_CHAN_HT;
#endif
		tap->wr_chan_flags = htole16(chan_flags);
		tap->wr_dbm_antsignal = rssi;
		tap->wr_dbm_antnoise = (int8_t)sc->sc_noise;
		tap->wr_tsft = device_timestamp;

		if (sc->sc_rate_n_flags_version >= 2) {
			uint32_t mod_type = (rate_n_flags &
			    IWX_RATE_MCS_MOD_TYPE_MSK);
			const struct ieee80211_rateset *rs = NULL;
			uint32_t ridx;
			have_legacy_rate = (mod_type == IWX_RATE_MCS_CCK_MSK ||
			    mod_type == IWX_RATE_MCS_LEGACY_OFDM_MSK);
			mcs = (rate_n_flags & IWX_RATE_HT_MCS_CODE_MSK);
			ridx = (rate_n_flags & IWX_RATE_LEGACY_RATE_MSK);
			if (mod_type == IWX_RATE_MCS_CCK_MSK)
				rs = &ieee80211_std_rateset_11b;
			else if (mod_type == IWX_RATE_MCS_LEGACY_OFDM_MSK)
				rs = &ieee80211_std_rateset_11a;
			if (rs && ridx < rs->rs_nrates) {
				rate = (rs->rs_rates[ridx] &
				    IEEE80211_RATE_VAL);
			} else
				rate = 0;
		} else {
			have_legacy_rate = ((rate_n_flags &
			    (IWX_RATE_MCS_HT_MSK_V1 |
			    IWX_RATE_MCS_VHT_MSK_V1)) == 0);
			mcs = (rate_n_flags &
			    (IWX_RATE_HT_MCS_RATE_CODE_MSK_V1 |
			    IWX_RATE_HT_MCS_NSS_MSK_V1));
			rate = (rate_n_flags & IWX_RATE_LEGACY_RATE_MSK_V1);
		}
		if (!have_legacy_rate) {
			tap->wr_rate = (0x80 | mcs);
		} else {
			switch (rate) {
			/* CCK rates. */
			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;
			/* OFDM rates. */
			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;
			/* Unknown rate: should not happen. */
			default:  tap->wr_rate =   0;
			}
			// XXX hack - this needs rebased with the new rate stuff anyway
			tap->wr_rate = rate;
		}
	}

	IWX_UNLOCK(sc);
	if (ni == NULL) {
		if (ieee80211_input_mimo_all(ic, m) == -1)
			printf("%s:%d input_all returned -1\n", __func__, __LINE__);
	} else {

		if (ieee80211_input_mimo(ni, m) == -1)
			printf("%s:%d input_all returned -1\n", __func__, __LINE__);
		ieee80211_free_node(ni);
	}
	IWX_LOCK(sc);
}

static void
iwx_rx_mpdu_mq(struct iwx_softc *sc, struct mbuf *m, void *pktdata,
    size_t maxlen)
{
	struct ieee80211com *ic = &sc->sc_ic;
	struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
	struct ieee80211_node *ni = vap->iv_bss;
	struct ieee80211_key *k;
	struct ieee80211_rx_stats rxs;
	struct iwx_rx_mpdu_desc *desc;
	uint32_t len, hdrlen, rate_n_flags, device_timestamp;
	int rssi;
	uint8_t chanidx;
	uint16_t phy_info;
	size_t desc_size;
	int pad = 0;

	if (sc->sc_device_family >= IWX_DEVICE_FAMILY_AX210)
		desc_size = sizeof(*desc);
	else
		desc_size = IWX_RX_DESC_SIZE_V1;

	if (maxlen < desc_size) {
		m_freem(m);
		return; /* drop */
	}

	desc = (struct iwx_rx_mpdu_desc *)pktdata;

	if (!(desc->status & htole16(IWX_RX_MPDU_RES_STATUS_CRC_OK)) ||
	    !(desc->status & htole16(IWX_RX_MPDU_RES_STATUS_OVERRUN_OK))) {
		printf("%s: Bad CRC or FIFO: 0x%08X\n", __func__, desc->status);
		m_freem(m);
		return; /* drop */
	}

	len = le16toh(desc->mpdu_len);
	if (ic->ic_opmode == IEEE80211_M_MONITOR) {
		/* Allow control frames in monitor mode. */
		if (len < sizeof(struct ieee80211_frame_cts)) {
			m_freem(m);
			return;
		}

	} else if (len < sizeof(struct ieee80211_frame)) {
		m_freem(m);
		return;
	}
	if (len > maxlen - desc_size) {
		m_freem(m);
		return;
	}

	// TODO: arithmetic on a pointer to void is a GNU extension
	m->m_data = (char *)pktdata + desc_size;
	m->m_pkthdr.len = m->m_len = len;

	/* Account for padding following the frame header. */
	if (desc->mac_flags2 & IWX_RX_MPDU_MFLG2_PAD) {
		struct ieee80211_frame *wh = mtod(m, struct ieee80211_frame *);
		int type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK;
		if (type == IEEE80211_FC0_TYPE_CTL) {
			switch (wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK) {
			case IEEE80211_FC0_SUBTYPE_CTS:
				hdrlen = sizeof(struct ieee80211_frame_cts);
				break;
			case IEEE80211_FC0_SUBTYPE_ACK:
				hdrlen = sizeof(struct ieee80211_frame_ack);
				break;
			default:
				hdrlen = sizeof(struct ieee80211_frame_min);
				break;
			}
		} else
			hdrlen = ieee80211_hdrsize(wh);

		if ((le16toh(desc->status) &
		    IWX_RX_MPDU_RES_STATUS_SEC_ENC_MSK) ==
		    IWX_RX_MPDU_RES_STATUS_SEC_CCM_ENC) {
			// CCMP header length
			hdrlen += 8;
		}

		memmove(m->m_data + 2, m->m_data, hdrlen);
		m_adj(m, 2);

	}

	if ((le16toh(desc->status) &
	    IWX_RX_MPDU_RES_STATUS_SEC_ENC_MSK) ==
	    IWX_RX_MPDU_RES_STATUS_SEC_CCM_ENC) {
		pad = 1;
	}

//	/*
//	 * Hardware de-aggregates A-MSDUs and copies the same MAC header
//	 * in place for each subframe. But it leaves the 'A-MSDU present'
//	 * bit set in the frame header. We need to clear this bit ourselves.
//	 * (XXX This workaround is not required on AX200/AX201 devices that
//	 * have been tested by me, but it's unclear when this problem was
//	 * fixed in the hardware. It definitely affects the 9k generation.
//	 * Leaving this in place for now since some 9k/AX200 hybrids seem
//	 * to exist that we may eventually add support for.)
//	 *
//	 * And we must allow the same CCMP PN for subframes following the
//	 * first subframe. Otherwise they would be discarded as replays.
//	 */
	if (desc->mac_flags2 & IWX_RX_MPDU_MFLG2_AMSDU) {
		DPRINTF(("%s: === IWX_RX_MPDU_MFLG2_AMSDU\n", __func__));
//		struct ieee80211_frame *wh = mtod(m, struct ieee80211_frame *);
//		uint8_t subframe_idx = (desc->amsdu_info &
//		    IWX_RX_MPDU_AMSDU_SUBFRAME_IDX_MASK);
//		if (subframe_idx > 0)
//			rxi.rxi_flags |= IEEE80211_RXI_HWDEC_SAME_PN;
//		if (ieee80211_has_qos(wh) && ieee80211_has_addr4(wh) &&
//		    m->m_len >= sizeof(struct ieee80211_qosframe_addr4)) {
//			struct ieee80211_qosframe_addr4 *qwh4 = mtod(m,
//			    struct ieee80211_qosframe_addr4 *);
//			qwh4->i_qos[0] &= htole16(~IEEE80211_QOS_AMSDU);
//		} else if (ieee80211_has_qos(wh) &&
//		    m->m_len >= sizeof(struct ieee80211_qosframe)) {
//			struct ieee80211_qosframe *qwh = mtod(m,
//			    struct ieee80211_qosframe *);
//			qwh->i_qos[0] &= htole16(~IEEE80211_QOS_AMSDU);
//		}
	}

	/*
	 * Verify decryption before duplicate detection. The latter uses
	 * the TID supplied in QoS frame headers and this TID is implicitly
	 * verified as part of the CCMP nonce.
	 */
	k = ieee80211_crypto_get_txkey(ni, m);
	if (k != NULL &&
	    (k->wk_cipher->ic_cipher == IEEE80211_CIPHER_AES_CCM) &&
	    iwx_rx_hwdecrypt(sc, m, le16toh(desc->status)/*, &rxi*/)) {
		DPRINTF(("%s: iwx_rx_hwdecrypt failed\n", __func__));
		m_freem(m);
		return;
	}

	if (sc->sc_device_family >= IWX_DEVICE_FAMILY_AX210) {
		rate_n_flags = le32toh(desc->v3.rate_n_flags);
		chanidx = desc->v3.channel;
		device_timestamp = le32toh(desc->v3.gp2_on_air_rise);
	} else {
		rate_n_flags = le32toh(desc->v1.rate_n_flags);
		chanidx = desc->v1.channel;
		device_timestamp = le32toh(desc->v1.gp2_on_air_rise);
	}

	phy_info = le16toh(desc->phy_info);

	rssi = iwx_rxmq_get_signal_strength(sc, desc);
	rssi = (0 - IWX_MIN_DBM) + rssi;		/* normalize */
	rssi = MIN(rssi, (IWX_MAX_DBM - IWX_MIN_DBM));	/* clip to max. 100% */

	memset(&rxs, 0, sizeof(rxs));
	rxs.r_flags |= IEEE80211_R_IEEE | IEEE80211_R_FREQ;
	rxs.r_flags |= IEEE80211_R_BAND;
	rxs.r_flags |= IEEE80211_R_NF | IEEE80211_R_RSSI;
	rxs.r_flags |= IEEE80211_R_TSF32 | IEEE80211_R_TSF_START;

	rxs.c_ieee = chanidx;
	rxs.c_freq = ieee80211_ieee2mhz(rxs.c_ieee,
	    chanidx <= 14 ? IEEE80211_CHAN_2GHZ : IEEE80211_CHAN_5GHZ);
	rxs.c_band = chanidx <= 14 ? IEEE80211_CHAN_2GHZ : IEEE80211_CHAN_5GHZ;
	rxs.c_rx_tsf = device_timestamp;
	rxs.c_chain = iwx_rxmq_get_chains(sc, desc);
	if (rxs.c_chain != 0)
		rxs.r_flags |= IEEE80211_R_C_CHAIN;

	/* rssi is in 1/2db units */
	rxs.c_rssi = rssi * 2;
	rxs.c_nf = sc->sc_noise;

	if (pad) {
		rxs.c_pktflags |= IEEE80211_RX_F_DECRYPTED;
		rxs.c_pktflags |= IEEE80211_RX_F_IV_STRIP;
	}

	if (ieee80211_add_rx_params(m, &rxs) == 0) {
		printf("%s: ieee80211_add_rx_params failed\n", __func__);
		return;
	}

	ieee80211_add_rx_params(m, &rxs);

#if 0
	if (iwx_rx_reorder(sc, m, chanidx, desc,
	    (phy_info & IWX_RX_MPDU_PHY_SHORT_PREAMBLE),
	    rate_n_flags, device_timestamp, &rxi, ml))
		return;
#endif

	if (pad) {
#define TRIM 8
		struct ieee80211_frame *wh = mtod(m, struct ieee80211_frame *);
		hdrlen = ieee80211_hdrsize(wh);
		memmove(m->m_data + TRIM, m->m_data, hdrlen);
		m_adj(m, TRIM);
#undef TRIM
	}

	iwx_rx_frame(sc, m, chanidx, le16toh(desc->status),
	    (phy_info & IWX_RX_MPDU_PHY_SHORT_PREAMBLE),
	    rate_n_flags, device_timestamp, rssi);
}

static void
iwx_clear_tx_desc(struct iwx_softc *sc, struct iwx_tx_ring *ring, int idx)
{
	struct iwx_tfh_tfd *desc = &ring->desc[idx];
	uint8_t num_tbs = le16toh(desc->num_tbs) & 0x1f;
	int i;

	/* First TB is never cleared - it is bidirectional DMA data. */
	for (i = 1; i < num_tbs; i++) {
		struct iwx_tfh_tb *tb = &desc->tbs[i];
		memset(tb, 0, sizeof(*tb));
	}
	desc->num_tbs = htole16(1);

	bus_dmamap_sync(ring->desc_dma.tag, ring->desc_dma.map,
	    BUS_DMASYNC_PREWRITE);
}

static void
iwx_txd_done(struct iwx_softc *sc, struct iwx_tx_ring *ring,
    struct iwx_tx_data *txd)
{
	bus_dmamap_sync(ring->data_dmat, txd->map, BUS_DMASYNC_POSTWRITE);
	bus_dmamap_unload(ring->data_dmat, txd->map);

	ieee80211_tx_complete(&txd->in->in_ni, txd->m, 0);
	txd->m = NULL;
	txd->in = NULL;
}

static void
iwx_txq_advance(struct iwx_softc *sc, struct iwx_tx_ring *ring, uint16_t idx)
{
	struct iwx_tx_data *txd;

	while (ring->tail_hw != idx) {
		txd = &ring->data[ring->tail];
		if (txd->m != NULL) {
			iwx_clear_tx_desc(sc, ring, ring->tail);
			iwx_tx_update_byte_tbl(sc, ring, ring->tail, 0, 0);
			iwx_txd_done(sc, ring, txd);
			ring->queued--;
			if (ring->queued < 0)
				panic("caught negative queue count");
		}
		ring->tail = (ring->tail + 1) % IWX_TX_RING_COUNT;
		ring->tail_hw = (ring->tail_hw + 1) % sc->max_tfd_queue_size;
	}
}

static void
iwx_rx_tx_cmd(struct iwx_softc *sc, struct iwx_rx_packet *pkt,
    struct iwx_rx_data *data)
{
	struct ieee80211com *ic = &sc->sc_ic;
	struct ifnet *ifp = IC2IFP(ic);
	struct iwx_cmd_header *cmd_hdr = &pkt->hdr;
	int qid = cmd_hdr->qid, status, txfail;
	struct iwx_tx_ring *ring = &sc->txq[qid];
	struct iwx_tx_resp *tx_resp = (void *)pkt->data;
	uint32_t ssn;
	uint32_t len = iwx_rx_packet_len(pkt);
	int idx = cmd_hdr->idx;
	struct iwx_tx_data *txd = &ring->data[idx];
	struct mbuf *m = txd->m;

	bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_POSTREAD);

	/* Sanity checks. */
	if (sizeof(*tx_resp) > len)
		return;
	if (qid < IWX_FIRST_AGG_TX_QUEUE && tx_resp->frame_count > 1)
		return;
	if (qid >= IWX_FIRST_AGG_TX_QUEUE && sizeof(*tx_resp) + sizeof(ssn) +
	    tx_resp->frame_count * sizeof(tx_resp->status) > len)
		return;

	sc->sc_tx_timer[qid] = 0;

	if (tx_resp->frame_count > 1) /* A-MPDU */
		return;

	status = le16toh(tx_resp->status.status) & IWX_TX_STATUS_MSK;
	txfail = (status != IWX_TX_STATUS_SUCCESS &&
	    status != IWX_TX_STATUS_DIRECT_DONE);

#ifdef __not_yet__
	/* TODO: Replace accounting below with ieee80211_tx_complete() */
	ieee80211_tx_complete(&in->in_ni, m, txfail);
#else
	if (txfail)
		if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
	else {
		if_inc_counter(ifp, IFCOUNTER_OBYTES, m->m_pkthdr.len);
		if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1);
		if (m->m_flags & M_MCAST)
			if_inc_counter(ifp, IFCOUNTER_OMCASTS, 1);
	}
#endif
	/*
	 * On hardware supported by iwx(4) the SSN counter corresponds
	 * to a Tx ring index rather than a sequence number.
	 * Frames up to this index (non-inclusive) can now be freed.
	 */
	memcpy(&ssn, &tx_resp->status + tx_resp->frame_count, sizeof(ssn));
	ssn = le32toh(ssn);
	if (ssn < sc->max_tfd_queue_size) {
		iwx_txq_advance(sc, ring, ssn);
		iwx_clear_oactive(sc, ring);
	}
}

static void
iwx_clear_oactive(struct iwx_softc *sc, struct iwx_tx_ring *ring)
{
	if (ring->queued < iwx_lomark) {
		sc->qfullmsk &= ~(1 << ring->qid);
		if (sc->qfullmsk == 0 /* && ifq_is_oactive(&ifp->if_snd) */) {
			/*
			 * Well, we're in interrupt context, but then again
			 * I guess net80211 does all sorts of stunts in
			 * interrupt context, so maybe this is no biggie.
			 */
			iwx_start(sc);
		}
	}
}

static void
iwx_rx_compressed_ba(struct iwx_softc *sc, struct iwx_rx_packet *pkt)
{
	struct iwx_compressed_ba_notif *ba_res = (void *)pkt->data;
	struct ieee80211com *ic = &sc->sc_ic;
	struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
	struct iwx_node *in = IWX_NODE(vap->iv_bss);
	struct ieee80211_node *ni = &in->in_ni;
	struct iwx_tx_ring *ring;
	uint16_t i, tfd_cnt, ra_tid_cnt, idx;
	int qid;

//	if (ic->ic_state != IEEE80211_S_RUN)
//		return;

	if (iwx_rx_packet_payload_len(pkt) < sizeof(*ba_res))
		return;

	if (ba_res->sta_id != IWX_STATION_ID)
		return;

	in = (void *)ni;

	tfd_cnt = le16toh(ba_res->tfd_cnt);
	ra_tid_cnt = le16toh(ba_res->ra_tid_cnt);
	if (!tfd_cnt || iwx_rx_packet_payload_len(pkt) < (sizeof(*ba_res) +
	    sizeof(ba_res->ra_tid[0]) * ra_tid_cnt +
	    sizeof(ba_res->tfd[0]) * tfd_cnt))
		return;

	for (i = 0; i < tfd_cnt; i++) {
		struct iwx_compressed_ba_tfd *ba_tfd = &ba_res->tfd[i];
		uint8_t tid;

		tid = ba_tfd->tid;
		if (tid >= nitems(sc->aggqid))
			continue;

		qid = sc->aggqid[tid];
		if (qid != htole16(ba_tfd->q_num))
			continue;

		ring = &sc->txq[qid];

#if 0
		ba = &ni->ni_tx_ba[tid];
		if (ba->ba_state != IEEE80211_BA_AGREED)
			continue;
#endif
		idx = le16toh(ba_tfd->tfd_index);
		sc->sc_tx_timer[qid] = 0;
		iwx_txq_advance(sc, ring, idx);
		iwx_clear_oactive(sc, ring);
	}
}

static void
iwx_rx_bmiss(struct iwx_softc *sc, struct iwx_rx_packet *pkt,
    struct iwx_rx_data *data)
{
	struct ieee80211com *ic = &sc->sc_ic;
	struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
	struct iwx_missed_beacons_notif *mbn = (void *)pkt->data;
	uint32_t missed;

	if ((ic->ic_opmode != IEEE80211_M_STA) ||
	    (vap->iv_state != IEEE80211_S_RUN))
		return;

	bus_dmamap_sync(sc->rxq.data_dmat, data->map,
	    BUS_DMASYNC_POSTREAD);

	missed = le32toh(mbn->consec_missed_beacons_since_last_rx);
	if (missed > vap->iv_bmissthreshold) {
		ieee80211_beacon_miss(ic);
	}

}

static int
iwx_binding_cmd(struct iwx_softc *sc, struct iwx_node *in, uint32_t action)
{
	struct iwx_binding_cmd cmd;
	struct ieee80211com *ic = &sc->sc_ic;
	struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
	struct iwx_vap *ivp = IWX_VAP(vap);
	struct iwx_phy_ctxt *phyctxt = ivp->phy_ctxt;
	uint32_t mac_id = IWX_FW_CMD_ID_AND_COLOR(in->in_id, in->in_color);
	int i, err, active = (sc->sc_flags & IWX_FLAG_BINDING_ACTIVE);
	uint32_t status;

	if (action == IWX_FW_CTXT_ACTION_ADD && active)
		panic("binding already added");
	if (action == IWX_FW_CTXT_ACTION_REMOVE && !active)
		panic("binding already removed");

	if (phyctxt == NULL) /* XXX race with iwx_stop() */
		return EINVAL;

	memset(&cmd, 0, sizeof(cmd));

	cmd.id_and_color
	    = htole32(IWX_FW_CMD_ID_AND_COLOR(phyctxt->id, phyctxt->color));
	cmd.action = htole32(action);
	cmd.phy = htole32(IWX_FW_CMD_ID_AND_COLOR(phyctxt->id, phyctxt->color));

	cmd.macs[0] = htole32(mac_id);
	for (i = 1; i < IWX_MAX_MACS_IN_BINDING; i++)
		cmd.macs[i] = htole32(IWX_FW_CTXT_INVALID);

	if (IEEE80211_IS_CHAN_2GHZ(phyctxt->channel) ||
	    !isset(sc->sc_enabled_capa, IWX_UCODE_TLV_CAPA_CDB_SUPPORT))
		cmd.lmac_id = htole32(IWX_LMAC_24G_INDEX);
	else
		cmd.lmac_id = htole32(IWX_LMAC_5G_INDEX);

	status = 0;
	err = iwx_send_cmd_pdu_status(sc, IWX_BINDING_CONTEXT_CMD, sizeof(cmd),
	    &cmd, &status);
	if (err == 0 && status != 0)
		err = EIO;

	return err;
}

static uint8_t
iwx_get_vht_ctrl_pos(struct ieee80211com *ic, struct ieee80211_channel *chan)
{
	int ctlchan = ieee80211_chan2ieee(ic, chan);
	int midpoint = chan->ic_vht_ch_freq1;

	/*
	 * The FW is expected to check the control channel position only
	 * when in HT/VHT and the channel width is not 20MHz. Return
	 * this value as the default one:
	 */
	uint8_t pos = IWX_PHY_VHT_CTRL_POS_1_BELOW;

	switch (ctlchan - midpoint) {
	case -6:
		pos = IWX_PHY_VHT_CTRL_POS_2_BELOW;
		break;
	case -2:
		pos = IWX_PHY_VHT_CTRL_POS_1_BELOW;
		break;
	case 2:
		pos = IWX_PHY_VHT_CTRL_POS_1_ABOVE;
		break;
	case 6:
		pos = IWX_PHY_VHT_CTRL_POS_2_ABOVE;
		break;
	default:
		break;
	}

	return pos;
}

static int
iwx_phy_ctxt_cmd_uhb_v3_v4(struct iwx_softc *sc, struct iwx_phy_ctxt *ctxt,
    uint8_t chains_static, uint8_t chains_dynamic, uint32_t action, uint8_t sco,
    uint8_t vht_chan_width, int cmdver)
{
	struct ieee80211com *ic = &sc->sc_ic;
	struct iwx_phy_context_cmd_uhb cmd;
	uint8_t active_cnt, idle_cnt;
	struct ieee80211_channel *chan = ctxt->channel;

	memset(&cmd, 0, sizeof(cmd));
	cmd.id_and_color = htole32(IWX_FW_CMD_ID_AND_COLOR(ctxt->id,
	    ctxt->color));
	cmd.action = htole32(action);

	if (IEEE80211_IS_CHAN_2GHZ(chan) ||
	    !isset(sc->sc_enabled_capa, IWX_UCODE_TLV_CAPA_CDB_SUPPORT))
		cmd.lmac_id = htole32(IWX_LMAC_24G_INDEX);
	else
		cmd.lmac_id = htole32(IWX_LMAC_5G_INDEX);

	cmd.ci.band = IEEE80211_IS_CHAN_2GHZ(chan) ?
	    IWX_PHY_BAND_24 : IWX_PHY_BAND_5;
	cmd.ci.channel = htole32(ieee80211_chan2ieee(ic, chan));

	if (IEEE80211_IS_CHAN_VHT80(chan)) {
		cmd.ci.ctrl_pos = iwx_get_vht_ctrl_pos(ic, chan);
		cmd.ci.width = IWX_PHY_VHT_CHANNEL_MODE80;
	} else if (IEEE80211_IS_CHAN_HT40(chan)) {
		cmd.ci.width = IWX_PHY_VHT_CHANNEL_MODE40;
		if (IEEE80211_IS_CHAN_HT40D(chan))
			cmd.ci.ctrl_pos = IWX_PHY_VHT_CTRL_POS_1_ABOVE;
		else
			cmd.ci.ctrl_pos = IWX_PHY_VHT_CTRL_POS_1_BELOW;
	} else {
		cmd.ci.width = IWX_PHY_VHT_CHANNEL_MODE20;
		cmd.ci.ctrl_pos = IWX_PHY_VHT_CTRL_POS_1_BELOW;
	}

	if (cmdver < 4 && iwx_lookup_cmd_ver(sc, IWX_DATA_PATH_GROUP,
	    IWX_RLC_CONFIG_CMD) != 2) {
		idle_cnt = chains_static;
		active_cnt = chains_dynamic;
		cmd.rxchain_info = htole32(iwx_fw_valid_rx_ant(sc) <<
		    IWX_PHY_RX_CHAIN_VALID_POS);
		cmd.rxchain_info |= htole32(idle_cnt <<
		    IWX_PHY_RX_CHAIN_CNT_POS);
		cmd.rxchain_info |= htole32(active_cnt <<
		    IWX_PHY_RX_CHAIN_MIMO_CNT_POS);
	}

	return iwx_send_cmd_pdu(sc, IWX_PHY_CONTEXT_CMD, 0, sizeof(cmd), &cmd);
}

#if 0
int
iwx_phy_ctxt_cmd_v3_v4(struct iwx_softc *sc, struct iwx_phy_ctxt *ctxt,
    uint8_t chains_static, uint8_t chains_dynamic, uint32_t action, uint8_t sco,
    uint8_t vht_chan_width, int cmdver)
{
	struct ieee80211com *ic = &sc->sc_ic;
	struct iwx_phy_context_cmd cmd;
	uint8_t active_cnt, idle_cnt;
	struct ieee80211_channel *chan = ctxt->channel;

	memset(&cmd, 0, sizeof(cmd));
	cmd.id_and_color = htole32(IWX_FW_CMD_ID_AND_COLOR(ctxt->id,
	    ctxt->color));
	cmd.action = htole32(action);

	if (IEEE80211_IS_CHAN_2GHZ(ctxt->channel) ||
	    !isset(sc->sc_enabled_capa, IWX_UCODE_TLV_CAPA_CDB_SUPPORT))
		cmd.lmac_id = htole32(IWX_LMAC_24G_INDEX);
	else
		cmd.lmac_id = htole32(IWX_LMAC_5G_INDEX);

	cmd.ci.band = IEEE80211_IS_CHAN_2GHZ(chan) ?
	    IWX_PHY_BAND_24 : IWX_PHY_BAND_5;
	cmd.ci.channel = ieee80211_chan2ieee(ic, chan);
	if (vht_chan_width == IEEE80211_VHTOP0_CHAN_WIDTH_80) {
		cmd.ci.ctrl_pos = iwx_get_vht_ctrl_pos(ic, chan);
		cmd.ci.width = IWX_PHY_VHT_CHANNEL_MODE80;
	} else if (chan->ic_flags & IEEE80211_CHAN_40MHZ) {
		if (sco == IEEE80211_HTOP0_SCO_SCA) {
			/* secondary chan above -> control chan below */
			cmd.ci.ctrl_pos = IWX_PHY_VHT_CTRL_POS_1_BELOW;
			cmd.ci.width = IWX_PHY_VHT_CHANNEL_MODE40;
		} else if (sco == IEEE80211_HTOP0_SCO_SCB) {
			/* secondary chan below -> control chan above */
			cmd.ci.ctrl_pos = IWX_PHY_VHT_CTRL_POS_1_ABOVE;
			cmd.ci.width = IWX_PHY_VHT_CHANNEL_MODE40;
		} else {
			cmd.ci.width = IWX_PHY_VHT_CHANNEL_MODE20;
			cmd.ci.ctrl_pos = IWX_PHY_VHT_CTRL_POS_1_BELOW;
		}
	} else {
		cmd.ci.width = IWX_PHY_VHT_CHANNEL_MODE20;
		cmd.ci.ctrl_pos = IWX_PHY_VHT_CTRL_POS_1_BELOW;
	}

	if (cmdver < 4 && iwx_lookup_cmd_ver(sc, IWX_DATA_PATH_GROUP,
	    IWX_RLC_CONFIG_CMD) != 2) {
		idle_cnt = chains_static;
		active_cnt = chains_dynamic;
		cmd.rxchain_info = htole32(iwx_fw_valid_rx_ant(sc) <<
		    IWX_PHY_RX_CHAIN_VALID_POS);
		cmd.rxchain_info |= htole32(idle_cnt <<
		    IWX_PHY_RX_CHAIN_CNT_POS);
		cmd.rxchain_info |= htole32(active_cnt <<
		    IWX_PHY_RX_CHAIN_MIMO_CNT_POS);
	}

	return iwx_send_cmd_pdu(sc, IWX_PHY_CONTEXT_CMD, 0, sizeof(cmd), &cmd);
}
#endif

static int
iwx_phy_ctxt_cmd(struct iwx_softc *sc, struct iwx_phy_ctxt *ctxt,
    uint8_t chains_static, uint8_t chains_dynamic, uint32_t action,
    uint32_t apply_time, uint8_t sco, uint8_t vht_chan_width)
{
	int cmdver;

	cmdver = iwx_lookup_cmd_ver(sc, IWX_LONG_GROUP, IWX_PHY_CONTEXT_CMD);
	if (cmdver != 3 && cmdver != 4) {
		printf("%s: firmware does not support phy-context-cmd v3/v4\n",
		    DEVNAME(sc));
		return ENOTSUP;
	}

	/*
	 * Intel increased the size of the fw_channel_info struct and neglected
	 * to bump the phy_context_cmd struct, which contains an fw_channel_info
	 * member in the middle.
	 * To keep things simple we use a separate function to handle the larger
	 * variant of the phy context command.
	 */
	if (isset(sc->sc_enabled_capa, IWX_UCODE_TLV_CAPA_ULTRA_HB_CHANNELS)) {
		return iwx_phy_ctxt_cmd_uhb_v3_v4(sc, ctxt, chains_static,
		    chains_dynamic, action, sco, vht_chan_width, cmdver);
	} else
		panic("Unsupported old hardware contact thj@");

#if 0
	return iwx_phy_ctxt_cmd_v3_v4(sc, ctxt, chains_static, chains_dynamic,
	    action, sco, vht_chan_width, cmdver);
#endif
}

static int
iwx_send_cmd(struct iwx_softc *sc, struct iwx_host_cmd *hcmd)
{
	struct iwx_tx_ring *ring = &sc->txq[IWX_DQA_CMD_QUEUE];
	struct iwx_tfh_tfd *desc;
	struct iwx_tx_data *txdata;
	struct iwx_device_cmd *cmd;
	struct mbuf *m;
	bus_addr_t paddr;
	uint64_t addr;
	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;
	bus_dma_segment_t seg[10];
	int nsegs;

	code = hcmd->id;
	async = hcmd->flags & IWX_CMD_ASYNC;
	idx = ring->cur;

	for (i = 0, paylen = 0; i < nitems(hcmd->len); i++) {
		paylen += hcmd->len[i];
	}

	/* If this command waits for a response, allocate response buffer. */
	hcmd->resp_pkt = NULL;
	if (hcmd->flags & IWX_CMD_WANT_RESP) {
		uint8_t *resp_buf;
		KASSERT(!async, ("async command want response"));
		KASSERT(hcmd->resp_pkt_len >= sizeof(struct iwx_rx_packet),
		    ("wrong pkt len 1"));
		KASSERT(hcmd->resp_pkt_len <= IWX_CMD_RESP_MAX,
		    ("wrong pkt len 2"));
		if (sc->sc_cmd_resp_pkt[idx] != NULL)
			return ENOSPC;
		resp_buf = malloc(hcmd->resp_pkt_len, M_DEVBUF,
		    M_NOWAIT | M_ZERO);
		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;
	}

	desc = &ring->desc[idx];
	txdata = &ring->data[idx];

	/*
	 * XXX Intel inside (tm)
	 * Firmware API versions >= 50 reject old-style commands in
	 * group 0 with a "BAD_COMMAND" firmware error. We must pretend
	 * that such commands were in the LONG_GROUP instead in order
	 * for firmware to accept them.
	 */
	if (iwx_cmd_groupid(code) == 0) {
		code = IWX_WIDE_ID(IWX_LONG_GROUP, code);
		txdata->flags |= IWX_TXDATA_FLAG_CMD_IS_NARROW;
	} else
		txdata->flags &= ~IWX_TXDATA_FLAG_CMD_IS_NARROW;

	group_id = iwx_cmd_groupid(code);

	hdrlen = sizeof(cmd->hdr_wide);
	datasz = sizeof(cmd->data_wide);

	if (paylen > datasz) {
		/* Command is too large to fit in pre-allocated space. */
		size_t totlen = hdrlen + paylen;
		if (paylen > IWX_MAX_CMD_PAYLOAD_SIZE) {
			printf("%s: firmware command too long (%zd bytes)\n",
			    DEVNAME(sc), totlen);
			err = EINVAL;
			goto out;
		}
		if (totlen > IWX_RBUF_SIZE)
			panic("totlen > IWX_RBUF_SIZE");
		m = m_getjcl(M_NOWAIT, MT_DATA, M_PKTHDR, IWX_RBUF_SIZE);
		if (m == NULL) {
			printf("%s: could not get fw cmd mbuf (%i bytes)\n",
			    DEVNAME(sc), IWX_RBUF_SIZE);
			err = ENOMEM;
			goto out;
		}
		m->m_len = m->m_pkthdr.len = m->m_ext.ext_size;
		err = bus_dmamap_load_mbuf_sg(ring->data_dmat, txdata->map, m,
		    seg, &nsegs, BUS_DMA_NOWAIT);
		if (nsegs > 20)
			panic("nsegs > 20");
		DPRINTF(("%s: nsegs=%i\n", __func__, nsegs));
		if (err) {
			printf("%s: could not load fw cmd mbuf (%zd bytes)\n",
			    DEVNAME(sc), totlen);
			m_freem(m);
			goto out;
		}
		txdata->m = m; /* mbuf will be freed in iwx_cmd_done() */
		cmd = mtod(m, struct iwx_device_cmd *);
		paddr = seg[0].ds_addr;
	} else {
		cmd = &ring->cmd[idx];
		paddr = txdata->cmd_paddr;
	}

	memset(cmd, 0, sizeof(*cmd));
	cmd->hdr_wide.opcode = iwx_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 = iwx_cmd_version(code);
	data = cmd->data_wide;

	for (i = 0, off = 0; i < nitems(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, ("off %d != paylen %d", off, paylen));

	desc->tbs[0].tb_len = htole16(MIN(hdrlen + paylen, IWX_FIRST_TB_SIZE));
	addr = htole64(paddr);
	memcpy(&desc->tbs[0].addr, &addr, sizeof(addr));
	if (hdrlen + paylen > IWX_FIRST_TB_SIZE) {
		DPRINTF(("%s: hdrlen=%zu paylen=%d\n", __func__, hdrlen,
		    paylen));
		desc->tbs[1].tb_len = htole16(hdrlen + paylen -
		    IWX_FIRST_TB_SIZE);
		addr = htole64(paddr + IWX_FIRST_TB_SIZE);
		memcpy(&desc->tbs[1].addr, &addr, sizeof(addr));
		desc->num_tbs = htole16(2);
	} else
		desc->num_tbs = htole16(1);

	if (paylen > datasz) {
		bus_dmamap_sync(ring->data_dmat, txdata->map,
		    BUS_DMASYNC_PREWRITE);
	} else {
		bus_dmamap_sync(ring->cmd_dma.tag, ring->cmd_dma.map,
		    BUS_DMASYNC_PREWRITE);
	}
	bus_dmamap_sync(ring->desc_dma.tag, ring->desc_dma.map,
	    BUS_DMASYNC_PREWRITE);

	/* Kick command ring. */
	ring->queued++;
	ring->cur = (ring->cur + 1) % IWX_TX_RING_COUNT;
	ring->cur_hw = (ring->cur_hw + 1) % sc->max_tfd_queue_size;
	DPRINTF(("%s: ring->cur_hw=%i\n", __func__, ring->cur_hw));
	IWX_WRITE(sc, IWX_HBUS_TARG_WRPTR, ring->qid << 16 | ring->cur_hw);

	if (!async) {
		err = msleep(desc, &sc->sc_mtx, PCATCH, "iwxcmd", hz);
		if (err == 0) {
			/* if hardware is no longer up, return error */
			if (generation != sc->sc_generation) {
				err = ENXIO;
				goto out;
			}

			/* Response buffer will be freed in iwx_free_resp(). */
			hcmd->resp_pkt = (void *)sc->sc_cmd_resp_pkt[idx];
			sc->sc_cmd_resp_pkt[idx] = NULL;
		} else if (generation == sc->sc_generation) {
			free(sc->sc_cmd_resp_pkt[idx], M_DEVBUF);
			sc->sc_cmd_resp_pkt[idx] = NULL;
		}
	}
out:
	return err;
}

static int
iwx_send_cmd_pdu(struct iwx_softc *sc, uint32_t id, uint32_t flags,
    uint16_t len, const void *data)
{
	struct iwx_host_cmd cmd = {
		.id = id,
		.len = { len, },
		.data = { data, },
		.flags = flags,
	};

	return iwx_send_cmd(sc, &cmd);
}

static int
iwx_send_cmd_status(struct iwx_softc *sc, struct iwx_host_cmd *cmd,
    uint32_t *status)
{
	struct iwx_rx_packet *pkt;
	struct iwx_cmd_response *resp;
	int err, resp_len;

	KASSERT(((cmd->flags & IWX_CMD_WANT_RESP) == 0), ("IWX_CMD_WANT_RESP"));
	cmd->flags |= IWX_CMD_WANT_RESP;
	cmd->resp_pkt_len = sizeof(*pkt) + sizeof(*resp);

	err = iwx_send_cmd(sc, cmd);
	if (err)
		return err;

	pkt = cmd->resp_pkt;
	if (pkt == NULL || (pkt->hdr.flags & IWX_CMD_FAILED_MSK))
		return EIO;

	resp_len = iwx_rx_packet_payload_len(pkt);
	if (resp_len != sizeof(*resp)) {
		iwx_free_resp(sc, cmd);
		return EIO;
	}

	resp = (void *)pkt->data;
	*status = le32toh(resp->status);
	iwx_free_resp(sc, cmd);
	return err;
}

static int
iwx_send_cmd_pdu_status(struct iwx_softc *sc, uint32_t id, uint16_t len,
    const void *data, uint32_t *status)
{
	struct iwx_host_cmd cmd = {
		.id = id,
		.len = { len, },
		.data = { data, },
	};

	return iwx_send_cmd_status(sc, &cmd, status);
}

static void
iwx_free_resp(struct iwx_softc *sc, struct iwx_host_cmd *hcmd)
{
	KASSERT((hcmd->flags & (IWX_CMD_WANT_RESP)) == IWX_CMD_WANT_RESP,
	    ("hcmd flags !IWX_CMD_WANT_RESP"));
	free(hcmd->resp_pkt, M_DEVBUF);
	hcmd->resp_pkt = NULL;
}

static void
iwx_cmd_done(struct iwx_softc *sc, int qid, int idx, int code)
{
	struct iwx_tx_ring *ring = &sc->txq[IWX_DQA_CMD_QUEUE];
	struct iwx_tx_data *data;

	if (qid != IWX_DQA_CMD_QUEUE) {
		return;	/* Not a command ack. */
	}

	data = &ring->data[idx];

	if (data->m != NULL) {
		bus_dmamap_sync(ring->data_dmat, data->map,
		    BUS_DMASYNC_POSTWRITE);
		bus_dmamap_unload(ring->data_dmat, data->map);
		m_freem(data->m);
		data->m = NULL;
	}
	wakeup(&ring->desc[idx]);

	DPRINTF(("%s: command 0x%x done\n", __func__, code));
	if (ring->queued == 0) {
		DPRINTF(("%s: unexpected firmware response to command 0x%x\n",
			DEVNAME(sc), code));
	} else if (ring->queued > 0)
		ring->queued--;
}

static uint32_t
iwx_fw_rateidx_ofdm(uint8_t rval)
{
	/* Firmware expects indices which match our 11a rate set. */
	const struct ieee80211_rateset *rs = &ieee80211_std_rateset_11a;
	int i;

	for (i = 0; i < rs->rs_nrates; i++) {
		if ((rs->rs_rates[i] & IEEE80211_RATE_VAL) == rval)
			return i;
	}

	return 0;
}

static uint32_t
iwx_fw_rateidx_cck(uint8_t rval)
{
	/* Firmware expects indices which match our 11b rate set. */
	const struct ieee80211_rateset *rs = &ieee80211_std_rateset_11b;
	int i;

	for (i = 0; i < rs->rs_nrates; i++) {
		if ((rs->rs_rates[i] & IEEE80211_RATE_VAL) == rval)
			return i;
	}

	return 0;
}

static int
iwx_min_basic_rate(struct ieee80211com *ic)
{
	struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
	struct ieee80211_node *ni = vap->iv_bss;
	struct ieee80211_rateset *rs = &ni->ni_rates;
	struct ieee80211_channel *c = ni->ni_chan;
	int i, min, rval;

	min = -1;

	if (c == IEEE80211_CHAN_ANYC) {
		printf("%s: channel is IEEE80211_CHAN_ANYC\n", __func__);
		return -1;
	}

	for (i = 0; i < rs->rs_nrates; i++) {
		if ((rs->rs_rates[i] & IEEE80211_RATE_BASIC) == 0)
			continue;
		rval = (rs->rs_rates[i] & IEEE80211_RATE_VAL);
		if (min == -1)
			min = rval;
		else if (rval < min)
			min = rval;
	}

	/* Default to 1 Mbit/s on 2GHz and 6 Mbit/s on 5GHz. */
	if (min == -1)
		min = IEEE80211_IS_CHAN_2GHZ(c) ? 2 : 12;

	return min;
}

/*
 * Determine the Tx command flags and Tx rate+flags to use.
 * Return the selected Tx rate.
 */
static const struct iwx_rate *
iwx_tx_fill_cmd(struct iwx_softc *sc, struct iwx_node *in,
    struct ieee80211_frame *wh, uint16_t *flags, uint32_t *rate_n_flags,
    struct mbuf *m)
{
	struct ieee80211com *ic = &sc->sc_ic;
	struct ieee80211_node *ni = &in->in_ni;
	struct ieee80211_rateset *rs = &ni->ni_rates;
	const struct iwx_rate *rinfo = NULL;
	int type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK;
	int ridx = iwx_min_basic_rate(ic);
	int min_ridx, rate_flags;
	uint8_t rval;

	/* We're in the process of clearing the node, no channel already */
	if (ridx == -1)
		return NULL;

	min_ridx = iwx_rval2ridx(ridx);

	*flags = 0;

	if (IEEE80211_IS_MULTICAST(wh->i_addr1) ||
	    type != IEEE80211_FC0_TYPE_DATA) {
		/* for non-data, use the lowest supported rate */
		ridx = min_ridx;
		*flags |= IWX_TX_FLAGS_CMD_RATE;
	} else if (ni->ni_flags & IEEE80211_NODE_HT) {
		ridx = iwx_mcs2ridx[ieee80211_node_get_txrate_dot11rate(ni)
		    & ~IEEE80211_RATE_MCS];
	} else {
		rval = (rs->rs_rates[ieee80211_node_get_txrate_dot11rate(ni)]
		    & IEEE80211_RATE_VAL);
		ridx = iwx_rval2ridx(rval);
		if (ridx < min_ridx)
			ridx = min_ridx;
	}

	if (m->m_flags & M_EAPOL)
		*flags |= IWX_TX_FLAGS_HIGH_PRI;

	rinfo = &iwx_rates[ridx];

	/*
	 * Do not fill rate_n_flags if firmware controls the Tx rate.
	 * For data frames we rely on Tx rate scaling in firmware by default.
	 */
	if ((*flags & IWX_TX_FLAGS_CMD_RATE) == 0) {
		*rate_n_flags = 0;
		return rinfo;
	}

	/*
	 * Forcing a CCK/OFDM legacy rate is important for management frames.
	 * Association will only succeed if we do this correctly.
	 */

	IWX_DPRINTF(sc, IWX_DEBUG_TXRATE,"%s%d:: min_ridx=%i\n", __func__, __LINE__, min_ridx);
	IWX_DPRINTF(sc, IWX_DEBUG_TXRATE, "%s:%d: ridx=%i\n", __func__, __LINE__, ridx);
	rate_flags = IWX_RATE_MCS_ANT_A_MSK;
	if (IWX_RIDX_IS_CCK(ridx)) {
		if (sc->sc_rate_n_flags_version >= 2)
			rate_flags |= IWX_RATE_MCS_CCK_MSK;
		else
			rate_flags |= IWX_RATE_MCS_CCK_MSK_V1;
	} else if (sc->sc_rate_n_flags_version >= 2)
		rate_flags |= IWX_RATE_MCS_LEGACY_OFDM_MSK;

	rval = (rs->rs_rates[ieee80211_node_get_txrate_dot11rate(ni)]
	    & IEEE80211_RATE_VAL);
	IWX_DPRINTF(sc, IWX_DEBUG_TXRATE, "%s:%d: rval=%i dot11 %d\n", __func__, __LINE__,
	    rval, rs->rs_rates[ieee80211_node_get_txrate_dot11rate(ni)]);

	if (sc->sc_rate_n_flags_version >= 2) {
		if (rate_flags & IWX_RATE_MCS_LEGACY_OFDM_MSK) {
			rate_flags |= (iwx_fw_rateidx_ofdm(rval) &
			    IWX_RATE_LEGACY_RATE_MSK);
		} else {
			rate_flags |= (iwx_fw_rateidx_cck(rval) &
			    IWX_RATE_LEGACY_RATE_MSK);
		}
	} else
		rate_flags |= rinfo->plcp;

	*rate_n_flags = rate_flags;
	IWX_DPRINTF(sc, IWX_DEBUG_TXRATE, "%s:%d flags=0x%x\n",
	    __func__, __LINE__,*flags);
	IWX_DPRINTF(sc, IWX_DEBUG_TXRATE, "%s:%d rate_n_flags=0x%x\n",
	    __func__, __LINE__, *rate_n_flags);

	if (sc->sc_debug & IWX_DEBUG_TXRATE)
		print_ratenflags(__func__, __LINE__,
		    *rate_n_flags, sc->sc_rate_n_flags_version);

	return rinfo;
}

static void
iwx_tx_update_byte_tbl(struct iwx_softc *sc, struct iwx_tx_ring *txq,
    int idx, uint16_t byte_cnt, uint16_t num_tbs)
{
	uint8_t filled_tfd_size, num_fetch_chunks;
	uint16_t len = byte_cnt;
	uint16_t bc_ent;

	filled_tfd_size = offsetof(struct iwx_tfh_tfd, tbs) +
			  num_tbs * sizeof(struct iwx_tfh_tb);
	/*
	 * filled_tfd_size contains the number of filled bytes in the TFD.
	 * Dividing it by 64 will give the number of chunks to fetch
	 * to SRAM- 0 for one chunk, 1 for 2 and so on.
	 * If, for example, TFD contains only 3 TBs then 32 bytes
	 * of the TFD are used, and only one chunk of 64 bytes should
	 * be fetched
	 */
	num_fetch_chunks = howmany(filled_tfd_size, 64) - 1;

	if (sc->sc_device_family >= IWX_DEVICE_FAMILY_AX210) {
		struct iwx_gen3_bc_tbl_entry *scd_bc_tbl = txq->bc_tbl.vaddr;
		/* Starting from AX210, the HW expects bytes */
		bc_ent = htole16(len | (num_fetch_chunks << 14));
		scd_bc_tbl[idx].tfd_offset = bc_ent;
	} else {
		struct iwx_agn_scd_bc_tbl *scd_bc_tbl = txq->bc_tbl.vaddr;
		/* Before AX210, the HW expects DW */
		len = howmany(len, 4);
		bc_ent = htole16(len | (num_fetch_chunks << 12));
		scd_bc_tbl->tfd_offset[idx] = bc_ent;
	}

	bus_dmamap_sync(sc->sc_dmat, txq->bc_tbl.map, BUS_DMASYNC_PREWRITE);
}

static int
iwx_tx(struct iwx_softc *sc, struct mbuf *m, struct ieee80211_node *ni)
{
	struct ieee80211com *ic = &sc->sc_ic;
	struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
	struct iwx_node *in = (void *)ni;
	struct iwx_tx_ring *ring;
	struct iwx_tx_data *data;
	struct iwx_tfh_tfd *desc;
	struct iwx_device_cmd *cmd;
	struct ieee80211_frame *wh;
	struct ieee80211_key *k = NULL;
	const struct iwx_rate *rinfo;
	uint64_t paddr;
	u_int hdrlen;
	uint32_t rate_n_flags;
	uint16_t num_tbs, flags, offload_assist = 0;
	uint8_t type, subtype;
	int i, totlen, err, pad, qid;
#define IWM_MAX_SCATTER 20
	bus_dma_segment_t *seg, segs[IWM_MAX_SCATTER];
	int nsegs;
	struct mbuf *m1;
	size_t txcmd_size;

	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);

	qid = sc->first_data_qid;

	/* Put QoS frames on the data queue which maps to their TID. */
	if (IEEE80211_QOS_HAS_SEQ(wh) && (sc->sc_flags & IWX_FLAG_AMPDUTX)) {
		uint16_t qos = ieee80211_gettid(wh);
		uint8_t tid = qos & IEEE80211_QOS_TID;
#if 0
		/*
		 * XXX-THJ: TODO when we enable ba we need to manage the
		 * mappings
		 */
		struct ieee80211_tx_ba *ba;
		ba = &ni->ni_tx_ba[tid];

		if (!IEEE80211_IS_MULTICAST(wh->i_addr1) &&
		    type == IEEE80211_FC0_TYPE_DATA &&
		    subtype != IEEE80211_FC0_SUBTYPE_NODATA &&
		    subtype != IEEE80211_FC0_SUBTYPE_BAR &&
		    sc->aggqid[tid] != 0  /*&&
		    ba->ba_state == IEEE80211_BA_AGREED*/) {
			qid = sc->aggqid[tid];
#else
		if (!IEEE80211_IS_MULTICAST(wh->i_addr1) &&
		    type == IEEE80211_FC0_TYPE_DATA &&
		    subtype != IEEE80211_FC0_SUBTYPE_NODATA &&
		    sc->aggqid[tid] != 0) {
			qid = sc->aggqid[tid];
#endif
		}
	}

	ring = &sc->txq[qid];
	desc = &ring->desc[ring->cur];
	memset(desc, 0, sizeof(*desc));
	data = &ring->data[ring->cur];

	cmd = &ring->cmd[ring->cur];
	cmd->hdr.code = IWX_TX_CMD;
	cmd->hdr.flags = 0;
	cmd->hdr.qid = ring->qid;
	cmd->hdr.idx = ring->cur;

	rinfo = iwx_tx_fill_cmd(sc, in, wh, &flags, &rate_n_flags, m);
	if (rinfo == NULL)
		return EINVAL;

	/* Offloaded sequence number assignment */
	/* Note: Should be done in firmware on all supported devices */

	/* Radiotap */
	if (ieee80211_radiotap_active_vap(vap)) {
		struct iwx_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);
		tap->wt_rate = rinfo->rate;
		if (k != NULL)
			tap->wt_flags |= IEEE80211_RADIOTAP_F_WEP;
		ieee80211_radiotap_tx(vap, m);
	}

	/* Encrypt - CCMP via direct HW path, TKIP/WEP indirected openbsd-style for now */
	if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) {
		k = ieee80211_crypto_get_txkey(ni, m);
		if (k == NULL) {
			printf("%s: k is NULL!\n", __func__);
			m_freem(m);
			return (ENOBUFS);
		} else if (k->wk_cipher->ic_cipher == IEEE80211_CIPHER_AES_CCM) {
			k->wk_keytsc++;
		} else {
			k->wk_cipher->ic_encap(k, m);

			/* 802.11 headers may have moved */
			wh = mtod(m, struct ieee80211_frame *);
			flags |= IWX_TX_FLAGS_ENCRYPT_DIS;
		}
	} else
		flags |= IWX_TX_FLAGS_ENCRYPT_DIS;

	totlen = m->m_pkthdr.len;

	if (hdrlen & 3) {
		/* First segment length must be a multiple of 4. */
		pad = 4 - (hdrlen & 3);
		offload_assist |= IWX_TX_CMD_OFFLD_PAD;
	} else
		pad = 0;

	if (sc->sc_device_family >= IWX_DEVICE_FAMILY_AX210) {
		struct iwx_tx_cmd_gen3 *tx = (void *)cmd->data;
		memset(tx, 0, sizeof(*tx));
		tx->len = htole16(totlen);
		tx->offload_assist = htole32(offload_assist);
		tx->flags = htole16(flags);
		tx->rate_n_flags = htole32(rate_n_flags);
		memcpy(tx->hdr, wh, hdrlen);
		txcmd_size = sizeof(*tx);
	} else {
		struct iwx_tx_cmd_gen2 *tx = (void *)cmd->data;
		memset(tx, 0, sizeof(*tx));
		tx->len = htole16(totlen);
		tx->offload_assist = htole16(offload_assist);
		tx->flags = htole32(flags);
		tx->rate_n_flags = htole32(rate_n_flags);
		memcpy(tx->hdr, wh, hdrlen);
		txcmd_size = sizeof(*tx);
	}

	/* Trim 802.11 header. */
	m_adj(m, hdrlen);

	err = bus_dmamap_load_mbuf_sg(ring->data_dmat, data->map, m, segs,
	    &nsegs, BUS_DMA_NOWAIT);
	if (err && err != EFBIG) {
		printf("%s: can't map mbuf (error %d)\n", DEVNAME(sc), err);
		m_freem(m);
		return err;
	}
	if (err) {
		/* Too many DMA segments, linearize mbuf. */
		m1 = m_collapse(m, M_NOWAIT, IWM_MAX_SCATTER - 2);
		if (m1 == NULL) {
			printf("%s: could not defrag mbufs\n", __func__);
			m_freem(m);
			return (ENOBUFS);
		}
		m = m1;
		err = bus_dmamap_load_mbuf_sg(ring->data_dmat, data->map, m,
		    segs, &nsegs, BUS_DMA_NOWAIT);
		if (err) {
			printf("%s: can't map mbuf (error %d)\n", __func__,
			    err);
			m_freem(m);
			return (err);
		}
	}
	data->m = m;
	data->in = in;

	/* Fill TX descriptor. */
	num_tbs = 2 + nsegs;
	desc->num_tbs = htole16(num_tbs);

	desc->tbs[0].tb_len = htole16(IWX_FIRST_TB_SIZE);
	paddr = htole64(data->cmd_paddr);
	memcpy(&desc->tbs[0].addr, &paddr, sizeof(paddr));
#if __SIZEOF_SIZE_T__ > 4
	if (data->cmd_paddr >> 32 != (data->cmd_paddr +
	    le32toh(desc->tbs[0].tb_len)) >> 32)
		DPRINTF(("%s: TB0 crosses 32bit boundary\n", __func__));
#endif
	desc->tbs[1].tb_len = htole16(sizeof(struct iwx_cmd_header) +
	    txcmd_size + hdrlen + pad - IWX_FIRST_TB_SIZE);
	paddr = htole64(data->cmd_paddr + IWX_FIRST_TB_SIZE);
	memcpy(&desc->tbs[1].addr, &paddr, sizeof(paddr));

#if __SIZEOF_SIZE_T__ > 4
	if (data->cmd_paddr >> 32 != (data->cmd_paddr +
	    le32toh(desc->tbs[1].tb_len)) >> 32)
		DPRINTF(("%s: TB1 crosses 32bit boundary\n", __func__));
#endif

	/* Other DMA segments are for data payload. */
	for (i = 0; i < nsegs; i++) {
		seg = &segs[i];
		desc->tbs[i + 2].tb_len = htole16(seg->ds_len);
		paddr = htole64(seg->ds_addr);
		memcpy(&desc->tbs[i + 2].addr, &paddr, sizeof(paddr));
#if __SIZEOF_SIZE_T__ > 4
		if (data->cmd_paddr >> 32 != (data->cmd_paddr +
		    le32toh(desc->tbs[i + 2].tb_len)) >> 32)
			DPRINTF(("%s: TB%d crosses 32bit boundary\n", __func__,
			    i + 2));
#endif
	}

	bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_PREWRITE);
	bus_dmamap_sync(ring->cmd_dma.tag, ring->cmd_dma.map,
	    BUS_DMASYNC_PREWRITE);
	bus_dmamap_sync(ring->desc_dma.tag, ring->desc_dma.map,
	    BUS_DMASYNC_PREWRITE);

	iwx_tx_update_byte_tbl(sc, ring, ring->cur, totlen, num_tbs);

	/* Kick TX ring. */
	ring->cur = (ring->cur + 1) % IWX_TX_RING_COUNT;
	ring->cur_hw = (ring->cur_hw + 1) % sc->max_tfd_queue_size;
	IWX_WRITE(sc, IWX_HBUS_TARG_WRPTR, ring->qid << 16 | ring->cur_hw);

	/* Mark TX ring as full if we reach a certain threshold. */
	if (++ring->queued > iwx_himark) {
		sc->qfullmsk |= 1 << ring->qid;
	}

	sc->sc_tx_timer[ring->qid] = 15;

	return 0;
}

static int
iwx_flush_sta_tids(struct iwx_softc *sc, int sta_id, uint16_t tids)
{
	struct iwx_rx_packet *pkt;
	struct iwx_tx_path_flush_cmd_rsp *resp;
	struct iwx_tx_path_flush_cmd flush_cmd = {
		.sta_id = htole32(sta_id),
		.tid_mask = htole16(tids),
	};
	struct iwx_host_cmd hcmd = {
		.id = IWX_TXPATH_FLUSH,
		.len = { sizeof(flush_cmd), },
		.data = { &flush_cmd, },
		.flags = IWX_CMD_WANT_RESP,
		.resp_pkt_len = sizeof(*pkt) + sizeof(*resp),
	};
	int err, resp_len, i, num_flushed_queues;

	err = iwx_send_cmd(sc, &hcmd);
	if (err)
		return err;

	pkt = hcmd.resp_pkt;
	if (!pkt || (pkt->hdr.flags & IWX_CMD_FAILED_MSK)) {
		err = EIO;
		goto out;
	}

	resp_len = iwx_rx_packet_payload_len(pkt);
	/* Some firmware versions don't provide a response. */
	if (resp_len == 0)
		goto out;
	else if (resp_len != sizeof(*resp)) {
		err = EIO;
		goto out;
	}

	resp = (void *)pkt->data;

	if (le16toh(resp->sta_id) != sta_id) {
		err = EIO;
		goto out;
	}

	num_flushed_queues = le16toh(resp->num_flushed_queues);
	if (num_flushed_queues > IWX_TX_FLUSH_QUEUE_RSP) {
		err = EIO;
		goto out;
	}

	for (i = 0; i < num_flushed_queues; i++) {
		struct iwx_flush_queue_info *queue_info = &resp->queues[i];
		uint16_t tid = le16toh(queue_info->tid);
		uint16_t read_after = le16toh(queue_info->read_after_flush);
		uint16_t qid = le16toh(queue_info->queue_num);
		struct iwx_tx_ring *txq;

		if (qid >= nitems(sc->txq))
			continue;

		txq = &sc->txq[qid];
		if (tid != txq->tid)
			continue;

		iwx_txq_advance(sc, txq, read_after);
	}
out:
	iwx_free_resp(sc, &hcmd);
	return err;
}

#define IWX_FLUSH_WAIT_MS	2000

static int
iwx_drain_sta(struct iwx_softc *sc, struct iwx_node* in, int drain)
{
	struct iwx_add_sta_cmd cmd;
	int err;
	uint32_t status;

	memset(&cmd, 0, sizeof(cmd));
	cmd.mac_id_n_color = htole32(IWX_FW_CMD_ID_AND_COLOR(in->in_id,
	    in->in_color));
	cmd.sta_id = IWX_STATION_ID;
	cmd.add_modify = IWX_STA_MODE_MODIFY;
	cmd.station_flags = drain ? htole32(IWX_STA_FLG_DRAIN_FLOW) : 0;
	cmd.station_flags_msk = htole32(IWX_STA_FLG_DRAIN_FLOW);

	status = IWX_ADD_STA_SUCCESS;
	err = iwx_send_cmd_pdu_status(sc, IWX_ADD_STA,
	    sizeof(cmd), &cmd, &status);
	if (err) {
		printf("%s: could not update sta (error %d)\n",
		    DEVNAME(sc), err);
		return err;
	}

	switch (status & IWX_ADD_STA_STATUS_MASK) {
	case IWX_ADD_STA_SUCCESS:
		break;
	default:
		err = EIO;
		printf("%s: Couldn't %s draining for station\n",
		    DEVNAME(sc), drain ? "enable" : "disable");
		break;
	}

	return err;
}

static int
iwx_flush_sta(struct iwx_softc *sc, struct iwx_node *in)
{
	int err;

	IWX_ASSERT_LOCKED(sc);

	sc->sc_flags |= IWX_FLAG_TXFLUSH;

	err = iwx_drain_sta(sc, in, 1);
	if (err)
		goto done;

	err = iwx_flush_sta_tids(sc, IWX_STATION_ID, 0xffff);
	if (err) {
		printf("%s: could not flush Tx path (error %d)\n",
		    DEVNAME(sc), err);
		goto done;
	}

	/*
	 * XXX-THJ: iwx_wait_tx_queues_empty was here, but it was a nope in the
	 * fc drive rand has has been replaced in OpenBSD.
	 */

	err = iwx_drain_sta(sc, in, 0);
done:
	sc->sc_flags &= ~IWX_FLAG_TXFLUSH;
	return err;
}

#define IWX_POWER_KEEP_ALIVE_PERIOD_SEC    25

static int
iwx_beacon_filter_send_cmd(struct iwx_softc *sc,
    struct iwx_beacon_filter_cmd *cmd)
{
	return iwx_send_cmd_pdu(sc, IWX_REPLY_BEACON_FILTERING_CMD,
	    0, sizeof(struct iwx_beacon_filter_cmd), cmd);
}

static int
iwx_update_beacon_abort(struct iwx_softc *sc, struct iwx_node *in, int enable)
{
	struct iwx_beacon_filter_cmd cmd = {
		IWX_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;
	return iwx_beacon_filter_send_cmd(sc, &cmd);
}

static void
iwx_power_build_cmd(struct iwx_softc *sc, struct iwx_node *in,
    struct iwx_mac_power_cmd *cmd)
{
	struct ieee80211com *ic = &sc->sc_ic;
	struct ieee80211_node *ni = &in->in_ni;
	struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
	int dtim_period, dtim_msec, keep_alive;

	cmd->id_and_color = htole32(IWX_FW_CMD_ID_AND_COLOR(in->in_id,
	    in->in_color));
	if (vap->iv_dtim_period)
		dtim_period = vap->iv_dtim_period;
	else
		dtim_period = 1;

	/*
	 * Regardless of power management state the driver must set
	 * keep alive period. FW will use it for sending keep alive NDPs
	 * immediately after association. Check that keep alive period
	 * is at least 3 * DTIM.
	 */
	dtim_msec = dtim_period * ni->ni_intval;
	keep_alive = MAX(3 * dtim_msec, 1000 * IWX_POWER_KEEP_ALIVE_PERIOD_SEC);
	keep_alive = roundup(keep_alive, 1000) / 1000;
	cmd->keep_alive_seconds = htole16(keep_alive);

	if (ic->ic_opmode != IEEE80211_M_MONITOR)
		cmd->flags = htole16(IWX_POWER_FLAGS_POWER_SAVE_ENA_MSK);
}

static int
iwx_power_mac_update_mode(struct iwx_softc *sc, struct iwx_node *in)
{
	int err;
	int ba_enable;
	struct iwx_mac_power_cmd cmd;

	memset(&cmd, 0, sizeof(cmd));

	iwx_power_build_cmd(sc, in, &cmd);

	err = iwx_send_cmd_pdu(sc, IWX_MAC_PM_POWER_TABLE, 0,
	    sizeof(cmd), &cmd);
	if (err != 0)
		return err;

	ba_enable = !!(cmd.flags &
	    htole16(IWX_POWER_FLAGS_POWER_MANAGEMENT_ENA_MSK));
	return iwx_update_beacon_abort(sc, in, ba_enable);
}

static int
iwx_power_update_device(struct iwx_softc *sc)
{
	struct iwx_device_power_cmd cmd = { };
	struct ieee80211com *ic = &sc->sc_ic;

	if (ic->ic_opmode != IEEE80211_M_MONITOR)
		cmd.flags = htole16(IWX_DEVICE_POWER_FLAGS_POWER_SAVE_ENA_MSK);

	return iwx_send_cmd_pdu(sc,
	    IWX_POWER_TABLE_CMD, 0, sizeof(cmd), &cmd);
}
#if 0
static int
iwx_enable_beacon_filter(struct iwx_softc *sc, struct iwx_node *in)
{
	struct iwx_beacon_filter_cmd cmd = {
		IWX_BF_CMD_CONFIG_DEFAULTS,
		.bf_enable_beacon_filter = htole32(1),
		.ba_enable_beacon_abort = htole32(sc->sc_bf.ba_enabled),
	};
	int err;

	err = iwx_beacon_filter_send_cmd(sc, &cmd);
	if (err == 0)
		sc->sc_bf.bf_enabled = 1;

	return err;
}
#endif
static int
iwx_disable_beacon_filter(struct iwx_softc *sc)
{
	struct iwx_beacon_filter_cmd cmd;
	int err;

	memset(&cmd, 0, sizeof(cmd));

	err = iwx_beacon_filter_send_cmd(sc, &cmd);
	if (err == 0)
		sc->sc_bf.bf_enabled = 0;

	return err;
}

static int
iwx_add_sta_cmd(struct iwx_softc *sc, struct iwx_node *in, int update)
{
	struct iwx_add_sta_cmd add_sta_cmd;
	int err, i;
	uint32_t status, aggsize;
	const uint32_t max_aggsize = (IWX_STA_FLG_MAX_AGG_SIZE_64K >>
		    IWX_STA_FLG_MAX_AGG_SIZE_SHIFT);
	struct ieee80211com *ic = &sc->sc_ic;
	struct ieee80211_node *ni = &in->in_ni;
	struct ieee80211_htrateset *htrs = &ni->ni_htrates;

	if (!update && (sc->sc_flags & IWX_FLAG_STA_ACTIVE))
		panic("STA already added");

	memset(&add_sta_cmd, 0, sizeof(add_sta_cmd));

	if (ic->ic_opmode == IEEE80211_M_MONITOR) {
		add_sta_cmd.sta_id = IWX_MONITOR_STA_ID;
		add_sta_cmd.station_type = IWX_STA_GENERAL_PURPOSE;
	} else {
		add_sta_cmd.sta_id = IWX_STATION_ID;
		add_sta_cmd.station_type = IWX_STA_LINK;
	}
	add_sta_cmd.mac_id_n_color
	    = htole32(IWX_FW_CMD_ID_AND_COLOR(in->in_id, in->in_color));
	if (!update) {
		if (ic->ic_opmode == IEEE80211_M_MONITOR)
			IEEE80211_ADDR_COPY(&add_sta_cmd.addr,
			    etheranyaddr);
		else
			IEEE80211_ADDR_COPY(&add_sta_cmd.addr,
			    in->in_macaddr);
	}
	DPRINTF(("%s: add_sta_cmd.addr=%s\n", __func__,
	    ether_sprintf(add_sta_cmd.addr)));
	add_sta_cmd.add_modify = update ? 1 : 0;
	add_sta_cmd.station_flags_msk
	    |= htole32(IWX_STA_FLG_FAT_EN_MSK | IWX_STA_FLG_MIMO_EN_MSK);

	if (in->in_ni.ni_flags & IEEE80211_NODE_HT) {
		add_sta_cmd.station_flags_msk
		    |= htole32(IWX_STA_FLG_MAX_AGG_SIZE_MSK |
		    IWX_STA_FLG_AGG_MPDU_DENS_MSK);

		if (iwx_mimo_enabled(sc)) {
			if (ni->ni_flags & IEEE80211_NODE_VHT) {
				add_sta_cmd.station_flags |=
				    htole32(IWX_STA_FLG_MIMO_EN_MIMO2);
			} else {
				int hasmimo = 0;
				for (i = 0; i < htrs->rs_nrates; i++) {
					if (htrs->rs_rates[i] > 7) {
						hasmimo = 1;
						break;
					}
				}
				if (hasmimo) {
					add_sta_cmd.station_flags |=
					    htole32(IWX_STA_FLG_MIMO_EN_MIMO2);
				}
			}
		}

		if (ni->ni_flags & IEEE80211_NODE_HT &&
		    IEEE80211_IS_CHAN_HT40(ni->ni_chan)) {
			add_sta_cmd.station_flags |= htole32(
			    IWX_STA_FLG_FAT_EN_40MHZ);
		}


		if (ni->ni_flags & IEEE80211_NODE_VHT) {
			if (IEEE80211_IS_CHAN_VHT80(ni->ni_chan)) {
				add_sta_cmd.station_flags |= htole32(
				    IWX_STA_FLG_FAT_EN_80MHZ);
			}
			// XXX-misha: TODO get real ampdu size
			aggsize = max_aggsize;
		} else {
			aggsize = _IEEE80211_MASKSHIFT(le16toh(ni->ni_htparam),
			    IEEE80211_HTCAP_MAXRXAMPDU);
		}

		if (aggsize > max_aggsize)
			aggsize = max_aggsize;
		add_sta_cmd.station_flags |= htole32((aggsize <<
		    IWX_STA_FLG_MAX_AGG_SIZE_SHIFT) &
		    IWX_STA_FLG_MAX_AGG_SIZE_MSK);

		switch (_IEEE80211_MASKSHIFT(le16toh(ni->ni_htparam),
		    IEEE80211_HTCAP_MPDUDENSITY)) {
		case IEEE80211_HTCAP_MPDUDENSITY_2:
			add_sta_cmd.station_flags
			    |= htole32(IWX_STA_FLG_AGG_MPDU_DENS_2US);
			break;
		case IEEE80211_HTCAP_MPDUDENSITY_4:
			add_sta_cmd.station_flags
			    |= htole32(IWX_STA_FLG_AGG_MPDU_DENS_4US);
			break;
		case IEEE80211_HTCAP_MPDUDENSITY_8:
			add_sta_cmd.station_flags
			    |= htole32(IWX_STA_FLG_AGG_MPDU_DENS_8US);
			break;
		case IEEE80211_HTCAP_MPDUDENSITY_16:
			add_sta_cmd.station_flags
			    |= htole32(IWX_STA_FLG_AGG_MPDU_DENS_16US);
			break;
		default:
			break;
		}
	}

	status = IWX_ADD_STA_SUCCESS;
	err = iwx_send_cmd_pdu_status(sc, IWX_ADD_STA, sizeof(add_sta_cmd),
	    &add_sta_cmd, &status);
	if (!err && (status & IWX_ADD_STA_STATUS_MASK) != IWX_ADD_STA_SUCCESS)
		err = EIO;

	return err;
}

static int
iwx_rm_sta_cmd(struct iwx_softc *sc, struct iwx_node *in)
{
	struct ieee80211com *ic = &sc->sc_ic;
	struct iwx_rm_sta_cmd rm_sta_cmd;
	int err;

	if ((sc->sc_flags & IWX_FLAG_STA_ACTIVE) == 0)
		panic("sta already removed");

	memset(&rm_sta_cmd, 0, sizeof(rm_sta_cmd));
	if (ic->ic_opmode == IEEE80211_M_MONITOR)
		rm_sta_cmd.sta_id = IWX_MONITOR_STA_ID;
	else
		rm_sta_cmd.sta_id = IWX_STATION_ID;

	err = iwx_send_cmd_pdu(sc, IWX_REMOVE_STA, 0, sizeof(rm_sta_cmd),
	    &rm_sta_cmd);

	return err;
}

static int
iwx_rm_sta(struct iwx_softc *sc, struct iwx_node *in)
{
	int err, i, cmd_ver;

	err = iwx_flush_sta(sc, in);
	if (err) {
		printf("%s: could not flush Tx path (error %d)\n",
		    DEVNAME(sc), err);
		return err;
	}

	/*
	 * New SCD_QUEUE_CONFIG API requires explicit queue removal
	 * before a station gets removed.
	 */
	cmd_ver = iwx_lookup_cmd_ver(sc, IWX_DATA_PATH_GROUP,
	    IWX_SCD_QUEUE_CONFIG_CMD);
	if (cmd_ver != 0 && cmd_ver != IWX_FW_CMD_VER_UNKNOWN) {
		err = iwx_disable_mgmt_queue(sc);
		if (err)
			return err;
		for (i = IWX_FIRST_AGG_TX_QUEUE;
		    i < IWX_LAST_AGG_TX_QUEUE; i++) {
			struct iwx_tx_ring *ring = &sc->txq[i];
			if ((sc->qenablemsk & (1 << i)) == 0)
				continue;
			err = iwx_disable_txq(sc, IWX_STATION_ID,
			    ring->qid, ring->tid);
			if (err) {
				printf("%s: could not disable Tx queue %d "
				    "(error %d)\n", DEVNAME(sc), ring->qid,
				    err);
				return err;
			}
		}
	}

	err = iwx_rm_sta_cmd(sc, in);
	if (err) {
		printf("%s: could not remove STA (error %d)\n",
		    DEVNAME(sc), err);
		return err;
	}

	in->in_flags = 0;

	sc->sc_rx_ba_sessions = 0;
	sc->ba_rx.start_tidmask = 0;
	sc->ba_rx.stop_tidmask = 0;
	memset(sc->aggqid, 0, sizeof(sc->aggqid));
	sc->ba_tx.start_tidmask = 0;
	sc->ba_tx.stop_tidmask = 0;
	for (i = IWX_FIRST_AGG_TX_QUEUE; i < IWX_LAST_AGG_TX_QUEUE; i++)
		sc->qenablemsk &= ~(1 << i);

#if 0
	for (i = 0; i < IEEE80211_NUM_TID; i++) {
		struct ieee80211_tx_ba *ba = &ni->ni_tx_ba[i];
		if (ba->ba_state != IEEE80211_BA_AGREED)
			continue;
		ieee80211_delba_request(ic, ni, 0, 1, i);
	}
#endif
	/* Clear ampdu rx state (GOS-1525) */
	for (i = 0; i < IWX_MAX_TID_COUNT; i++) {
		struct iwx_rx_ba *ba = &sc->ni_rx_ba[i];
		ba->ba_flags = 0;
	}

	return 0;
}

static uint8_t
iwx_umac_scan_fill_channels(struct iwx_softc *sc,
    struct iwx_scan_channel_cfg_umac *chan, size_t chan_nitems,
    int n_ssids, uint32_t channel_cfg_flags)
{
	struct ieee80211com *ic = &sc->sc_ic;
	struct ieee80211_scan_state *ss = ic->ic_scan;
	struct ieee80211_channel *c;
	uint8_t nchan;
	int j;

	for (nchan = j = 0;
	    j < ss->ss_last &&
	    nchan < sc->sc_capa_n_scan_channels;
	    j++) {
		uint8_t channel_num;

		c = ss->ss_chans[j];
		channel_num = ieee80211_mhz2ieee(c->ic_freq, 0);
		if (isset(sc->sc_ucode_api,
		    IWX_UCODE_TLV_API_SCAN_EXT_CHAN_VER)) {
			chan->v2.channel_num = channel_num;
			if (IEEE80211_IS_CHAN_2GHZ(c))
				chan->v2.band = IWX_PHY_BAND_24;
			else
				chan->v2.band = IWX_PHY_BAND_5;
			chan->v2.iter_count = 1;
			chan->v2.iter_interval = 0;
		} else {
			chan->v1.channel_num = channel_num;
			chan->v1.iter_count = 1;
			chan->v1.iter_interval = htole16(0);
		}
		chan->flags |= htole32(channel_cfg_flags);
		chan++;
		nchan++;
	}

	return nchan;
}

static int
iwx_fill_probe_req(struct iwx_softc *sc, struct iwx_scan_probe_req *preq)
{
	struct ieee80211com *ic = &sc->sc_ic;
	struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
	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));

	if (remain < sizeof(*wh) + 2)
		return ENOBUFS;

	/*
	 * Build a probe request frame.  Most of the following code is a
	 * copy & paste of what is done in net80211.
	 */
	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, vap ? vap->iv_myaddr : ic->ic_macaddr);
	IEEE80211_ADDR_COPY(wh->i_addr3, etherbroadcastaddr);
	*(uint16_t *)&wh->i_dur[0] = 0;	/* filled by HW */
	*(uint16_t *)&wh->i_seq[0] = 0;	/* filled by HW */

	frm = (uint8_t *)(wh + 1);
	*frm++ = IEEE80211_ELEMID_SSID;
	*frm++ = 0;
	/* hardware inserts SSID */

	/* Tell the firmware where the MAC header is. */
	preq->mac_header.offset = 0;
	preq->mac_header.len = htole16(frm - (uint8_t *)wh);
	remain -= frm - (uint8_t *)wh;

	/* Fill in 2GHz IEs and tell firmware where they are. */
	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);
	remain -= frm - pos;

	if (isset(sc->sc_enabled_capa,
	    IWX_UCODE_TLV_CAPA_DS_PARAM_SET_IE_SUPPORT)) {
		if (remain < 3)
			return ENOBUFS;
		*frm++ = IEEE80211_ELEMID_DSPARMS;
		*frm++ = 1;
		*frm++ = 0;
		remain -= 3;
	}
	preq->band_data[0].len = htole16(frm - pos);

	if (sc->sc_nvm.sku_cap_band_52GHz_enable) {
		/* Fill in 5GHz IEs. */
		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;
		if (vap->iv_vht_flags & IEEE80211_FVHT_VHT) {
			if (remain < 14)
				return ENOBUFS;
			frm = ieee80211_add_vhtcap(frm, vap->iv_bss);
			remain -= frm - pos;
			preq->band_data[1].len = htole16(frm - pos);
		}
	}

	/* Send 11n IEs on both 2GHz and 5GHz bands. */
	preq->common_data.offset = htole16(frm - (uint8_t *)wh);
	pos = frm;
	if (vap->iv_flags_ht & IEEE80211_FHT_HT) {
		if (remain < 28)
			return ENOBUFS;
		frm = ieee80211_add_htcap(frm, vap->iv_bss);
		/* XXX add WME info? */
		remain -= frm - pos;
	}

	preq->common_data.len = htole16(frm - pos);

	return 0;
}

static int
iwx_config_umac_scan_reduced(struct iwx_softc *sc)
{
	struct iwx_scan_config scan_cfg;
	struct iwx_host_cmd hcmd = {
		.id = iwx_cmd_id(IWX_SCAN_CFG_CMD, IWX_LONG_GROUP, 0),
		.len[0] = sizeof(scan_cfg),
		.data[0] = &scan_cfg,
		.flags = 0,
	};
	int cmdver;

	if (!isset(sc->sc_ucode_api, IWX_UCODE_TLV_API_REDUCED_SCAN_CONFIG)) {
		printf("%s: firmware does not support reduced scan config\n",
		    DEVNAME(sc));
		return ENOTSUP;
	}

	memset(&scan_cfg, 0, sizeof(scan_cfg));

	/*
	 * SCAN_CFG version >= 5 implies that the broadcast
	 * STA ID field is deprecated.
	 */
	cmdver = iwx_lookup_cmd_ver(sc, IWX_LONG_GROUP, IWX_SCAN_CFG_CMD);
	if (cmdver == IWX_FW_CMD_VER_UNKNOWN || cmdver < 5)
		scan_cfg.bcast_sta_id = 0xff;

	scan_cfg.tx_chains = htole32(iwx_fw_valid_tx_ant(sc));
	scan_cfg.rx_chains = htole32(iwx_fw_valid_rx_ant(sc));

	return iwx_send_cmd(sc, &hcmd);
}

static uint16_t
iwx_scan_umac_flags_v2(struct iwx_softc *sc, int bgscan)
{
	struct ieee80211com *ic = &sc->sc_ic;
	struct ieee80211_scan_state *ss = ic->ic_scan;
	uint16_t flags = 0;

	if (ss->ss_nssid == 0) {
		DPRINTF(("%s: Passive scan started\n", __func__));
		flags |= IWX_UMAC_SCAN_GEN_FLAGS_V2_FORCE_PASSIVE;
	}

	flags |= IWX_UMAC_SCAN_GEN_FLAGS_V2_PASS_ALL;
	flags |= IWX_UMAC_SCAN_GEN_FLAGS_V2_NTFY_ITER_COMPLETE;
	flags |= IWX_UMAC_SCAN_GEN_FLAGS_V2_ADAPTIVE_DWELL;

	return flags;
}

#define IWX_SCAN_DWELL_ACTIVE		10
#define IWX_SCAN_DWELL_PASSIVE		110

/* adaptive dwell max budget time [TU] for full scan */
#define IWX_SCAN_ADWELL_MAX_BUDGET_FULL_SCAN 300
/* adaptive dwell max budget time [TU] for directed scan */
#define IWX_SCAN_ADWELL_MAX_BUDGET_DIRECTED_SCAN 100
/* adaptive dwell default high band APs number */
#define IWX_SCAN_ADWELL_DEFAULT_HB_N_APS 8
/* adaptive dwell default low band APs number */
#define IWX_SCAN_ADWELL_DEFAULT_LB_N_APS 2
/* adaptive dwell default APs number in social channels (1, 6, 11) */
#define IWX_SCAN_ADWELL_DEFAULT_N_APS_SOCIAL 10
/* adaptive dwell number of APs override for p2p friendly GO channels */
#define IWX_SCAN_ADWELL_N_APS_GO_FRIENDLY 10
/* adaptive dwell number of APs override for social channels */
#define IWX_SCAN_ADWELL_N_APS_SOCIAL_CHS 2

static void
iwx_scan_umac_dwell_v10(struct iwx_softc *sc,
    struct iwx_scan_general_params_v10 *general_params, int bgscan)
{
	uint32_t suspend_time, max_out_time;
	uint8_t active_dwell, passive_dwell;

	active_dwell = IWX_SCAN_DWELL_ACTIVE;
	passive_dwell = IWX_SCAN_DWELL_PASSIVE;

	general_params->adwell_default_social_chn =
		IWX_SCAN_ADWELL_DEFAULT_N_APS_SOCIAL;
	general_params->adwell_default_2g = IWX_SCAN_ADWELL_DEFAULT_LB_N_APS;
	general_params->adwell_default_5g = IWX_SCAN_ADWELL_DEFAULT_HB_N_APS;

	if (bgscan)
		general_params->adwell_max_budget =
			htole16(IWX_SCAN_ADWELL_MAX_BUDGET_DIRECTED_SCAN);
	else
		general_params->adwell_max_budget =
			htole16(IWX_SCAN_ADWELL_MAX_BUDGET_FULL_SCAN);

	general_params->scan_priority = htole32(IWX_SCAN_PRIORITY_EXT_6);
	if (bgscan) {
		max_out_time = htole32(120);
		suspend_time = htole32(120);
	} else {
		max_out_time = htole32(0);
		suspend_time = htole32(0);
	}
	general_params->max_out_of_time[IWX_SCAN_LB_LMAC_IDX] =
		htole32(max_out_time);
	general_params->suspend_time[IWX_SCAN_LB_LMAC_IDX] =
		htole32(suspend_time);
	general_params->max_out_of_time[IWX_SCAN_HB_LMAC_IDX] =
		htole32(max_out_time);
	general_params->suspend_time[IWX_SCAN_HB_LMAC_IDX] =
		htole32(suspend_time);

	general_params->active_dwell[IWX_SCAN_LB_LMAC_IDX] = active_dwell;
	general_params->passive_dwell[IWX_SCAN_LB_LMAC_IDX] = passive_dwell;
	general_params->active_dwell[IWX_SCAN_HB_LMAC_IDX] = active_dwell;
	general_params->passive_dwell[IWX_SCAN_HB_LMAC_IDX] = passive_dwell;
}

static void
iwx_scan_umac_fill_general_p_v10(struct iwx_softc *sc,
    struct iwx_scan_general_params_v10 *gp, uint16_t gen_flags, int bgscan)
{
	iwx_scan_umac_dwell_v10(sc, gp, bgscan);

	gp->flags = htole16(gen_flags);

	if (gen_flags & IWX_UMAC_SCAN_GEN_FLAGS_V2_FRAGMENTED_LMAC1)
		gp->num_of_fragments[IWX_SCAN_LB_LMAC_IDX] = 3;
	if (gen_flags & IWX_UMAC_SCAN_GEN_FLAGS_V2_FRAGMENTED_LMAC2)
		gp->num_of_fragments[IWX_SCAN_HB_LMAC_IDX] = 3;

	gp->scan_start_mac_id = 0;
}

static void
iwx_scan_umac_fill_ch_p_v6(struct iwx_softc *sc,
    struct iwx_scan_channel_params_v6 *cp, uint32_t channel_cfg_flags,
    int n_ssid)
{
	cp->flags = IWX_SCAN_CHANNEL_FLAG_ENABLE_CHAN_ORDER;

	cp->count = iwx_umac_scan_fill_channels(sc, cp->channel_config,
	    nitems(cp->channel_config), n_ssid, channel_cfg_flags);

	cp->n_aps_override[0] = IWX_SCAN_ADWELL_N_APS_GO_FRIENDLY;
	cp->n_aps_override[1] = IWX_SCAN_ADWELL_N_APS_SOCIAL_CHS;
}

static int
iwx_umac_scan_v14(struct iwx_softc *sc, int bgscan)
{
	struct ieee80211com *ic = &sc->sc_ic;
	struct ieee80211_scan_state *ss = ic->ic_scan;
	struct iwx_host_cmd hcmd = {
		.id = iwx_cmd_id(IWX_SCAN_REQ_UMAC, IWX_LONG_GROUP, 0),
		.len = { 0, },
		.data = { NULL, },
		.flags = 0,
	};
	struct iwx_scan_req_umac_v14 *cmd = &sc->sc_umac_v14_cmd;
	struct iwx_scan_req_params_v14 *scan_p;
	int err, async = bgscan, n_ssid = 0;
	uint16_t gen_flags;
	uint32_t bitmap_ssid = 0;

	IWX_ASSERT_LOCKED(sc);

	bzero(cmd, sizeof(struct iwx_scan_req_umac_v14));

	scan_p = &cmd->scan_params;

	cmd->ooc_priority = htole32(IWX_SCAN_PRIORITY_EXT_6);
	cmd->uid = htole32(0);

	gen_flags = iwx_scan_umac_flags_v2(sc, bgscan);
	iwx_scan_umac_fill_general_p_v10(sc, &scan_p->general_params,
	    gen_flags, bgscan);

	scan_p->periodic_params.schedule[0].interval = htole16(0);
	scan_p->periodic_params.schedule[0].iter_count = 1;

	err = iwx_fill_probe_req(sc, &scan_p->probe_params.preq);
	if (err) {
		printf("%s: iwx_fill_probe_req failed (error %d)\n", __func__,
		    err);
		return err;
	}

	for (int i=0; i < ss->ss_nssid; i++) {
		scan_p->probe_params.direct_scan[i].id = IEEE80211_ELEMID_SSID;
		scan_p->probe_params.direct_scan[i].len =
		    MIN(ss->ss_ssid[i].len, IEEE80211_NWID_LEN);
		DPRINTF(("%s: Active scan started for ssid ", __func__));
		memcpy(scan_p->probe_params.direct_scan[i].ssid,
		    ss->ss_ssid[i].ssid, ss->ss_ssid[i].len);
		n_ssid++;
		bitmap_ssid |= (1 << i);
	}
	DPRINTF(("%s: bitmap_ssid=0x%x\n", __func__, bitmap_ssid));

	iwx_scan_umac_fill_ch_p_v6(sc, &scan_p->channel_params, bitmap_ssid,
	    n_ssid);

	hcmd.len[0] = sizeof(*cmd);
	hcmd.data[0] = (void *)cmd;
	hcmd.flags |= async ? IWX_CMD_ASYNC : 0;

	err = iwx_send_cmd(sc, &hcmd);
	return err;
}

static void
iwx_mcc_update(struct iwx_softc *sc, struct iwx_mcc_chub_notif *notif)
{
	char alpha2[3];

	snprintf(alpha2, sizeof(alpha2), "%c%c",
	    (le16toh(notif->mcc) & 0xff00) >> 8, le16toh(notif->mcc) & 0xff);

	IWX_DPRINTF(sc, IWX_DEBUG_FW, "%s: firmware has detected regulatory domain '%s' "
		    "(0x%x)\n", DEVNAME(sc), alpha2, le16toh(notif->mcc));

	/* TODO: Schedule a task to send MCC_UPDATE_CMD? */
}

uint8_t
iwx_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 == iwx_rates[ridx].rate)
			return rs->rs_rates[i];
	}

	return 0;
}

static int
iwx_rval2ridx(int rval)
{
	int ridx;

	for (ridx = 0; ridx < nitems(iwx_rates); ridx++) {
		if (iwx_rates[ridx].plcp == IWX_RATE_INVM_PLCP)
			continue;
		if (rval == iwx_rates[ridx].rate)
			break;
	}

       return ridx;
}

static void
iwx_ack_rates(struct iwx_softc *sc, struct iwx_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 = IWX_FIRST_CCK_RATE; i < IWX_FIRST_OFDM_RATE; i++) {
			if ((iwx_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 = IWX_FIRST_OFDM_RATE; i <= IWX_LAST_NON_HT_RATE; i++) {
		if ((iwx_ridx2rate(rs, i) & IEEE80211_RATE_BASIC) == 0)
			continue;
		ofdm |= (1 << (i - IWX_FIRST_OFDM_RATE));
		if (lowest_present_ofdm == -1 || lowest_present_ofdm > i)
			lowest_present_ofdm = i;
	}

	/*
	 * Now we've got the basic rates as bitmaps in the ofdm and cck
	 * variables. This isn't sufficient though, as there might not
	 * be all the right rates in the bitmap. E.g. if the only basic
	 * rates are 5.5 Mbps and 11 Mbps, we still need to add 1 Mbps
	 * and 6 Mbps because the 802.11-2007 standard says in 9.6:
	 *
	 *    [...] a STA responding to a received frame shall transmit
	 *    its Control Response frame [...] at the highest rate in the
	 *    BSSBasicRateSet parameter that is less than or equal to the
	 *    rate of the immediately previous frame in the frame exchange
	 *    sequence ([...]) and that is of the same modulation class
	 *    ([...]) as the received frame. If no rate contained in the
	 *    BSSBasicRateSet parameter meets these conditions, then the
	 *    control frame sent in response to a received frame shall be
	 *    transmitted at the highest mandatory rate of the PHY that is
	 *    less than or equal to the rate of the received frame, and
	 *    that is of the same modulation class as the received frame.
	 *
	 * As a consequence, we need to add all mandatory rates that are
	 * lower than all of the basic rates to these bitmaps.
	 */

	if (IWX_RATE_24M_INDEX < lowest_present_ofdm)
		ofdm |= IWX_RATE_BIT_MSK(24) >> IWX_FIRST_OFDM_RATE;
	if (IWX_RATE_12M_INDEX < lowest_present_ofdm)
		ofdm |= IWX_RATE_BIT_MSK(12) >> IWX_FIRST_OFDM_RATE;
	/* 6M already there or needed so always add */
	ofdm |= IWX_RATE_BIT_MSK(6) >> IWX_FIRST_OFDM_RATE;

	/*
	 * CCK is a bit more complex with DSSS vs. HR/DSSS vs. ERP.
	 * Note, however:
	 *  - if no CCK rates are basic, it must be ERP since there must
	 *    be some basic rates at all, so they're OFDM => ERP PHY
	 *    (or we're in 5 GHz, and the cck bitmap will never be used)
	 *  - if 11M is a basic rate, it must be ERP as well, so add 5.5M
	 *  - if 5.5M is basic, 1M and 2M are mandatory
	 *  - if 2M is basic, 1M is mandatory
	 *  - if 1M is basic, that's the only valid ACK rate.
	 * As a consequence, it's not as complicated as it sounds, just add
	 * any lower rates to the ACK rate bitmap.
	 */
	if (IWX_RATE_11M_INDEX < lowest_present_cck)
		cck |= IWX_RATE_BIT_MSK(11) >> IWX_FIRST_CCK_RATE;
	if (IWX_RATE_5M_INDEX < lowest_present_cck)
		cck |= IWX_RATE_BIT_MSK(5) >> IWX_FIRST_CCK_RATE;
	if (IWX_RATE_2M_INDEX < lowest_present_cck)
		cck |= IWX_RATE_BIT_MSK(2) >> IWX_FIRST_CCK_RATE;
	/* 1M already there or needed so always add */
	cck |= IWX_RATE_BIT_MSK(1) >> IWX_FIRST_CCK_RATE;

	*cck_rates = cck;
	*ofdm_rates = ofdm;
}

static void
iwx_mac_ctxt_cmd_common(struct iwx_softc *sc, struct iwx_node *in,
    struct iwx_mac_ctx_cmd *cmd, uint32_t action)
{
#define IWX_EXP2(x)	((1 << (x)) - 1)	/* CWmin = 2^ECWmin - 1 */
	struct ieee80211com *ic = &sc->sc_ic;
	struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
	struct ieee80211_node *ni = vap->iv_bss;
	int cck_ack_rates, ofdm_ack_rates;

	cmd->id_and_color = htole32(IWX_FW_CMD_ID_AND_COLOR(in->in_id,
	    in->in_color));
	cmd->action = htole32(action);

	if (action == IWX_FW_CTXT_ACTION_REMOVE)
		return;

	if (ic->ic_opmode == IEEE80211_M_MONITOR)
		cmd->mac_type = htole32(IWX_FW_MAC_TYPE_LISTENER);
	else if (ic->ic_opmode == IEEE80211_M_STA)
		cmd->mac_type = htole32(IWX_FW_MAC_TYPE_BSS_STA);
	else
		panic("unsupported operating mode %d", ic->ic_opmode);
	cmd->tsf_id = htole32(IWX_TSF_ID_A);

	IEEE80211_ADDR_COPY(cmd->node_addr, vap->iv_myaddr);
	DPRINTF(("%s: cmd->node_addr=%s\n", __func__,
	    ether_sprintf(cmd->node_addr)));
	if (ic->ic_opmode == IEEE80211_M_MONITOR) {
		IEEE80211_ADDR_COPY(cmd->bssid_addr, etherbroadcastaddr);
		return;
	}

	IEEE80211_ADDR_COPY(cmd->bssid_addr, in->in_macaddr);
	DPRINTF(("%s: cmd->bssid_addr=%s\n", __func__,
	    ether_sprintf(cmd->bssid_addr)));
	iwx_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)
	      ? IWX_MAC_FLG_SHORT_PREAMBLE : 0);
	cmd->short_slot
	    = htole32((ic->ic_flags & IEEE80211_F_SHSLOT)
	      ? IWX_MAC_FLG_SHORT_SLOT : 0);

	struct chanAccParams chp;
	ieee80211_wme_vap_getparams(vap, &chp);

	for (int i = 0; i < WME_NUM_AC; i++) {
		int txf = iwx_ac_to_tx_fifo[i];
		cmd->ac[txf].cw_min = IWX_EXP2(chp.cap_wmeParams[i].wmep_logcwmin);
		cmd->ac[txf].cw_max = IWX_EXP2(chp.cap_wmeParams[i].wmep_logcwmax);
		cmd->ac[txf].aifsn = chp.cap_wmeParams[i].wmep_aifsn;
		cmd->ac[txf].fifos_mask = (1 << txf);
		cmd->ac[txf].edca_txop = chp.cap_wmeParams[i].wmep_txopLimit;

		cmd->ac[txf].edca_txop = htole16(chp.cap_wmeParams[i].wmep_txopLimit * 32);
	}

	if (ni->ni_flags & IEEE80211_NODE_QOS) {
		DPRINTF(("%s: === IEEE80211_NODE_QOS\n", __func__));
		cmd->qos_flags |= htole32(IWX_MAC_QOS_FLG_UPDATE_EDCA);
	}

	if (ni->ni_flags & IEEE80211_NODE_HT) {
		switch (vap->iv_curhtprotmode) {
		case IEEE80211_HTINFO_OPMODE_PURE:
			break;
		case IEEE80211_HTINFO_OPMODE_PROTOPT:
		case IEEE80211_HTINFO_OPMODE_MIXED:
			cmd->protection_flags |=
			    htole32(IWX_MAC_PROT_FLG_HT_PROT |
			    IWX_MAC_PROT_FLG_FAT_PROT);
			break;
		case IEEE80211_HTINFO_OPMODE_HT20PR:
			if (in->in_phyctxt &&
			    (in->in_phyctxt->sco == IEEE80211_HTINFO_2NDCHAN_ABOVE ||
			    in->in_phyctxt->sco == IEEE80211_HTINFO_2NDCHAN_BELOW)) {
				cmd->protection_flags |=
				    htole32(IWX_MAC_PROT_FLG_HT_PROT |
				    IWX_MAC_PROT_FLG_FAT_PROT);
			}
			break;
		default:
			break;
		}
		cmd->qos_flags |= htole32(IWX_MAC_QOS_FLG_TGN);
		DPRINTF(("%s: === IWX_MAC_QOS_FLG_TGN\n", __func__));
	}

	if (ic->ic_flags & IEEE80211_F_USEPROT)
		cmd->protection_flags |= htole32(IWX_MAC_PROT_FLG_TGG_PROTECT);
	cmd->filter_flags = htole32(IWX_MAC_FILTER_ACCEPT_GRP);
#undef IWX_EXP2
}

static void
iwx_mac_ctxt_cmd_fill_sta(struct iwx_softc *sc, struct iwx_node *in,
    struct iwx_mac_data_sta *sta, int assoc)
{
	struct ieee80211_node *ni = &in->in_ni;
	struct ieee80211com *ic = &sc->sc_ic;
	struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
	uint32_t dtim_off;
	uint64_t tsf;
	int dtim_period;

	dtim_off = ni->ni_dtim_count * ni->ni_intval * IEEE80211_DUR_TU;
	tsf = le64toh(ni->ni_tstamp.tsf);
	dtim_period = vap->iv_dtim_period;

	sta->is_assoc = htole32(assoc);

	if (assoc) {
		sta->dtim_time = htole32(tsf + dtim_off);
		sta->dtim_tsf = htole64(tsf + dtim_off);
		// XXX: unset in iwm
		sta->assoc_beacon_arrive_time = 0;
	}
	sta->bi = htole32(ni->ni_intval);
	sta->dtim_interval = htole32(ni->ni_intval * dtim_period);
	sta->data_policy = htole32(0);
	sta->listen_interval = htole32(10);
	sta->assoc_id = htole32(ni->ni_associd);
}

static int
iwx_mac_ctxt_cmd(struct iwx_softc *sc, struct iwx_node *in, uint32_t action,
    int assoc)
{
	struct ieee80211com *ic = &sc->sc_ic;
	struct ieee80211_node *ni = &in->in_ni;
	struct iwx_mac_ctx_cmd cmd;
	int active = (sc->sc_flags & IWX_FLAG_MAC_ACTIVE);

	if (action == IWX_FW_CTXT_ACTION_ADD && active)
		panic("MAC already added");
	if (action == IWX_FW_CTXT_ACTION_REMOVE && !active)
		panic("MAC already removed");

	memset(&cmd, 0, sizeof(cmd));

	iwx_mac_ctxt_cmd_common(sc, in, &cmd, action);

	if (action == IWX_FW_CTXT_ACTION_REMOVE) {
		return iwx_send_cmd_pdu(sc, IWX_MAC_CONTEXT_CMD, 0,
		    sizeof(cmd), &cmd);
	}

	if (ic->ic_opmode == IEEE80211_M_MONITOR) {
		cmd.filter_flags |= htole32(IWX_MAC_FILTER_IN_PROMISC |
		    IWX_MAC_FILTER_IN_CONTROL_AND_MGMT |
		    IWX_MAC_FILTER_ACCEPT_GRP |
		    IWX_MAC_FILTER_IN_BEACON |
		    IWX_MAC_FILTER_IN_PROBE_REQUEST |
		    IWX_MAC_FILTER_IN_CRC32);
	// XXX: dtim period is in vap
	} else if (!assoc || !ni->ni_associd /*|| !ni->ni_dtimperiod*/) {
		/*
		 * Allow beacons to pass through as long as we are not
		 * associated or we do not have dtim period information.
		 */
		cmd.filter_flags |= htole32(IWX_MAC_FILTER_IN_BEACON);
	}
	iwx_mac_ctxt_cmd_fill_sta(sc, in, &cmd.sta, assoc);
	return iwx_send_cmd_pdu(sc, IWX_MAC_CONTEXT_CMD, 0, sizeof(cmd), &cmd);
}

static int
iwx_clear_statistics(struct iwx_softc *sc)
{
	struct iwx_statistics_cmd scmd = {
		.flags = htole32(IWX_STATISTICS_FLG_CLEAR)
	};
	struct iwx_host_cmd cmd = {
		.id = IWX_STATISTICS_CMD,
		.len[0] = sizeof(scmd),
		.data[0] = &scmd,
		.flags = IWX_CMD_WANT_RESP,
		.resp_pkt_len = sizeof(struct iwx_notif_statistics),
	};
	int err;

	err = iwx_send_cmd(sc, &cmd);
	if (err)
		return err;

	iwx_free_resp(sc, &cmd);
	return 0;
}

static int
iwx_scan(struct iwx_softc *sc)
{
	int err;
	err = iwx_umac_scan_v14(sc, 0);

	if (err) {
		printf("%s: could not initiate scan\n", DEVNAME(sc));
		return err;
	}
	return 0;
}

static int
iwx_bgscan(struct ieee80211com *ic)
{
	struct iwx_softc *sc = ic->ic_softc;
	int err;

	err = iwx_umac_scan_v14(sc, 1);
	if (err) {
		printf("%s: could not initiate scan\n", DEVNAME(sc));
		return err;
	}
	return 0;
}

static int
iwx_enable_mgmt_queue(struct iwx_softc *sc)
{
	int err;

	sc->first_data_qid = IWX_DQA_CMD_QUEUE + 1;

	/*
	 * Non-QoS frames use the "MGMT" TID and queue.
	 * Other TIDs and data queues are reserved for QoS data frames.
	 */
	err = iwx_enable_txq(sc, IWX_STATION_ID, sc->first_data_qid,
	    IWX_MGMT_TID, IWX_TX_RING_COUNT);
	if (err) {
		printf("%s: could not enable Tx queue %d (error %d)\n",
		    DEVNAME(sc), sc->first_data_qid, err);
		return err;
	}

	return 0;
}

static int
iwx_disable_mgmt_queue(struct iwx_softc *sc)
{
	int err, cmd_ver;

	/* Explicit removal is only required with old SCD_QUEUE_CFG command. */
	cmd_ver = iwx_lookup_cmd_ver(sc, IWX_DATA_PATH_GROUP,
	    IWX_SCD_QUEUE_CONFIG_CMD);
	if (cmd_ver == 0 || cmd_ver == IWX_FW_CMD_VER_UNKNOWN)
		return 0;

	sc->first_data_qid = IWX_DQA_CMD_QUEUE + 1;

	err = iwx_disable_txq(sc, IWX_STATION_ID, sc->first_data_qid,
	    IWX_MGMT_TID);
	if (err) {
		printf("%s: could not disable Tx queue %d (error %d)\n",
		    DEVNAME(sc), sc->first_data_qid, err);
		return err;
	}

	return 0;
}

static int
iwx_rs_rval2idx(uint8_t rval)
{
	/* Firmware expects indices which match our 11g rate set. */
	const struct ieee80211_rateset *rs = &ieee80211_std_rateset_11g;
	int i;

	for (i = 0; i < rs->rs_nrates; i++) {
		if ((rs->rs_rates[i] & IEEE80211_RATE_VAL) == rval)
			return i;
	}

	return -1;
}

static uint16_t
iwx_rs_ht_rates(struct iwx_softc *sc, struct ieee80211_node *ni, int rsidx)
{
	uint16_t htrates = 0;
	struct ieee80211_htrateset *htrs = &ni->ni_htrates;
	int i;

	if (rsidx == IEEE80211_HT_RATESET_SISO) {
		for (i = 0; i < htrs->rs_nrates; i++) {
			if (htrs->rs_rates[i] <= 7)
				htrates |= (1 << htrs->rs_rates[i]);
		}
	} else if (rsidx == IEEE80211_HT_RATESET_MIMO2) {
		for (i = 0; i < htrs->rs_nrates; i++) {
			if (htrs->rs_rates[i] > 7 && htrs->rs_rates[i] <= 15)
				htrates |= (1 << (htrs->rs_rates[i] - 8));
		}
	} else
		panic(("iwx_rs_ht_rates"));

	IWX_DPRINTF(sc, IWX_DEBUG_TXRATE,
	    "%s:%d rsidx=%i htrates=0x%x\n", __func__, __LINE__, rsidx, htrates);

	return htrates;
}

uint16_t
iwx_rs_vht_rates(struct iwx_softc *sc, struct ieee80211_node *ni, int num_ss)
{
	uint16_t rx_mcs;
	int max_mcs = -1;
#define IEEE80211_VHT_MCS_FOR_SS_MASK(n)        (0x3 << (2*((n)-1)))
#define IEEE80211_VHT_MCS_FOR_SS_SHIFT(n)       (2*((n)-1))
	rx_mcs = (ni->ni_vht_mcsinfo.tx_mcs_map &
	    IEEE80211_VHT_MCS_FOR_SS_MASK(num_ss)) >>
	    IEEE80211_VHT_MCS_FOR_SS_SHIFT(num_ss);

	switch (rx_mcs) {
	case IEEE80211_VHT_MCS_NOT_SUPPORTED:
		break;
	case IEEE80211_VHT_MCS_SUPPORT_0_7:
		max_mcs = 7;
		break;
	case IEEE80211_VHT_MCS_SUPPORT_0_8:
		max_mcs = 8;
		break;
	case IEEE80211_VHT_MCS_SUPPORT_0_9:
		/* Disable VHT MCS 9 for 20MHz-only stations. */
		if ((ni->ni_htcap & IEEE80211_HTCAP_CHWIDTH40) == 0)
			max_mcs = 8;
		else
			max_mcs = 9;
		break;
	default:
		/* Should not happen; Values above cover the possible range. */
		panic("invalid VHT Rx MCS value %u", rx_mcs);
	}

	return ((1 << (max_mcs + 1)) - 1);
}

static int
iwx_rs_init_v3(struct iwx_softc *sc, struct iwx_node *in)
{
#if 1
	panic("iwx: Trying to init rate set on untested version");
#else
	struct ieee80211_node *ni = &in->in_ni;
	struct ieee80211_rateset *rs = &ni->ni_rates;
	struct iwx_tlc_config_cmd_v3 cfg_cmd;
	uint32_t cmd_id;
	int i;
	size_t cmd_size = sizeof(cfg_cmd);

	memset(&cfg_cmd, 0, sizeof(cfg_cmd));

	for (i = 0; i < rs->rs_nrates; i++) {
		uint8_t rval = rs->rs_rates[i] & IEEE80211_RATE_VAL;
		int idx = iwx_rs_rval2idx(rval);
		if (idx == -1)
			return EINVAL;
		cfg_cmd.non_ht_rates |= (1 << idx);
	}

	if (ni->ni_flags & IEEE80211_NODE_VHT) {
		cfg_cmd.mode = IWX_TLC_MNG_MODE_VHT;
		cfg_cmd.ht_rates[IWX_TLC_NSS_1][IWX_TLC_MCS_PER_BW_80] =
		    htole16(iwx_rs_vht_rates(sc, ni, 1));
		cfg_cmd.ht_rates[IWX_TLC_NSS_2][IWX_TLC_MCS_PER_BW_80] =
		    htole16(iwx_rs_vht_rates(sc, ni, 2));
	} else if (ni->ni_flags & IEEE80211_NODE_HT) {
		cfg_cmd.mode = IWX_TLC_MNG_MODE_HT;
		cfg_cmd.ht_rates[IWX_TLC_NSS_1][IWX_TLC_MCS_PER_BW_80] =
		    htole16(iwx_rs_ht_rates(sc, ni,
		    IEEE80211_HT_RATESET_SISO));
		cfg_cmd.ht_rates[IWX_TLC_NSS_2][IWX_TLC_MCS_PER_BW_80] =
		    htole16(iwx_rs_ht_rates(sc, ni,
		    IEEE80211_HT_RATESET_MIMO2));
	} else
		cfg_cmd.mode = IWX_TLC_MNG_MODE_NON_HT;

	cfg_cmd.sta_id = IWX_STATION_ID;
	if (in->in_phyctxt->vht_chan_width == IEEE80211_VHTOP0_CHAN_WIDTH_80)
		cfg_cmd.max_ch_width = IWX_TLC_MNG_CH_WIDTH_80MHZ;
	else if (in->in_phyctxt->sco == IEEE80211_HTOP0_SCO_SCA ||
	    in->in_phyctxt->sco == IEEE80211_HTOP0_SCO_SCB)
		cfg_cmd.max_ch_width = IWX_TLC_MNG_CH_WIDTH_40MHZ;
	else
		cfg_cmd.max_ch_width = IWX_TLC_MNG_CH_WIDTH_20MHZ;
	cfg_cmd.chains = IWX_TLC_MNG_CHAIN_A_MSK | IWX_TLC_MNG_CHAIN_B_MSK;
	if (ni->ni_flags & IEEE80211_NODE_VHT)
		cfg_cmd.max_mpdu_len = htole16(3895);
	else
		cfg_cmd.max_mpdu_len = htole16(3839);
	if (ni->ni_flags & IEEE80211_NODE_HT) {
		if (ieee80211_node_supports_ht_sgi20(ni)) {
			cfg_cmd.sgi_ch_width_supp |= (1 <<
			    IWX_TLC_MNG_CH_WIDTH_20MHZ);
		}
		if (ieee80211_node_supports_ht_sgi40(ni)) {
			cfg_cmd.sgi_ch_width_supp |= (1 <<
			    IWX_TLC_MNG_CH_WIDTH_40MHZ);
		}
	}
	if ((ni->ni_flags & IEEE80211_NODE_VHT) &&
	    ieee80211_node_supports_vht_sgi80(ni))
		cfg_cmd.sgi_ch_width_supp |= (1 << IWX_TLC_MNG_CH_WIDTH_80MHZ);

	cmd_id = iwx_cmd_id(IWX_TLC_MNG_CONFIG_CMD, IWX_DATA_PATH_GROUP, 0);
	return iwx_send_cmd_pdu(sc, cmd_id, IWX_CMD_ASYNC, cmd_size, &cfg_cmd);
#endif
}

static int
iwx_rs_init_v4(struct iwx_softc *sc, struct iwx_node *in)
{
	struct ieee80211_node *ni = &in->in_ni;
	struct ieee80211_rateset *rs = &ni->ni_rates;
	struct ieee80211_htrateset *htrs = &ni->ni_htrates;
	struct iwx_tlc_config_cmd_v4 cfg_cmd;
	uint32_t cmd_id;
	int i;
	int sgi80 = 0;
	size_t cmd_size = sizeof(cfg_cmd);

	memset(&cfg_cmd, 0, sizeof(cfg_cmd));

	for (i = 0; i < rs->rs_nrates; i++) {
		uint8_t rval = rs->rs_rates[i] & IEEE80211_RATE_VAL;
		int idx = iwx_rs_rval2idx(rval);
		if (idx == -1)
			return EINVAL;
		cfg_cmd.non_ht_rates |= (1 << idx);
	}
	for (i = 0; i < htrs->rs_nrates; i++) {
		DPRINTF(("%s: htrate=%i\n", __func__, htrs->rs_rates[i]));
	}

	if (ni->ni_flags & IEEE80211_NODE_VHT) {
		cfg_cmd.mode = IWX_TLC_MNG_MODE_VHT;
		cfg_cmd.ht_rates[IWX_TLC_NSS_1][IWX_TLC_MCS_PER_BW_80] =
		    htole16(iwx_rs_vht_rates(sc, ni, 1));
		cfg_cmd.ht_rates[IWX_TLC_NSS_2][IWX_TLC_MCS_PER_BW_80] =
		    htole16(iwx_rs_vht_rates(sc, ni, 2));

		IWX_DPRINTF(sc, IWX_DEBUG_TXRATE, "%s:%d SISO=0x%x\n",
		    __func__, __LINE__,
		    cfg_cmd.ht_rates[IWX_TLC_NSS_1][IWX_TLC_MCS_PER_BW_80]);
		IWX_DPRINTF(sc, IWX_DEBUG_TXRATE, "%s:%d MIMO2=0x%x\n",
		    __func__, __LINE__,
		    cfg_cmd.ht_rates[IWX_TLC_NSS_2][IWX_TLC_MCS_PER_BW_80]);
	} else if (ni->ni_flags & IEEE80211_NODE_HT) {
		cfg_cmd.mode = IWX_TLC_MNG_MODE_HT;
		cfg_cmd.ht_rates[IWX_TLC_NSS_1][IWX_TLC_MCS_PER_BW_80] =
		    htole16(iwx_rs_ht_rates(sc, ni,
		    IEEE80211_HT_RATESET_SISO));
		cfg_cmd.ht_rates[IWX_TLC_NSS_2][IWX_TLC_MCS_PER_BW_80] =
		    htole16(iwx_rs_ht_rates(sc, ni,
		    IEEE80211_HT_RATESET_MIMO2));

		IWX_DPRINTF(sc, IWX_DEBUG_TXRATE, "%s:%d SISO=0x%x\n",
		    __func__, __LINE__,
		    cfg_cmd.ht_rates[IWX_TLC_NSS_1][IWX_TLC_MCS_PER_BW_80]);
		IWX_DPRINTF(sc, IWX_DEBUG_TXRATE, "%s:%d MIMO2=0x%x\n",
		    __func__, __LINE__,
		    cfg_cmd.ht_rates[IWX_TLC_NSS_2][IWX_TLC_MCS_PER_BW_80]);
	} else
		cfg_cmd.mode = IWX_TLC_MNG_MODE_NON_HT;

	cfg_cmd.sta_id = IWX_STATION_ID;
#if 0
	if (in->in_phyctxt->vht_chan_width == IEEE80211_VHTOP0_CHAN_WIDTH_80)
		cfg_cmd.max_ch_width = IWX_TLC_MNG_CH_WIDTH_80MHZ;
	else if (in->in_phyctxt->sco == IEEE80211_HTOP0_SCO_SCA ||
	    in->in_phyctxt->sco == IEEE80211_HTOP0_SCO_SCB)
		cfg_cmd.max_ch_width = IWX_TLC_MNG_CH_WIDTH_40MHZ;
	else
		cfg_cmd.max_ch_width = IWX_TLC_MNG_CH_WIDTH_20MHZ;
#endif
	if (IEEE80211_IS_CHAN_VHT80(in->in_ni.ni_chan)) {
		cfg_cmd.max_ch_width = IWX_TLC_MNG_CH_WIDTH_80MHZ;
	} else if (IEEE80211_IS_CHAN_HT40(in->in_ni.ni_chan)) {
		cfg_cmd.max_ch_width = IWX_TLC_MNG_CH_WIDTH_40MHZ;
	} else {
		cfg_cmd.max_ch_width = IWX_TLC_MNG_CH_WIDTH_20MHZ;
	}

	cfg_cmd.chains = IWX_TLC_MNG_CHAIN_A_MSK | IWX_TLC_MNG_CHAIN_B_MSK;
	if (ni->ni_flags & IEEE80211_NODE_VHT)
		cfg_cmd.max_mpdu_len = htole16(3895);
	else
		cfg_cmd.max_mpdu_len = htole16(3839);
	if (ni->ni_flags & IEEE80211_NODE_HT) {
		if (ni->ni_htcap & IEEE80211_HTCAP_SHORTGI20) {
			cfg_cmd.sgi_ch_width_supp |= (1 <<
			    IWX_TLC_MNG_CH_WIDTH_20MHZ);
		}
		if (ni->ni_htcap & IEEE80211_HTCAP_SHORTGI40) {
			cfg_cmd.sgi_ch_width_supp |= (1 <<
			    IWX_TLC_MNG_CH_WIDTH_40MHZ);
		}
	}
	sgi80 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap,
	    IEEE80211_VHTCAP_SHORT_GI_80);
	if ((ni->ni_flags & IEEE80211_NODE_VHT) && sgi80) {
		cfg_cmd.sgi_ch_width_supp |= (1 << IWX_TLC_MNG_CH_WIDTH_80MHZ);
	}

	cmd_id = iwx_cmd_id(IWX_TLC_MNG_CONFIG_CMD, IWX_DATA_PATH_GROUP, 0);
	return iwx_send_cmd_pdu(sc, cmd_id, IWX_CMD_ASYNC, cmd_size, &cfg_cmd);
}

static int
iwx_rs_init(struct iwx_softc *sc, struct iwx_node *in)
{
	int cmd_ver;

	cmd_ver = iwx_lookup_cmd_ver(sc, IWX_DATA_PATH_GROUP,
	    IWX_TLC_MNG_CONFIG_CMD);
	if (cmd_ver == 4)
		return iwx_rs_init_v4(sc, in);
	else
		return iwx_rs_init_v3(sc, in);
}

static void
iwx_rs_update(struct iwx_softc *sc, struct iwx_tlc_update_notif *notif)
{
	struct ieee80211com *ic = &sc->sc_ic;
	struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
	struct ieee80211_node *ni = (void *)vap->iv_bss;

	struct ieee80211_rateset *rs = &ni->ni_rates;
	uint32_t rate_n_flags;
	uint8_t plcp, rval;
	int i, cmd_ver, rate_n_flags_ver2 = 0;

	if (notif->sta_id != IWX_STATION_ID ||
	    (le32toh(notif->flags) & IWX_TLC_NOTIF_FLAG_RATE) == 0)
		return;

	rate_n_flags = le32toh(notif->rate);

	if (sc->sc_debug & IWX_DEBUG_TXRATE)
		print_ratenflags(__func__, __LINE__,
		    rate_n_flags, sc->sc_rate_n_flags_version);

	cmd_ver = iwx_lookup_notif_ver(sc, IWX_DATA_PATH_GROUP,
	    IWX_TLC_MNG_UPDATE_NOTIF);
	if (cmd_ver != IWX_FW_CMD_VER_UNKNOWN && cmd_ver >= 3)
		rate_n_flags_ver2 = 1;

	if (rate_n_flags_ver2) {
		uint32_t mod_type = (rate_n_flags & IWX_RATE_MCS_MOD_TYPE_MSK);
		if (mod_type == IWX_RATE_MCS_HT_MSK) {

			ieee80211_node_set_txrate_dot11rate(ni,
				IWX_RATE_HT_MCS_INDEX(rate_n_flags) |
				IEEE80211_RATE_MCS);
			IWX_DPRINTF(sc, IWX_DEBUG_TXRATE,
			    "%s:%d new MCS: %d rate_n_flags: %x\n",
			    __func__, __LINE__,
			    ieee80211_node_get_txrate_dot11rate(ni) & ~IEEE80211_RATE_MCS,
			    rate_n_flags);
			return;
		}
	} else {
		if (rate_n_flags & IWX_RATE_MCS_HT_MSK_V1) {
			ieee80211_node_set_txrate_dot11rate(ni,
			    rate_n_flags & (IWX_RATE_HT_MCS_RATE_CODE_MSK_V1 |
			    IWX_RATE_HT_MCS_NSS_MSK_V1));

			IWX_DPRINTF(sc, IWX_DEBUG_TXRATE,
			    "%s:%d new MCS idx: %d rate_n_flags: %x\n",
			    __func__, __LINE__,
			    ieee80211_node_get_txrate_dot11rate(ni), rate_n_flags);
			return;
		}
	}

	if (rate_n_flags_ver2) {
		const struct ieee80211_rateset *rs;
		uint32_t ridx = (rate_n_flags & IWX_RATE_LEGACY_RATE_MSK);
		if (rate_n_flags & IWX_RATE_MCS_LEGACY_OFDM_MSK)
			rs = &ieee80211_std_rateset_11a;
		else
			rs = &ieee80211_std_rateset_11b;
		if (ridx < rs->rs_nrates)
			rval = (rs->rs_rates[ridx] & IEEE80211_RATE_VAL);
		else
			rval = 0;
	} else {
		plcp = (rate_n_flags & IWX_RATE_LEGACY_RATE_MSK_V1);

		rval = 0;
		for (i = IWX_RATE_1M_INDEX; i < nitems(iwx_rates); i++) {
			if (iwx_rates[i].plcp == plcp) {
				rval = iwx_rates[i].rate;
				break;
			}
		}
	}

	if (rval) {
		uint8_t rv;
		for (i = 0; i < rs->rs_nrates; i++) {
			rv = rs->rs_rates[i] & IEEE80211_RATE_VAL;
			if (rv == rval) {
				ieee80211_node_set_txrate_dot11rate(ni, i);
				break;
			}
		}
		IWX_DPRINTF(sc, IWX_DEBUG_TXRATE,
		    "%s:%d new rate %d\n", __func__, __LINE__,
		    ieee80211_node_get_txrate_dot11rate(ni));
	}
}

static int
iwx_phy_send_rlc(struct iwx_softc *sc, struct iwx_phy_ctxt *phyctxt,
    uint8_t chains_static, uint8_t chains_dynamic)
{
	struct iwx_rlc_config_cmd cmd;
	uint32_t cmd_id;
	uint8_t active_cnt, idle_cnt;

	memset(&cmd, 0, sizeof(cmd));

	idle_cnt = chains_static;
	active_cnt = chains_dynamic;

	cmd.phy_id = htole32(phyctxt->id);
	cmd.rlc.rx_chain_info = htole32(iwx_fw_valid_rx_ant(sc) <<
	    IWX_PHY_RX_CHAIN_VALID_POS);
	cmd.rlc.rx_chain_info |= htole32(idle_cnt << IWX_PHY_RX_CHAIN_CNT_POS);
	cmd.rlc.rx_chain_info |= htole32(active_cnt <<
	    IWX_PHY_RX_CHAIN_MIMO_CNT_POS);

	cmd_id = iwx_cmd_id(IWX_RLC_CONFIG_CMD, IWX_DATA_PATH_GROUP, 2);
	return iwx_send_cmd_pdu(sc, cmd_id, 0, sizeof(cmd), &cmd);
}

static int
iwx_phy_ctxt_update(struct iwx_softc *sc, struct iwx_phy_ctxt *phyctxt,
    struct ieee80211_channel *chan, uint8_t chains_static,
    uint8_t chains_dynamic, uint32_t apply_time, uint8_t sco,
    uint8_t vht_chan_width)
{
	uint16_t band_flags = (IEEE80211_CHAN_2GHZ | IEEE80211_CHAN_5GHZ);
	int err;

	if (chan == IEEE80211_CHAN_ANYC) {
		printf("%s: GOS-3833: IEEE80211_CHAN_ANYC triggered\n",
		    DEVNAME(sc));
		    return EIO;
	}

	if (isset(sc->sc_enabled_capa,
	    IWX_UCODE_TLV_CAPA_BINDING_CDB_SUPPORT) &&
	    (phyctxt->channel->ic_flags & band_flags) !=
	    (chan->ic_flags & band_flags)) {
		err = iwx_phy_ctxt_cmd(sc, phyctxt, chains_static,
		    chains_dynamic, IWX_FW_CTXT_ACTION_REMOVE, apply_time, sco,
		    vht_chan_width);
		if (err) {
			printf("%s: could not remove PHY context "
			    "(error %d)\n", DEVNAME(sc), err);
			return err;
		}
		phyctxt->channel = chan;
		err = iwx_phy_ctxt_cmd(sc, phyctxt, chains_static,
		    chains_dynamic, IWX_FW_CTXT_ACTION_ADD, apply_time, sco,
		    vht_chan_width);
		if (err) {
			printf("%s: could not add PHY context "
			    "(error %d)\n", DEVNAME(sc), err);
			return err;
		}
	} else {
		phyctxt->channel = chan;
		err = iwx_phy_ctxt_cmd(sc, phyctxt, chains_static,
		    chains_dynamic, IWX_FW_CTXT_ACTION_MODIFY, apply_time, sco,
		    vht_chan_width);
		if (err) {
			printf("%s: could not update PHY context (error %d)\n",
			    DEVNAME(sc), err);
			return err;
		}
	}

	phyctxt->sco = sco;
	phyctxt->vht_chan_width = vht_chan_width;

	DPRINTF(("%s: phyctxt->channel->ic_ieee=%d\n", __func__,
	    phyctxt->channel->ic_ieee));
	DPRINTF(("%s: phyctxt->sco=%d\n", __func__, phyctxt->sco));
	DPRINTF(("%s: phyctxt->vht_chan_width=%d\n", __func__,
	    phyctxt->vht_chan_width));

	if (iwx_lookup_cmd_ver(sc, IWX_DATA_PATH_GROUP,
	    IWX_RLC_CONFIG_CMD) == 2)
		return iwx_phy_send_rlc(sc, phyctxt,
		    chains_static, chains_dynamic);

	return 0;
}

static int
iwx_auth(struct ieee80211vap *vap, struct iwx_softc *sc)
{
	struct ieee80211com *ic = &sc->sc_ic;
	struct iwx_node *in;
	struct iwx_vap *ivp = IWX_VAP(vap);
	struct ieee80211_node *ni;
	uint32_t duration;
	int generation = sc->sc_generation, err;

	IWX_ASSERT_LOCKED(sc);

	ni = ieee80211_ref_node(vap->iv_bss);
	in = IWX_NODE(ni);

	if (ic->ic_opmode == IEEE80211_M_MONITOR) {
		err = iwx_phy_ctxt_update(sc, &sc->sc_phyctxt[0],
		    ic->ic_bsschan, 1, 1, 0, IEEE80211_HTOP0_SCO_SCN,
		    IEEE80211_VHTOP0_CHAN_WIDTH_HT);
		if (err)
			return err;
	} else {
		err = iwx_phy_ctxt_update(sc, &sc->sc_phyctxt[0],
		    in->in_ni.ni_chan, 1, 1, 0, IEEE80211_HTOP0_SCO_SCN,
		    IEEE80211_VHTOP0_CHAN_WIDTH_HT);
		if (err)
			return err;
	}
	ivp->phy_ctxt = &sc->sc_phyctxt[0];
	IEEE80211_ADDR_COPY(in->in_macaddr, in->in_ni.ni_macaddr);
	DPRINTF(("%s: in-in_macaddr=%s\n", __func__,
	    ether_sprintf(in->in_macaddr)));

	err = iwx_mac_ctxt_cmd(sc, in, IWX_FW_CTXT_ACTION_ADD, 0);
	if (err) {
		printf("%s: could not add MAC context (error %d)\n",
		    DEVNAME(sc), err);
		return err;
 	}
	sc->sc_flags |= IWX_FLAG_MAC_ACTIVE;

	err = iwx_binding_cmd(sc, in, IWX_FW_CTXT_ACTION_ADD);
	if (err) {
		printf("%s: could not add binding (error %d)\n",
		    DEVNAME(sc), err);
		goto rm_mac_ctxt;
	}
	sc->sc_flags |= IWX_FLAG_BINDING_ACTIVE;

	err = iwx_add_sta_cmd(sc, in, 0);
	if (err) {
		printf("%s: could not add sta (error %d)\n",
		    DEVNAME(sc), err);
		goto rm_binding;
	}
	sc->sc_flags |= IWX_FLAG_STA_ACTIVE;

	if (ic->ic_opmode == IEEE80211_M_MONITOR) {
		err = iwx_enable_txq(sc, IWX_MONITOR_STA_ID,
		    IWX_DQA_INJECT_MONITOR_QUEUE, IWX_MGMT_TID,
		    IWX_TX_RING_COUNT);
		if (err)
			goto rm_sta;
		return 0;
	}

	err = iwx_enable_mgmt_queue(sc);
	if (err)
		goto rm_sta;

	err = iwx_clear_statistics(sc);
	if (err)
		goto rm_mgmt_queue;

	/*
	 * Prevent the FW from wandering off channel during association
	 * by "protecting" the session with a time event.
	 */
	if (in->in_ni.ni_intval)
		duration = in->in_ni.ni_intval * 9;
	else
		duration = 900;
	return iwx_schedule_session_protection(sc, in, duration);

rm_mgmt_queue:
	if (generation == sc->sc_generation)
		iwx_disable_mgmt_queue(sc);
rm_sta:
	if (generation == sc->sc_generation) {
		iwx_rm_sta_cmd(sc, in);
		sc->sc_flags &= ~IWX_FLAG_STA_ACTIVE;
	}
rm_binding:
	if (generation == sc->sc_generation) {
		iwx_binding_cmd(sc, in, IWX_FW_CTXT_ACTION_REMOVE);
		sc->sc_flags &= ~IWX_FLAG_BINDING_ACTIVE;
	}
rm_mac_ctxt:
	if (generation == sc->sc_generation) {
		iwx_mac_ctxt_cmd(sc, in, IWX_FW_CTXT_ACTION_REMOVE, 0);
		sc->sc_flags &= ~IWX_FLAG_MAC_ACTIVE;
	}
	return err;
}

static int
iwx_deauth(struct iwx_softc *sc)
{
	struct ieee80211com *ic = &sc->sc_ic;
	struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
	struct iwx_node *in = IWX_NODE(vap->iv_bss);
	int err;

	IWX_ASSERT_LOCKED(sc);

	iwx_unprotect_session(sc, in);

	if (sc->sc_flags & IWX_FLAG_STA_ACTIVE) {
		err = iwx_rm_sta(sc, in);
		if (err)
			return err;
		sc->sc_flags &= ~IWX_FLAG_STA_ACTIVE;
	}

	if (sc->sc_flags & IWX_FLAG_BINDING_ACTIVE) {
		err = iwx_binding_cmd(sc, in, IWX_FW_CTXT_ACTION_REMOVE);
		if (err) {
			printf("%s: could not remove binding (error %d)\n",
			    DEVNAME(sc), err);
			return err;
		}
		sc->sc_flags &= ~IWX_FLAG_BINDING_ACTIVE;
	}

	DPRINTF(("%s:  IWX_FLAG_MAC_ACTIVE=%d\n", __func__, sc->sc_flags &
	    IWX_FLAG_MAC_ACTIVE));
	if (sc->sc_flags & IWX_FLAG_MAC_ACTIVE) {
		err = iwx_mac_ctxt_cmd(sc, in, IWX_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 &= ~IWX_FLAG_MAC_ACTIVE;
	}

	/* Move unused PHY context to a default channel. */
	//TODO uncommented in obsd, but stays on the way of auth->auth
	err = iwx_phy_ctxt_update(sc, &sc->sc_phyctxt[0],
	    &ic->ic_channels[1], 1, 1, 0, IEEE80211_HTOP0_SCO_SCN,
	    IEEE80211_VHTOP0_CHAN_WIDTH_HT);
	if (err)
		return err;

	return 0;
}

static int
iwx_run(struct ieee80211vap *vap, struct iwx_softc *sc)
{
	struct ieee80211com *ic = &sc->sc_ic;
	struct iwx_node *in = IWX_NODE(vap->iv_bss);
	struct ieee80211_node *ni = &in->in_ni;
	struct iwx_vap *ivp = IWX_VAP(vap);
	int err;

	IWX_ASSERT_LOCKED(sc);

	if (ni->ni_flags & IEEE80211_NODE_HT) {
		uint8_t chains = iwx_mimo_enabled(sc) ? 2 : 1;
		uint8_t sco, vht_chan_width;
			sco = IEEE80211_HTOP0_SCO_SCN;
		if ((ni->ni_flags & IEEE80211_NODE_VHT) &&
		    IEEE80211_IS_CHAN_VHT80(ni->ni_chan))
			vht_chan_width = IEEE80211_VHTOP0_CHAN_WIDTH_80;
		else
			vht_chan_width = IEEE80211_VHTOP0_CHAN_WIDTH_HT;
		err = iwx_phy_ctxt_update(sc, ivp->phy_ctxt,
		    ivp->phy_ctxt->channel, chains, chains,
		    0, sco, vht_chan_width);
		if (err) {
			printf("%s: failed to update PHY\n", DEVNAME(sc));
			return err;
		}
	}

	/* Update STA again to apply HT and VHT settings. */
	err = iwx_add_sta_cmd(sc, in, 1);
	if (err) {
		printf("%s: could not update STA (error %d)\n",
		    DEVNAME(sc), err);
		return err;
	}

	/* We have now been assigned an associd by the AP. */
	err = iwx_mac_ctxt_cmd(sc, in, IWX_FW_CTXT_ACTION_MODIFY, 1);
	if (err) {
		printf("%s: failed to update MAC\n", DEVNAME(sc));
		return err;
	}

	err = iwx_sf_config(sc, IWX_SF_FULL_ON);
	if (err) {
		printf("%s: could not set sf full on (error %d)\n",
		    DEVNAME(sc), err);
		return err;
	}

	err = iwx_allow_mcast(sc);
	if (err) {
		printf("%s: could not allow mcast (error %d)\n",
		    DEVNAME(sc), err);
		return err;
	}

	err = iwx_power_update_device(sc);
	if (err) {
		printf("%s: could not send power command (error %d)\n",
		    DEVNAME(sc), err);
		return err;
	}
#ifdef notyet
	/*
	 * Disabled for now. Default beacon filter settings
	 * prevent net80211 from getting ERP and HT protection
	 * updates from beacons.
	 */
	err = iwx_enable_beacon_filter(sc, in);
	if (err) {
		printf("%s: could not enable beacon filter\n",
		    DEVNAME(sc));
		return err;
	}
#endif
	err = iwx_power_mac_update_mode(sc, in);
	if (err) {
		printf("%s: could not update MAC power (error %d)\n",
		    DEVNAME(sc), err);
		return err;
	}

	if (ic->ic_opmode == IEEE80211_M_MONITOR)
		return 0;

	err = iwx_rs_init(sc, in);
	if (err) {
		printf("%s: could not init rate scaling (error %d)\n",
		    DEVNAME(sc), err);
		return err;
	}

	return 0;
}

static int
iwx_run_stop(struct iwx_softc *sc)
{
	struct ieee80211com *ic = &sc->sc_ic;
	struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
	struct iwx_node *in = IWX_NODE(vap->iv_bss);
	struct ieee80211_node *ni = &in->in_ni;
	int err, i;

	IWX_ASSERT_LOCKED(sc);

	err = iwx_flush_sta(sc, in);
	if (err) {
		printf("%s: could not flush Tx path (error %d)\n",
		    DEVNAME(sc), err);
		return err;
	}

	/*
	 * Stop Rx BA sessions now. We cannot rely on the BA task
	 * for this when moving out of RUN state since it runs in a
	 * separate thread.
	 * Note that in->in_ni (struct ieee80211_node) already represents
	 * our new access point in case we are roaming between APs.
	 * This means we cannot rely on struct ieee802111_node to tell
	 * us which BA sessions exist.
	 */
	// TODO agg
	for (i = 0; i < nitems(sc->sc_rxba_data); i++) {
		struct iwx_rxba_data *rxba = &sc->sc_rxba_data[i];
		if (rxba->baid == IWX_RX_REORDER_DATA_INVALID_BAID)
			continue;
		iwx_sta_rx_agg(sc, ni, rxba->tid, 0, 0, 0, 0);
	}

	err = iwx_sf_config(sc, IWX_SF_INIT_OFF);
	if (err)
		return err;

	err = iwx_disable_beacon_filter(sc);
	if (err) {
		printf("%s: could not disable beacon filter (error %d)\n",
		    DEVNAME(sc), err);
		return err;
	}

	/* Mark station as disassociated. */
	err = iwx_mac_ctxt_cmd(sc, in, IWX_FW_CTXT_ACTION_MODIFY, 0);
	if (err) {
		printf("%s: failed to update MAC\n", DEVNAME(sc));
		return err;
	}

	return 0;
}

static struct ieee80211_node *
iwx_node_alloc(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN])
{
	return malloc(sizeof (struct iwx_node), M_80211_NODE,
	    M_NOWAIT | M_ZERO);
}

#if 0
int
iwx_set_key(struct ieee80211com *ic, struct ieee80211_node *ni,
    struct ieee80211_key *k)
{
	struct iwx_softc *sc = ic->ic_softc;
	struct iwx_node *in = (void *)ni;
	struct iwx_setkey_task_arg *a;
	int err;

	if (k->k_cipher != IEEE80211_CIPHER_CCMP) {
		/* Fallback to software crypto for other ciphers. */
		err = ieee80211_set_key(ic, ni, k);
		if (!err && in != NULL && (k->k_flags & IEEE80211_KEY_GROUP))
			in->in_flags |= IWX_NODE_FLAG_HAVE_GROUP_KEY;
		return err;
	}

	if (sc->setkey_nkeys >= nitems(sc->setkey_arg))
		return ENOSPC;

	a = &sc->setkey_arg[sc->setkey_cur];
	a->sta_id = IWX_STATION_ID;
	a->ni = ni;
	a->k = k;
	sc->setkey_cur = (sc->setkey_cur + 1) % nitems(sc->setkey_arg);
	sc->setkey_nkeys++;
	iwx_add_task(sc, systq, &sc->setkey_task);
	return EBUSY;
}

int
iwx_add_sta_key(struct iwx_softc *sc, int sta_id, struct ieee80211_node *ni,
    struct ieee80211_key *k)
{
	struct ieee80211com *ic = &sc->sc_ic;
	struct iwx_node *in = (void *)ni;
	struct iwx_add_sta_key_cmd cmd;
	uint32_t status;
	const int want_keymask = (IWX_NODE_FLAG_HAVE_PAIRWISE_KEY |
	    IWX_NODE_FLAG_HAVE_GROUP_KEY);
	int err;

	/*
	 * Keys are stored in 'ni' so 'k' is valid if 'ni' is valid.
	 * Currently we only implement station mode where 'ni' is always
	 * ic->ic_bss so there is no need to validate arguments beyond this:
	 */
	KASSERT(ni == ic->ic_bss);

	memset(&cmd, 0, sizeof(cmd));

	cmd.common.key_flags = htole16(IWX_STA_KEY_FLG_CCM |
	    IWX_STA_KEY_FLG_WEP_KEY_MAP |
	    ((k->k_id << IWX_STA_KEY_FLG_KEYID_POS) &
	    IWX_STA_KEY_FLG_KEYID_MSK));
	if (k->k_flags & IEEE80211_KEY_GROUP) {
		cmd.common.key_offset = 1;
		cmd.common.key_flags |= htole16(IWX_STA_KEY_MULTICAST);
	} else
		cmd.common.key_offset = 0;

	memcpy(cmd.common.key, k->k_key, MIN(sizeof(cmd.common.key), k->k_len));
	cmd.common.sta_id = sta_id;

	cmd.transmit_seq_cnt = htole64(k->k_tsc);

	status = IWX_ADD_STA_SUCCESS;
	err = iwx_send_cmd_pdu_status(sc, IWX_ADD_STA_KEY, sizeof(cmd), &cmd,
	    &status);
	if (sc->sc_flags & IWX_FLAG_SHUTDOWN)
		return ECANCELED;
	if (!err && (status & IWX_ADD_STA_STATUS_MASK) != IWX_ADD_STA_SUCCESS)
		err = EIO;
	if (err) {
		IEEE80211_SEND_MGMT(ic, ni, IEEE80211_FC0_SUBTYPE_DEAUTH,
		    IEEE80211_REASON_AUTH_LEAVE);
		ieee80211_new_state(ic, IEEE80211_S_SCAN, -1);
		return err;
	}

	if (k->k_flags & IEEE80211_KEY_GROUP)
		in->in_flags |= IWX_NODE_FLAG_HAVE_GROUP_KEY;
	else
		in->in_flags |= IWX_NODE_FLAG_HAVE_PAIRWISE_KEY;

	if ((in->in_flags & want_keymask) == want_keymask) {
		DPRINTF(("marking port %s valid\n",
		    ether_sprintf(ni->ni_macaddr)));
		ni->ni_port_valid = 1;
		ieee80211_set_link_state(ic, LINK_STATE_UP);
	}

	return 0;
}

void
iwx_setkey_task(void *arg)
{
	struct iwx_softc *sc = arg;
	struct iwx_setkey_task_arg *a;
	int err = 0, s = splnet();

	while (sc->setkey_nkeys > 0) {
		if (err || (sc->sc_flags & IWX_FLAG_SHUTDOWN))
			break;
		a = &sc->setkey_arg[sc->setkey_tail];
		err = iwx_add_sta_key(sc, a->sta_id, a->ni, a->k);
		a->sta_id = 0;
		a->ni = NULL;
		a->k = NULL;
		sc->setkey_tail = (sc->setkey_tail + 1) %
		    nitems(sc->setkey_arg);
		sc->setkey_nkeys--;
	}

	refcnt_rele_wake(&sc->task_refs);
	splx(s);
}

void
iwx_delete_key(struct ieee80211com *ic, struct ieee80211_node *ni,
    struct ieee80211_key *k)
{
	struct iwx_softc *sc = ic->ic_softc;
	struct iwx_add_sta_key_cmd cmd;

	if (k->k_cipher != IEEE80211_CIPHER_CCMP) {
		/* Fallback to software crypto for other ciphers. */
                ieee80211_delete_key(ic, ni, k);
		return;
	}

	if ((sc->sc_flags & IWX_FLAG_STA_ACTIVE) == 0)
		return;

	memset(&cmd, 0, sizeof(cmd));

	cmd.common.key_flags = htole16(IWX_STA_KEY_NOT_VALID |
	    IWX_STA_KEY_FLG_NO_ENC | IWX_STA_KEY_FLG_WEP_KEY_MAP |
	    ((k->k_id << IWX_STA_KEY_FLG_KEYID_POS) &
	    IWX_STA_KEY_FLG_KEYID_MSK));
	memcpy(cmd.common.key, k->k_key, MIN(sizeof(cmd.common.key), k->k_len));
	if (k->k_flags & IEEE80211_KEY_GROUP)
		cmd.common.key_offset = 1;
	else
		cmd.common.key_offset = 0;
	cmd.common.sta_id = IWX_STATION_ID;

	iwx_send_cmd_pdu(sc, IWX_ADD_STA_KEY, IWX_CMD_ASYNC, sizeof(cmd), &cmd);
}
#endif

static int
iwx_newstate_sub(struct ieee80211vap *vap, enum ieee80211_state nstate)
{
	struct ieee80211com *ic = vap->iv_ic;
	struct iwx_softc *sc = ic->ic_softc;
	enum ieee80211_state ostate = vap->iv_state;
	int err = 0;

	IWX_LOCK(sc);

	if (nstate <= ostate || nstate > IEEE80211_S_RUN) {
		switch (ostate) {
		case IEEE80211_S_RUN:
			err = iwx_run_stop(sc);
			if (err)
				goto out;
			/* FALLTHROUGH */
		case IEEE80211_S_ASSOC:
		case IEEE80211_S_AUTH:
			if (nstate <= IEEE80211_S_AUTH) {
				err = iwx_deauth(sc);
				if (err)
					goto out;
			}
			/* FALLTHROUGH */
		case IEEE80211_S_SCAN:
		case IEEE80211_S_INIT:
		default:
			break;
		}
//
//		/* Die now if iwx_stop() was called while we were sleeping. */
//		if (sc->sc_flags & IWX_FLAG_SHUTDOWN) {
//			refcnt_rele_wake(&sc->task_refs);
//			splx(s);
//			return;
//		}
	}

	switch (nstate) {
	case IEEE80211_S_INIT:
		break;

	case IEEE80211_S_SCAN:
			break;

	case IEEE80211_S_AUTH:
		err = iwx_auth(vap, sc);
		break;

	case IEEE80211_S_ASSOC:
		break;

	case IEEE80211_S_RUN:
		err = iwx_run(vap, sc);
		break;
	default:
		break;
	}

out:
	IWX_UNLOCK(sc);

	return (err);
}

static int
iwx_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg)
{
	struct iwx_vap *ivp = IWX_VAP(vap);
	struct ieee80211com *ic = vap->iv_ic;
	enum ieee80211_state ostate = vap->iv_state;
	int err;

	/*
	 * Prevent attempts to transition towards the same state, unless
	 * we are scanning in which case a SCAN -> SCAN transition
	 * triggers another scan iteration. And AUTH -> AUTH is needed
	 * to support band-steering.
	 */
	if (ostate == nstate && nstate != IEEE80211_S_SCAN &&
	    nstate != IEEE80211_S_AUTH)
		return 0;
	IEEE80211_UNLOCK(ic);
	err = iwx_newstate_sub(vap, nstate);
	IEEE80211_LOCK(ic);
	if (err == 0)
		err = ivp->iv_newstate(vap, nstate, arg);

	return (err);
}

static void
iwx_endscan(struct iwx_softc *sc)
{
        struct ieee80211com *ic = &sc->sc_ic;
        struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);

        if ((sc->sc_flags & (IWX_FLAG_SCANNING | IWX_FLAG_BGSCAN)) == 0)
                return;

        sc->sc_flags &= ~(IWX_FLAG_SCANNING | IWX_FLAG_BGSCAN);

        ieee80211_scan_done(TAILQ_FIRST(&ic->ic_vaps));
        wakeup(&vap->iv_state); /* wake up iwx_newstate */
}

/*
 * Aging and idle timeouts for the different possible scenarios
 * in default configuration
 */
static const uint32_t
iwx_sf_full_timeout_def[IWX_SF_NUM_SCENARIO][IWX_SF_NUM_TIMEOUT_TYPES] = {
	{
		htole32(IWX_SF_SINGLE_UNICAST_AGING_TIMER_DEF),
		htole32(IWX_SF_SINGLE_UNICAST_IDLE_TIMER_DEF)
	},
	{
		htole32(IWX_SF_AGG_UNICAST_AGING_TIMER_DEF),
		htole32(IWX_SF_AGG_UNICAST_IDLE_TIMER_DEF)
	},
	{
		htole32(IWX_SF_MCAST_AGING_TIMER_DEF),
		htole32(IWX_SF_MCAST_IDLE_TIMER_DEF)
	},
	{
		htole32(IWX_SF_BA_AGING_TIMER_DEF),
		htole32(IWX_SF_BA_IDLE_TIMER_DEF)
	},
	{
		htole32(IWX_SF_TX_RE_AGING_TIMER_DEF),
		htole32(IWX_SF_TX_RE_IDLE_TIMER_DEF)
	},
};

/*
 * Aging and idle timeouts for the different possible scenarios
 * in single BSS MAC configuration.
 */
static const uint32_t
iwx_sf_full_timeout[IWX_SF_NUM_SCENARIO][IWX_SF_NUM_TIMEOUT_TYPES] = {
	{
		htole32(IWX_SF_SINGLE_UNICAST_AGING_TIMER),
		htole32(IWX_SF_SINGLE_UNICAST_IDLE_TIMER)
	},
	{
		htole32(IWX_SF_AGG_UNICAST_AGING_TIMER),
		htole32(IWX_SF_AGG_UNICAST_IDLE_TIMER)
	},
	{
		htole32(IWX_SF_MCAST_AGING_TIMER),
		htole32(IWX_SF_MCAST_IDLE_TIMER)
	},
	{
		htole32(IWX_SF_BA_AGING_TIMER),
		htole32(IWX_SF_BA_IDLE_TIMER)
	},
	{
		htole32(IWX_SF_TX_RE_AGING_TIMER),
		htole32(IWX_SF_TX_RE_IDLE_TIMER)
	},
};

static void
iwx_fill_sf_command(struct iwx_softc *sc, struct iwx_sf_cfg_cmd *sf_cmd,
    struct ieee80211_node *ni)
{
	int i, j, watermark;

	sf_cmd->watermark[IWX_SF_LONG_DELAY_ON] = htole32(IWX_SF_W_MARK_SCAN);

	/*
	 * If we are in association flow - check antenna configuration
	 * capabilities of the AP station, and choose the watermark accordingly.
	 */
	if (ni) {
		if (ni->ni_flags & IEEE80211_NODE_HT) {
			struct ieee80211_htrateset *htrs = &ni->ni_htrates;
			int hasmimo = 0;
			for (i = 0; i < htrs->rs_nrates; i++) {
				if (htrs->rs_rates[i] > 7) {
					hasmimo = 1;
					break;
				}
			}
			if (hasmimo)
				watermark = IWX_SF_W_MARK_MIMO2;
			else
				watermark = IWX_SF_W_MARK_SISO;
		} else {
			watermark = IWX_SF_W_MARK_LEGACY;
		}
	/* default watermark value for unassociated mode. */
	} else {
		watermark = IWX_SF_W_MARK_MIMO2;
	}
	sf_cmd->watermark[IWX_SF_FULL_ON] = htole32(watermark);

	for (i = 0; i < IWX_SF_NUM_SCENARIO; i++) {
		for (j = 0; j < IWX_SF_NUM_TIMEOUT_TYPES; j++) {
			sf_cmd->long_delay_timeouts[i][j] =
					htole32(IWX_SF_LONG_DELAY_AGING_TIMER);
		}
	}

	if (ni) {
		memcpy(sf_cmd->full_on_timeouts, iwx_sf_full_timeout,
		       sizeof(iwx_sf_full_timeout));
	} else {
		memcpy(sf_cmd->full_on_timeouts, iwx_sf_full_timeout_def,
		       sizeof(iwx_sf_full_timeout_def));
	}

}

static int
iwx_sf_config(struct iwx_softc *sc, int new_state)
{
	struct ieee80211com *ic = &sc->sc_ic;
	struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
	struct ieee80211_node *ni = vap->iv_bss;
	struct iwx_sf_cfg_cmd sf_cmd = {
		.state = htole32(new_state),
	};
	int err = 0;

	switch (new_state) {
	case IWX_SF_UNINIT:
	case IWX_SF_INIT_OFF:
		iwx_fill_sf_command(sc, &sf_cmd, NULL);
		break;
	case IWX_SF_FULL_ON:
		iwx_fill_sf_command(sc, &sf_cmd, ni);
		break;
	default:
		return EINVAL;
	}

	err = iwx_send_cmd_pdu(sc, IWX_REPLY_SF_CFG_CMD, IWX_CMD_ASYNC,
				   sizeof(sf_cmd), &sf_cmd);
	return err;
}

static int
iwx_send_bt_init_conf(struct iwx_softc *sc)
{
	struct iwx_bt_coex_cmd bt_cmd;

	bzero(&bt_cmd, sizeof(struct iwx_bt_coex_cmd));

	bt_cmd.mode = htole32(IWX_BT_COEX_NW);
	bt_cmd.enabled_modules |= BT_COEX_SYNC2SCO_ENABLED;
	bt_cmd.enabled_modules |= BT_COEX_HIGH_BAND_RET;


	return iwx_send_cmd_pdu(sc, IWX_BT_CONFIG, 0, sizeof(bt_cmd),
	    &bt_cmd);
}

static int
iwx_send_soc_conf(struct iwx_softc *sc)
{
	struct iwx_soc_configuration_cmd cmd;
	int err;
	uint32_t cmd_id, flags = 0;

	memset(&cmd, 0, sizeof(cmd));

	/*
	 * In VER_1 of this command, the discrete value is considered
	 * an integer; In VER_2, it's a bitmask.  Since we have only 2
	 * values in VER_1, this is backwards-compatible with VER_2,
	 * as long as we don't set any other flag bits.
	 */
	if (!sc->sc_integrated) { /* VER_1 */
		flags = IWX_SOC_CONFIG_CMD_FLAGS_DISCRETE;
	} else { /* VER_2 */
		uint8_t scan_cmd_ver;
		if (sc->sc_ltr_delay != IWX_SOC_FLAGS_LTR_APPLY_DELAY_NONE)
			flags |= (sc->sc_ltr_delay &
			    IWX_SOC_FLAGS_LTR_APPLY_DELAY_MASK);
		scan_cmd_ver = iwx_lookup_cmd_ver(sc, IWX_LONG_GROUP,
		    IWX_SCAN_REQ_UMAC);
		if (scan_cmd_ver != IWX_FW_CMD_VER_UNKNOWN &&
		    scan_cmd_ver >= 2 && sc->sc_low_latency_xtal)
			flags |= IWX_SOC_CONFIG_CMD_FLAGS_LOW_LATENCY;
	}
	cmd.flags = htole32(flags);

	cmd.latency = htole32(sc->sc_xtal_latency);

	cmd_id = iwx_cmd_id(IWX_SOC_CONFIGURATION_CMD, IWX_SYSTEM_GROUP, 0);
	err = iwx_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
iwx_send_update_mcc_cmd(struct iwx_softc *sc, const char *alpha2)
{
	struct iwx_mcc_update_cmd mcc_cmd;
	struct iwx_host_cmd hcmd = {
		.id = IWX_MCC_UPDATE_CMD,
		.flags = IWX_CMD_WANT_RESP,
		.data = { &mcc_cmd },
	};
	struct iwx_rx_packet *pkt;
	struct iwx_mcc_update_resp *resp;
	size_t resp_len;
	int err;

	memset(&mcc_cmd, 0, sizeof(mcc_cmd));
	mcc_cmd.mcc = htole16(alpha2[0] << 8 | alpha2[1]);
	if (isset(sc->sc_ucode_api, IWX_UCODE_TLV_API_WIFI_MCC_UPDATE) ||
	    isset(sc->sc_enabled_capa, IWX_UCODE_TLV_CAPA_LAR_MULTI_MCC))
		mcc_cmd.source_id = IWX_MCC_SOURCE_GET_CURRENT;
	else
		mcc_cmd.source_id = IWX_MCC_SOURCE_OLD_FW;

	hcmd.len[0] = sizeof(struct iwx_mcc_update_cmd);
	hcmd.resp_pkt_len = IWX_CMD_RESP_MAX;

	err = iwx_send_cmd(sc, &hcmd);
	if (err)
		return err;

	pkt = hcmd.resp_pkt;
	if (!pkt || (pkt->hdr.flags & IWX_CMD_FAILED_MSK)) {
		err = EIO;
		goto out;
	}

	resp_len = iwx_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;
	}

	DPRINTF(("MCC status=0x%x mcc=0x%x cap=0x%x time=0x%x geo_info=0x%x source_id=0x%d n_channels=%u\n",
	    resp->status, resp->mcc, resp->cap, resp->time, resp->geo_info, resp->source_id, resp->n_channels));

out:
	iwx_free_resp(sc, &hcmd);

	return err;
}

static int
iwx_send_temp_report_ths_cmd(struct iwx_softc *sc)
{
	struct iwx_temp_report_ths_cmd cmd;
	int err;

	/*
	 * In order to give responsibility for critical-temperature-kill
	 * and TX backoff to FW we need to send an empty temperature
	 * reporting command at init time.
	 */
	memset(&cmd, 0, sizeof(cmd));

	err = iwx_send_cmd_pdu(sc,
	    IWX_WIDE_ID(IWX_PHY_OPS_GROUP, IWX_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 int
iwx_init_hw(struct iwx_softc *sc)
{
	struct ieee80211com *ic = &sc->sc_ic;
	int err = 0, i;

	err = iwx_run_init_mvm_ucode(sc, 0);
	if (err)
		return err;

	if (!iwx_nic_lock(sc))
		return EBUSY;

	err = iwx_send_tx_ant_cfg(sc, iwx_fw_valid_tx_ant(sc));
	if (err) {
		printf("%s: could not init tx ant config (error %d)\n",
		    DEVNAME(sc), err);
		goto err;
	}

	if (sc->sc_tx_with_siso_diversity) {
		err = iwx_send_phy_cfg_cmd(sc);
		if (err) {
			printf("%s: could not send phy config (error %d)\n",
			    DEVNAME(sc), err);
			goto err;
		}
	}

	err = iwx_send_bt_init_conf(sc);
	if (err) {
		printf("%s: could not init bt coex (error %d)\n",
		    DEVNAME(sc), err);
		return err;
	}

	err = iwx_send_soc_conf(sc);
	if (err) {
		printf("%s: iwx_send_soc_conf failed\n", __func__);
		return err;
	}

	if (isset(sc->sc_enabled_capa, IWX_UCODE_TLV_CAPA_DQA_SUPPORT)) {
		printf("%s: === IWX_UCODE_TLV_CAPA_DQA_SUPPORT\n", __func__);
		err = iwx_send_dqa_cmd(sc);
		if (err) {
			printf("%s: IWX_UCODE_TLV_CAPA_DQA_SUPPORT "
			    "failed (error %d)\n", __func__, err);
			return err;
		}
	}
	// TODO phyctxt
	for (i = 0; i < IWX_NUM_PHY_CTX; i++) {
		/*
		 * The channel used here isn't relevant as it's
		 * going to be overwritten in the other flows.
		 * For now use the first channel we have.
		 */
		sc->sc_phyctxt[i].id = i;
		sc->sc_phyctxt[i].channel = &ic->ic_channels[1];
		err = iwx_phy_ctxt_cmd(sc, &sc->sc_phyctxt[i], 1, 1,
		    IWX_FW_CTXT_ACTION_ADD, 0, 0, 0);
		if (err) {
			printf("%s: could not add phy context %d (error %d)\n",
			    DEVNAME(sc), i, err);
			goto err;
		}
		if (iwx_lookup_cmd_ver(sc, IWX_DATA_PATH_GROUP,
		    IWX_RLC_CONFIG_CMD) == 2) {
			err = iwx_phy_send_rlc(sc, &sc->sc_phyctxt[i], 1, 1);
			if (err) {
				printf("%s: could not configure RLC for PHY "
				    "%d (error %d)\n", DEVNAME(sc), i, err);
				goto err;
			}
		}
	}

	err = iwx_config_ltr(sc);
	if (err) {
		printf("%s: PCIe LTR configuration failed (error %d)\n",
		    DEVNAME(sc), err);
	}

	if (isset(sc->sc_enabled_capa, IWX_UCODE_TLV_CAPA_CT_KILL_BY_FW)) {
		err = iwx_send_temp_report_ths_cmd(sc);
		if (err) {
			printf("%s: iwx_send_temp_report_ths_cmd failed\n",
			    __func__);
			goto err;
		}
	}

	err = iwx_power_update_device(sc);
	if (err) {
		printf("%s: could not send power command (error %d)\n",
		    DEVNAME(sc), err);
		goto err;
	}

	if (sc->sc_nvm.lar_enabled) {
		err = iwx_send_update_mcc_cmd(sc, "ZZ");
		if (err) {
			printf("%s: could not init LAR (error %d)\n",
			    DEVNAME(sc), err);
			goto err;
		}
	}

	err = iwx_config_umac_scan_reduced(sc);
	if (err) {
		printf("%s: could not configure scan (error %d)\n",
		    DEVNAME(sc), err);
		goto err;
	}

	err = iwx_disable_beacon_filter(sc);
	if (err) {
		printf("%s: could not disable beacon filter (error %d)\n",
		    DEVNAME(sc), err);
		goto err;
	}

err:
	iwx_nic_unlock(sc);
	return err;
}

/* Allow multicast from our BSSID. */
static int
iwx_allow_mcast(struct iwx_softc *sc)
{
	struct ieee80211com *ic = &sc->sc_ic;
	struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
	struct iwx_node *in = IWX_NODE(vap->iv_bss);
	struct iwx_mcast_filter_cmd *cmd;
	size_t size;
	int err;

	size = roundup(sizeof(*cmd), 4);
	cmd = malloc(size, M_DEVBUF, M_NOWAIT | M_ZERO);
	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 = iwx_send_cmd_pdu(sc, IWX_MCAST_FILTER_CMD,
	    0, size, cmd);
	free(cmd, M_DEVBUF);
	return err;
}

static int
iwx_init(struct iwx_softc *sc)
{
	int err, generation;
	generation = ++sc->sc_generation;
	iwx_preinit(sc);

	err = iwx_start_hw(sc);
	if (err) {
		printf("%s: iwx_start_hw failed\n", __func__);
		return err;
	}

	err = iwx_init_hw(sc);
	if (err) {
		if (generation == sc->sc_generation)
			iwx_stop_device(sc);
		printf("%s: iwx_init_hw failed (error %d)\n", __func__, err);
		return err;
	}

	sc->sc_flags |= IWX_FLAG_HW_INITED;
	callout_reset(&sc->watchdog_to, hz, iwx_watchdog, sc);

	return 0;
}

static void
iwx_start(struct iwx_softc *sc)
{
        struct ieee80211_node *ni;
        struct mbuf *m;

        while (sc->qfullmsk == 0 && (m = mbufq_dequeue(&sc->sc_snd)) != NULL) {
                ni = (struct ieee80211_node *)m->m_pkthdr.rcvif;
                if (iwx_tx(sc, m, ni) != 0) {
                      if_inc_counter(ni->ni_vap->iv_ifp, IFCOUNTER_OERRORS, 1);
                        continue;
                }
        }
}

static void
iwx_stop(struct iwx_softc *sc)
{
	struct ieee80211com *ic = &sc->sc_ic;
	struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
	struct iwx_vap *ivp = IWX_VAP(vap);

	iwx_stop_device(sc);

	/* Reset soft state. */
	sc->sc_generation++;
	ivp->phy_ctxt = NULL;

	sc->sc_flags &= ~(IWX_FLAG_SCANNING | IWX_FLAG_BGSCAN);
	sc->sc_flags &= ~IWX_FLAG_MAC_ACTIVE;
	sc->sc_flags &= ~IWX_FLAG_BINDING_ACTIVE;
	sc->sc_flags &= ~IWX_FLAG_STA_ACTIVE;
	sc->sc_flags &= ~IWX_FLAG_TE_ACTIVE;
	sc->sc_flags &= ~IWX_FLAG_HW_ERR;
	sc->sc_flags &= ~IWX_FLAG_SHUTDOWN;
	sc->sc_flags &= ~IWX_FLAG_TXFLUSH;

	sc->sc_rx_ba_sessions = 0;
	sc->ba_rx.start_tidmask = 0;
	sc->ba_rx.stop_tidmask = 0;
	memset(sc->aggqid, 0, sizeof(sc->aggqid));
	sc->ba_tx.start_tidmask = 0;
	sc->ba_tx.stop_tidmask = 0;
}

static void
iwx_watchdog(void *arg)
{
	struct iwx_softc *sc = arg;
	struct ieee80211com *ic = &sc->sc_ic;
	int i;

	/*
	 * We maintain a separate timer for each Tx queue because
	 * Tx aggregation queues can get "stuck" while other queues
	 * keep working. The Linux driver uses a similar workaround.
	 */
	for (i = 0; i < nitems(sc->sc_tx_timer); i++) {
		if (sc->sc_tx_timer[i] > 0) {
			if (--sc->sc_tx_timer[i] == 0) {
				printf("%s: device timeout\n", DEVNAME(sc));

				iwx_nic_error(sc);
				iwx_dump_driver_status(sc);
				ieee80211_restart_all(ic);
				return;
			}
		}
	}
	callout_reset(&sc->watchdog_to, hz, iwx_watchdog, sc);
}

/*
 * Note: This structure is read from the device with IO accesses,
 * and the reading already does the endian conversion. As it is
 * read with uint32_t-sized accesses, any members with a different size
 * need to be ordered correctly though!
 */
struct iwx_error_event_table {
	uint32_t valid;		/* (nonzero) valid, (0) log is empty */
	uint32_t error_id;		/* type of error */
	uint32_t trm_hw_status0;	/* TRM HW status */
	uint32_t trm_hw_status1;	/* TRM HW status */
	uint32_t blink2;		/* branch link */
	uint32_t ilink1;		/* interrupt link */
	uint32_t ilink2;		/* interrupt link */
	uint32_t data1;		/* error-specific data */
	uint32_t data2;		/* error-specific data */
	uint32_t data3;		/* error-specific data */
	uint32_t bcon_time;		/* beacon timer */
	uint32_t tsf_low;		/* network timestamp function timer */
	uint32_t tsf_hi;		/* network timestamp function timer */
	uint32_t gp1;		/* GP1 timer register */
	uint32_t gp2;		/* GP2 timer register */
	uint32_t fw_rev_type;	/* firmware revision type */
	uint32_t major;		/* uCode version major */
	uint32_t minor;		/* uCode version minor */
	uint32_t hw_ver;		/* HW Silicon version */
	uint32_t brd_ver;		/* HW board version */
	uint32_t log_pc;		/* log program counter */
	uint32_t frame_ptr;		/* frame pointer */
	uint32_t stack_ptr;		/* stack pointer */
	uint32_t hcmd;		/* last host command header */
	uint32_t isr0;		/* isr status register LMPM_NIC_ISR0:
				 * rxtx_flag */
	uint32_t isr1;		/* isr status register LMPM_NIC_ISR1:
				 * host_flag */
	uint32_t isr2;		/* isr status register LMPM_NIC_ISR2:
				 * enc_flag */
	uint32_t isr3;		/* isr status register LMPM_NIC_ISR3:
				 * time_flag */
	uint32_t isr4;		/* isr status register LMPM_NIC_ISR4:
				 * wico interrupt */
	uint32_t last_cmd_id;	/* last HCMD id handled by the firmware */
	uint32_t wait_event;		/* wait event() caller address */
	uint32_t l2p_control;	/* L2pControlField */
	uint32_t l2p_duration;	/* L2pDurationField */
	uint32_t l2p_mhvalid;	/* L2pMhValidBits */
	uint32_t l2p_addr_match;	/* L2pAddrMatchStat */
	uint32_t lmpm_pmg_sel;	/* indicate which clocks are turned on
				 * (LMPM_PMG_SEL) */
	uint32_t u_timestamp;	/* indicate when the date and time of the
				 * compilation */
	uint32_t flow_handler;	/* FH read/write pointers, RX credit */
} __packed /* LOG_ERROR_TABLE_API_S_VER_3 */;

/*
 * UMAC error struct - relevant starting from family 8000 chip.
 * Note: This structure is read from the device with IO accesses,
 * and the reading already does the endian conversion. As it is
 * read with u32-sized accesses, any members with a different size
 * need to be ordered correctly though!
 */
struct iwx_umac_error_event_table {
	uint32_t valid;		/* (nonzero) valid, (0) log is empty */
	uint32_t error_id;	/* type of error */
	uint32_t blink1;	/* branch link */
	uint32_t blink2;	/* branch link */
	uint32_t ilink1;	/* interrupt link */
	uint32_t ilink2;	/* interrupt link */
	uint32_t data1;		/* error-specific data */
	uint32_t data2;		/* error-specific data */
	uint32_t data3;		/* error-specific data */
	uint32_t umac_major;
	uint32_t umac_minor;
	uint32_t frame_pointer;	/* core register 27*/
	uint32_t stack_pointer;	/* core register 28 */
	uint32_t cmd_header;	/* latest host cmd sent to UMAC */
	uint32_t nic_isr_pref;	/* ISR status register */
} __packed;

#define ERROR_START_OFFSET  (1 * sizeof(uint32_t))
#define ERROR_ELEM_SIZE     (7 * sizeof(uint32_t))

static void
iwx_nic_umac_error(struct iwx_softc *sc)
{
	struct iwx_umac_error_event_table table;
	uint32_t base;

	base = sc->sc_uc.uc_umac_error_event_table;

	if (base < 0x400000) {
		printf("%s: Invalid error log pointer 0x%08x\n",
		    DEVNAME(sc), base);
		return;
	}

	if (iwx_read_mem(sc, base, &table, sizeof(table)/sizeof(uint32_t))) {
		printf("%s: reading errlog failed\n", DEVNAME(sc));
		return;
	}

	if (ERROR_START_OFFSET <= table.valid * ERROR_ELEM_SIZE) {
		printf("%s: Start UMAC Error Log Dump:\n", DEVNAME(sc));
		printf("%s: Status: 0x%x, count: %d\n", DEVNAME(sc),
			sc->sc_flags, table.valid);
	}

	printf("%s: 0x%08X | %s\n", DEVNAME(sc), table.error_id,
		iwx_desc_lookup(table.error_id));
	printf("%s: 0x%08X | umac branchlink1\n", DEVNAME(sc), table.blink1);
	printf("%s: 0x%08X | umac branchlink2\n", DEVNAME(sc), table.blink2);
	printf("%s: 0x%08X | umac interruptlink1\n", DEVNAME(sc), table.ilink1);
	printf("%s: 0x%08X | umac interruptlink2\n", DEVNAME(sc), table.ilink2);
	printf("%s: 0x%08X | umac data1\n", DEVNAME(sc), table.data1);
	printf("%s: 0x%08X | umac data2\n", DEVNAME(sc), table.data2);
	printf("%s: 0x%08X | umac data3\n", DEVNAME(sc), table.data3);
	printf("%s: 0x%08X | umac major\n", DEVNAME(sc), table.umac_major);
	printf("%s: 0x%08X | umac minor\n", DEVNAME(sc), table.umac_minor);
	printf("%s: 0x%08X | frame pointer\n", DEVNAME(sc),
	    table.frame_pointer);
	printf("%s: 0x%08X | stack pointer\n", DEVNAME(sc),
	    table.stack_pointer);
	printf("%s: 0x%08X | last host cmd\n", DEVNAME(sc), table.cmd_header);
	printf("%s: 0x%08X | isr status reg\n", DEVNAME(sc),
	    table.nic_isr_pref);
}

#define IWX_FW_SYSASSERT_CPU_MASK 0xf0000000
static 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 *
iwx_desc_lookup(uint32_t num)
{
	int i;

	for (i = 0; i < nitems(advanced_lookup) - 1; i++)
		if (advanced_lookup[i].num ==
		    (num & ~IWX_FW_SYSASSERT_CPU_MASK))
			return advanced_lookup[i].name;

	/* No entry matches 'num', so it is the last: ADVANCED_SYSASSERT */
	return advanced_lookup[i].name;
}

/*
 * Support for dumping the error log seemed like a good idea ...
 * but it's mostly hex junk and the only sensible thing is the
 * hw/ucode revision (which we know anyway).  Since it's here,
 * I'll just leave it in, just in case e.g. the Intel guys want to
 * help us decipher some "ADVANCED_SYSASSERT" later.
 */
static void
iwx_nic_error(struct iwx_softc *sc)
{
	struct iwx_error_event_table table;
	uint32_t base;

	printf("%s: dumping device error log\n", DEVNAME(sc));
	printf("%s: GOS-3758: 1\n", __func__);
	base = sc->sc_uc.uc_lmac_error_event_table[0];
	printf("%s: GOS-3758: 2\n", __func__);
	if (base < 0x400000) {
		printf("%s: Invalid error log pointer 0x%08x\n",
		    DEVNAME(sc), base);
		return;
	}

	printf("%s: GOS-3758: 3\n", __func__);
	if (iwx_read_mem(sc, base, &table, sizeof(table)/sizeof(uint32_t))) {
		printf("%s: reading errlog failed\n", DEVNAME(sc));
		return;
	}

	printf("%s: GOS-3758: 4\n", __func__);
	if (!table.valid) {
		printf("%s: errlog not found, skipping\n", DEVNAME(sc));
		return;
	}

	printf("%s: GOS-3758: 5\n", __func__);
	if (ERROR_START_OFFSET <= table.valid * ERROR_ELEM_SIZE) {
		printf("%s: Start Error Log Dump:\n", DEVNAME(sc));
		printf("%s: Status: 0x%x, count: %d\n", DEVNAME(sc),
		    sc->sc_flags, table.valid);
	}

	printf("%s: GOS-3758: 6\n", __func__);
	printf("%s: 0x%08X | %-28s\n", DEVNAME(sc), table.error_id,
	    iwx_desc_lookup(table.error_id));
	printf("%s: %08X | trm_hw_status0\n", DEVNAME(sc),
	    table.trm_hw_status0);
	printf("%s: %08X | trm_hw_status1\n", DEVNAME(sc),
	    table.trm_hw_status1);
	printf("%s: %08X | branchlink2\n", DEVNAME(sc), table.blink2);
	printf("%s: %08X | interruptlink1\n", DEVNAME(sc), table.ilink1);
	printf("%s: %08X | interruptlink2\n", DEVNAME(sc), table.ilink2);
	printf("%s: %08X | data1\n", DEVNAME(sc), table.data1);
	printf("%s: %08X | data2\n", DEVNAME(sc), table.data2);
	printf("%s: %08X | data3\n", DEVNAME(sc), table.data3);
	printf("%s: %08X | beacon time\n", DEVNAME(sc), table.bcon_time);
	printf("%s: %08X | tsf low\n", DEVNAME(sc), table.tsf_low);
	printf("%s: %08X | tsf hi\n", DEVNAME(sc), table.tsf_hi);
	printf("%s: %08X | time gp1\n", DEVNAME(sc), table.gp1);
	printf("%s: %08X | time gp2\n", DEVNAME(sc), table.gp2);
	printf("%s: %08X | uCode revision type\n", DEVNAME(sc),
	    table.fw_rev_type);
	printf("%s: %08X | uCode version major\n", DEVNAME(sc),
	    table.major);
	printf("%s: %08X | uCode version minor\n", DEVNAME(sc),
	    table.minor);
	printf("%s: %08X | hw version\n", DEVNAME(sc), table.hw_ver);
	printf("%s: %08X | board version\n", DEVNAME(sc), table.brd_ver);
	printf("%s: %08X | hcmd\n", DEVNAME(sc), table.hcmd);
	printf("%s: %08X | isr0\n", DEVNAME(sc), table.isr0);
	printf("%s: %08X | isr1\n", DEVNAME(sc), table.isr1);
	printf("%s: %08X | isr2\n", DEVNAME(sc), table.isr2);
	printf("%s: %08X | isr3\n", DEVNAME(sc), table.isr3);
	printf("%s: %08X | isr4\n", DEVNAME(sc), table.isr4);
	printf("%s: %08X | last cmd Id\n", DEVNAME(sc), table.last_cmd_id);
	printf("%s: %08X | wait_event\n", DEVNAME(sc), table.wait_event);
	printf("%s: %08X | l2p_control\n", DEVNAME(sc), table.l2p_control);
	printf("%s: %08X | l2p_duration\n", DEVNAME(sc), table.l2p_duration);
	printf("%s: %08X | l2p_mhvalid\n", DEVNAME(sc), table.l2p_mhvalid);
	printf("%s: %08X | l2p_addr_match\n", DEVNAME(sc), table.l2p_addr_match);
	printf("%s: %08X | lmpm_pmg_sel\n", DEVNAME(sc), table.lmpm_pmg_sel);
	printf("%s: %08X | timestamp\n", DEVNAME(sc), table.u_timestamp);
	printf("%s: %08X | flow_handler\n", DEVNAME(sc), table.flow_handler);

	if (sc->sc_uc.uc_umac_error_event_table)
		iwx_nic_umac_error(sc);
}

static void
iwx_dump_driver_status(struct iwx_softc *sc)
{
	struct ieee80211com *ic = &sc->sc_ic;
	struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
	enum ieee80211_state state = vap->iv_state;
	int i;

	printf("driver status:\n");
	for (i = 0; i < nitems(sc->txq); i++) {
		struct iwx_tx_ring *ring = &sc->txq[i];
		printf("  tx ring %2d: qid=%-2d cur=%-3d "
		    "cur_hw=%-3d queued=%-3d\n",
		    i, ring->qid, ring->cur, ring->cur_hw,
		    ring->queued);
	}
	printf("  rx ring: cur=%d\n", sc->rxq.cur);
	printf("  802.11 state %s\n", ieee80211_state_name[state]);
}

#define SYNC_RESP_STRUCT(_var_, _pkt_)					\
do {									\
	bus_dmamap_sync(sc->rxq.data_dmat, data->map, BUS_DMASYNC_POSTREAD); \
	_var_ = (void *)((_pkt_)+1);					\
} while (/*CONSTCOND*/0)

static int
iwx_rx_pkt_valid(struct iwx_rx_packet *pkt)
{
	int qid, idx, code;

	qid = pkt->hdr.qid & ~0x80;
	idx = pkt->hdr.idx;
	code = IWX_WIDE_ID(pkt->hdr.flags, pkt->hdr.code);

	return (!(qid == 0 && idx == 0 && code == 0) &&
	    pkt->len_n_flags != htole32(IWX_FH_RSCSR_FRAME_INVALID));
}

static void
iwx_rx_pkt(struct iwx_softc *sc, struct iwx_rx_data *data, struct mbuf *ml)
{
	struct ieee80211com *ic = &sc->sc_ic;
	struct iwx_rx_packet *pkt, *nextpkt;
	uint32_t offset = 0, nextoff = 0, nmpdu = 0, len;
	struct mbuf *m0, *m;
	const size_t minsz = sizeof(pkt->len_n_flags) + sizeof(pkt->hdr);
	int qid, idx, code, handled = 1;

	m0 = data->m;
	while (m0 && offset + minsz < IWX_RBUF_SIZE) {
		pkt = (struct iwx_rx_packet *)(m0->m_data + offset);
		qid = pkt->hdr.qid;
		idx = pkt->hdr.idx;
		code = IWX_WIDE_ID(pkt->hdr.flags, pkt->hdr.code);

		if (!iwx_rx_pkt_valid(pkt))
			break;

		/*
		 * XXX Intel inside (tm)
		 * Any commands in the LONG_GROUP could actually be in the
		 * LEGACY group. Firmware API versions >= 50 reject commands
		 * in group 0, forcing us to use this hack.
		 */
		if (iwx_cmd_groupid(code) == IWX_LONG_GROUP) {
			struct iwx_tx_ring *ring = &sc->txq[qid];
			struct iwx_tx_data *txdata = &ring->data[idx];
			if (txdata->flags & IWX_TXDATA_FLAG_CMD_IS_NARROW)
				code = iwx_cmd_opcode(code);
		}

		len = sizeof(pkt->len_n_flags) + iwx_rx_packet_len(pkt);
		if (len < minsz || len > (IWX_RBUF_SIZE - offset))
			break;

		// TODO ???
		if (code == IWX_REPLY_RX_MPDU_CMD && ++nmpdu == 1) {
			/* Take mbuf m0 off the RX ring. */
			if (iwx_rx_addbuf(sc, IWX_RBUF_SIZE, sc->rxq.cur)) {
				break;
			}
			KASSERT((data->m != m0), ("%s: data->m != m0", __func__));
		}

		switch (code) {
		case IWX_REPLY_RX_PHY_CMD:
			/* XXX-THJ: I've not managed to hit this path in testing */
			iwx_rx_rx_phy_cmd(sc, pkt, data);
			break;

		case IWX_REPLY_RX_MPDU_CMD: {
			size_t maxlen = IWX_RBUF_SIZE - offset - minsz;
			nextoff = offset +
			    roundup(len, IWX_FH_RSCSR_FRAME_ALIGN);
			nextpkt = (struct iwx_rx_packet *)
			    (m0->m_data + nextoff);
			/* AX210 devices ship only one packet per Rx buffer. */
			if (sc->sc_device_family >= IWX_DEVICE_FAMILY_AX210 ||
			    nextoff + minsz >= IWX_RBUF_SIZE ||
			    !iwx_rx_pkt_valid(nextpkt)) {
				/* No need to copy last frame in buffer. */
				if (offset > 0)
					m_adj(m0, offset);
				iwx_rx_mpdu_mq(sc, m0, pkt->data, maxlen);
				m0 = NULL; /* stack owns m0 now; abort loop */
			} else {
				/*
				 * Create an mbuf which points to the current
				 * packet. Always copy from offset zero to
				 * preserve m_pkthdr.
				 */
				m = m_copym(m0, 0, M_COPYALL, M_NOWAIT);
				if (m == NULL) {
					m_freem(m0);
					m0 = NULL;
					break;
				}
				m_adj(m, offset);
				iwx_rx_mpdu_mq(sc, m, pkt->data, maxlen);
			}
			break;
		}

//		case IWX_BAR_FRAME_RELEASE:
//			iwx_rx_bar_frame_release(sc, pkt, ml);
//			break;
//
		case IWX_TX_CMD:
			iwx_rx_tx_cmd(sc, pkt, data);
			break;

		case IWX_BA_NOTIF:
			iwx_rx_compressed_ba(sc, pkt);
			break;

		case IWX_MISSED_BEACONS_NOTIFICATION:
			iwx_rx_bmiss(sc, pkt, data);
			DPRINTF(("%s: IWX_MISSED_BEACONS_NOTIFICATION\n",
			    __func__));
			ieee80211_beacon_miss(ic);
			break;

		case IWX_MFUART_LOAD_NOTIFICATION:
			break;

		case IWX_ALIVE: {
			struct iwx_alive_resp_v4 *resp4;
			struct iwx_alive_resp_v5 *resp5;
			struct iwx_alive_resp_v6 *resp6;

			DPRINTF(("%s: firmware alive\n", __func__));
			sc->sc_uc.uc_ok = 0;

			/*
			 * For v5 and above, we can check the version, for older
			 * versions we need to check the size.
			 */
			 if (iwx_lookup_notif_ver(sc, IWX_LEGACY_GROUP,
			    IWX_ALIVE) == 6) {
				SYNC_RESP_STRUCT(resp6, pkt);
				if (iwx_rx_packet_payload_len(pkt) !=
				    sizeof(*resp6)) {
					sc->sc_uc.uc_intr = 1;
					wakeup(&sc->sc_uc);
					break;
				}
				sc->sc_uc.uc_lmac_error_event_table[0] = le32toh(
				    resp6->lmac_data[0].dbg_ptrs.error_event_table_ptr);
				sc->sc_uc.uc_lmac_error_event_table[1] = le32toh(
				    resp6->lmac_data[1].dbg_ptrs.error_event_table_ptr);
				sc->sc_uc.uc_log_event_table = le32toh(
				    resp6->lmac_data[0].dbg_ptrs.log_event_table_ptr);
				sc->sc_uc.uc_umac_error_event_table = le32toh(
				    resp6->umac_data.dbg_ptrs.error_info_addr);
				sc->sc_sku_id[0] =
				    le32toh(resp6->sku_id.data[0]);
				sc->sc_sku_id[1] =
				    le32toh(resp6->sku_id.data[1]);
				sc->sc_sku_id[2] =
				    le32toh(resp6->sku_id.data[2]);
				if (resp6->status == IWX_ALIVE_STATUS_OK) {
					sc->sc_uc.uc_ok = 1;
				}
			 } else if (iwx_lookup_notif_ver(sc, IWX_LEGACY_GROUP,
			    IWX_ALIVE) == 5) {
				SYNC_RESP_STRUCT(resp5, pkt);
				if (iwx_rx_packet_payload_len(pkt) !=
				    sizeof(*resp5)) {
					sc->sc_uc.uc_intr = 1;
					wakeup(&sc->sc_uc);
					break;
				}
				sc->sc_uc.uc_lmac_error_event_table[0] = le32toh(
				    resp5->lmac_data[0].dbg_ptrs.error_event_table_ptr);
				sc->sc_uc.uc_lmac_error_event_table[1] = le32toh(
				    resp5->lmac_data[1].dbg_ptrs.error_event_table_ptr);
				sc->sc_uc.uc_log_event_table = le32toh(
				    resp5->lmac_data[0].dbg_ptrs.log_event_table_ptr);
				sc->sc_uc.uc_umac_error_event_table = le32toh(
				    resp5->umac_data.dbg_ptrs.error_info_addr);
				sc->sc_sku_id[0] =
				    le32toh(resp5->sku_id.data[0]);
				sc->sc_sku_id[1] =
				    le32toh(resp5->sku_id.data[1]);
				sc->sc_sku_id[2] =
				    le32toh(resp5->sku_id.data[2]);
				if (resp5->status == IWX_ALIVE_STATUS_OK)
					sc->sc_uc.uc_ok = 1;
			} else if (iwx_rx_packet_payload_len(pkt) == sizeof(*resp4)) {
				SYNC_RESP_STRUCT(resp4, pkt);
				sc->sc_uc.uc_lmac_error_event_table[0] = le32toh(
				    resp4->lmac_data[0].dbg_ptrs.error_event_table_ptr);
				sc->sc_uc.uc_lmac_error_event_table[1] = le32toh(
				    resp4->lmac_data[1].dbg_ptrs.error_event_table_ptr);
				sc->sc_uc.uc_log_event_table = le32toh(
				    resp4->lmac_data[0].dbg_ptrs.log_event_table_ptr);
				sc->sc_uc.uc_umac_error_event_table = le32toh(
				    resp4->umac_data.dbg_ptrs.error_info_addr);
				if (resp4->status == IWX_ALIVE_STATUS_OK)
					sc->sc_uc.uc_ok = 1;
			} else
				printf("unknown payload version");

			sc->sc_uc.uc_intr = 1;
			wakeup(&sc->sc_uc);
			break;
		}

		case IWX_STATISTICS_NOTIFICATION: {
			struct iwx_notif_statistics *stats;
			SYNC_RESP_STRUCT(stats, pkt);
			memcpy(&sc->sc_stats, stats, sizeof(sc->sc_stats));
			sc->sc_noise = iwx_get_noise(&stats->rx.general);
			break;
		}

		case IWX_DTS_MEASUREMENT_NOTIFICATION:
		case IWX_WIDE_ID(IWX_PHY_OPS_GROUP,
				 IWX_DTS_MEASUREMENT_NOTIF_WIDE):
		case IWX_WIDE_ID(IWX_PHY_OPS_GROUP,
				 IWX_TEMP_REPORTING_THRESHOLDS_CMD):
			break;

		case IWX_WIDE_ID(IWX_PHY_OPS_GROUP,
		    IWX_CT_KILL_NOTIFICATION): {
			struct iwx_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 |= IWX_FLAG_HW_ERR;
			ieee80211_restart_all(ic);
			break;
		}

		case IWX_WIDE_ID(IWX_DATA_PATH_GROUP,
		    IWX_SCD_QUEUE_CONFIG_CMD):
		case IWX_WIDE_ID(IWX_DATA_PATH_GROUP,
		    IWX_RX_BAID_ALLOCATION_CONFIG_CMD):
		case IWX_WIDE_ID(IWX_MAC_CONF_GROUP,
		    IWX_SESSION_PROTECTION_CMD):
		case IWX_WIDE_ID(IWX_REGULATORY_AND_NVM_GROUP,
		    IWX_NVM_GET_INFO):
		case IWX_ADD_STA_KEY:
		case IWX_PHY_CONFIGURATION_CMD:
		case IWX_TX_ANT_CONFIGURATION_CMD:
		case IWX_ADD_STA:
		case IWX_MAC_CONTEXT_CMD:
		case IWX_REPLY_SF_CFG_CMD:
		case IWX_POWER_TABLE_CMD:
		case IWX_LTR_CONFIG:
		case IWX_PHY_CONTEXT_CMD:
		case IWX_BINDING_CONTEXT_CMD:
		case IWX_WIDE_ID(IWX_LONG_GROUP, IWX_SCAN_CFG_CMD):
		case IWX_WIDE_ID(IWX_LONG_GROUP, IWX_SCAN_REQ_UMAC):
		case IWX_WIDE_ID(IWX_LONG_GROUP, IWX_SCAN_ABORT_UMAC):
		case IWX_REPLY_BEACON_FILTERING_CMD:
		case IWX_MAC_PM_POWER_TABLE:
		case IWX_TIME_QUOTA_CMD:
		case IWX_REMOVE_STA:
		case IWX_TXPATH_FLUSH:
		case IWX_BT_CONFIG:
		case IWX_MCC_UPDATE_CMD:
		case IWX_TIME_EVENT_CMD:
		case IWX_STATISTICS_CMD:
		case IWX_SCD_QUEUE_CFG: {
			size_t pkt_len;

			if (sc->sc_cmd_resp_pkt[idx] == NULL)
				break;

			bus_dmamap_sync(sc->rxq.data_dmat, data->map,
			    BUS_DMASYNC_POSTREAD);

			pkt_len = sizeof(pkt->len_n_flags) +
			    iwx_rx_packet_len(pkt);

			if ((pkt->hdr.flags & IWX_CMD_FAILED_MSK) ||
			    pkt_len < sizeof(*pkt) ||
			    pkt_len > sc->sc_cmd_resp_len[idx]) {
				free(sc->sc_cmd_resp_pkt[idx], M_DEVBUF);
				sc->sc_cmd_resp_pkt[idx] = NULL;
				break;
			}

			bus_dmamap_sync(sc->rxq.data_dmat, data->map,
			    BUS_DMASYNC_POSTREAD);
			memcpy(sc->sc_cmd_resp_pkt[idx], pkt, pkt_len);
			break;
		}

		case IWX_INIT_COMPLETE_NOTIF:
			sc->sc_init_complete |= IWX_INIT_COMPLETE;
			wakeup(&sc->sc_init_complete);
			break;

		case IWX_SCAN_COMPLETE_UMAC: {
			DPRINTF(("%s: >>> IWX_SCAN_COMPLETE_UMAC\n", __func__));
			struct iwx_umac_scan_complete *notif __attribute__((unused));
			SYNC_RESP_STRUCT(notif, pkt);
			DPRINTF(("%s: scan complete notif->status=%d\n", __func__,
			    notif->status));
			ieee80211_runtask(&sc->sc_ic, &sc->sc_es_task);
			iwx_endscan(sc);
			break;
		}

		case IWX_SCAN_ITERATION_COMPLETE_UMAC: {
			DPRINTF(("%s: >>> IWX_SCAN_ITERATION_COMPLETE_UMAC\n",
			    __func__));
			struct iwx_umac_scan_iter_complete_notif *notif __attribute__((unused));
			SYNC_RESP_STRUCT(notif, pkt);
			DPRINTF(("%s: iter scan complete notif->status=%d\n", __func__,
			    notif->status));
			iwx_endscan(sc);
			break;
		}

		case IWX_MCC_CHUB_UPDATE_CMD: {
			struct iwx_mcc_chub_notif *notif;
			SYNC_RESP_STRUCT(notif, pkt);
			iwx_mcc_update(sc, notif);
			break;
		}

		case IWX_REPLY_ERROR: {
			struct iwx_error_resp *resp;
			SYNC_RESP_STRUCT(resp, pkt);
			printf("%s: firmware error 0x%x, cmd 0x%x\n",
				DEVNAME(sc), le32toh(resp->error_type),
				resp->cmd_id);
			break;
		}

		case IWX_TIME_EVENT_NOTIFICATION: {
			struct iwx_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 & IWX_TE_V2_NOTIF_HOST_EVENT_END)
				sc->sc_flags &= ~IWX_FLAG_TE_ACTIVE;
			break;
		}

		case IWX_WIDE_ID(IWX_MAC_CONF_GROUP,
		    IWX_SESSION_PROTECTION_NOTIF): {
			struct iwx_session_prot_notif *notif;
			uint32_t status, start, conf_id;

			SYNC_RESP_STRUCT(notif, pkt);

			status = le32toh(notif->status);
			start = le32toh(notif->start);
			conf_id = le32toh(notif->conf_id);
			/* Check for end of successful PROTECT_CONF_ASSOC. */
			if (status == 1 && start == 0 &&
			    conf_id == IWX_SESSION_PROTECT_CONF_ASSOC)
				sc->sc_flags &= ~IWX_FLAG_TE_ACTIVE;
			break;
		}

		case IWX_WIDE_ID(IWX_SYSTEM_GROUP,
		    IWX_FSEQ_VER_MISMATCH_NOTIFICATION):
		    break;

		/*
		 * Firmware versions 21 and 22 generate some DEBUG_LOG_MSG
		 * messages. Just ignore them for now.
		 */
		case IWX_DEBUG_LOG_MSG:
			break;

		case IWX_MCAST_FILTER_CMD:
			break;

		case IWX_WIDE_ID(IWX_DATA_PATH_GROUP, IWX_DQA_ENABLE_CMD):
			break;

		case IWX_WIDE_ID(IWX_SYSTEM_GROUP, IWX_SOC_CONFIGURATION_CMD):
			break;

		case IWX_WIDE_ID(IWX_SYSTEM_GROUP, IWX_INIT_EXTENDED_CFG_CMD):
			break;

		case IWX_WIDE_ID(IWX_REGULATORY_AND_NVM_GROUP,
		    IWX_NVM_ACCESS_COMPLETE):
			break;

		case IWX_WIDE_ID(IWX_DATA_PATH_GROUP, IWX_RX_NO_DATA_NOTIF):
			break; /* happens in monitor mode; ignore for now */

		case IWX_WIDE_ID(IWX_DATA_PATH_GROUP, IWX_TLC_MNG_CONFIG_CMD):
			break;

		case IWX_WIDE_ID(IWX_DATA_PATH_GROUP,
		    IWX_TLC_MNG_UPDATE_NOTIF): {
			struct iwx_tlc_update_notif *notif;
			SYNC_RESP_STRUCT(notif, pkt);
			(void)notif;
			if (iwx_rx_packet_payload_len(pkt) == sizeof(*notif))
				iwx_rs_update(sc, notif);
			break;
		}

		case IWX_WIDE_ID(IWX_DATA_PATH_GROUP, IWX_RLC_CONFIG_CMD):
			break;

		/* undocumented notification from iwx-ty-a0-gf-a0-77 image */
		case IWX_WIDE_ID(IWX_DATA_PATH_GROUP, 0xf8):
			break;

		case IWX_WIDE_ID(IWX_REGULATORY_AND_NVM_GROUP,
		    IWX_PNVM_INIT_COMPLETE):
			DPRINTF(("%s: IWX_PNVM_INIT_COMPLETE\n", __func__));
			sc->sc_init_complete |= IWX_PNVM_COMPLETE;
			wakeup(&sc->sc_init_complete);
			break;

		default:
			handled = 0;
			/* XXX wulf: Get rid of bluetooth-related spam */
			if ((code == 0xc2 && pkt->len_n_flags == 0x0000000c) ||
			    (code == 0xce && pkt->len_n_flags == 0x2000002c))
				break;
			printf("%s: unhandled firmware response 0x%x/0x%x "
			    "rx ring %d[%d]\n",
			    DEVNAME(sc), code, pkt->len_n_flags,
			    (qid & ~0x80), idx);
			break;
		}

		/*
		 * uCode sets bit 0x80 when it originates the notification,
		 * i.e. when the notification is not a direct response to a
		 * command sent by the driver.
		 * For example, uCode issues IWX_REPLY_RX when it sends a
		 * received frame to the driver.
		 */
		if (handled && !(qid & (1 << 7))) {
			iwx_cmd_done(sc, qid, idx, code);
		}

		offset += roundup(len, IWX_FH_RSCSR_FRAME_ALIGN);

		/* AX210 devices ship only one packet per Rx buffer. */
		if (sc->sc_device_family >= IWX_DEVICE_FAMILY_AX210)
			break;
	}

	if (m0 && m0 != data->m)
		m_freem(m0);
}

static void
iwx_notif_intr(struct iwx_softc *sc)
{
	struct mbuf m;
	uint16_t hw;

	bus_dmamap_sync(sc->rxq.stat_dma.tag, sc->rxq.stat_dma.map,
	    BUS_DMASYNC_POSTREAD);

	if (sc->sc_device_family >= IWX_DEVICE_FAMILY_AX210) {
		uint16_t *status = sc->rxq.stat_dma.vaddr;
		hw = le16toh(*status) & 0xfff;
	} else
		hw = le16toh(sc->rxq.stat->closed_rb_num) & 0xfff;
	hw &= (IWX_RX_MQ_RING_COUNT - 1);
	while (sc->rxq.cur != hw) {
		struct iwx_rx_data *data = &sc->rxq.data[sc->rxq.cur];

		bus_dmamap_sync(sc->rxq.data_dmat, data->map,
		BUS_DMASYNC_POSTREAD);

		iwx_rx_pkt(sc, data, &m);
		sc->rxq.cur = (sc->rxq.cur + 1) % IWX_RX_MQ_RING_COUNT;
	}

	/*
	 * Tell the firmware what we have processed.
	 * Seems like the hardware gets upset unless we align the write by 8??
	 */
	hw = (hw == 0) ? IWX_RX_MQ_RING_COUNT - 1 : hw - 1;
	IWX_WRITE(sc, IWX_RFH_Q0_FRBDCB_WIDX_TRG, hw & ~7);
}

#if 0
int
iwx_intr(void *arg)
{
	struct iwx_softc *sc = arg;
	struct ieee80211com *ic = &sc->sc_ic;
	struct ifnet *ifp = IC2IFP(ic);
	int r1, r2, rv = 0;

	IWX_WRITE(sc, IWX_CSR_INT_MASK, 0);

	if (sc->sc_flags & IWX_FLAG_USE_ICT) {
		uint32_t *ict = sc->ict_dma.vaddr;
		int tmp;

		tmp = htole32(ict[sc->ict_cur]);
		if (!tmp)
			goto out_ena;

		/*
		 * ok, there was something.  keep plowing until we have all.
		 */
		r1 = r2 = 0;
		while (tmp) {
			r1 |= tmp;
			ict[sc->ict_cur] = 0;
			sc->ict_cur = (sc->ict_cur+1) % IWX_ICT_COUNT;
			tmp = htole32(ict[sc->ict_cur]);
		}

		/* this is where the fun begins.  don't ask */
		if (r1 == 0xffffffff)
			r1 = 0;

		/* i am not expected to understand this */
		if (r1 & 0xc0000)
			r1 |= 0x8000;
		r1 = (0xff & r1) | ((0xff00 & r1) << 16);
	} else {
		r1 = IWX_READ(sc, IWX_CSR_INT);
		if (r1 == 0xffffffff || (r1 & 0xfffffff0) == 0xa5a5a5a0)
			goto out;
		r2 = IWX_READ(sc, IWX_CSR_FH_INT_STATUS);
	}
	if (r1 == 0 && r2 == 0) {
		goto out_ena;
	}

	IWX_WRITE(sc, IWX_CSR_INT, r1 | ~sc->sc_intmask);

	if (r1 & IWX_CSR_INT_BIT_ALIVE) {
#if 0
		int i;
		/* Firmware has now configured the RFH. */
		for (i = 0; i < IWX_RX_MQ_RING_COUNT; i++)
			iwx_update_rx_desc(sc, &sc->rxq, i);
#endif
		IWX_WRITE(sc, IWX_RFH_Q0_FRBDCB_WIDX_TRG, 8);
	}


	if (r1 & IWX_CSR_INT_BIT_RF_KILL) {
		iwx_check_rfkill(sc);
		rv = 1;
		goto out_ena;
	}

	if (r1 & IWX_CSR_INT_BIT_SW_ERR) {
		if (ifp->if_flags & IFF_DEBUG) {
			iwx_nic_error(sc);
			iwx_dump_driver_status(sc);
		}
		printf("%s: fatal firmware error\n", DEVNAME(sc));
		ieee80211_restart_all(ic);
		rv = 1;
		goto out;

	}

	if (r1 & IWX_CSR_INT_BIT_HW_ERR) {
		printf("%s: hardware error, stopping device \n", DEVNAME(sc));
		iwx_stop(sc);
		rv = 1;
		goto out;
	}

	/* firmware chunk loaded */
	if (r1 & IWX_CSR_INT_BIT_FH_TX) {
		IWX_WRITE(sc, IWX_CSR_FH_INT_STATUS, IWX_CSR_FH_INT_TX_MASK);

		sc->sc_fw_chunk_done = 1;
		wakeup(&sc->sc_fw);
	}

	if (r1 & (IWX_CSR_INT_BIT_FH_RX | IWX_CSR_INT_BIT_SW_RX |
	    IWX_CSR_INT_BIT_RX_PERIODIC)) {
		if (r1 & (IWX_CSR_INT_BIT_FH_RX | IWX_CSR_INT_BIT_SW_RX)) {
			IWX_WRITE(sc, IWX_CSR_FH_INT_STATUS, IWX_CSR_FH_INT_RX_MASK);
		}
		if (r1 & IWX_CSR_INT_BIT_RX_PERIODIC) {
			IWX_WRITE(sc, IWX_CSR_INT, IWX_CSR_INT_BIT_RX_PERIODIC);
		}

		/* Disable periodic interrupt; we use it as just a one-shot. */
		IWX_WRITE_1(sc, IWX_CSR_INT_PERIODIC_REG, IWX_CSR_INT_PERIODIC_DIS);

		/*
		 * Enable periodic interrupt in 8 msec only if we received
		 * real RX interrupt (instead of just periodic int), to catch
		 * any dangling Rx interrupt.  If it was just the periodic
		 * interrupt, there was no dangling Rx activity, and no need
		 * to extend the periodic interrupt; one-shot is enough.
		 */
		if (r1 & (IWX_CSR_INT_BIT_FH_RX | IWX_CSR_INT_BIT_SW_RX))
			IWX_WRITE_1(sc, IWX_CSR_INT_PERIODIC_REG,
			    IWX_CSR_INT_PERIODIC_ENA);

		iwx_notif_intr(sc);
	}

	rv = 1;

 out_ena:
	iwx_restore_interrupts(sc);
 out:
	return rv;
}
#endif

static void
iwx_intr_msix(void *arg)
{
	struct iwx_softc *sc = arg;
	struct ieee80211com *ic = &sc->sc_ic;
	uint32_t inta_fh, inta_hw;
	int vector = 0;

	IWX_LOCK(sc);

	inta_fh = IWX_READ(sc, IWX_CSR_MSIX_FH_INT_CAUSES_AD);
	inta_hw = IWX_READ(sc, IWX_CSR_MSIX_HW_INT_CAUSES_AD);
	IWX_WRITE(sc, IWX_CSR_MSIX_FH_INT_CAUSES_AD, inta_fh);
	IWX_WRITE(sc, IWX_CSR_MSIX_HW_INT_CAUSES_AD, inta_hw);
	inta_fh &= sc->sc_fh_mask;
	inta_hw &= sc->sc_hw_mask;

	if (inta_fh & IWX_MSIX_FH_INT_CAUSES_Q0 ||
	    inta_fh & IWX_MSIX_FH_INT_CAUSES_Q1) {
		iwx_notif_intr(sc);
	}

	/* firmware chunk loaded */
	if (inta_fh & IWX_MSIX_FH_INT_CAUSES_D2S_CH0_NUM) {
		sc->sc_fw_chunk_done = 1;
		wakeup(&sc->sc_fw);
	}

	if ((inta_fh & IWX_MSIX_FH_INT_CAUSES_FH_ERR) ||
	    (inta_hw & IWX_MSIX_HW_INT_CAUSES_REG_SW_ERR) ||
	    (inta_hw & IWX_MSIX_HW_INT_CAUSES_REG_SW_ERR_V2)) {
		if (sc->sc_debug) {
			iwx_nic_error(sc);
			iwx_dump_driver_status(sc);
		}
		printf("%s: fatal firmware error\n", DEVNAME(sc));
		ieee80211_restart_all(ic);
		goto out;
	}

	if (inta_hw & IWX_MSIX_HW_INT_CAUSES_REG_RF_KILL) {
		iwx_check_rfkill(sc);
	}

	if (inta_hw & IWX_MSIX_HW_INT_CAUSES_REG_HW_ERR) {
		printf("%s: hardware error, stopping device \n", DEVNAME(sc));
		sc->sc_flags |= IWX_FLAG_HW_ERR;
		iwx_stop(sc);
		goto out;
	}

	if (inta_hw & IWX_MSIX_HW_INT_CAUSES_REG_ALIVE) {
		IWX_DPRINTF(sc, IWX_DEBUG_TRACE,
		    "%s:%d WARNING: Skipping rx desc update\n",
		    __func__, __LINE__);
#if 0
		/*
		 * XXX-THJ: we don't have the dma segment handy. This is hacked
		 * out in the fc release, return to it if we ever get this
		 * warning.
		 */
		/* Firmware has now configured the RFH. */
		for (int i = 0; i < IWX_RX_MQ_RING_COUNT; i++)
			iwx_update_rx_desc(sc, &sc->rxq, i);
#endif
		IWX_WRITE(sc, IWX_RFH_Q0_FRBDCB_WIDX_TRG, 8);
	}

	/*
	 * Before sending the interrupt the HW disables it to prevent
	 * a nested interrupt. This is done by writing 1 to the corresponding
	 * bit in the mask register. After handling the interrupt, it should be
	 * re-enabled by clearing this bit. This register is defined as
	 * write 1 clear (W1C) register, meaning that it's being clear
	 * by writing 1 to the bit.
	 */
	IWX_WRITE(sc, IWX_CSR_MSIX_AUTOMASK_ST_AD, 1 << vector);
out:
	IWX_UNLOCK(sc);
	return;
}

/*
 * The device info table below contains device-specific config overrides.
 * The most important parameter derived from this table is the name of the
 * firmware image to load.
 *
 * The Linux iwlwifi driver uses an "old" and a "new" device info table.
 * The "old" table matches devices based on PCI vendor/product IDs only.
 * The "new" table extends this with various device parameters derived
 * from MAC type, and RF type.
 *
 * In iwlwifi "old" and "new" tables share the same array, where "old"
 * entries contain dummy values for data defined only for "new" entries.
 * As of 2022, Linux developers are still in the process of moving entries
 * from "old" to "new" style and it looks like this effort has stalled in
 * in some work-in-progress state for quite a while. Linux commits moving
 * entries from "old" to "new" have at times been reverted due to regressions.
 * Part of this complexity comes from iwlwifi supporting both iwm(4) and iwx(4)
 * devices in the same driver.
 *
 * Our table below contains mostly "new" entries declared in iwlwifi
 * with the _IWL_DEV_INFO() macro (with a leading underscore).
 * Other devices are matched based on PCI vendor/product ID as usual,
 * unless matching specific PCI subsystem vendor/product IDs is required.
 *
 * Some "old"-style entries are required to identify the firmware image to use.
 * Others might be used to print a specific marketing name into Linux dmesg,
 * but we can't be sure whether the corresponding devices would be matched
 * correctly in the absence of their entries. So we include them just in case.
 */

struct iwx_dev_info {
	uint16_t device;
	uint16_t subdevice;
	uint16_t mac_type;
	uint16_t rf_type;
	uint8_t mac_step;
	uint8_t rf_id;
	uint8_t no_160;
	uint8_t cores;
	uint8_t cdb;
	uint8_t jacket;
	const struct iwx_device_cfg *cfg;
};

#define _IWX_DEV_INFO(_device, _subdevice, _mac_type, _mac_step, _rf_type, \
		      _rf_id, _no_160, _cores, _cdb, _jacket, _cfg) \
	{ .device = (_device), .subdevice = (_subdevice), .cfg = &(_cfg),  \
	  .mac_type = _mac_type, .rf_type = _rf_type,	   \
	  .no_160 = _no_160, .cores = _cores, .rf_id = _rf_id,		   \
	  .mac_step = _mac_step, .cdb = _cdb, .jacket = _jacket }

#define IWX_DEV_INFO(_device, _subdevice, _cfg) \
	_IWX_DEV_INFO(_device, _subdevice, IWX_CFG_ANY, IWX_CFG_ANY,	   \
		      IWX_CFG_ANY, IWX_CFG_ANY, IWX_CFG_ANY, IWX_CFG_ANY,  \
		      IWX_CFG_ANY, IWX_CFG_ANY, _cfg)

/*
 * When adding entries to this table keep in mind that entries must
 * be listed in the same order as in the Linux driver. Code walks this
 * table backwards and uses the first matching entry it finds.
 * Device firmware must be available in fw_update(8).
 */
static const struct iwx_dev_info iwx_dev_info_table[] = {
	/* So with HR */
	IWX_DEV_INFO(0x2725, 0x0090, iwx_2ax_cfg_so_gf_a0),
	IWX_DEV_INFO(0x2725, 0x0020, iwx_2ax_cfg_ty_gf_a0),
	IWX_DEV_INFO(0x2725, 0x2020, iwx_2ax_cfg_ty_gf_a0),
	IWX_DEV_INFO(0x2725, 0x0024, iwx_2ax_cfg_ty_gf_a0),
	IWX_DEV_INFO(0x2725, 0x0310, iwx_2ax_cfg_ty_gf_a0),
	IWX_DEV_INFO(0x2725, 0x0510, iwx_2ax_cfg_ty_gf_a0),
	IWX_DEV_INFO(0x2725, 0x0A10, iwx_2ax_cfg_ty_gf_a0),
	IWX_DEV_INFO(0x2725, 0xE020, iwx_2ax_cfg_ty_gf_a0),
	IWX_DEV_INFO(0x2725, 0xE024, iwx_2ax_cfg_ty_gf_a0),
	IWX_DEV_INFO(0x2725, 0x4020, iwx_2ax_cfg_ty_gf_a0),
	IWX_DEV_INFO(0x2725, 0x6020, iwx_2ax_cfg_ty_gf_a0),
	IWX_DEV_INFO(0x2725, 0x6024, iwx_2ax_cfg_ty_gf_a0),
	IWX_DEV_INFO(0x2725, 0x1673, iwx_2ax_cfg_ty_gf_a0), /* killer_1675w */
	IWX_DEV_INFO(0x2725, 0x1674, iwx_2ax_cfg_ty_gf_a0), /* killer_1675x */
	IWX_DEV_INFO(0x51f0, 0x1691, iwx_2ax_cfg_so_gf4_a0), /* killer_1690s */
	IWX_DEV_INFO(0x51f0, 0x1692, iwx_2ax_cfg_so_gf4_a0), /* killer_1690i */
	IWX_DEV_INFO(0x51f1, 0x1691, iwx_2ax_cfg_so_gf4_a0),
	IWX_DEV_INFO(0x51f1, 0x1692, iwx_2ax_cfg_so_gf4_a0),
	IWX_DEV_INFO(0x54f0, 0x1691, iwx_2ax_cfg_so_gf4_a0), /* killer_1690s */
	IWX_DEV_INFO(0x54f0, 0x1692, iwx_2ax_cfg_so_gf4_a0), /* killer_1690i */
	IWX_DEV_INFO(0x7a70, 0x0090, iwx_2ax_cfg_so_gf_a0_long),
	IWX_DEV_INFO(0x7a70, 0x0098, iwx_2ax_cfg_so_gf_a0_long),
	IWX_DEV_INFO(0x7a70, 0x00b0, iwx_2ax_cfg_so_gf4_a0_long),
	IWX_DEV_INFO(0x7a70, 0x0310, iwx_2ax_cfg_so_gf_a0_long),
	IWX_DEV_INFO(0x7a70, 0x0510, iwx_2ax_cfg_so_gf_a0_long),
	IWX_DEV_INFO(0x7a70, 0x0a10, iwx_2ax_cfg_so_gf_a0_long),
	IWX_DEV_INFO(0x7af0, 0x0090, iwx_2ax_cfg_so_gf_a0),
	IWX_DEV_INFO(0x7af0, 0x0098, iwx_2ax_cfg_so_gf_a0),
	IWX_DEV_INFO(0x7af0, 0x00b0, iwx_2ax_cfg_so_gf4_a0),
	IWX_DEV_INFO(0x7a70, 0x1691, iwx_2ax_cfg_so_gf4_a0), /* killer_1690s */
	IWX_DEV_INFO(0x7a70, 0x1692, iwx_2ax_cfg_so_gf4_a0), /* killer_1690i */
	IWX_DEV_INFO(0x7af0, 0x0310, iwx_2ax_cfg_so_gf_a0),
	IWX_DEV_INFO(0x7af0, 0x0510, iwx_2ax_cfg_so_gf_a0),
	IWX_DEV_INFO(0x7af0, 0x0a10, iwx_2ax_cfg_so_gf_a0),
	IWX_DEV_INFO(0x7f70, 0x1691, iwx_2ax_cfg_so_gf4_a0), /* killer_1690s */
	IWX_DEV_INFO(0x7f70, 0x1692, iwx_2ax_cfg_so_gf4_a0), /* killer_1690i */

	/* So with GF2 */
	IWX_DEV_INFO(0x2726, 0x1671, iwx_2ax_cfg_so_gf_a0), /* killer_1675s */
	IWX_DEV_INFO(0x2726, 0x1672, iwx_2ax_cfg_so_gf_a0), /* killer_1675i */
	IWX_DEV_INFO(0x51f0, 0x1671, iwx_2ax_cfg_so_gf_a0), /* killer_1675s */
	IWX_DEV_INFO(0x51f0, 0x1672, iwx_2ax_cfg_so_gf_a0), /* killer_1675i */
	IWX_DEV_INFO(0x54f0, 0x1671, iwx_2ax_cfg_so_gf_a0), /* killer_1675s */
	IWX_DEV_INFO(0x54f0, 0x1672, iwx_2ax_cfg_so_gf_a0), /* killer_1675i */
	IWX_DEV_INFO(0x7a70, 0x1671, iwx_2ax_cfg_so_gf_a0), /* killer_1675s */
	IWX_DEV_INFO(0x7a70, 0x1672, iwx_2ax_cfg_so_gf_a0), /* killer_1675i */
	IWX_DEV_INFO(0x7af0, 0x1671, iwx_2ax_cfg_so_gf_a0), /* killer_1675s */
	IWX_DEV_INFO(0x7af0, 0x1672, iwx_2ax_cfg_so_gf_a0), /* killer_1675i */
	IWX_DEV_INFO(0x7f70, 0x1671, iwx_2ax_cfg_so_gf_a0), /* killer_1675s */
	IWX_DEV_INFO(0x7f70, 0x1672, iwx_2ax_cfg_so_gf_a0), /* killer_1675i */

	/* Qu with Jf, C step */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_QU, IWX_SILICON_C_STEP,
		      IWX_CFG_RF_TYPE_JF1, IWX_CFG_RF_ID_JF1,
		      IWX_CFG_160, IWX_CFG_CORES_BT, IWX_CFG_NO_CDB,
		      IWX_CFG_ANY, iwx_9560_qu_c0_jf_b0_cfg), /* 9461_160 */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_QU, IWX_SILICON_C_STEP,
		      IWX_CFG_RF_TYPE_JF1, IWX_CFG_RF_ID_JF1,
		      IWX_CFG_NO_160, IWX_CFG_CORES_BT, IWX_CFG_NO_CDB,
		      IWX_CFG_ANY, iwx_9560_qu_c0_jf_b0_cfg), /* iwl9461 */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_QU, IWX_SILICON_C_STEP,
		      IWX_CFG_RF_TYPE_JF1, IWX_CFG_RF_ID_JF1_DIV,
		      IWX_CFG_160, IWX_CFG_CORES_BT, IWX_CFG_NO_CDB,
		      IWX_CFG_ANY, iwx_9560_qu_c0_jf_b0_cfg), /* 9462_160 */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_QU, IWX_SILICON_C_STEP,
		      IWX_CFG_RF_TYPE_JF1, IWX_CFG_RF_ID_JF1_DIV,
		      IWX_CFG_NO_160, IWX_CFG_CORES_BT, IWX_CFG_NO_CDB,
		      IWX_CFG_ANY, iwx_9560_qu_c0_jf_b0_cfg), /* 9462 */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_QU, IWX_SILICON_C_STEP,
		      IWX_CFG_RF_TYPE_JF2, IWX_CFG_RF_ID_JF,
		      IWX_CFG_160, IWX_CFG_CORES_BT, IWX_CFG_NO_CDB,
		      IWX_CFG_ANY, iwx_9560_qu_c0_jf_b0_cfg), /* 9560_160 */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_QU, IWX_SILICON_C_STEP,
		      IWX_CFG_RF_TYPE_JF2, IWX_CFG_RF_ID_JF,
		      IWX_CFG_NO_160, IWX_CFG_CORES_BT, IWX_CFG_NO_CDB,
		      IWX_CFG_ANY, iwx_9560_qu_c0_jf_b0_cfg), /* 9560 */
	_IWX_DEV_INFO(IWX_CFG_ANY, 0x1551,
		      IWX_CFG_MAC_TYPE_QU, IWX_SILICON_C_STEP,
		      IWX_CFG_RF_TYPE_JF2, IWX_CFG_RF_ID_JF,
		      IWX_CFG_160, IWX_CFG_CORES_BT, IWX_CFG_NO_CDB,
		      IWX_CFG_ANY,
		      iwx_9560_qu_c0_jf_b0_cfg), /* 9560_killer_1550s */
	_IWX_DEV_INFO(IWX_CFG_ANY, 0x1552,
		      IWX_CFG_MAC_TYPE_QU, IWX_SILICON_C_STEP,
		      IWX_CFG_RF_TYPE_JF2, IWX_CFG_RF_ID_JF,
		      IWX_CFG_NO_160, IWX_CFG_CORES_BT, IWX_CFG_NO_CDB,
		      IWX_CFG_ANY,
		      iwx_9560_qu_c0_jf_b0_cfg), /* 9560_killer_1550i */

	/* QuZ with Jf */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_QUZ, IWX_CFG_ANY,
		      IWX_CFG_RF_TYPE_JF2, IWX_CFG_RF_ID_JF,
		      IWX_CFG_160, IWX_CFG_CORES_BT, IWX_CFG_NO_CDB,
		      IWX_CFG_ANY, iwx_9560_quz_a0_jf_b0_cfg), /* 9461_160 */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_QUZ, IWX_CFG_ANY,
		      IWX_CFG_RF_TYPE_JF2, IWX_CFG_RF_ID_JF,
		      IWX_CFG_NO_160, IWX_CFG_CORES_BT, IWX_CFG_NO_CDB,
		      IWX_CFG_ANY, iwx_9560_quz_a0_jf_b0_cfg), /* 9461 */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_QUZ, IWX_CFG_ANY,
		      IWX_CFG_RF_TYPE_JF1, IWX_CFG_RF_ID_JF1_DIV,
		      IWX_CFG_160, IWX_CFG_CORES_BT, IWX_CFG_NO_CDB,
		      IWX_CFG_ANY, iwx_9560_quz_a0_jf_b0_cfg), /* 9462_160 */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_QUZ, IWX_CFG_ANY,
		      IWX_CFG_RF_TYPE_JF1, IWX_CFG_RF_ID_JF1_DIV,
		      IWX_CFG_NO_160, IWX_CFG_CORES_BT, IWX_CFG_NO_CDB,
		      IWX_CFG_ANY, iwx_9560_quz_a0_jf_b0_cfg), /* 9462 */
	_IWX_DEV_INFO(IWX_CFG_ANY, 0x1551,
		      IWX_CFG_MAC_TYPE_QUZ, IWX_CFG_ANY,
		      IWX_CFG_RF_TYPE_JF2, IWX_CFG_RF_ID_JF,
		      IWX_CFG_160, IWX_CFG_CORES_BT, IWX_CFG_NO_CDB,
		      IWX_CFG_ANY,
		      iwx_9560_quz_a0_jf_b0_cfg), /* killer_1550s */
	_IWX_DEV_INFO(IWX_CFG_ANY, 0x1552,
		      IWX_CFG_MAC_TYPE_QUZ, IWX_CFG_ANY,
		      IWX_CFG_RF_TYPE_JF2, IWX_CFG_RF_ID_JF,
		      IWX_CFG_NO_160, IWX_CFG_CORES_BT, IWX_CFG_NO_CDB,
		      IWX_CFG_ANY,
		      iwx_9560_quz_a0_jf_b0_cfg), /* 9560_killer_1550i */

	/* Qu with Hr, B step */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_QU, IWX_SILICON_B_STEP,
		      IWX_CFG_RF_TYPE_HR1, IWX_CFG_ANY,
		      IWX_CFG_ANY, IWX_CFG_ANY, IWX_CFG_NO_CDB, IWX_CFG_ANY,
		      iwx_qu_b0_hr1_b0), /* AX101 */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_QU, IWX_SILICON_B_STEP,
		      IWX_CFG_RF_TYPE_HR2, IWX_CFG_ANY,
		      IWX_CFG_NO_160, IWX_CFG_ANY, IWX_CFG_NO_CDB, IWX_CFG_ANY,
		      iwx_qu_b0_hr_b0), /* AX203 */

	/* Qu with Hr, C step */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_QU, IWX_SILICON_C_STEP,
		      IWX_CFG_RF_TYPE_HR1, IWX_CFG_ANY,
		      IWX_CFG_ANY, IWX_CFG_ANY, IWX_CFG_NO_CDB, IWX_CFG_ANY,
		      iwx_qu_c0_hr1_b0), /* AX101 */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_QU, IWX_SILICON_C_STEP,
		      IWX_CFG_RF_TYPE_HR2, IWX_CFG_ANY,
		      IWX_CFG_NO_160, IWX_CFG_ANY, IWX_CFG_NO_CDB, IWX_CFG_ANY,
		      iwx_qu_c0_hr_b0), /* AX203 */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_QU, IWX_SILICON_C_STEP,
		      IWX_CFG_RF_TYPE_HR2, IWX_CFG_ANY,
		      IWX_CFG_160, IWX_CFG_ANY, IWX_CFG_NO_CDB, IWX_CFG_ANY,
		      iwx_qu_c0_hr_b0), /* AX201 */

	/* QuZ with Hr */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_QUZ, IWX_CFG_ANY,
		      IWX_CFG_RF_TYPE_HR1, IWX_CFG_ANY,
		      IWX_CFG_ANY, IWX_CFG_ANY, IWX_CFG_NO_CDB, IWX_CFG_ANY,
		      iwx_quz_a0_hr1_b0), /* AX101 */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_QUZ, IWX_SILICON_B_STEP,
		      IWX_CFG_RF_TYPE_HR2, IWX_CFG_ANY,
		      IWX_CFG_NO_160, IWX_CFG_ANY, IWX_CFG_NO_CDB, IWX_CFG_ANY,
		      iwx_cfg_quz_a0_hr_b0), /* AX203 */

	/* SoF with JF2 */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_SOF, IWX_CFG_ANY,
		      IWX_CFG_RF_TYPE_JF2, IWX_CFG_RF_ID_JF,
		      IWX_CFG_160, IWX_CFG_CORES_BT, IWX_CFG_NO_CDB,
		      IWX_CFG_ANY, iwx_2ax_cfg_so_jf_b0), /* 9560_160 */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_SOF, IWX_CFG_ANY,
		      IWX_CFG_RF_TYPE_JF2, IWX_CFG_RF_ID_JF,
		      IWX_CFG_NO_160, IWX_CFG_CORES_BT, IWX_CFG_NO_CDB,
		      IWX_CFG_ANY, iwx_2ax_cfg_so_jf_b0), /* 9560 */

	/* SoF with JF */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_SOF, IWX_CFG_ANY,
		      IWX_CFG_RF_TYPE_JF1, IWX_CFG_RF_ID_JF1,
		      IWX_CFG_160, IWX_CFG_CORES_BT, IWX_CFG_NO_CDB,
		      IWX_CFG_ANY, iwx_2ax_cfg_so_jf_b0), /* 9461_160 */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_SOF, IWX_CFG_ANY,
		      IWX_CFG_RF_TYPE_JF1, IWX_CFG_RF_ID_JF1_DIV,
		      IWX_CFG_160, IWX_CFG_CORES_BT, IWX_CFG_NO_CDB,
		      IWX_CFG_ANY, iwx_2ax_cfg_so_jf_b0), /* 9462_160 */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_SOF, IWX_CFG_ANY,
		      IWX_CFG_RF_TYPE_JF1, IWX_CFG_RF_ID_JF1,
		      IWX_CFG_NO_160, IWX_CFG_CORES_BT, IWX_CFG_NO_CDB,
		      IWX_CFG_ANY, iwx_2ax_cfg_so_jf_b0), /* 9461_name */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_SOF, IWX_CFG_ANY,
		      IWX_CFG_RF_TYPE_JF1, IWX_CFG_RF_ID_JF1_DIV,
		      IWX_CFG_NO_160, IWX_CFG_CORES_BT, IWX_CFG_NO_CDB,
		      IWX_CFG_ANY, iwx_2ax_cfg_so_jf_b0), /* 9462 */

	/* So with Hr */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_SO, IWX_CFG_ANY,
		      IWX_CFG_RF_TYPE_HR2, IWX_CFG_ANY,
		      IWX_CFG_NO_160, IWX_CFG_ANY, IWX_CFG_NO_CDB, IWX_CFG_ANY,
		      iwx_cfg_so_a0_hr_b0), /* AX203 */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_SO, IWX_CFG_ANY,
		      IWX_CFG_RF_TYPE_HR1, IWX_CFG_ANY,
		      IWX_CFG_160, IWX_CFG_ANY, IWX_CFG_NO_CDB, IWX_CFG_ANY,
		      iwx_cfg_so_a0_hr_b0), /* ax101 */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_SO, IWX_CFG_ANY,
		      IWX_CFG_RF_TYPE_HR2, IWX_CFG_ANY,
		      IWX_CFG_160, IWX_CFG_ANY, IWX_CFG_NO_CDB, IWX_CFG_ANY,
		      iwx_cfg_so_a0_hr_b0), /* ax201 */

	/* So-F with Hr */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_SOF, IWX_CFG_ANY,
		      IWX_CFG_RF_TYPE_HR2, IWX_CFG_ANY,
		      IWX_CFG_NO_160, IWX_CFG_ANY, IWX_CFG_NO_CDB, IWX_CFG_ANY,
		      iwx_cfg_so_a0_hr_b0), /* AX203 */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_SOF, IWX_CFG_ANY,
		      IWX_CFG_RF_TYPE_HR1, IWX_CFG_ANY,
		      IWX_CFG_160, IWX_CFG_ANY, IWX_CFG_NO_CDB, IWX_CFG_ANY,
		      iwx_cfg_so_a0_hr_b0), /* AX101 */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_SOF, IWX_CFG_ANY,
		      IWX_CFG_RF_TYPE_HR2, IWX_CFG_ANY,
		      IWX_CFG_160, IWX_CFG_ANY, IWX_CFG_NO_CDB, IWX_CFG_ANY,
		      iwx_cfg_so_a0_hr_b0), /* AX201 */

	/* So-F with GF */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_SOF, IWX_CFG_ANY,
		      IWX_CFG_RF_TYPE_GF, IWX_CFG_ANY,
		      IWX_CFG_160, IWX_CFG_ANY, IWX_CFG_NO_CDB, IWX_CFG_ANY,
		      iwx_2ax_cfg_so_gf_a0), /* AX211 */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_SOF, IWX_CFG_ANY,
		      IWX_CFG_RF_TYPE_GF, IWX_CFG_ANY,
		      IWX_CFG_160, IWX_CFG_ANY, IWX_CFG_CDB, IWX_CFG_ANY,
		      iwx_2ax_cfg_so_gf4_a0), /* AX411 */

	/* So with GF */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_SO, IWX_CFG_ANY,
		      IWX_CFG_RF_TYPE_GF, IWX_CFG_ANY,
		      IWX_CFG_160, IWX_CFG_ANY, IWX_CFG_NO_CDB, IWX_CFG_ANY,
		      iwx_2ax_cfg_so_gf_a0), /* AX211 */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_SO, IWX_CFG_ANY,
		      IWX_CFG_RF_TYPE_GF, IWX_CFG_ANY,
		      IWX_CFG_160, IWX_CFG_ANY, IWX_CFG_CDB, IWX_CFG_ANY,
		      iwx_2ax_cfg_so_gf4_a0), /* AX411 */

	/* So with JF2 */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_SO, IWX_CFG_ANY,
		      IWX_CFG_RF_TYPE_JF2, IWX_CFG_RF_ID_JF,
		      IWX_CFG_160, IWX_CFG_CORES_BT, IWX_CFG_NO_CDB,
		      IWX_CFG_ANY, iwx_2ax_cfg_so_jf_b0), /* 9560_160 */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_SO, IWX_CFG_ANY,
		      IWX_CFG_RF_TYPE_JF2, IWX_CFG_RF_ID_JF,
		      IWX_CFG_NO_160, IWX_CFG_CORES_BT, IWX_CFG_NO_CDB,
		      IWX_CFG_ANY, iwx_2ax_cfg_so_jf_b0), /* 9560 */

	/* So with JF */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_SO, IWX_CFG_ANY,
		      IWX_CFG_RF_TYPE_JF1, IWX_CFG_RF_ID_JF1,
		      IWX_CFG_160, IWX_CFG_CORES_BT, IWX_CFG_NO_CDB,
		      IWX_CFG_ANY, iwx_2ax_cfg_so_jf_b0), /* 9461_160 */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_SO, IWX_CFG_ANY,
		      IWX_CFG_RF_TYPE_JF1, IWX_CFG_RF_ID_JF1_DIV,
		      IWX_CFG_160, IWX_CFG_CORES_BT, IWX_CFG_NO_CDB,
		      IWX_CFG_ANY, iwx_2ax_cfg_so_jf_b0), /* 9462_160 */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_SO, IWX_CFG_ANY,
		      IWX_CFG_RF_TYPE_JF1, IWX_CFG_RF_ID_JF1,
		      IWX_CFG_NO_160, IWX_CFG_CORES_BT, IWX_CFG_NO_CDB,
		      IWX_CFG_ANY, iwx_2ax_cfg_so_jf_b0), /* iwl9461 */
	_IWX_DEV_INFO(IWX_CFG_ANY, IWX_CFG_ANY,
		      IWX_CFG_MAC_TYPE_SO, IWX_CFG_ANY,
		      IWX_CFG_RF_TYPE_JF1, IWX_CFG_RF_ID_JF1_DIV,
		      IWX_CFG_NO_160, IWX_CFG_CORES_BT, IWX_CFG_NO_CDB,
		      IWX_CFG_ANY, iwx_2ax_cfg_so_jf_b0), /* 9462 */
};

static int
iwx_preinit(struct iwx_softc *sc)
{
	struct ieee80211com *ic = &sc->sc_ic;
	int err;

	err = iwx_prepare_card_hw(sc);
	if (err) {
		printf("%s: could not initialize hardware\n", DEVNAME(sc));
		return err;
	}

	if (sc->attached) {
		return 0;
	}

	err = iwx_start_hw(sc);
	if (err) {
		printf("%s: could not initialize hardware\n", DEVNAME(sc));
		return err;
	}

	err = iwx_run_init_mvm_ucode(sc, 1);
	iwx_stop_device(sc);
	if (err) {
		printf("%s: failed to stop device\n", DEVNAME(sc));
		return err;
	}

	/* Print version info and MAC address on first successful fw load. */
	sc->attached = 1;
	if (sc->sc_pnvm_ver) {
		printf("%s: hw rev 0x%x, fw %s, pnvm %08x, "
		    "address %s\n",
		    DEVNAME(sc), sc->sc_hw_rev & IWX_CSR_HW_REV_TYPE_MSK,
		    sc->sc_fwver, sc->sc_pnvm_ver,
		    ether_sprintf(sc->sc_nvm.hw_addr));
	} else {
		printf("%s: hw rev 0x%x, fw %s, address %s\n",
		    DEVNAME(sc), sc->sc_hw_rev & IWX_CSR_HW_REV_TYPE_MSK,
		    sc->sc_fwver, ether_sprintf(sc->sc_nvm.hw_addr));
	}

	/* not all hardware can do 5GHz band */
	if (!sc->sc_nvm.sku_cap_band_52GHz_enable)
		memset(&ic->ic_sup_rates[IEEE80211_MODE_11A], 0,
		    sizeof(ic->ic_sup_rates[IEEE80211_MODE_11A]));

	return 0;
}

static void
iwx_attach_hook(void *self)
{
	struct iwx_softc *sc = (void *)self;
	struct ieee80211com *ic = &sc->sc_ic;
	int err;

	IWX_LOCK(sc);
	err = iwx_preinit(sc);
	IWX_UNLOCK(sc);
	if (err != 0)
		goto out;

	iwx_init_channel_map(ic, IEEE80211_CHAN_MAX, &ic->ic_nchans,
	    ic->ic_channels);

	ieee80211_ifattach(ic);
	ic->ic_vap_create = iwx_vap_create;
	ic->ic_vap_delete = iwx_vap_delete;
	ic->ic_raw_xmit = iwx_raw_xmit;
	ic->ic_node_alloc = iwx_node_alloc;
	ic->ic_scan_start = iwx_scan_start;
	ic->ic_scan_end = iwx_scan_end;
	ic->ic_update_mcast = iwx_update_mcast;
	ic->ic_getradiocaps = iwx_init_channel_map;

	ic->ic_set_channel = iwx_set_channel;
	ic->ic_scan_curchan = iwx_scan_curchan;
	ic->ic_scan_mindwell = iwx_scan_mindwell;
	ic->ic_wme.wme_update = iwx_wme_update;
	ic->ic_parent = iwx_parent;
	ic->ic_transmit = iwx_transmit;

	sc->sc_ampdu_rx_start = ic->ic_ampdu_rx_start;
	ic->ic_ampdu_rx_start = iwx_ampdu_rx_start;
	sc->sc_ampdu_rx_stop = ic->ic_ampdu_rx_stop;
	ic->ic_ampdu_rx_stop = iwx_ampdu_rx_stop;

	sc->sc_addba_request = ic->ic_addba_request;
	ic->ic_addba_request = iwx_addba_request;
	sc->sc_addba_response = ic->ic_addba_response;
	ic->ic_addba_response = iwx_addba_response;

	iwx_radiotap_attach(sc);
	ieee80211_announce(ic);
out:
	config_intrhook_disestablish(&sc->sc_preinit_hook);
}

const struct iwx_device_cfg *
iwx_find_device_cfg(struct iwx_softc *sc)
{
	uint16_t sdev_id, mac_type, rf_type;
	uint8_t mac_step, cdb, jacket, rf_id, no_160, cores;
	int i;

	sdev_id = pci_get_subdevice(sc->sc_dev);
	mac_type = IWX_CSR_HW_REV_TYPE(sc->sc_hw_rev);
	mac_step = IWX_CSR_HW_REV_STEP(sc->sc_hw_rev << 2);
	rf_type = IWX_CSR_HW_RFID_TYPE(sc->sc_hw_rf_id);
	cdb = IWX_CSR_HW_RFID_IS_CDB(sc->sc_hw_rf_id);
	jacket = IWX_CSR_HW_RFID_IS_JACKET(sc->sc_hw_rf_id);

	rf_id = IWX_SUBDEVICE_RF_ID(sdev_id);
	no_160 = IWX_SUBDEVICE_NO_160(sdev_id);
	cores = IWX_SUBDEVICE_CORES(sdev_id);

	for (i = nitems(iwx_dev_info_table) - 1; i >= 0; i--) {
		 const struct iwx_dev_info *dev_info = &iwx_dev_info_table[i];

		if (dev_info->device != (uint16_t)IWX_CFG_ANY &&
		    dev_info->device != sc->sc_pid)
			continue;

		if (dev_info->subdevice != (uint16_t)IWX_CFG_ANY &&
		    dev_info->subdevice != sdev_id)
			continue;

		if (dev_info->mac_type != (uint16_t)IWX_CFG_ANY &&
		    dev_info->mac_type != mac_type)
			continue;

		if (dev_info->mac_step != (uint8_t)IWX_CFG_ANY &&
		    dev_info->mac_step != mac_step)
			continue;

		if (dev_info->rf_type != (uint16_t)IWX_CFG_ANY &&
		    dev_info->rf_type != rf_type)
			continue;

		if (dev_info->cdb != (uint8_t)IWX_CFG_ANY &&
		    dev_info->cdb != cdb)
			continue;

		if (dev_info->jacket != (uint8_t)IWX_CFG_ANY &&
		    dev_info->jacket != jacket)
			continue;

		if (dev_info->rf_id != (uint8_t)IWX_CFG_ANY &&
		    dev_info->rf_id != rf_id)
			continue;

		if (dev_info->no_160 != (uint8_t)IWX_CFG_ANY &&
		    dev_info->no_160 != no_160)
			continue;

		if (dev_info->cores != (uint8_t)IWX_CFG_ANY &&
		    dev_info->cores != cores)
			continue;

		return dev_info->cfg;
	}

	return NULL;
}

static int
iwx_probe(device_t dev)
{
	int i;

	for (i = 0; i < nitems(iwx_devices); i++) {
		if (pci_get_vendor(dev) == PCI_VENDOR_INTEL &&
		    pci_get_device(dev) == iwx_devices[i].device) {
			device_set_desc(dev, iwx_devices[i].name);

			/*
			 * Due to significant existing deployments using
			 * iwlwifi lower the priority of iwx.
			 *
			 * This inverts the advice in bus.h where drivers
			 * supporting newer hardware should return
			 * BUS_PROBE_DEFAULT and drivers for older devices
			 * return BUS_PROBE_LOW_PRIORITY.
			 *
			 */
			return (BUS_PROBE_LOW_PRIORITY);
		}
	}

	return (ENXIO);
}

static int
iwx_attach(device_t dev)
{
	struct iwx_softc *sc = device_get_softc(dev);
	struct ieee80211com *ic = &sc->sc_ic;
	const struct iwx_device_cfg *cfg;
	int err;
	int txq_i, i, j;
	size_t ctxt_info_size;
	int rid;
	int count;
	int error;
	sc->sc_dev = dev;
	sc->sc_pid = pci_get_device(dev);
	sc->sc_dmat = bus_get_dma_tag(sc->sc_dev);

	TASK_INIT(&sc->sc_es_task, 0, iwx_endscan_cb, sc);
	IWX_LOCK_INIT(sc);
	mbufq_init(&sc->sc_snd, ifqmaxlen);
	TASK_INIT(&sc->ba_rx_task, 0, iwx_ba_rx_task, sc);
	TASK_INIT(&sc->ba_tx_task, 0, iwx_ba_tx_task, sc);
	sc->sc_tq = taskqueue_create("iwm_taskq", M_WAITOK,
	    taskqueue_thread_enqueue, &sc->sc_tq);
	error = taskqueue_start_threads(&sc->sc_tq, 1, 0, "iwm_taskq");
	if (error != 0) {
		device_printf(dev, "can't start taskq thread, error %d\n",
		    error);
		return (ENXIO);
	}

	pci_find_cap(dev, PCIY_EXPRESS, &sc->sc_cap_off);
	if (sc->sc_cap_off == 0) {
		device_printf(dev, "PCIe capability structure not found!\n");
		return (ENXIO);
	}

	/*
	 * We disable the RETRY_TIMEOUT register (0x41) to keep
	 * PCI Tx retries from interfering with C3 CPU state.
	 */
	pci_write_config(dev, PCI_CFG_RETRY_TIMEOUT, 0x00, 1);

	if (pci_msix_count(dev)) {
		sc->sc_msix = 1;
	} else {
		device_printf(dev, "no MSI-X found\n");
		return (ENXIO);
	}

	pci_enable_busmaster(dev);
	rid = PCIR_BAR(0);
	sc->sc_mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
	    RF_ACTIVE);
	if (sc->sc_mem == NULL) {
		device_printf(sc->sc_dev, "can't map mem space\n");
		return (ENXIO);
	}
	sc->sc_st = rman_get_bustag(sc->sc_mem);
	sc->sc_sh = rman_get_bushandle(sc->sc_mem);

	count = 1;
	rid = 0;
	if (pci_alloc_msix(dev, &count) == 0)
		rid = 1;
	DPRINTF(("%s: count=%d\n", __func__, count));
	sc->sc_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE |
	    (rid != 0 ? 0 : RF_SHAREABLE));
	if (sc->sc_irq == NULL) {
		device_printf(dev, "can't map interrupt\n");
		return (ENXIO);
	}
	error = bus_setup_intr(dev, sc->sc_irq, INTR_TYPE_NET | INTR_MPSAFE,
	    NULL, iwx_intr_msix, sc, &sc->sc_ih);
	if (error != 0) {
		device_printf(dev, "can't establish interrupt\n");
		return (ENXIO);
	}

	/* Clear pending interrupts. */
	IWX_WRITE(sc, IWX_CSR_INT_MASK, 0);
	IWX_WRITE(sc, IWX_CSR_INT, ~0);
	IWX_WRITE(sc, IWX_CSR_FH_INT_STATUS, ~0);

	sc->sc_hw_rev = IWX_READ(sc, IWX_CSR_HW_REV);
	DPRINTF(("%s: sc->sc_hw_rev=%d\n", __func__, sc->sc_hw_rev));
	sc->sc_hw_rf_id = IWX_READ(sc, IWX_CSR_HW_RF_ID);
	DPRINTF(("%s: sc->sc_hw_rf_id =%d\n", __func__, sc->sc_hw_rf_id));

	/*
	 * In the 8000 HW family the format of the 4 bytes of CSR_HW_REV have
	 * changed, and now the revision step also includes bit 0-1 (no more
	 * "dash" value). To keep hw_rev backwards compatible - we'll store it
	 * in the old format.
	 */
	sc->sc_hw_rev = (sc->sc_hw_rev & 0xfff0) |
			(IWX_CSR_HW_REV_STEP(sc->sc_hw_rev << 2) << 2);

	switch (sc->sc_pid) {
	case PCI_PRODUCT_INTEL_WL_22500_1:
		sc->sc_fwname = IWX_CC_A_FW;
		sc->sc_device_family = IWX_DEVICE_FAMILY_22000;
		sc->sc_integrated = 0;
		sc->sc_ltr_delay = IWX_SOC_FLAGS_LTR_APPLY_DELAY_NONE;
		sc->sc_low_latency_xtal = 0;
		sc->sc_xtal_latency = 0;
		sc->sc_tx_with_siso_diversity = 0;
		sc->sc_uhb_supported = 0;
		break;
	case PCI_PRODUCT_INTEL_WL_22500_2:
	case PCI_PRODUCT_INTEL_WL_22500_5:
		/* These devices should be QuZ only. */
		if (sc->sc_hw_rev != IWX_CSR_HW_REV_TYPE_QUZ) {
			device_printf(dev, "unsupported AX201 adapter\n");
			return (ENXIO);
		}
		sc->sc_fwname = IWX_QUZ_A_HR_B_FW;
		sc->sc_device_family = IWX_DEVICE_FAMILY_22000;
		sc->sc_integrated = 1;
		sc->sc_ltr_delay = IWX_SOC_FLAGS_LTR_APPLY_DELAY_200;
		sc->sc_low_latency_xtal = 0;
		sc->sc_xtal_latency = 500;
		sc->sc_tx_with_siso_diversity = 0;
		sc->sc_uhb_supported = 0;
		break;
	case PCI_PRODUCT_INTEL_WL_22500_3:
		if (sc->sc_hw_rev == IWX_CSR_HW_REV_TYPE_QU_C0)
			sc->sc_fwname = IWX_QU_C_HR_B_FW;
		else if (sc->sc_hw_rev == IWX_CSR_HW_REV_TYPE_QUZ)
			sc->sc_fwname = IWX_QUZ_A_HR_B_FW;
		else
			sc->sc_fwname = IWX_QU_B_HR_B_FW;
		sc->sc_device_family = IWX_DEVICE_FAMILY_22000;
		sc->sc_integrated = 1;
		sc->sc_ltr_delay = IWX_SOC_FLAGS_LTR_APPLY_DELAY_200;
		sc->sc_low_latency_xtal = 0;
		sc->sc_xtal_latency = 500;
		sc->sc_tx_with_siso_diversity = 0;
		sc->sc_uhb_supported = 0;
		break;
	case PCI_PRODUCT_INTEL_WL_22500_4:
	case PCI_PRODUCT_INTEL_WL_22500_7:
	case PCI_PRODUCT_INTEL_WL_22500_8:
		if (sc->sc_hw_rev == IWX_CSR_HW_REV_TYPE_QU_C0)
			sc->sc_fwname = IWX_QU_C_HR_B_FW;
		else if (sc->sc_hw_rev == IWX_CSR_HW_REV_TYPE_QUZ)
			sc->sc_fwname = IWX_QUZ_A_HR_B_FW;
		else
			sc->sc_fwname = IWX_QU_B_HR_B_FW;
		sc->sc_device_family = IWX_DEVICE_FAMILY_22000;
		sc->sc_integrated = 1;
		sc->sc_ltr_delay = IWX_SOC_FLAGS_LTR_APPLY_DELAY_1820;
		sc->sc_low_latency_xtal = 0;
		sc->sc_xtal_latency = 1820;
		sc->sc_tx_with_siso_diversity = 0;
		sc->sc_uhb_supported = 0;
		break;
	case PCI_PRODUCT_INTEL_WL_22500_6:
		if (sc->sc_hw_rev == IWX_CSR_HW_REV_TYPE_QU_C0)
			sc->sc_fwname = IWX_QU_C_HR_B_FW;
		else if (sc->sc_hw_rev == IWX_CSR_HW_REV_TYPE_QUZ)
			sc->sc_fwname = IWX_QUZ_A_HR_B_FW;
		else
			sc->sc_fwname = IWX_QU_B_HR_B_FW;
		sc->sc_device_family = IWX_DEVICE_FAMILY_22000;
		sc->sc_integrated = 1;
		sc->sc_ltr_delay = IWX_SOC_FLAGS_LTR_APPLY_DELAY_2500;
		sc->sc_low_latency_xtal = 1;
		sc->sc_xtal_latency = 12000;
		sc->sc_tx_with_siso_diversity = 0;
		sc->sc_uhb_supported = 0;
		break;
	case PCI_PRODUCT_INTEL_WL_22500_9:
	case PCI_PRODUCT_INTEL_WL_22500_10:
	case PCI_PRODUCT_INTEL_WL_22500_11:
	case PCI_PRODUCT_INTEL_WL_22500_13:
	/* _14 is an MA device, not yet supported */
	case PCI_PRODUCT_INTEL_WL_22500_15:
	case PCI_PRODUCT_INTEL_WL_22500_16:
		sc->sc_fwname = IWX_SO_A_GF_A_FW;
		sc->sc_pnvm_name = IWX_SO_A_GF_A_PNVM;
		sc->sc_device_family = IWX_DEVICE_FAMILY_AX210;
		sc->sc_integrated = 0;
		sc->sc_ltr_delay = IWX_SOC_FLAGS_LTR_APPLY_DELAY_NONE;
		sc->sc_low_latency_xtal = 0;
		sc->sc_xtal_latency = 0;
		sc->sc_tx_with_siso_diversity = 0;
		sc->sc_uhb_supported = 1;
		break;
	case PCI_PRODUCT_INTEL_WL_22500_12:
	case PCI_PRODUCT_INTEL_WL_22500_17:
		sc->sc_fwname = IWX_SO_A_GF_A_FW;
		sc->sc_pnvm_name = IWX_SO_A_GF_A_PNVM;
		sc->sc_device_family = IWX_DEVICE_FAMILY_AX210;
		sc->sc_integrated = 1;
		sc->sc_ltr_delay = IWX_SOC_FLAGS_LTR_APPLY_DELAY_2500;
		sc->sc_low_latency_xtal = 1;
		sc->sc_xtal_latency = 12000;
		sc->sc_tx_with_siso_diversity = 0;
		sc->sc_uhb_supported = 0;
		sc->sc_imr_enabled = 1;
		break;
	default:
		device_printf(dev, "unknown adapter type\n");
		return (ENXIO);
	}

	cfg = iwx_find_device_cfg(sc);
	DPRINTF(("%s: cfg=%p\n", __func__, cfg));
	if (cfg) {
		sc->sc_fwname = cfg->fw_name;
		sc->sc_pnvm_name = cfg->pnvm_name;
		sc->sc_tx_with_siso_diversity = cfg->tx_with_siso_diversity;
		sc->sc_uhb_supported = cfg->uhb_supported;
		if (cfg->xtal_latency) {
			sc->sc_xtal_latency = cfg->xtal_latency;
			sc->sc_low_latency_xtal = cfg->low_latency_xtal;
		}
	}

	sc->mac_addr_from_csr = 0x380; /* differs on BZ hw generation */

	if (sc->sc_device_family >= IWX_DEVICE_FAMILY_AX210) {
		sc->sc_umac_prph_offset = 0x300000;
		sc->max_tfd_queue_size = IWX_TFD_QUEUE_SIZE_MAX_GEN3;
	} else
		sc->max_tfd_queue_size = IWX_TFD_QUEUE_SIZE_MAX;

	/* Allocate DMA memory for loading firmware. */
	if (sc->sc_device_family >= IWX_DEVICE_FAMILY_AX210)
		ctxt_info_size = sizeof(struct iwx_context_info_gen3);
	else
		ctxt_info_size = sizeof(struct iwx_context_info);
	err = iwx_dma_contig_alloc(sc->sc_dmat, &sc->ctxt_info_dma,
	    ctxt_info_size, 1);
	if (err) {
		device_printf(dev,
		    "could not allocate memory for loading firmware\n");
		return (ENXIO);
	}

	if (sc->sc_device_family >= IWX_DEVICE_FAMILY_AX210) {
		err = iwx_dma_contig_alloc(sc->sc_dmat, &sc->prph_scratch_dma,
		    sizeof(struct iwx_prph_scratch), 1);
		if (err) {
			device_printf(dev,
			    "could not allocate prph scratch memory\n");
			goto fail1;
		}

		/*
		 * Allocate prph information. The driver doesn't use this.
		 * We use the second half of this page to give the device
		 * some dummy TR/CR tail pointers - which shouldn't be
		 * necessary as we don't use this, but the hardware still
		 * reads/writes there and we can't let it go do that with
		 * a NULL pointer.
		 */
		KASSERT((sizeof(struct iwx_prph_info) < PAGE_SIZE / 2),
		    ("iwx_prph_info has wrong size"));
		err = iwx_dma_contig_alloc(sc->sc_dmat, &sc->prph_info_dma,
		    PAGE_SIZE, 1);
		if (err) {
			device_printf(dev,
			    "could not allocate prph info memory\n");
			goto fail1;
		}
	}

	/* Allocate interrupt cause table (ICT).*/
	err = iwx_dma_contig_alloc(sc->sc_dmat, &sc->ict_dma,
	    IWX_ICT_SIZE, 1<<IWX_ICT_PADDR_SHIFT);
	if (err) {
		device_printf(dev, "could not allocate ICT table\n");
		goto fail1;
	}

	for (txq_i = 0; txq_i < nitems(sc->txq); txq_i++) {
		err = iwx_alloc_tx_ring(sc, &sc->txq[txq_i], txq_i);
		if (err) {
			device_printf(dev, "could not allocate TX ring %d\n",
			    txq_i);
			goto fail4;
		}
	}

	err = iwx_alloc_rx_ring(sc, &sc->rxq);
	if (err) {
		device_printf(sc->sc_dev, "could not allocate RX ring\n");
		goto fail4;
	}

#ifdef IWX_DEBUG
	SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "debug",
	    CTLFLAG_RWTUN, &sc->sc_debug, 0, "bitmask to control debugging");

	SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "himark",
	    CTLFLAG_RW, &iwx_himark, 0, "queues high watermark");
	SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "lomark",
	    CTLFLAG_RW, &iwx_lomark, 0, "queues low watermark");

	SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "qfullmsk",
	    CTLFLAG_RD, &sc->qfullmsk, 0, "queue fullmask");

	SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "queue0",
	    CTLFLAG_RD, &sc->txq[0].queued, 0, "queue 0");
	SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "queue1",
	    CTLFLAG_RD, &sc->txq[1].queued, 0, "queue 1");
	SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "queue2",
	    CTLFLAG_RD, &sc->txq[2].queued, 0, "queue 2");
	SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "queue3",
	    CTLFLAG_RD, &sc->txq[3].queued, 0, "queue 3");
	SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "queue4",
	    CTLFLAG_RD, &sc->txq[4].queued, 0, "queue 4");
	SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "queue5",
	    CTLFLAG_RD, &sc->txq[5].queued, 0, "queue 5");
	SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "queue6",
	    CTLFLAG_RD, &sc->txq[6].queued, 0, "queue 6");
	SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "queue7",
	    CTLFLAG_RD, &sc->txq[7].queued, 0, "queue 7");
#endif
	ic->ic_softc = sc;
	ic->ic_name = device_get_nameunit(sc->sc_dev);
	ic->ic_phytype = IEEE80211_T_OFDM;	/* not only, but not used */
	ic->ic_opmode = IEEE80211_M_STA;	/* default to BSS mode */

	/* Set device capabilities. */
	ic->ic_caps =
	    IEEE80211_C_STA |
	    IEEE80211_C_MONITOR |
	    IEEE80211_C_WPA |		/* WPA/RSN */
	    IEEE80211_C_WME |
	    IEEE80211_C_PMGT |
	    IEEE80211_C_SHSLOT |	/* short slot time supported */
	    IEEE80211_C_SHPREAMBLE |	/* short preamble supported */
	    IEEE80211_C_BGSCAN		/* capable of bg scanning */
	    ;
	ic->ic_flags_ext = IEEE80211_FEXT_SCAN_OFFLOAD;
	/* Enable seqno offload */
	ic->ic_flags_ext |= IEEE80211_FEXT_SEQNO_OFFLOAD;

	ic->ic_txstream = 2;
	ic->ic_rxstream = 2;
	ic->ic_htcaps |= IEEE80211_HTC_HT
			| IEEE80211_HTCAP_SMPS_OFF
			| IEEE80211_HTCAP_SHORTGI20	/* short GI in 20MHz */
			| IEEE80211_HTCAP_SHORTGI40	/* short GI in 40MHz */
			| IEEE80211_HTCAP_CHWIDTH40	/* 40MHz channel width*/
			| IEEE80211_HTC_AMPDU		/* tx A-MPDU */
//			| IEEE80211_HTC_RX_AMSDU_AMPDU	/* TODO: hw reorder */
			| IEEE80211_HTCAP_MAXAMSDU_3839;	/* max A-MSDU length */

	ic->ic_cryptocaps |= IEEE80211_CRYPTO_AES_CCM;

	/*
	 * XXX: setupcurchan() expects vhtcaps to be non-zero
	 * https://bugs.freebsd.org/274156
	 */
	ic->ic_vht_cap.vht_cap_info |= IEEE80211_VHTCAP_MAX_MPDU_LENGTH_3895
			| IEEE80211_VHTCAP_SHORT_GI_80
			| 3 << IEEE80211_VHTCAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK_S
			| IEEE80211_VHTCAP_RX_ANTENNA_PATTERN
			| IEEE80211_VHTCAP_TX_ANTENNA_PATTERN;

	ic->ic_flags_ext |= IEEE80211_FEXT_VHT;
	int mcsmap = IEEE80211_VHT_MCS_SUPPORT_0_9 << 0 |
		  IEEE80211_VHT_MCS_SUPPORT_0_9 << 2 |
		  IEEE80211_VHT_MCS_NOT_SUPPORTED << 4 |
		  IEEE80211_VHT_MCS_NOT_SUPPORTED << 6 |
		  IEEE80211_VHT_MCS_NOT_SUPPORTED << 8 |
		  IEEE80211_VHT_MCS_NOT_SUPPORTED << 10 |
		  IEEE80211_VHT_MCS_NOT_SUPPORTED << 12 |
		  IEEE80211_VHT_MCS_NOT_SUPPORTED << 14;
	ic->ic_vht_cap.supp_mcs.tx_mcs_map = htole16(mcsmap);
	ic->ic_vht_cap.supp_mcs.rx_mcs_map = htole16(mcsmap);

	callout_init_mtx(&sc->watchdog_to, &sc->sc_mtx, 0);
	for (i = 0; i < nitems(sc->sc_rxba_data); i++) {
		struct iwx_rxba_data *rxba = &sc->sc_rxba_data[i];
		rxba->baid = IWX_RX_REORDER_DATA_INVALID_BAID;
		rxba->sc = sc;
		for (j = 0; j < nitems(rxba->entries); j++)
			mbufq_init(&rxba->entries[j].frames, ifqmaxlen);
	}

	sc->sc_preinit_hook.ich_func = iwx_attach_hook;
	sc->sc_preinit_hook.ich_arg = sc;
	if (config_intrhook_establish(&sc->sc_preinit_hook) != 0) {
		device_printf(dev,
		    "config_intrhook_establish failed\n");
		goto fail4;
	}

	return (0);

fail4:
	while (--txq_i >= 0)
		iwx_free_tx_ring(sc, &sc->txq[txq_i]);
	iwx_free_rx_ring(sc, &sc->rxq);
	if (sc->ict_dma.vaddr != NULL)
		iwx_dma_contig_free(&sc->ict_dma);

fail1:
	iwx_dma_contig_free(&sc->ctxt_info_dma);
	iwx_dma_contig_free(&sc->prph_scratch_dma);
	iwx_dma_contig_free(&sc->prph_info_dma);
	return (ENXIO);
}

static int
iwx_detach(device_t dev)
{
	struct iwx_softc *sc = device_get_softc(dev);
	int txq_i;

	iwx_stop_device(sc);

	taskqueue_drain_all(sc->sc_tq);
	taskqueue_free(sc->sc_tq);

	ieee80211_ifdetach(&sc->sc_ic);

	callout_drain(&sc->watchdog_to);

	for (txq_i = 0; txq_i < nitems(sc->txq); txq_i++)
		iwx_free_tx_ring(sc, &sc->txq[txq_i]);
	iwx_free_rx_ring(sc, &sc->rxq);

	if (sc->sc_fwp != NULL) {
		firmware_put(sc->sc_fwp, FIRMWARE_UNLOAD);
		sc->sc_fwp = NULL;
	}

	if (sc->sc_pnvm != NULL) {
		firmware_put(sc->sc_pnvm, FIRMWARE_UNLOAD);
		sc->sc_pnvm = NULL;
	}

	if (sc->sc_irq != NULL) {
		bus_teardown_intr(dev, sc->sc_irq, sc->sc_ih);
		bus_release_resource(dev, SYS_RES_IRQ,
		    rman_get_rid(sc->sc_irq), sc->sc_irq);
		pci_release_msi(dev);
        }
	if (sc->sc_mem != NULL)
		bus_release_resource(dev, SYS_RES_MEMORY,
		    rman_get_rid(sc->sc_mem), sc->sc_mem);

	IWX_LOCK_DESTROY(sc);

	return (0);
}

static void
iwx_radiotap_attach(struct iwx_softc *sc)
{
	struct ieee80211com *ic = &sc->sc_ic;

	IWX_DPRINTF(sc, IWX_DEBUG_RESET | IWX_DEBUG_TRACE,
	    "->%s begin\n", __func__);

	ieee80211_radiotap_attach(ic,
	    &sc->sc_txtap.wt_ihdr, sizeof(sc->sc_txtap),
		IWX_TX_RADIOTAP_PRESENT,
	    &sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap),
		IWX_RX_RADIOTAP_PRESENT);

	IWX_DPRINTF(sc, IWX_DEBUG_RESET | IWX_DEBUG_TRACE,
	    "->%s end\n", __func__);
}

struct ieee80211vap *
iwx_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit,
    enum ieee80211_opmode opmode, int flags,
    const uint8_t bssid[IEEE80211_ADDR_LEN],
    const uint8_t mac[IEEE80211_ADDR_LEN])
{
	struct iwx_vap *ivp;
	struct ieee80211vap *vap;

	if (!TAILQ_EMPTY(&ic->ic_vaps))         /* only one at a time */
		return NULL;
	ivp = malloc(sizeof(struct iwx_vap), M_80211_VAP, M_WAITOK | M_ZERO);
	vap = &ivp->iv_vap;
	ieee80211_vap_setup(ic, vap, name, unit, opmode, flags, bssid);
	vap->iv_bmissthreshold = 10;            /* override default */
	/* Override with driver methods. */
	ivp->iv_newstate = vap->iv_newstate;
	vap->iv_newstate = iwx_newstate;

	ivp->id = IWX_DEFAULT_MACID;
	ivp->color = IWX_DEFAULT_COLOR;

	ivp->have_wme = TRUE;
	ivp->ps_disabled = FALSE;

	vap->iv_ampdu_rxmax = IEEE80211_HTCAP_MAXRXAMPDU_64K;
	vap->iv_ampdu_density = IEEE80211_HTCAP_MPDUDENSITY_4;

	/* h/w crypto support */
	vap->iv_key_alloc = iwx_key_alloc;
	vap->iv_key_delete = iwx_key_delete;
	vap->iv_key_set = iwx_key_set;
	vap->iv_key_update_begin = iwx_key_update_begin;
	vap->iv_key_update_end = iwx_key_update_end;

	ieee80211_ratectl_init(vap);
	/* Complete setup. */
	ieee80211_vap_attach(vap, ieee80211_media_change,
	    ieee80211_media_status, mac);
	ic->ic_opmode = opmode;

	return vap;
}

static void
iwx_vap_delete(struct ieee80211vap *vap)
{
	struct iwx_vap *ivp = IWX_VAP(vap);

	ieee80211_ratectl_deinit(vap);
	ieee80211_vap_detach(vap);
	free(ivp, M_80211_VAP);
}

static void
iwx_parent(struct ieee80211com *ic)
{
	struct iwx_softc *sc = ic->ic_softc;
	IWX_LOCK(sc);

	if (sc->sc_flags & IWX_FLAG_HW_INITED) {
		iwx_stop(sc);
		sc->sc_flags &= ~IWX_FLAG_HW_INITED;
	} else {
		iwx_init(sc);
		ieee80211_start_all(ic);
	}
	IWX_UNLOCK(sc);
}

static int
iwx_suspend(device_t dev)
{
	struct iwx_softc *sc = device_get_softc(dev);
	struct ieee80211com *ic = &sc->sc_ic;

	if (sc->sc_flags & IWX_FLAG_HW_INITED) {
		ieee80211_suspend_all(ic);

		iwx_stop(sc);
		sc->sc_flags &= ~IWX_FLAG_HW_INITED;
	}
	return (0);
}

static int
iwx_resume(device_t dev)
{
	struct iwx_softc *sc = device_get_softc(dev);
	struct ieee80211com *ic = &sc->sc_ic;
	int err;

	/*
	 * We disable the RETRY_TIMEOUT register (0x41) to keep
	 * PCI Tx retries from interfering with C3 CPU state.
	 */
	pci_write_config(dev, PCI_CFG_RETRY_TIMEOUT, 0x00, 1);

	IWX_LOCK(sc);

	err = iwx_init(sc);
	if (err) {
		iwx_stop_device(sc);
		IWX_UNLOCK(sc);
		return err;
	}

	IWX_UNLOCK(sc);

	ieee80211_resume_all(ic);
	return (0);
}

static void
iwx_scan_start(struct ieee80211com *ic)
{
	struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
	struct iwx_softc *sc = ic->ic_softc;
	int err;

	IWX_LOCK(sc);
	if ((ic->ic_flags_ext & IEEE80211_FEXT_BGSCAN) == 0)
		err = iwx_scan(sc);
	else
		err = iwx_bgscan(ic);
	IWX_UNLOCK(sc);
	if (err)
		ieee80211_cancel_scan(vap);

	return;
}

static void
iwx_update_mcast(struct ieee80211com *ic)
{
}

static void
iwx_scan_curchan(struct ieee80211_scan_state *ss, unsigned long maxdwell)
{
}

static void
iwx_scan_mindwell(struct ieee80211_scan_state *ss)
{
}

static void
iwx_scan_end(struct ieee80211com *ic)
{
	iwx_endscan(ic->ic_softc);
}

static void
iwx_set_channel(struct ieee80211com *ic)
{
#if 0
        struct iwx_softc *sc = ic->ic_softc;
        struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);

        IWX_DPRINTF(sc, IWX_DEBUG_NI , "%s:%d NOT IMPLEMENTED\n", __func__, __LINE__);
        iwx_phy_ctxt_task((void *)sc);
#endif
}

static void
iwx_endscan_cb(void *arg, int pending)
{
	struct iwx_softc *sc = arg;
	struct ieee80211com *ic = &sc->sc_ic;

	DPRINTF(("scan ended\n"));
	ieee80211_scan_done(TAILQ_FIRST(&ic->ic_vaps));
}

static int
iwx_wme_update(struct ieee80211com *ic)
{
	return 0;
}

static int
iwx_raw_xmit(struct ieee80211_node *ni, struct mbuf *m,
    const struct ieee80211_bpf_params *params)
{
	struct ieee80211com *ic = ni->ni_ic;
	struct iwx_softc *sc = ic->ic_softc;
	int err;

	IWX_LOCK(sc);
	if (sc->sc_flags & IWX_FLAG_STA_ACTIVE) {
		err = iwx_tx(sc, m, ni);
		IWX_UNLOCK(sc);
		return err;
	} else {
		IWX_UNLOCK(sc);
		return EIO;
	}
}

static int
iwx_transmit(struct ieee80211com *ic, struct mbuf *m)
{
	struct iwx_softc *sc = ic->ic_softc;
	int error;

	// TODO: mbufq_enqueue in iwm
	// TODO dequeue in iwm_start, counters, locking
	IWX_LOCK(sc);
	error = mbufq_enqueue(&sc->sc_snd, m);
	if (error) {
		IWX_UNLOCK(sc);
		return (error);
	}

	iwx_start(sc);
	IWX_UNLOCK(sc);
	return (0);
}

static int
iwx_ampdu_rx_start(struct ieee80211_node *ni, struct ieee80211_rx_ampdu *rap,
    int baparamset, int batimeout, int baseqctl)
{
	struct ieee80211com *ic = ni->ni_ic;
	struct iwx_softc *sc = ic->ic_softc;
	int tid;

	tid = _IEEE80211_MASKSHIFT(le16toh(baparamset), IEEE80211_BAPS_TID);
	sc->ni_rx_ba[tid].ba_winstart =
	    _IEEE80211_MASKSHIFT(le16toh(baseqctl), IEEE80211_BASEQ_START);
	sc->ni_rx_ba[tid].ba_winsize =
	    _IEEE80211_MASKSHIFT(le16toh(baparamset), IEEE80211_BAPS_BUFSIZ);
	sc->ni_rx_ba[tid].ba_timeout_val = batimeout;

	if (sc->sc_rx_ba_sessions >= IWX_MAX_RX_BA_SESSIONS ||
	    tid >= IWX_MAX_TID_COUNT)
		return ENOSPC;

	if (sc->ba_rx.start_tidmask & (1 << tid)) {
		DPRINTF(("%s: tid %d already added\n", __func__, tid));
		return EBUSY;
	}
	DPRINTF(("%s: sc->ba_rx.start_tidmask=%x\n", __func__, sc->ba_rx.start_tidmask));

	sc->ba_rx.start_tidmask |= (1 << tid);
	DPRINTF(("%s: tid=%i\n", __func__, tid));
	DPRINTF(("%s: ba_winstart=%i\n", __func__, sc->ni_rx_ba[tid].ba_winstart));
	DPRINTF(("%s: ba_winsize=%i\n", __func__, sc->ni_rx_ba[tid].ba_winsize));
	DPRINTF(("%s: ba_timeout_val=%i\n", __func__, sc->ni_rx_ba[tid].ba_timeout_val));

	taskqueue_enqueue(sc->sc_tq, &sc->ba_rx_task);

	// TODO:misha move to ba_task (serialize)
	sc->sc_ampdu_rx_start(ni, rap, baparamset, batimeout, baseqctl);

	return (0);
}

static void
iwx_ampdu_rx_stop(struct ieee80211_node *ni, struct ieee80211_rx_ampdu *rap)
{
	return;
}

static int
iwx_addba_request(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap,
    int dialogtoken, int baparamset, int batimeout)
{
	struct iwx_softc *sc = ni->ni_ic->ic_softc;
	int tid;

	tid = _IEEE80211_MASKSHIFT(le16toh(baparamset), IEEE80211_BAPS_TID);
	DPRINTF(("%s: tid=%i\n", __func__, tid));
	sc->ba_tx.start_tidmask |= (1 << tid);
	taskqueue_enqueue(sc->sc_tq, &sc->ba_tx_task);
	return 0;
}


static int
iwx_addba_response(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap,
    int code, int baparamset, int batimeout)
{
	return 0;
}

static void
iwx_key_update_begin(struct ieee80211vap *vap)
{
	return;
}

static void
iwx_key_update_end(struct ieee80211vap *vap)
{
	return;
}

static int
iwx_key_alloc(struct ieee80211vap *vap, struct ieee80211_key *k,
	ieee80211_keyix *keyix, ieee80211_keyix *rxkeyix)
{

	if (k->wk_cipher->ic_cipher == IEEE80211_CIPHER_AES_CCM) {
		return 1;
	}
	if (!(&vap->iv_nw_keys[0] <= k &&
	     k < &vap->iv_nw_keys[IEEE80211_WEP_NKID])) {
		/*
		 * Not in the global key table, the driver should handle this
		 * by allocating a slot in the h/w key table/cache.  In
		 * lieu of that return key slot 0 for any unicast key
		 * request.  We disallow the request if this is a group key.
		 * This default policy does the right thing for legacy hardware
		 * with a 4 key table.  It also handles devices that pass
		 * packets through untouched when marked with the WEP bit
		 * and key index 0.
		 */
		if (k->wk_flags & IEEE80211_KEY_GROUP)
			return 0;
		*keyix = 0;	/* NB: use key index 0 for ucast key */
	} else {
		*keyix = ieee80211_crypto_get_key_wepidx(vap, k);
	}
	*rxkeyix = IEEE80211_KEYIX_NONE;	/* XXX maybe *keyix? */
	return 1;
}

static int
iwx_key_set(struct ieee80211vap *vap, const struct ieee80211_key *k)
{
	struct ieee80211com *ic = vap->iv_ic;
	struct iwx_softc *sc = ic->ic_softc;
	struct iwx_add_sta_key_cmd cmd;
	uint32_t status;
	int err;
	int id;

	if (k->wk_cipher->ic_cipher != IEEE80211_CIPHER_AES_CCM) {
		return 1;
	}

	IWX_LOCK(sc);
	/*
	 * Keys are stored in 'ni' so 'k' is valid if 'ni' is valid.
	 * Currently we only implement station mode where 'ni' is always
	 * ic->ic_bss so there is no need to validate arguments beyond this:
	 */

	memset(&cmd, 0, sizeof(cmd));

	if (k->wk_flags & IEEE80211_KEY_GROUP) {
		DPRINTF(("%s: adding group key\n", __func__));
	} else {
		DPRINTF(("%s: adding key\n", __func__));
	}
	if (k >= &vap->iv_nw_keys[0] &&
	    k <  &vap->iv_nw_keys[IEEE80211_WEP_NKID])
		id = (k - vap->iv_nw_keys);
	else
		id = (0);
	DPRINTF(("%s: setting keyid=%i\n", __func__, id));
	cmd.common.key_flags = htole16(IWX_STA_KEY_FLG_CCM |
	    IWX_STA_KEY_FLG_WEP_KEY_MAP |
	    ((id << IWX_STA_KEY_FLG_KEYID_POS) &
	    IWX_STA_KEY_FLG_KEYID_MSK));
	if (k->wk_flags & IEEE80211_KEY_GROUP) {
		cmd.common.key_offset = 1;
		cmd.common.key_flags |= htole16(IWX_STA_KEY_MULTICAST);
	} else {
		cmd.common.key_offset = 0;
	}
	memcpy(cmd.common.key, k->wk_key, MIN(sizeof(cmd.common.key),
	    k->wk_keylen));
	DPRINTF(("%s: wk_keylen=%i\n", __func__, k->wk_keylen));
	for (int i=0; i<k->wk_keylen; i++) {
		DPRINTF(("%s: key[%d]=%x\n", __func__, i, k->wk_key[i]));
	}
	cmd.common.sta_id = IWX_STATION_ID;

	cmd.transmit_seq_cnt = htole64(k->wk_keytsc);
	DPRINTF(("%s: k->wk_keytsc=%" PRIu64 "\n", __func__, k->wk_keytsc));

	status = IWX_ADD_STA_SUCCESS;
	err = iwx_send_cmd_pdu_status(sc, IWX_ADD_STA_KEY, sizeof(cmd), &cmd,
	    &status);
	if (!err && (status & IWX_ADD_STA_STATUS_MASK) != IWX_ADD_STA_SUCCESS)
		err = EIO;
	if (err) {
		printf("%s: can't set wpa2 keys (error %d)\n", __func__, err);
		IWX_UNLOCK(sc);
		return err;
	} else
		DPRINTF(("%s: key added successfully\n", __func__));
	IWX_UNLOCK(sc);
	return 1;
}

static int
iwx_key_delete(struct ieee80211vap *vap, const struct ieee80211_key *k)
{
	return 1;
}

static device_method_t iwx_pci_methods[] = {
	/* Device interface */
	DEVMETHOD(device_probe,		iwx_probe),
	DEVMETHOD(device_attach,	iwx_attach),
	DEVMETHOD(device_detach,	iwx_detach),
	DEVMETHOD(device_suspend,	iwx_suspend),
	DEVMETHOD(device_resume,	iwx_resume),

	DEVMETHOD_END
};

static driver_t iwx_pci_driver = {
	"iwx",
	iwx_pci_methods,
	sizeof (struct iwx_softc)
};

DRIVER_MODULE(iwx, pci, iwx_pci_driver, NULL, NULL);
MODULE_PNP_INFO("U16:device;D:#;T:vendor=0x8086", pci, iwx_pci_driver,
    iwx_devices, nitems(iwx_devices));
MODULE_DEPEND(iwx, firmware, 1, 1, 1);
MODULE_DEPEND(iwx, pci, 1, 1, 1);
MODULE_DEPEND(iwx, wlan, 1, 1, 1);
