Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 115 kB image not shown  

Quelle  trans.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
 * Copyright (C) 2007-2015, 2018-2024 Intel Corporation
 * Copyright (C) 2013-2015 Intel Mobile Communications GmbH
 * Copyright (C) 2016-2017 Intel Deutschland GmbH
 */

#include <linux/pci.h>
#include <linux/interrupt.h>
#include <linux/debugfs.h>
#include <linux/sched.h>
#include <linux/bitops.h>
#include <linux/gfp.h>
#include <linux/vmalloc.h>
#include <linux/module.h>
#include <linux/wait.h>
#include <linux/seq_file.h>

#include "iwl-drv.h"
#include "iwl-trans.h"
#include "iwl-csr.h"
#include "iwl-prph.h"
#include "iwl-scd.h"
#include "iwl-agn-hw.h"
#include "fw/error-dump.h"
#include "fw/dbg.h"
#include "fw/api/tx.h"
#include "fw/acpi.h"
#include "fw/api/tx.h"
#include "mei/iwl-mei.h"
#include "internal.h"
#include "iwl-fh.h"
#include "pcie/iwl-context-info-v2.h"
#include "pcie/utils.h"

/* extended range in FW SRAM */
#define IWL_FW_MEM_EXTENDED_START 0x40000
#define IWL_FW_MEM_EXTENDED_END  0x57FFF

int iwl_trans_pcie_sw_reset(struct iwl_trans *trans, bool retake_ownership)
{
 /* Reset entire device - do controller reset (results in SHRD_HW_RST) */
 if (trans->mac_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) {
  iwl_set_bit(trans, CSR_GP_CNTRL,
       CSR_GP_CNTRL_REG_FLAG_SW_RESET);
  usleep_range(10000, 20000);
 } else {
  iwl_set_bit(trans, CSR_RESET,
       CSR_RESET_REG_FLAG_SW_RESET);
  usleep_range(5000, 6000);
 }

 if (retake_ownership)
  return iwl_pcie_prepare_card_hw(trans);

 return 0;
}

static void iwl_pcie_free_fw_monitor(struct iwl_trans *trans)
{
 struct iwl_dram_data *fw_mon = &trans->dbg.fw_mon;

 if (!fw_mon->size)
  return;

 dma_free_coherent(trans->dev, fw_mon->size, fw_mon->block,
     fw_mon->physical);

 fw_mon->block = NULL;
 fw_mon->physical = 0;
 fw_mon->size = 0;
}

static void iwl_pcie_alloc_fw_monitor_block(struct iwl_trans *trans,
         u8 max_power)
{
 struct iwl_dram_data *fw_mon = &trans->dbg.fw_mon;
 void *block = NULL;
 dma_addr_t physical = 0;
 u32 size = 0;
 u8 power;

 if (fw_mon->size) {
  memset(fw_mon->block, 0, fw_mon->size);
  return;
 }

 /* need at least 2 KiB, so stop at 11 */
 for (power = max_power; power >= 11; power--) {
  size = BIT(power);
  block = dma_alloc_coherent(trans->dev, size, &physical,
        GFP_KERNEL | __GFP_NOWARN);
  if (!block)
   continue;

  IWL_INFO(trans,
    "Allocated 0x%08x bytes for firmware monitor.\n",
    size);
  break;
 }

 if (WARN_ON_ONCE(!block))
  return;

 if (power != max_power)
  IWL_ERR(trans,
   "Sorry - debug buffer is only %luK while you requested %luK\n",
   (unsigned long)BIT(power - 10),
   (unsigned long)BIT(max_power - 10));

 fw_mon->block = block;
 fw_mon->physical = physical;
 fw_mon->size = size;
}

void iwl_pcie_alloc_fw_monitor(struct iwl_trans *trans, u8 max_power)
{
 if (!max_power) {
  /* default max_power is maximum */
  max_power = 26;
 } else {
  max_power += 11;
 }

 if (WARN(max_power > 26,
   "External buffer size for monitor is too big %d, check the FW TLV\n",
   max_power))
  return;

 iwl_pcie_alloc_fw_monitor_block(trans, max_power);
}

static u32 iwl_trans_pcie_read_shr(struct iwl_trans *trans, u32 reg)
{
 iwl_write32(trans, HEEP_CTRL_WRD_PCIEX_CTRL_REG,
      ((reg & 0x0000ffff) | (2 << 28)));
 return iwl_read32(trans, HEEP_CTRL_WRD_PCIEX_DATA_REG);
}

static void iwl_trans_pcie_write_shr(struct iwl_trans *trans, u32 reg, u32 val)
{
 iwl_write32(trans, HEEP_CTRL_WRD_PCIEX_DATA_REG, val);
 iwl_write32(trans, HEEP_CTRL_WRD_PCIEX_CTRL_REG,
      ((reg & 0x0000ffff) | (3 << 28)));
}

static void iwl_pcie_set_pwr(struct iwl_trans *trans, bool vaux)
{
 if (trans->mac_cfg->base->apmg_not_supported)
  return;

 if (vaux && pci_pme_capable(to_pci_dev(trans->dev), PCI_D3cold))
  iwl_set_bits_mask_prph(trans, APMG_PS_CTRL_REG,
           APMG_PS_CTRL_VAL_PWR_SRC_VAUX,
           ~APMG_PS_CTRL_MSK_PWR_SRC);
 else
  iwl_set_bits_mask_prph(trans, APMG_PS_CTRL_REG,
           APMG_PS_CTRL_VAL_PWR_SRC_VMAIN,
           ~APMG_PS_CTRL_MSK_PWR_SRC);
}

/* PCI registers */
#define PCI_CFG_RETRY_TIMEOUT 0x041

void iwl_pcie_apm_config(struct iwl_trans *trans)
{
 struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
 u16 lctl;
 u16 cap;

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

 iwl_set_bit(trans, CSR_GIO_REG, CSR_GIO_REG_VAL_L0S_DISABLED);

 pcie_capability_read_word(trans_pcie->pci_dev, PCI_EXP_LNKCTL, &lctl);
 trans->pm_support = !(lctl & PCI_EXP_LNKCTL_ASPM_L0S);

 pcie_capability_read_word(trans_pcie->pci_dev, PCI_EXP_DEVCTL2, &cap);
 trans->ltr_enabled = cap & PCI_EXP_DEVCTL2_LTR_EN;
 IWL_DEBUG_POWER(trans, "L1 %sabled - LTR %sabled\n",
   (lctl & PCI_EXP_LNKCTL_ASPM_L1) ? "En" : "Dis",
   trans->ltr_enabled ? "En" : "Dis");
}

/*
 * Start up NIC's basic functionality after it has been reset
 * (e.g. after platform boot, or shutdown via iwl_pcie_apm_stop())
 * NOTE:  This does not load uCode nor start the embedded processor
 */

static int iwl_pcie_apm_init(struct iwl_trans *trans)
{
 int ret;

 IWL_DEBUG_INFO(trans, "Init card's basic functions\n");

 /*
 * Use "set_bit" below rather than "write", to preserve any hardware
 * bits already set by default after reset.
 */


 /* Disable L0S exit timer (platform NMI Work/Around) */
 if (trans->mac_cfg->device_family < IWL_DEVICE_FAMILY_8000)
  iwl_set_bit(trans, CSR_GIO_CHICKEN_BITS,
       CSR_GIO_CHICKEN_BITS_REG_BIT_DIS_L0S_EXIT_TIMER);

 /*
 * Disable L0s without affecting L1;
 *  don't wait for ICH L0s (ICH bug W/A)
 */

 iwl_set_bit(trans, CSR_GIO_CHICKEN_BITS,
      CSR_GIO_CHICKEN_BITS_REG_BIT_L1A_NO_L0S_RX);

 /* Set FH wait threshold to maximum (HW error during stress W/A) */
 iwl_set_bit(trans, CSR_DBG_HPET_MEM_REG, CSR_DBG_HPET_MEM_REG_VAL);

 /*
 * Enable HAP INTA (interrupt from management bus) to
 * wake device's PCI Express link L1a -> L0s
 */

 iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG,
      CSR_HW_IF_CONFIG_REG_HAP_WAKE);

 iwl_pcie_apm_config(trans);

 /* Configure analog phase-lock-loop before activating to D0A */
 if (trans->mac_cfg->base->pll_cfg)
  iwl_set_bit(trans, CSR_ANA_PLL_CFG, CSR50_ANA_PLL_CFG_VAL);

 ret = iwl_finish_nic_init(trans);
 if (ret)
  return ret;

 if (trans->cfg->host_interrupt_operation_mode) {
  /*
 * This is a bit of an abuse - This is needed for 7260 / 3160
 * only check host_interrupt_operation_mode even if this is
 * not related to host_interrupt_operation_mode.
 *
 * Enable the oscillator to count wake up time for L1 exit. This
 * consumes slightly more power (100uA) - but allows to be sure
 * that we wake up from L1 on time.
 *
 * This looks weird: read twice the same register, discard the
 * value, set a bit, and yet again, read that same register
 * just to discard the value. But that's the way the hardware
 * seems to like it.
 */

  iwl_read_prph(trans, OSC_CLK);
  iwl_read_prph(trans, OSC_CLK);
  iwl_set_bits_prph(trans, OSC_CLK, OSC_CLK_FORCE_CONTROL);
  iwl_read_prph(trans, OSC_CLK);
  iwl_read_prph(trans, OSC_CLK);
 }

 /*
 * Enable DMA clock and wait for it to stabilize.
 *
 * Write to "CLK_EN_REG"; "1" bits enable clocks, while "0"
 * bits do not disable clocks.  This preserves any hardware
 * bits already set by default in "CLK_CTRL_REG" after reset.
 */

 if (!trans->mac_cfg->base->apmg_not_supported) {
  iwl_write_prph(trans, APMG_CLK_EN_REG,
          APMG_CLK_VAL_DMA_CLK_RQT);
  udelay(20);

  /* Disable L1-Active */
  iwl_set_bits_prph(trans, APMG_PCIDEV_STT_REG,
      APMG_PCIDEV_STT_VAL_L1_ACT_DIS);

  /* Clear the interrupt in APMG if the NIC is in RFKILL */
  iwl_write_prph(trans, APMG_RTC_INT_STT_REG,
          APMG_RTC_INT_STT_RFKILL);
 }

 set_bit(STATUS_DEVICE_ENABLED, &trans->status);

 return 0;
}

/*
 * Enable LP XTAL to avoid HW bug where device may consume much power if
 * FW is not loaded after device reset. LP XTAL is disabled by default
 * after device HW reset. Do it only if XTAL is fed by internal source.
 * Configure device's "persistence" mode to avoid resetting XTAL again when
 * SHRD_HW_RST occurs in S3.
 */

