]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/commitdiff
iwlwifi: pcie: don't report RF-kill enabled while shutting down
authorJohannes Berg <johannes.berg@intel.com>
Tue, 25 Apr 2017 11:41:20 +0000 (13:41 +0200)
committerLuca Coelho <luciano.coelho@intel.com>
Thu, 22 Jun 2017 21:13:01 +0000 (00:13 +0300)
When toggling the RF-kill pin quickly in succession, the driver can
get rather confused because it might be in the process of shutting
down, expecting all commands to go through quickly due to rfkill,
but the transport already thinks the device is accessible again,
even though it previously shut it down. This leads to bugs, and I
even observed a kernel panic.

Avoid this by making the PCIe code only report that the radio is
enabled again after the higher layers actually decided to shut it
off.

This also pulls out this common RF-kill checking code into a common
function called by both transport generations and also moves it to
the direct method - in the internal helper we don't really care
about the RF-kill status anymore since we won't report it up until
the stop anyway.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Luca Coelho <luciano.coelho@intel.com>
drivers/net/wireless/intel/iwlwifi/iwl-trans.c
drivers/net/wireless/intel/iwlwifi/iwl-trans.h
drivers/net/wireless/intel/iwlwifi/pcie/drv.c
drivers/net/wireless/intel/iwlwifi/pcie/internal.h
drivers/net/wireless/intel/iwlwifi/pcie/rx.c
drivers/net/wireless/intel/iwlwifi/pcie/trans-gen2.c
drivers/net/wireless/intel/iwlwifi/pcie/trans.c
drivers/net/wireless/intel/iwlwifi/pcie/tx-gen2.c
drivers/net/wireless/intel/iwlwifi/pcie/tx.c

index dcf596217d9ea53abef831e02741256b3694e7f2..784bdd0ed233e3186c207b85df2594e282a1be4f 100644 (file)
@@ -117,7 +117,7 @@ int iwl_trans_send_cmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd)
        int ret;
 
        if (unlikely(!(cmd->flags & CMD_SEND_IN_RFKILL) &&
-                    test_bit(STATUS_RFKILL, &trans->status)))
+                    test_bit(STATUS_RFKILL_OPMODE, &trans->status)))
                return -ERFKILL;
 
        if (unlikely(test_bit(STATUS_FW_ERROR, &trans->status)))
