| // 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; |
| } |