static void iwl_pcie_apm_lp_xtal_enable(struct iwl_trans *trans)
{
 int ret;
 u32 apmg_gp1_reg;
 u32 apmg_xtal_cfg_reg;
 u32 dl_cfg_reg;

 /* Force XTAL ON */
 iwl_trans_set_bit(trans, CSR_GP_CNTRL,
     CSR_GP_CNTRL_REG_FLAG_XTAL_ON);

 ret = iwl_trans_pcie_sw_reset(trans, true);

 if (!ret)
  ret = iwl_finish_nic_init(trans);

 if (WARN_ON(ret)) {
  /* Release XTAL ON request */
  iwl_trans_clear_bit(trans, CSR_GP_CNTRL,
        CSR_GP_CNTRL_REG_FLAG_XTAL_ON);
  return;
 }

 /*
 * Clear "disable persistence" to avoid LP XTAL resetting when
 * SHRD_HW_RST is applied in S3.
 */

 iwl_clear_bits_prph(trans, APMG_PCIDEV_STT_REG,
        APMG_PCIDEV_STT_VAL_PERSIST_DIS);

 /*
 * Force APMG XTAL to be active to prevent its disabling by HW
 * caused by APMG idle state.
 */

 apmg_xtal_cfg_reg = iwl_trans_pcie_read_shr(trans,
          SHR_APMG_XTAL_CFG_REG);
 iwl_trans_pcie_write_shr(trans, SHR_APMG_XTAL_CFG_REG,
     apmg_xtal_cfg_reg |
     SHR_APMG_XTAL_CFG_XTAL_ON_REQ);

 ret = iwl_trans_pcie_sw_reset(trans, true);
 if (ret)
  IWL_ERR(trans,
   "iwl_pcie_apm_lp_xtal_enable: failed to retake NIC ownership\n");

 /* Enable LP XTAL by indirect access through CSR */
 apmg_gp1_reg = iwl_trans_pcie_read_shr(trans, SHR_APMG_GP1_REG);
 iwl_trans_pcie_write_shr(trans, SHR_APMG_GP1_REG, apmg_gp1_reg |
     SHR_APMG_GP1_WF_XTAL_LP_EN |
     SHR_APMG_GP1_CHICKEN_BIT_SELECT);

 /* Clear delay line clock power up */
 dl_cfg_reg = iwl_trans_pcie_read_shr(trans, SHR_APMG_DL_CFG_REG);
 iwl_trans_pcie_write_shr(trans, SHR_APMG_DL_CFG_REG, dl_cfg_reg &
     ~SHR_APMG_DL_CFG_DL_CLOCK_POWER_UP);

 /*
 * Enable persistence mode to avoid LP XTAL resetting when
 * SHRD_HW_RST is applied in S3.
 */

 iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG,
      CSR_HW_IF_CONFIG_REG_PERSISTENCE);

 /*
 * Clear "initialization complete" bit to move adapter from
 * D0A* (powered-up Active) --> D0U* (Uninitialized) state.
 */

 iwl_clear_bit(trans, CSR_GP_CNTRL, CSR_GP_CNTRL_REG_FLAG_INIT_DONE);

 /* Activates XTAL resources monitor */
 iwl_trans_set_bit(trans, CSR_MONITOR_CFG_REG,
     CSR_MONITOR_XTAL_RESOURCES);

 /* Release XTAL ON request */
 iwl_trans_clear_bit(trans, CSR_GP_CNTRL,
       CSR_GP_CNTRL_REG_FLAG_XTAL_ON);
 udelay(10);

 /* Release APMG XTAL */
 iwl_trans_pcie_write_shr(trans, SHR_APMG_XTAL_CFG_REG,
     apmg_xtal_cfg_reg &
     ~SHR_APMG_XTAL_CFG_XTAL_ON_REQ);
}

void iwl_pcie_apm_stop_master(struct iwl_trans *trans)
{
 int ret;

 /* stop device's busmaster DMA activity */

 if (trans->mac_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) {
  iwl_set_bit(trans, CSR_GP_CNTRL,
       CSR_GP_CNTRL_REG_FLAG_BUS_MASTER_DISABLE_REQ);

  ret = iwl_poll_bits(trans, CSR_GP_CNTRL,
        CSR_GP_CNTRL_REG_FLAG_BUS_MASTER_DISABLE_STATUS,
        100);
  usleep_range(10000, 20000);
 } else {
  iwl_set_bit(trans, CSR_RESET, CSR_RESET_REG_FLAG_STOP_MASTER);

  ret = iwl_poll_bits(trans, CSR_RESET,
        CSR_RESET_REG_FLAG_MASTER_DISABLED, 100);
 }

 if (ret)
  IWL_WARN(trans, "Master Disable Timed Out, 100 usec\n");

 IWL_DEBUG_INFO(trans, "stop master\n");
}

static void iwl_pcie_apm_stop(struct iwl_trans *trans, bool op_mode_leave)
{
 IWL_DEBUG_INFO(trans, "Stop card, put in low power state\n");

 if (op_mode_leave) {
  if (!test_bit(STATUS_DEVICE_ENABLED, &trans->status))
   iwl_pcie_apm_init(trans);

  /* inform ME that we are leaving */
  if (trans->mac_cfg->device_family == IWL_DEVICE_FAMILY_7000)
   iwl_set_bits_prph(trans, APMG_PCIDEV_STT_REG,
       APMG_PCIDEV_STT_VAL_WAKE_ME);
  else if (trans->mac_cfg->device_family >=
    IWL_DEVICE_FAMILY_8000) {
   iwl_set_bit(trans, CSR_DBG_LINK_PWR_MGMT_REG,
        CSR_RESET_LINK_PWR_MGMT_DISABLED);
   iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG,
        CSR_HW_IF_CONFIG_REG_WAKE_ME |
        CSR_HW_IF_CONFIG_REG_WAKE_ME_PCIE_OWNER_EN);
   mdelay(1);
   iwl_clear_bit(trans, CSR_DBG_LINK_PWR_MGMT_REG,
          CSR_RESET_LINK_PWR_MGMT_DISABLED);
  }
  mdelay(5);
 }

 clear_bit(STATUS_DEVICE_ENABLED, &trans->status);

 /* Stop device's DMA activity */
 iwl_pcie_apm_stop_master(trans);

 if (trans->cfg->lp_xtal_workaround) {
  iwl_pcie_apm_lp_xtal_enable(trans);
  return;
 }

 iwl_trans_pcie_sw_reset(trans, false);

 /*
 * Clear "initialization complete" bit to move adapter from
 * D0A* (powered-up Active) --> D0U* (Uninitialized) state.
 */

 iwl_clear_bit(trans, CSR_GP_CNTRL, CSR_GP_CNTRL_REG_FLAG_INIT_DONE);
}

static int iwl_pcie_nic_init(struct iwl_trans *trans)
{
 struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
 int ret;

 /* nic_init */
 spin_lock_bh(&trans_pcie->irq_lock);
 ret = iwl_pcie_apm_init(trans);
 spin_unlock_bh(&trans_pcie->irq_lock);

 if (ret)
  return ret;

 iwl_pcie_set_pwr(trans, false);

 iwl_op_mode_nic_config(trans->op_mode);

 /* Allocate the RX queue, or reset if it is already allocated */
 ret = iwl_pcie_rx_init(trans);
 if (ret)
  return ret;

 /* Allocate or reset and init all Tx and Command queues */
 if (iwl_pcie_tx_init(trans)) {
  iwl_pcie_rx_free(trans);
  return -ENOMEM;
 }

 if (trans->mac_cfg->base->shadow_reg_enable) {
  /* enable shadow regs in HW */
  iwl_set_bit(trans, CSR_MAC_SHADOW_REG_CTRL, 0x800FFFFF);
  IWL_DEBUG_INFO(trans, "Enabling shadow registers in device\n");
 }

 return 0;
}

#define HW_READY_TIMEOUT (50)

/* Note: returns poll_bit return value, which is >= 0 if success */
static int iwl_pcie_set_hw_ready(struct iwl_trans *trans)
{
 int ret;

 iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG,
      CSR_HW_IF_CONFIG_REG_PCI_OWN_SET);

 /* See if we got it */
 ret = iwl_poll_bits(trans, CSR_HW_IF_CONFIG_REG,
       CSR_HW_IF_CONFIG_REG_PCI_OWN_SET,
       HW_READY_TIMEOUT);

 if (!ret)
  iwl_set_bit(trans, CSR_MBOX_SET_REG, CSR_MBOX_SET_REG_OS_ALIVE);

 IWL_DEBUG_INFO(trans, "hardware%s ready\n", ret ? " not" : "");
 return ret;
}

/* Note: returns standard 0/-ERROR code */
int iwl_pcie_prepare_card_hw(struct iwl_trans *trans)
{
 int ret;
 int iter;

 IWL_DEBUG_INFO(trans, "iwl_trans_prepare_card_hw enter\n");

 ret = iwl_pcie_set_hw_ready(trans);
 /* If the card is ready, exit 0 */
 if (!ret) {
  trans->csme_own = false;
  return 0;
 }

 iwl_set_bit(trans, CSR_DBG_LINK_PWR_MGMT_REG,
      CSR_RESET_LINK_PWR_MGMT_DISABLED);
 usleep_range(1000, 2000);

 for (iter = 0; iter < 10; iter++) {
  int t = 0;

  /* If HW is not ready, prepare the conditions to check again */
  iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG,
       CSR_HW_IF_CONFIG_REG_WAKE_ME);

  do {
   ret = iwl_pcie_set_hw_ready(trans);
   if (!ret) {
    trans->csme_own = false;
    return 0;
   }

   if (iwl_mei_is_connected()) {
    IWL_DEBUG_INFO(trans,
            "Couldn't prepare the card but SAP is connected\n");
    trans->csme_own = true;
    if (trans->mac_cfg->device_family !=
        IWL_DEVICE_FAMILY_9000)
     IWL_ERR(trans,
      "SAP not supported for this NIC family\n");

    return -EBUSY;
   }

   usleep_range(200, 1000);
   t += 200;
  } while (t < 150000);
  msleep(25);
 }

 IWL_ERR(trans, "Couldn't prepare the card\n");

 return ret;
}

/*
 * ucode
 */

static void iwl_pcie_load_firmware_chunk_fh(struct iwl_trans *trans,
         u32 dst_addr, dma_addr_t phy_addr,
         u32 byte_cnt)
{
 iwl_write32(trans, FH_TCSR_CHNL_TX_CONFIG_REG(FH_SRVC_CHNL),
      FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_PAUSE);

 iwl_write32(trans, FH_SRVC_CHNL_SRAM_ADDR_REG(FH_SRVC_CHNL),
      dst_addr);

 iwl_write32(trans, FH_TFDIB_CTRL0_REG(FH_SRVC_CHNL),
      phy_addr & FH_MEM_TFDIB_DRAM_ADDR_LSB_MSK);

 iwl_write32(trans, FH_TFDIB_CTRL1_REG(FH_SRVC_CHNL),
      (iwl_get_dma_hi_addr(phy_addr)
   << FH_MEM_TFDIB_REG1_ADDR_BITSHIFT) | byte_cnt);

 iwl_write32(trans, FH_TCSR_CHNL_TX_BUF_STS_REG(FH_SRVC_CHNL),
      BIT(FH_TCSR_CHNL_TX_BUF_STS_REG_POS_TB_NUM) |
      BIT(FH_TCSR_CHNL_TX_BUF_STS_REG_POS_TB_IDX) |
      FH_TCSR_CHNL_TX_BUF_STS_REG_VAL_TFDB_VALID);

 iwl_write32(trans, FH_TCSR_CHNL_TX_CONFIG_REG(FH_SRVC_CHNL),
      FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_ENABLE |
      FH_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_DISABLE |
      FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_HOST_ENDTFD);
}