index a150da3c824b8370e0b29a844eed08fc20129e67..2e975c0be045f5975ece373b59829b2fd1c17611 100644 (file)
@@ -322,7 +322,8 @@ enum iwl_d3_status {
  * @STATUS_DEVICE_ENABLED: APM is enabled
  * @STATUS_TPOWER_PMI: the device might be asleep (need to wake it up)
  * @STATUS_INT_ENABLED: interrupts are enabled
- * @STATUS_RFKILL: the HW RFkill switch is in KILL position
+ * @STATUS_RFKILL_HW: the actual HW state of the RF-kill switch
+ * @STATUS_RFKILL_OPMODE: RF-kill state reported to opmode
  * @STATUS_FW_ERROR: the fw is in error state
  * @STATUS_TRANS_GOING_IDLE: shutting down the trans, only special commands
  *     are sent
@@ -334,7 +335,8 @@ enum iwl_trans_status {
        STATUS_DEVICE_ENABLED,
        STATUS_TPOWER_PMI,
        STATUS_INT_ENABLED,
-       STATUS_RFKILL,
+       STATUS_RFKILL_HW,
+       STATUS_RFKILL_OPMODE,
        STATUS_FW_ERROR,
        STATUS_TRANS_GOING_IDLE,
        STATUS_TRANS_IDLE,
index 2d92d3708619ad078fc415b58f6617ec259fc11e..a8fb77483313e45695884c563011ebf873da07da 100644 (file)
@@ -766,7 +766,6 @@ static int iwl_pci_resume(struct device *device)
        struct pci_dev *pdev = to_pci_dev(device);
        struct iwl_trans *trans = pci_get_drvdata(pdev);
        struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
-       bool hw_rfkill;
 
        /* Before you put code here, think about WoWLAN. You cannot check here
         * whether WoWLAN is enabled or not, and your code will run even if
@@ -783,16 +782,13 @@ static int iwl_pci_resume(struct device *device)
                return 0;
 
        /*
-        * Enable rfkill interrupt (in order to keep track of
-        * the rfkill status). Must be locked to avoid processing
-        * a possible rfkill interrupt between reading the state
-        * and calling iwl_trans_pcie_rf_kill() with it.
+        * Enable rfkill interrupt (in order to keep track of the rfkill
+        * status). Must be locked to avoid processing a possible rfkill
+        * interrupt while in iwl_trans_check_hw_rf_kill().
         */
        mutex_lock(&trans_pcie->mutex);
        iwl_enable_rfkill_int(trans);
-
-       hw_rfkill = iwl_is_rfkill_set(trans);
-       iwl_trans_pcie_rf_kill(trans, hw_rfkill);
+       iwl_trans_check_hw_rf_kill(trans);
        mutex_unlock(&trans_pcie->mutex);
 
        return 0;
index 17bd9561448397cabf1472652cc9b95e65628913..644c0e583f32f28a5cac91163ee2e149e12bcd02 100644 (file)
@@ -403,7 +403,7 @@ struct iwl_trans_pcie {
        dma_addr_t ict_tbl_dma;
        int ict_index;
        bool use_ict;
-       bool is_down;
+       bool is_down, opmode_down;
        bool debug_rfkill;
        struct isr_statistics isr_stats;
 
@@ -775,6 +775,8 @@ void iwl_pcie_apm_config(struct iwl_trans *trans);
 int iwl_pcie_prepare_card_hw(struct iwl_trans *trans);
 void iwl_pcie_synchronize_irqs(struct iwl_trans *trans);
 bool iwl_trans_check_hw_rf_kill(struct iwl_trans *trans);
+void iwl_trans_pcie_handle_stop_rfkill(struct iwl_trans *trans,
+                                      bool was_in_rfkill);
 void iwl_pcie_txq_free_tfd(struct iwl_trans *trans, struct iwl_txq *txq);
 int iwl_queue_space(const struct iwl_txq *q);
 int iwl_pcie_apm_stop_master(struct iwl_trans *trans);
index b6b04e72521e429d68e1b4ccba6af0f781625980..6eef2e789426b56e9f27316cc55afd7c363a47e3 100644 (file)
@@ -1513,19 +1513,27 @@ void iwl_pcie_handle_rfkill_irq(struct iwl_trans *trans)
 {
        struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
        struct isr_statistics *isr_stats = &trans_pcie->isr_stats;
-       bool hw_rfkill;
+       bool hw_rfkill, prev, report;
 
        mutex_lock(&trans_pcie->mutex);
+       prev = test_bit(STATUS_RFKILL_OPMODE, &trans->status);
        hw_rfkill = iwl_is_rfkill_set(trans);
-       if (hw_rfkill)
-               set_bit(STATUS_RFKILL, &trans->status);
+       if (hw_rfkill) {
+               set_bit(STATUS_RFKILL_OPMODE, &trans->status);
+               set_bit(STATUS_RFKILL_HW, &trans->status);
+       }
+       if (trans_pcie->opmode_down)
+               report = hw_rfkill;
+       else
+               report = test_bit(STATUS_RFKILL_OPMODE, &trans->status);
 
        IWL_WARN(trans, "RF_KILL bit toggled to %s.\n",
                 hw_rfkill ? "disable radio" : "enable radio");
 
        isr_stats->rfkill++;
 
-       iwl_trans_pcie_rf_kill(trans, hw_rfkill);
+       if (prev != report)
+               iwl_trans_pcie_rf_kill(trans, report);
        mutex_unlock(&trans_pcie->mutex);
 
        if (hw_rfkill) {
@@ -1535,7 +1543,9 @@ void iwl_pcie_handle_rfkill_irq(struct iwl_trans *trans)
                                          "Rfkill while SYNC HCMD in flight\n");
                wake_up(&trans_pcie->wait_command_queue);
        } else {
-               clear_bit(STATUS_RFKILL, &trans->status);
+               clear_bit(STATUS_RFKILL_HW, &trans->status);
+               if (trans_pcie->opmode_down)
+                       clear_bit(STATUS_RFKILL_OPMODE, &trans->status);
        }
 }
 
index ac60a282d6debc1eb2b891ef0fb2b20bf1370df0..e84c5ff389a841ddba7dcb8c3e5ce711ea9a6105 100644 (file)
@@ -150,7 +150,6 @@ static void iwl_pcie_gen2_apm_stop(struct iwl_trans *trans, bool op_mode_leave)
 void _iwl_trans_pcie_gen2_stop_device(struct iwl_trans *trans, bool low_power)
 {
        struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
-       bool hw_rfkill, was_hw_rfkill;
 
        lockdep_assert_held(&trans_pcie->mutex);
 
@@ -159,8 +158,6 @@ void _iwl_trans_pcie_gen2_stop_device(struct iwl_trans *trans, bool low_power)
 
        trans_pcie->is_down = true;
 
-       was_hw_rfkill = iwl_is_rfkill_set(trans);
-
        /* tell the device to stop sending interrupts */
        iwl_disable_interrupts(trans);
 
@@ -217,7 +214,6 @@ void _iwl_trans_pcie_gen2_stop_device(struct iwl_trans *trans, bool low_power)
        clear_bit(STATUS_SYNC_HCMD_ACTIVE, &trans->status);
        clear_bit(STATUS_INT_ENABLED, &trans->status);
        clear_bit(STATUS_TPOWER_PMI, &trans->status);
-       clear_bit(STATUS_RFKILL, &trans->status);
 
        /*
         * Even if we stop the HW, we still want the RF kill
@@ -225,26 +221,6 @@ void _iwl_trans_pcie_gen2_stop_device(struct iwl_trans *trans, bool low_power)
         */
        iwl_enable_rfkill_int(trans);
 
-       /*
-        * 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, &trans->status);
-       else
-               clear_bit(STATUS_RFKILL, &trans->status);
-       if (hw_rfkill != was_hw_rfkill)
-               iwl_trans_pcie_rf_kill(trans, hw_rfkill);
-
        /* re-take ownership to prevent other users from stealing the device */
        iwl_pcie_prepare_card_hw(trans);
 }
@@ -252,9 +228,13 @@ void _iwl_trans_pcie_gen2_stop_device(struct iwl_trans *trans, bool low_power)
 void iwl_trans_pcie_gen2_stop_device(struct iwl_trans *trans, bool low_power)
 {
        struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
+       bool was_in_rfkill;
 
        mutex_lock(&trans_pcie->mutex);
+       trans_pcie->opmode_down = true;
+       was_in_rfkill = test_bit(STATUS_RFKILL_OPMODE, &trans->status);
        _iwl_trans_pcie_gen2_stop_device(trans, low_power);
+       iwl_trans_pcie_handle_stop_rfkill(trans, was_in_rfkill);
        mutex_unlock(&trans_pcie->mutex);
 }
 
index 6c4f8e377fb905036f0cd539b673d79373c2cf04..959a3d5ece67a89bb5334469e259238f89389309 100644 (file)
@@ -996,14 +996,24 @@ static int iwl_pcie_load_given_ucode_8000(struct iwl_trans *trans,
 
 bool iwl_trans_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, &trans->status);
-       else
-               clear_bit(STATUS_RFKILL, &trans->status);
+       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);
 
-       iwl_trans_pcie_rf_kill(trans, hw_rfkill);
+       if (prev != report)
+               iwl_trans_pcie_rf_kill(trans, report);
 
        return hw_rfkill;
 }
