linux/drivers/net/wireless/intel/iwlwifi/mvm/tt.c

865 lines
22 KiB
C
Raw Permalink Normal View History

// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2012-2014, 2019-2022, 2024-2025 Intel Corporation
* Copyright (C) 2013-2014 Intel Mobile Communications GmbH
* Copyright (C) 2015-2016 Intel Deutschland GmbH
*/
#include <linux/sort.h>
#include "mvm.h"
#define IWL_MVM_NUM_CTDP_STEPS 20
#define IWL_MVM_MIN_CTDP_BUDGET_MW 150
#define IWL_MVM_TEMP_NOTIF_WAIT_TIMEOUT HZ
void iwl_mvm_enter_ctkill(struct iwl_mvm *mvm)
{
struct iwl_mvm_tt_mgmt *tt = &mvm->thermal_throttle;
u32 duration = tt->params.ct_kill_duration;
if (test_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status))
return;
IWL_ERR(mvm, "Enter CT Kill\n");
iwl_mvm_set_hw_ctkill_state(mvm, true);
if (!iwl_mvm_is_tt_in_fw(mvm)) {
tt->throttle = false;
tt->dynamic_smps = false;
}
/* Don't schedule an exit work if we're in test mode, since
* the temperature will not change unless we manually set it
* again (or disable testing).
*/
if (!mvm->temperature_test)
schedule_delayed_work(&tt->ct_kill_exit,
round_jiffies_relative(duration * HZ));
}
static void iwl_mvm_exit_ctkill(struct iwl_mvm *mvm)
{
if (!test_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status))
return;
IWL_ERR(mvm, "Exit CT Kill\n");
iwl_mvm_set_hw_ctkill_state(mvm, false);
}
static void iwl_mvm_tt_temp_changed(struct iwl_mvm *mvm, u32 temp)
{
/* ignore the notification if we are in test mode */
if (mvm->temperature_test)
return;
if (mvm->temperature == temp)
return;
mvm->temperature = temp;
iwl_mvm_tt_handler(mvm);
}
static int iwl_mvm_temp_notif_parse(struct iwl_mvm *mvm,
struct iwl_rx_packet *pkt)
{
struct iwl_dts_measurement_notif_v1 *notif_v1;
int len = iwl_rx_packet_payload_len(pkt);
int temp;
/* we can use notif_v1 only, because v2 only adds an additional
* parameter, which is not used in this function.
*/
if (WARN_ON_ONCE(len < sizeof(*notif_v1))) {
IWL_ERR(mvm, "Invalid DTS_MEASUREMENT_NOTIFICATION\n");
return -EINVAL;
}
notif_v1 = (void *)pkt->data;
temp = le32_to_cpu(notif_v1->temp);
/* shouldn't be negative, but since it's s32, make sure it isn't */
if (WARN_ON_ONCE(temp < 0))
temp = 0;
IWL_DEBUG_TEMP(mvm, "DTS_MEASUREMENT_NOTIFICATION - %d\n", temp);
return temp;
}
static bool iwl_mvm_temp_notif_wait(struct iwl_notif_wait_data *notif_wait,
struct iwl_rx_packet *pkt, void *data)
{
struct iwl_mvm *mvm =
container_of(notif_wait, struct iwl_mvm, notif_wait);
int *temp = data;
int ret;
ret = iwl_mvm_temp_notif_parse(mvm, pkt);
if (ret < 0)
return true;
*temp = ret;
return true;
}
void iwl_mvm_temp_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb)
{
struct iwl_rx_packet *pkt = rxb_addr(rxb);
struct iwl_dts_measurement_notif *notif_v2;
int len = iwl_rx_packet_payload_len(pkt);
int temp;
u32 ths_crossed;
/* the notification is handled synchronously in ctkill, so skip here */
if (test_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status))
return;
temp = iwl_mvm_temp_notif_parse(mvm, pkt);
if (!iwl_mvm_is_tt_in_fw(mvm)) {
if (temp >= 0)
iwl_mvm_tt_temp_changed(mvm, temp);
return;
}
if (WARN_ON_ONCE(len < sizeof(*notif_v2))) {
IWL_ERR(mvm, "Invalid DTS_MEASUREMENT_NOTIFICATION\n");
return;
}
notif_v2 = (void *)pkt->data;
ths_crossed = le32_to_cpu(notif_v2->threshold_idx);
/* 0xFF in ths_crossed means the notification is not related
* to a trip, so we can ignore it here.
*/
if (ths_crossed == 0xFF)
return;
IWL_DEBUG_TEMP(mvm, "Temp = %d Threshold crossed = %d\n",
temp, ths_crossed);
#ifdef CONFIG_THERMAL
if (WARN_ON(ths_crossed >= IWL_MAX_DTS_TRIPS))
return;
if (mvm->tz_device.tzone) {
struct iwl_mvm_thermal_device *tz_dev = &mvm->tz_device;
thermal_zone_device_update(tz_dev->tzone,
THERMAL_TRIP_VIOLATED);
}
#endif /* CONFIG_THERMAL */
}
void iwl_mvm_ct_kill_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb)
{
struct iwl_rx_packet *pkt = rxb_addr(rxb);
struct ct_kill_notif *notif;
notif = (struct ct_kill_notif *)pkt->data;
IWL_DEBUG_TEMP(mvm, "CT Kill notification temperature = %d\n",
notif->temperature);
if (iwl_fw_lookup_notif_ver(mvm->fw, PHY_OPS_GROUP,
CT_KILL_NOTIFICATION, 0) > 1)
IWL_DEBUG_TEMP(mvm,
"CT kill notification DTS bitmap = 0x%x, Scheme = %d\n",
notif->dts, notif->scheme);
iwl_mvm_enter_ctkill(mvm);
}
/*
* send the DTS_MEASUREMENT_TRIGGER command with or without waiting for a
* response. If we get a response then the measurement is stored in 'temp'
*/
static int iwl_mvm_send_temp_cmd(struct iwl_mvm *mvm, bool response, s32 *temp)
{
struct iwl_host_cmd cmd = {};
struct iwl_dts_measurement_cmd dts_cmd = {
.flags = cpu_to_le32(DTS_TRIGGER_CMD_FLAGS_TEMP),
};
struct iwl_ext_dts_measurement_cmd ext_cmd = {
.control_mode = cpu_to_le32(DTS_DIRECT_WITHOUT_MEASURE),
};
struct iwl_dts_measurement_resp *resp;
void *cmd_ptr;
int ret;
u32 cmd_flags = 0;
u16 len;
/* Check which command format is used (regular/extended) */
if (fw_has_capa(&mvm->fw->ucode_capa,
IWL_UCODE_TLV_CAPA_EXTENDED_DTS_MEASURE)) {
len = sizeof(ext_cmd);
cmd_ptr = &ext_cmd;
} else {
len = sizeof(dts_cmd);
cmd_ptr = &dts_cmd;
}
/* The command version where we get a response is zero length */
if (response) {
cmd_flags = CMD_WANT_SKB;
len = 0;
}
cmd.id = WIDE_ID(PHY_OPS_GROUP, CMD_DTS_MEASUREMENT_TRIGGER_WIDE);
cmd.len[0] = len;
cmd.flags = cmd_flags;
cmd.data[0] = cmd_ptr;
IWL_DEBUG_TEMP(mvm,
"Sending temperature measurement command - %s response\n",
response ? "with" : "without");
ret = iwl_mvm_send_cmd(mvm, &cmd);
if (ret) {
IWL_ERR(mvm,
"Failed to send the temperature measurement command (err=%d)\n",
ret);
return ret;
}
if (response) {
resp = (void *)cmd.resp_pkt->data;
*temp = le32_to_cpu(resp->temp);
IWL_DEBUG_TEMP(mvm,
"Got temperature measurement response: temp=%d\n",
*temp);
iwl_free_resp(&cmd);
}
return ret;
}
int iwl_mvm_get_temp(struct iwl_mvm *mvm, s32 *temp)
{
struct iwl_notification_wait wait_temp_notif;
static u16 temp_notif[] = { WIDE_ID(PHY_OPS_GROUP,
DTS_MEASUREMENT_NOTIF_WIDE) };
int ret;
u8 cmd_ver;
/*
* If command version is 1 we send the command and immediately get
* a response. For older versions we send the command and wait for a
* notification (no command TLV for previous versions).
*/
iwlwifi: make iwl_fw_lookup_cmd_ver() take a cmd_id Instead of taking the group/command separately, make the function take a combined command ID. In many cases, this allows us to pass an existing command ID (e.g. cmd.id), or introduce a new variable for it, so that we don't use the command ID twice. This way, we can also use LONG_GROUP implicitly, so we don't need to spell that out for many commands. Apart from mvm.h, fw/img.{c,h} changes and some copyright and indentation updates, this was done with spatch: @@ identifier cmd; expression fw, G, C, def; @@ struct iwl_host_cmd cmd = { .id = WIDE_ID(G, C), ... }; ... -iwl_fw_lookup_cmd_ver(fw, G, C, def) +iwl_fw_lookup_cmd_ver(fw, cmd.id, def) @@ identifier cmd; expression fw, C, def; @@ struct iwl_host_cmd cmd = { .id = C, ... }; ... -iwl_fw_lookup_cmd_ver(fw, \(IWL_ALWAYS_LONG_GROUP\|LONG_GROUP\), C, def) +iwl_fw_lookup_cmd_ver(fw, cmd.id, def) @@ identifier func; expression fw, G, C, mvm, flags, cmd, size, def; type rettype; @@ rettype func(...) { +u32 cmd_id = WIDE_ID(G, C); ... -iwl_fw_lookup_cmd_ver(fw, G, C, def) +iwl_fw_lookup_cmd_ver(fw, cmd_id, def) ... -iwl_mvm_send_cmd_pdu(mvm, WIDE_ID(G, C), flags, cmd, size) +iwl_mvm_send_cmd_pdu(mvm, cmd_id, flags, cmd, size) ... } @@ identifier func; expression fw, G, C, mvm, flags, cmd, size, def; type rettype; @@ rettype func(...) { +u32 cmd_id = C; ... -iwl_fw_lookup_cmd_ver(fw, \(IWL_ALWAYS_LONG_GROUP\|LONG_GROUP\), C, def) +iwl_fw_lookup_cmd_ver(fw, cmd_id, def) ... -iwl_mvm_send_cmd_pdu(mvm, C, flags, cmd, size) +iwl_mvm_send_cmd_pdu(mvm, cmd_id, flags, cmd, size) ... } @@ expression fw, C, def; @@ -iwl_fw_lookup_cmd_ver(fw, \(IWL_ALWAYS_LONG_GROUP\|LONG_GROUP\), C, def) +iwl_fw_lookup_cmd_ver(fw, C, def) @@ expression fw, C, G, def; @@ -iwl_fw_lookup_cmd_ver(fw, G, C, def) +iwl_fw_lookup_cmd_ver(fw, WIDE_ID(G, C), def) Signed-off-by: Johannes Berg <johannes.berg@intel.com> Signed-off-by: Luca Coelho <luciano.coelho@intel.com> Link: https://lore.kernel.org/r/iwlwifi.20220128153014.c4ac213cef5c.I6fd9a4fcbcf16ef3a3ae20a2b08ee54ebe06f96f@changeid Signed-off-by: Luca Coelho <luciano.coelho@intel.com>
2022-01-28 15:34:25 +02:00
cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw,
WIDE_ID(PHY_OPS_GROUP, CMD_DTS_MEASUREMENT_TRIGGER_WIDE),
IWL_FW_CMD_VER_UNKNOWN);
if (cmd_ver == 1)
return iwl_mvm_send_temp_cmd(mvm, true, temp);
lockdep_assert_held(&mvm->mutex);
iwl_init_notification_wait(&mvm->notif_wait, &wait_temp_notif,
temp_notif, ARRAY_SIZE(temp_notif),
iwl_mvm_temp_notif_wait, temp);
ret = iwl_mvm_send_temp_cmd(mvm, false, temp);
if (ret) {
iwl_remove_notification(&mvm->notif_wait, &wait_temp_notif);
return ret;
}
ret = iwl_wait_notification(&mvm->notif_wait, &wait_temp_notif,
IWL_MVM_TEMP_NOTIF_WAIT_TIMEOUT);
if (ret)
IWL_WARN(mvm, "Getting the temperature timed out\n");
return ret;
}
static void check_exit_ctkill(struct work_struct *work)
{
struct iwl_mvm_tt_mgmt *tt;
struct iwl_mvm *mvm;
u32 duration;
s32 temp;
int ret;
tt = container_of(work, struct iwl_mvm_tt_mgmt, ct_kill_exit.work);
mvm = container_of(tt, struct iwl_mvm, thermal_throttle);
if (iwl_mvm_is_tt_in_fw(mvm)) {
iwl_mvm_exit_ctkill(mvm);
return;
}
duration = tt->params.ct_kill_duration;
flush_work(&mvm->roc_done_wk);
mutex_lock(&mvm->mutex);
if (__iwl_mvm_mac_start(mvm))
goto reschedule;
ret = iwl_mvm_get_temp(mvm, &temp);
__iwl_mvm_mac_stop(mvm, false);
if (ret)
goto reschedule;
IWL_DEBUG_TEMP(mvm, "NIC temperature: %d\n", temp);
if (temp <= tt->params.ct_kill_exit) {
mutex_unlock(&mvm->mutex);
iwl_mvm_exit_ctkill(mvm);
return;
}
reschedule:
mutex_unlock(&mvm->mutex);
schedule_delayed_work(&mvm->thermal_throttle.ct_kill_exit,
round_jiffies(duration * HZ));
}
static void iwl_mvm_tt_smps_iterator(void *_data, u8 *mac,
struct ieee80211_vif *vif)
{
struct iwl_mvm *mvm = _data;
enum ieee80211_smps_mode smps_mode;
lockdep_assert_held(&mvm->mutex);
if (mvm->thermal_throttle.dynamic_smps)
smps_mode = IEEE80211_SMPS_DYNAMIC;
else
smps_mode = IEEE80211_SMPS_AUTOMATIC;
if (vif->type != NL80211_IFTYPE_STATION)
return;
iwl_mvm_update_smps(mvm, vif, IWL_MVM_SMPS_REQ_TT, smps_mode, 0);
}
static void iwl_mvm_tt_tx_protection(struct iwl_mvm *mvm, bool enable)
{
struct iwl_mvm_sta *mvmsta;
int i, err;
for (i = 0; i < mvm->fw->ucode_capa.num_stations; i++) {
mvmsta = iwl_mvm_sta_from_staid_protected(mvm, i);
if (!mvmsta)
continue;
if (enable == mvmsta->tt_tx_protection)
continue;
err = iwl_mvm_tx_protection(mvm, mvmsta, enable);
if (err) {
IWL_ERR(mvm, "Failed to %s Tx protection\n",
enable ? "enable" : "disable");
} else {
IWL_DEBUG_TEMP(mvm, "%s Tx protection\n",
enable ? "Enable" : "Disable");
mvmsta->tt_tx_protection = enable;
}
}
}
void iwl_mvm_tt_tx_backoff(struct iwl_mvm *mvm, u32 backoff)
{
struct iwl_host_cmd cmd = {
.id = REPLY_THERMAL_MNG_BACKOFF,
.len = { sizeof(u32), },
.data = { &backoff, },
};
backoff = max(backoff, mvm->thermal_throttle.min_backoff);
if (iwl_mvm_send_cmd(mvm, &cmd) == 0) {
IWL_DEBUG_TEMP(mvm, "Set Thermal Tx backoff to: %u\n",
backoff);
mvm->thermal_throttle.tx_backoff = backoff;
} else {
IWL_ERR(mvm, "Failed to change Thermal Tx backoff\n");
}
}
void iwl_mvm_tt_handler(struct iwl_mvm *mvm)
{
struct iwl_tt_params *params = &mvm->thermal_throttle.params;
struct iwl_mvm_tt_mgmt *tt = &mvm->thermal_throttle;
s32 temperature = mvm->temperature;
bool throttle_enable = false;
int i;
u32 tx_backoff;
IWL_DEBUG_TEMP(mvm, "NIC temperature: %d\n", mvm->temperature);
if (params->support_ct_kill && temperature >= params->ct_kill_entry) {
iwl_mvm_enter_ctkill(mvm);
return;
}
if (params->support_ct_kill &&
temperature <= params->ct_kill_exit) {
iwl_mvm_exit_ctkill(mvm);
return;
}
if (params->support_dynamic_smps) {
if (!tt->dynamic_smps &&
temperature >= params->dynamic_smps_entry) {
IWL_DEBUG_TEMP(mvm, "Enable dynamic SMPS\n");
tt->dynamic_smps = true;
ieee80211_iterate_active_interfaces_atomic(
mvm->hw, IEEE80211_IFACE_ITER_NORMAL,
iwl_mvm_tt_smps_iterator, mvm);
throttle_enable = true;
} else if (tt->dynamic_smps &&
temperature <= params->dynamic_smps_exit) {
IWL_DEBUG_TEMP(mvm, "Disable dynamic SMPS\n");
tt->dynamic_smps = false;
ieee80211_iterate_active_interfaces_atomic(
mvm->hw, IEEE80211_IFACE_ITER_NORMAL,
iwl_mvm_tt_smps_iterator, mvm);
}
}
if (params->support_tx_protection) {
if (temperature >= params->tx_protection_entry) {
iwl_mvm_tt_tx_protection(mvm, true);
throttle_enable = true;
} else if (temperature <= params->tx_protection_exit) {
iwl_mvm_tt_tx_protection(mvm, false);
}
}
if (params->support_tx_backoff) {
tx_backoff = tt->min_backoff;
for (i = 0; i < TT_TX_BACKOFF_SIZE; i++) {
if (temperature < params->tx_backoff[i].temperature)
break;
tx_backoff = max(tt->min_backoff,
params->tx_backoff[i].backoff);
}
if (tx_backoff != tt->min_backoff)
throttle_enable = true;
if (tt->tx_backoff != tx_backoff)
iwl_mvm_tt_tx_backoff(mvm, tx_backoff);
}
if (!tt->throttle && throttle_enable) {
IWL_WARN(mvm,
"Due to high temperature thermal throttling initiated\n");
tt->throttle = true;
} else if (tt->throttle && !tt->dynamic_smps &&
tt->tx_backoff == tt->min_backoff &&
temperature <= params->tx_protection_exit) {
IWL_WARN(mvm,
"Temperature is back to normal thermal throttling stopped\n");
tt->throttle = false;
}
}
static const struct iwl_tt_params iwl_mvm_default_tt_params = {
.ct_kill_entry = 118,
.ct_kill_exit = 96,
.ct_kill_duration = 5,
.dynamic_smps_entry = 114,
.dynamic_smps_exit = 110,
.tx_protection_entry = 114,
.tx_protection_exit = 108,
.tx_backoff = {
{.temperature = 112, .backoff = 200},
{.temperature = 113, .backoff = 600},
{.temperature = 114, .backoff = 1200},
{.temperature = 115, .backoff = 2000},
{.temperature = 116, .backoff = 4000},
{.temperature = 117, .backoff = 10000},
},
.support_ct_kill = true,
.support_dynamic_smps = true,
.support_tx_protection = true,
.support_tx_backoff = true,
};
int iwl_mvm_ctdp_command(struct iwl_mvm *mvm, u32 op, u32 state)
{
struct iwl_ctdp_cmd cmd = {
.operation = cpu_to_le32(op),
.window_size = 0,
};
u32 budget;
int ret;
u32 status;
lockdep_assert_held(&mvm->mutex);
/* Do a linear scale from IWL_MVM_MIN_CTDP_BUDGET_MW to the configured
* maximum in the predefined number of steps.
*/
budget = ((mvm->thermal_throttle.power_budget_mw -
IWL_MVM_MIN_CTDP_BUDGET_MW) *
(IWL_MVM_NUM_CTDP_STEPS - 1 - state)) /
(IWL_MVM_NUM_CTDP_STEPS - 1) +
IWL_MVM_MIN_CTDP_BUDGET_MW;
cmd.budget = cpu_to_le32(budget);
status = 0;
ret = iwl_mvm_send_cmd_pdu_status(mvm, WIDE_ID(PHY_OPS_GROUP,
CTDP_CONFIG_CMD),
sizeof(cmd), &cmd, &status);
if (ret) {
IWL_ERR(mvm, "cTDP command failed (err=%d)\n", ret);
return ret;
}
switch (op) {
case CTDP_CMD_OPERATION_START:
#ifdef CONFIG_THERMAL
mvm->cooling_dev.cur_state = state;
#endif /* CONFIG_THERMAL */
break;
case CTDP_CMD_OPERATION_REPORT:
IWL_DEBUG_TEMP(mvm, "cTDP avg energy in mWatt = %d\n", status);
/* when the function is called with CTDP_CMD_OPERATION_REPORT
* option the function should return the average budget value
* that is received from the FW.
* The budget can't be less or equal to 0, so it's possible
* to distinguish between error values and budgets.
*/
return status;
case CTDP_CMD_OPERATION_STOP:
IWL_DEBUG_TEMP(mvm, "cTDP stopped successfully\n");
break;
}
return 0;
}
#ifdef CONFIG_THERMAL
static int compare_temps(const void *a, const void *b)
{
return ((s16)le16_to_cpu(*(const __le16 *)a) -
(s16)le16_to_cpu(*(const __le16 *)b));
}
struct iwl_trip_walk_data {
__le16 *thresholds;
int count;
};
static int iwl_trip_temp_cb(struct thermal_trip *trip, void *arg)
{
struct iwl_trip_walk_data *twd = arg;
if (trip->temperature == THERMAL_TEMP_INVALID)
return 0;
twd->thresholds[twd->count++] = cpu_to_le16((s16)(trip->temperature / 1000));
return 0;
}
#endif
int iwl_mvm_send_temp_report_ths_cmd(struct iwl_mvm *mvm)
{
struct temp_report_ths_cmd cmd = {0};
int ret;
#ifdef CONFIG_THERMAL
struct iwl_trip_walk_data twd = { .thresholds = cmd.thresholds, .count = 0 };
lockdep_assert_held(&mvm->mutex);
if (!mvm->tz_device.tzone)
goto send;
/*
* The thermal core holds an array of temperature trips that are
* unsorted and uncompressed, the FW should get it compressed and
* sorted.
*/
/* compress trips to cmd array, remove uninitialized values*/
for_each_thermal_trip(mvm->tz_device.tzone, iwl_trip_temp_cb, &twd);
cmd.num_temps = cpu_to_le32(twd.count);
if (twd.count)
sort(cmd.thresholds, twd.count, sizeof(s16), compare_temps, NULL);
send:
#endif
ret = iwl_mvm_send_cmd_pdu(mvm, WIDE_ID(PHY_OPS_GROUP,
TEMP_REPORTING_THRESHOLDS_CMD),
0, sizeof(cmd), &cmd);
if (ret)
IWL_ERR(mvm, "TEMP_REPORT_THS_CMD command failed (err=%d)\n",
ret);
return ret;
}
#ifdef CONFIG_THERMAL
static int iwl_mvm_tzone_get_temp(struct thermal_zone_device *device,
int *temperature)
{
struct iwl_mvm *mvm = thermal_zone_device_priv(device);
int ret;
int temp;
guard(mvm)(mvm);
if (!iwl_mvm_firmware_running(mvm) ||
thermal: core: Allow thermal zones to tell the core to ignore them The iwlwifi wireless driver registers a thermal zone that is only needed when the network interface handled by it is up and it wants that thermal zone to be effectively ignored by the core otherwise. Before commit a8a261774466 ("thermal: core: Call monitor_thermal_zone() if zone temperature is invalid") that could be achieved by returning an error code from the thermal zone's .get_temp() callback because the core did not really handle errors returned by it almost at all. However, commit a8a261774466 made the core attempt to recover from the situation in which the temperature of a thermal zone cannot be determined due to errors returned by its .get_temp() and is always invalid from the core's perspective. That was done because there are thermal zones in which .get_temp() returns errors to start with due to some difficulties related to the initialization ordering, but then it will start to produce valid temperature values at one point. Unfortunately, the simple approach taken by commit a8a261774466, which is to poll the thermal zone periodically until its .get_temp() callback starts to return valid temperature values, is at odds with the special thermal zone in iwlwifi in which .get_temp() may always return an error because its network interface may always be down. If that happens, every attempt to invoke the thermal zone's .get_temp() callback resulting in an error causes the thermal core to print a dev_warn() message to the kernel log which is super-noisy. To address this problem, make the core handle the case in which .get_temp() returns 0, but the temperature value returned by it is not actually valid, in a special way. Namely, make the core completely ignore the invalid temperature value coming from .get_temp() in that case, which requires folding in update_temperature() into its caller and a few related changes. On the iwlwifi side, modify iwl_mvm_tzone_get_temp() to return 0 and put THERMAL_TEMP_INVALID into the temperature return memory location instead of returning an error when the firmware is not running or it is not of the right type. Also, to clearly separate the handling of invalid temperature values from the thermal zone initialization, introduce a special THERMAL_TEMP_INIT value specifically for the latter purpose. Fixes: a8a261774466 ("thermal: core: Call monitor_thermal_zone() if zone temperature is invalid") Closes: https://lore.kernel.org/linux-pm/20240715044527.GA1544@sol.localdomain/ Reported-by: Eric Biggers <ebiggers@kernel.org> Reported-by: Stefan Lippers-Hollmann <s.l-h@gmx.de> Link: https://bugzilla.kernel.org/show_bug.cgi?id=201761 Tested-by: Oleksandr Natalenko <oleksandr@natalenko.name> Tested-by: Stefan Lippers-Hollmann <s.l-h@gmx.de> Cc: 6.10+ <stable@vger.kernel.org> # 6.10+ Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com> Link: https://patch.msgid.link/4950004.31r3eYUQgx@rjwysocki.net [ rjw: Rebased on top of the current mainline ] Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
2024-07-17 21:45:02 +02:00
mvm->fwrt.cur_fw_img != IWL_UCODE_REGULAR) {
/*
* Tell the core that there is no valid temperature value to
* return, but it need not worry about this.
*/
*temperature = THERMAL_TEMP_INVALID;
return 0;
}
ret = iwl_mvm_get_temp(mvm, &temp);
if (ret)
return ret;
*temperature = temp * 1000;
return 0;
}
static int iwl_mvm_tzone_set_trip_temp(struct thermal_zone_device *device,
thermal: trip: Pass trip pointer to .set_trip_temp() thermal zone callback Out of several drivers implementing the .set_trip_temp() thermal zone operation, three don't actually use the trip ID argument passed to it, two call __thermal_zone_get_trip() to get a struct thermal_trip corresponding to the given trip ID, and the other use the trip ID as an index into their own data structures with the assumption that it will always match the ordering of entries in the trips table passed to the core during thermal zone registration, which is fragile and not really guaranteed. Even though the trip IDs used by the core are in fact their indices in the trips table passed to it by the thermal zone creator, that is purely a matter of convenience and should not be relied on for correctness. For this reason, modify trip_point_temp_store() to pass a (const) trip pointer to .set_trip_temp() and adjust the drivers implementing it accordingly. This helps to simplify the drivers invoking __thermal_zone_get_trip() from their .set_trip_temp() callback functions because they will not need to do it now and the other drivers can store their internal trip indices in the priv field in struct thermal_trip and their .set_trip_temp() callback functions can get those indices from there. The intel_quark_dts thermal driver can instead use the trip type to determine the requisite trip index. Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com> Link: https://patch.msgid.link/8392906.T7Z3S40VBb@rjwysocki.net [ rjw: Add missing colon and 2 empty code lines ] [ rjw: Add missing change in imx_thermal.c and adjust the changelog ] [ rjw: Drop an unused local variable ] Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
2024-07-02 16:42:33 +02:00
const struct thermal_trip *trip, int temp)
{
struct iwl_mvm *mvm = thermal_zone_device_priv(device);
guard(mvm)(mvm);
if (!iwl_mvm_firmware_running(mvm) ||
mvm->fwrt.cur_fw_img != IWL_UCODE_REGULAR)
return -EIO;
if ((temp / 1000) > S16_MAX)
return -EINVAL;
return iwl_mvm_send_temp_report_ths_cmd(mvm);
}
static struct thermal_zone_device_ops tzone_ops = {
.get_temp = iwl_mvm_tzone_get_temp,
.set_trip_temp = iwl_mvm_tzone_set_trip_temp,
};
static void iwl_mvm_thermal_zone_register(struct iwl_mvm *mvm)
{
int i, ret;
char name[16];
static atomic_t counter = ATOMIC_INIT(0);
if (!iwl_mvm_is_tt_in_fw(mvm)) {
mvm->tz_device.tzone = NULL;
return;
}
BUILD_BUG_ON(ARRAY_SIZE(name) >= THERMAL_NAME_LENGTH);
sprintf(name, "iwlwifi_%u", atomic_inc_return(&counter) & 0xFF);
/*
* 0 is a valid temperature,
* so initialize the array with S16_MIN which invalid temperature
*/
for (i = 0 ; i < IWL_MAX_DTS_TRIPS; i++) {
mvm->tz_device.trips[i].temperature = THERMAL_TEMP_INVALID;
mvm->tz_device.trips[i].type = THERMAL_TRIP_PASSIVE;
mvm->tz_device.trips[i].flags = THERMAL_TRIP_FLAG_RW_TEMP;
}
mvm->tz_device.tzone = thermal_zone_device_register_with_trips(name,
mvm->tz_device.trips,
IWL_MAX_DTS_TRIPS,
mvm, &tzone_ops,
NULL, 0, 0);
if (IS_ERR(mvm->tz_device.tzone)) {
IWL_DEBUG_TEMP(mvm,
"Failed to register to thermal zone (err = %ld)\n",
PTR_ERR(mvm->tz_device.tzone));
mvm->tz_device.tzone = NULL;
return;
}
ret = thermal_zone_device_enable(mvm->tz_device.tzone);
if (ret) {
IWL_DEBUG_TEMP(mvm, "Failed to enable thermal zone\n");
thermal_zone_device_unregister(mvm->tz_device.tzone);
}
}
static int iwl_mvm_tcool_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
*state = IWL_MVM_NUM_CTDP_STEPS - 1;
return 0;
}
static int iwl_mvm_tcool_get_cur_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct iwl_mvm *mvm = (struct iwl_mvm *)(cdev->devdata);
*state = mvm->cooling_dev.cur_state;
return 0;
}
static int iwl_mvm_tcool_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long new_state)
{
struct iwl_mvm *mvm = (struct iwl_mvm *)(cdev->devdata);
guard(mvm)(mvm);
if (!iwl_mvm_firmware_running(mvm) ||
mvm->fwrt.cur_fw_img != IWL_UCODE_REGULAR)
return -EIO;
if (new_state >= IWL_MVM_NUM_CTDP_STEPS)
return -EINVAL;
return iwl_mvm_ctdp_command(mvm, CTDP_CMD_OPERATION_START,
new_state);
}
static const struct thermal_cooling_device_ops tcooling_ops = {
.get_max_state = iwl_mvm_tcool_get_max_state,
.get_cur_state = iwl_mvm_tcool_get_cur_state,
.set_cur_state = iwl_mvm_tcool_set_cur_state,
};
static void iwl_mvm_cooling_device_register(struct iwl_mvm *mvm)
{
char name[] = "iwlwifi";
if (!iwl_mvm_is_ctdp_supported(mvm))
return;
BUILD_BUG_ON(ARRAY_SIZE(name) >= THERMAL_NAME_LENGTH);
mvm->cooling_dev.cdev =
thermal_cooling_device_register(name,
mvm,
&tcooling_ops);
if (IS_ERR(mvm->cooling_dev.cdev)) {
IWL_DEBUG_TEMP(mvm,
"Failed to register to cooling device (err = %ld)\n",
PTR_ERR(mvm->cooling_dev.cdev));
mvm->cooling_dev.cdev = NULL;
return;
}
}
static void iwl_mvm_thermal_zone_unregister(struct iwl_mvm *mvm)
{
if (!iwl_mvm_is_tt_in_fw(mvm) || !mvm->tz_device.tzone)
return;
IWL_DEBUG_TEMP(mvm, "Thermal zone device unregister\n");
if (mvm->tz_device.tzone) {
thermal_zone_device_unregister(mvm->tz_device.tzone);
mvm->tz_device.tzone = NULL;
}
}
static void iwl_mvm_cooling_device_unregister(struct iwl_mvm *mvm)
{
if (!iwl_mvm_is_ctdp_supported(mvm) || !mvm->cooling_dev.cdev)
return;
IWL_DEBUG_TEMP(mvm, "Cooling device unregister\n");
if (mvm->cooling_dev.cdev) {
thermal_cooling_device_unregister(mvm->cooling_dev.cdev);
mvm->cooling_dev.cdev = NULL;
}
}
#endif /* CONFIG_THERMAL */
static u32 iwl_mvm_ctdp_get_max_budget(struct iwl_mvm *mvm)
{
u64 bios_power_budget = 0;
u32 default_power_budget;
switch (CSR_HW_RFID_TYPE(mvm->trans->info.hw_rf_id)) {
case IWL_CFG_RF_TYPE_JF2:
case IWL_CFG_RF_TYPE_JF1:
default_power_budget = 2000;
break;
case IWL_CFG_RF_TYPE_HR2:
case IWL_CFG_RF_TYPE_HR1:
default_power_budget = 2400;
break;
case IWL_CFG_RF_TYPE_GF:
/* dual-radio devices have a higher budget */
if (CSR_HW_RFID_IS_CDB(mvm->trans->info.hw_rf_id))
default_power_budget = 5200;
else
default_power_budget = 2880;
break;
case IWL_CFG_RF_TYPE_FM:
default_power_budget = 3450;
break;
default:
default_power_budget = 5550;
break;
}
iwl_bios_get_pwr_limit(&mvm->fwrt, &bios_power_budget);
/* 32bit in UEFI, 16bit in ACPI; use BIOS value if it is in range */
if (bios_power_budget &&
bios_power_budget != 0xffff && bios_power_budget != 0xffffffff &&
bios_power_budget >= IWL_MVM_MIN_CTDP_BUDGET_MW &&
bios_power_budget <= default_power_budget)
return (u32)bios_power_budget;
return default_power_budget;
}
void iwl_mvm_thermal_initialize(struct iwl_mvm *mvm, u32 min_backoff)
{
struct iwl_mvm_tt_mgmt *tt = &mvm->thermal_throttle;
IWL_DEBUG_TEMP(mvm, "Initialize Thermal Throttling\n");
if (mvm->cfg->thermal_params)
tt->params = *mvm->cfg->thermal_params;
else
tt->params = iwl_mvm_default_tt_params;
tt->power_budget_mw = iwl_mvm_ctdp_get_max_budget(mvm);
IWL_DEBUG_TEMP(mvm, "cTDP power budget: %d mW\n", tt->power_budget_mw);
tt->throttle = false;
tt->dynamic_smps = false;
tt->min_backoff = min_backoff;
INIT_DELAYED_WORK(&tt->ct_kill_exit, check_exit_ctkill);
#ifdef CONFIG_THERMAL
iwl_mvm_cooling_device_register(mvm);
iwl_mvm_thermal_zone_register(mvm);
#endif
mvm->init_status |= IWL_MVM_INIT_STATUS_THERMAL_INIT_COMPLETE;
}
void iwl_mvm_thermal_exit(struct iwl_mvm *mvm)
{
if (!(mvm->init_status & IWL_MVM_INIT_STATUS_THERMAL_INIT_COMPLETE))
return;
cancel_delayed_work_sync(&mvm->thermal_throttle.ct_kill_exit);
IWL_DEBUG_TEMP(mvm, "Exit Thermal Throttling\n");
#ifdef CONFIG_THERMAL
iwl_mvm_cooling_device_unregister(mvm);
iwl_mvm_thermal_zone_unregister(mvm);
#endif
mvm->init_status &= ~IWL_MVM_INIT_STATUS_THERMAL_INIT_COMPLETE;
}