static int iwl_pcie_load_firmware_chunk(struct iwl_trans *trans,
     u32 dst_addr, dma_addr_t phy_addr,
     u32 byte_cnt)
{
 struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
 int ret;

 trans_pcie->ucode_write_complete = false;

 if (!iwl_trans_grab_nic_access(trans))
  return -EIO;

 iwl_pcie_load_firmware_chunk_fh(trans, dst_addr, phy_addr,
     byte_cnt);
 iwl_trans_release_nic_access(trans);

 ret = wait_event_timeout(trans_pcie->ucode_write_waitq,
     trans_pcie->ucode_write_complete, 5 * HZ);
 if (!ret) {
  IWL_ERR(trans, "Failed to load firmware chunk!\n");
  iwl_trans_pcie_dump_regs(trans, trans_pcie->pci_dev);
  return -ETIMEDOUT;
 }

 return 0;
}

static int iwl_pcie_load_section(struct iwl_trans *trans, u8 section_num,
       const struct fw_desc *section)
{
 u8 *v_addr;
 dma_addr_t p_addr;
 u32 offset, chunk_sz = min_t(u32, FH_MEM_TB_MAX_LENGTH, section->len);
 int ret = 0;

 IWL_DEBUG_FW(trans, "[%d] uCode section being loaded...\n",
       section_num);

 v_addr = dma_alloc_coherent(trans->dev, chunk_sz, &p_addr,
        GFP_KERNEL | __GFP_NOWARN);
 if (!v_addr) {
  IWL_DEBUG_INFO(trans, "Falling back to small chunks of DMA\n");
  chunk_sz = PAGE_SIZE;
  v_addr = dma_alloc_coherent(trans->dev, chunk_sz,
         &p_addr, GFP_KERNEL);
  if (!v_addr)
   return -ENOMEM;
 }

 for (offset = 0; offset < section->len; offset += chunk_sz) {
  u32 copy_size, dst_addr;
  bool extended_addr = false;

  copy_size = min_t(u32, chunk_sz, section->len - offset);
  dst_addr = section->offset + offset;

  if (dst_addr >= IWL_FW_MEM_EXTENDED_START &&
      dst_addr <= IWL_FW_MEM_EXTENDED_END)
   extended_addr = true;

  if (extended_addr)
   iwl_set_bits_prph(trans, LMPM_CHICK,
       LMPM_CHICK_EXTENDED_ADDR_SPACE);

  memcpy(v_addr, (const u8 *)section->data + offset, copy_size);
  ret = iwl_pcie_load_firmware_chunk(trans, dst_addr, p_addr,
         copy_size);

  if (extended_addr)
   iwl_clear_bits_prph(trans, LMPM_CHICK,
         LMPM_CHICK_EXTENDED_ADDR_SPACE);

  if (ret) {
   IWL_ERR(trans,
    "Could not load the [%d] uCode section\n",
    section_num);
   break;
  }
 }

 dma_free_coherent(trans->dev, chunk_sz, v_addr, p_addr);
 return ret;
}

static int iwl_pcie_load_cpu_sections_8000(struct iwl_trans *trans,
        const struct fw_img *image,
        int cpu,
        int *first_ucode_section)
{
 int shift_param;
 int i, ret = 0, sec_num = 0x1;
 u32 val, last_read_idx = 0;

 if (cpu == 1) {
  shift_param = 0;
  *first_ucode_section = 0;
 } else {
  shift_param = 16;
  (*first_ucode_section)++;
 }

 for (i = *first_ucode_section; i < image->num_sec; i++) {
  last_read_idx = i;

  /*
 * CPU1_CPU2_SEPARATOR_SECTION delimiter - separate between
 * CPU1 to CPU2.
 * PAGING_SEPARATOR_SECTION delimiter - separate between
 * CPU2 non paged to CPU2 paging sec.
 */

  if (!image->sec[i].data ||
      image->sec[i].offset == CPU1_CPU2_SEPARATOR_SECTION ||
      image->sec[i].offset == PAGING_SEPARATOR_SECTION) {
   IWL_DEBUG_FW(trans,
         "Break since Data not valid or Empty section, sec = %d\n",
         i);
   break;
  }

  ret = iwl_pcie_load_section(trans, i, &image->sec[i]);
  if (ret)
   return ret;

  /* Notify ucode of loaded section number and status */
  val = iwl_read_direct32(trans, FH_UCODE_LOAD_STATUS);
  val = val | (sec_num << shift_param);
  iwl_write_direct32(trans, FH_UCODE_LOAD_STATUS, val);

  sec_num = (sec_num << 1) | 0x1;
 }

 *first_ucode_section = last_read_idx;

 iwl_enable_interrupts(trans);

 if (trans->mac_cfg->gen2) {
  if (cpu == 1)
   iwl_write_prph(trans, UREG_UCODE_LOAD_STATUS,
           0xFFFF);
  else
   iwl_write_prph(trans, UREG_UCODE_LOAD_STATUS,
           0xFFFFFFFF);
 } else {
  if (cpu == 1)
   iwl_write_direct32(trans, FH_UCODE_LOAD_STATUS,
        0xFFFF);
  else
   iwl_write_direct32(trans, FH_UCODE_LOAD_STATUS,
        0xFFFFFFFF);
 }

 return 0;
}

static int iwl_pcie_load_cpu_sections(struct iwl_trans *trans,
          const struct fw_img *image,
          int cpu,
          int *first_ucode_section)
{
 int i, ret = 0;
 u32 last_read_idx = 0;

 if (cpu == 1)
  *first_ucode_section = 0;
 else
  (*first_ucode_section)++;

 for (i = *first_ucode_section; i < image->num_sec; i++) {
  last_read_idx = i;

  /*
 * CPU1_CPU2_SEPARATOR_SECTION delimiter - separate between
 * CPU1 to CPU2.
 * PAGING_SEPARATOR_SECTION delimiter - separate between
 * CPU2 non paged to CPU2 paging sec.
 */

  if (!image->sec[i].data ||
      image->sec[i].offset == CPU1_CPU2_SEPARATOR_SECTION ||
      image->sec[i].offset == PAGING_SEPARATOR_SECTION) {
   IWL_DEBUG_FW(trans,
         "Break since Data not valid or Empty section, sec = %d\n",
         i);
   break;
  }

  ret = iwl_pcie_load_section(trans, i, &image->sec[i]);
  if (ret)
   return ret;
 }

 *first_ucode_section = last_read_idx;

 return 0;
}

static void iwl_pcie_apply_destination_ini(struct iwl_trans *trans)
{
 enum iwl_fw_ini_allocation_id alloc_id = IWL_FW_INI_ALLOCATION_ID_DBGC1;
 struct iwl_fw_ini_allocation_tlv *fw_mon_cfg =
  &trans->dbg.fw_mon_cfg[alloc_id];
 struct iwl_dram_data *frag;

 if (!iwl_trans_dbg_ini_valid(trans))
  return;

 if (le32_to_cpu(fw_mon_cfg->buf_location) ==
     IWL_FW_INI_LOCATION_SRAM_PATH) {
  IWL_DEBUG_FW(trans, "WRT: Applying SMEM buffer destination\n");
  /* set sram monitor by enabling bit 7 */
  iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG,
       CSR_HW_IF_CONFIG_REG_BIT_MONITOR_SRAM);

  return;
 }

 if (le32_to_cpu(fw_mon_cfg->buf_location) !=
     IWL_FW_INI_LOCATION_DRAM_PATH ||
     !trans->dbg.fw_mon_ini[alloc_id].num_frags)
  return;

 frag = &trans->dbg.fw_mon_ini[alloc_id].frags[0];

 IWL_DEBUG_FW(trans, "WRT: Applying DRAM destination (alloc_id=%u)\n",
       alloc_id);

 iwl_write_umac_prph(trans, MON_BUFF_BASE_ADDR_VER2,
       frag->physical >> MON_BUFF_SHIFT_VER2);
 iwl_write_umac_prph(trans, MON_BUFF_END_ADDR_VER2,
       (frag->physical + frag->size - 256) >>
       MON_BUFF_SHIFT_VER2);
}

void iwl_pcie_apply_destination(struct iwl_trans *trans)
{
 const struct iwl_fw_dbg_dest_tlv_v1 *dest = trans->dbg.dest_tlv;
 const struct iwl_dram_data *fw_mon = &trans->dbg.fw_mon;
 int i;

 if (iwl_trans_dbg_ini_valid(trans)) {
  iwl_pcie_apply_destination_ini(trans);
  return;
 }

 IWL_INFO(trans, "Applying debug destination %s\n",
   get_fw_dbg_mode_string(dest->monitor_mode));

 if (dest->monitor_mode == EXTERNAL_MODE)
  iwl_pcie_alloc_fw_monitor(trans, dest->size_power);
 else
  IWL_WARN(trans, "PCI should have external buffer debug\n");

 for (i = 0; i < trans->dbg.n_dest_reg; i++) {
  u32 addr = le32_to_cpu(dest->reg_ops[i].addr);
  u32 val = le32_to_cpu(dest->reg_ops[i].val);

  switch (dest->reg_ops[i].op) {
  case CSR_ASSIGN:
   iwl_write32(trans, addr, val);
   break;
  case CSR_SETBIT:
   iwl_set_bit(trans, addr, BIT(val));
   break;
  case CSR_CLEARBIT:
   iwl_clear_bit(trans, addr, BIT(val));
   break;
  case PRPH_ASSIGN:
   iwl_write_prph(trans, addr, val);
   break;
  case PRPH_SETBIT:
   iwl_set_bits_prph(trans, addr, BIT(val));
   break;
  case PRPH_CLEARBIT:
   iwl_clear_bits_prph(trans, addr, BIT(val));
   break;
  case PRPH_BLOCKBIT:
   if (iwl_read_prph(trans, addr) & BIT(val)) {
    IWL_ERR(trans,
     "BIT(%u) in address 0x%x is 1, stopping FW configuration\n",
     val, addr);
    goto monitor;
   }
   break;
  default:
   IWL_ERR(trans, "FW debug - unknown OP %d\n",
    dest->reg_ops[i].op);
   break;
  }
 }

monitor:
 if (dest->monitor_mode == EXTERNAL_MODE && fw_mon->size) {
  iwl_write_prph(trans, le32_to_cpu(dest->base_reg),
          fw_mon->physical >> dest->base_shift);
  if (trans->mac_cfg->device_family >= IWL_DEVICE_FAMILY_8000)
   iwl_write_prph(trans, le32_to_cpu(dest->end_reg),
           (fw_mon->physical + fw_mon->size -
     256) >> dest->end_shift);
  else
   iwl_write_prph(trans, le32_to_cpu(dest->end_reg),
           (fw_mon->physical + fw_mon->size) >>
           dest->end_shift);
 }
}