@@ -1128,7 +1138,6 @@ static void iwl_pcie_init_msix(struct iwl_trans_pcie *trans_pcie)
 static void _iwl_trans_pcie_stop_device(struct iwl_trans *trans, bool low_power)
 {
        struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
-       bool hw_rfkill, was_hw_rfkill;
 
        lockdep_assert_held(&trans_pcie->mutex);
 
@@ -1137,8 +1146,6 @@ static void _iwl_trans_pcie_stop_device(struct iwl_trans *trans, bool low_power)
 
        trans_pcie->is_down = true;
 
-       was_hw_rfkill = iwl_is_rfkill_set(trans);
-
        /* tell the device to stop sending interrupts */
        iwl_disable_interrupts(trans);
 
@@ -1199,7 +1206,6 @@ static void _iwl_trans_pcie_stop_device(struct iwl_trans *trans, bool low_power)
        clear_bit(STATUS_SYNC_HCMD_ACTIVE, &trans->status);
        clear_bit(STATUS_INT_ENABLED, &trans->status);
        clear_bit(STATUS_TPOWER_PMI, &trans->status);
-       clear_bit(STATUS_RFKILL, &trans->status);
 
        /*
         * Even if we stop the HW, we still want the RF kill
@@ -1207,26 +1213,6 @@ static void _iwl_trans_pcie_stop_device(struct iwl_trans *trans, bool low_power)
         */
        iwl_enable_rfkill_int(trans);
 
-       /*
-        * 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, &trans->status);
-       else
-               clear_bit(STATUS_RFKILL, &trans->status);
-       if (hw_rfkill != was_hw_rfkill)
-               iwl_trans_pcie_rf_kill(trans, hw_rfkill);
-
        /* re-take ownership to prevent other users from stealing the device */
        iwl_pcie_prepare_card_hw(trans);
 }
