/** * iwl_good_plcp_health - checks for plcp error. * * When the plcp error is exceeding the thresholds, reset the radio * to improve the throughput. */ bool iwl_good_plcp_health(struct iwl_priv *priv, struct iwl_rx_packet *pkt) { bool rc = true; int combined_plcp_delta; unsigned int plcp_msec; unsigned long plcp_received_jiffies; if (priv->cfg->base_params->plcp_delta_threshold == IWL_MAX_PLCP_ERR_THRESHOLD_DISABLE) { IWL_DEBUG_RADIO(priv, "plcp_err check disabled\n"); return rc; } /* * check for plcp_err and trigger radio reset if it exceeds * the plcp error threshold plcp_delta. */ plcp_received_jiffies = jiffies; plcp_msec = jiffies_to_msecs((long) plcp_received_jiffies - (long) priv->plcp_jiffies); priv->plcp_jiffies = plcp_received_jiffies; /* * check to make sure plcp_msec is not 0 to prevent division * by zero. */ if (plcp_msec) { struct statistics_rx_phy *ofdm; struct statistics_rx_ht_phy *ofdm_ht; if (iwl_bt_statistics(priv)) { ofdm = &pkt->u.stats_bt.rx.ofdm; ofdm_ht = &pkt->u.stats_bt.rx.ofdm_ht; combined_plcp_delta = (le32_to_cpu(ofdm->plcp_err) - le32_to_cpu(priv->_agn.statistics_bt. rx.ofdm.plcp_err)) + (le32_to_cpu(ofdm_ht->plcp_err) - le32_to_cpu(priv->_agn.statistics_bt. rx.ofdm_ht.plcp_err)); } else { ofdm = &pkt->u.stats.rx.ofdm; ofdm_ht = &pkt->u.stats.rx.ofdm_ht; combined_plcp_delta = (le32_to_cpu(ofdm->plcp_err) - le32_to_cpu(priv->_agn.statistics. rx.ofdm.plcp_err)) + (le32_to_cpu(ofdm_ht->plcp_err) - le32_to_cpu(priv->_agn.statistics. rx.ofdm_ht.plcp_err)); } if ((combined_plcp_delta > 0) && ((combined_plcp_delta * 100) / plcp_msec) > priv->cfg->base_params->plcp_delta_threshold) { /* * if plcp_err exceed the threshold, * the following data is printed in csv format: * Text: plcp_err exceeded %d, * Received ofdm.plcp_err, * Current ofdm.plcp_err, * Received ofdm_ht.plcp_err, * Current ofdm_ht.plcp_err, * combined_plcp_delta, * plcp_msec */ IWL_DEBUG_RADIO(priv, "plcp_err exceeded %u, " "%u, %u, %u, %u, %d, %u mSecs\n", priv->cfg->base_params->plcp_delta_threshold, le32_to_cpu(ofdm->plcp_err), le32_to_cpu(ofdm->plcp_err), le32_to_cpu(ofdm_ht->plcp_err), le32_to_cpu(ofdm_ht->plcp_err), combined_plcp_delta, plcp_msec); rc = false; } } return rc; }
int iwl_mvm_sar_select_profile(struct iwl_mvm *mvm, int prof_a, int prof_b) { union { struct iwl_dev_tx_power_cmd v5; struct iwl_dev_tx_power_cmd_v4 v4; } cmd; int i, j, idx; int profs[ACPI_SAR_NUM_CHAIN_LIMITS] = { prof_a, prof_b }; int len; BUILD_BUG_ON(ACPI_SAR_NUM_CHAIN_LIMITS < 2); BUILD_BUG_ON(ACPI_SAR_NUM_CHAIN_LIMITS * ACPI_SAR_NUM_SUB_BANDS != ACPI_SAR_TABLE_SIZE); cmd.v5.v3.set_mode = cpu_to_le32(IWL_TX_POWER_MODE_SET_CHAINS); if (fw_has_api(&mvm->fw->ucode_capa, IWL_UCODE_TLV_API_REDUCE_TX_POWER)) len = sizeof(cmd.v5); else if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_TX_POWER_ACK)) len = sizeof(cmd.v4); else len = sizeof(cmd.v4.v3); for (i = 0; i < ACPI_SAR_NUM_CHAIN_LIMITS; i++) { struct iwl_mvm_sar_profile *prof; /* don't allow SAR to be disabled (profile 0 means disable) */ if (profs[i] == 0) return -EPERM; /* we are off by one, so allow up to ACPI_SAR_PROFILE_NUM */ if (profs[i] > ACPI_SAR_PROFILE_NUM) return -EINVAL; /* profiles go from 1 to 4, so decrement to access the array */ prof = &mvm->sar_profiles[profs[i] - 1]; /* if the profile is disabled, do nothing */ if (!prof->enabled) { IWL_DEBUG_RADIO(mvm, "SAR profile %d is disabled.\n", profs[i]); /* if one of the profiles is disabled, we fail all */ return -ENOENT; } IWL_DEBUG_RADIO(mvm, " Chain[%d]:\n", i); for (j = 0; j < ACPI_SAR_NUM_SUB_BANDS; j++) { idx = (i * ACPI_SAR_NUM_SUB_BANDS) + j; cmd.v5.v3.per_chain_restriction[i][j] = cpu_to_le16(prof->table[idx]); IWL_DEBUG_RADIO(mvm, " Band[%d] = %d * .125dBm\n", j, prof->table[idx]); } } #ifdef CPTCFG_IWLMVM_VENDOR_CMDS mvm->sar_chain_a_profile = prof_a; mvm->sar_chain_b_profile = prof_b; #endif IWL_DEBUG_RADIO(mvm, "Sending REDUCE_TX_POWER_CMD per chain\n"); return iwl_mvm_send_cmd_pdu(mvm, REDUCE_TX_POWER_CMD, 0, len, &cmd); }
int iwl_mvm_get_sar_geo_profile(struct iwl_mvm *mvm) { struct iwl_geo_tx_power_profiles_resp *resp; int ret; struct iwl_geo_tx_power_profiles_cmd geo_cmd = { .ops = cpu_to_le32(IWL_PER_CHAIN_OFFSET_GET_CURRENT_TABLE), }; struct iwl_host_cmd cmd = { .id = WIDE_ID(PHY_OPS_GROUP, GEO_TX_POWER_LIMIT), .len = { sizeof(geo_cmd), }, .flags = CMD_WANT_SKB, .data = { &geo_cmd }, }; ret = iwl_mvm_send_cmd(mvm, &cmd); if (ret) { IWL_ERR(mvm, "Failed to get geographic profile info %d\n", ret); return ret; } resp = (void *)cmd.resp_pkt->data; ret = le32_to_cpu(resp->profile_idx); if (WARN_ON(ret > ACPI_NUM_GEO_PROFILES)) { ret = -EIO; IWL_WARN(mvm, "Invalid geographic profile idx (%d)\n", ret); } iwl_free_resp(&cmd); return ret; } static int iwl_mvm_sar_geo_init(struct iwl_mvm *mvm) { struct iwl_geo_tx_power_profiles_cmd cmd = { .ops = cpu_to_le32(IWL_PER_CHAIN_OFFSET_SET_TABLES), }; int ret, i, j; u16 cmd_wide_id = WIDE_ID(PHY_OPS_GROUP, GEO_TX_POWER_LIMIT); ret = iwl_mvm_sar_get_wgds_table(mvm); if (ret < 0) { IWL_DEBUG_RADIO(mvm, "Geo SAR BIOS table invalid or unavailable. (%d)\n", ret); /* we don't fail if the table is not available */ return 0; } IWL_DEBUG_RADIO(mvm, "Sending GEO_TX_POWER_LIMIT\n"); BUILD_BUG_ON(ACPI_NUM_GEO_PROFILES * ACPI_WGDS_NUM_BANDS * ACPI_WGDS_TABLE_SIZE != ACPI_WGDS_WIFI_DATA_SIZE); BUILD_BUG_ON(ACPI_NUM_GEO_PROFILES > IWL_NUM_GEO_PROFILES); for (i = 0; i < ACPI_NUM_GEO_PROFILES; i++) { struct iwl_per_chain_offset *chain = (struct iwl_per_chain_offset *)&cmd.table[i]; for (j = 0; j < ACPI_WGDS_NUM_BANDS; j++) { u8 *value; value = &mvm->geo_profiles[i].values[j * ACPI_GEO_PER_CHAIN_SIZE]; chain[j].max_tx_power = cpu_to_le16(value[0]); chain[j].chain_a = value[1]; chain[j].chain_b = value[2]; IWL_DEBUG_RADIO(mvm, "SAR geographic profile[%d] Band[%d]: chain A = %d chain B = %d max_tx_power = %d\n", i, j, value[1], value[2], value[0]); } } return iwl_mvm_send_cmd_pdu(mvm, cmd_wide_id, 0, sizeof(cmd), &cmd); } #else /* CONFIG_ACPI */ static int iwl_mvm_sar_get_wrds_table(struct iwl_mvm *mvm) { return -ENOENT; } static int iwl_mvm_sar_get_ewrd_table(struct iwl_mvm *mvm) { return -ENOENT; } static int iwl_mvm_sar_geo_init(struct iwl_mvm *mvm) { return 0; } int iwl_mvm_sar_select_profile(struct iwl_mvm *mvm, int prof_a, int prof_b) { return -ENOENT; } int iwl_mvm_get_sar_geo_profile(struct iwl_mvm *mvm) { return -ENOENT; } #endif /* CONFIG_ACPI */ static int iwl_mvm_sar_init(struct iwl_mvm *mvm) { int ret; ret = iwl_mvm_sar_get_wrds_table(mvm); if (ret < 0) { IWL_DEBUG_RADIO(mvm, "WRDS SAR BIOS table invalid or unavailable. (%d)\n", ret); /* if not available, don't fail and don't bother with EWRD */ return 0; } ret = iwl_mvm_sar_get_ewrd_table(mvm); /* if EWRD is not available, we can still use WRDS, so don't fail */ if (ret < 0) IWL_DEBUG_RADIO(mvm, "EWRD SAR BIOS table invalid or unavailable. (%d)\n", ret); #if defined(CPTCFG_IWLMVM_VENDOR_CMDS) && defined(CONFIG_ACPI) /* * if no profile was chosen by the user yet, choose profile 1 (WRDS) as * default for both chains */ if (mvm->sar_chain_a_profile && mvm->sar_chain_b_profile) ret = iwl_mvm_sar_select_profile(mvm, mvm->sar_chain_a_profile, mvm->sar_chain_b_profile); else #endif ret = iwl_mvm_sar_select_profile(mvm, 1, 1); /* if we don't have profile 0 from BIOS, just skip it */ if (ret == -ENOENT) return 0; return ret; }
void iwl_rx_statistics(struct iwl_priv *priv, struct iwl_rx_mem_buffer *rxb) { int change; struct iwl_rx_packet *pkt = rxb_addr(rxb); int combined_plcp_delta; unsigned int plcp_msec; unsigned long plcp_received_jiffies; IWL_DEBUG_RX(priv, "Statistics notification received (%d vs %d).\n", (int)sizeof(priv->statistics), le32_to_cpu(pkt->len_n_flags) & FH_RSCSR_FRAME_SIZE_MSK); change = ((priv->statistics.general.temperature != pkt->u.stats.general.temperature) || ((priv->statistics.flag & STATISTICS_REPLY_FLG_HT40_MODE_MSK) != (pkt->u.stats.flag & STATISTICS_REPLY_FLG_HT40_MODE_MSK))); #ifdef CONFIG_IWLWIFI_DEBUG iwl_accumulative_statistics(priv, (__le32 *)&pkt->u.stats); #endif /* * check for plcp_err and trigger radio reset if it exceeds * the plcp error threshold plcp_delta. */ plcp_received_jiffies = jiffies; plcp_msec = jiffies_to_msecs((long) plcp_received_jiffies - (long) priv->plcp_jiffies); priv->plcp_jiffies = plcp_received_jiffies; /* * check to make sure plcp_msec is not 0 to prevent division * by zero. */ if (plcp_msec) { combined_plcp_delta = (le32_to_cpu(pkt->u.stats.rx.ofdm.plcp_err) - le32_to_cpu(priv->statistics.rx.ofdm.plcp_err)) + (le32_to_cpu(pkt->u.stats.rx.ofdm_ht.plcp_err) - le32_to_cpu(priv->statistics.rx.ofdm_ht.plcp_err)); if ((combined_plcp_delta > 0) && ((combined_plcp_delta * 100) / plcp_msec) > priv->cfg->plcp_delta_threshold) { /* * if plcp_err exceed the threshold, the following * data is printed in csv format: * Text: plcp_err exceeded %d, * Received ofdm.plcp_err, * Current ofdm.plcp_err, * Received ofdm_ht.plcp_err, * Current ofdm_ht.plcp_err, * combined_plcp_delta, * plcp_msec */ IWL_DEBUG_RADIO(priv, PLCP_MSG, priv->cfg->plcp_delta_threshold, le32_to_cpu(pkt->u.stats.rx.ofdm.plcp_err), le32_to_cpu(priv->statistics.rx.ofdm.plcp_err), le32_to_cpu(pkt->u.stats.rx.ofdm_ht.plcp_err), le32_to_cpu( priv->statistics.rx.ofdm_ht.plcp_err), combined_plcp_delta, plcp_msec); /* * Reset the RF radio due to the high plcp * error rate */ iwl_force_reset(priv, IWL_RF_RESET); } } memcpy(&priv->statistics, &pkt->u.stats, sizeof(priv->statistics)); set_bit(STATUS_STATISTICS, &priv->status); /* Reschedule the statistics timer to occur in * REG_RECALIB_PERIOD seconds to ensure we get a * thermal update even if the uCode doesn't give * us one */ mod_timer(&priv->statistics_periodic, jiffies + msecs_to_jiffies(REG_RECALIB_PERIOD * 1000)); if (unlikely(!test_bit(STATUS_SCANNING, &priv->status)) && (pkt->hdr.cmd == STATISTICS_NOTIFICATION)) { iwl_rx_calc_noise(priv); queue_work(priv->workqueue, &priv->run_time_calib_work); } if (priv->cfg->ops->lib->temp_ops.temperature && change) priv->cfg->ops->lib->temp_ops.temperature(priv); }