static int iwl_pcie_load_given_ucode(struct iwl_trans *trans,
    const struct fw_img *image)
{
 int ret = 0;
 int first_ucode_section;

 IWL_DEBUG_FW(trans, "working with %s CPU\n",
       image->is_dual_cpus ? "Dual" : "Single");

 /* load to FW the binary non secured sections of CPU1 */
 ret = iwl_pcie_load_cpu_sections(trans, image, 1, &first_ucode_section);
 if (ret)
  return ret;

 if (image->is_dual_cpus) {
  /* set CPU2 header address */
  iwl_write_prph(trans,
          LMPM_SECURE_UCODE_LOAD_CPU2_HDR_ADDR,
          LMPM_SECURE_CPU2_HDR_MEM_SPACE);

  /* load to FW the binary sections of CPU2 */
  ret = iwl_pcie_load_cpu_sections(trans, image, 2,
       &first_ucode_section);
  if (ret)
   return ret;
 }

 if (iwl_pcie_dbg_on(trans))
  iwl_pcie_apply_destination(trans);

 iwl_enable_interrupts(trans);

 /* release CPU reset */
 iwl_write32(trans, CSR_RESET, 0);

 return 0;
}

static int iwl_pcie_load_given_ucode_8000(struct iwl_trans *trans,
       const struct fw_img *image)
{
 int ret = 0;
 int first_ucode_section;

 IWL_DEBUG_FW(trans, "working with %s CPU\n",
       image->is_dual_cpus ? "Dual" : "Single");

 if (iwl_pcie_dbg_on(trans))
  iwl_pcie_apply_destination(trans);

 IWL_DEBUG_POWER(trans, "Original WFPM value = 0x%08X\n",
   iwl_read_prph(trans, WFPM_GP2));

 /*
 * Set default value. On resume reading the values that were
 * zeored can provide debug data on the resume flow.
 * This is for debugging only and has no functional impact.
 */

 iwl_write_prph(trans, WFPM_GP2, 0x01010101);

 /* configure the ucode to be ready to get the secured image */
 /* release CPU reset */
 iwl_write_prph(trans, RELEASE_CPU_RESET, RELEASE_CPU_RESET_BIT);

 /* load to FW the binary Secured sections of CPU1 */
 ret = iwl_pcie_load_cpu_sections_8000(trans, image, 1,
           &first_ucode_section);
 if (ret)
  return ret;

 /* load to FW the binary sections of CPU2 */
 return iwl_pcie_load_cpu_sections_8000(trans, image, 2,
            &first_ucode_section);
}

bool iwl_pcie_check_hw_rf_kill(struct iwl_trans *trans)
{
 struct iwl_trans_pcie *trans_pcie =  IWL_TRANS_GET_PCIE_TRANS(trans);
 bool hw_rfkill = iwl_is_rfkill_set(trans);
 bool prev = test_bit(STATUS_RFKILL_OPMODE, &trans->status);
 bool report;

 if (hw_rfkill) {
  set_bit(STATUS_RFKILL_HW, &trans->status);
  set_bit(STATUS_RFKILL_OPMODE, &trans->status);
 } else {
  clear_bit(STATUS_RFKILL_HW, &trans->status);
  if (trans_pcie->opmode_down)
   clear_bit(STATUS_RFKILL_OPMODE, &trans->status);
 }

 report = test_bit(STATUS_RFKILL_OPMODE, &trans->status);

 if (prev != report)
  iwl_trans_pcie_rf_kill(trans, report, false);

 return hw_rfkill;
}

struct iwl_causes_list {
 u16 mask_reg;
 u8 bit;
 u8 addr;
};

#define IWL_CAUSE(reg, mask)      \
 {        \
  .mask_reg = reg,     \
  .bit = ilog2(mask),     \
  .addr = ilog2(mask) +     \
   ((reg) == CSR_MSIX_FH_INT_MASK_AD ? -16 : \
    (reg) == CSR_MSIX_HW_INT_MASK_AD ? 16 : \
    0xffff), /* causes overflow warning */ \
 }

static const struct iwl_causes_list causes_list_common[] = {
 IWL_CAUSE(CSR_MSIX_FH_INT_MASK_AD, MSIX_FH_INT_CAUSES_D2S_CH0_NUM),
 IWL_CAUSE(CSR_MSIX_FH_INT_MASK_AD, MSIX_FH_INT_CAUSES_D2S_CH1_NUM),
 IWL_CAUSE(CSR_MSIX_FH_INT_MASK_AD, MSIX_FH_INT_CAUSES_S2D),
 IWL_CAUSE(CSR_MSIX_FH_INT_MASK_AD, MSIX_FH_INT_CAUSES_FH_ERR),
 IWL_CAUSE(CSR_MSIX_HW_INT_MASK_AD, MSIX_HW_INT_CAUSES_REG_ALIVE),
 IWL_CAUSE(CSR_MSIX_HW_INT_MASK_AD, MSIX_HW_INT_CAUSES_REG_WAKEUP),
 IWL_CAUSE(CSR_MSIX_HW_INT_MASK_AD, MSIX_HW_INT_CAUSES_REG_RESET_DONE),
 IWL_CAUSE(CSR_MSIX_HW_INT_MASK_AD, MSIX_HW_INT_CAUSES_REG_TOP_FATAL_ERR),
 IWL_CAUSE(CSR_MSIX_HW_INT_MASK_AD, MSIX_HW_INT_CAUSES_REG_CT_KILL),
 IWL_CAUSE(CSR_MSIX_HW_INT_MASK_AD, MSIX_HW_INT_CAUSES_REG_RF_KILL),
 IWL_CAUSE(CSR_MSIX_HW_INT_MASK_AD, MSIX_HW_INT_CAUSES_REG_PERIODIC),
 IWL_CAUSE(CSR_MSIX_HW_INT_MASK_AD, MSIX_HW_INT_CAUSES_REG_SCD),
 IWL_CAUSE(CSR_MSIX_HW_INT_MASK_AD, MSIX_HW_INT_CAUSES_REG_FH_TX),
 IWL_CAUSE(CSR_MSIX_HW_INT_MASK_AD, MSIX_HW_INT_CAUSES_REG_HW_ERR),
 IWL_CAUSE(CSR_MSIX_HW_INT_MASK_AD, MSIX_HW_INT_CAUSES_REG_HAP),
};

static const struct iwl_causes_list causes_list_pre_bz[] = {
 IWL_CAUSE(CSR_MSIX_HW_INT_MASK_AD, MSIX_HW_INT_CAUSES_REG_SW_ERR),
};

static const struct iwl_causes_list causes_list_bz[] = {
 IWL_CAUSE(CSR_MSIX_HW_INT_MASK_AD, MSIX_HW_INT_CAUSES_REG_SW_ERR_BZ),
};

static void iwl_pcie_map_list(struct iwl_trans *trans,
         const struct iwl_causes_list *causes,
         int arr_size, int val)
{
 int i;

 for (i = 0; i < arr_size; i++) {
  iwl_write8(trans, CSR_MSIX_IVAR(causes[i].addr), val);
  iwl_clear_bit(trans, causes[i].mask_reg,
         BIT(causes[i].bit));
 }
}

static void iwl_pcie_map_non_rx_causes(struct iwl_trans *trans)
{
 struct iwl_trans_pcie *trans_pcie =  IWL_TRANS_GET_PCIE_TRANS(trans);
 int val = trans_pcie->def_irq | MSIX_NON_AUTO_CLEAR_CAUSE;
 /*
 * Access all non RX causes and map them to the default irq.
 * In case we are missing at least one interrupt vector,
 * the first interrupt vector will serve non-RX and FBQ causes.
 */

 iwl_pcie_map_list(trans, causes_list_common,
     ARRAY_SIZE(causes_list_common), val);
 if (trans->mac_cfg->device_family >= IWL_DEVICE_FAMILY_BZ)
  iwl_pcie_map_list(trans, causes_list_bz,
      ARRAY_SIZE(causes_list_bz), val);
 else
  iwl_pcie_map_list(trans, causes_list_pre_bz,
      ARRAY_SIZE(causes_list_pre_bz), val);
}

static void iwl_pcie_map_rx_causes(struct iwl_trans *trans)
{
 struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
 u32 offset =
  trans_pcie->shared_vec_mask & IWL_SHARED_IRQ_FIRST_RSS ? 1 : 0;
 u32 val, idx;

 /*
 * The first RX queue - fallback queue, which is designated for
 * management frame, command responses etc, is always mapped to the
 * first interrupt vector. The other RX queues are mapped to
 * the other (N - 2) interrupt vectors.
 */

 val = BIT(MSIX_FH_INT_CAUSES_Q(0));
 for (idx = 1; idx < trans->info.num_rxqs; idx++) {
  iwl_write8(trans, CSR_MSIX_RX_IVAR(idx),
      MSIX_FH_INT_CAUSES_Q(idx - offset));
  val |= BIT(MSIX_FH_INT_CAUSES_Q(idx));
 }
 iwl_write32(trans, CSR_MSIX_FH_INT_MASK_AD, ~val);

 val = MSIX_FH_INT_CAUSES_Q(0);
 if (trans_pcie->shared_vec_mask & IWL_SHARED_IRQ_NON_RX)
  val |= MSIX_NON_AUTO_CLEAR_CAUSE;
 iwl_write8(trans, CSR_MSIX_RX_IVAR(0), val);

 if (trans_pcie->shared_vec_mask & IWL_SHARED_IRQ_FIRST_RSS)
  iwl_write8(trans, CSR_MSIX_RX_IVAR(1), val);
}

void iwl_pcie_conf_msix_hw(struct iwl_trans_pcie *trans_pcie)
{
 struct iwl_trans *trans = trans_pcie->trans;

 if (!trans_pcie->msix_enabled) {
  if (trans->mac_cfg->mq_rx_supported &&
      test_bit(STATUS_DEVICE_ENABLED, &trans->status))
   iwl_write_umac_prph(trans, UREG_CHICK,
         UREG_CHICK_MSI_ENABLE);
  return;
 }
 /*
 * The IVAR table needs to be configured again after reset,
 * but if the device is disabled, we can't write to
 * prph.
 */

 if (test_bit(STATUS_DEVICE_ENABLED, &trans->status))
  iwl_write_umac_prph(trans, UREG_CHICK, UREG_CHICK_MSIX_ENABLE);

 /*
 * Each cause from the causes list above and the RX causes is
 * represented as a byte in the IVAR table. The first nibble
 * represents the bound interrupt vector of the cause, the second
 * represents no auto clear for this cause. This will be set if its
 * interrupt vector is bound to serve other causes.
 */

 iwl_pcie_map_rx_causes(trans);

 iwl_pcie_map_non_rx_causes(trans);
}