@@ -1339,12 +1325,45 @@ static void iwl_trans_pcie_fw_alive(struct iwl_trans *trans, u32 scd_addr)
        iwl_pcie_tx_start(trans, scd_addr);
 }
 
+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);
+}
+
 static void iwl_trans_pcie_stop_device(struct iwl_trans *trans, bool low_power)
 {
        struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
+       bool was_in_rfkill;
 
        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, low_power);
+       iwl_trans_pcie_handle_stop_rfkill(trans, was_in_rfkill);
        mutex_unlock(&trans_pcie->mutex);
 }
 
@@ -1355,6 +1374,8 @@ void iwl_trans_pcie_rf_kill(struct iwl_trans *trans, bool state)
 
        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)) {
                if (trans->cfg->gen2)
                        _iwl_trans_pcie_gen2_stop_device(trans, true);
@@ -1646,6 +1667,8 @@ static int _iwl_trans_pcie_start_hw(struct iwl_trans *trans, bool low_power)
        /* 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;
 
@@ -3005,6 +3028,7 @@ struct iwl_trans *iwl_trans_pcie_alloc(struct pci_dev *pdev,
        trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
 
        trans_pcie->trans = trans;
+       trans_pcie->opmode_down = true;
        spin_lock_init(&trans_pcie->irq_lock);
        spin_lock_init(&trans_pcie->reg_lock);
        mutex_init(&trans_pcie->mutex);
index 8f00019cd3b4e4a097f35d885f1616270e135dab..464a435a444055854dadc07e57d4211833c4a6c8 100644 (file)
@@ -863,7 +863,7 @@ static int iwl_pcie_gen2_send_hcmd_sync(struct iwl_trans *trans,
        }
 
        if (!(cmd->flags & CMD_SEND_IN_RFKILL) &&
-           test_bit(STATUS_RFKILL, &trans->status)) {
+           test_bit(STATUS_RFKILL_OPMODE, &trans->status)) {
                IWL_DEBUG_RF_KILL(trans, "RFKILL in SYNC CMD... no rsp\n");
                ret = -ERFKILL;
                goto cancel;
@@ -900,7 +900,7 @@ int iwl_trans_pcie_gen2_send_hcmd(struct iwl_trans *trans,
                                  struct iwl_host_cmd *cmd)
 {
        if (!(cmd->flags & CMD_SEND_IN_RFKILL) &&
-           test_bit(STATUS_RFKILL, &trans->status)) {
+           test_bit(STATUS_RFKILL_OPMODE, &trans->status)) {
                IWL_DEBUG_RF_KILL(trans, "Dropping CMD 0x%x: RF KILL\n",
                                  cmd->id);
                return -ERFKILL;
index 94ab01164f6660501faf8d443f03f229b1519607..0a6e36a8a0bf26484a23b2d5c049f50377357c29 100644 (file)
@@ -1874,7 +1874,7 @@ static int iwl_pcie_send_hcmd_sync(struct iwl_trans *trans,
        }
 
        if (!(cmd->flags & CMD_SEND_IN_RFKILL) &&
-           test_bit(STATUS_RFKILL, &trans->status)) {
+           test_bit(STATUS_RFKILL_OPMODE, &trans->status)) {
                IWL_DEBUG_RF_KILL(trans, "RFKILL in SYNC CMD... no rsp\n");
                ret = -ERFKILL;
                goto cancel;
@@ -1911,7 +1911,7 @@ cancel:
 int iwl_trans_pcie_send_hcmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd)
 {
        if (!(cmd->flags & CMD_SEND_IN_RFKILL) &&
-           test_bit(STATUS_RFKILL, &trans->status)) {
+           test_bit(STATUS_RFKILL_OPMODE, &trans->status)) {
                IWL_DEBUG_RF_KILL(trans, "Dropping CMD 0x%x: RF KILL\n",
                                  cmd->id);
                return -ERFKILL;