blob: c63b33f095019f8b03359ee4aa08fa856c19f31f [file] [log] [blame]
// Copyright 2017 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 <cpuid.h>
#include <errno.h>
#include <fcntl.h>
#include <fuchsia/hardware/thermal/c/fidl.h>
#include <fuchsia/kernel/cpp/fidl.h>
#include <inttypes.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/fdio/cpp/caller.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/fdio/watcher.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/trace-provider/provider.h>
#include <lib/trace/event.h>
#include <lib/zx/channel.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/system.h>
static zx_handle_t power_resource;
// degrees Celsius below threshold before we adjust PL value
constexpr float kCoolThresholdCelsius = 5.0f;
class PlatformConfiguration {
public:
static std::unique_ptr<PlatformConfiguration> Create();
zx_status_t SetMinPL1() { return SetPL1Mw(pl1_min_mw_); }
zx_status_t SetMaxPL1() { return SetPL1Mw(pl1_max_mw_); }
bool IsAtMax() { return current_pl1_mw_ == pl1_max_mw_; }
bool IsAtMin() { return current_pl1_mw_ == pl1_min_mw_; }
private:
PlatformConfiguration(uint32_t pl1_min_mw, uint32_t pl1_max_mw)
: pl1_min_mw_(pl1_min_mw), pl1_max_mw_(pl1_max_mw) {}
zx_status_t SetPL1Mw(uint32_t target_mw);
const uint32_t pl1_min_mw_;
const uint32_t pl1_max_mw_;
static constexpr uint32_t kEvePL1MinMw = 2500;
static constexpr uint32_t kEvePL1MaxMw = 7000;
static constexpr uint32_t kAtlasPL1MinMw = 3000;
static constexpr uint32_t kAtlasPL1MaxMw = 7000;
uint32_t current_pl1_mw_;
};
static zx_status_t get_power_resource(zx_handle_t* power_resource_handle) {
zx::channel local, remote;
zx_status_t status = zx::channel::create(0, &local, &remote);
if (status != ZX_OK) {
return status;
}
status = fdio_service_connect(
(std::string("/svc/") + fuchsia::kernel::PowerResource::Name_).c_str(), remote.release());
if (status != ZX_OK) {
FX_PLOGS(ERROR, status) << "Failed to open fuchsia.kernel.PowerResource";
return status;
}
fuchsia::kernel::PowerResource_SyncProxy proxy(std::move(local));
zx::resource power_resource;
status = proxy.Get(&power_resource);
if (status != ZX_OK) {
FX_PLOGS(ERROR, status) << "FIDL error while trying to get power resource";
return status;
}
*power_resource_handle = power_resource.release();
return ZX_OK;
}
std::unique_ptr<PlatformConfiguration> PlatformConfiguration::Create() {
unsigned int a, b, c, d;
unsigned int leaf_num = 0x80000002;
char brand_string[50];
memset(brand_string, 0, sizeof(brand_string));
for (int i = 0; i < 3; i++) {
if (!__get_cpuid(leaf_num + i, &a, &b, &c, &d)) {
return nullptr;
}
memcpy(brand_string + (i * 16), &a, sizeof(uint32_t));
memcpy(brand_string + (i * 16) + 4, &b, sizeof(uint32_t));
memcpy(brand_string + (i * 16) + 8, &c, sizeof(uint32_t));
memcpy(brand_string + (i * 16) + 12, &d, sizeof(uint32_t));
}
// Only run thermd for processors used in Pixelbooks. The PL1 min/max settings are specified by
// the chipset.
if (strstr(brand_string, "i5-7Y57") || strstr(brand_string, "i7-7Y75")) {
return std::unique_ptr<PlatformConfiguration>(
new PlatformConfiguration(kEvePL1MinMw, kEvePL1MaxMw));
} else if (strstr(brand_string, "i5-8200Y") || strstr(brand_string, "i7-8500Y")) {
return std::unique_ptr<PlatformConfiguration>(
new PlatformConfiguration(kAtlasPL1MinMw, kAtlasPL1MaxMw));
}
return nullptr;
}
zx_status_t PlatformConfiguration::SetPL1Mw(uint32_t target_mw) {
zx_system_powerctl_arg_t arg = {
.x86_power_limit =
{
.power_limit = target_mw,
.time_window = 0,
.clamp = 1,
.enable = 1,
},
};
zx_status_t st = zx_system_powerctl(power_resource, ZX_SYSTEM_POWERCTL_X86_SET_PKG_PL1, &arg);
if (st != ZX_OK) {
FX_PLOGS(ERROR, st) << "Failed to set PL1 to " << target_mw;
return st;
}
current_pl1_mw_ = target_mw;
TRACE_COUNTER("thermal", "throttle", 0, "pl1", target_mw);
return ZX_OK;
}
static zx_status_t thermal_device_added(int dirfd, int event, const char* name, void* cookie) {
if (event != WATCH_EVENT_ADD_FILE) {
return ZX_OK;
}
if (!strcmp("000", name)) {
// Device found, terminate watcher
return ZX_ERR_STOP;
} else {
return ZX_OK;
}
}
static void start_trace(void) {
// Create a message loop
static async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
static trace::TraceProviderWithFdio trace_provider(loop.dispatcher());
static bool started = false;
if (!started) {
FX_LOGS(INFO) << "thermd: start trace";
loop.StartThread();
started = true;
}
}
int main(int argc, char** argv) {
auto config = PlatformConfiguration::Create();
if (!config) {
return 0;
}
FX_LOGS(INFO) << "thermd: started";
start_trace();
zx_status_t st = get_power_resource(&power_resource);
if (st != ZX_OK) {
FX_PLOGS(ERROR, st) << "Failed to get power resource";
return -1;
}
zx_nanosleep(zx_deadline_after(ZX_SEC(3)));
int dirfd = open("/dev/class/thermal", O_DIRECTORY | O_RDONLY);
if (dirfd < 0) {
FX_PLOGS(ERROR, errno) << "Failed to open /dev/class/thermal: " << dirfd;
return -1;
}
st = fdio_watch_directory(dirfd, thermal_device_added, ZX_TIME_INFINITE, NULL);
if (st != ZX_ERR_STOP) {
FX_LOGS(ERROR) << "watcher terminating without finding sensors, terminating thermd...";
return -1;
}
// first sensor is ambient sensor
// TODO: come up with a way to detect this is the ambient sensor
fbl::unique_fd fd(open("/dev/class/thermal/000", O_RDWR));
if (fd.get() < 0) {
FX_PLOGS(ERROR, fd.get()) << "Failed to open sensor";
return -1;
}
fdio_cpp::FdioCaller caller(std::move(fd));
zx_status_t status2;
float temp;
st = fuchsia_hardware_thermal_DeviceGetTemperatureCelsius(caller.borrow_channel(), &status2,
&temp);
if (st != ZX_OK || status2 != ZX_OK) {
FX_LOGS(ERROR) << "Failed to get temperature: " << st << " " << status2;
return -1;
}
TRACE_COUNTER("thermal", "temp", 0, "ambient-c", temp);
fuchsia_hardware_thermal_ThermalInfo info;
st = fuchsia_hardware_thermal_DeviceGetInfo(caller.borrow_channel(), &status2, &info);
if (st != ZX_OK || status2 != ZX_OK) {
FX_LOGS(ERROR) << "Failed to get thermal info: %d %d" << st << " " << status2;
return -1;
}
TRACE_COUNTER("thermal", "trip-point", 0, "passive-c", info.passive_temp_celsius, "critical-c",
info.critical_temp_celsius);
zx_handle_t h = ZX_HANDLE_INVALID;
st = fuchsia_hardware_thermal_DeviceGetStateChangeEvent(caller.borrow_channel(), &status2, &h);
if (st != ZX_OK || status2 != ZX_OK) {
FX_LOGS(ERROR) << "Failed to get event: %d %d" << st << " " << status2;
return -1;
}
if (info.max_trip_count == 0) {
FX_LOGS(ERROR) << "Trip points not supported, exiting";
return 0;
}
// Set a trip point
st = fuchsia_hardware_thermal_DeviceSetTripCelsius(caller.borrow_channel(), 0,
info.passive_temp_celsius, &status2);
if (st != ZX_OK || status2 != ZX_OK) {
FX_LOGS(ERROR) << "Failed to set trip point: %d %d" << st << " " << status2;
return -1;
}
// Update info
st = fuchsia_hardware_thermal_DeviceGetInfo(caller.borrow_channel(), &status2, &info);
if (st != ZX_OK || status2 != ZX_OK) {
FX_LOGS(ERROR) << "Failed to get thermal info: %d %d" << st << " " << status2;
return -1;
}
TRACE_COUNTER("thermal", "trip-point", 0, "passive-c", info.passive_temp_celsius, "critical-c",
info.critical_temp_celsius, "active0-c", info.active_trip[0]);
// Set PL1 to the platform maximum.
config->SetMaxPL1();
for (;;) {
zx_signals_t observed = 0;
st = zx_object_wait_one(h, ZX_USER_SIGNAL_0, zx_deadline_after(ZX_SEC(1)), &observed);
if ((st != ZX_OK) && (st != ZX_ERR_TIMED_OUT)) {
FX_PLOGS(ERROR, st) << "Failed to wait on event";
return st;
}
if (observed & ZX_USER_SIGNAL_0) {
st = fuchsia_hardware_thermal_DeviceGetInfo(caller.borrow_channel(), &status2, &info);
if (st != ZX_OK || status2 != ZX_OK) {
FX_LOGS(ERROR) << "Failed to get thermal info: %d %d" << st << " " << status2;
return -1;
}
if (info.state) {
// Decrease power limit
config->SetMinPL1();
st = fuchsia_hardware_thermal_DeviceGetTemperatureCelsius(caller.borrow_channel(), &status2,
&temp);
if (st != ZX_OK || status2 != ZX_OK) {
FX_LOGS(ERROR) << "Failed to get temperature: %d %d" << st << " " << status2;
return -1;
}
} else {
TRACE_COUNTER("thermal", "event", 0, "spurious", temp);
}
}
if (st == ZX_ERR_TIMED_OUT) {
st = fuchsia_hardware_thermal_DeviceGetTemperatureCelsius(caller.borrow_channel(), &status2,
&temp);
if (st != ZX_OK || status2 != ZX_OK) {
FX_LOGS(ERROR) << "Failed to get temperature: %d %d" << st << " " << status2;
return -1;
}
TRACE_COUNTER("thermal", "temp", 0, "ambient-c", temp);
// Increase power limit if the temperature dropped enough
if (temp < info.active_trip[0] - kCoolThresholdCelsius && !config->IsAtMax()) {
// Make sure the state is clear
st = fuchsia_hardware_thermal_DeviceGetInfo(caller.borrow_channel(), &status2, &info);
if (st != ZX_OK || status2 != ZX_OK) {
FX_LOGS(ERROR) << "Failed to get thermal info: %d %d" << st << " " << status2;
return -1;
}
if (!info.state) {
config->SetMaxPL1();
}
}
if (temp > info.active_trip[0] && !config->IsAtMin()) {
// Decrease power limit
config->SetMinPL1();
}
}
}
FX_PLOGS(INFO, st) << "thermd terminating";
return 0;
}