static void iwl_pcie_init_msix(struct iwl_trans_pcie *trans_pcie)
{
 struct iwl_trans *trans = trans_pcie->trans;

 iwl_pcie_conf_msix_hw(trans_pcie);

 if (!trans_pcie->msix_enabled)
  return;

 trans_pcie->fh_init_mask = ~iwl_read32(trans, CSR_MSIX_FH_INT_MASK_AD);
 trans_pcie->fh_mask = trans_pcie->fh_init_mask;
 trans_pcie->hw_init_mask = ~iwl_read32(trans, CSR_MSIX_HW_INT_MASK_AD);
 trans_pcie->hw_mask = trans_pcie->hw_init_mask;
}

static void _iwl_trans_pcie_stop_device(struct iwl_trans *trans, bool from_irq)
{
 struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);

 lockdep_assert_held(&trans_pcie->mutex);

 if (trans_pcie->is_down)
  return;

 trans_pcie->is_down = true;

 /* tell the device to stop sending interrupts */
 iwl_disable_interrupts(trans);

 /* device going down, Stop using ICT table */
 iwl_pcie_disable_ict(trans);

 /*
 * If a HW restart happens during firmware loading,
 * then the firmware loading might call this function
 * and later it might be called again due to the
 * restart. So don't process again if the device is
 * already dead.
 */

 if (test_and_clear_bit(STATUS_DEVICE_ENABLED, &trans->status)) {
  IWL_DEBUG_INFO(trans,
          "DEVICE_ENABLED bit was set and is now cleared\n");
  if (!from_irq)
   iwl_pcie_synchronize_irqs(trans);
  iwl_pcie_rx_napi_sync(trans);
  iwl_pcie_tx_stop(trans);
  iwl_pcie_rx_stop(trans);

  /* Power-down device's busmaster DMA clocks */
  if (!trans->mac_cfg->base->apmg_not_supported) {
   iwl_write_prph(trans, APMG_CLK_DIS_REG,
           APMG_CLK_VAL_DMA_CLK_RQT);
   udelay(5);
  }
 }

 /* Make sure (redundant) we've released our request to stay awake */
 if (trans->mac_cfg->device_family >= IWL_DEVICE_FAMILY_BZ)
  iwl_clear_bit(trans, CSR_GP_CNTRL,
         CSR_GP_CNTRL_REG_FLAG_BZ_MAC_ACCESS_REQ);
 else
  iwl_clear_bit(trans, CSR_GP_CNTRL,
         CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ);

 /* Stop the device, and put it in low power state */
 iwl_pcie_apm_stop(trans, false);

 /* re-take ownership to prevent other users from stealing the device */
 iwl_trans_pcie_sw_reset(trans, true);

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

 iwl_pcie_conf_msix_hw(trans_pcie);

 /*
 * Upon stop, the APM issues an interrupt if HW RF kill is set.
 * This is a bug in certain verions of the hardware.
 * Certain devices also keep sending HW RF kill interrupt all
 * the time, unless the interrupt is ACKed even if the interrupt
 * should be masked. Re-ACK all the interrupts here.
 */

 iwl_disable_interrupts(trans);

 /* clear all status bits */
 clear_bit(STATUS_SYNC_HCMD_ACTIVE, &trans->status);
 clear_bit(STATUS_INT_ENABLED, &trans->status);
 clear_bit(STATUS_TPOWER_PMI, &trans->status);

 /*
 * Even if we stop the HW, we still want the RF kill
 * interrupt
 */

 iwl_enable_rfkill_int(trans);
}

void iwl_pcie_synchronize_irqs(struct iwl_trans *trans)
{
 struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);

 if (trans_pcie->msix_enabled) {
  int i;

  for (i = 0; i < trans_pcie->alloc_vecs; i++)
   synchronize_irq(trans_pcie->msix_entries[i].vector);
 } else {
  synchronize_irq(trans_pcie->pci_dev->irq);
 }
}

int iwl_trans_pcie_start_fw(struct iwl_trans *trans,
       const struct iwl_fw *fw,
       const struct fw_img *img,
       bool run_in_rfkill)
{
 struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
 bool hw_rfkill;
 int ret;

 /* This may fail if AMT took ownership of the device */
 if (iwl_pcie_prepare_card_hw(trans)) {
  IWL_WARN(trans, "Exit HW not ready\n");
  return -EIO;
 }

 iwl_enable_rfkill_int(trans);

 iwl_write32(trans, CSR_INT, 0xFFFFFFFF);

 /*
 * We enabled the RF-Kill interrupt and the handler may very
 * well be running. Disable the interrupts to make sure no other
 * interrupt can be fired.
 */

 iwl_disable_interrupts(trans);

 /* Make sure it finished running */
 iwl_pcie_synchronize_irqs(trans);

 mutex_lock(&trans_pcie->mutex);

 /* If platform's RF_KILL switch is NOT set to KILL */
 hw_rfkill = iwl_pcie_check_hw_rf_kill(trans);
 if (hw_rfkill && !run_in_rfkill) {
  ret = -ERFKILL;
  goto out;
 }

 /* Someone called stop_device, don't try to start_fw */
 if (trans_pcie->is_down) {
  IWL_WARN(trans,
    "Can't start_fw since the HW hasn't been started\n");
  ret = -EIO;
  goto out;
 }

 /* make sure rfkill handshake bits are cleared */
 iwl_write32(trans, CSR_UCODE_DRV_GP1_CLR, CSR_UCODE_SW_BIT_RFKILL);
 iwl_write32(trans, CSR_UCODE_DRV_GP1_CLR,
      CSR_UCODE_DRV_GP1_BIT_CMD_BLOCKED);

 /* clear (again), then enable host interrupts */
 iwl_write32(trans, CSR_INT, 0xFFFFFFFF);

 ret = iwl_pcie_nic_init(trans);
 if (ret) {
  IWL_ERR(trans, "Unable to init nic\n");
  goto out;
 }

 /*
 * Now, we load the firmware and don't want to be interrupted, even
 * by the RF-Kill interrupt (hence mask all the interrupt besides the
 * FH_TX interrupt which is needed to load the firmware). If the
 * RF-Kill switch is toggled, we will find out after having loaded
 * the firmware and return the proper value to the caller.
 */

 iwl_enable_fw_load_int(trans);

 /* really make sure rfkill handshake bits are cleared */
 iwl_write32(trans, CSR_UCODE_DRV_GP1_CLR, CSR_UCODE_SW_BIT_RFKILL);
 iwl_write32(trans, CSR_UCODE_DRV_GP1_CLR, CSR_UCODE_SW_BIT_RFKILL);

 /* Load the given image to the HW */
 if (trans->mac_cfg->device_family >= IWL_DEVICE_FAMILY_8000)
  ret = iwl_pcie_load_given_ucode_8000(trans, img);
 else
  ret = iwl_pcie_load_given_ucode(trans, img);

 /* re-check RF-Kill state since we may have missed the interrupt */
 hw_rfkill = iwl_pcie_check_hw_rf_kill(trans);
 if (hw_rfkill && !run_in_rfkill)
  ret = -ERFKILL;

out:
 mutex_unlock(&trans_pcie->mutex);
 return ret;
}

void iwl_trans_pcie_fw_alive(struct iwl_trans *trans)
{
 iwl_pcie_reset_ict(trans);
 iwl_pcie_tx_start(trans);
}

void iwl_trans_pcie_handle_stop_rfkill(struct iwl_trans *trans,
           bool was_in_rfkill)
{
 bool hw_rfkill;

 /*
 * Check again since the RF kill state may have changed while
 * all the interrupts were disabled, in this case we couldn't
 * receive the RF kill interrupt and update the state in the
 * op_mode.
 * Don't call the op_mode if the rkfill state hasn't changed.
 * This allows the op_mode to call stop_device from the rfkill
 * notification without endless recursion. Under very rare
 * circumstances, we might have a small recursion if the rfkill
 * state changed exactly now while we were called from stop_device.
 * This is very unlikely but can happen and is supported.
 */

 hw_rfkill = iwl_is_rfkill_set(trans);
 if (hw_rfkill) {
  set_bit(STATUS_RFKILL_HW, &trans->status);
  set_bit(STATUS_RFKILL_OPMODE, &trans->status);
 } else {
  clear_bit(STATUS_RFKILL_HW, &trans->status);
  clear_bit(STATUS_RFKILL_OPMODE, &trans->status);
 }
 if (hw_rfkill != was_in_rfkill)
  iwl_trans_pcie_rf_kill(trans, hw_rfkill, false);
}

void iwl_trans_pcie_stop_device(struct iwl_trans *trans)
{
 struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
 bool was_in_rfkill;

 iwl_op_mode_time_point(trans->op_mode,
          IWL_FW_INI_TIME_POINT_HOST_DEVICE_DISABLE,
          NULL);

 mutex_lock(&trans_pcie->mutex);
 trans_pcie->opmode_down = true;
 was_in_rfkill = test_bit(STATUS_RFKILL_OPMODE, &trans->status);
 _iwl_trans_pcie_stop_device(trans, false);
 iwl_trans_pcie_handle_stop_rfkill(trans, was_in_rfkill);
 mutex_unlock(&trans_pcie->mutex);
}

void iwl_trans_pcie_rf_kill(struct iwl_trans *trans, bool state, bool from_irq)
{
 struct iwl_trans_pcie __maybe_unused *trans_pcie =
  IWL_TRANS_GET_PCIE_TRANS(trans);

 lockdep_assert_held(&trans_pcie->mutex);

 IWL_WARN(trans, "reporting RF_KILL (radio %s)\n",
   state ? "disabled" : "enabled");
 if (iwl_op_mode_hw_rf_kill(trans->op_mode, state) &&
     !WARN_ON(trans->mac_cfg->gen2))
  _iwl_trans_pcie_stop_device(trans, from_irq);
}

static void iwl_pcie_d3_complete_suspend(struct iwl_trans *trans,
      bool test, bool reset)
{
 iwl_disable_interrupts(trans);

 /*
 * in testing mode, the host stays awake and the
 * hardware won't be reset (not even partially)
 */

 if (test)
  return;

 iwl_pcie_disable_ict(trans);

 iwl_pcie_synchronize_irqs(trans);

 if (trans->mac_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) {
  iwl_clear_bit(trans, CSR_GP_CNTRL,
         CSR_GP_CNTRL_REG_FLAG_BZ_MAC_ACCESS_REQ);
  iwl_clear_bit(trans, CSR_GP_CNTRL,
         CSR_GP_CNTRL_REG_FLAG_MAC_INIT);
 } else {
  iwl_clear_bit(trans, CSR_GP_CNTRL,
         CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ);
  iwl_clear_bit(trans, CSR_GP_CNTRL,
         CSR_GP_CNTRL_REG_FLAG_INIT_DONE);
 }

 if (reset) {
  /*
 * reset TX queues -- some of their registers reset during S3
 * so if we don't reset everything here the D3 image would try
 * to execute some invalid memory upon resume
 */

  iwl_trans_pcie_tx_reset(trans);
 }

 iwl_pcie_set_pwr(trans, true);
}

