blob: f4f5818ec279ed93d5b9f3b3e9e30b7aa2fd2a9b [file] [log] [blame]
// Copyright 2018 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 "garnet/bin/scpi/app.h"
#include <ddk/protocol/scpi.h>
#include <fuchsia/sysinfo/c/fidl.h>
#include <lib/fdio/util.h>
#include <lib/fdio/watcher.h>
#include <stdio.h>
#include <zircon/device/thermal.h>
#include <zircon/status.h>
#include <zircon/syscalls/object.h>
#include "lib/component/cpp/startup_context.h"
namespace scpi {
static const char kThermalDir[] = "/dev/class/thermal";
App::App() : App(component::StartupContext::CreateFromStartupInfo()) {}
App::App(std::unique_ptr<component::StartupContext> context)
: context_(std::move(context)) {}
App::~App() {}
zx::handle App::GetRootResource() {
const int fd = open("/dev/misc/sysinfo", O_RDWR);
if (fd == 0)
return {};
zx::channel channel;
zx_status_t status =
fdio_get_service_handle(fd, channel.reset_and_get_address());
if (status != ZX_OK) {
return {};
}
zx_handle_t root_resource;
zx_status_t fidl_status = fuchsia_sysinfo_DeviceGetRootResource(
channel.get(), &status, &root_resource);
if (fidl_status != ZX_OK || status != ZX_OK)
return {};
return zx::handle(root_resource);
}
size_t App::ReadCpuCount(const zx::handle& root_resource) {
size_t actual, available;
zx_status_t err = root_resource.get_info(ZX_INFO_CPU_STATS, nullptr, 0,
&actual, &available);
if (err != ZX_OK) {
return 0;
}
return available;
}
bool App::ReadCpuStats() {
size_t actual, available;
zx_status_t err = root_resource_handle_.get_info(
ZX_INFO_CPU_STATS, &cpu_stats_[0], num_cores_ * sizeof(zx_info_cpu_stats),
&actual, &available);
return (err == ZX_OK);
}
bool App::ReadMemStats() {
zx_status_t err =
root_resource_handle_.get_info(ZX_INFO_KMEM_STATS, &mem_stats_,
sizeof(zx_info_kmem_stats_t), NULL, NULL);
return (err == ZX_OK);
}
zx_status_t App::Start() {
fxl::UniqueFD dirfd(open(kThermalDir, O_DIRECTORY | O_RDONLY));
if (!dirfd.is_valid()) {
return ZX_ERR_NOT_DIR;
}
auto DeviceAdded = [](int dirfd, int event, const char* name, void* cookie) {
if (event != WATCH_EVENT_ADD_FILE) {
return ZX_OK;
}
if (!strcmp("000", name)) {
return ZX_ERR_STOP;
} else {
return ZX_OK;
}
};
zx_status_t status =
fdio_watch_directory(dirfd.get(), DeviceAdded, ZX_TIME_INFINITE, NULL);
if (status != ZX_ERR_STOP) {
return ZX_ERR_NOT_FOUND;
}
fd_.reset(open("/dev/class/thermal/000", O_RDWR));
if (!fd_.is_valid()) {
FXL_LOG(ERROR) << "Failed to open sensor " << errno;
return ZX_ERR_UNAVAILABLE;
}
root_resource_handle_ = GetRootResource();
num_cores_ = ReadCpuCount(root_resource_handle_);
cpu_stats_.reserve(num_cores_);
last_cpu_stats_.reserve(num_cores_);
context_->outgoing().AddPublicService(bindings_.GetHandler(this));
return ZX_OK;
}
void App::GetDvfsInfo(const uint32_t power_domain,
GetDvfsInfoCallback callback) {
scpi_opp_t opps;
auto result = fidl::VectorPtr<fuchsia::scpi::DvfsOpp>::New(0);
size_t rc = ioctl_thermal_get_dvfs_info(fd_.get(), &power_domain, &opps);
if (rc != sizeof(opps)) {
fprintf(stderr, "ERROR: Failed to get thermal info: %zd\n", rc);
callback(fuchsia::scpi::Status::ERR_DVFS_INFO, std::move(result));
return;
}
for (uint32_t i = 0; i < opps.count; i++) {
fuchsia::scpi::DvfsOpp opp;
opp.freq_hz = opps.opp[i].freq_hz;
opp.volt_mv = opps.opp[i].volt_mv;
result.push_back(std::move(opp));
}
callback(fuchsia::scpi::Status::OK, std::move(result));
}
void App::GetSystemStatus(GetSystemStatusCallback callback) {
fuchsia::scpi::SystemStatus info;
uint32_t power_domain = BIG_CLUSTER_POWER_DOMAIN;
size_t rc = ioctl_thermal_get_dvfs_opp(fd_.get(), &power_domain,
&info.big_cluster_op_index);
if (rc != sizeof(uint32_t)) {
fprintf(stderr, "ERROR: Failed to get dvfs opp idx: %zd\n", rc);
callback(fuchsia::scpi::Status::ERR_DVFS_OPP_IDX, std::move(info));
return;
}
power_domain = LITTLE_CLUSTER_POWER_DOMAIN;
rc = ioctl_thermal_get_dvfs_opp(fd_.get(), &power_domain,
&info.small_cluster_op_index);
if (rc != sizeof(uint32_t)) {
fprintf(stderr, "ERROR: Failed to get dvfs opp idx: %zd\n", rc);
callback(fuchsia::scpi::Status::ERR_DVFS_OPP_IDX, std::move(info));
return;
}
rc = ioctl_thermal_get_temperature(fd_.get(), &info.temperature);
if (rc != sizeof(uint32_t)) {
fprintf(stderr, "ERROR: Failed to get current temperature: %zd\n", rc);
callback(fuchsia::scpi::Status::ERR_TEMPERATURE, std::move(info));
return;
}
rc = ioctl_thermal_get_fan_level(fd_.get(), &info.fan_level);
if (rc != sizeof(uint32_t)) {
fprintf(stderr, "ERROR: Failed to get fan level: %zd\n", rc);
callback(fuchsia::scpi::Status::ERR_FAN_LEVEL, std::move(info));
return;
}
zx_time_t delay = ZX_SEC(1);
if (!ReadCpuStats()) {
fprintf(stderr, "ERROR: Failed to get cpu_stats_ \n");
callback(fuchsia::scpi::Status::ERR_CPU_STATS, std::move(info));
return;
}
last_cpu_stats_.swap(cpu_stats_);
sleep(1);
if (!ReadCpuStats()) {
fprintf(stderr, "ERROR: Failed to get cpu_stats_ \n");
callback(fuchsia::scpi::Status::ERR_CPU_STATS, std::move(info));
return;
}
zx_time_t idle_time, busy_time;
double busypercent_sum = 0;
for (size_t i = 0; i < num_cores_; i++) {
idle_time = cpu_stats_[i].idle_time - last_cpu_stats_[i].idle_time;
busy_time = delay - (idle_time > delay ? delay : idle_time);
double busypercent = (busy_time * 100) / (double)delay;
busypercent_sum += busypercent;
}
info.cpu_utilization = busypercent_sum / num_cores_;
if (!ReadMemStats()) {
fprintf(stderr, "ERROR: Failed to get mem_stats_ \n");
callback(fuchsia::scpi::Status::ERR_MEM_STATS, std::move(info));
return;
}
info.memory_utilization = ((mem_stats_.total_bytes - mem_stats_.free_bytes) *
100 / mem_stats_.total_bytes);
callback(fuchsia::scpi::Status::OK, std::move(info));
}
} // namespace scpi