blob: c8087f40d250902d5815596db148ab4d8fad8fa5 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/media/audio/audio_core/thermal_agent.h"
#include <lib/syslog/cpp/macros.h>
#include <unordered_set>
#include <rapidjson/document.h>
#include "src/media/audio/audio_core/audio_device_manager.h"
#include "src/media/audio/audio_core/reporter.h"
namespace media::audio {
namespace {
// Finds the nominal config string for the specified target. Returns no value if the specified
// target could not be found.
std::optional<std::string> FindNominalConfigForTarget(const std::string& target_name,
const DeviceConfig& device_config) {
// For 'special' target names (not effect names), this method must return a string. An empty
// string is fine. The remainder of this method assumes the |target_name| references an effect.
const PipelineConfig::Effect* effect = device_config.FindEffect(target_name);
return effect ? std::optional(effect->effect_config) : std::nullopt;
}
// Constructs a map {target_name: configs_by_thermal_state}, where configs_by_thermal_state
// is a vector of configurations for the target indexed by thermal state.
std::unordered_map<std::string, std::vector<std::string>> PopulateTargetConfigurations(
const ThermalConfig& thermal_config, const DeviceConfig& device_config) {
const auto& entries = thermal_config.entries();
const size_t num_thermal_states = entries.size() + 1;
std::unordered_map<std::string, std::vector<std::string>> result;
Reporter::Singleton().SetNumThermalStates(num_thermal_states);
// "Bad" targets have no nominal configuration. We record them so the name of every such target
// can be logged only once.
std::unordered_set<std::string> bad_targets;
for (size_t i = 0; i < entries.size(); i++) {
const auto& entry = entries[i];
for (const auto& transition : entry.state_transitions()) {
const auto& target_name = transition.target_name();
if (bad_targets.find(target_name) != bad_targets.end()) {
continue;
}
auto configs_it = result.find(target_name);
// This target isn't in target_configurations. If there's no corresponding nominal config,
// record it as a bad target and continue. Otherwise, initialize this target's entry in
// `result`.
if (configs_it == result.end()) {
auto nominal_config = FindNominalConfigForTarget(target_name, device_config);
if (!nominal_config.has_value()) {
bad_targets.insert(target_name);
FX_LOGS(ERROR) << "Thermal config references unknown target '" << target_name << "'.";
continue;
}
configs_it = result.insert({target_name, {}}).first;
auto& configs = configs_it->second;
configs.reserve(num_thermal_states);
configs.push_back(nominal_config.value());
}
// `transition` specifies that this target should change from its previous configuration at
// state `i` to `transition.config()` at state `i+1`. Copy the last element until entry `i`
// is populated, and then copy the new config into position `i+1`.
std::vector<std::string>& configs = configs_it->second;
for (size_t j = configs.size(); j < i + 1; j++) {
configs.push_back(configs.back());
}
configs.push_back(transition.config());
}
}
// Extend the configs for each target to the appropriate length -- any target not present in the
// final state transition will have missing elements.
for (auto& entry : result) {
auto& configs = entry.second;
if (configs.size() < num_thermal_states) {
for (size_t j = configs.size(); j < num_thermal_states + 1; j++) {
configs.push_back(configs.back());
}
}
}
return result;
}
} // namespace
// static
std::unique_ptr<ThermalAgent> ThermalAgent::CreateAndServe(Context* context) {
auto& thermal_config = context->process_config().thermal_config();
if (thermal_config.entries().empty()) {
FX_LOGS(INFO) << "No thermal config found, so we won't start the thermal agent";
return nullptr;
}
return std::make_unique<ThermalAgent>(
context->component_context().svc()->Connect<fuchsia::thermal::Controller>(), thermal_config,
context->process_config().device_config(),
[context](const std::string& target_name, const std::string& config) {
auto promise =
context->device_manager().UpdateEffect(target_name, config, true /* persist */);
context->threading_model().FidlDomain().executor()->schedule_task(
promise.then([target_name, config](
fit::result<void, fuchsia::media::audio::UpdateEffectError>& result) {
if (result.is_error()) {
std::ostringstream err;
if (result.error() == fuchsia::media::audio::UpdateEffectError::NOT_FOUND) {
err << "effect with name " << target_name << " was not found";
} else {
err << "message " << config << " was rejected";
}
FX_LOGS_FIRST_N(ERROR, 10) << "Unable to apply thermal policy: " << err.str();
}
}));
});
}
ThermalAgent::ThermalAgent(fuchsia::thermal::ControllerPtr thermal_controller,
const ThermalConfig& thermal_config, const DeviceConfig& device_config,
SetConfigCallback set_config_callback)
: thermal_controller_(std::move(thermal_controller)),
binding_(this),
set_config_callback_(std::move(set_config_callback)) {
FX_DCHECK(thermal_controller_);
FX_DCHECK(set_config_callback_);
TRACE_DURATION_BEGIN("audio", "ThermalState_0");
if (thermal_config.entries().empty()) {
FX_LOGS(ERROR) << "No thermal config, so we won't start the thermal agent";
thermal_controller_ = nullptr;
return;
}
targets_ = PopulateTargetConfigurations(thermal_config, device_config);
thermal_controller_.set_error_handler([this](zx_status_t status) {
FX_PLOGS(ERROR, status) << "Connection to fuchsia.thermal.Controller failed: ";
thermal_controller_.set_error_handler(nullptr);
thermal_controller_.Unbind();
});
std::vector<fuchsia::thermal::TripPoint> trip_points;
trip_points.reserve(thermal_config.entries().size());
for (const auto& entry : thermal_config.entries()) {
trip_points.push_back(entry.trip_point());
}
thermal_controller_->Subscribe(
binding_.NewBinding(), fuchsia::thermal::ActorType::AUDIO, std::move(trip_points),
[this](fuchsia::thermal::Controller_Subscribe_Result result) {
if (result.is_err()) {
FX_CHECK(result.err() != fuchsia::thermal::Error::INVALID_ARGUMENTS);
FX_LOGS(ERROR) << "fuchsia.thermal.Controller/Subscribe failed";
}
thermal_controller_.set_error_handler(nullptr);
thermal_controller_.Unbind();
});
}
namespace {
std::optional<std::string> ParseThermalConfigComment(std::string& config) {
rapidjson::Document doc;
rapidjson::ParseResult result = doc.Parse(config);
if (!result.IsError() && doc["_comment"].IsString()) {
return doc["_comment"].GetString();
} else {
return std::nullopt;
}
}
} // namespace
// Handle a thermal state change from fuchsia::thermal::Controller.
// After doing the actual work, update our telemetry and invoke the FIDL completion.
void ThermalAgent::SetThermalState(uint32_t state, SetThermalStateCallback callback) {
if (current_state_ == state) {
callback();
FX_LOGS(INFO) << "No thermal state change (was already " << state << ")";
return;
}
TRACE_DURATION_END("audio",
std::string("ThermalState_" + std::to_string(current_state_)).c_str());
TRACE_DURATION_BEGIN("audio", std::string("ThermalState_" + std::to_string(state)).c_str());
for (auto& [target_name, configs_by_state] : targets_) {
FX_CHECK(state < configs_by_state.size());
FX_CHECK(current_state_ < configs_by_state.size());
if (configs_by_state[state] != configs_by_state[current_state_]) {
auto comment = ParseThermalConfigComment(configs_by_state[state]);
FX_LOGS(INFO) << "Set thermal state to " << state << (comment ? " - " + comment.value() : "");
set_config_callback_(target_name, configs_by_state[state]);
}
}
auto previous_state = current_state_;
current_state_ = state;
Reporter::Singleton().SetThermalState(state);
callback();
FX_LOGS(INFO) << "Thermal state change (from " << previous_state << " to " << state
<< ") is complete";
}
} // namespace media::audio