static int iwl_pcie_d3_handshake(struct iwl_trans *trans, bool suspend)
{
 struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
 int ret;

 if (trans->mac_cfg->device_family < IWL_DEVICE_FAMILY_AX210)
  return 0;

 trans_pcie->sx_state = IWL_SX_WAITING;

 if (trans->mac_cfg->device_family == IWL_DEVICE_FAMILY_AX210)
  iwl_write_umac_prph(trans, UREG_DOORBELL_TO_ISR6,
        suspend ? UREG_DOORBELL_TO_ISR6_SUSPEND :
           UREG_DOORBELL_TO_ISR6_RESUME);
 else
  iwl_write32(trans, CSR_IPC_SLEEP_CONTROL,
       suspend ? CSR_IPC_SLEEP_CONTROL_SUSPEND :
          CSR_IPC_SLEEP_CONTROL_RESUME);

 ret = wait_event_timeout(trans_pcie->sx_waitq,
     trans_pcie->sx_state != IWL_SX_WAITING,
     2 * HZ);
 if (!ret) {
  IWL_ERR(trans, "Timeout %s D3\n",
   suspend ? "entering" : "exiting");
  ret = -ETIMEDOUT;
 } else {
  ret = 0;
 }

 if (trans_pcie->sx_state == IWL_SX_ERROR) {
  IWL_ERR(trans, "FW error while %s D3\n",
   suspend ? "entering" : "exiting");
  ret = -EIO;
 }

 /* Invalidate it toward next suspend or resume */
 trans_pcie->sx_state = IWL_SX_INVALID;

 return ret;
}

int iwl_trans_pcie_d3_suspend(struct iwl_trans *trans, bool test, bool reset)
{
 int ret;

 if (!reset)
  /* Enable persistence mode to avoid reset */
  iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG,
       CSR_HW_IF_CONFIG_REG_PERSISTENCE);

 ret = iwl_pcie_d3_handshake(trans, true);
 if (ret)
  return ret;

 iwl_pcie_d3_complete_suspend(trans, test, reset);

 return 0;
}

int iwl_trans_pcie_d3_resume(struct iwl_trans *trans,
        enum iwl_d3_status *status,
        bool test,  bool reset)
{
 struct iwl_trans_pcie *trans_pcie =  IWL_TRANS_GET_PCIE_TRANS(trans);
 u32 val;
 int ret;

 if (test) {
  iwl_enable_interrupts(trans);
  *status = IWL_D3_STATUS_ALIVE;
  ret = 0;
  goto out;
 }

 if (trans->mac_cfg->device_family >= IWL_DEVICE_FAMILY_BZ)
  iwl_set_bit(trans, CSR_GP_CNTRL,
       CSR_GP_CNTRL_REG_FLAG_BZ_MAC_ACCESS_REQ);
 else
  iwl_set_bit(trans, CSR_GP_CNTRL,
       CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ);

 ret = iwl_finish_nic_init(trans);
 if (ret)
  return ret;

 /*
 * Reconfigure IVAR table in case of MSIX or reset ict table in
 * MSI mode since HW reset erased it.
 * Also enables interrupts - none will happen as
 * the device doesn't know we're waking it up, only when
 * the opmode actually tells it after this call.
 */

 iwl_pcie_conf_msix_hw(trans_pcie);
 if (!trans_pcie->msix_enabled)
  iwl_pcie_reset_ict(trans);
 iwl_enable_interrupts(trans);

 iwl_pcie_set_pwr(trans, false);

 if (!reset) {
  iwl_clear_bit(trans, CSR_GP_CNTRL,
         CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ);
 } else {
  iwl_trans_pcie_tx_reset(trans);

  ret = iwl_pcie_rx_init(trans);
  if (ret) {
   IWL_ERR(trans,
    "Failed to resume the device (RX reset)\n");
   return ret;
  }
 }

 IWL_DEBUG_POWER(trans, "WFPM value upon resume = 0x%08X\n",
   iwl_read_umac_prph(trans, WFPM_GP2));

 val = iwl_read32(trans, CSR_RESET);
 if (val & CSR_RESET_REG_FLAG_NEVO_RESET)
  *status = IWL_D3_STATUS_RESET;
 else
  *status = IWL_D3_STATUS_ALIVE;

out:
 if (*status == IWL_D3_STATUS_ALIVE)
  ret = iwl_pcie_d3_handshake(trans, false);
 else
  trans->state = IWL_TRANS_NO_FW;

 return ret;
}

static void
iwl_pcie_set_interrupt_capa(struct pci_dev *pdev,
       struct iwl_trans *trans,
       const struct iwl_mac_cfg *mac_cfg,
       struct iwl_trans_info *info)
{
 struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
 int max_irqs, num_irqs, i, ret;
 u16 pci_cmd;
 u32 max_rx_queues = IWL_MAX_RX_HW_QUEUES;

 if (!mac_cfg->mq_rx_supported)
  goto enable_msi;

 if (mac_cfg->device_family <= IWL_DEVICE_FAMILY_9000)
  max_rx_queues = IWL_9000_MAX_RX_HW_QUEUES;

 max_irqs = min_t(u32, num_online_cpus() + 2, max_rx_queues);
 for (i = 0; i < max_irqs; i++)
  trans_pcie->msix_entries[i].entry = i;

 num_irqs = pci_enable_msix_range(pdev, trans_pcie->msix_entries,
      MSIX_MIN_INTERRUPT_VECTORS,
      max_irqs);
 if (num_irqs < 0) {
  IWL_DEBUG_INFO(trans,
          "Failed to enable msi-x mode (ret %d). Moving to msi mode.\n",
          num_irqs);
  goto enable_msi;
 }
 trans_pcie->def_irq = (num_irqs == max_irqs) ? num_irqs - 1 : 0;

 IWL_DEBUG_INFO(trans,
         "MSI-X enabled. %d interrupt vectors were allocated\n",
         num_irqs);

 /*
 * In case the OS provides fewer interrupts than requested, different
 * causes will share the same interrupt vector as follows:
 * One interrupt less: non rx causes shared with FBQ.
 * Two interrupts less: non rx causes shared with FBQ and RSS.
 * More than two interrupts: we will use fewer RSS queues.
 */

 if (num_irqs <= max_irqs - 2) {
  info->num_rxqs = num_irqs + 1;
  trans_pcie->shared_vec_mask = IWL_SHARED_IRQ_NON_RX |
   IWL_SHARED_IRQ_FIRST_RSS;
 } else if (num_irqs == max_irqs - 1) {
  info->num_rxqs = num_irqs;
  trans_pcie->shared_vec_mask = IWL_SHARED_IRQ_NON_RX;
 } else {
  info->num_rxqs = num_irqs - 1;
 }

 IWL_DEBUG_INFO(trans,
         "MSI-X enabled with rx queues %d, vec mask 0x%x\n",
         info->num_rxqs, trans_pcie->shared_vec_mask);

 WARN_ON(info->num_rxqs > IWL_MAX_RX_HW_QUEUES);

 trans_pcie->alloc_vecs = num_irqs;
 trans_pcie->msix_enabled = true;
 return;

enable_msi:
 info->num_rxqs = 1;
 ret = pci_enable_msi(pdev);
 if (ret) {
  dev_err(&pdev->dev, "pci_enable_msi failed - %d\n", ret);
  /* enable rfkill interrupt: hw bug w/a */
  pci_read_config_word(pdev, PCI_COMMAND, &pci_cmd);
  if (pci_cmd & PCI_COMMAND_INTX_DISABLE) {
   pci_cmd &= ~PCI_COMMAND_INTX_DISABLE;
   pci_write_config_word(pdev, PCI_COMMAND, pci_cmd);
  }
 }
}

static void iwl_pcie_irq_set_affinity(struct iwl_trans *trans,
          struct iwl_trans_info *info)
{
#if defined(CONFIG_SMP)
 int iter_rx_q, i, ret, cpu, offset;
 struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);

 i = trans_pcie->shared_vec_mask & IWL_SHARED_IRQ_FIRST_RSS ? 0 : 1;
 iter_rx_q = info->num_rxqs - 1 + i;
 offset = 1 + i;
 for (; i < iter_rx_q ; i++) {
  /*
 * Get the cpu prior to the place to search
 * (i.e. return will be > i - 1).
 */

  cpu = cpumask_next(i - offset, cpu_online_mask);
  cpumask_set_cpu(cpu, &trans_pcie->affinity_mask[i]);
  ret = irq_set_affinity_hint(trans_pcie->msix_entries[i].vector,
         &trans_pcie->affinity_mask[i]);
  if (ret)
   IWL_ERR(trans_pcie->trans,
    "Failed to set affinity mask for IRQ %d\n",
    trans_pcie->msix_entries[i].vector);
 }
#endif
}

static int iwl_pcie_init_msix_handler(struct pci_dev *pdev,
          struct iwl_trans_pcie *trans_pcie,
          struct iwl_trans_info *info)
{
 int i;

 for (i = 0; i < trans_pcie->alloc_vecs; i++) {
  int ret;
  struct msix_entry *msix_entry;
  const char *qname = queue_name(&pdev->dev, trans_pcie, i);

  if (!qname)
   return -ENOMEM;

  msix_entry = &trans_pcie->msix_entries[i];
  ret = devm_request_threaded_irq(&pdev->dev,
      msix_entry->vector,
      iwl_pcie_msix_isr,
      (i == trans_pcie->def_irq) ?
      iwl_pcie_irq_msix_handler :
      iwl_pcie_irq_rx_msix_handler,
      IRQF_SHARED,
      qname,
      msix_entry);
  if (ret) {
   IWL_ERR(trans_pcie->trans,
    "Error allocating IRQ %d\n", i);

   return ret;
  }
 }
 iwl_pcie_irq_set_affinity(trans_pcie->trans, info);

 return 0;
}

static int iwl_trans_pcie_clear_persistence_bit(struct iwl_trans *trans)
{
 u32 hpm, wprot;

 switch (trans->mac_cfg->device_family) {
 case IWL_DEVICE_FAMILY_9000:
  wprot = PREG_PRPH_WPROT_9000;
  break;
 case IWL_DEVICE_FAMILY_22000:
  wprot = PREG_PRPH_WPROT_22000;
  break;
 default:
  return 0;
 }

 hpm = iwl_read_umac_prph_no_grab(trans, HPM_DEBUG);
 if (!iwl_trans_is_hw_error_value(hpm) && (hpm & PERSISTENCE_BIT)) {
  u32 wprot_val = iwl_read_umac_prph_no_grab(trans, wprot);

  if (wprot_val & PREG_WFPM_ACCESS) {
   IWL_ERR(trans,
    "Error, can not clear persistence bit\n");
   return -EPERM;
  }
  iwl_write_umac_prph_no_grab(trans, HPM_DEBUG,
         hpm & ~PERSISTENCE_BIT);
 }

 return 0;
}

static int iwl_pcie_gen2_force_power_gating(struct iwl_trans *trans)
{
 int ret;

 ret = iwl_finish_nic_init(trans);
 if (ret < 0)
  return ret;

 iwl_set_bits_prph(trans, HPM_HIPM_GEN_CFG,
     HPM_HIPM_GEN_CFG_CR_FORCE_ACTIVE);
 udelay(20);
 iwl_set_bits_prph(trans, HPM_HIPM_GEN_CFG,
     HPM_HIPM_GEN_CFG_CR_PG_EN |
     HPM_HIPM_GEN_CFG_CR_SLP_EN);
 udelay(20);
 iwl_clear_bits_prph(trans, HPM_HIPM_GEN_CFG,
       HPM_HIPM_GEN_CFG_CR_FORCE_ACTIVE);

 return iwl_trans_pcie_sw_reset(trans, true);
}

