freebsd-dev/sys/dev/iwm/if_iwm.c

6279 lines
170 KiB
C
Raw Normal View History

/* $OpenBSD: if_iwm.c,v 1.42 2015/05/30 02:49:23 deraadt Exp $ */
/*
* Copyright (c) 2014 genua mbh <info@genua.de>
* Copyright (c) 2014 Fixup Software Ltd.
*
* 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.
*
* Driver version we are currently based off of is
* Linux 3.14.3 (tag id a2df521e42b1d9a23f620ac79dbfe8655a8391dd)
*
***********************************************************************
*
* 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) 2007 - 2013 Intel Corporation. All rights reserved.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110,
* USA
*
* The full GNU General Public License is included in this distribution
* in the file called COPYING.
*
* Contact Information:
* Intel Linux Wireless <ilw@linux.intel.com>
* Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497
*
*
* BSD LICENSE
*
* Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved.
* 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/cdefs.h>
__FBSDID("$FreeBSD$");
#include "opt_wlan.h"
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/conf.h>
#include <sys/endian.h>
#include <sys/firmware.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/mutex.h>
#include <sys/module.h>
#include <sys/proc.h>
#include <sys/rman.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/sysctl.h>
#include <sys/linker.h>
#include <machine/bus.h>
#include <machine/endian.h>
#include <machine/resource.h>
#include <dev/pci/pcivar.h>
#include <dev/pci/pcireg.h>
#include <net/bpf.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/if_arp.h>
#include <net/if_dl.h>
#include <net/if_media.h>
#include <net/if_types.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/if_ether.h>
#include <netinet/ip.h>
#include <net80211/ieee80211_var.h>
#include <net80211/ieee80211_regdomain.h>
#include <net80211/ieee80211_ratectl.h>
#include <net80211/ieee80211_radiotap.h>
#include <dev/iwm/if_iwmreg.h>
#include <dev/iwm/if_iwmvar.h>
#include <dev/iwm/if_iwm_debug.h>
#include <dev/iwm/if_iwm_util.h>
#include <dev/iwm/if_iwm_binding.h>
#include <dev/iwm/if_iwm_phy_db.h>
#include <dev/iwm/if_iwm_mac_ctxt.h>
#include <dev/iwm/if_iwm_phy_ctxt.h>
#include <dev/iwm/if_iwm_time_event.h>
#include <dev/iwm/if_iwm_power.h>
#include <dev/iwm/if_iwm_scan.h>
#include <dev/iwm/if_iwm_pcie_trans.h>
#include <dev/iwm/if_iwm_led.h>
const uint8_t iwm_nvm_channels[] = {
/* 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,
100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144,
149, 153, 157, 161, 165
};
_Static_assert(nitems(iwm_nvm_channels) <= IWM_NUM_CHANNELS,
"IWM_NUM_CHANNELS is too small");
const uint8_t iwm_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_assert(nitems(iwm_nvm_channels_8000) <= IWM_NUM_CHANNELS_8000,
"IWM_NUM_CHANNELS_8000 is too small");
#define IWM_NUM_2GHZ_CHANNELS 14
#define IWM_N_HW_ADDR_MASK 0xF
/*
* XXX For now, there's simply a fixed set of rate table entries
* that are populated.
*/
const struct iwm_rate {
uint8_t rate;
uint8_t plcp;
} iwm_rates[] = {
{ 2, IWM_RATE_1M_PLCP },
{ 4, IWM_RATE_2M_PLCP },
{ 11, IWM_RATE_5M_PLCP },
{ 22, IWM_RATE_11M_PLCP },
{ 12, IWM_RATE_6M_PLCP },
{ 18, IWM_RATE_9M_PLCP },
{ 24, IWM_RATE_12M_PLCP },
{ 36, IWM_RATE_18M_PLCP },
{ 48, IWM_RATE_24M_PLCP },
{ 72, IWM_RATE_36M_PLCP },
{ 96, IWM_RATE_48M_PLCP },
{ 108, IWM_RATE_54M_PLCP },
};
#define IWM_RIDX_CCK 0
#define IWM_RIDX_OFDM 4
#define IWM_RIDX_MAX (nitems(iwm_rates)-1)
#define IWM_RIDX_IS_CCK(_i_) ((_i_) < IWM_RIDX_OFDM)
#define IWM_RIDX_IS_OFDM(_i_) ((_i_) >= IWM_RIDX_OFDM)
struct iwm_nvm_section {
uint16_t length;
uint8_t *data;
};
static int iwm_store_cscheme(struct iwm_softc *, const uint8_t *, size_t);
static int iwm_firmware_store_section(struct iwm_softc *,
enum iwm_ucode_type,
const uint8_t *, size_t);
static int iwm_set_default_calib(struct iwm_softc *, const void *);
static void iwm_fw_info_free(struct iwm_fw_info *);
static int iwm_read_firmware(struct iwm_softc *, enum iwm_ucode_type);
static void iwm_dma_map_addr(void *, bus_dma_segment_t *, int, int);
static int iwm_dma_contig_alloc(bus_dma_tag_t, struct iwm_dma_info *,
bus_size_t, bus_size_t);
static void iwm_dma_contig_free(struct iwm_dma_info *);
static int iwm_alloc_fwmem(struct iwm_softc *);
static int iwm_alloc_sched(struct iwm_softc *);
static int iwm_alloc_kw(struct iwm_softc *);
static int iwm_alloc_ict(struct iwm_softc *);
static int iwm_alloc_rx_ring(struct iwm_softc *, struct iwm_rx_ring *);
static void iwm_disable_rx_dma(struct iwm_softc *);
static void iwm_reset_rx_ring(struct iwm_softc *, struct iwm_rx_ring *);
static void iwm_free_rx_ring(struct iwm_softc *, struct iwm_rx_ring *);
static int iwm_alloc_tx_ring(struct iwm_softc *, struct iwm_tx_ring *,
int);
static void iwm_reset_tx_ring(struct iwm_softc *, struct iwm_tx_ring *);
static void iwm_free_tx_ring(struct iwm_softc *, struct iwm_tx_ring *);
static void iwm_enable_interrupts(struct iwm_softc *);
static void iwm_restore_interrupts(struct iwm_softc *);
static void iwm_disable_interrupts(struct iwm_softc *);
static void iwm_ict_reset(struct iwm_softc *);
static int iwm_allow_mcast(struct ieee80211vap *, struct iwm_softc *);
static void iwm_stop_device(struct iwm_softc *);
static void iwm_mvm_nic_config(struct iwm_softc *);
static int iwm_nic_rx_init(struct iwm_softc *);
static int iwm_nic_tx_init(struct iwm_softc *);
static int iwm_nic_init(struct iwm_softc *);
static int iwm_enable_txq(struct iwm_softc *, int, int, int);
static int iwm_post_alive(struct iwm_softc *);
static int iwm_nvm_read_chunk(struct iwm_softc *, uint16_t, uint16_t,
uint16_t, uint8_t *, uint16_t *);
static int iwm_nvm_read_section(struct iwm_softc *, uint16_t, uint8_t *,
uint16_t *, size_t);
static uint32_t iwm_eeprom_channel_flags(uint16_t);
static void iwm_add_channel_band(struct iwm_softc *,
struct ieee80211_channel[], int, int *, int, size_t,
const uint8_t[]);
static void iwm_init_channel_map(struct ieee80211com *, int, int *,
struct ieee80211_channel[]);
static int iwm_parse_nvm_data(struct iwm_softc *, const uint16_t *,
const uint16_t *, const uint16_t *,
const uint16_t *, const uint16_t *,
const uint16_t *);
static void iwm_set_hw_address_8000(struct iwm_softc *,
struct iwm_nvm_data *,
const uint16_t *, const uint16_t *);
static int iwm_get_sku(const struct iwm_softc *, const uint16_t *,
const uint16_t *);
static int iwm_get_nvm_version(const struct iwm_softc *, const uint16_t *);
static int iwm_get_radio_cfg(const struct iwm_softc *, const uint16_t *,
const uint16_t *);
static int iwm_get_n_hw_addrs(const struct iwm_softc *,
const uint16_t *);
static void iwm_set_radio_cfg(const struct iwm_softc *,
struct iwm_nvm_data *, uint32_t);
static int iwm_parse_nvm_sections(struct iwm_softc *,
struct iwm_nvm_section *);
static int iwm_nvm_init(struct iwm_softc *);
static int iwm_firmware_load_sect(struct iwm_softc *, uint32_t,
const uint8_t *, uint32_t);
static int iwm_firmware_load_chunk(struct iwm_softc *, uint32_t,
const uint8_t *, uint32_t);
static int iwm_load_firmware_7000(struct iwm_softc *, enum iwm_ucode_type);
static int iwm_load_cpu_sections_8000(struct iwm_softc *,
struct iwm_fw_sects *, int , int *);
static int iwm_load_firmware_8000(struct iwm_softc *, enum iwm_ucode_type);
static int iwm_load_firmware(struct iwm_softc *, enum iwm_ucode_type);
static int iwm_start_fw(struct iwm_softc *, enum iwm_ucode_type);
static int iwm_send_tx_ant_cfg(struct iwm_softc *, uint8_t);
static int iwm_send_phy_cfg_cmd(struct iwm_softc *);
static int iwm_mvm_load_ucode_wait_alive(struct iwm_softc *,
enum iwm_ucode_type);
static int iwm_run_init_mvm_ucode(struct iwm_softc *, int);
static int iwm_rx_addbuf(struct iwm_softc *, int, int);
static int iwm_mvm_calc_rssi(struct iwm_softc *, struct iwm_rx_phy_info *);
static int iwm_mvm_get_signal_strength(struct iwm_softc *,
struct iwm_rx_phy_info *);
static void iwm_mvm_rx_rx_phy_cmd(struct iwm_softc *,
struct iwm_rx_packet *,
struct iwm_rx_data *);
static int iwm_get_noise(struct iwm_softc *sc,
const struct iwm_mvm_statistics_rx_non_phy *);
static void iwm_mvm_rx_rx_mpdu(struct iwm_softc *, struct iwm_rx_packet *,
struct iwm_rx_data *);
static int iwm_mvm_rx_tx_cmd_single(struct iwm_softc *,
struct iwm_rx_packet *,
struct iwm_node *);
static void iwm_mvm_rx_tx_cmd(struct iwm_softc *, struct iwm_rx_packet *,
struct iwm_rx_data *);
static void iwm_cmd_done(struct iwm_softc *, struct iwm_rx_packet *);
#if 0
static void iwm_update_sched(struct iwm_softc *, int, int, uint8_t,
uint16_t);
#endif
static const struct iwm_rate *
iwm_tx_fill_cmd(struct iwm_softc *, struct iwm_node *,
struct mbuf *, struct iwm_tx_cmd *);
static int iwm_tx(struct iwm_softc *, struct mbuf *,
struct ieee80211_node *, int);
static int iwm_raw_xmit(struct ieee80211_node *, struct mbuf *,
const struct ieee80211_bpf_params *);
static int iwm_mvm_flush_tx_path(struct iwm_softc *sc,
uint32_t tfd_msk, uint32_t flags);
static int iwm_mvm_send_add_sta_cmd_status(struct iwm_softc *,
struct iwm_mvm_add_sta_cmd_v7 *,
int *);
static int iwm_mvm_sta_send_to_fw(struct iwm_softc *, struct iwm_node *,
int);
static int iwm_mvm_add_sta(struct iwm_softc *, struct iwm_node *);
static int iwm_mvm_update_sta(struct iwm_softc *, struct iwm_node *);
static int iwm_mvm_add_int_sta_common(struct iwm_softc *,
struct iwm_int_sta *,
const uint8_t *, uint16_t, uint16_t);
static int iwm_mvm_add_aux_sta(struct iwm_softc *);
static int iwm_mvm_update_quotas(struct iwm_softc *, struct iwm_node *);
static int iwm_auth(struct ieee80211vap *, struct iwm_softc *);
static int iwm_assoc(struct ieee80211vap *, struct iwm_softc *);
static int iwm_release(struct iwm_softc *, struct iwm_node *);
static struct ieee80211_node *
iwm_node_alloc(struct ieee80211vap *,
const uint8_t[IEEE80211_ADDR_LEN]);
static void iwm_setrates(struct iwm_softc *, struct iwm_node *);
static int iwm_media_change(struct ifnet *);
static int iwm_newstate(struct ieee80211vap *, enum ieee80211_state, int);
static void iwm_endscan_cb(void *, int);
static void iwm_mvm_fill_sf_command(struct iwm_softc *,
struct iwm_sf_cfg_cmd *,
struct ieee80211_node *);
static int iwm_mvm_sf_config(struct iwm_softc *, enum iwm_sf_state);
static int iwm_send_bt_init_conf(struct iwm_softc *);
static int iwm_send_update_mcc_cmd(struct iwm_softc *, const char *);
static void iwm_mvm_tt_tx_backoff(struct iwm_softc *, uint32_t);
static int iwm_init_hw(struct iwm_softc *);
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
static void iwm_init(struct iwm_softc *);
static void iwm_start(struct iwm_softc *);
static void iwm_stop(struct iwm_softc *);
static void iwm_watchdog(void *);
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
static void iwm_parent(struct ieee80211com *);
#ifdef IWM_DEBUG
static const char *
iwm_desc_lookup(uint32_t);
static void iwm_nic_error(struct iwm_softc *);
static void iwm_nic_umac_error(struct iwm_softc *);
#endif
static void iwm_notif_intr(struct iwm_softc *);
static void iwm_intr(void *);
static int iwm_attach(device_t);
static int iwm_is_valid_ether_addr(uint8_t *);
static void iwm_preinit(void *);
static int iwm_detach_local(struct iwm_softc *sc, int);
static void iwm_init_task(void *);
static void iwm_radiotap_attach(struct iwm_softc *);
static struct ieee80211vap *
iwm_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 iwm_vap_delete(struct ieee80211vap *);
static void iwm_scan_start(struct ieee80211com *);
static void iwm_scan_end(struct ieee80211com *);
static void iwm_update_mcast(struct ieee80211com *);
static void iwm_set_channel(struct ieee80211com *);
static void iwm_scan_curchan(struct ieee80211_scan_state *, unsigned long);
static void iwm_scan_mindwell(struct ieee80211_scan_state *);
static int iwm_detach(device_t);
/*
* Firmware parser.
*/
static int
iwm_store_cscheme(struct iwm_softc *sc, const uint8_t *data, size_t dlen)
{
const struct iwm_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
iwm_firmware_store_section(struct iwm_softc *sc,
enum iwm_ucode_type type, const uint8_t *data, size_t dlen)
{
struct iwm_fw_sects *fws;
struct iwm_fw_onesect *fwone;
if (type >= IWM_UCODE_TYPE_MAX)
return EINVAL;
if (dlen < sizeof(uint32_t))
return EINVAL;
fws = &sc->sc_fw.fw_sects[type];
if (fws->fw_count >= IWM_UCODE_SECT_MAX)
return EINVAL;
fwone = &fws->fw_sect[fws->fw_count];
/* 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++;
return 0;
}
#define IWM_DEFAULT_SCAN_CHANNELS 40
/* iwlwifi: iwl-drv.c */
struct iwm_tlv_calib_data {
uint32_t ucode_type;
struct iwm_tlv_calib_ctrl calib;
} __packed;
static int
iwm_set_default_calib(struct iwm_softc *sc, const void *data)
{
const struct iwm_tlv_calib_data *def_calib = data;
uint32_t ucode_type = le32toh(def_calib->ucode_type);
if (ucode_type >= IWM_UCODE_TYPE_MAX) {
device_printf(sc->sc_dev,
"Wrong ucode_type %u for default "
"calibration.\n", ucode_type);
return EINVAL;
}
sc->sc_default_calib[ucode_type].flow_trigger =
def_calib->calib.flow_trigger;
sc->sc_default_calib[ucode_type].event_trigger =
def_calib->calib.event_trigger;
return 0;
}
static void
iwm_fw_info_free(struct iwm_fw_info *fw)
{
firmware_put(fw->fw_fp, FIRMWARE_UNLOAD);
fw->fw_fp = NULL;
/* don't touch fw->fw_status */
memset(fw->fw_sects, 0, sizeof(fw->fw_sects));
}
static int
iwm_read_firmware(struct iwm_softc *sc, enum iwm_ucode_type ucode_type)
{
struct iwm_fw_info *fw = &sc->sc_fw;
const struct iwm_tlv_ucode_header *uhdr;
struct iwm_ucode_tlv tlv;
enum iwm_ucode_tlv_type tlv_type;
const struct firmware *fwp;
const uint8_t *data;
int error = 0;
size_t len;
if (fw->fw_status == IWM_FW_STATUS_DONE &&
ucode_type != IWM_UCODE_TYPE_INIT)
return 0;
while (fw->fw_status == IWM_FW_STATUS_INPROGRESS)
msleep(&sc->sc_fw, &sc->sc_mtx, 0, "iwmfwp", 0);
fw->fw_status = IWM_FW_STATUS_INPROGRESS;
if (fw->fw_fp != NULL)
iwm_fw_info_free(fw);
/*
* Load firmware into driver memory.
* fw_fp will be set.
*/
IWM_UNLOCK(sc);
fwp = firmware_get(sc->sc_fwname);
IWM_LOCK(sc);
if (fwp == NULL) {
device_printf(sc->sc_dev,
"could not read firmware %s (error %d)\n",
sc->sc_fwname, error);
goto out;
}
fw->fw_fp = fwp;
/* (Re-)Initialize default values. */
sc->sc_capaflags = 0;
sc->sc_capa_n_scan_channels = IWM_DEFAULT_SCAN_CHANNELS;
memset(sc->sc_enabled_capa, 0, sizeof(sc->sc_enabled_capa));
memset(sc->sc_fw_mcc, 0, sizeof(sc->sc_fw_mcc));
/*
* Parse firmware contents
*/
uhdr = (const void *)fw->fw_fp->data;
if (*(const uint32_t *)fw->fw_fp->data != 0
|| le32toh(uhdr->magic) != IWM_TLV_UCODE_MAGIC) {
device_printf(sc->sc_dev, "invalid firmware %s\n",
sc->sc_fwname);
error = EINVAL;
goto out;
}
snprintf(sc->sc_fwver, sizeof(sc->sc_fwver), "%d.%d (API ver %d)",
IWM_UCODE_MAJOR(le32toh(uhdr->ver)),
IWM_UCODE_MINOR(le32toh(uhdr->ver)),
IWM_UCODE_API(le32toh(uhdr->ver)));
data = uhdr->data;
len = fw->fw_fp->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) {
device_printf(sc->sc_dev,
"firmware too short: %zu bytes\n",
len);
error = EINVAL;
goto parse_out;
}
switch ((int)tlv_type) {
case IWM_UCODE_TLV_PROBE_MAX_LEN:
if (tlv_len < sizeof(uint32_t)) {
device_printf(sc->sc_dev,
"%s: PROBE_MAX_LEN (%d) < sizeof(uint32_t)\n",
__func__,
(int) tlv_len);
error = EINVAL;
goto parse_out;
}
sc->sc_capa_max_probe_len
= le32toh(*(const uint32_t *)tlv_data);
/* limit it to something sensible */
if (sc->sc_capa_max_probe_len >
IWM_SCAN_OFFLOAD_PROBE_REQ_SIZE) {
IWM_DPRINTF(sc, IWM_DEBUG_FIRMWARE_TLV,
"%s: IWM_UCODE_TLV_PROBE_MAX_LEN "
"ridiculous\n", __func__);
error = EINVAL;
goto parse_out;
}
break;
case IWM_UCODE_TLV_PAN:
if (tlv_len) {
device_printf(sc->sc_dev,
"%s: IWM_UCODE_TLV_PAN: tlv_len (%d) > 0\n",
__func__,
(int) tlv_len);
error = EINVAL;
goto parse_out;
}
sc->sc_capaflags |= IWM_UCODE_TLV_FLAGS_PAN;
break;
case IWM_UCODE_TLV_FLAGS:
if (tlv_len < sizeof(uint32_t)) {
device_printf(sc->sc_dev,
"%s: IWM_UCODE_TLV_FLAGS: tlv_len (%d) < sizeof(uint32_t)\n",
__func__,
(int) tlv_len);
error = 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 IWM_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 IWM_UCODE_TLV_CSCHEME:
if ((error = iwm_store_cscheme(sc,
tlv_data, tlv_len)) != 0) {
device_printf(sc->sc_dev,
"%s: iwm_store_cscheme(): returned %d\n",
__func__,
error);
goto parse_out;
}
break;
case IWM_UCODE_TLV_NUM_OF_CPU: {
uint32_t num_cpu;
if (tlv_len != sizeof(uint32_t)) {
device_printf(sc->sc_dev,
"%s: IWM_UCODE_TLV_NUM_OF_CPU: tlv_len (%d) < sizeof(uint32_t)\n",
__func__,
(int) tlv_len);
error = EINVAL;
goto parse_out;
}
num_cpu = le32toh(*(const uint32_t *)tlv_data);
if (num_cpu < 1 || num_cpu > 2) {
device_printf(sc->sc_dev,
"%s: Driver supports only 1 or 2 CPUs\n",
__func__);
error = EINVAL;
goto parse_out;
}
break;
}
case IWM_UCODE_TLV_SEC_RT:
if ((error = iwm_firmware_store_section(sc,
IWM_UCODE_TYPE_REGULAR, tlv_data, tlv_len)) != 0) {
device_printf(sc->sc_dev,
"%s: IWM_UCODE_TYPE_REGULAR: iwm_firmware_store_section() failed; %d\n",
__func__,
error);
goto parse_out;
}
break;
case IWM_UCODE_TLV_SEC_INIT:
if ((error = iwm_firmware_store_section(sc,
IWM_UCODE_TYPE_INIT, tlv_data, tlv_len)) != 0) {
device_printf(sc->sc_dev,
"%s: IWM_UCODE_TYPE_INIT: iwm_firmware_store_section() failed; %d\n",
__func__,
error);
goto parse_out;
}
break;
case IWM_UCODE_TLV_SEC_WOWLAN:
if ((error = iwm_firmware_store_section(sc,
IWM_UCODE_TYPE_WOW, tlv_data, tlv_len)) != 0) {
device_printf(sc->sc_dev,
"%s: IWM_UCODE_TYPE_WOW: iwm_firmware_store_section() failed; %d\n",
__func__,
error);
goto parse_out;
}
break;
case IWM_UCODE_TLV_DEF_CALIB:
if (tlv_len != sizeof(struct iwm_tlv_calib_data)) {
device_printf(sc->sc_dev,
"%s: IWM_UCODE_TLV_DEV_CALIB: tlv_len (%d) < sizeof(iwm_tlv_calib_data) (%d)\n",
__func__,
(int) tlv_len,
(int) sizeof(struct iwm_tlv_calib_data));
error = EINVAL;
goto parse_out;
}
if ((error = iwm_set_default_calib(sc, tlv_data)) != 0) {
device_printf(sc->sc_dev,
"%s: iwm_set_default_calib() failed: %d\n",
__func__,
error);
goto parse_out;
}
break;
case IWM_UCODE_TLV_PHY_SKU:
if (tlv_len != sizeof(uint32_t)) {
error = EINVAL;
device_printf(sc->sc_dev,
"%s: IWM_UCODE_TLV_PHY_SKU: tlv_len (%d) < sizeof(uint32_t)\n",
__func__,
(int) tlv_len);
goto parse_out;
}
sc->sc_fw_phy_config =
le32toh(*(const uint32_t *)tlv_data);
break;
case IWM_UCODE_TLV_API_CHANGES_SET: {
const struct iwm_ucode_api *api;
if (tlv_len != sizeof(*api)) {
error = EINVAL;
goto parse_out;
}
api = (const struct iwm_ucode_api *)tlv_data;
/* Flags may exceed 32 bits in future firmware. */
if (le32toh(api->api_index) > 0) {
device_printf(sc->sc_dev,
"unsupported API index %d\n",
le32toh(api->api_index));
goto parse_out;
}
sc->sc_ucode_api = le32toh(api->api_flags);
break;
}
case IWM_UCODE_TLV_ENABLED_CAPABILITIES: {
const struct iwm_ucode_capa *capa;
int idx, i;
if (tlv_len != sizeof(*capa)) {
error = EINVAL;
goto parse_out;
}
capa = (const struct iwm_ucode_capa *)tlv_data;
idx = le32toh(capa->api_index);
if (idx >= howmany(IWM_NUM_UCODE_TLV_CAPA, 32)) {
device_printf(sc->sc_dev,
"unsupported API index %d\n", idx);
goto parse_out;
}
for (i = 0; i < 32; i++) {
if ((le32toh(capa->api_capa) & (1U << i)) == 0)
continue;
setbit(sc->sc_enabled_capa, i + (32 * idx));
}
break;
}
case 48: /* undocumented TLV */
case IWM_UCODE_TLV_SDIO_ADMA_ADDR:
case IWM_UCODE_TLV_FW_GSCAN_CAPA:
/* ignore, not used by current driver */
break;
case IWM_UCODE_TLV_SEC_RT_USNIFFER:
if ((error = iwm_firmware_store_section(sc,
IWM_UCODE_TYPE_REGULAR_USNIFFER, tlv_data,
tlv_len)) != 0)
goto parse_out;
break;
case IWM_UCODE_TLV_N_SCAN_CHANNELS:
if (tlv_len != sizeof(uint32_t)) {
error = EINVAL;
goto parse_out;
}
sc->sc_capa_n_scan_channels =
le32toh(*(const uint32_t *)tlv_data);
break;
case IWM_UCODE_TLV_FW_VERSION:
if (tlv_len != sizeof(uint32_t) * 3) {
error = EINVAL;
goto parse_out;
}
snprintf(sc->sc_fwver, sizeof(sc->sc_fwver),
"%d.%d.%d",
le32toh(((const uint32_t *)tlv_data)[0]),
le32toh(((const uint32_t *)tlv_data)[1]),
le32toh(((const uint32_t *)tlv_data)[2]));
break;
default:
device_printf(sc->sc_dev,
"%s: unknown firmware section %d, abort\n",
__func__, tlv_type);
error = EINVAL;
goto parse_out;
}
len -= roundup(tlv_len, 4);
data += roundup(tlv_len, 4);
}
KASSERT(error == 0, ("unhandled error"));
parse_out:
if (error) {
device_printf(sc->sc_dev, "firmware parse error %d, "
"section type %d\n", error, tlv_type);
}
if (!(sc->sc_capaflags & IWM_UCODE_TLV_FLAGS_PM_CMD_SUPPORT)) {
device_printf(sc->sc_dev,
"device uses unsupported power ops\n");
error = ENOTSUP;
}
out:
if (error) {
fw->fw_status = IWM_FW_STATUS_NONE;
if (fw->fw_fp != NULL)
iwm_fw_info_free(fw);
} else
fw->fw_status = IWM_FW_STATUS_DONE;
wakeup(&sc->sc_fw);
return error;
}
/*
* DMA resource routines
*/
static void
iwm_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
iwm_dma_contig_alloc(bus_dma_tag_t tag, struct iwm_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,
iwm_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:
iwm_dma_contig_free(dma);
return error;
}
static void
iwm_dma_contig_free(struct iwm_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;
}
}
/* fwmem is used to load firmware onto the card */
static int
iwm_alloc_fwmem(struct iwm_softc *sc)
{
/* Must be aligned on a 16-byte boundary. */
return iwm_dma_contig_alloc(sc->sc_dmat, &sc->fw_dma,
sc->sc_fwdmasegsz, 16);
}
/* tx scheduler rings. not used? */
static int
iwm_alloc_sched(struct iwm_softc *sc)
{
/* TX scheduler rings must be aligned on a 1KB boundary. */
return iwm_dma_contig_alloc(sc->sc_dmat, &sc->sched_dma,
nitems(sc->txq) * sizeof(struct iwm_agn_scd_bc_tbl), 1024);
}
/* keep-warm page is used internally by the card. see iwl-fh.h for more info */
static int
iwm_alloc_kw(struct iwm_softc *sc)
{
return iwm_dma_contig_alloc(sc->sc_dmat, &sc->kw_dma, 4096, 4096);
}
/* interrupt cause table */
static int
iwm_alloc_ict(struct iwm_softc *sc)
{
return iwm_dma_contig_alloc(sc->sc_dmat, &sc->ict_dma,
IWM_ICT_SIZE, 1<<IWM_ICT_PADDR_SHIFT);
}
static int
iwm_alloc_rx_ring(struct iwm_softc *sc, struct iwm_rx_ring *ring)
{
bus_size_t size;
int i, error;
ring->cur = 0;
/* Allocate RX descriptors (256-byte aligned). */
size = IWM_RX_RING_COUNT * sizeof(uint32_t);
error = iwm_dma_contig_alloc(sc->sc_dmat, &ring->desc_dma, size, 256);
if (error != 0) {
device_printf(sc->sc_dev,
"could not allocate RX ring DMA memory\n");
goto fail;
}
ring->desc = ring->desc_dma.vaddr;
/* Allocate RX status area (16-byte aligned). */
error = iwm_dma_contig_alloc(sc->sc_dmat, &ring->stat_dma,
sizeof(*ring->stat), 16);
if (error != 0) {
device_printf(sc->sc_dev,
"could not allocate RX status DMA memory\n");
goto fail;
}
ring->stat = ring->stat_dma.vaddr;
/* Create RX buffer DMA tag. */
error = bus_dma_tag_create(sc->sc_dmat, 1, 0,
BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL,
IWM_RBUF_SIZE, 1, IWM_RBUF_SIZE, 0, NULL, NULL, &ring->data_dmat);
if (error != 0) {
device_printf(sc->sc_dev,
"%s: could not create RX buf DMA tag, error %d\n",
__func__, error);
goto fail;
}
/* Allocate spare bus_dmamap_t for iwm_rx_addbuf() */
error = bus_dmamap_create(ring->data_dmat, 0, &ring->spare_map);
if (error != 0) {
device_printf(sc->sc_dev,
"%s: could not create RX buf DMA map, error %d\n",
__func__, error);
goto fail;
}
/*
* Allocate and map RX buffers.
*/
for (i = 0; i < IWM_RX_RING_COUNT; i++) {
struct iwm_rx_data *data = &ring->data[i];
error = bus_dmamap_create(ring->data_dmat, 0, &data->map);
if (error != 0) {
device_printf(sc->sc_dev,
"%s: could not create RX buf DMA map, error %d\n",
__func__, error);
goto fail;
}
data->m = NULL;
if ((error = iwm_rx_addbuf(sc, IWM_RBUF_SIZE, i)) != 0) {
goto fail;
}
}
return 0;
fail: iwm_free_rx_ring(sc, ring);
return error;
}
static void
iwm_disable_rx_dma(struct iwm_softc *sc)
{
/* XXX conditional nic locks are stupid */
/* XXX print out if we can't lock the NIC? */
if (iwm_nic_lock(sc)) {
/* XXX handle if RX stop doesn't finish? */
(void) iwm_pcie_rx_stop(sc);
iwm_nic_unlock(sc);
}
}
static void
iwm_reset_rx_ring(struct iwm_softc *sc, struct iwm_rx_ring *ring)
{
/* Reset the ring state */
ring->cur = 0;
/*
* The hw rx ring index in shared memory must also be cleared,
* otherwise the discrepancy can cause reprocessing chaos.
*/
memset(sc->rxq.stat, 0, sizeof(*sc->rxq.stat));
}
static void
iwm_free_rx_ring(struct iwm_softc *sc, struct iwm_rx_ring *ring)
{
int i;
iwm_dma_contig_free(&ring->desc_dma);
iwm_dma_contig_free(&ring->stat_dma);
for (i = 0; i < IWM_RX_RING_COUNT; i++) {
struct iwm_rx_data *data = &ring->data[i];
if (data->m != NULL) {
bus_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->spare_map != NULL) {
bus_dmamap_destroy(ring->data_dmat, ring->spare_map);
ring->spare_map = NULL;
}
if (ring->data_dmat != NULL) {
bus_dma_tag_destroy(ring->data_dmat);
ring->data_dmat = NULL;
}
}
static int
iwm_alloc_tx_ring(struct iwm_softc *sc, struct iwm_tx_ring *ring, int qid)
{
bus_addr_t paddr;
bus_size_t size;
size_t maxsize;
int nsegments;
int i, error;
ring->qid = qid;
ring->queued = 0;
ring->cur = 0;
/* Allocate TX descriptors (256-byte aligned). */
size = IWM_TX_RING_COUNT * sizeof (struct iwm_tfd);
error = iwm_dma_contig_alloc(sc->sc_dmat, &ring->desc_dma, size, 256);
if (error != 0) {
device_printf(sc->sc_dev,
"could not allocate TX ring DMA memory\n");
goto fail;
}
ring->desc = ring->desc_dma.vaddr;
/*
* We only use rings 0 through 9 (4 EDCA + cmd) so there is no need
* to allocate commands space for other rings.
*/
if (qid > IWM_MVM_CMD_QUEUE)
return 0;
size = IWM_TX_RING_COUNT * sizeof(struct iwm_device_cmd);
error = iwm_dma_contig_alloc(sc->sc_dmat, &ring->cmd_dma, size, 4);
if (error != 0) {
device_printf(sc->sc_dev,
"could not allocate TX cmd DMA memory\n");
goto fail;
}
ring->cmd = ring->cmd_dma.vaddr;
/* FW commands may require more mapped space than packets. */
if (qid == IWM_MVM_CMD_QUEUE) {
maxsize = IWM_RBUF_SIZE;
nsegments = 1;
} else {
maxsize = MCLBYTES;
nsegments = IWM_MAX_SCATTER - 2;
}
error = bus_dma_tag_create(sc->sc_dmat, 1, 0,
BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, maxsize,
nsegments, maxsize, 0, NULL, NULL, &ring->data_dmat);
if (error != 0) {
device_printf(sc->sc_dev, "could not create TX buf DMA tag\n");
goto fail;
}
paddr = ring->cmd_dma.paddr;
for (i = 0; i < IWM_TX_RING_COUNT; i++) {
struct iwm_tx_data *data = &ring->data[i];
data->cmd_paddr = paddr;
data->scratch_paddr = paddr + sizeof(struct iwm_cmd_header)
+ offsetof(struct iwm_tx_cmd, scratch);
paddr += sizeof(struct iwm_device_cmd);
error = bus_dmamap_create(ring->data_dmat, 0, &data->map);
if (error != 0) {
device_printf(sc->sc_dev,
"could not create TX buf DMA map\n");
goto fail;
}
}
KASSERT(paddr == ring->cmd_dma.paddr + size,
("invalid physical address"));
return 0;
fail: iwm_free_tx_ring(sc, ring);
return error;
}
static void
iwm_reset_tx_ring(struct iwm_softc *sc, struct iwm_tx_ring *ring)
{
int i;
for (i = 0; i < IWM_TX_RING_COUNT; i++) {
struct iwm_tx_data *data = &ring->data[i];
if (data->m != NULL) {
bus_dmamap_sync(ring->data_dmat, data->map,
BUS_DMASYNC_POSTWRITE);
bus_dmamap_unload(ring->data_dmat, data->map);
m_freem(data->m);
data->m = NULL;
}
}
/* 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);
ring->queued = 0;
ring->cur = 0;
}
static void
iwm_free_tx_ring(struct iwm_softc *sc, struct iwm_tx_ring *ring)
{
int i;
iwm_dma_contig_free(&ring->desc_dma);
iwm_dma_contig_free(&ring->cmd_dma);
for (i = 0; i < IWM_TX_RING_COUNT; i++) {
struct iwm_tx_data *data = &ring->data[i];
if (data->m != NULL) {
bus_dmamap_sync(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;
}
}
/*
* High-level hardware frobbing routines
*/
static void
iwm_enable_interrupts(struct iwm_softc *sc)
{
sc->sc_intmask = IWM_CSR_INI_SET_MASK;
IWM_WRITE(sc, IWM_CSR_INT_MASK, sc->sc_intmask);
}
static void
iwm_restore_interrupts(struct iwm_softc *sc)
{
IWM_WRITE(sc, IWM_CSR_INT_MASK, sc->sc_intmask);
}
static void
iwm_disable_interrupts(struct iwm_softc *sc)
{
/* disable interrupts */
IWM_WRITE(sc, IWM_CSR_INT_MASK, 0);
/* acknowledge all interrupts */
IWM_WRITE(sc, IWM_CSR_INT, ~0);
IWM_WRITE(sc, IWM_CSR_FH_INT_STATUS, ~0);
}
static void
iwm_ict_reset(struct iwm_softc *sc)
{
iwm_disable_interrupts(sc);
/* Reset ICT table. */
memset(sc->ict_dma.vaddr, 0, IWM_ICT_SIZE);
sc->ict_cur = 0;
/* Set physical address of ICT table (4KB aligned). */
IWM_WRITE(sc, IWM_CSR_DRAM_INT_TBL_REG,
IWM_CSR_DRAM_INT_TBL_ENABLE
| IWM_CSR_DRAM_INIT_TBL_WRITE_POINTER
| IWM_CSR_DRAM_INIT_TBL_WRAP_CHECK
| sc->ict_dma.paddr >> IWM_ICT_PADDR_SHIFT);
/* Switch to ICT interrupt mode in driver. */
sc->sc_flags |= IWM_FLAG_USE_ICT;
/* Re-enable interrupts. */
IWM_WRITE(sc, IWM_CSR_INT, ~0);
iwm_enable_interrupts(sc);
}
/* iwlwifi pcie/trans.c */
/*
* Since this .. hard-resets things, it's time to actually
* mark the first vap (if any) as having no mac context.
* It's annoying, but since the driver is potentially being
* stop/start'ed whilst active (thanks openbsd port!) we
* have to correctly track this.
*/
static void
iwm_stop_device(struct iwm_softc *sc)
{
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
struct ieee80211com *ic = &sc->sc_ic;
struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
int chnl, qid;
uint32_t mask = 0;
/* tell the device to stop sending interrupts */
iwm_disable_interrupts(sc);
/*
* FreeBSD-local: mark the first vap as not-uploaded,
* so the next transition through auth/assoc
* will correctly populate the MAC context.
*/
if (vap) {
struct iwm_vap *iv = IWM_VAP(vap);
iv->is_uploaded = 0;
}
/* device going down, Stop using ICT table */
sc->sc_flags &= ~IWM_FLAG_USE_ICT;
/* stop tx and rx. tx and rx bits, as usual, are from if_iwn */
iwm_write_prph(sc, IWM_SCD_TXFACT, 0);
if (iwm_nic_lock(sc)) {
/* Stop each Tx DMA channel */
for (chnl = 0; chnl < IWM_FH_TCSR_CHNL_NUM; chnl++) {
IWM_WRITE(sc,
IWM_FH_TCSR_CHNL_TX_CONFIG_REG(chnl), 0);
mask |= IWM_FH_TSSR_TX_STATUS_REG_MSK_CHNL_IDLE(chnl);
}
/* Wait for DMA channels to be idle */
if (!iwm_poll_bit(sc, IWM_FH_TSSR_TX_STATUS_REG, mask, mask,
5000)) {
device_printf(sc->sc_dev,
"Failing on timeout while stopping DMA channel: [0x%08x]\n",
IWM_READ(sc, IWM_FH_TSSR_TX_STATUS_REG));
}
iwm_nic_unlock(sc);
}
iwm_disable_rx_dma(sc);
/* Stop RX ring. */
iwm_reset_rx_ring(sc, &sc->rxq);
/* Reset all TX rings. */
for (qid = 0; qid < nitems(sc->txq); qid++)
iwm_reset_tx_ring(sc, &sc->txq[qid]);
/*
* Power-down device's busmaster DMA clocks
*/
iwm_write_prph(sc, IWM_APMG_CLK_DIS_REG, IWM_APMG_CLK_VAL_DMA_CLK_RQT);
DELAY(5);
/* Make sure (redundant) we've released our request to stay awake */
IWM_CLRBITS(sc, IWM_CSR_GP_CNTRL,
IWM_CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ);
/* Stop the device, and put it in low power state */
iwm_apm_stop(sc);
/* Upon stop, the APM issues an interrupt if HW RF kill is set.
* Clean again the interrupt here
*/
iwm_disable_interrupts(sc);
/* stop and reset the on-board processor */
IWM_WRITE(sc, IWM_CSR_RESET, IWM_CSR_RESET_REG_FLAG_SW_RESET);
/*
* Even if we stop the HW, we still want the RF kill
* interrupt
*/
iwm_enable_rfkill_int(sc);
iwm_check_rfkill(sc);
}
/* iwlwifi: mvm/ops.c */
static void
iwm_mvm_nic_config(struct iwm_softc *sc)
{
uint8_t radio_cfg_type, radio_cfg_step, radio_cfg_dash;
uint32_t reg_val = 0;
radio_cfg_type = (sc->sc_fw_phy_config & IWM_FW_PHY_CFG_RADIO_TYPE) >>
IWM_FW_PHY_CFG_RADIO_TYPE_POS;
radio_cfg_step = (sc->sc_fw_phy_config & IWM_FW_PHY_CFG_RADIO_STEP) >>
IWM_FW_PHY_CFG_RADIO_STEP_POS;
radio_cfg_dash = (sc->sc_fw_phy_config & IWM_FW_PHY_CFG_RADIO_DASH) >>
IWM_FW_PHY_CFG_RADIO_DASH_POS;
/* SKU control */
reg_val |= IWM_CSR_HW_REV_STEP(sc->sc_hw_rev) <<
IWM_CSR_HW_IF_CONFIG_REG_POS_MAC_STEP;
reg_val |= IWM_CSR_HW_REV_DASH(sc->sc_hw_rev) <<
IWM_CSR_HW_IF_CONFIG_REG_POS_MAC_DASH;
/* radio configuration */
reg_val |= radio_cfg_type << IWM_CSR_HW_IF_CONFIG_REG_POS_PHY_TYPE;
reg_val |= radio_cfg_step << IWM_CSR_HW_IF_CONFIG_REG_POS_PHY_STEP;
reg_val |= radio_cfg_dash << IWM_CSR_HW_IF_CONFIG_REG_POS_PHY_DASH;
IWM_WRITE(sc, IWM_CSR_HW_IF_CONFIG_REG, reg_val);
IWM_DPRINTF(sc, IWM_DEBUG_RESET,
"Radio type=0x%x-0x%x-0x%x\n", radio_cfg_type,
radio_cfg_step, radio_cfg_dash);
/*
* W/A : NIC is stuck in a reset state after Early PCIe power off
* (PCIe power is lost before PERST# is asserted), causing ME FW
* to lose ownership and not being able to obtain it back.
*/
if (sc->sc_device_family == IWM_DEVICE_FAMILY_7000) {
iwm_set_bits_mask_prph(sc, IWM_APMG_PS_CTRL_REG,
IWM_APMG_PS_CTRL_EARLY_PWR_OFF_RESET_DIS,
~IWM_APMG_PS_CTRL_EARLY_PWR_OFF_RESET_DIS);
}
}
static int
iwm_nic_rx_init(struct iwm_softc *sc)
{
if (!iwm_nic_lock(sc))
return EBUSY;
/*
* Initialize RX ring. This is from the iwn driver.
*/
memset(sc->rxq.stat, 0, sizeof(*sc->rxq.stat));
/* stop DMA */
iwm_disable_rx_dma(sc);
IWM_WRITE(sc, IWM_FH_MEM_RCSR_CHNL0_RBDCB_WPTR, 0);
IWM_WRITE(sc, IWM_FH_MEM_RCSR_CHNL0_FLUSH_RB_REQ, 0);
IWM_WRITE(sc, IWM_FH_RSCSR_CHNL0_RDPTR, 0);
IWM_WRITE(sc, IWM_FH_RSCSR_CHNL0_RBDCB_WPTR_REG, 0);
/* Set physical address of RX ring (256-byte aligned). */
IWM_WRITE(sc,
IWM_FH_RSCSR_CHNL0_RBDCB_BASE_REG, sc->rxq.desc_dma.paddr >> 8);
/* Set physical address of RX status (16-byte aligned). */
IWM_WRITE(sc,
IWM_FH_RSCSR_CHNL0_STTS_WPTR_REG, sc->rxq.stat_dma.paddr >> 4);
/* Enable RX. */
IWM_WRITE(sc, IWM_FH_MEM_RCSR_CHNL0_CONFIG_REG,
IWM_FH_RCSR_RX_CONFIG_CHNL_EN_ENABLE_VAL |
IWM_FH_RCSR_CHNL0_RX_IGNORE_RXF_EMPTY | /* HW bug */
IWM_FH_RCSR_CHNL0_RX_CONFIG_IRQ_DEST_INT_HOST_VAL |
IWM_FH_RCSR_CHNL0_RX_CONFIG_SINGLE_FRAME_MSK |
(IWM_RX_RB_TIMEOUT << IWM_FH_RCSR_RX_CONFIG_REG_IRQ_RBTH_POS) |
IWM_FH_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_4K |
IWM_RX_QUEUE_SIZE_LOG << IWM_FH_RCSR_RX_CONFIG_RBDCB_SIZE_POS);
IWM_WRITE_1(sc, IWM_CSR_INT_COALESCING, IWM_HOST_INT_TIMEOUT_DEF);
/* W/A for interrupt coalescing bug in 7260 and 3160 */
if (sc->host_interrupt_operation_mode)
IWM_SETBITS(sc, IWM_CSR_INT_COALESCING, IWM_HOST_INT_OPER_MODE);
/*
* Thus sayeth el jefe (iwlwifi) via a comment:
*
* This value should initially be 0 (before preparing any
* RBs), should be 8 after preparing the first 8 RBs (for example)
*/
IWM_WRITE(sc, IWM_FH_RSCSR_CHNL0_WPTR, 8);
iwm_nic_unlock(sc);
return 0;
}
static int
iwm_nic_tx_init(struct iwm_softc *sc)
{
int qid;
if (!iwm_nic_lock(sc))
return EBUSY;
/* Deactivate TX scheduler. */
iwm_write_prph(sc, IWM_SCD_TXFACT, 0);
/* Set physical address of "keep warm" page (16-byte aligned). */
IWM_WRITE(sc, IWM_FH_KW_MEM_ADDR_REG, sc->kw_dma.paddr >> 4);
/* Initialize TX rings. */
for (qid = 0; qid < nitems(sc->txq); qid++) {
struct iwm_tx_ring *txq = &sc->txq[qid];
/* Set physical address of TX ring (256-byte aligned). */
IWM_WRITE(sc, IWM_FH_MEM_CBBC_QUEUE(qid),
txq->desc_dma.paddr >> 8);
IWM_DPRINTF(sc, IWM_DEBUG_XMIT,
"%s: loading ring %d descriptors (%p) at %lx\n",
__func__,
qid, txq->desc,
(unsigned long) (txq->desc_dma.paddr >> 8));
}
iwm_write_prph(sc, IWM_SCD_GP_CTRL, IWM_SCD_GP_CTRL_AUTO_ACTIVE_MODE);
iwm_nic_unlock(sc);
return 0;
}
static int
iwm_nic_init(struct iwm_softc *sc)
{
int error;
iwm_apm_init(sc);
if (sc->sc_device_family == IWM_DEVICE_FAMILY_7000)
iwm_set_pwr(sc);
iwm_mvm_nic_config(sc);
if ((error = iwm_nic_rx_init(sc)) != 0)
return error;
/*
* Ditto for TX, from iwn
*/
if ((error = iwm_nic_tx_init(sc)) != 0)
return error;
IWM_DPRINTF(sc, IWM_DEBUG_RESET,
"%s: shadow registers enabled\n", __func__);
IWM_SETBITS(sc, IWM_CSR_MAC_SHADOW_REG_CTRL, 0x800fffff);
return 0;
}
const uint8_t iwm_mvm_ac_to_tx_fifo[] = {
IWM_MVM_TX_FIFO_VO,
IWM_MVM_TX_FIFO_VI,
IWM_MVM_TX_FIFO_BE,
IWM_MVM_TX_FIFO_BK,
};
static int
iwm_enable_txq(struct iwm_softc *sc, int sta_id, int qid, int fifo)
{
if (!iwm_nic_lock(sc)) {
device_printf(sc->sc_dev,
"%s: cannot enable txq %d\n",
__func__,
qid);
return EBUSY;
}
IWM_WRITE(sc, IWM_HBUS_TARG_WRPTR, qid << 8 | 0);
if (qid == IWM_MVM_CMD_QUEUE) {
/* unactivate before configuration */
iwm_write_prph(sc, IWM_SCD_QUEUE_STATUS_BITS(qid),
(0 << IWM_SCD_QUEUE_STTS_REG_POS_ACTIVE)
| (1 << IWM_SCD_QUEUE_STTS_REG_POS_SCD_ACT_EN));
iwm_clear_bits_prph(sc, IWM_SCD_AGGR_SEL, (1 << qid));
iwm_write_prph(sc, IWM_SCD_QUEUE_RDPTR(qid), 0);
iwm_write_mem32(sc, sc->sched_base + IWM_SCD_CONTEXT_QUEUE_OFFSET(qid), 0);
/* Set scheduler window size and frame limit. */
iwm_write_mem32(sc,
sc->sched_base + IWM_SCD_CONTEXT_QUEUE_OFFSET(qid) +
sizeof(uint32_t),
((IWM_FRAME_LIMIT << IWM_SCD_QUEUE_CTX_REG2_WIN_SIZE_POS) &
IWM_SCD_QUEUE_CTX_REG2_WIN_SIZE_MSK) |
((IWM_FRAME_LIMIT << IWM_SCD_QUEUE_CTX_REG2_FRAME_LIMIT_POS) &
IWM_SCD_QUEUE_CTX_REG2_FRAME_LIMIT_MSK));
iwm_write_prph(sc, IWM_SCD_QUEUE_STATUS_BITS(qid),
(1 << IWM_SCD_QUEUE_STTS_REG_POS_ACTIVE) |
(fifo << IWM_SCD_QUEUE_STTS_REG_POS_TXF) |
(1 << IWM_SCD_QUEUE_STTS_REG_POS_WSL) |
IWM_SCD_QUEUE_STTS_REG_MSK);
} else {
struct iwm_scd_txq_cfg_cmd cmd;
int error;
iwm_nic_unlock(sc);
memset(&cmd, 0, sizeof(cmd));
cmd.scd_queue = qid;
cmd.enable = 1;
cmd.sta_id = sta_id;
cmd.tx_fifo = fifo;
cmd.aggregate = 0;
cmd.window = IWM_FRAME_LIMIT;
error = iwm_mvm_send_cmd_pdu(sc, IWM_SCD_QUEUE_CFG, IWM_CMD_SYNC,
sizeof(cmd), &cmd);
if (error) {
device_printf(sc->sc_dev,
"cannot enable txq %d\n", qid);
return error;
}
if (!iwm_nic_lock(sc))
return EBUSY;
}
iwm_write_prph(sc, IWM_SCD_EN_CTRL,
iwm_read_prph(sc, IWM_SCD_EN_CTRL) | qid);
iwm_nic_unlock(sc);
IWM_DPRINTF(sc, IWM_DEBUG_XMIT, "%s: enabled txq %d FIFO %d\n",
__func__, qid, fifo);
return 0;
}
static int
iwm_post_alive(struct iwm_softc *sc)
{
int nwords;
int error, chnl;
uint32_t base;
if (!iwm_nic_lock(sc))
return EBUSY;
base = iwm_read_prph(sc, IWM_SCD_SRAM_BASE_ADDR);
if (sc->sched_base != base) {
device_printf(sc->sc_dev,
"%s: sched addr mismatch: alive: 0x%x prph: 0x%x\n",
__func__, sc->sched_base, base);
}
iwm_ict_reset(sc);
/* Clear TX scheduler state in SRAM. */
nwords = (IWM_SCD_TRANS_TBL_MEM_UPPER_BOUND -
IWM_SCD_CONTEXT_MEM_LOWER_BOUND)
/ sizeof(uint32_t);
error = iwm_write_mem(sc,
sc->sched_base + IWM_SCD_CONTEXT_MEM_LOWER_BOUND,
NULL, nwords);
if (error)
goto out;
/* Set physical address of TX scheduler rings (1KB aligned). */
iwm_write_prph(sc, IWM_SCD_DRAM_BASE_ADDR, sc->sched_dma.paddr >> 10);
iwm_write_prph(sc, IWM_SCD_CHAINEXT_EN, 0);
iwm_nic_unlock(sc);
/* enable command channel */
error = iwm_enable_txq(sc, 0 /* unused */, IWM_MVM_CMD_QUEUE, 7);
if (error)
return error;
if (!iwm_nic_lock(sc))
return EBUSY;
iwm_write_prph(sc, IWM_SCD_TXFACT, 0xff);
/* Enable DMA channels. */
for (chnl = 0; chnl < IWM_FH_TCSR_CHNL_NUM; chnl++) {
IWM_WRITE(sc, IWM_FH_TCSR_CHNL_TX_CONFIG_REG(chnl),
IWM_FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_ENABLE |
IWM_FH_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_ENABLE);
}
IWM_SETBITS(sc, IWM_FH_TX_CHICKEN_BITS_REG,
IWM_FH_TX_CHICKEN_BITS_SCD_AUTO_RETRY_EN);
/* Enable L1-Active */
if (sc->sc_device_family != IWM_DEVICE_FAMILY_8000) {
iwm_clear_bits_prph(sc, IWM_APMG_PCIDEV_STT_REG,
IWM_APMG_PCIDEV_STT_VAL_L1_ACT_DIS);
}
out:
iwm_nic_unlock(sc);
return error;
}
/*
* NVM read access and content parsing. We do not support
* external NVM or writing NVM.
* iwlwifi/mvm/nvm.c
*/
/* list of NVM sections we are allowed/need to read */
const int nvm_to_read[] = {
IWM_NVM_SECTION_TYPE_HW,
IWM_NVM_SECTION_TYPE_SW,
IWM_NVM_SECTION_TYPE_REGULATORY,
IWM_NVM_SECTION_TYPE_CALIBRATION,
IWM_NVM_SECTION_TYPE_PRODUCTION,
IWM_NVM_SECTION_TYPE_HW_8000,
IWM_NVM_SECTION_TYPE_MAC_OVERRIDE,
IWM_NVM_SECTION_TYPE_PHY_SKU,
};
/* Default NVM size to read */
#define IWM_NVM_DEFAULT_CHUNK_SIZE (2*1024)
#define IWM_MAX_NVM_SECTION_SIZE 8192
#define IWM_NVM_WRITE_OPCODE 1
#define IWM_NVM_READ_OPCODE 0
/* load nvm chunk response */
enum {
IWM_READ_NVM_CHUNK_SUCCEED = 0,
IWM_READ_NVM_CHUNK_NOT_VALID_ADDRESS = 1
};
static int
iwm_nvm_read_chunk(struct iwm_softc *sc, uint16_t section,
uint16_t offset, uint16_t length, uint8_t *data, uint16_t *len)
{
offset = 0;
struct iwm_nvm_access_cmd nvm_access_cmd = {
.offset = htole16(offset),
.length = htole16(length),
.type = htole16(section),
.op_code = IWM_NVM_READ_OPCODE,
};
struct iwm_nvm_access_resp *nvm_resp;
struct iwm_rx_packet *pkt;
struct iwm_host_cmd cmd = {
.id = IWM_NVM_ACCESS_CMD,
.flags = IWM_CMD_WANT_SKB | IWM_CMD_SEND_IN_RFKILL,
.data = { &nvm_access_cmd, },
};
int ret, bytes_read, offset_read;
uint8_t *resp_data;
cmd.len[0] = sizeof(struct iwm_nvm_access_cmd);
ret = iwm_send_cmd(sc, &cmd);
if (ret) {
device_printf(sc->sc_dev,
"Could not send NVM_ACCESS command (error=%d)\n", ret);
return ret;
}
pkt = cmd.resp_pkt;
if (pkt->hdr.flags & IWM_CMD_FAILED_MSK) {
device_printf(sc->sc_dev,
"Bad return from IWM_NVM_ACCES_COMMAND (0x%08X)\n",
pkt->hdr.flags);
ret = EIO;
goto exit;
}
/* Extract NVM response */
nvm_resp = (void *)pkt->data;
ret = le16toh(nvm_resp->status);
bytes_read = le16toh(nvm_resp->length);
offset_read = le16toh(nvm_resp->offset);
resp_data = nvm_resp->data;
if (ret) {
if ((offset != 0) &&
(ret == IWM_READ_NVM_CHUNK_NOT_VALID_ADDRESS)) {
/*
* meaning of NOT_VALID_ADDRESS:
* driver try to read chunk from address that is
* multiple of 2K and got an error since addr is empty.
* meaning of (offset != 0): driver already
* read valid data from another chunk so this case
* is not an error.
*/
IWM_DPRINTF(sc, IWM_DEBUG_EEPROM | IWM_DEBUG_RESET,
"NVM access command failed on offset 0x%x since that section size is multiple 2K\n",
offset);
*len = 0;
ret = 0;
} else {
IWM_DPRINTF(sc, IWM_DEBUG_EEPROM | IWM_DEBUG_RESET,
"NVM access command failed with status %d\n", ret);
ret = EIO;
}
goto exit;
}
if (offset_read != offset) {
device_printf(sc->sc_dev,
"NVM ACCESS response with invalid offset %d\n",
offset_read);
ret = EINVAL;
goto exit;
}
if (bytes_read > length) {
device_printf(sc->sc_dev,
"NVM ACCESS response with too much data "
"(%d bytes requested, %d bytes received)\n",
length, bytes_read);
ret = EINVAL;
goto exit;
}
memcpy(data + offset, resp_data, bytes_read);
*len = bytes_read;
exit:
iwm_free_resp(sc, &cmd);
return ret;
}
/*
* Reads an NVM section completely.
* NICs prior to 7000 family don't have a real NVM, but just read
* section 0 which is the EEPROM. Because the EEPROM reading is unlimited
* by uCode, we need to manually check in this case that we don't
* overflow and try to read more than the EEPROM size.
* For 7000 family NICs, we supply the maximal size we can read, and
* the uCode fills the response with as much data as we can,
* without overflowing, so no check is needed.
*/
static int
iwm_nvm_read_section(struct iwm_softc *sc,
uint16_t section, uint8_t *data, uint16_t *len, size_t max_len)
{
uint16_t chunklen, seglen;
int error = 0;
IWM_DPRINTF(sc, IWM_DEBUG_RESET,
"reading NVM section %d\n", section);
chunklen = seglen = IWM_NVM_DEFAULT_CHUNK_SIZE;
*len = 0;
/* Read NVM chunks until exhausted (reading less than requested) */
while (seglen == chunklen && *len < max_len) {
error = iwm_nvm_read_chunk(sc,
section, *len, chunklen, data, &seglen);
if (error) {
IWM_DPRINTF(sc, IWM_DEBUG_RESET,
"Cannot read from NVM section "
"%d at offset %d\n", section, *len);
return error;
}
*len += seglen;
}
IWM_DPRINTF(sc, IWM_DEBUG_RESET,
"NVM section %d read completed (%d bytes, error=%d)\n",
section, *len, error);
return error;
}
/*
* BEGIN IWM_NVM_PARSE
*/
/* iwlwifi/iwl-nvm-parse.c */
/* NVM offsets (in words) definitions */
enum iwm_nvm_offsets {
/* NVM HW-Section offset (in words) definitions */
IWM_HW_ADDR = 0x15,
/* NVM SW-Section offset (in words) definitions */
IWM_NVM_SW_SECTION = 0x1C0,
IWM_NVM_VERSION = 0,
IWM_RADIO_CFG = 1,
IWM_SKU = 2,
IWM_N_HW_ADDRS = 3,
IWM_NVM_CHANNELS = 0x1E0 - IWM_NVM_SW_SECTION,
/* NVM calibration section offset (in words) definitions */
IWM_NVM_CALIB_SECTION = 0x2B8,
IWM_XTAL_CALIB = 0x316 - IWM_NVM_CALIB_SECTION
};
enum iwm_8000_nvm_offsets {
/* NVM HW-Section offset (in words) definitions */
IWM_HW_ADDR0_WFPM_8000 = 0x12,
IWM_HW_ADDR1_WFPM_8000 = 0x16,
IWM_HW_ADDR0_PCIE_8000 = 0x8A,
IWM_HW_ADDR1_PCIE_8000 = 0x8E,
IWM_MAC_ADDRESS_OVERRIDE_8000 = 1,
/* NVM SW-Section offset (in words) definitions */
IWM_NVM_SW_SECTION_8000 = 0x1C0,
IWM_NVM_VERSION_8000 = 0,
IWM_RADIO_CFG_8000 = 0,
IWM_SKU_8000 = 2,
IWM_N_HW_ADDRS_8000 = 3,
/* NVM REGULATORY -Section offset (in words) definitions */
IWM_NVM_CHANNELS_8000 = 0,
IWM_NVM_LAR_OFFSET_8000_OLD = 0x4C7,
IWM_NVM_LAR_OFFSET_8000 = 0x507,
IWM_NVM_LAR_ENABLED_8000 = 0x7,
/* NVM calibration section offset (in words) definitions */
IWM_NVM_CALIB_SECTION_8000 = 0x2B8,
IWM_XTAL_CALIB_8000 = 0x316 - IWM_NVM_CALIB_SECTION_8000
};
/* SKU Capabilities (actual values from NVM definition) */
enum nvm_sku_bits {
IWM_NVM_SKU_CAP_BAND_24GHZ = (1 << 0),
IWM_NVM_SKU_CAP_BAND_52GHZ = (1 << 1),
IWM_NVM_SKU_CAP_11N_ENABLE = (1 << 2),
IWM_NVM_SKU_CAP_11AC_ENABLE = (1 << 3),
};
/* radio config bits (actual values from NVM definition) */
#define IWM_NVM_RF_CFG_DASH_MSK(x) (x & 0x3) /* bits 0-1 */
#define IWM_NVM_RF_CFG_STEP_MSK(x) ((x >> 2) & 0x3) /* bits 2-3 */
#define IWM_NVM_RF_CFG_TYPE_MSK(x) ((x >> 4) & 0x3) /* bits 4-5 */
#define IWM_NVM_RF_CFG_PNUM_MSK(x) ((x >> 6) & 0x3) /* bits 6-7 */
#define IWM_NVM_RF_CFG_TX_ANT_MSK(x) ((x >> 8) & 0xF) /* bits 8-11 */
#define IWM_NVM_RF_CFG_RX_ANT_MSK(x) ((x >> 12) & 0xF) /* bits 12-15 */
#define IWM_NVM_RF_CFG_FLAVOR_MSK_8000(x) (x & 0xF)
#define IWM_NVM_RF_CFG_DASH_MSK_8000(x) ((x >> 4) & 0xF)
#define IWM_NVM_RF_CFG_STEP_MSK_8000(x) ((x >> 8) & 0xF)
#define IWM_NVM_RF_CFG_TYPE_MSK_8000(x) ((x >> 12) & 0xFFF)
#define IWM_NVM_RF_CFG_TX_ANT_MSK_8000(x) ((x >> 24) & 0xF)
#define IWM_NVM_RF_CFG_RX_ANT_MSK_8000(x) ((x >> 28) & 0xF)
#define DEFAULT_MAX_TX_POWER 16
/**
* enum iwm_nvm_channel_flags - channel flags in NVM
* @IWM_NVM_CHANNEL_VALID: channel is usable for this SKU/geo
* @IWM_NVM_CHANNEL_IBSS: usable as an IBSS channel
* @IWM_NVM_CHANNEL_ACTIVE: active scanning allowed
* @IWM_NVM_CHANNEL_RADAR: radar detection required
* XXX cannot find this (DFS) flag in iwl-nvm-parse.c
* @IWM_NVM_CHANNEL_DFS: dynamic freq selection candidate
* @IWM_NVM_CHANNEL_WIDE: 20 MHz channel okay (?)
* @IWM_NVM_CHANNEL_40MHZ: 40 MHz channel okay (?)
* @IWM_NVM_CHANNEL_80MHZ: 80 MHz channel okay (?)
* @IWM_NVM_CHANNEL_160MHZ: 160 MHz channel okay (?)
*/
enum iwm_nvm_channel_flags {
IWM_NVM_CHANNEL_VALID = (1 << 0),
IWM_NVM_CHANNEL_IBSS = (1 << 1),
IWM_NVM_CHANNEL_ACTIVE = (1 << 3),
IWM_NVM_CHANNEL_RADAR = (1 << 4),
IWM_NVM_CHANNEL_DFS = (1 << 7),
IWM_NVM_CHANNEL_WIDE = (1 << 8),
IWM_NVM_CHANNEL_40MHZ = (1 << 9),
IWM_NVM_CHANNEL_80MHZ = (1 << 10),
IWM_NVM_CHANNEL_160MHZ = (1 << 11),
};
/*
* Translate EEPROM flags to net80211.
*/
static uint32_t
iwm_eeprom_channel_flags(uint16_t ch_flags)
{
uint32_t nflags;
nflags = 0;
if ((ch_flags & IWM_NVM_CHANNEL_ACTIVE) == 0)
nflags |= IEEE80211_CHAN_PASSIVE;
if ((ch_flags & IWM_NVM_CHANNEL_IBSS) == 0)
nflags |= IEEE80211_CHAN_NOADHOC;
if (ch_flags & IWM_NVM_CHANNEL_RADAR) {
nflags |= IEEE80211_CHAN_DFS;
/* Just in case. */
nflags |= IEEE80211_CHAN_NOADHOC;
}
return (nflags);
}
static void
iwm_add_channel_band(struct iwm_softc *sc, struct ieee80211_channel chans[],
int maxchans, int *nchans, int ch_idx, size_t ch_num,
const uint8_t bands[])
{
const uint16_t * const nvm_ch_flags = sc->sc_nvm.nvm_ch_flags;
uint32_t nflags;
uint16_t ch_flags;
uint8_t ieee;
int error;
for (; ch_idx < ch_num; ch_idx++) {
ch_flags = le16_to_cpup(nvm_ch_flags + ch_idx);
if (sc->sc_device_family == IWM_DEVICE_FAMILY_7000)
ieee = iwm_nvm_channels[ch_idx];
else
ieee = iwm_nvm_channels_8000[ch_idx];
if (!(ch_flags & IWM_NVM_CHANNEL_VALID)) {
IWM_DPRINTF(sc, IWM_DEBUG_EEPROM,
"Ch. %d Flags %x [%sGHz] - No traffic\n",
ieee, ch_flags,
(ch_idx >= IWM_NUM_2GHZ_CHANNELS) ?
"5.2" : "2.4");
continue;
}
nflags = iwm_eeprom_channel_flags(ch_flags);
error = ieee80211_add_channel(chans, maxchans, nchans,
ieee, 0, 0, nflags, bands);
if (error != 0)
break;
IWM_DPRINTF(sc, IWM_DEBUG_EEPROM,
"Ch. %d Flags %x [%sGHz] - Added\n",
ieee, ch_flags,
(ch_idx >= IWM_NUM_2GHZ_CHANNELS) ?
"5.2" : "2.4");
}
}
static void
iwm_init_channel_map(struct ieee80211com *ic, int maxchans, int *nchans,
struct ieee80211_channel chans[])
{
struct iwm_softc *sc = ic->ic_softc;
struct iwm_nvm_data *data = &sc->sc_nvm;
uint8_t bands[IEEE80211_MODE_BYTES];
size_t ch_num;
memset(bands, 0, sizeof(bands));
/* 1-13: 11b/g channels. */
setbit(bands, IEEE80211_MODE_11B);
setbit(bands, IEEE80211_MODE_11G);
iwm_add_channel_band(sc, chans, maxchans, nchans, 0,
IWM_NUM_2GHZ_CHANNELS - 1, bands);
/* 14: 11b channel only. */
clrbit(bands, IEEE80211_MODE_11G);
iwm_add_channel_band(sc, chans, maxchans, nchans,
IWM_NUM_2GHZ_CHANNELS - 1, IWM_NUM_2GHZ_CHANNELS, bands);
if (data->sku_cap_band_52GHz_enable) {
if (sc->sc_device_family == IWM_DEVICE_FAMILY_7000)
ch_num = nitems(iwm_nvm_channels);
else
ch_num = nitems(iwm_nvm_channels_8000);
memset(bands, 0, sizeof(bands));
setbit(bands, IEEE80211_MODE_11A);
iwm_add_channel_band(sc, chans, maxchans, nchans,
IWM_NUM_2GHZ_CHANNELS, ch_num, bands);
}
}
static void
iwm_set_hw_address_8000(struct iwm_softc *sc, struct iwm_nvm_data *data,
const uint16_t *mac_override, const uint16_t *nvm_hw)
{
const uint8_t *hw_addr;
if (mac_override) {
static const uint8_t reserved_mac[] = {
0x02, 0xcc, 0xaa, 0xff, 0xee, 0x00
};
hw_addr = (const uint8_t *)(mac_override +
IWM_MAC_ADDRESS_OVERRIDE_8000);
/*
* Store the MAC address from MAO section.
* No byte swapping is required in MAO section
*/
IEEE80211_ADDR_COPY(data->hw_addr, hw_addr);
/*
* Force the use of the OTP MAC address in case of reserved MAC
* address in the NVM, or if address is given but invalid.
*/
if (!IEEE80211_ADDR_EQ(reserved_mac, hw_addr) &&
!IEEE80211_ADDR_EQ(ieee80211broadcastaddr, data->hw_addr) &&
iwm_is_valid_ether_addr(data->hw_addr) &&
!IEEE80211_IS_MULTICAST(data->hw_addr))
return;
IWM_DPRINTF(sc, IWM_DEBUG_RESET,
"%s: mac address from nvm override section invalid\n",
__func__);
}
if (nvm_hw) {
/* read the mac address from WFMP registers */
uint32_t mac_addr0 =
htole32(iwm_read_prph(sc, IWM_WFMP_MAC_ADDR_0));
uint32_t mac_addr1 =
htole32(iwm_read_prph(sc, IWM_WFMP_MAC_ADDR_1));
hw_addr = (const uint8_t *)&mac_addr0;
data->hw_addr[0] = hw_addr[3];
data->hw_addr[1] = hw_addr[2];
data->hw_addr[2] = hw_addr[1];
data->hw_addr[3] = hw_addr[0];
hw_addr = (const uint8_t *)&mac_addr1;
data->hw_addr[4] = hw_addr[1];
data->hw_addr[5] = hw_addr[0];
return;
}
device_printf(sc->sc_dev, "%s: mac address not found\n", __func__);
memset(data->hw_addr, 0, sizeof(data->hw_addr));
}
static int
iwm_get_sku(const struct iwm_softc *sc, const uint16_t *nvm_sw,
const uint16_t *phy_sku)
{
if (sc->sc_device_family != IWM_DEVICE_FAMILY_8000)
return le16_to_cpup(nvm_sw + IWM_SKU);
return le32_to_cpup((const uint32_t *)(phy_sku + IWM_SKU_8000));
}
static int
iwm_get_nvm_version(const struct iwm_softc *sc, const uint16_t *nvm_sw)
{
if (sc->sc_device_family != IWM_DEVICE_FAMILY_8000)
return le16_to_cpup(nvm_sw + IWM_NVM_VERSION);
else
return le32_to_cpup((const uint32_t *)(nvm_sw +
IWM_NVM_VERSION_8000));
}
static int
iwm_get_radio_cfg(const struct iwm_softc *sc, const uint16_t *nvm_sw,
const uint16_t *phy_sku)
{
if (sc->sc_device_family != IWM_DEVICE_FAMILY_8000)
return le16_to_cpup(nvm_sw + IWM_RADIO_CFG);
return le32_to_cpup((const uint32_t *)(phy_sku + IWM_RADIO_CFG_8000));
}
static int
iwm_get_n_hw_addrs(const struct iwm_softc *sc, const uint16_t *nvm_sw)
{
int n_hw_addr;
if (sc->sc_device_family != IWM_DEVICE_FAMILY_8000)
return le16_to_cpup(nvm_sw + IWM_N_HW_ADDRS);
n_hw_addr = le32_to_cpup((const uint32_t *)(nvm_sw + IWM_N_HW_ADDRS_8000));
return n_hw_addr & IWM_N_HW_ADDR_MASK;
}
static void
iwm_set_radio_cfg(const struct iwm_softc *sc, struct iwm_nvm_data *data,
uint32_t radio_cfg)
{
if (sc->sc_device_family != IWM_DEVICE_FAMILY_8000) {
data->radio_cfg_type = IWM_NVM_RF_CFG_TYPE_MSK(radio_cfg);
data->radio_cfg_step = IWM_NVM_RF_CFG_STEP_MSK(radio_cfg);
data->radio_cfg_dash = IWM_NVM_RF_CFG_DASH_MSK(radio_cfg);
data->radio_cfg_pnum = IWM_NVM_RF_CFG_PNUM_MSK(radio_cfg);
return;
}
/* set the radio configuration for family 8000 */
data->radio_cfg_type = IWM_NVM_RF_CFG_TYPE_MSK_8000(radio_cfg);
data->radio_cfg_step = IWM_NVM_RF_CFG_STEP_MSK_8000(radio_cfg);
data->radio_cfg_dash = IWM_NVM_RF_CFG_DASH_MSK_8000(radio_cfg);
data->radio_cfg_pnum = IWM_NVM_RF_CFG_FLAVOR_MSK_8000(radio_cfg);
data->valid_tx_ant = IWM_NVM_RF_CFG_TX_ANT_MSK_8000(radio_cfg);
data->valid_rx_ant = IWM_NVM_RF_CFG_RX_ANT_MSK_8000(radio_cfg);
}
static int
iwm_parse_nvm_data(struct iwm_softc *sc,
const uint16_t *nvm_hw, const uint16_t *nvm_sw,
const uint16_t *nvm_calib, const uint16_t *mac_override,
const uint16_t *phy_sku, const uint16_t *regulatory)
{
struct iwm_nvm_data *data = &sc->sc_nvm;
uint8_t hw_addr[IEEE80211_ADDR_LEN];
uint32_t sku, radio_cfg;
data->nvm_version = iwm_get_nvm_version(sc, nvm_sw);
radio_cfg = iwm_get_radio_cfg(sc, nvm_sw, phy_sku);
iwm_set_radio_cfg(sc, data, radio_cfg);
sku = iwm_get_sku(sc, nvm_sw, phy_sku);
data->sku_cap_band_24GHz_enable = sku & IWM_NVM_SKU_CAP_BAND_24GHZ;
data->sku_cap_band_52GHz_enable = sku & IWM_NVM_SKU_CAP_BAND_52GHZ;
data->sku_cap_11n_enable = 0;
data->n_hw_addrs = iwm_get_n_hw_addrs(sc, nvm_sw);
/* The byte order is little endian 16 bit, meaning 214365 */
if (sc->sc_device_family == IWM_DEVICE_FAMILY_7000) {
IEEE80211_ADDR_COPY(hw_addr, nvm_hw + IWM_HW_ADDR);
data->hw_addr[0] = hw_addr[1];
data->hw_addr[1] = hw_addr[0];
data->hw_addr[2] = hw_addr[3];
data->hw_addr[3] = hw_addr[2];
data->hw_addr[4] = hw_addr[5];
data->hw_addr[5] = hw_addr[4];
} else {
iwm_set_hw_address_8000(sc, data, mac_override, nvm_hw);
}
if (sc->sc_device_family == IWM_DEVICE_FAMILY_7000) {
memcpy(data->nvm_ch_flags, &nvm_sw[IWM_NVM_CHANNELS],
IWM_NUM_CHANNELS * sizeof(uint16_t));
} else {
memcpy(data->nvm_ch_flags, &regulatory[IWM_NVM_CHANNELS_8000],
IWM_NUM_CHANNELS_8000 * sizeof(uint16_t));
}
return 0;
}
/*
* END NVM PARSE
*/
static int
iwm_parse_nvm_sections(struct iwm_softc *sc, struct iwm_nvm_section *sections)
{
const uint16_t *hw, *sw, *calib, *regulatory, *mac_override, *phy_sku;
/* Checking for required sections */
if (sc->sc_device_family == IWM_DEVICE_FAMILY_7000) {
if (!sections[IWM_NVM_SECTION_TYPE_SW].data ||
!sections[IWM_NVM_SECTION_TYPE_HW].data) {
device_printf(sc->sc_dev,
"Can't parse empty OTP/NVM sections\n");
return ENOENT;
}
hw = (const uint16_t *) sections[IWM_NVM_SECTION_TYPE_HW].data;
} else if (sc->sc_device_family == IWM_DEVICE_FAMILY_8000) {
/* SW and REGULATORY sections are mandatory */
if (!sections[IWM_NVM_SECTION_TYPE_SW].data ||
!sections[IWM_NVM_SECTION_TYPE_REGULATORY].data) {
device_printf(sc->sc_dev,
"Can't parse empty OTP/NVM sections\n");
return ENOENT;
}
/* MAC_OVERRIDE or at least HW section must exist */
if (!sections[IWM_NVM_SECTION_TYPE_HW_8000].data &&
!sections[IWM_NVM_SECTION_TYPE_MAC_OVERRIDE].data) {
device_printf(sc->sc_dev,
"Can't parse mac_address, empty sections\n");
return ENOENT;
}
/* PHY_SKU section is mandatory in B0 */
if (!sections[IWM_NVM_SECTION_TYPE_PHY_SKU].data) {
device_printf(sc->sc_dev,
"Can't parse phy_sku in B0, empty sections\n");
return ENOENT;
}
hw = (const uint16_t *)
sections[IWM_NVM_SECTION_TYPE_HW_8000].data;
} else {
panic("unknown device family %d\n", sc->sc_device_family);
}
sw = (const uint16_t *)sections[IWM_NVM_SECTION_TYPE_SW].data;
calib = (const uint16_t *)
sections[IWM_NVM_SECTION_TYPE_CALIBRATION].data;
regulatory = (const uint16_t *)
sections[IWM_NVM_SECTION_TYPE_REGULATORY].data;
mac_override = (const uint16_t *)
sections[IWM_NVM_SECTION_TYPE_MAC_OVERRIDE].data;
phy_sku = (const uint16_t *)sections[IWM_NVM_SECTION_TYPE_PHY_SKU].data;
return iwm_parse_nvm_data(sc, hw, sw, calib, mac_override,
phy_sku, regulatory);
}
static int
iwm_nvm_init(struct iwm_softc *sc)
{
struct iwm_nvm_section nvm_sections[IWM_NVM_NUM_OF_SECTIONS];
int i, section, error;
uint16_t len;
uint8_t *buf;
const size_t bufsz = IWM_MAX_NVM_SECTION_SIZE;
memset(nvm_sections, 0 , sizeof(nvm_sections));
buf = malloc(bufsz, M_DEVBUF, M_NOWAIT);
if (buf == NULL)
return ENOMEM;
for (i = 0; i < nitems(nvm_to_read); i++) {
section = nvm_to_read[i];
KASSERT(section <= nitems(nvm_sections),
("too many sections"));
error = iwm_nvm_read_section(sc, section, buf, &len, bufsz);
if (error) {
error = 0;
continue;
}
nvm_sections[section].data = malloc(len, M_DEVBUF, M_NOWAIT);
if (nvm_sections[section].data == NULL) {
error = ENOMEM;
break;
}
memcpy(nvm_sections[section].data, buf, len);
nvm_sections[section].length = len;
}
free(buf, M_DEVBUF);
if (error == 0)
error = iwm_parse_nvm_sections(sc, nvm_sections);
for (i = 0; i < IWM_NVM_NUM_OF_SECTIONS; i++) {
if (nvm_sections[i].data != NULL)
free(nvm_sections[i].data, M_DEVBUF);
}
return error;
}
/*
* Firmware loading gunk. This is kind of a weird hybrid between the
* iwn driver and the Linux iwlwifi driver.
*/
static int
iwm_firmware_load_sect(struct iwm_softc *sc, uint32_t dst_addr,
const uint8_t *section, uint32_t byte_cnt)
{
int error = EINVAL;
uint32_t chunk_sz, offset;
chunk_sz = MIN(IWM_FH_MEM_TB_MAX_LENGTH, byte_cnt);
for (offset = 0; offset < byte_cnt; offset += chunk_sz) {
uint32_t addr, len;
const uint8_t *data;
addr = dst_addr + offset;
len = MIN(chunk_sz, byte_cnt - offset);
data = section + offset;
error = iwm_firmware_load_chunk(sc, addr, data, len);
if (error)
break;
}
return error;
}
static int
iwm_firmware_load_chunk(struct iwm_softc *sc, uint32_t dst_addr,
const uint8_t *chunk, uint32_t byte_cnt)
{
struct iwm_dma_info *dma = &sc->fw_dma;
int error;
/* Copy firmware chunk into pre-allocated DMA-safe memory. */
memcpy(dma->vaddr, chunk, byte_cnt);
bus_dmamap_sync(dma->tag, dma->map, BUS_DMASYNC_PREWRITE);
if (dst_addr >= IWM_FW_MEM_EXTENDED_START &&
dst_addr <= IWM_FW_MEM_EXTENDED_END) {
iwm_set_bits_prph(sc, IWM_LMPM_CHICK,
IWM_LMPM_CHICK_EXTENDED_ADDR_SPACE);
}
sc->sc_fw_chunk_done = 0;
if (!iwm_nic_lock(sc))
return EBUSY;
IWM_WRITE(sc, IWM_FH_TCSR_CHNL_TX_CONFIG_REG(IWM_FH_SRVC_CHNL),
IWM_FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_PAUSE);
IWM_WRITE(sc, IWM_FH_SRVC_CHNL_SRAM_ADDR_REG(IWM_FH_SRVC_CHNL),
dst_addr);
IWM_WRITE(sc, IWM_FH_TFDIB_CTRL0_REG(IWM_FH_SRVC_CHNL),
dma->paddr & IWM_FH_MEM_TFDIB_DRAM_ADDR_LSB_MSK);
IWM_WRITE(sc, IWM_FH_TFDIB_CTRL1_REG(IWM_FH_SRVC_CHNL),
(iwm_get_dma_hi_addr(dma->paddr)
<< IWM_FH_MEM_TFDIB_REG1_ADDR_BITSHIFT) | byte_cnt);
IWM_WRITE(sc, IWM_FH_TCSR_CHNL_TX_BUF_STS_REG(IWM_FH_SRVC_CHNL),
1 << IWM_FH_TCSR_CHNL_TX_BUF_STS_REG_POS_TB_NUM |
1 << IWM_FH_TCSR_CHNL_TX_BUF_STS_REG_POS_TB_IDX |
IWM_FH_TCSR_CHNL_TX_BUF_STS_REG_VAL_TFDB_VALID);
IWM_WRITE(sc, IWM_FH_TCSR_CHNL_TX_CONFIG_REG(IWM_FH_SRVC_CHNL),
IWM_FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_ENABLE |
IWM_FH_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_DISABLE |
IWM_FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_HOST_ENDTFD);
iwm_nic_unlock(sc);
/* wait 1s for this segment to load */
while (!sc->sc_fw_chunk_done)
if ((error = msleep(&sc->sc_fw, &sc->sc_mtx, 0, "iwmfw", hz)) != 0)
break;
if (!sc->sc_fw_chunk_done) {
device_printf(sc->sc_dev,
"fw chunk addr 0x%x len %d failed to load\n",
dst_addr, byte_cnt);
}
if (dst_addr >= IWM_FW_MEM_EXTENDED_START &&
dst_addr <= IWM_FW_MEM_EXTENDED_END && iwm_nic_lock(sc)) {
iwm_clear_bits_prph(sc, IWM_LMPM_CHICK,
IWM_LMPM_CHICK_EXTENDED_ADDR_SPACE);
iwm_nic_unlock(sc);
}
return error;
}
int
iwm_load_cpu_sections_8000(struct iwm_softc *sc, struct iwm_fw_sects *fws,
int cpu, int *first_ucode_section)
{
int shift_param;
int i, error = 0, sec_num = 0x1;
uint32_t val, last_read_idx = 0;
const void *data;
uint32_t dlen;
uint32_t offset;
if (cpu == 1) {
shift_param = 0;
*first_ucode_section = 0;
} else {
shift_param = 16;
(*first_ucode_section)++;
}
for (i = *first_ucode_section; i < IWM_UCODE_SECT_MAX; i++) {
last_read_idx = i;
data = fws->fw_sect[i].fws_data;
dlen = fws->fw_sect[i].fws_len;
offset = fws->fw_sect[i].fws_devoff;
/*
* CPU1_CPU2_SEPARATOR_SECTION delimiter - separate between
* CPU1 to CPU2.
* PAGING_SEPARATOR_SECTION delimiter - separate between
* CPU2 non paged to CPU2 paging sec.
*/
if (!data || offset == IWM_CPU1_CPU2_SEPARATOR_SECTION ||
offset == IWM_PAGING_SEPARATOR_SECTION)
break;
IWM_DPRINTF(sc, IWM_DEBUG_RESET,
"LOAD FIRMWARE chunk %d offset 0x%x len %d for cpu %d\n",
i, offset, dlen, cpu);
if (dlen > sc->sc_fwdmasegsz) {
IWM_DPRINTF(sc, IWM_DEBUG_RESET,
"chunk %d too large (%d bytes)\n", i, dlen);
error = EFBIG;
} else {
error = iwm_firmware_load_sect(sc, offset, data, dlen);
}
if (error) {
device_printf(sc->sc_dev,
"could not load firmware chunk %d (error %d)\n",
i, error);
return error;
}
/* Notify the ucode of the loaded section number and status */
if (iwm_nic_lock(sc)) {
val = IWM_READ(sc, IWM_FH_UCODE_LOAD_STATUS);
val = val | (sec_num << shift_param);
IWM_WRITE(sc, IWM_FH_UCODE_LOAD_STATUS, val);
sec_num = (sec_num << 1) | 0x1;
iwm_nic_unlock(sc);
/*
* The firmware won't load correctly without this delay.
*/
DELAY(8000);
}
}
*first_ucode_section = last_read_idx;
if (iwm_nic_lock(sc)) {
if (cpu == 1)
IWM_WRITE(sc, IWM_FH_UCODE_LOAD_STATUS, 0xFFFF);
else
IWM_WRITE(sc, IWM_FH_UCODE_LOAD_STATUS, 0xFFFFFFFF);
iwm_nic_unlock(sc);
}
return 0;
}
int
iwm_load_firmware_8000(struct iwm_softc *sc, enum iwm_ucode_type ucode_type)
{
struct iwm_fw_sects *fws;
int error = 0;
int first_ucode_section;
IWM_DPRINTF(sc, IWM_DEBUG_RESET, "loading ucode type %d\n",
ucode_type);
fws = &sc->sc_fw.fw_sects[ucode_type];
/* configure the ucode to be ready to get the secured image */
/* release CPU reset */
iwm_write_prph(sc, IWM_RELEASE_CPU_RESET, IWM_RELEASE_CPU_RESET_BIT);
/* load to FW the binary Secured sections of CPU1 */
error = iwm_load_cpu_sections_8000(sc, fws, 1, &first_ucode_section);
if (error)
return error;
/* load to FW the binary sections of CPU2 */
return iwm_load_cpu_sections_8000(sc, fws, 2, &first_ucode_section);
}
static int
iwm_load_firmware_7000(struct iwm_softc *sc, enum iwm_ucode_type ucode_type)
{
struct iwm_fw_sects *fws;
int error, i;
const void *data;
uint32_t dlen;
uint32_t offset;
sc->sc_uc.uc_intr = 0;
fws = &sc->sc_fw.fw_sects[ucode_type];
for (i = 0; i < fws->fw_count; i++) {
data = fws->fw_sect[i].fws_data;
dlen = fws->fw_sect[i].fws_len;
offset = fws->fw_sect[i].fws_devoff;
IWM_DPRINTF(sc, IWM_DEBUG_FIRMWARE_TLV,
"LOAD FIRMWARE type %d offset %u len %d\n",
ucode_type, offset, dlen);
if (dlen > sc->sc_fwdmasegsz) {
IWM_DPRINTF(sc, IWM_DEBUG_FIRMWARE_TLV,
"chunk %d too large (%d bytes)\n", i, dlen);
error = EFBIG;
} else {
error = iwm_firmware_load_sect(sc, offset, data, dlen);
}
if (error) {
device_printf(sc->sc_dev,
"could not load firmware chunk %u of %u "
"(error=%d)\n", i, fws->fw_count, error);
return error;
}
}
IWM_WRITE(sc, IWM_CSR_RESET, 0);
return 0;
}
static int
iwm_load_firmware(struct iwm_softc *sc, enum iwm_ucode_type ucode_type)
{
int error, w;
if (sc->sc_device_family == IWM_DEVICE_FAMILY_8000)
error = iwm_load_firmware_8000(sc, ucode_type);
else
error = iwm_load_firmware_7000(sc, ucode_type);
if (error)
return error;
/* wait for the firmware to load */
for (w = 0; !sc->sc_uc.uc_intr && w < 10; w++) {
error = msleep(&sc->sc_uc, &sc->sc_mtx, 0, "iwmuc", hz/10);
}
if (error || !sc->sc_uc.uc_ok) {
device_printf(sc->sc_dev, "could not load firmware\n");
if (sc->sc_device_family == IWM_DEVICE_FAMILY_8000) {
device_printf(sc->sc_dev, "cpu1 status: 0x%x\n",
iwm_read_prph(sc, IWM_SB_CPU_1_STATUS));
device_printf(sc->sc_dev, "cpu2 status: 0x%x\n",
iwm_read_prph(sc, IWM_SB_CPU_2_STATUS));
}
}
/*
* Give the firmware some time to initialize.
* Accessing it too early causes errors.
*/
msleep(&w, &sc->sc_mtx, 0, "iwmfwinit", hz);
return error;
}
/* iwlwifi: pcie/trans.c */
static int
iwm_start_fw(struct iwm_softc *sc, enum iwm_ucode_type ucode_type)
{
int error;
IWM_WRITE(sc, IWM_CSR_INT, ~0);
if ((error = iwm_nic_init(sc)) != 0) {
device_printf(sc->sc_dev, "unable to init nic\n");
return error;
}
/* make sure rfkill handshake bits are cleared */
IWM_WRITE(sc, IWM_CSR_UCODE_DRV_GP1_CLR, IWM_CSR_UCODE_SW_BIT_RFKILL);
IWM_WRITE(sc, IWM_CSR_UCODE_DRV_GP1_CLR,
IWM_CSR_UCODE_DRV_GP1_BIT_CMD_BLOCKED);
/* clear (again), then enable host interrupts */
IWM_WRITE(sc, IWM_CSR_INT, ~0);
iwm_enable_interrupts(sc);
/* really make sure rfkill handshake bits are cleared */
/* maybe we should write a few times more? just to make sure */
IWM_WRITE(sc, IWM_CSR_UCODE_DRV_GP1_CLR, IWM_CSR_UCODE_SW_BIT_RFKILL);
IWM_WRITE(sc, IWM_CSR_UCODE_DRV_GP1_CLR, IWM_CSR_UCODE_SW_BIT_RFKILL);
/* Load the given image to the HW */
return iwm_load_firmware(sc, ucode_type);
}
static int
iwm_send_tx_ant_cfg(struct iwm_softc *sc, uint8_t valid_tx_ant)
{
struct iwm_tx_ant_cfg_cmd tx_ant_cmd = {
.valid = htole32(valid_tx_ant),
};
return iwm_mvm_send_cmd_pdu(sc, IWM_TX_ANT_CONFIGURATION_CMD,
IWM_CMD_SYNC, sizeof(tx_ant_cmd), &tx_ant_cmd);
}
/* iwlwifi: mvm/fw.c */
static int
iwm_send_phy_cfg_cmd(struct iwm_softc *sc)
{
struct iwm_phy_cfg_cmd phy_cfg_cmd;
enum iwm_ucode_type ucode_type = sc->sc_uc_current;
/* Set parameters */
phy_cfg_cmd.phy_cfg = htole32(sc->sc_fw_phy_config);
phy_cfg_cmd.calib_control.event_trigger =
sc->sc_default_calib[ucode_type].event_trigger;
phy_cfg_cmd.calib_control.flow_trigger =
sc->sc_default_calib[ucode_type].flow_trigger;
IWM_DPRINTF(sc, IWM_DEBUG_CMD | IWM_DEBUG_RESET,
"Sending Phy CFG command: 0x%x\n", phy_cfg_cmd.phy_cfg);
return iwm_mvm_send_cmd_pdu(sc, IWM_PHY_CONFIGURATION_CMD, IWM_CMD_SYNC,
sizeof(phy_cfg_cmd), &phy_cfg_cmd);
}
static int
iwm_mvm_load_ucode_wait_alive(struct iwm_softc *sc,
enum iwm_ucode_type ucode_type)
{
enum iwm_ucode_type old_type = sc->sc_uc_current;
int error;
if ((error = iwm_read_firmware(sc, ucode_type)) != 0) {
device_printf(sc->sc_dev, "iwm_read_firmware: failed %d\n",
error);
return error;
}
sc->sc_uc_current = ucode_type;
error = iwm_start_fw(sc, ucode_type);
if (error) {
device_printf(sc->sc_dev, "iwm_start_fw: failed %d\n", error);
sc->sc_uc_current = old_type;
return error;
}
error = iwm_post_alive(sc);
if (error) {
device_printf(sc->sc_dev, "iwm_fw_alive: failed %d\n", error);
}
return error;
}
/*
* mvm misc bits
*/
/*
* follows iwlwifi/fw.c
*/
static int
iwm_run_init_mvm_ucode(struct iwm_softc *sc, int justnvm)
{
int error;
/* do not operate with rfkill switch turned on */
if ((sc->sc_flags & IWM_FLAG_RFKILL) && !justnvm) {
device_printf(sc->sc_dev,
"radio is disabled by hardware switch\n");
return EPERM;
}
sc->sc_init_complete = 0;
if ((error = iwm_mvm_load_ucode_wait_alive(sc,
IWM_UCODE_TYPE_INIT)) != 0) {
device_printf(sc->sc_dev, "failed to load init firmware\n");
return error;
}
if (justnvm) {
if ((error = iwm_nvm_init(sc)) != 0) {
device_printf(sc->sc_dev, "failed to read nvm\n");
return error;
}
IEEE80211_ADDR_COPY(sc->sc_ic.ic_macaddr, sc->sc_nvm.hw_addr);
return 0;
}
if ((error = iwm_send_bt_init_conf(sc)) != 0) {
device_printf(sc->sc_dev,
"failed to send bt coex configuration: %d\n", error);
return error;
}
/* Init Smart FIFO. */
error = iwm_mvm_sf_config(sc, IWM_SF_INIT_OFF);
if (error != 0)
return error;
IWM_DPRINTF(sc, IWM_DEBUG_RESET,
"%s: phy_txant=0x%08x, nvm_valid_tx_ant=0x%02x, valid=0x%02x\n",
__func__,
((sc->sc_fw_phy_config & IWM_FW_PHY_CFG_TX_CHAIN)
>> IWM_FW_PHY_CFG_TX_CHAIN_POS),
sc->sc_nvm.valid_tx_ant,
iwm_fw_valid_tx_ant(sc));
/* Send TX valid antennas before triggering calibrations */
if ((error = iwm_send_tx_ant_cfg(sc, iwm_fw_valid_tx_ant(sc))) != 0) {
device_printf(sc->sc_dev,
"failed to send antennas before calibration: %d\n", error);
return error;
}
/*
* Send phy configurations command to init uCode
* to start the 16.0 uCode init image internal calibrations.
*/
if ((error = iwm_send_phy_cfg_cmd(sc)) != 0 ) {
device_printf(sc->sc_dev,
"%s: failed to run internal calibration: %d\n",
__func__, error);
return error;
}
/*
* Nothing to do but wait for the init complete notification
* from the firmware
*/
while (!sc->sc_init_complete) {
error = msleep(&sc->sc_init_complete, &sc->sc_mtx,
0, "iwminit", 2*hz);
if (error) {
device_printf(sc->sc_dev, "init complete failed: %d\n",
sc->sc_init_complete);
break;
}
}
IWM_DPRINTF(sc, IWM_DEBUG_RESET, "init %scomplete\n",
sc->sc_init_complete ? "" : "not ");
return error;
}
/*
* receive side
*/
/* (re)stock rx ring, called at init-time and at runtime */
static int
iwm_rx_addbuf(struct iwm_softc *sc, int size, int idx)
{
struct iwm_rx_ring *ring = &sc->rxq;
struct iwm_rx_data *data = &ring->data[idx];
struct mbuf *m;
bus_dmamap_t dmamap = NULL;
bus_dma_segment_t seg;
int nsegs, error;
m = m_getjcl(M_NOWAIT, MT_DATA, M_PKTHDR, IWM_RBUF_SIZE);
if (m == NULL)
return ENOBUFS;
m->m_len = m->m_pkthdr.len = m->m_ext.ext_size;
error = bus_dmamap_load_mbuf_sg(ring->data_dmat, ring->spare_map, m,
&seg, &nsegs, BUS_DMA_NOWAIT);
if (error != 0) {
device_printf(sc->sc_dev,
"%s: can't map mbuf, error %d\n", __func__, error);
goto fail;
}
if (data->m != NULL)
bus_dmamap_unload(ring->data_dmat, data->map);
/* Swap ring->spare_map with data->map */
dmamap = data->map;
data->map = ring->spare_map;
ring->spare_map = dmamap;
bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_PREREAD);
data->m = m;
/* Update RX descriptor. */
KASSERT((seg.ds_addr & 255) == 0, ("seg.ds_addr not aligned"));
ring->desc[idx] = htole32(seg.ds_addr >> 8);
bus_dmamap_sync(ring->desc_dma.tag, ring->desc_dma.map,
BUS_DMASYNC_PREWRITE);
return 0;
fail:
m_freem(m);
return error;
}
/* iwlwifi: mvm/rx.c */
#define IWM_RSSI_OFFSET 50
static int
iwm_mvm_calc_rssi(struct iwm_softc *sc, struct iwm_rx_phy_info *phy_info)
{
int rssi_a, rssi_b, rssi_a_dbm, rssi_b_dbm, max_rssi_dbm;
uint32_t agc_a, agc_b;
uint32_t val;
val = le32toh(phy_info->non_cfg_phy[IWM_RX_INFO_AGC_IDX]);
agc_a = (val & IWM_OFDM_AGC_A_MSK) >> IWM_OFDM_AGC_A_POS;
agc_b = (val & IWM_OFDM_AGC_B_MSK) >> IWM_OFDM_AGC_B_POS;
val = le32toh(phy_info->non_cfg_phy[IWM_RX_INFO_RSSI_AB_IDX]);
rssi_a = (val & IWM_OFDM_RSSI_INBAND_A_MSK) >> IWM_OFDM_RSSI_A_POS;
rssi_b = (val & IWM_OFDM_RSSI_INBAND_B_MSK) >> IWM_OFDM_RSSI_B_POS;
/*
* dBm = rssi dB - agc dB - constant.
* Higher AGC (higher radio gain) means lower signal.
*/
rssi_a_dbm = rssi_a - IWM_RSSI_OFFSET - agc_a;
rssi_b_dbm = rssi_b - IWM_RSSI_OFFSET - agc_b;
max_rssi_dbm = MAX(rssi_a_dbm, rssi_b_dbm);
IWM_DPRINTF(sc, IWM_DEBUG_RECV,
"Rssi In A %d B %d Max %d AGCA %d AGCB %d\n",
rssi_a_dbm, rssi_b_dbm, max_rssi_dbm, agc_a, agc_b);
return max_rssi_dbm;
}
/* iwlwifi: mvm/rx.c */
/*
* iwm_mvm_get_signal_strength - use new rx PHY INFO API
* values are reported by the fw as positive values - need to negate
* to obtain their dBM. Account for missing antennas by replacing 0
* values by -256dBm: practically 0 power and a non-feasible 8 bit value.
*/
static int
iwm_mvm_get_signal_strength(struct iwm_softc *sc, struct iwm_rx_phy_info *phy_info)
{
int energy_a, energy_b, energy_c, max_energy;
uint32_t val;
val = le32toh(phy_info->non_cfg_phy[IWM_RX_INFO_ENERGY_ANT_ABC_IDX]);
energy_a = (val & IWM_RX_INFO_ENERGY_ANT_A_MSK) >>
IWM_RX_INFO_ENERGY_ANT_A_POS;
energy_a = energy_a ? -energy_a : -256;
energy_b = (val & IWM_RX_INFO_ENERGY_ANT_B_MSK) >>
IWM_RX_INFO_ENERGY_ANT_B_POS;
energy_b = energy_b ? -energy_b : -256;
energy_c = (val & IWM_RX_INFO_ENERGY_ANT_C_MSK) >>
IWM_RX_INFO_ENERGY_ANT_C_POS;
energy_c = energy_c ? -energy_c : -256;
max_energy = MAX(energy_a, energy_b);
max_energy = MAX(max_energy, energy_c);
IWM_DPRINTF(sc, IWM_DEBUG_RECV,
"energy In A %d B %d C %d , and max %d\n",
energy_a, energy_b, energy_c, max_energy);
return max_energy;
}
static void
iwm_mvm_rx_rx_phy_cmd(struct iwm_softc *sc,
struct iwm_rx_packet *pkt, struct iwm_rx_data *data)
{
struct iwm_rx_phy_info *phy_info = (void *)pkt->data;
IWM_DPRINTF(sc, IWM_DEBUG_RECV, "received PHY stats\n");
bus_dmamap_sync(sc->rxq.data_dmat, data->map, BUS_DMASYNC_POSTREAD);
memcpy(&sc->sc_last_phy_info, phy_info, sizeof(sc->sc_last_phy_info));
}
/*
* Retrieve the average noise (in dBm) among receivers.
*/
static int
iwm_get_noise(struct iwm_softc *sc,
const struct iwm_mvm_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;
IWM_DPRINTF(sc, IWM_DEBUG_RECV, "%s: i=%d, noise=%d\n",
__func__,
i,
noise);
if (noise) {
total += noise;
nbant++;
}
}
IWM_DPRINTF(sc, IWM_DEBUG_RECV, "%s: nbant=%d, total=%d\n",
__func__, nbant, total);
#if 0
/* There should be at least one antenna but check anyway. */
return (nbant == 0) ? -127 : (total / nbant) - 107;
#else
/* For now, just hard-code it to -96 to be safe */
return (-96);
#endif
}
/*
* iwm_mvm_rx_rx_mpdu - IWM_REPLY_RX_MPDU_CMD handler
*
* Handles the actual data of the Rx packet from the fw
*/
static void
iwm_mvm_rx_rx_mpdu(struct iwm_softc *sc,
struct iwm_rx_packet *pkt, struct iwm_rx_data *data)
{
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
struct ieee80211com *ic = &sc->sc_ic;
struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
struct ieee80211_frame *wh;
struct ieee80211_node *ni;
struct ieee80211_rx_stats rxs;
struct mbuf *m;
struct iwm_rx_phy_info *phy_info;
struct iwm_rx_mpdu_res_start *rx_res;
uint32_t len;
uint32_t rx_pkt_status;
int rssi;
bus_dmamap_sync(sc->rxq.data_dmat, data->map, BUS_DMASYNC_POSTREAD);
phy_info = &sc->sc_last_phy_info;
rx_res = (struct iwm_rx_mpdu_res_start *)pkt->data;
wh = (struct ieee80211_frame *)(pkt->data + sizeof(*rx_res));
len = le16toh(rx_res->byte_count);
rx_pkt_status = le32toh(*(uint32_t *)(pkt->data + sizeof(*rx_res) + len));
m = data->m;
m->m_data = pkt->data + sizeof(*rx_res);
m->m_pkthdr.len = m->m_len = len;
if (__predict_false(phy_info->cfg_phy_cnt > 20)) {
device_printf(sc->sc_dev,
"dsp size out of range [0,20]: %d\n",
phy_info->cfg_phy_cnt);
goto fail;
}
if (!(rx_pkt_status & IWM_RX_MPDU_RES_STATUS_CRC_OK) ||
!(rx_pkt_status & IWM_RX_MPDU_RES_STATUS_OVERRUN_OK)) {
IWM_DPRINTF(sc, IWM_DEBUG_RECV,
"Bad CRC or FIFO: 0x%08X.\n", rx_pkt_status);
goto fail;
}
if (sc->sc_capaflags & IWM_UCODE_TLV_FLAGS_RX_ENERGY_API) {
rssi = iwm_mvm_get_signal_strength(sc, phy_info);
} else {
rssi = iwm_mvm_calc_rssi(sc, phy_info);
}
/* Note: RSSI is absolute (ie a -ve value) */
if (rssi < IWM_MIN_DBM)
rssi = IWM_MIN_DBM;
else if (rssi > IWM_MAX_DBM)
rssi = IWM_MAX_DBM;
/* Map it to relative value */
rssi = rssi - sc->sc_noise;
/* replenish ring for the buffer we're going to feed to the sharks */
if (iwm_rx_addbuf(sc, IWM_RBUF_SIZE, sc->rxq.cur) != 0) {
device_printf(sc->sc_dev, "%s: unable to add more buffers\n",
__func__);
goto fail;
}
IWM_DPRINTF(sc, IWM_DEBUG_RECV,
"%s: rssi=%d, noise=%d\n", __func__, rssi, sc->sc_noise);
ni = ieee80211_find_rxnode(ic, (struct ieee80211_frame_min *)wh);
IWM_DPRINTF(sc, IWM_DEBUG_RECV,
"%s: phy_info: channel=%d, flags=0x%08x\n",
__func__,
le16toh(phy_info->channel),
le16toh(phy_info->phy_flags));
/*
* Populate an RX state struct with the provided information.
*/
bzero(&rxs, sizeof(rxs));
rxs.r_flags |= IEEE80211_R_IEEE | IEEE80211_R_FREQ;
rxs.r_flags |= IEEE80211_R_NF | IEEE80211_R_RSSI;
rxs.c_ieee = le16toh(phy_info->channel);
if (le16toh(phy_info->phy_flags & IWM_RX_RES_PHY_FLAGS_BAND_24)) {
rxs.c_freq = ieee80211_ieee2mhz(rxs.c_ieee, IEEE80211_CHAN_2GHZ);
} else {
rxs.c_freq = ieee80211_ieee2mhz(rxs.c_ieee, IEEE80211_CHAN_5GHZ);
}
/* rssi is in 1/2db units */
[net80211] extend the ieee80211_rx_stats struct to include more information. There are a variety of more interesting RX statistics that we should keep track of but we don't. This is a starting point for adding more information. Specifically: * now the RX rate information and some of the packet status is passed up; * The 32 bit or 64 bit TSF is passed up; * the PHY mode is passed up; * the "I'm decap'ed AMSDU!" state is passed up; * number of RX chains is bumped to 4. This is all mostly a placeholder for getting the data into the RX status before we pass it up to net80211 - unfortunately we don't yet enforce that drivers provide it, nor do we pass the provided info back up the stack so anyone can use the data. We're going to need to use some of this data moving forward. Notably, now that some hardware can do AMSDU decap for us (the intel iwm driver can do it when we flip it on; the ath10k port I'm doing does it for us) then we need to pass it up through the stack so the duplicate RX sequence numbers and crypto/IV details don't cause the packet to be dropped and/or counted against a replay counter. It's also the beginning of being able to do more interesting node accounting in net80211. Specifically, once drivers start populating per-packet rate information, AMPDU information, timestamps, etc, we can start providing histograms of rate-versus-RSSI, account for receive time spent per node and other such interesting things. (Note: I'm also hoping to include ranging and RTT information for future chipset support; and it's likely going to include it in this kind of fashion.)
2016-10-08 01:12:29 +00:00
rxs.c_rssi = rssi * 2;
rxs.c_nf = sc->sc_noise;
if (ieee80211_add_rx_params(m, &rxs) == 0)
goto fail;
if (ieee80211_radiotap_active_vap(vap)) {
struct iwm_rx_radiotap_header *tap = &sc->sc_rxtap;
tap->wr_flags = 0;
if (phy_info->phy_flags & htole16(IWM_PHY_INFO_FLAG_SHPREAMBLE))
tap->wr_flags |= IEEE80211_RADIOTAP_F_SHORTPRE;
tap->wr_chan_freq = htole16(rxs.c_freq);
/* XXX only if ic->ic_curchan->ic_ieee == rxs.c_ieee */
tap->wr_chan_flags = htole16(ic->ic_curchan->ic_flags);
tap->wr_dbm_antsignal = (int8_t)rssi;
tap->wr_dbm_antnoise = (int8_t)sc->sc_noise;
tap->wr_tsft = phy_info->system_timestamp;
switch (phy_info->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;
}
}
IWM_UNLOCK(sc);
if (ni != NULL) {
IWM_DPRINTF(sc, IWM_DEBUG_RECV, "input m %p\n", m);
ieee80211_input_mimo(ni, m);
ieee80211_free_node(ni);
} else {
IWM_DPRINTF(sc, IWM_DEBUG_RECV, "inputall m %p\n", m);
ieee80211_input_mimo_all(ic, m);
}
IWM_LOCK(sc);
return;
fail: counter_u64_add(ic->ic_ierrors, 1);
}
static int
iwm_mvm_rx_tx_cmd_single(struct iwm_softc *sc, struct iwm_rx_packet *pkt,
struct iwm_node *in)
{
struct iwm_mvm_tx_resp *tx_resp = (void *)pkt->data;
struct ieee80211_ratectl_tx_status *txs = &sc->sc_txs;
struct ieee80211_node *ni = &in->in_ni;
int status = le16toh(tx_resp->status.status) & IWM_TX_STATUS_MSK;
KASSERT(tx_resp->frame_count == 1, ("too many frames"));
/* Update rate control statistics. */
IWM_DPRINTF(sc, IWM_DEBUG_XMIT, "%s: status=0x%04x, seq=%d, fc=%d, btc=%d, frts=%d, ff=%d, irate=%08x, wmt=%d\n",
__func__,
(int) le16toh(tx_resp->status.status),
(int) le16toh(tx_resp->status.sequence),
tx_resp->frame_count,
tx_resp->bt_kill_count,
tx_resp->failure_rts,
tx_resp->failure_frame,
le32toh(tx_resp->initial_rate),
(int) le16toh(tx_resp->wireless_media_time));
txs->flags = IEEE80211_RATECTL_STATUS_SHORT_RETRY |
IEEE80211_RATECTL_STATUS_LONG_RETRY;
txs->short_retries = tx_resp->failure_rts;
txs->long_retries = tx_resp->failure_frame;
if (status != IWM_TX_STATUS_SUCCESS &&
status != IWM_TX_STATUS_DIRECT_DONE) {
switch (status) {
case IWM_TX_STATUS_FAIL_SHORT_LIMIT:
txs->status = IEEE80211_RATECTL_TX_FAIL_SHORT;
break;
case IWM_TX_STATUS_FAIL_LONG_LIMIT:
txs->status = IEEE80211_RATECTL_TX_FAIL_LONG;
break;
case IWM_TX_STATUS_FAIL_LIFE_EXPIRE:
txs->status = IEEE80211_RATECTL_TX_FAIL_EXPIRED;
break;
default:
txs->status = IEEE80211_RATECTL_TX_FAIL_UNSPECIFIED;
break;
}
} else {
txs->status = IEEE80211_RATECTL_TX_SUCCESS;
}
ieee80211_ratectl_tx_complete(ni, txs);
return (txs->status != IEEE80211_RATECTL_TX_SUCCESS);
}
static void
iwm_mvm_rx_tx_cmd(struct iwm_softc *sc,
struct iwm_rx_packet *pkt, struct iwm_rx_data *data)
{
struct iwm_cmd_header *cmd_hdr = &pkt->hdr;
int idx = cmd_hdr->idx;
int qid = cmd_hdr->qid;
struct iwm_tx_ring *ring = &sc->txq[qid];
struct iwm_tx_data *txd = &ring->data[idx];
struct iwm_node *in = txd->in;
struct mbuf *m = txd->m;
int status;
KASSERT(txd->done == 0, ("txd not done"));
KASSERT(txd->in != NULL, ("txd without node"));
KASSERT(txd->m != NULL, ("txd without mbuf"));
bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_POSTREAD);
sc->sc_tx_timer = 0;
status = iwm_mvm_rx_tx_cmd_single(sc, pkt, in);
/* Unmap and free mbuf. */
bus_dmamap_sync(ring->data_dmat, txd->map, BUS_DMASYNC_POSTWRITE);
bus_dmamap_unload(ring->data_dmat, txd->map);
IWM_DPRINTF(sc, IWM_DEBUG_XMIT,
"free txd %p, in %p\n", txd, txd->in);
txd->done = 1;
txd->m = NULL;
txd->in = NULL;
ieee80211_tx_complete(&in->in_ni, m, status);
if (--ring->queued < IWM_TX_RING_LOMARK) {
sc->qfullmsk &= ~(1 << ring->qid);
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
if (sc->qfullmsk == 0) {
/*
* 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.
*/
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
iwm_start(sc);
}
}
}
/*
* transmit side
*/
/*
* Process a "command done" firmware notification. This is where we wakeup
* processes waiting for a synchronous command completion.
* from if_iwn
*/
static void
iwm_cmd_done(struct iwm_softc *sc, struct iwm_rx_packet *pkt)
{
struct iwm_tx_ring *ring = &sc->txq[IWM_MVM_CMD_QUEUE];
struct iwm_tx_data *data;
if (pkt->hdr.qid != IWM_MVM_CMD_QUEUE) {
return; /* Not a command ack. */
}
/* XXX wide commands? */
IWM_DPRINTF(sc, IWM_DEBUG_CMD,
"cmd notification type 0x%x qid %d idx %d\n",
pkt->hdr.code, pkt->hdr.qid, pkt->hdr.idx);
data = &ring->data[pkt->hdr.idx];
/* If the command was mapped in an mbuf, free it. */
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[pkt->hdr.idx]);
}
#if 0
/*
* necessary only for block ack mode
*/
void
iwm_update_sched(struct iwm_softc *sc, int qid, int idx, uint8_t sta_id,
uint16_t len)
{
struct iwm_agn_scd_bc_tbl *scd_bc_tbl;
uint16_t w_val;
scd_bc_tbl = sc->sched_dma.vaddr;
len += 8; /* magic numbers came naturally from paris */
if (sc->sc_capaflags & IWM_UCODE_TLV_FLAGS_DW_BC_TABLE)
len = roundup(len, 4) / 4;
w_val = htole16(sta_id << 12 | len);
/* Update TX scheduler. */
scd_bc_tbl[qid].tfd_offset[idx] = w_val;
bus_dmamap_sync(sc->sched_dma.tag, sc->sched_dma.map,
BUS_DMASYNC_PREWRITE);
/* I really wonder what this is ?!? */
if (idx < IWM_TFD_QUEUE_SIZE_BC_DUP) {
scd_bc_tbl[qid].tfd_offset[IWM_TFD_QUEUE_SIZE_MAX + idx] = w_val;
bus_dmamap_sync(sc->sched_dma.tag, sc->sched_dma.map,
BUS_DMASYNC_PREWRITE);
}
}
#endif
/*
* Take an 802.11 (non-n) rate, find the relevant rate
* table entry. return the index into in_ridx[].
*
* The caller then uses that index back into in_ridx
* to figure out the rate index programmed /into/
* the firmware for this given node.
*/
static int
iwm_tx_rateidx_lookup(struct iwm_softc *sc, struct iwm_node *in,
uint8_t rate)
{
int i;
uint8_t r;
for (i = 0; i < nitems(in->in_ridx); i++) {
r = iwm_rates[in->in_ridx[i]].rate;
if (rate == r)
return (i);
}
IWM_DPRINTF(sc, IWM_DEBUG_XMIT | IWM_DEBUG_TXRATE,
"%s: couldn't find an entry for rate=%d\n",
__func__,
rate);
/* XXX Return the first */
/* XXX TODO: have it return the /lowest/ */
return (0);
}
static int
iwm_tx_rateidx_global_lookup(struct iwm_softc *sc, uint8_t rate)
{
int i;
for (i = 0; i < nitems(iwm_rates); i++) {
if (iwm_rates[i].rate == rate)
return (i);
}
/* XXX error? */
IWM_DPRINTF(sc, IWM_DEBUG_XMIT | IWM_DEBUG_TXRATE,
"%s: couldn't find an entry for rate=%d\n",
__func__,
rate);
return (0);
}
/*
* Fill in the rate related information for a transmit command.
*/
static const struct iwm_rate *
iwm_tx_fill_cmd(struct iwm_softc *sc, struct iwm_node *in,
struct mbuf *m, struct iwm_tx_cmd *tx)
{
struct ieee80211_node *ni = &in->in_ni;
struct ieee80211_frame *wh;
const struct ieee80211_txparam *tp = ni->ni_txparms;
const struct iwm_rate *rinfo;
int type;
int ridx, rate_flags;
wh = mtod(m, struct ieee80211_frame *);
type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK;
tx->rts_retry_limit = IWM_RTS_DFAULT_RETRY_LIMIT;
tx->data_retry_limit = IWM_DEFAULT_TX_RETRY;
if (type == IEEE80211_FC0_TYPE_MGT) {
ridx = iwm_tx_rateidx_global_lookup(sc, tp->mgmtrate);
IWM_DPRINTF(sc, IWM_DEBUG_TXRATE,
"%s: MGT (%d)\n", __func__, tp->mgmtrate);
} else if (IEEE80211_IS_MULTICAST(wh->i_addr1)) {
ridx = iwm_tx_rateidx_global_lookup(sc, tp->mcastrate);
IWM_DPRINTF(sc, IWM_DEBUG_TXRATE,
"%s: MCAST (%d)\n", __func__, tp->mcastrate);
} else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) {
ridx = iwm_tx_rateidx_global_lookup(sc, tp->ucastrate);
IWM_DPRINTF(sc, IWM_DEBUG_TXRATE,
"%s: FIXED_RATE (%d)\n", __func__, tp->ucastrate);
} else if (m->m_flags & M_EAPOL) {
ridx = iwm_tx_rateidx_global_lookup(sc, tp->mgmtrate);
IWM_DPRINTF(sc, IWM_DEBUG_TXRATE,
"%s: EAPOL\n", __func__);
} else if (type == IEEE80211_FC0_TYPE_DATA) {
int i;
/* for data frames, use RS table */
IWM_DPRINTF(sc, IWM_DEBUG_TXRATE, "%s: DATA\n", __func__);
/* XXX pass pktlen */
(void) ieee80211_ratectl_rate(ni, NULL, 0);
i = iwm_tx_rateidx_lookup(sc, in, ni->ni_txrate);
ridx = in->in_ridx[i];
/* This is the index into the programmed table */
tx->initial_rate_index = i;
tx->tx_flags |= htole32(IWM_TX_CMD_FLG_STA_RATE);
IWM_DPRINTF(sc, IWM_DEBUG_XMIT | IWM_DEBUG_TXRATE,
"%s: start with i=%d, txrate %d\n",
__func__, i, iwm_rates[ridx].rate);
} else {
ridx = iwm_tx_rateidx_global_lookup(sc, tp->mgmtrate);
IWM_DPRINTF(sc, IWM_DEBUG_TXRATE, "%s: DEFAULT (%d)\n",
__func__, tp->mgmtrate);
}
IWM_DPRINTF(sc, IWM_DEBUG_XMIT | IWM_DEBUG_TXRATE,
"%s: frame type=%d txrate %d\n",
__func__, type, iwm_rates[ridx].rate);
rinfo = &iwm_rates[ridx];
IWM_DPRINTF(sc, IWM_DEBUG_TXRATE, "%s: ridx=%d; rate=%d, CCK=%d\n",
__func__, ridx,
rinfo->rate,
!! (IWM_RIDX_IS_CCK(ridx))
);
/* XXX TODO: hard-coded TX antenna? */
rate_flags = 1 << IWM_RATE_MCS_ANT_POS;
if (IWM_RIDX_IS_CCK(ridx))
rate_flags |= IWM_RATE_MCS_CCK_MSK;
tx->rate_n_flags = htole32(rate_flags | rinfo->plcp);
return rinfo;
}
#define TB0_SIZE 16
static int
iwm_tx(struct iwm_softc *sc, struct mbuf *m, struct ieee80211_node *ni, int ac)
{
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
struct ieee80211com *ic = &sc->sc_ic;
struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
struct iwm_node *in = IWM_NODE(ni);
struct iwm_tx_ring *ring;
struct iwm_tx_data *data;
struct iwm_tfd *desc;
struct iwm_device_cmd *cmd;
struct iwm_tx_cmd *tx;
struct ieee80211_frame *wh;
struct ieee80211_key *k = NULL;
struct mbuf *m1;
const struct iwm_rate *rinfo;
uint32_t flags;
u_int hdrlen;
bus_dma_segment_t *seg, segs[IWM_MAX_SCATTER];
int nsegs;
uint8_t tid, type;
int i, totlen, error, pad;
wh = mtod(m, struct ieee80211_frame *);
hdrlen = ieee80211_anyhdrsize(wh);
type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK;
tid = 0;
ring = &sc->txq[ac];
desc = &ring->desc[ring->cur];
memset(desc, 0, sizeof(*desc));
data = &ring->data[ring->cur];
/* Fill out iwm_tx_cmd to send to the firmware */
cmd = &ring->cmd[ring->cur];
cmd->hdr.code = IWM_TX_CMD;
cmd->hdr.flags = 0;
cmd->hdr.qid = ring->qid;
cmd->hdr.idx = ring->cur;
tx = (void *)cmd->data;
memset(tx, 0, sizeof(*tx));
rinfo = iwm_tx_fill_cmd(sc, in, m, tx);
/* Encrypt the frame if need be. */
if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) {
/* Retrieve key for TX && do software encryption. */
k = ieee80211_crypto_encap(ni, m);
if (k == NULL) {
m_freem(m);
return (ENOBUFS);
}
/* 802.11 header may have moved. */
wh = mtod(m, struct ieee80211_frame *);
}
if (ieee80211_radiotap_active_vap(vap)) {
struct iwm_tx_radiotap_header *tap = &sc->sc_txtap;
tap->wt_flags = 0;
tap->wt_chan_freq = htole16(ni->ni_chan->ic_freq);
tap->wt_chan_flags = htole16(ni->ni_chan->ic_flags);
tap->wt_rate = rinfo->rate;
if (k != NULL)
tap->wt_flags |= IEEE80211_RADIOTAP_F_WEP;
ieee80211_radiotap_tx(vap, m);
}
totlen = m->m_pkthdr.len;
flags = 0;
if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) {
flags |= IWM_TX_CMD_FLG_ACK;
}
if (type == IEEE80211_FC0_TYPE_DATA
&& (totlen + IEEE80211_CRC_LEN > vap->iv_rtsthreshold)
&& !IEEE80211_IS_MULTICAST(wh->i_addr1)) {
flags |= IWM_TX_CMD_FLG_PROT_REQUIRE;
}
if (IEEE80211_IS_MULTICAST(wh->i_addr1) ||
type != IEEE80211_FC0_TYPE_DATA)
tx->sta_id = sc->sc_aux_sta.sta_id;
else
tx->sta_id = IWM_STATION_ID;
if (type == IEEE80211_FC0_TYPE_MGT) {
uint8_t subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK;
if (subtype == IEEE80211_FC0_SUBTYPE_ASSOC_REQ ||
subtype == IEEE80211_FC0_SUBTYPE_REASSOC_REQ) {
tx->pm_frame_timeout = htole16(IWM_PM_FRAME_ASSOC);
} else if (subtype == IEEE80211_FC0_SUBTYPE_ACTION) {
tx->pm_frame_timeout = htole16(IWM_PM_FRAME_NONE);
} else {
tx->pm_frame_timeout = htole16(IWM_PM_FRAME_MGMT);
}
} else {
tx->pm_frame_timeout = htole16(IWM_PM_FRAME_NONE);
}
if (hdrlen & 3) {
/* First segment length must be a multiple of 4. */
flags |= IWM_TX_CMD_FLG_MH_PAD;
pad = 4 - (hdrlen & 3);
} else
pad = 0;
tx->driver_txop = 0;
tx->next_frame_len = 0;
tx->len = htole16(totlen);
tx->tid_tspec = tid;
tx->life_time = htole32(IWM_TX_CMD_LIFE_TIME_INFINITE);
/* Set physical address of "scratch area". */
tx->dram_lsb_ptr = htole32(data->scratch_paddr);
tx->dram_msb_ptr = iwm_get_dma_hi_addr(data->scratch_paddr);
/* Copy 802.11 header in TX command. */
memcpy(((uint8_t *)tx) + sizeof(*tx), wh, hdrlen);
flags |= IWM_TX_CMD_FLG_BT_DIS | IWM_TX_CMD_FLG_SEQ_CTL;
tx->sec_ctl = 0;
tx->tx_flags |= htole32(flags);
/* Trim 802.11 header. */
m_adj(m, hdrlen);
error = bus_dmamap_load_mbuf_sg(ring->data_dmat, data->map, m,
segs, &nsegs, BUS_DMA_NOWAIT);
if (error != 0) {
if (error != EFBIG) {
device_printf(sc->sc_dev, "can't map mbuf (error %d)\n",
error);
m_freem(m);
return error;
}
/* Too many DMA segments, linearize mbuf. */
m1 = m_collapse(m, M_NOWAIT, IWM_MAX_SCATTER - 2);
if (m1 == NULL) {
device_printf(sc->sc_dev,
"%s: could not defrag mbuf\n", __func__);
m_freem(m);
return (ENOBUFS);
}
m = m1;
error = bus_dmamap_load_mbuf_sg(ring->data_dmat, data->map, m,
segs, &nsegs, BUS_DMA_NOWAIT);
if (error != 0) {
device_printf(sc->sc_dev, "can't map mbuf (error %d)\n",
error);
m_freem(m);
return error;
}
}
data->m = m;
data->in = in;
data->done = 0;
IWM_DPRINTF(sc, IWM_DEBUG_XMIT,
"sending txd %p, in %p\n", data, data->in);
KASSERT(data->in != NULL, ("node is NULL"));
IWM_DPRINTF(sc, IWM_DEBUG_XMIT,
"sending data: qid=%d idx=%d len=%d nsegs=%d txflags=0x%08x rate_n_flags=0x%08x rateidx=%u\n",
ring->qid, ring->cur, totlen, nsegs,
le32toh(tx->tx_flags),
le32toh(tx->rate_n_flags),
tx->initial_rate_index
);
/* Fill TX descriptor. */
desc->num_tbs = 2 + nsegs;
desc->tbs[0].lo = htole32(data->cmd_paddr);
desc->tbs[0].hi_n_len = htole16(iwm_get_dma_hi_addr(data->cmd_paddr)) |
(TB0_SIZE << 4);
desc->tbs[1].lo = htole32(data->cmd_paddr + TB0_SIZE);
desc->tbs[1].hi_n_len = htole16(iwm_get_dma_hi_addr(data->cmd_paddr)) |
((sizeof(struct iwm_cmd_header) + sizeof(*tx)
+ hdrlen + pad - TB0_SIZE) << 4);
/* Other DMA segments are for data payload. */
for (i = 0; i < nsegs; i++) {
seg = &segs[i];
desc->tbs[i+2].lo = htole32(seg->ds_addr);
desc->tbs[i+2].hi_n_len = \
htole16(iwm_get_dma_hi_addr(seg->ds_addr))
| ((seg->ds_len) << 4);
}
bus_dmamap_sync(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);
#if 0
iwm_update_sched(sc, ring->qid, ring->cur, tx->sta_id, le16toh(tx->len));
#endif
/* Kick TX ring. */
ring->cur = (ring->cur + 1) % IWM_TX_RING_COUNT;
IWM_WRITE(sc, IWM_HBUS_TARG_WRPTR, ring->qid << 8 | ring->cur);
/* Mark TX ring as full if we reach a certain threshold. */
if (++ring->queued > IWM_TX_RING_HIMARK) {
sc->qfullmsk |= 1 << ring->qid;
}
return 0;
}
static int
iwm_raw_xmit(struct ieee80211_node *ni, struct mbuf *m,
const struct ieee80211_bpf_params *params)
{
struct ieee80211com *ic = ni->ni_ic;
struct iwm_softc *sc = ic->ic_softc;
int error = 0;
IWM_DPRINTF(sc, IWM_DEBUG_XMIT,
"->%s begin\n", __func__);
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
if ((sc->sc_flags & IWM_FLAG_HW_INITED) == 0) {
m_freem(m);
IWM_DPRINTF(sc, IWM_DEBUG_XMIT,
"<-%s not RUNNING\n", __func__);
return (ENETDOWN);
}
IWM_LOCK(sc);
/* XXX fix this */
if (params == NULL) {
error = iwm_tx(sc, m, ni, 0);
} else {
error = iwm_tx(sc, m, ni, 0);
}
sc->sc_tx_timer = 5;
IWM_UNLOCK(sc);
return (error);
}
/*
* mvm/tx.c
*/
/*
* Note that there are transports that buffer frames before they reach
* the firmware. This means that after flush_tx_path is called, the
* queue might not be empty. The race-free way to handle this is to:
* 1) set the station as draining
* 2) flush the Tx path
* 3) wait for the transport queues to be empty
*/
int
iwm_mvm_flush_tx_path(struct iwm_softc *sc, uint32_t tfd_msk, uint32_t flags)
{
int ret;
struct iwm_tx_path_flush_cmd flush_cmd = {
.queues_ctl = htole32(tfd_msk),
.flush_ctl = htole16(IWM_DUMP_TX_FIFO_FLUSH),
};
ret = iwm_mvm_send_cmd_pdu(sc, IWM_TXPATH_FLUSH, flags,
sizeof(flush_cmd), &flush_cmd);
if (ret)
device_printf(sc->sc_dev,
"Flushing tx queue failed: %d\n", ret);
return ret;
}
/*
* BEGIN mvm/sta.c
*/
static int
iwm_mvm_send_add_sta_cmd_status(struct iwm_softc *sc,
struct iwm_mvm_add_sta_cmd_v7 *cmd, int *status)
{
return iwm_mvm_send_cmd_pdu_status(sc, IWM_ADD_STA, sizeof(*cmd),
cmd, status);
}
/* send station add/update command to firmware */
static int
iwm_mvm_sta_send_to_fw(struct iwm_softc *sc, struct iwm_node *in, int update)
{
struct iwm_mvm_add_sta_cmd_v7 add_sta_cmd;
int ret;
uint32_t status;
memset(&add_sta_cmd, 0, sizeof(add_sta_cmd));
add_sta_cmd.sta_id = IWM_STATION_ID;
add_sta_cmd.mac_id_n_color
= htole32(IWM_FW_CMD_ID_AND_COLOR(IWM_DEFAULT_MACID,
IWM_DEFAULT_COLOR));
if (!update) {
int ac;
for (ac = 0; ac < WME_NUM_AC; ac++) {
add_sta_cmd.tfd_queue_msk |=
htole32(1 << iwm_mvm_ac_to_tx_fifo[ac]);
}
IEEE80211_ADDR_COPY(&add_sta_cmd.addr, in->in_ni.ni_bssid);
}
add_sta_cmd.add_modify = update ? 1 : 0;
add_sta_cmd.station_flags_msk
|= htole32(IWM_STA_FLG_FAT_EN_MSK | IWM_STA_FLG_MIMO_EN_MSK);
add_sta_cmd.tid_disable_tx = htole16(0xffff);
if (update)
add_sta_cmd.modify_mask |= (IWM_STA_MODIFY_TID_DISABLE_TX);
status = IWM_ADD_STA_SUCCESS;
ret = iwm_mvm_send_add_sta_cmd_status(sc, &add_sta_cmd, &status);
if (ret)
return ret;
switch (status) {
case IWM_ADD_STA_SUCCESS:
break;
default:
ret = EIO;
device_printf(sc->sc_dev, "IWM_ADD_STA failed\n");
break;
}
return ret;
}
static int
iwm_mvm_add_sta(struct iwm_softc *sc, struct iwm_node *in)
{
return iwm_mvm_sta_send_to_fw(sc, in, 0);
}
static int
iwm_mvm_update_sta(struct iwm_softc *sc, struct iwm_node *in)
{
return iwm_mvm_sta_send_to_fw(sc, in, 1);
}
static int
iwm_mvm_add_int_sta_common(struct iwm_softc *sc, struct iwm_int_sta *sta,
const uint8_t *addr, uint16_t mac_id, uint16_t color)
{
struct iwm_mvm_add_sta_cmd_v7 cmd;
int ret;
uint32_t status;
memset(&cmd, 0, sizeof(cmd));
cmd.sta_id = sta->sta_id;
cmd.mac_id_n_color = htole32(IWM_FW_CMD_ID_AND_COLOR(mac_id, color));
cmd.tfd_queue_msk = htole32(sta->tfd_queue_msk);
cmd.tid_disable_tx = htole16(0xffff);
if (addr)
IEEE80211_ADDR_COPY(cmd.addr, addr);
ret = iwm_mvm_send_add_sta_cmd_status(sc, &cmd, &status);
if (ret)
return ret;
switch (status) {
case IWM_ADD_STA_SUCCESS:
IWM_DPRINTF(sc, IWM_DEBUG_RESET,
"%s: Internal station added.\n", __func__);
return 0;
default:
device_printf(sc->sc_dev,
"%s: Add internal station failed, status=0x%x\n",
__func__, status);
ret = EIO;
break;
}
return ret;
}
static int
iwm_mvm_add_aux_sta(struct iwm_softc *sc)
{
int ret;
sc->sc_aux_sta.sta_id = IWM_AUX_STA_ID;
sc->sc_aux_sta.tfd_queue_msk = (1 << IWM_MVM_AUX_QUEUE);
ret = iwm_enable_txq(sc, 0, IWM_MVM_AUX_QUEUE, IWM_MVM_TX_FIFO_MCAST);
if (ret)
return ret;
ret = iwm_mvm_add_int_sta_common(sc,
&sc->sc_aux_sta, NULL, IWM_MAC_INDEX_AUX, 0);
if (ret)
memset(&sc->sc_aux_sta, 0, sizeof(sc->sc_aux_sta));
return ret;
}
/*
* END mvm/sta.c
*/
/*
* BEGIN mvm/quota.c
*/
static int
iwm_mvm_update_quotas(struct iwm_softc *sc, struct iwm_node *in)
{
struct iwm_time_quota_cmd cmd;
int i, idx, ret, num_active_macs, quota, quota_rem;
int colors[IWM_MAX_BINDINGS] = { -1, -1, -1, -1, };
int n_ifs[IWM_MAX_BINDINGS] = {0, };
uint16_t id;
memset(&cmd, 0, sizeof(cmd));
/* currently, PHY ID == binding ID */
if (in) {
id = in->in_phyctxt->id;
KASSERT(id < IWM_MAX_BINDINGS, ("invalid id"));
colors[id] = in->in_phyctxt->color;
if (1)
n_ifs[id] = 1;
}
/*
* The FW's scheduling session consists of
* IWM_MVM_MAX_QUOTA fragments. Divide these fragments
* equally between all the bindings that require quota
*/
num_active_macs = 0;
for (i = 0; i < IWM_MAX_BINDINGS; i++) {
cmd.quotas[i].id_and_color = htole32(IWM_FW_CTXT_INVALID);
num_active_macs += n_ifs[i];
}
quota = 0;
quota_rem = 0;
if (num_active_macs) {
quota = IWM_MVM_MAX_QUOTA / num_active_macs;
quota_rem = IWM_MVM_MAX_QUOTA % num_active_macs;
}
for (idx = 0, i = 0; i < IWM_MAX_BINDINGS; i++) {
if (colors[i] < 0)
continue;
cmd.quotas[idx].id_and_color =
htole32(IWM_FW_CMD_ID_AND_COLOR(i, colors[i]));
if (n_ifs[i] <= 0) {
cmd.quotas[idx].quota = htole32(0);
cmd.quotas[idx].max_duration = htole32(0);
} else {
cmd.quotas[idx].quota = htole32(quota * n_ifs[i]);
cmd.quotas[idx].max_duration = htole32(0);
}
idx++;
}
/* Give the remainder of the session to the first binding */
cmd.quotas[0].quota = htole32(le32toh(cmd.quotas[0].quota) + quota_rem);
ret = iwm_mvm_send_cmd_pdu(sc, IWM_TIME_QUOTA_CMD, IWM_CMD_SYNC,
sizeof(cmd), &cmd);
if (ret)
device_printf(sc->sc_dev,
"%s: Failed to send quota: %d\n", __func__, ret);
return ret;
}
/*
* END mvm/quota.c
*/
/*
* ieee80211 routines
*/
/*
* Change to AUTH state in 80211 state machine. Roughly matches what
* Linux does in bss_info_changed().
*/
static int
iwm_auth(struct ieee80211vap *vap, struct iwm_softc *sc)
{
struct ieee80211_node *ni;
struct iwm_node *in;
struct iwm_vap *iv = IWM_VAP(vap);
uint32_t duration;
int error;
/*
* XXX i have a feeling that the vap node is being
* freed from underneath us. Grr.
*/
ni = ieee80211_ref_node(vap->iv_bss);
in = IWM_NODE(ni);
IWM_DPRINTF(sc, IWM_DEBUG_RESET | IWM_DEBUG_STATE,
"%s: called; vap=%p, bss ni=%p\n",
__func__,
vap,
ni);
in->in_assoc = 0;
error = iwm_mvm_sf_config(sc, IWM_SF_FULL_ON);
if (error != 0)
return error;
error = iwm_allow_mcast(vap, sc);
if (error) {
device_printf(sc->sc_dev,
"%s: failed to set multicast\n", __func__);
goto out;
}
/*
* This is where it deviates from what Linux does.
*
* Linux iwlwifi doesn't reset the nic each time, nor does it
* call ctxt_add() here. Instead, it adds it during vap creation,
* and always does a mac_ctx_changed().
*
* The openbsd port doesn't attempt to do that - it reset things
* at odd states and does the add here.
*
* So, until the state handling is fixed (ie, we never reset
* the NIC except for a firmware failure, which should drag
* the NIC back to IDLE, re-setup and re-add all the mac/phy
* contexts that are required), let's do a dirty hack here.
*/
if (iv->is_uploaded) {
if ((error = iwm_mvm_mac_ctxt_changed(sc, vap)) != 0) {
device_printf(sc->sc_dev,
"%s: failed to update MAC\n", __func__);
goto out;
}
if ((error = iwm_mvm_phy_ctxt_changed(sc, &sc->sc_phyctxt[0],
in->in_ni.ni_chan, 1, 1)) != 0) {
device_printf(sc->sc_dev,
"%s: failed update phy ctxt\n", __func__);
goto out;
}
in->in_phyctxt = &sc->sc_phyctxt[0];
if ((error = iwm_mvm_binding_update(sc, in)) != 0) {
device_printf(sc->sc_dev,
"%s: binding update cmd\n", __func__);
goto out;
}
if ((error = iwm_mvm_update_sta(sc, in)) != 0) {
device_printf(sc->sc_dev,
"%s: failed to update sta\n", __func__);
goto out;
}
} else {
if ((error = iwm_mvm_mac_ctxt_add(sc, vap)) != 0) {
device_printf(sc->sc_dev,
"%s: failed to add MAC\n", __func__);
goto out;
}
if ((error = iwm_mvm_phy_ctxt_changed(sc, &sc->sc_phyctxt[0],
in->in_ni.ni_chan, 1, 1)) != 0) {
device_printf(sc->sc_dev,
"%s: failed add phy ctxt!\n", __func__);
error = ETIMEDOUT;
goto out;
}
in->in_phyctxt = &sc->sc_phyctxt[0];
if ((error = iwm_mvm_binding_add_vif(sc, in)) != 0) {
device_printf(sc->sc_dev,
"%s: binding add cmd\n", __func__);
goto out;
}
if ((error = iwm_mvm_add_sta(sc, in)) != 0) {
device_printf(sc->sc_dev,
"%s: failed to add sta\n", __func__);
goto out;
}
}
/*
* Prevent the FW from wandering off channel during association
* by "protecting" the session with a time event.
*/
/* XXX duration is in units of TU, not MS */
duration = IWM_MVM_TE_SESSION_PROTECTION_MAX_TIME_MS;
iwm_mvm_protect_session(sc, in, duration, 500 /* XXX magic number */);
DELAY(100);
error = 0;
out:
ieee80211_free_node(ni);
return (error);
}
static int
iwm_assoc(struct ieee80211vap *vap, struct iwm_softc *sc)
{
struct iwm_node *in = IWM_NODE(vap->iv_bss);
int error;
if ((error = iwm_mvm_update_sta(sc, in)) != 0) {
device_printf(sc->sc_dev,
"%s: failed to update STA\n", __func__);
return error;
}
in->in_assoc = 1;
if ((error = iwm_mvm_mac_ctxt_changed(sc, vap)) != 0) {
device_printf(sc->sc_dev,
"%s: failed to update MAC\n", __func__);
return error;
}
return 0;
}
static int
iwm_release(struct iwm_softc *sc, struct iwm_node *in)
{
uint32_t tfd_msk;
/*
* Ok, so *technically* the proper set of calls for going
* from RUN back to SCAN is:
*
* iwm_mvm_power_mac_disable(sc, in);
* iwm_mvm_mac_ctxt_changed(sc, in);
* iwm_mvm_rm_sta(sc, in);
* iwm_mvm_update_quotas(sc, NULL);
* iwm_mvm_mac_ctxt_changed(sc, in);
* iwm_mvm_binding_remove_vif(sc, in);
* iwm_mvm_mac_ctxt_remove(sc, in);
*
* However, that freezes the device not matter which permutations
* and modifications are attempted. Obviously, this driver is missing
* something since it works in the Linux driver, but figuring out what
* is missing is a little more complicated. Now, since we're going
* back to nothing anyway, we'll just do a complete device reset.
* Up your's, device!
*/
/*
* Just using 0xf for the queues mask is fine as long as we only
* get here from RUN state.
*/
tfd_msk = 0xf;
mbufq_drain(&sc->sc_snd);
iwm_mvm_flush_tx_path(sc, tfd_msk, IWM_CMD_SYNC);
/*
* We seem to get away with just synchronously sending the
* IWM_TXPATH_FLUSH command.
*/
// iwm_trans_wait_tx_queue_empty(sc, tfd_msk);
iwm_stop_device(sc);
iwm_init_hw(sc);
if (in)
in->in_assoc = 0;
return 0;
#if 0
int error;
iwm_mvm_power_mac_disable(sc, in);
if ((error = iwm_mvm_mac_ctxt_changed(sc, in)) != 0) {
device_printf(sc->sc_dev, "mac ctxt change fail 1 %d\n", error);
return error;
}
if ((error = iwm_mvm_rm_sta(sc, in)) != 0) {
device_printf(sc->sc_dev, "sta remove fail %d\n", error);
return error;
}
error = iwm_mvm_rm_sta(sc, in);
in->in_assoc = 0;
iwm_mvm_update_quotas(sc, NULL);
if ((error = iwm_mvm_mac_ctxt_changed(sc, in)) != 0) {
device_printf(sc->sc_dev, "mac ctxt change fail 2 %d\n", error);
return error;
}
iwm_mvm_binding_remove_vif(sc, in);
iwm_mvm_mac_ctxt_remove(sc, in);
return error;
#endif
}
static struct ieee80211_node *
iwm_node_alloc(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN])
{
return malloc(sizeof (struct iwm_node), M_80211_NODE,
M_NOWAIT | M_ZERO);
}
static void
iwm_setrates(struct iwm_softc *sc, struct iwm_node *in)
{
struct ieee80211_node *ni = &in->in_ni;
struct iwm_lq_cmd *lq = &in->in_lq;
int nrates = ni->ni_rates.rs_nrates;
int i, ridx, tab = 0;
// int txant = 0;
if (nrates > nitems(lq->rs_table)) {
device_printf(sc->sc_dev,
"%s: node supports %d rates, driver handles "
"only %zu\n", __func__, nrates, nitems(lq->rs_table));
return;
}
if (nrates == 0) {
device_printf(sc->sc_dev,
"%s: node supports 0 rates, odd!\n", __func__);
return;
}
/*
* XXX .. and most of iwm_node is not initialised explicitly;
* it's all just 0x0 passed to the firmware.
*/
/* first figure out which rates we should support */
/* XXX TODO: this isn't 11n aware /at all/ */
memset(&in->in_ridx, -1, sizeof(in->in_ridx));
IWM_DPRINTF(sc, IWM_DEBUG_TXRATE,
"%s: nrates=%d\n", __func__, nrates);
/*
* Loop over nrates and populate in_ridx from the highest
* rate to the lowest rate. Remember, in_ridx[] has
* IEEE80211_RATE_MAXSIZE entries!
*/
for (i = 0; i < min(nrates, IEEE80211_RATE_MAXSIZE); i++) {
int rate = ni->ni_rates.rs_rates[(nrates - 1) - i] & IEEE80211_RATE_VAL;
/* Map 802.11 rate to HW rate index. */
for (ridx = 0; ridx <= IWM_RIDX_MAX; ridx++)
if (iwm_rates[ridx].rate == rate)
break;
if (ridx > IWM_RIDX_MAX) {
device_printf(sc->sc_dev,
"%s: WARNING: device rate for %d not found!\n",
__func__, rate);
} else {
IWM_DPRINTF(sc, IWM_DEBUG_TXRATE,
"%s: rate: i: %d, rate=%d, ridx=%d\n",
__func__,
i,
rate,
ridx);
in->in_ridx[i] = ridx;
}
}
/* then construct a lq_cmd based on those */
memset(lq, 0, sizeof(*lq));
lq->sta_id = IWM_STATION_ID;
/* For HT, always enable RTS/CTS to avoid excessive retries. */
if (ni->ni_flags & IEEE80211_NODE_HT)
lq->flags |= IWM_LQ_FLAG_USE_RTS_MSK;
/*
* are these used? (we don't do SISO or MIMO)
* need to set them to non-zero, though, or we get an error.
*/
lq->single_stream_ant_msk = 1;
lq->dual_stream_ant_msk = 1;
/*
* Build the actual rate selection table.
* The lowest bits are the rates. Additionally,
* CCK needs bit 9 to be set. The rest of the bits
* we add to the table select the tx antenna
* Note that we add the rates in the highest rate first
* (opposite of ni_rates).
*/
/*
* XXX TODO: this should be looping over the min of nrates
* and LQ_MAX_RETRY_NUM. Sigh.
*/
for (i = 0; i < nrates; i++) {
int nextant;
#if 0
if (txant == 0)
txant = iwm_fw_valid_tx_ant(sc);
nextant = 1<<(ffs(txant)-1);
txant &= ~nextant;
#else
nextant = iwm_fw_valid_tx_ant(sc);
#endif
/*
* Map the rate id into a rate index into
* our hardware table containing the
* configuration to use for this rate.
*/
ridx = in->in_ridx[i];
tab = iwm_rates[ridx].plcp;
tab |= nextant << IWM_RATE_MCS_ANT_POS;
if (IWM_RIDX_IS_CCK(ridx))
tab |= IWM_RATE_MCS_CCK_MSK;
IWM_DPRINTF(sc, IWM_DEBUG_TXRATE,
"station rate i=%d, rate=%d, hw=%x\n",
i, iwm_rates[ridx].rate, tab);
lq->rs_table[i] = htole32(tab);
}
/* then fill the rest with the lowest possible rate */
for (i = nrates; i < nitems(lq->rs_table); i++) {
KASSERT(tab != 0, ("invalid tab"));
lq->rs_table[i] = htole32(tab);
}
}
static int
iwm_media_change(struct ifnet *ifp)
{
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
struct ieee80211vap *vap = ifp->if_softc;
struct ieee80211com *ic = vap->iv_ic;
struct iwm_softc *sc = ic->ic_softc;
int error;
error = ieee80211_media_change(ifp);
if (error != ENETRESET)
return error;
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
IWM_LOCK(sc);
if (ic->ic_nrunning > 0) {
iwm_stop(sc);
iwm_init(sc);
}
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
IWM_UNLOCK(sc);
return error;
}
static int
iwm_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg)
{
struct iwm_vap *ivp = IWM_VAP(vap);
struct ieee80211com *ic = vap->iv_ic;
struct iwm_softc *sc = ic->ic_softc;
struct iwm_node *in;
int error;
IWM_DPRINTF(sc, IWM_DEBUG_STATE,
"switching state %s -> %s\n",
ieee80211_state_name[vap->iv_state],
ieee80211_state_name[nstate]);
IEEE80211_UNLOCK(ic);
IWM_LOCK(sc);
if (vap->iv_state == IEEE80211_S_SCAN && nstate != vap->iv_state)
iwm_led_blink_stop(sc);
/* disable beacon filtering if we're hopping out of RUN */
if (vap->iv_state == IEEE80211_S_RUN && nstate != vap->iv_state) {
iwm_mvm_disable_beacon_filter(sc);
if (((in = IWM_NODE(vap->iv_bss)) != NULL))
in->in_assoc = 0;
if (nstate == IEEE80211_S_INIT) {
IWM_UNLOCK(sc);
IEEE80211_LOCK(ic);
error = ivp->iv_newstate(vap, nstate, arg);
IEEE80211_UNLOCK(ic);
IWM_LOCK(sc);
iwm_release(sc, NULL);
IWM_UNLOCK(sc);
IEEE80211_LOCK(ic);
return error;
}
/*
* It's impossible to directly go RUN->SCAN. If we iwm_release()
* above then the card will be completely reinitialized,
* so the driver must do everything necessary to bring the card
* from INIT to SCAN.
*
* Additionally, upon receiving deauth frame from AP,
* OpenBSD 802.11 stack puts the driver in IEEE80211_S_AUTH
* state. This will also fail with this driver, so bring the FSM
* from IEEE80211_S_RUN to IEEE80211_S_SCAN in this case as well.
*
* XXX TODO: fix this for FreeBSD!
*/
if (nstate == IEEE80211_S_SCAN ||
nstate == IEEE80211_S_AUTH ||
nstate == IEEE80211_S_ASSOC) {
IWM_DPRINTF(sc, IWM_DEBUG_STATE,
"Force transition to INIT; MGT=%d\n", arg);
IWM_UNLOCK(sc);
IEEE80211_LOCK(ic);
/* Always pass arg as -1 since we can't Tx right now. */
/*
* XXX arg is just ignored anyway when transitioning
* to IEEE80211_S_INIT.
*/
vap->iv_newstate(vap, IEEE80211_S_INIT, -1);
IWM_DPRINTF(sc, IWM_DEBUG_STATE,
"Going INIT->SCAN\n");
nstate = IEEE80211_S_SCAN;
IEEE80211_UNLOCK(ic);
IWM_LOCK(sc);
}
}
switch (nstate) {
case IEEE80211_S_INIT:
break;
case IEEE80211_S_AUTH:
if ((error = iwm_auth(vap, sc)) != 0) {
device_printf(sc->sc_dev,
"%s: could not move to auth state: %d\n",
__func__, error);
break;
}
break;
case IEEE80211_S_ASSOC:
if ((error = iwm_assoc(vap, sc)) != 0) {
device_printf(sc->sc_dev,
"%s: failed to associate: %d\n", __func__,
error);
break;
}
break;
case IEEE80211_S_RUN:
{
struct iwm_host_cmd cmd = {
.id = IWM_LQ_CMD,
.len = { sizeof(in->in_lq), },
.flags = IWM_CMD_SYNC,
};
/* Update the association state, now we have it all */
/* (eg associd comes in at this point */
error = iwm_assoc(vap, sc);
if (error != 0) {
device_printf(sc->sc_dev,
"%s: failed to update association state: %d\n",
__func__,
error);
break;
}
in = IWM_NODE(vap->iv_bss);
iwm_mvm_power_mac_update_mode(sc, in);
iwm_mvm_enable_beacon_filter(sc, in);
iwm_mvm_update_quotas(sc, in);
iwm_setrates(sc, in);
cmd.data[0] = &in->in_lq;
if ((error = iwm_send_cmd(sc, &cmd)) != 0) {
device_printf(sc->sc_dev,
"%s: IWM_LQ_CMD failed\n", __func__);
}
iwm_mvm_led_enable(sc);
break;
}
default:
break;
}
IWM_UNLOCK(sc);
IEEE80211_LOCK(ic);
return (ivp->iv_newstate(vap, nstate, arg));
}
void
iwm_endscan_cb(void *arg, int pending)
{
struct iwm_softc *sc = arg;
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
struct ieee80211com *ic = &sc->sc_ic;
IWM_DPRINTF(sc, IWM_DEBUG_SCAN | IWM_DEBUG_TRACE,
"%s: scan ended\n",
__func__);
ieee80211_scan_done(TAILQ_FIRST(&ic->ic_vaps));
}
/*
* Aging and idle timeouts for the different possible scenarios
* in default configuration
*/
static const uint32_t
iwm_sf_full_timeout_def[IWM_SF_NUM_SCENARIO][IWM_SF_NUM_TIMEOUT_TYPES] = {
{
htole32(IWM_SF_SINGLE_UNICAST_AGING_TIMER_DEF),
htole32(IWM_SF_SINGLE_UNICAST_IDLE_TIMER_DEF)
},
{
htole32(IWM_SF_AGG_UNICAST_AGING_TIMER_DEF),
htole32(IWM_SF_AGG_UNICAST_IDLE_TIMER_DEF)
},
{
htole32(IWM_SF_MCAST_AGING_TIMER_DEF),
htole32(IWM_SF_MCAST_IDLE_TIMER_DEF)
},
{
htole32(IWM_SF_BA_AGING_TIMER_DEF),
htole32(IWM_SF_BA_IDLE_TIMER_DEF)
},
{
htole32(IWM_SF_TX_RE_AGING_TIMER_DEF),
htole32(IWM_SF_TX_RE_IDLE_TIMER_DEF)
},
};
/*
* Aging and idle timeouts for the different possible scenarios
* in single BSS MAC configuration.
*/
static const uint32_t
iwm_sf_full_timeout[IWM_SF_NUM_SCENARIO][IWM_SF_NUM_TIMEOUT_TYPES] = {
{
htole32(IWM_SF_SINGLE_UNICAST_AGING_TIMER),
htole32(IWM_SF_SINGLE_UNICAST_IDLE_TIMER)
},
{
htole32(IWM_SF_AGG_UNICAST_AGING_TIMER),
htole32(IWM_SF_AGG_UNICAST_IDLE_TIMER)
},
{
htole32(IWM_SF_MCAST_AGING_TIMER),
htole32(IWM_SF_MCAST_IDLE_TIMER)
},
{
htole32(IWM_SF_BA_AGING_TIMER),
htole32(IWM_SF_BA_IDLE_TIMER)
},
{
htole32(IWM_SF_TX_RE_AGING_TIMER),
htole32(IWM_SF_TX_RE_IDLE_TIMER)
},
};
static void
iwm_mvm_fill_sf_command(struct iwm_softc *sc, struct iwm_sf_cfg_cmd *sf_cmd,
struct ieee80211_node *ni)
{
int i, j, watermark;
sf_cmd->watermark[IWM_SF_LONG_DELAY_ON] = htole32(IWM_SF_W_MARK_SCAN);
/*
* If 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) {
#ifdef notyet
if (ni->ni_rxmcs[2] != 0)
watermark = IWM_SF_W_MARK_MIMO3;
else if (ni->ni_rxmcs[1] != 0)
watermark = IWM_SF_W_MARK_MIMO2;
else
#endif
watermark = IWM_SF_W_MARK_SISO;
} else {
watermark = IWM_SF_W_MARK_LEGACY;
}
/* default watermark value for unassociated mode. */
} else {
watermark = IWM_SF_W_MARK_MIMO2;
}
sf_cmd->watermark[IWM_SF_FULL_ON] = htole32(watermark);
for (i = 0; i < IWM_SF_NUM_SCENARIO; i++) {
for (j = 0; j < IWM_SF_NUM_TIMEOUT_TYPES; j++) {
sf_cmd->long_delay_timeouts[i][j] =
htole32(IWM_SF_LONG_DELAY_AGING_TIMER);
}
}
if (ni) {
memcpy(sf_cmd->full_on_timeouts, iwm_sf_full_timeout,
sizeof(iwm_sf_full_timeout));
} else {
memcpy(sf_cmd->full_on_timeouts, iwm_sf_full_timeout_def,
sizeof(iwm_sf_full_timeout_def));
}
}
static int
iwm_mvm_sf_config(struct iwm_softc *sc, enum iwm_sf_state new_state)
{
struct ieee80211com *ic = &sc->sc_ic;
struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
struct iwm_sf_cfg_cmd sf_cmd = {
.state = htole32(IWM_SF_FULL_ON),
};
int ret = 0;
if (sc->sc_device_family == IWM_DEVICE_FAMILY_8000)
sf_cmd.state |= htole32(IWM_SF_CFG_DUMMY_NOTIF_OFF);
switch (new_state) {
case IWM_SF_UNINIT:
case IWM_SF_INIT_OFF:
iwm_mvm_fill_sf_command(sc, &sf_cmd, NULL);
break;
case IWM_SF_FULL_ON:
iwm_mvm_fill_sf_command(sc, &sf_cmd, vap->iv_bss);
break;
default:
IWM_DPRINTF(sc, IWM_DEBUG_PWRSAVE,
"Invalid state: %d. not sending Smart Fifo cmd\n",
new_state);
return EINVAL;
}
ret = iwm_mvm_send_cmd_pdu(sc, IWM_REPLY_SF_CFG_CMD, IWM_CMD_ASYNC,
sizeof(sf_cmd), &sf_cmd);
return ret;
}
static int
iwm_send_bt_init_conf(struct iwm_softc *sc)
{
struct iwm_bt_coex_cmd bt_cmd;
bt_cmd.mode = htole32(IWM_BT_COEX_WIFI);
bt_cmd.enabled_modules = htole32(IWM_BT_COEX_HIGH_BAND_RET);
return iwm_mvm_send_cmd_pdu(sc, IWM_BT_CONFIG, 0, sizeof(bt_cmd),
&bt_cmd);
}
static int
iwm_send_update_mcc_cmd(struct iwm_softc *sc, const char *alpha2)
{
struct iwm_mcc_update_cmd mcc_cmd;
struct iwm_host_cmd hcmd = {
.id = IWM_MCC_UPDATE_CMD,
.flags = (IWM_CMD_SYNC | IWM_CMD_WANT_SKB),
.data = { &mcc_cmd },
};
int ret;
#ifdef IWM_DEBUG
struct iwm_rx_packet *pkt;
struct iwm_mcc_update_resp_v1 *mcc_resp_v1 = NULL;
struct iwm_mcc_update_resp *mcc_resp;
int n_channels;
uint16_t mcc;
#endif
int resp_v2 = isset(sc->sc_enabled_capa,
IWM_UCODE_TLV_CAPA_LAR_SUPPORT_V2);
memset(&mcc_cmd, 0, sizeof(mcc_cmd));
mcc_cmd.mcc = htole16(alpha2[0] << 8 | alpha2[1]);
if ((sc->sc_ucode_api & IWM_UCODE_TLV_API_WIFI_MCC_UPDATE) ||
isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_LAR_MULTI_MCC))
mcc_cmd.source_id = IWM_MCC_SOURCE_GET_CURRENT;
else
mcc_cmd.source_id = IWM_MCC_SOURCE_OLD_FW;
if (resp_v2)
hcmd.len[0] = sizeof(struct iwm_mcc_update_cmd);
else
hcmd.len[0] = sizeof(struct iwm_mcc_update_cmd_v1);
IWM_DPRINTF(sc, IWM_DEBUG_NODE,
"send MCC update to FW with '%c%c' src = %d\n",
alpha2[0], alpha2[1], mcc_cmd.source_id);
ret = iwm_send_cmd(sc, &hcmd);
if (ret)
return ret;
#ifdef IWM_DEBUG
pkt = hcmd.resp_pkt;
/* Extract MCC response */
if (resp_v2) {
mcc_resp = (void *)pkt->data;
mcc = mcc_resp->mcc;
n_channels = le32toh(mcc_resp->n_channels);
} else {
mcc_resp_v1 = (void *)pkt->data;
mcc = mcc_resp_v1->mcc;
n_channels = le32toh(mcc_resp_v1->n_channels);
}
/* W/A for a FW/NVM issue - returns 0x00 for the world domain */
if (mcc == 0)
mcc = 0x3030; /* "00" - world */
IWM_DPRINTF(sc, IWM_DEBUG_NODE,
"regulatory domain '%c%c' (%d channels available)\n",
mcc >> 8, mcc & 0xff, n_channels);
#endif
iwm_free_resp(sc, &hcmd);
return 0;
}
static void
iwm_mvm_tt_tx_backoff(struct iwm_softc *sc, uint32_t backoff)
{
struct iwm_host_cmd cmd = {
.id = IWM_REPLY_THERMAL_MNG_BACKOFF,
.len = { sizeof(uint32_t), },
.data = { &backoff, },
};
if (iwm_send_cmd(sc, &cmd) != 0) {
device_printf(sc->sc_dev,
"failed to change thermal tx backoff\n");
}
}
static int
iwm_init_hw(struct iwm_softc *sc)
{
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
struct ieee80211com *ic = &sc->sc_ic;
int error, i, ac;
if ((error = iwm_start_hw(sc)) != 0) {
printf("iwm_start_hw: failed %d\n", error);
return error;
}
if ((error = iwm_run_init_mvm_ucode(sc, 0)) != 0) {
printf("iwm_run_init_mvm_ucode: failed %d\n", error);
return error;
}
/*
* should stop and start HW since that INIT
* image just loaded
*/
iwm_stop_device(sc);
if ((error = iwm_start_hw(sc)) != 0) {
device_printf(sc->sc_dev, "could not initialize hardware\n");
return error;
}
/* omstart, this time with the regular firmware */
error = iwm_mvm_load_ucode_wait_alive(sc, IWM_UCODE_TYPE_REGULAR);
if (error) {
device_printf(sc->sc_dev, "could not load firmware\n");
goto error;
}
if ((error = iwm_send_bt_init_conf(sc)) != 0) {
device_printf(sc->sc_dev, "bt init conf failed\n");
goto error;
}
if ((error = iwm_send_tx_ant_cfg(sc, iwm_fw_valid_tx_ant(sc))) != 0) {
device_printf(sc->sc_dev, "antenna config failed\n");
goto error;
}
/* Send phy db control command and then phy db calibration*/
if ((error = iwm_send_phy_db_data(sc)) != 0) {
device_printf(sc->sc_dev, "phy_db_data failed\n");
goto error;
}
if ((error = iwm_send_phy_cfg_cmd(sc)) != 0) {
device_printf(sc->sc_dev, "phy_cfg_cmd failed\n");
goto error;
}
/* Add auxiliary station for scanning */
if ((error = iwm_mvm_add_aux_sta(sc)) != 0) {
device_printf(sc->sc_dev, "add_aux_sta failed\n");
goto error;
}
for (i = 0; i < IWM_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.
*/
if ((error = iwm_mvm_phy_ctxt_add(sc,
&sc->sc_phyctxt[i], &ic->ic_channels[1], 1, 1)) != 0)
goto error;
}
/* Initialize tx backoffs to the minimum. */
if (sc->sc_device_family == IWM_DEVICE_FAMILY_7000)
iwm_mvm_tt_tx_backoff(sc, 0);
error = iwm_mvm_power_update_device(sc);
if (error)
goto error;
if (isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_LAR_SUPPORT)) {
if ((error = iwm_send_update_mcc_cmd(sc, "ZZ")) != 0)
goto error;
}
if (isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_UMAC_SCAN)) {
if ((error = iwm_mvm_config_umac_scan(sc)) != 0)
goto error;
}
/* Enable Tx queues. */
for (ac = 0; ac < WME_NUM_AC; ac++) {
error = iwm_enable_txq(sc, IWM_STATION_ID, ac,
iwm_mvm_ac_to_tx_fifo[ac]);
if (error)
goto error;
}
if ((error = iwm_mvm_disable_beacon_filter(sc)) != 0) {
device_printf(sc->sc_dev, "failed to disable beacon filter\n");
goto error;
}
return 0;
error:
iwm_stop_device(sc);
return error;
}
/* Allow multicast from our BSSID. */
static int
iwm_allow_mcast(struct ieee80211vap *vap, struct iwm_softc *sc)
{
struct ieee80211_node *ni = vap->iv_bss;
struct iwm_mcast_filter_cmd *cmd;
size_t size;
int error;
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, ni->ni_bssid);
error = iwm_mvm_send_cmd_pdu(sc, IWM_MCAST_FILTER_CMD,
IWM_CMD_SYNC, size, cmd);
free(cmd, M_DEVBUF);
return (error);
}
/*
* ifnet interfaces
*/
static void
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
iwm_init(struct iwm_softc *sc)
{
int error;
if (sc->sc_flags & IWM_FLAG_HW_INITED) {
return;
}
sc->sc_generation++;
sc->sc_flags &= ~IWM_FLAG_STOPPED;
if ((error = iwm_init_hw(sc)) != 0) {
printf("iwm_init_hw failed %d\n", error);
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
iwm_stop(sc);
return;
}
/*
* Ok, firmware loaded and we are jogging
*/
sc->sc_flags |= IWM_FLAG_HW_INITED;
callout_reset(&sc->sc_watchdog_to, hz, iwm_watchdog, sc);
}
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
static int
iwm_transmit(struct ieee80211com *ic, struct mbuf *m)
{
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
struct iwm_softc *sc;
int error;
sc = ic->ic_softc;
IWM_LOCK(sc);
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
if ((sc->sc_flags & IWM_FLAG_HW_INITED) == 0) {
IWM_UNLOCK(sc);
return (ENXIO);
}
error = mbufq_enqueue(&sc->sc_snd, m);
if (error) {
IWM_UNLOCK(sc);
return (error);
}
iwm_start(sc);
IWM_UNLOCK(sc);
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
return (0);
}
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
/*
* Dequeue packets from sendq and call send.
*/
static void
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
iwm_start(struct iwm_softc *sc)
{
struct ieee80211_node *ni;
struct mbuf *m;
int ac = 0;
IWM_DPRINTF(sc, IWM_DEBUG_XMIT | IWM_DEBUG_TRACE, "->%s\n", __func__);
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
while (sc->qfullmsk == 0 &&
(m = mbufq_dequeue(&sc->sc_snd)) != NULL) {
ni = (struct ieee80211_node *)m->m_pkthdr.rcvif;
if (iwm_tx(sc, m, ni, ac) != 0) {
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
if_inc_counter(ni->ni_vap->iv_ifp,
IFCOUNTER_OERRORS, 1);
ieee80211_free_node(ni);
continue;
}
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
sc->sc_tx_timer = 15;
}
IWM_DPRINTF(sc, IWM_DEBUG_XMIT | IWM_DEBUG_TRACE, "<-%s\n", __func__);
}
static void
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
iwm_stop(struct iwm_softc *sc)
{
sc->sc_flags &= ~IWM_FLAG_HW_INITED;
sc->sc_flags |= IWM_FLAG_STOPPED;
sc->sc_generation++;
iwm_led_blink_stop(sc);
sc->sc_tx_timer = 0;
iwm_stop_device(sc);
}
static void
iwm_watchdog(void *arg)
{
struct iwm_softc *sc = arg;
struct ieee80211com *ic = &sc->sc_ic;
if (sc->sc_tx_timer > 0) {
if (--sc->sc_tx_timer == 0) {
device_printf(sc->sc_dev, "device timeout\n");
#ifdef IWM_DEBUG
iwm_nic_error(sc);
#endif
ieee80211_restart_all(ic);
counter_u64_add(sc->sc_ic.ic_oerrors, 1);
return;
}
}
callout_reset(&sc->sc_watchdog_to, hz, iwm_watchdog, sc);
}
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
static void
iwm_parent(struct ieee80211com *ic)
{
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
struct iwm_softc *sc = ic->ic_softc;
int startall = 0;
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
IWM_LOCK(sc);
if (ic->ic_nrunning > 0) {
if (!(sc->sc_flags & IWM_FLAG_HW_INITED)) {
iwm_init(sc);
startall = 1;
}
} else if (sc->sc_flags & IWM_FLAG_HW_INITED)
iwm_stop(sc);
IWM_UNLOCK(sc);
if (startall)
ieee80211_start_all(ic);
}
/*
* The interrupt side of things
*/
/*
* error dumping routines are from iwlwifi/mvm/utils.c
*/
/*
* 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 iwm_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 iwm_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))
#ifdef IWM_DEBUG
struct {
const char *name;
uint8_t num;
} advanced_lookup[] = {
{ "NMI_INTERRUPT_WDG", 0x34 },
{ "SYSASSERT", 0x35 },
{ "UCODE_VERSION_MISMATCH", 0x37 },
{ "BAD_COMMAND", 0x38 },
{ "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_ACTION_PT", 0x7C },
{ "NMI_INTERRUPT_UNKNOWN", 0x84 },
{ "NMI_INTERRUPT_INST_ACTION_PT", 0x86 },
{ "ADVANCED_SYSASSERT", 0 },
};
static const char *
iwm_desc_lookup(uint32_t num)
{
int i;
for (i = 0; i < nitems(advanced_lookup) - 1; i++)
if (advanced_lookup[i].num == num)
return advanced_lookup[i].name;
/* No entry matches 'num', so it is the last: ADVANCED_SYSASSERT */
return advanced_lookup[i].name;
}
static void
iwm_nic_umac_error(struct iwm_softc *sc)
{
struct iwm_umac_error_event_table table;
uint32_t base;
base = sc->sc_uc.uc_umac_error_event_table;
if (base < 0x800000) {
device_printf(sc->sc_dev, "Invalid error log pointer 0x%08x\n",
base);
return;
}
if (iwm_read_mem(sc, base, &table, sizeof(table)/sizeof(uint32_t))) {
device_printf(sc->sc_dev, "reading errlog failed\n");
return;
}
if (ERROR_START_OFFSET <= table.valid * ERROR_ELEM_SIZE) {
device_printf(sc->sc_dev, "Start UMAC Error Log Dump:\n");
device_printf(sc->sc_dev, "Status: 0x%x, count: %d\n",
sc->sc_flags, table.valid);
}
device_printf(sc->sc_dev, "0x%08X | %s\n", table.error_id,
iwm_desc_lookup(table.error_id));
device_printf(sc->sc_dev, "0x%08X | umac branchlink1\n", table.blink1);
device_printf(sc->sc_dev, "0x%08X | umac branchlink2\n", table.blink2);
device_printf(sc->sc_dev, "0x%08X | umac interruptlink1\n",
table.ilink1);
device_printf(sc->sc_dev, "0x%08X | umac interruptlink2\n",
table.ilink2);
device_printf(sc->sc_dev, "0x%08X | umac data1\n", table.data1);
device_printf(sc->sc_dev, "0x%08X | umac data2\n", table.data2);
device_printf(sc->sc_dev, "0x%08X | umac data3\n", table.data3);
device_printf(sc->sc_dev, "0x%08X | umac major\n", table.umac_major);
device_printf(sc->sc_dev, "0x%08X | umac minor\n", table.umac_minor);
device_printf(sc->sc_dev, "0x%08X | frame pointer\n",
table.frame_pointer);
device_printf(sc->sc_dev, "0x%08X | stack pointer\n",
table.stack_pointer);
device_printf(sc->sc_dev, "0x%08X | last host cmd\n", table.cmd_header);
device_printf(sc->sc_dev, "0x%08X | isr status reg\n",
table.nic_isr_pref);
}
/*
* 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
iwm_nic_error(struct iwm_softc *sc)
{
struct iwm_error_event_table table;
uint32_t base;
device_printf(sc->sc_dev, "dumping device error log\n");
base = sc->sc_uc.uc_error_event_table;
if (base < 0x800000) {
device_printf(sc->sc_dev,
"Invalid error log pointer 0x%08x\n", base);
return;
}
if (iwm_read_mem(sc, base, &table, sizeof(table)/sizeof(uint32_t))) {
device_printf(sc->sc_dev, "reading errlog failed\n");
return;
}
if (!table.valid) {
device_printf(sc->sc_dev, "errlog not found, skipping\n");
return;
}
if (ERROR_START_OFFSET <= table.valid * ERROR_ELEM_SIZE) {
device_printf(sc->sc_dev, "Start Error Log Dump:\n");
device_printf(sc->sc_dev, "Status: 0x%x, count: %d\n",
sc->sc_flags, table.valid);
}
device_printf(sc->sc_dev, "0x%08X | %-28s\n", table.error_id,
iwm_desc_lookup(table.error_id));
device_printf(sc->sc_dev, "%08X | trm_hw_status0\n",
table.trm_hw_status0);
device_printf(sc->sc_dev, "%08X | trm_hw_status1\n",
table.trm_hw_status1);
device_printf(sc->sc_dev, "%08X | branchlink2\n", table.blink2);
device_printf(sc->sc_dev, "%08X | interruptlink1\n", table.ilink1);
device_printf(sc->sc_dev, "%08X | interruptlink2\n", table.ilink2);
device_printf(sc->sc_dev, "%08X | data1\n", table.data1);
device_printf(sc->sc_dev, "%08X | data2\n", table.data2);
device_printf(sc->sc_dev, "%08X | data3\n", table.data3);
device_printf(sc->sc_dev, "%08X | beacon time\n", table.bcon_time);
device_printf(sc->sc_dev, "%08X | tsf low\n", table.tsf_low);
device_printf(sc->sc_dev, "%08X | tsf hi\n", table.tsf_hi);
device_printf(sc->sc_dev, "%08X | time gp1\n", table.gp1);
device_printf(sc->sc_dev, "%08X | time gp2\n", table.gp2);
device_printf(sc->sc_dev, "%08X | uCode revision type\n",
table.fw_rev_type);
device_printf(sc->sc_dev, "%08X | uCode version major\n", table.major);
device_printf(sc->sc_dev, "%08X | uCode version minor\n", table.minor);
device_printf(sc->sc_dev, "%08X | hw version\n", table.hw_ver);
device_printf(sc->sc_dev, "%08X | board version\n", table.brd_ver);
device_printf(sc->sc_dev, "%08X | hcmd\n", table.hcmd);
device_printf(sc->sc_dev, "%08X | isr0\n", table.isr0);
device_printf(sc->sc_dev, "%08X | isr1\n", table.isr1);
device_printf(sc->sc_dev, "%08X | isr2\n", table.isr2);
device_printf(sc->sc_dev, "%08X | isr3\n", table.isr3);
device_printf(sc->sc_dev, "%08X | isr4\n", table.isr4);
device_printf(sc->sc_dev, "%08X | last cmd Id\n", table.last_cmd_id);
device_printf(sc->sc_dev, "%08X | wait_event\n", table.wait_event);
device_printf(sc->sc_dev, "%08X | l2p_control\n", table.l2p_control);
device_printf(sc->sc_dev, "%08X | l2p_duration\n", table.l2p_duration);
device_printf(sc->sc_dev, "%08X | l2p_mhvalid\n", table.l2p_mhvalid);
device_printf(sc->sc_dev, "%08X | l2p_addr_match\n", table.l2p_addr_match);
device_printf(sc->sc_dev, "%08X | lmpm_pmg_sel\n", table.lmpm_pmg_sel);
device_printf(sc->sc_dev, "%08X | timestamp\n", table.u_timestamp);
device_printf(sc->sc_dev, "%08X | flow_handler\n", table.flow_handler);
if (sc->sc_uc.uc_umac_error_event_table)
iwm_nic_umac_error(sc);
}
#endif
#define ADVANCE_RXQ(sc) (sc->rxq.cur = (sc->rxq.cur + 1) % IWM_RX_RING_COUNT);
/*
* Process an IWM_CSR_INT_BIT_FH_RX or IWM_CSR_INT_BIT_SW_RX interrupt.
* Basic structure from if_iwn
*/
static void
iwm_notif_intr(struct iwm_softc *sc)
{
struct ieee80211com *ic = &sc->sc_ic;
uint16_t hw;
bus_dmamap_sync(sc->rxq.stat_dma.tag, sc->rxq.stat_dma.map,
BUS_DMASYNC_POSTREAD);
hw = le16toh(sc->rxq.stat->closed_rb_num) & 0xfff;
/*
* Process responses
*/
while (sc->rxq.cur != hw) {
struct iwm_rx_ring *ring = &sc->rxq;
struct iwm_rx_data *data = &ring->data[ring->cur];
struct iwm_rx_packet *pkt;
struct iwm_cmd_response *cresp;
int qid, idx, code;
bus_dmamap_sync(ring->data_dmat, data->map,
BUS_DMASYNC_POSTREAD);
pkt = mtod(data->m, struct iwm_rx_packet *);
qid = pkt->hdr.qid & ~0x80;
idx = pkt->hdr.idx;
code = IWM_WIDE_ID(pkt->hdr.flags, pkt->hdr.code);
IWM_DPRINTF(sc, IWM_DEBUG_INTR,
"rx packet qid=%d idx=%d type=%x %d %d\n",
pkt->hdr.qid & ~0x80, pkt->hdr.idx, code, ring->cur, hw);
/*
* randomly get these from the firmware, no idea why.
* they at least seem harmless, so just ignore them for now
*/
if (__predict_false((pkt->hdr.code == 0 && qid == 0 && idx == 0)
|| pkt->len_n_flags == htole32(0x55550000))) {
ADVANCE_RXQ(sc);
continue;
}
switch (code) {
case IWM_REPLY_RX_PHY_CMD:
iwm_mvm_rx_rx_phy_cmd(sc, pkt, data);
break;
case IWM_REPLY_RX_MPDU_CMD:
iwm_mvm_rx_rx_mpdu(sc, pkt, data);
break;
case IWM_TX_CMD:
iwm_mvm_rx_tx_cmd(sc, pkt, data);
break;
case IWM_MISSED_BEACONS_NOTIFICATION: {
struct iwm_missed_beacons_notif *resp;
int missed;
/* XXX look at mac_id to determine interface ID */
struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
resp = (void *)pkt->data;
missed = le32toh(resp->consec_missed_beacons);
IWM_DPRINTF(sc, IWM_DEBUG_BEACON | IWM_DEBUG_STATE,
"%s: MISSED_BEACON: mac_id=%d, "
"consec_since_last_rx=%d, consec=%d, num_expect=%d "
"num_rx=%d\n",
__func__,
le32toh(resp->mac_id),
le32toh(resp->consec_missed_beacons_since_last_rx),
le32toh(resp->consec_missed_beacons),
le32toh(resp->num_expected_beacons),
le32toh(resp->num_recvd_beacons));
/* Be paranoid */
if (vap == NULL)
break;
/* XXX no net80211 locking? */
if (vap->iv_state == IEEE80211_S_RUN &&
(ic->ic_flags & IEEE80211_F_SCAN) == 0) {
if (missed > vap->iv_bmissthreshold) {
/* XXX bad locking; turn into task */
IWM_UNLOCK(sc);
ieee80211_beacon_miss(ic);
IWM_LOCK(sc);
}
}
break; }
case IWM_MFUART_LOAD_NOTIFICATION:
break;
case IWM_MVM_ALIVE: {
struct iwm_mvm_alive_resp_v1 *resp1;
struct iwm_mvm_alive_resp_v2 *resp2;
struct iwm_mvm_alive_resp_v3 *resp3;
if (iwm_rx_packet_payload_len(pkt) == sizeof(*resp1)) {
resp1 = (void *)pkt->data;
sc->sc_uc.uc_error_event_table
= le32toh(resp1->error_event_table_ptr);
sc->sc_uc.uc_log_event_table
= le32toh(resp1->log_event_table_ptr);
sc->sched_base = le32toh(resp1->scd_base_ptr);
if (resp1->status == IWM_ALIVE_STATUS_OK)
sc->sc_uc.uc_ok = 1;
else
sc->sc_uc.uc_ok = 0;
}
if (iwm_rx_packet_payload_len(pkt) == sizeof(*resp2)) {
resp2 = (void *)pkt->data;
sc->sc_uc.uc_error_event_table
= le32toh(resp2->error_event_table_ptr);
sc->sc_uc.uc_log_event_table
= le32toh(resp2->log_event_table_ptr);
sc->sched_base = le32toh(resp2->scd_base_ptr);
sc->sc_uc.uc_umac_error_event_table
= le32toh(resp2->error_info_addr);
if (resp2->status == IWM_ALIVE_STATUS_OK)
sc->sc_uc.uc_ok = 1;
else
sc->sc_uc.uc_ok = 0;
}
if (iwm_rx_packet_payload_len(pkt) == sizeof(*resp3)) {
resp3 = (void *)pkt->data;
sc->sc_uc.uc_error_event_table
= le32toh(resp3->error_event_table_ptr);
sc->sc_uc.uc_log_event_table
= le32toh(resp3->log_event_table_ptr);
sc->sched_base = le32toh(resp3->scd_base_ptr);
sc->sc_uc.uc_umac_error_event_table
= le32toh(resp3->error_info_addr);
if (resp3->status == IWM_ALIVE_STATUS_OK)
sc->sc_uc.uc_ok = 1;
else
sc->sc_uc.uc_ok = 0;
}
sc->sc_uc.uc_intr = 1;
wakeup(&sc->sc_uc);
break; }
case IWM_CALIB_RES_NOTIF_PHY_DB: {
struct iwm_calib_res_notif_phy_db *phy_db_notif;
phy_db_notif = (void *)pkt->data;
iwm_phy_db_set_section(sc, phy_db_notif);
break; }
case IWM_STATISTICS_NOTIFICATION: {
struct iwm_notif_statistics *stats;
stats = (void *)pkt->data;
memcpy(&sc->sc_stats, stats, sizeof(sc->sc_stats));
sc->sc_noise = iwm_get_noise(sc, &stats->rx.general);
break; }
case IWM_NVM_ACCESS_CMD:
case IWM_MCC_UPDATE_CMD:
if (sc->sc_wantresp == ((qid << 16) | idx)) {
memcpy(sc->sc_cmd_resp,
pkt, sizeof(sc->sc_cmd_resp));
}
break;
case IWM_MCC_CHUB_UPDATE_CMD: {
struct iwm_mcc_chub_notif *notif;
notif = (void *)pkt->data;
sc->sc_fw_mcc[0] = (notif->mcc & 0xff00) >> 8;
sc->sc_fw_mcc[1] = notif->mcc & 0xff;
sc->sc_fw_mcc[2] = '\0';
IWM_DPRINTF(sc, IWM_DEBUG_RESET,
"fw source %d sent CC '%s'\n",
notif->source_id, sc->sc_fw_mcc);
break; }
case IWM_DTS_MEASUREMENT_NOTIFICATION:
break;
case IWM_PHY_CONFIGURATION_CMD:
case IWM_TX_ANT_CONFIGURATION_CMD:
case IWM_ADD_STA:
case IWM_MAC_CONTEXT_CMD:
case IWM_REPLY_SF_CFG_CMD:
case IWM_POWER_TABLE_CMD:
case IWM_PHY_CONTEXT_CMD:
case IWM_BINDING_CONTEXT_CMD:
case IWM_TIME_EVENT_CMD:
case IWM_WIDE_ID(IWM_ALWAYS_LONG_GROUP, IWM_SCAN_CFG_CMD):
case IWM_WIDE_ID(IWM_ALWAYS_LONG_GROUP, IWM_SCAN_REQ_UMAC):
case IWM_SCAN_OFFLOAD_REQUEST_CMD:
case IWM_REPLY_BEACON_FILTERING_CMD:
case IWM_MAC_PM_POWER_TABLE:
case IWM_TIME_QUOTA_CMD:
case IWM_REMOVE_STA:
case IWM_TXPATH_FLUSH:
case IWM_LQ_CMD:
case IWM_BT_CONFIG:
case IWM_REPLY_THERMAL_MNG_BACKOFF:
cresp = (void *)pkt->data;
if (sc->sc_wantresp == ((qid << 16) | idx)) {
memcpy(sc->sc_cmd_resp,
pkt, sizeof(*pkt)+sizeof(*cresp));
}
break;
/* ignore */
case 0x6c: /* IWM_PHY_DB_CMD, no idea why it's not in fw-api.h */
break;
case IWM_INIT_COMPLETE_NOTIF:
sc->sc_init_complete = 1;
wakeup(&sc->sc_init_complete);
break;
case IWM_SCAN_OFFLOAD_COMPLETE: {
struct iwm_periodic_scan_complete *notif;
notif = (void *)pkt->data;
break;
}
case IWM_SCAN_ITERATION_COMPLETE: {
struct iwm_lmac_scan_complete_notif *notif;
notif = (void *)pkt->data;
ieee80211_runtask(&sc->sc_ic, &sc->sc_es_task);
break;
}
case IWM_SCAN_COMPLETE_UMAC: {
struct iwm_umac_scan_complete *notif;
notif = (void *)pkt->data;
IWM_DPRINTF(sc, IWM_DEBUG_SCAN,
"UMAC scan complete, status=0x%x\n",
notif->status);
#if 0 /* XXX This would be a duplicate scan end call */
taskqueue_enqueue(sc->sc_tq, &sc->sc_es_task);
#endif
break;
}
case IWM_SCAN_ITERATION_COMPLETE_UMAC: {
struct iwm_umac_scan_iter_complete_notif *notif;
notif = (void *)pkt->data;
IWM_DPRINTF(sc, IWM_DEBUG_SCAN, "UMAC scan iteration "
"complete, status=0x%x, %d channels scanned\n",
notif->status, notif->scanned_channels);
ieee80211_runtask(&sc->sc_ic, &sc->sc_es_task);
break;
}
case IWM_REPLY_ERROR: {
struct iwm_error_resp *resp;
resp = (void *)pkt->data;
device_printf(sc->sc_dev,
"firmware error 0x%x, cmd 0x%x\n",
le32toh(resp->error_type),
resp->cmd_id);
break;
}
case IWM_TIME_EVENT_NOTIFICATION: {
struct iwm_time_event_notif *notif;
notif = (void *)pkt->data;
IWM_DPRINTF(sc, IWM_DEBUG_INTR,
"TE notif status = 0x%x action = 0x%x\n",
notif->status, notif->action);
break;
}
case IWM_MCAST_FILTER_CMD:
break;
case IWM_SCD_QUEUE_CFG: {
struct iwm_scd_txq_cfg_rsp *rsp;
rsp = (void *)pkt->data;
IWM_DPRINTF(sc, IWM_DEBUG_CMD,
"queue cfg token=0x%x sta_id=%d "
"tid=%d scd_queue=%d\n",
rsp->token, rsp->sta_id, rsp->tid,
rsp->scd_queue);
break;
}
default:
device_printf(sc->sc_dev,
"frame %d/%d %x UNHANDLED (this should "
"not happen)\n", qid, idx,
pkt->len_n_flags);
break;
}
/*
* Why test bit 0x80? The Linux driver:
*
* There is one exception: uCode sets bit 15 when it
* originates the response/notification, i.e. when the
* response/notification is not a direct response to a
* command sent by the driver. For example, uCode issues
* IWM_REPLY_RX when it sends a received frame to the driver;
* it is not a direct response to any driver command.
*
* Ok, so since when is 7 == 15? Well, the Linux driver
* uses a slightly different format for pkt->hdr, and "qid"
* is actually the upper byte of a two-byte field.
*/
if (!(pkt->hdr.qid & (1 << 7))) {
iwm_cmd_done(sc, pkt);
}
ADVANCE_RXQ(sc);
}
IWM_CLRBITS(sc, IWM_CSR_GP_CNTRL,
IWM_CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ);
/*
* Tell the firmware what we have processed.
* Seems like the hardware gets upset unless we align
* the write by 8??
*/
hw = (hw == 0) ? IWM_RX_RING_COUNT - 1 : hw - 1;
IWM_WRITE(sc, IWM_FH_RSCSR_CHNL0_WPTR, hw & ~7);
}
static void
iwm_intr(void *arg)
{
struct iwm_softc *sc = arg;
int handled = 0;
int r1, r2, rv = 0;
int isperiodic = 0;
IWM_LOCK(sc);
IWM_WRITE(sc, IWM_CSR_INT_MASK, 0);
if (sc->sc_flags & IWM_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) % IWM_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 = IWM_READ(sc, IWM_CSR_INT);
/* "hardware gone" (where, fishing?) */
if (r1 == 0xffffffff || (r1 & 0xfffffff0) == 0xa5a5a5a0)
goto out;
r2 = IWM_READ(sc, IWM_CSR_FH_INT_STATUS);
}
if (r1 == 0 && r2 == 0) {
goto out_ena;
}
IWM_WRITE(sc, IWM_CSR_INT, r1 | ~sc->sc_intmask);
/* ignored */
handled |= (r1 & (IWM_CSR_INT_BIT_ALIVE /*| IWM_CSR_INT_BIT_SCD*/));
if (r1 & IWM_CSR_INT_BIT_SW_ERR) {
int i;
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
struct ieee80211com *ic = &sc->sc_ic;
struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
#ifdef IWM_DEBUG
iwm_nic_error(sc);
#endif
/* Dump driver status (TX and RX rings) while we're here. */
device_printf(sc->sc_dev, "driver status:\n");
for (i = 0; i < IWM_MVM_MAX_QUEUES; i++) {
struct iwm_tx_ring *ring = &sc->txq[i];
device_printf(sc->sc_dev,
" tx ring %2d: qid=%-2d cur=%-3d "
"queued=%-3d\n",
i, ring->qid, ring->cur, ring->queued);
}
device_printf(sc->sc_dev,
" rx ring: cur=%d\n", sc->rxq.cur);
device_printf(sc->sc_dev,
" 802.11 state %d\n", (vap == NULL) ? -1 : vap->iv_state);
/* Don't stop the device; just do a VAP restart */
IWM_UNLOCK(sc);
if (vap == NULL) {
printf("%s: null vap\n", __func__);
return;
}
device_printf(sc->sc_dev, "%s: controller panicked, iv_state = %d; "
"restarting\n", __func__, vap->iv_state);
/* XXX TODO: turn this into a callout/taskqueue */
ieee80211_restart_all(ic);
return;
}
if (r1 & IWM_CSR_INT_BIT_HW_ERR) {
handled |= IWM_CSR_INT_BIT_HW_ERR;
device_printf(sc->sc_dev, "hardware error, stopping device\n");
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
iwm_stop(sc);
rv = 1;
goto out;
}
/* firmware chunk loaded */
if (r1 & IWM_CSR_INT_BIT_FH_TX) {
IWM_WRITE(sc, IWM_CSR_FH_INT_STATUS, IWM_CSR_FH_INT_TX_MASK);
handled |= IWM_CSR_INT_BIT_FH_TX;
sc->sc_fw_chunk_done = 1;
wakeup(&sc->sc_fw);
}
if (r1 & IWM_CSR_INT_BIT_RF_KILL) {
handled |= IWM_CSR_INT_BIT_RF_KILL;
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
if (iwm_check_rfkill(sc)) {
device_printf(sc->sc_dev,
"%s: rfkill switch, disabling interface\n",
__func__);
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
iwm_stop(sc);
}
}
/*
* The Linux driver uses periodic interrupts to avoid races.
* We cargo-cult like it's going out of fashion.
*/
if (r1 & IWM_CSR_INT_BIT_RX_PERIODIC) {
handled |= IWM_CSR_INT_BIT_RX_PERIODIC;
IWM_WRITE(sc, IWM_CSR_INT, IWM_CSR_INT_BIT_RX_PERIODIC);
if ((r1 & (IWM_CSR_INT_BIT_FH_RX | IWM_CSR_INT_BIT_SW_RX)) == 0)
IWM_WRITE_1(sc,
IWM_CSR_INT_PERIODIC_REG, IWM_CSR_INT_PERIODIC_DIS);
isperiodic = 1;
}
if ((r1 & (IWM_CSR_INT_BIT_FH_RX | IWM_CSR_INT_BIT_SW_RX)) || isperiodic) {
handled |= (IWM_CSR_INT_BIT_FH_RX | IWM_CSR_INT_BIT_SW_RX);
IWM_WRITE(sc, IWM_CSR_FH_INT_STATUS, IWM_CSR_FH_INT_RX_MASK);
iwm_notif_intr(sc);
/* enable periodic interrupt, see above */
if (r1 & (IWM_CSR_INT_BIT_FH_RX | IWM_CSR_INT_BIT_SW_RX) && !isperiodic)
IWM_WRITE_1(sc, IWM_CSR_INT_PERIODIC_REG,
IWM_CSR_INT_PERIODIC_ENA);
}
if (__predict_false(r1 & ~handled))
IWM_DPRINTF(sc, IWM_DEBUG_INTR,
"%s: unhandled interrupts: %x\n", __func__, r1);
rv = 1;
out_ena:
iwm_restore_interrupts(sc);
out:
IWM_UNLOCK(sc);
return;
}
/*
* Autoconf glue-sniffing
*/
#define PCI_VENDOR_INTEL 0x8086
#define PCI_PRODUCT_INTEL_WL_3160_1 0x08b3
#define PCI_PRODUCT_INTEL_WL_3160_2 0x08b4
#define PCI_PRODUCT_INTEL_WL_3165_1 0x3165
#define PCI_PRODUCT_INTEL_WL_3165_2 0x3166
#define PCI_PRODUCT_INTEL_WL_7260_1 0x08b1
#define PCI_PRODUCT_INTEL_WL_7260_2 0x08b2
#define PCI_PRODUCT_INTEL_WL_7265_1 0x095a
#define PCI_PRODUCT_INTEL_WL_7265_2 0x095b
#define PCI_PRODUCT_INTEL_WL_8260_1 0x24f3
#define PCI_PRODUCT_INTEL_WL_8260_2 0x24f4
static const struct iwm_devices {
uint16_t device;
const char *name;
} iwm_devices[] = {
{ PCI_PRODUCT_INTEL_WL_3160_1, "Intel Dual Band Wireless AC 3160" },
{ PCI_PRODUCT_INTEL_WL_3160_2, "Intel Dual Band Wireless AC 3160" },
{ PCI_PRODUCT_INTEL_WL_3165_1, "Intel Dual Band Wireless AC 3165" },
{ PCI_PRODUCT_INTEL_WL_3165_2, "Intel Dual Band Wireless AC 3165" },
{ PCI_PRODUCT_INTEL_WL_7260_1, "Intel Dual Band Wireless AC 7260" },
{ PCI_PRODUCT_INTEL_WL_7260_2, "Intel Dual Band Wireless AC 7260" },
{ PCI_PRODUCT_INTEL_WL_7265_1, "Intel Dual Band Wireless AC 7265" },
{ PCI_PRODUCT_INTEL_WL_7265_2, "Intel Dual Band Wireless AC 7265" },
{ PCI_PRODUCT_INTEL_WL_8260_1, "Intel Dual Band Wireless AC 8260" },
{ PCI_PRODUCT_INTEL_WL_8260_2, "Intel Dual Band Wireless AC 8260" },
};
static int
iwm_probe(device_t dev)
{
int i;
for (i = 0; i < nitems(iwm_devices); i++) {
if (pci_get_vendor(dev) == PCI_VENDOR_INTEL &&
pci_get_device(dev) == iwm_devices[i].device) {
device_set_desc(dev, iwm_devices[i].name);
return (BUS_PROBE_DEFAULT);
}
}
return (ENXIO);
}
static int
iwm_dev_check(device_t dev)
{
struct iwm_softc *sc;
sc = device_get_softc(dev);
sc->sc_hw_rev = IWM_READ(sc, IWM_CSR_HW_REV);
switch (pci_get_device(dev)) {
case PCI_PRODUCT_INTEL_WL_3160_1:
case PCI_PRODUCT_INTEL_WL_3160_2:
sc->sc_fwname = "iwm3160fw";
sc->host_interrupt_operation_mode = 1;
sc->sc_device_family = IWM_DEVICE_FAMILY_7000;
sc->sc_fwdmasegsz = IWM_FWDMASEGSZ;
return (0);
case PCI_PRODUCT_INTEL_WL_3165_1:
case PCI_PRODUCT_INTEL_WL_3165_2:
sc->sc_fwname = "iwm7265fw";
sc->host_interrupt_operation_mode = 0;
sc->sc_device_family = IWM_DEVICE_FAMILY_7000;
sc->sc_fwdmasegsz = IWM_FWDMASEGSZ;
return (0);
case PCI_PRODUCT_INTEL_WL_7260_1:
case PCI_PRODUCT_INTEL_WL_7260_2:
sc->sc_fwname = "iwm7260fw";
sc->host_interrupt_operation_mode = 1;
sc->sc_device_family = IWM_DEVICE_FAMILY_7000;
sc->sc_fwdmasegsz = IWM_FWDMASEGSZ;
return (0);
case PCI_PRODUCT_INTEL_WL_7265_1:
case PCI_PRODUCT_INTEL_WL_7265_2:
sc->sc_fwname = "iwm7265fw";
sc->host_interrupt_operation_mode = 0;
sc->sc_device_family = IWM_DEVICE_FAMILY_7000;
sc->sc_fwdmasegsz = IWM_FWDMASEGSZ;
return (0);
case PCI_PRODUCT_INTEL_WL_8260_1:
case PCI_PRODUCT_INTEL_WL_8260_2:
sc->sc_fwname = "iwm8000Cfw";
sc->host_interrupt_operation_mode = 0;
sc->sc_device_family = IWM_DEVICE_FAMILY_8000;
sc->sc_fwdmasegsz = IWM_FWDMASEGSZ_8000;
return (0);
default:
device_printf(dev, "unknown adapter type\n");
return ENXIO;
}
}
static int
iwm_pci_attach(device_t dev)
{
struct iwm_softc *sc;
int count, error, rid;
uint16_t reg;
sc = device_get_softc(dev);
/* Clear device-specific "PCI retry timeout" register (41h). */
reg = pci_read_config(dev, 0x40, sizeof(reg));
pci_write_config(dev, 0x40, reg & ~0xff00, sizeof(reg));
/* Enable bus-mastering and hardware bug workaround. */
pci_enable_busmaster(dev);
reg = pci_read_config(dev, PCIR_STATUS, sizeof(reg));
/* if !MSI */
if (reg & PCIM_STATUS_INTxSTATE) {
reg &= ~PCIM_STATUS_INTxSTATE;
}
pci_write_config(dev, PCIR_STATUS, reg, sizeof(reg));
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);
/* Install interrupt handler. */
count = 1;
rid = 0;
if (pci_alloc_msi(dev, &count) == 0)
rid = 1;
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, iwm_intr, sc, &sc->sc_ih);
if (sc->sc_ih == NULL) {
device_printf(dev, "can't establish interrupt");
return (ENXIO);
}
sc->sc_dmat = bus_get_dma_tag(sc->sc_dev);
return (0);
}
static void
iwm_pci_detach(device_t dev)
{
struct iwm_softc *sc = device_get_softc(dev);
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);
}
static int
iwm_attach(device_t dev)
{
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
struct iwm_softc *sc = device_get_softc(dev);
struct ieee80211com *ic = &sc->sc_ic;
int error;
int txq_i, i;
sc->sc_dev = dev;
IWM_LOCK_INIT(sc);
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
mbufq_init(&sc->sc_snd, ifqmaxlen);
callout_init_mtx(&sc->sc_watchdog_to, &sc->sc_mtx, 0);
callout_init_mtx(&sc->sc_led_blink_to, &sc->sc_mtx, 0);
TASK_INIT(&sc->sc_es_task, 0, iwm_endscan_cb, sc);
/* PCI attach */
error = iwm_pci_attach(dev);
if (error != 0)
goto fail;
sc->sc_wantresp = -1;
/* Check device type */
error = iwm_dev_check(dev);
if (error != 0)
goto fail;
/*
* We now start fiddling with the hardware
*/
/*
* 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.
*/
if (sc->sc_device_family == IWM_DEVICE_FAMILY_8000)
sc->sc_hw_rev = (sc->sc_hw_rev & 0xfff0) |
(IWM_CSR_HW_REV_STEP(sc->sc_hw_rev << 2) << 2);
if (iwm_prepare_card_hw(sc) != 0) {
device_printf(dev, "could not initialize hardware\n");
goto fail;
}
if (sc->sc_device_family == IWM_DEVICE_FAMILY_8000) {
int ret;
uint32_t hw_step;
/*
* In order to recognize C step the driver should read the
* chip version id located at the AUX bus MISC address.
*/
IWM_SETBITS(sc, IWM_CSR_GP_CNTRL,
IWM_CSR_GP_CNTRL_REG_FLAG_INIT_DONE);
DELAY(2);
ret = iwm_poll_bit(sc, IWM_CSR_GP_CNTRL,
IWM_CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY,
IWM_CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY,
25000);
if (!ret) {
device_printf(sc->sc_dev,
"Failed to wake up the nic\n");
goto fail;
}
if (iwm_nic_lock(sc)) {
hw_step = iwm_read_prph(sc, IWM_WFPM_CTRL_REG);
hw_step |= IWM_ENABLE_WFPM;
iwm_write_prph(sc, IWM_WFPM_CTRL_REG, hw_step);
hw_step = iwm_read_prph(sc, IWM_AUX_MISC_REG);
hw_step = (hw_step >> IWM_HW_STEP_LOCATION_BITS) & 0xF;
if (hw_step == 0x3)
sc->sc_hw_rev = (sc->sc_hw_rev & 0xFFFFFFF3) |
(IWM_SILICON_C_STEP << 2);
iwm_nic_unlock(sc);
} else {
device_printf(sc->sc_dev, "Failed to lock the nic\n");
goto fail;
}
}
/* Allocate DMA memory for firmware transfers. */
if ((error = iwm_alloc_fwmem(sc)) != 0) {
device_printf(dev, "could not allocate memory for firmware\n");
goto fail;
}
/* Allocate "Keep Warm" page. */
if ((error = iwm_alloc_kw(sc)) != 0) {
device_printf(dev, "could not allocate keep warm page\n");
goto fail;
}
/* We use ICT interrupts */
if ((error = iwm_alloc_ict(sc)) != 0) {
device_printf(dev, "could not allocate ICT table\n");
goto fail;
}
/* Allocate TX scheduler "rings". */
if ((error = iwm_alloc_sched(sc)) != 0) {
device_printf(dev, "could not allocate TX scheduler rings\n");
goto fail;
}
/* Allocate TX rings */
for (txq_i = 0; txq_i < nitems(sc->txq); txq_i++) {
if ((error = iwm_alloc_tx_ring(sc,
&sc->txq[txq_i], txq_i)) != 0) {
device_printf(dev,
"could not allocate TX ring %d\n",
txq_i);
goto fail;
}
}
/* Allocate RX ring. */
if ((error = iwm_alloc_rx_ring(sc, &sc->rxq)) != 0) {
device_printf(dev, "could not allocate RX ring\n");
goto fail;
}
/* Clear pending interrupts. */
IWM_WRITE(sc, IWM_CSR_INT, 0xffffffff);
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_WPA | /* WPA/RSN */
IEEE80211_C_WME |
IEEE80211_C_SHSLOT | /* short slot time supported */
IEEE80211_C_SHPREAMBLE /* short preamble supported */
// IEEE80211_C_BGSCAN /* capable of bg scanning */
;
/* Advertise full-offload scanning */
ic->ic_flags_ext = IEEE80211_FEXT_SCAN_OFFLOAD;
for (i = 0; i < nitems(sc->sc_phyctxt); i++) {
sc->sc_phyctxt[i].id = i;
sc->sc_phyctxt[i].color = 0;
sc->sc_phyctxt[i].ref = 0;
sc->sc_phyctxt[i].channel = NULL;
}
/* Default noise floor */
sc->sc_noise = -96;
/* Max RSSI */
sc->sc_max_rssi = IWM_MAX_DBM - IWM_MIN_DBM;
sc->sc_preinit_hook.ich_func = iwm_preinit;
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 fail;
}
#ifdef IWM_DEBUG
SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "debug",
CTLFLAG_RW, &sc->sc_debug, 0, "control debugging");
#endif
IWM_DPRINTF(sc, IWM_DEBUG_RESET | IWM_DEBUG_TRACE,
"<-%s\n", __func__);
return 0;
/* Free allocated memory if something failed during attachment. */
fail:
iwm_detach_local(sc, 0);
return ENXIO;
}
static int
iwm_is_valid_ether_addr(uint8_t *addr)
{
char zero_addr[IEEE80211_ADDR_LEN] = { 0, 0, 0, 0, 0, 0 };
if ((addr[0] & 1) || IEEE80211_ADDR_EQ(zero_addr, addr))
return (FALSE);
return (TRUE);
}
static int
iwm_update_edca(struct ieee80211com *ic)
{
struct iwm_softc *sc = ic->ic_softc;
device_printf(sc->sc_dev, "%s: called\n", __func__);
return (0);
}
static void
iwm_preinit(void *arg)
{
struct iwm_softc *sc = arg;
device_t dev = sc->sc_dev;
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
struct ieee80211com *ic = &sc->sc_ic;
int error;
IWM_DPRINTF(sc, IWM_DEBUG_RESET | IWM_DEBUG_TRACE,
"->%s\n", __func__);
IWM_LOCK(sc);
if ((error = iwm_start_hw(sc)) != 0) {
device_printf(dev, "could not initialize hardware\n");
IWM_UNLOCK(sc);
goto fail;
}
error = iwm_run_init_mvm_ucode(sc, 1);
iwm_stop_device(sc);
if (error) {
IWM_UNLOCK(sc);
goto fail;
}
device_printf(dev,
"hw rev 0x%x, fw ver %s, address %s\n",
sc->sc_hw_rev & IWM_CSR_HW_REV_TYPE_MSK,
sc->sc_fwver, ether_sprintf(sc->sc_nvm.hw_addr));
/* 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]));
IWM_UNLOCK(sc);
iwm_init_channel_map(ic, IEEE80211_CHAN_MAX, &ic->ic_nchans,
ic->ic_channels);
/*
* At this point we've committed - if we fail to do setup,
* we now also have to tear down the net80211 state.
*/
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
ieee80211_ifattach(ic);
ic->ic_vap_create = iwm_vap_create;
ic->ic_vap_delete = iwm_vap_delete;
ic->ic_raw_xmit = iwm_raw_xmit;
ic->ic_node_alloc = iwm_node_alloc;
ic->ic_scan_start = iwm_scan_start;
ic->ic_scan_end = iwm_scan_end;
ic->ic_update_mcast = iwm_update_mcast;
ic->ic_getradiocaps = iwm_init_channel_map;
ic->ic_set_channel = iwm_set_channel;
ic->ic_scan_curchan = iwm_scan_curchan;
ic->ic_scan_mindwell = iwm_scan_mindwell;
ic->ic_wme.wme_update = iwm_update_edca;
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
ic->ic_parent = iwm_parent;
ic->ic_transmit = iwm_transmit;
iwm_radiotap_attach(sc);
if (bootverbose)
ieee80211_announce(ic);
IWM_DPRINTF(sc, IWM_DEBUG_RESET | IWM_DEBUG_TRACE,
"<-%s\n", __func__);
config_intrhook_disestablish(&sc->sc_preinit_hook);
return;
fail:
config_intrhook_disestablish(&sc->sc_preinit_hook);
iwm_detach_local(sc, 0);
}
/*
* Attach the interface to 802.11 radiotap.
*/
static void
iwm_radiotap_attach(struct iwm_softc *sc)
{
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
struct ieee80211com *ic = &sc->sc_ic;
IWM_DPRINTF(sc, IWM_DEBUG_RESET | IWM_DEBUG_TRACE,
"->%s begin\n", __func__);
ieee80211_radiotap_attach(ic,
&sc->sc_txtap.wt_ihdr, sizeof(sc->sc_txtap),
IWM_TX_RADIOTAP_PRESENT,
&sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap),
IWM_RX_RADIOTAP_PRESENT);
IWM_DPRINTF(sc, IWM_DEBUG_RESET | IWM_DEBUG_TRACE,
"->%s end\n", __func__);
}
static struct ieee80211vap *
iwm_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 iwm_vap *ivp;
struct ieee80211vap *vap;
if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */
return NULL;
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
ivp = malloc(sizeof(struct iwm_vap), M_80211_VAP, M_WAITOK | M_ZERO);
vap = &ivp->iv_vap;
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
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 = iwm_newstate;
ieee80211_ratectl_init(vap);
/* Complete setup. */
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
ieee80211_vap_attach(vap, iwm_media_change, ieee80211_media_status,
mac);
ic->ic_opmode = opmode;
return vap;
}
static void
iwm_vap_delete(struct ieee80211vap *vap)
{
struct iwm_vap *ivp = IWM_VAP(vap);
ieee80211_ratectl_deinit(vap);
ieee80211_vap_detach(vap);
free(ivp, M_80211_VAP);
}
static void
iwm_scan_start(struct ieee80211com *ic)
{
struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
struct iwm_softc *sc = ic->ic_softc;
int error;
IWM_LOCK(sc);
if (isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_UMAC_SCAN))
error = iwm_mvm_umac_scan(sc);
else
error = iwm_mvm_lmac_scan(sc);
if (error != 0) {
device_printf(sc->sc_dev, "could not initiate 2 GHz scan\n");
IWM_UNLOCK(sc);
ieee80211_cancel_scan(vap);
} else {
iwm_led_blink_start(sc);
IWM_UNLOCK(sc);
}
}
static void
iwm_scan_end(struct ieee80211com *ic)
{
struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
struct iwm_softc *sc = ic->ic_softc;
IWM_LOCK(sc);
iwm_led_blink_stop(sc);
if (vap->iv_state == IEEE80211_S_RUN)
iwm_mvm_led_enable(sc);
IWM_UNLOCK(sc);
}
static void
iwm_update_mcast(struct ieee80211com *ic)
{
}
static void
iwm_set_channel(struct ieee80211com *ic)
{
}
static void
iwm_scan_curchan(struct ieee80211_scan_state *ss, unsigned long maxdwell)
{
}
static void
iwm_scan_mindwell(struct ieee80211_scan_state *ss)
{
return;
}
void
iwm_init_task(void *arg1)
{
struct iwm_softc *sc = arg1;
IWM_LOCK(sc);
while (sc->sc_flags & IWM_FLAG_BUSY)
msleep(&sc->sc_flags, &sc->sc_mtx, 0, "iwmpwr", 0);
sc->sc_flags |= IWM_FLAG_BUSY;
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
iwm_stop(sc);
if (sc->sc_ic.ic_nrunning > 0)
iwm_init(sc);
sc->sc_flags &= ~IWM_FLAG_BUSY;
wakeup(&sc->sc_flags);
IWM_UNLOCK(sc);
}
static int
iwm_resume(device_t dev)
{
struct iwm_softc *sc = device_get_softc(dev);
int do_reinit = 0;
uint16_t reg;
/* Clear device-specific "PCI retry timeout" register (41h). */
reg = pci_read_config(dev, 0x40, sizeof(reg));
pci_write_config(dev, 0x40, reg & ~0xff00, sizeof(reg));
iwm_init_task(device_get_softc(dev));
IWM_LOCK(sc);
if (sc->sc_flags & IWM_FLAG_SCANNING) {
sc->sc_flags &= ~IWM_FLAG_SCANNING;
do_reinit = 1;
}
IWM_UNLOCK(sc);
if (do_reinit)
ieee80211_resume_all(&sc->sc_ic);
return 0;
}
static int
iwm_suspend(device_t dev)
{
int do_stop = 0;
struct iwm_softc *sc = device_get_softc(dev);
do_stop = !! (sc->sc_ic.ic_nrunning > 0);
ieee80211_suspend_all(&sc->sc_ic);
if (do_stop) {
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
IWM_LOCK(sc);
iwm_stop(sc);
sc->sc_flags |= IWM_FLAG_SCANNING;
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
IWM_UNLOCK(sc);
}
return (0);
}
static int
iwm_detach_local(struct iwm_softc *sc, int do_net80211)
{
struct iwm_fw_info *fw = &sc->sc_fw;
device_t dev = sc->sc_dev;
int i;
if (do_net80211)
ieee80211_draintask(&sc->sc_ic, &sc->sc_es_task);
callout_drain(&sc->sc_led_blink_to);
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
callout_drain(&sc->sc_watchdog_to);
iwm_stop_device(sc);
if (do_net80211) {
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
ieee80211_ifdetach(&sc->sc_ic);
}
iwm_phy_db_free(sc);
/* Free descriptor rings */
iwm_free_rx_ring(sc, &sc->rxq);
for (i = 0; i < nitems(sc->txq); i++)
iwm_free_tx_ring(sc, &sc->txq[i]);
/* Free firmware */
if (fw->fw_fp != NULL)
iwm_fw_info_free(fw);
/* Free scheduler */
iwm_dma_contig_free(&sc->sched_dma);
iwm_dma_contig_free(&sc->ict_dma);
iwm_dma_contig_free(&sc->kw_dma);
iwm_dma_contig_free(&sc->fw_dma);
/* Finished with the hardware - detach things */
iwm_pci_detach(dev);
Replay r286410. Change KPI of how device drivers that provide wireless connectivity interact with the net80211 stack. Historical background: originally wireless devices created an interface, just like Ethernet devices do. Name of an interface matched the name of the driver that created. Later, wlan(4) layer was introduced, and the wlanX interfaces become the actual interface, leaving original ones as "a parent interface" of wlanX. Kernelwise, the KPI between net80211 layer and a driver became a mix of methods that pass a pointer to struct ifnet as identifier and methods that pass pointer to struct ieee80211com. From user point of view, the parent interface just hangs on in the ifconfig list, and user can't do anything useful with it. Now, the struct ifnet goes away. The struct ieee80211com is the only KPI between a device driver and net80211. Details: - The struct ieee80211com is embedded into drivers softc. - Packets are sent via new ic_transmit method, which is very much like the previous if_transmit. - Bringing parent up/down is done via new ic_parent method, which notifies driver about any changes: number of wlan(4) interfaces, number of them in promisc or allmulti state. - Device specific ioctls (if any) are received on new ic_ioctl method. - Packets/errors accounting are done by the stack. In certain cases, when driver experiences errors and can not attribute them to any specific interface, driver updates ic_oerrors or ic_ierrors counters. Details on interface configuration with new world order: - A sequence of commands needed to bring up wireless DOESN"T change. - /etc/rc.conf parameters DON'T change. - List of devices that can be used to create wlan(4) interfaces is now provided by net.wlan.devices sysctl. Most drivers in this change were converted by me, except of wpi(4), that was done by Andriy Voskoboinyk. Big thanks to Kevin Lo for testing changes to at least 8 drivers. Thanks to pluknet@, Oliver Hartmann, Olivier Cochard, gjb@, mmoll@, op@ and lev@, who also participated in testing. Reviewed by: adrian Sponsored by: Netflix Sponsored by: Nginx, Inc.
2015-08-27 08:56:39 +00:00
mbufq_drain(&sc->sc_snd);
IWM_LOCK_DESTROY(sc);
return (0);
}
static int
iwm_detach(device_t dev)
{
struct iwm_softc *sc = device_get_softc(dev);
return (iwm_detach_local(sc, 1));
}
static device_method_t iwm_pci_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, iwm_probe),
DEVMETHOD(device_attach, iwm_attach),
DEVMETHOD(device_detach, iwm_detach),
DEVMETHOD(device_suspend, iwm_suspend),
DEVMETHOD(device_resume, iwm_resume),
DEVMETHOD_END
};
static driver_t iwm_pci_driver = {
"iwm",
iwm_pci_methods,
sizeof (struct iwm_softc)
};
static devclass_t iwm_devclass;
DRIVER_MODULE(iwm, pci, iwm_pci_driver, iwm_devclass, NULL, NULL);
MODULE_DEPEND(iwm, firmware, 1, 1, 1);
MODULE_DEPEND(iwm, pci, 1, 1, 1);
MODULE_DEPEND(iwm, wlan, 1, 1, 1);