Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 326477e4 authored by Johannes Berg's avatar Johannes Berg Committed by Luca Coelho
Browse files

iwlwifi: pcie: don't report RF-kill enabled while shutting down



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: default avatarJohannes Berg <johannes.berg@intel.com>
Signed-off-by: default avatarLuca Coelho <luciano.coelho@intel.com>
parent 6ad04359
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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)))
+4 −2
Original line number Diff line number Diff line
@@ -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,
+4 −8
Original line number Diff line number Diff line
@@ -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;
+3 −1
Original line number Diff line number Diff line
@@ -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);
+15 −5
Original line number Diff line number Diff line
@@ -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);
	}
}

Loading