int _iwl_trans_pcie_start_hw(struct iwl_trans *trans)
{
 struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
 int err;

 lockdep_assert_held(&trans_pcie->mutex);

 err = iwl_pcie_prepare_card_hw(trans);
 if (err) {
  IWL_ERR(trans, "Error while preparing HW: %d\n", err);
  return err;
 }

 err = iwl_trans_pcie_clear_persistence_bit(trans);
 if (err)
  return err;

 err = iwl_trans_pcie_sw_reset(trans, true);
 if (err)
  return err;

 if (trans->mac_cfg->device_family == IWL_DEVICE_FAMILY_22000 &&
     trans->mac_cfg->integrated) {
  err = iwl_pcie_gen2_force_power_gating(trans);
  if (err)
   return err;
 }

 err = iwl_pcie_apm_init(trans);
 if (err)
  return err;

 iwl_pcie_init_msix(trans_pcie);

 /* From now on, the op_mode will be kept updated about RF kill state */
 iwl_enable_rfkill_int(trans);

 trans_pcie->opmode_down = false;

 /* Set is_down to false here so that...*/
 trans_pcie->is_down = false;

 /* ...rfkill can call stop_device and set it false if needed */
 iwl_pcie_check_hw_rf_kill(trans);

 return 0;
}

int iwl_trans_pcie_start_hw(struct iwl_trans *trans)
{
 struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
 int ret;

 mutex_lock(&trans_pcie->mutex);
 ret = _iwl_trans_pcie_start_hw(trans);
 mutex_unlock(&trans_pcie->mutex);

 return ret;
}

void iwl_trans_pcie_op_mode_leave(struct iwl_trans *trans)
{
 struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);

 mutex_lock(&trans_pcie->mutex);

 /* disable interrupts - don't enable HW RF kill interrupt */
 iwl_disable_interrupts(trans);

 iwl_pcie_apm_stop(trans, true);

 iwl_disable_interrupts(trans);

 iwl_pcie_disable_ict(trans);

 mutex_unlock(&trans_pcie->mutex);

 iwl_pcie_synchronize_irqs(trans);
}

void iwl_trans_pcie_write8(struct iwl_trans *trans, u32 ofs, u8 val)
{
 writeb(val, IWL_TRANS_GET_PCIE_TRANS(trans)->hw_base + ofs);
}

void iwl_trans_pcie_write32(struct iwl_trans *trans, u32 ofs, u32 val)
{
 writel(val, IWL_TRANS_GET_PCIE_TRANS(trans)->hw_base + ofs);
}

u32 iwl_trans_pcie_read32(struct iwl_trans *trans, u32 ofs)
{
 return readl(IWL_TRANS_GET_PCIE_TRANS(trans)->hw_base + ofs);
}

static u32 iwl_trans_pcie_prph_msk(struct iwl_trans *trans)
{
 if (trans->mac_cfg->device_family >= IWL_DEVICE_FAMILY_AX210)
  return 0x00FFFFFF;
 else
  return 0x000FFFFF;
}

u32 iwl_trans_pcie_read_prph(struct iwl_trans *trans, u32 reg)
{
 u32 mask = iwl_trans_pcie_prph_msk(trans);

 iwl_trans_pcie_write32(trans, HBUS_TARG_PRPH_RADDR,
          ((reg & mask) | (3 << 24)));
 return iwl_trans_pcie_read32(trans, HBUS_TARG_PRPH_RDAT);
}

void iwl_trans_pcie_write_prph(struct iwl_trans *trans, u32 addr, u32 val)
{
 u32 mask = iwl_trans_pcie_prph_msk(trans);

 iwl_trans_pcie_write32(trans, HBUS_TARG_PRPH_WADDR,
          ((addr & mask) | (3 << 24)));
 iwl_trans_pcie_write32(trans, HBUS_TARG_PRPH_WDAT, val);
}

void iwl_trans_pcie_op_mode_enter(struct iwl_trans *trans)
{
 struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);

 /* free all first - we might be reconfigured for a different size */
 iwl_pcie_free_rbs_pool(trans);

 trans_pcie->rx_page_order =
  iwl_trans_get_rb_size_order(trans->conf.rx_buf_size);
 trans_pcie->rx_buf_bytes =
  iwl_trans_get_rb_size(trans->conf.rx_buf_size);
}

void iwl_trans_pcie_free_pnvm_dram_regions(struct iwl_dram_regions *dram_regions,
        struct device *dev)
{
 u8 i;
 struct iwl_dram_data *desc_dram = &dram_regions->prph_scratch_mem_desc;

 /* free DRAM payloads */
 for (i = 0; i < dram_regions->n_regions; i++) {
  dma_free_coherent(dev, dram_regions->drams[i].size,
      dram_regions->drams[i].block,
      dram_regions->drams[i].physical);
 }
 dram_regions->n_regions = 0;

 /* free DRAM addresses array */
 if (desc_dram->block) {
  dma_free_coherent(dev, desc_dram->size,
      desc_dram->block,
      desc_dram->physical);
 }
 memset(desc_dram, 0, sizeof(*desc_dram));
}

static void iwl_pcie_free_invalid_tx_cmd(struct iwl_trans *trans)
{
 struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);

 iwl_pcie_free_dma_ptr(trans, &trans_pcie->invalid_tx_cmd);
}

static int iwl_pcie_alloc_invalid_tx_cmd(struct iwl_trans *trans)
{
 struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
 struct iwl_cmd_header_wide bad_cmd = {
  .cmd = INVALID_WR_PTR_CMD,
  .group_id = DEBUG_GROUP,
  .sequence = cpu_to_le16(0xffff),
  .length = cpu_to_le16(0),
  .version = 0,
 };
 int ret;

 ret = iwl_pcie_alloc_dma_ptr(trans, &trans_pcie->invalid_tx_cmd,
         sizeof(bad_cmd));
 if (ret)
  return ret;
 memcpy(trans_pcie->invalid_tx_cmd.addr, &bad_cmd, sizeof(bad_cmd));
 return 0;
}

void iwl_trans_pcie_free(struct iwl_trans *trans)
{
 struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
 int i;

 iwl_pcie_synchronize_irqs(trans);

 if (trans->mac_cfg->gen2)
  iwl_txq_gen2_tx_free(trans);
 else
  iwl_pcie_tx_free(trans);
 iwl_pcie_rx_free(trans);

 if (trans_pcie->rba.alloc_wq) {
  destroy_workqueue(trans_pcie->rba.alloc_wq);
  trans_pcie->rba.alloc_wq = NULL;
 }

 if (trans_pcie->msix_enabled) {
  for (i = 0; i < trans_pcie->alloc_vecs; i++) {
   irq_set_affinity_hint(
    trans_pcie->msix_entries[i].vector,
    NULL);
  }

  trans_pcie->msix_enabled = false;
 } else {
  iwl_pcie_free_ict(trans);
 }

 free_netdev(trans_pcie->napi_dev);

 iwl_pcie_free_invalid_tx_cmd(trans);

 iwl_pcie_free_fw_monitor(trans);

 iwl_trans_pcie_free_pnvm_dram_regions(&trans_pcie->pnvm_data,
           trans->dev);
 iwl_trans_pcie_free_pnvm_dram_regions(&trans_pcie->reduced_tables_data,
           trans->dev);

 mutex_destroy(&trans_pcie->mutex);

 if (trans_pcie->txqs.tso_hdr_page) {
  for_each_possible_cpu(i) {
   struct iwl_tso_hdr_page *p =
    per_cpu_ptr(trans_pcie->txqs.tso_hdr_page, i);

   if (p && p->page)
    __free_page(p->page);
  }

  free_percpu(trans_pcie->txqs.tso_hdr_page);
 }

 iwl_trans_free(trans);
}

static union acpi_object *
iwl_trans_pcie_call_prod_reset_dsm(struct pci_dev *pdev, u16 cmd, u16 value)
{
#ifdef CONFIG_ACPI
 struct iwl_dsm_internal_product_reset_cmd pldr_arg = {
  .cmd = cmd,
  .value = value,
 };
 union acpi_object arg = {
  .buffer.type = ACPI_TYPE_BUFFER,
  .buffer.length = sizeof(pldr_arg),
  .buffer.pointer = (void *)&pldr_arg,
 };
 static const guid_t dsm_guid = GUID_INIT(0x7266172C, 0x220B, 0x4B29,
       0x81, 0x4F, 0x75, 0xE4,
       0xDD, 0x26, 0xB5, 0xFD);

 if (!acpi_check_dsm(ACPI_HANDLE(&pdev->dev), &dsm_guid, ACPI_DSM_REV,
       DSM_INTERNAL_FUNC_PRODUCT_RESET))
  return ERR_PTR(-ENODEV);

 return iwl_acpi_get_dsm_object(&pdev->dev, ACPI_DSM_REV,
           DSM_INTERNAL_FUNC_PRODUCT_RESET,
           &arg, &dsm_guid);
#else
 return ERR_PTR(-EOPNOTSUPP);
#endif
}

void iwl_trans_pcie_check_product_reset_mode(struct pci_dev *pdev)
{
 union acpi_object *res;

 res = iwl_trans_pcie_call_prod_reset_dsm(pdev,
       DSM_INTERNAL_PLDR_CMD_GET_MODE,
       0);
 if (IS_ERR(res))
  return;

 if (res->type != ACPI_TYPE_INTEGER)
  IWL_ERR_DEV(&pdev->dev,
       "unexpected return type from product reset DSM\n");
 else
  IWL_DEBUG_DEV_POWER(&pdev->dev,
        "product reset mode is 0x%llx\n",
        res->integer.value);

 ACPI_FREE(res);
}

static void iwl_trans_pcie_set_product_reset(struct pci_dev *pdev, bool enable,
          bool integrated)
{
 union acpi_object *res;
 u16 mode = enable ? DSM_INTERNAL_PLDR_MODE_EN_PROD_RESET : 0;

 if (!integrated)
  mode |= DSM_INTERNAL_PLDR_MODE_EN_WIFI_FLR |
   DSM_INTERNAL_PLDR_MODE_EN_BT_OFF_ON;

 res = iwl_trans_pcie_call_prod_reset_dsm(pdev,
       DSM_INTERNAL_PLDR_CMD_SET_MODE,
       mode);
 if (IS_ERR(res)) {
  if (enable)
   IWL_ERR_DEV(&pdev->dev,
        "ACPI _DSM not available (%d), cannot do product reset\n",
        (int)PTR_ERR(res));
  return;
 }

 ACPI_FREE(res);
 IWL_DEBUG_DEV_POWER(&pdev->dev, "%sabled product reset via DSM\n",
       enable ? "En" : "Dis");
 iwl_trans_pcie_check_product_reset_mode(pdev);
}

void iwl_trans_pcie_check_product_reset_status(struct pci_dev *pdev)
{
 union acpi_object *res;

 res = iwl_trans_pcie_call_prod_reset_dsm(pdev,
       DSM_INTERNAL_PLDR_CMD_GET_STATUS,
       0);
 if (IS_ERR(res))
  return;

 if (res->type != ACPI_TYPE_INTEGER)
  IWL_ERR_DEV(&pdev->dev,
       "unexpected return type from product reset DSM\n");
 else
  IWL_DEBUG_DEV_POWER(&pdev->dev,
        "product reset status is 0x%llx\n",
        res->integer.value);

 ACPI_FREE(res);
}

static void iwl_trans_pcie_call_reset(struct pci_dev *pdev)
{
#ifdef CONFIG_ACPI
 struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
 union acpi_object *p, *ref;
 acpi_status status;
 int ret = -EINVAL;

 status = acpi_evaluate_object(ACPI_HANDLE(&pdev->dev),
          "_PRR", NULL, &buffer);
 if (ACPI_FAILURE(status)) {
  IWL_DEBUG_DEV_POWER(&pdev->dev, "No _PRR method found\n");
  goto out;
 }
 p = buffer.pointer;

 if (p->type != ACPI_TYPE_PACKAGE || p->package.count != 1) {
  pci_err(pdev, "Bad _PRR return type\n");
  goto out;
 }

 ref = &p->package.elements[0];
 if (ref->type != ACPI_TYPE_LOCAL_REFERENCE) {
  pci_err(pdev, "_PRR wasn't a reference\n");
  goto out;
 }

 status = acpi_evaluate_object(ref->reference.handle,
          "_RST", NULL, NULL);
 if (ACPI_FAILURE(status)) {
  pci_err(pdev,
   "Failed to call _RST on object returned by _PRR (%d)\n",
   status);
  goto out;
 }
 ret = 0;
out:
 kfree(buffer.pointer);
 if (!ret) {
  IWL_DEBUG_DEV_POWER(&pdev->dev, "called _RST on _PRR object\n");
  return;
 }
 IWL_DEBUG_DEV_POWER(&pdev->dev,
       "No BIOS support, using pci_reset_function()\n");
#endif
 pci_reset_function(pdev);
}

struct iwl_trans_pcie_removal {
 struct pci_dev *pdev;
 struct work_struct work;
 enum iwl_reset_mode mode;
 bool integrated;
};

static void iwl_trans_pcie_removal_wk(struct work_struct *wk)
{
 struct iwl_trans_pcie_removal *removal =
  container_of(wk, struct iwl_trans_pcie_removal, work);
 struct pci_dev *pdev = removal->pdev;
 static char *prop[] = {"EVENT=INACCESSIBLE", NULL};
 struct pci_bus *bus;

 pci_lock_rescan_remove();

 bus = pdev->bus;
 /* in this case, something else already removed the device */
 if (!bus)
  goto out;

 kobject_uevent_env(&pdev->dev.kobj, KOBJ_CHANGE, prop);

 if (removal->mode == IWL_RESET_MODE_PROD_RESET) {
  struct pci_dev *bt = NULL;

  if (!removal->integrated) {
   /* discrete devices have WiFi/BT at function 0/1 */
   int slot = PCI_SLOT(pdev->devfn);
   int func = PCI_FUNC(pdev->devfn);

   if (func == 0)
    bt = pci_get_slot(bus, PCI_DEVFN(slot, 1));
   else
    pci_info(pdev, "Unexpected function %d\n",
      func);
  } else {
   /* on integrated we have to look up by ID (same bus) */
   static const struct pci_device_id bt_device_ids[] = {
#define BT_DEV(_id) { PCI_DEVICE(PCI_VENDOR_ID_INTEL, _id) }
    BT_DEV(0xA876), /* LNL */
    BT_DEV(0xE476), /* PTL-P */
    BT_DEV(0xE376), /* PTL-H */
    BT_DEV(0xD346), /* NVL-H */
    BT_DEV(0x6E74), /* NVL-S */
    BT_DEV(0x4D76), /* WCL */
    BT_DEV(0xD246), /* RZL-H */
    BT_DEV(0x6C46), /* RZL-M */
    {}
   };
   struct pci_dev *tmp = NULL;

   for_each_pci_dev(tmp) {
    if (tmp->bus != bus)
     continue;

    if (pci_match_id(bt_device_ids, tmp)) {
     bt = tmp;
     break;
    }
   }
  }

  if (bt) {
   pci_info(bt, "Removal by WiFi due to product reset\n");
   pci_stop_and_remove_bus_device(bt);
   pci_dev_put(bt);
  }
 }

 iwl_trans_pcie_set_product_reset(pdev,
      removal->mode ==
      IWL_RESET_MODE_PROD_RESET,
      removal->integrated);
 if (removal->mode >= IWL_RESET_MODE_FUNC_RESET)
  iwl_trans_pcie_call_reset(pdev);

 pci_stop_and_remove_bus_device(pdev);
 pci_dev_put(pdev);

 if (removal->mode >= IWL_RESET_MODE_RESCAN) {
  if (bus->parent)
   bus = bus->parent;
  pci_rescan_bus(bus);
 }

out:
 pci_unlock_rescan_remove();

 kfree(removal);
 module_put(THIS_MODULE);
}

void iwl_trans_pcie_reset(struct iwl_trans *trans, enum iwl_reset_mode mode)
{
 struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
 struct iwl_trans_pcie_removal *removal;
 char _msg = 0, *msg = &_msg;

 if (WARN_ON(mode < IWL_RESET_MODE_REMOVE_ONLY ||
      mode == IWL_RESET_MODE_BACKOFF))
  return;

 if (test_bit(STATUS_TRANS_DEAD, &trans->status))
  return;

 if (trans_pcie->me_present && mode == IWL_RESET_MODE_PROD_RESET) {
  mode = IWL_RESET_MODE_FUNC_RESET;
  if (trans_pcie->me_present < 0)
   msg = " instead of product reset as ME may be present";
  else
   msg = " instead of product reset as ME is present";
 }

 IWL_INFO(trans, "scheduling reset (mode=%d%s)\n", mode, msg);

 iwl_pcie_dump_csr(trans);

 /*
 * get a module reference to avoid doing this
 * while unloading anyway and to avoid
 * scheduling a work with code that's being
 * removed.
 */

 if (!try_module_get(THIS_MODULE)) {
  IWL_ERR(trans,
   "Module is being unloaded - abort\n");
  return;
 }

 removal = kzalloc(sizeof(*removal), GFP_ATOMIC);
 if (!removal) {
  module_put(THIS_MODULE);
  return;
 }
 /*
 * we don't need to clear this flag, because
 * the trans will be freed and reallocated.
 */

 set_bit(STATUS_TRANS_DEAD, &trans->status);

 removal->pdev = to_pci_dev(trans->dev);
 removal->mode = mode;
 removal->integrated = trans->mac_cfg->integrated;
 INIT_WORK(&removal->work, iwl_trans_pcie_removal_wk);
 pci_dev_get(removal->pdev);
 schedule_work(&removal->work);
}
EXPORT_SYMBOL(iwl_trans_pcie_reset);

/*
 * This version doesn't disable BHs but rather assumes they're
 * already disabled.
 */

bool __iwl_trans_pcie_grab_nic_access(struct iwl_trans *trans, bool silent)
{
 int ret;
 struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
 u32 write = CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ;
 u32 mask = CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY |
     CSR_GP_CNTRL_REG_FLAG_GOING_TO_SLEEP;
 u32 poll = CSR_GP_CNTRL_REG_VAL_MAC_ACCESS_EN;

 if (test_bit(STATUS_TRANS_DEAD, &trans->status))
  return false;

 spin_lock(&trans_pcie->reg_lock);

 if (trans_pcie->cmd_hold_nic_awake)
  goto out;

 if (trans->mac_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) {
  write = CSR_GP_CNTRL_REG_FLAG_BZ_MAC_ACCESS_REQ;
  mask = CSR_GP_CNTRL_REG_FLAG_MAC_STATUS;
  poll = CSR_GP_CNTRL_REG_FLAG_MAC_STATUS;
 }

 /* this bit wakes up the NIC */
 iwl_trans_set_bit(trans, CSR_GP_CNTRL, write);
 if (trans->mac_cfg->device_family >= IWL_DEVICE_FAMILY_8000)
  udelay(2);

 /*
 * These bits say the device is running, and should keep running for
 * at least a short while (at least as long as MAC_ACCESS_REQ stays 1),
 * but they do not indicate that embedded SRAM is restored yet;
 * HW with volatile SRAM must save/restore contents to/from
 * host DRAM when sleeping/waking for power-saving.
 * Each direction takes approximately 1/4 millisecond; with this
 * overhead, it's a good idea to grab and hold MAC_ACCESS_REQUEST if a
 * series of register accesses are expected (e.g. reading Event Log),
 * to keep device from sleeping.
 *
 * CSR_UCODE_DRV_GP1 register bit MAC_SLEEP == 0 indicates that
 * SRAM is okay/restored.  We don't check that here because this call
 * is just for hardware register access; but GP1 MAC_SLEEP
 * check is a good idea before accessing the SRAM of HW with
 * volatile SRAM (e.g. reading Event Log).
 *
 * 5000 series and later (including 1000 series) have non-volatile SRAM,
 * and do not save/restore SRAM when power cycling.
 */

 ret = iwl_poll_bits_mask(trans, CSR_GP_CNTRL, poll, mask, 15000);
 if (unlikely(ret)) {
  u32 cntrl = iwl_read32(trans, CSR_GP_CNTRL);

  if (silent) {
   spin_unlock(&trans_pcie->reg_lock);
   return false;
  }

  WARN_ONCE(1,
     "Timeout waiting for hardware access (CSR_GP_CNTRL 0x%08x)\n",
     cntrl);

  iwl_trans_pcie_dump_regs(trans, trans_pcie->pci_dev);

  if (iwlwifi_mod_params.remove_when_gone && cntrl == ~0U)
   iwl_trans_pcie_reset(trans,
          IWL_RESET_MODE_REMOVE_ONLY);
  else
   iwl_write32(trans, CSR_RESET,
        CSR_RESET_REG_FLAG_FORCE_NMI);

  spin_unlock(&trans_pcie->reg_lock);
  return false;
 }

out:
 /*
 * Fool sparse by faking we release the lock - sparse will
 * track nic_access anyway.
 */

 __release(&trans_pcie->reg_lock);
 return true;
}

bool iwl_trans_pcie_grab_nic_access(struct iwl_trans *trans)
{
 bool ret;

 local_bh_disable();
 ret = __iwl_trans_pcie_grab_nic_access(trans, false);
 if (ret) {
  /* keep BHs disabled until iwl_trans_pcie_release_nic_access */
  return ret;
 }
 local_bh_enable();
 return false;
}

void __releases(nic_access_nobh)
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=95 H=93 G=93

¤ Dauer der Verarbeitung: 0.23 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.