blob: 52dc74c4b7d8f7e25c7e6c13d2130ec9ae4c63ea [file] [log] [blame]
// Copyright 2019 The Chromium OS 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 "debugd/src/cros_healthd_tool.h"
#include <map>
#include <string>
#include <vector>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/process/launch.h>
#include <base/strings/stringprintf.h>
#include <base/strings/string_util.h>
#include "debugd/src/error_utils.h"
#include "debugd/src/process_with_output.h"
namespace debugd {
namespace {
constexpr char kErrorPath[] = "org.chromium.debugd.CrosHealthdToolError";
constexpr char kSandboxDirPath[] = "/usr/share/policy/";
constexpr char kRunAs[] = "healthd_ec";
// The ectool i2cread command below follows the format:
// ectool i2cread [NUM_BITS] [PORT] [BATTERY_I2C_ADDRESS (addr8)] [OFFSET]
// Note that [NUM_BITS] can either be 8 or 16.
constexpr char kEctoolBinary[] = "/usr/sbin/ectool";
constexpr char kI2cReadCommand[] = "i2cread";
// The specification for smart battery can be found at:
// http://sbs-forum.org/specs/sbdat110.pdf. This states
// that both the temperature and manufacture_date commands
// use the "Read Word" SMBus Protocol, which is 16 bits.
constexpr char kNumBits[] = "16";
// The i2c address is well defined at:
// src/platform/ec/include/battery_smart.h
constexpr char kBatteryI2cAddress[] = "0x16";
// The only i2cread argument different across models is the port.
const std::map<std::string, std::string> kModelToPort = {
{"sona", "2"},
{"careena", "0"},
{"dratini", "5"},
{"dorp", "0"},
};
const std::map<std::string, std::string> kMetricNameToOffset = {
{"temperature_smart", "0x08"},
{"manufacture_date_smart", "0x1b"},
};
// The ectool command used to collect fan speed in RPM.
// Returns the ectool policy file corresponding to the provided
// |ectool_command|.
std::string GetEctoolPolicyFile(const std::string& ectool_command) {
return base::StringPrintf("ectool_%s-seccomp.policy", ectool_command.c_str());
}
// Runs ectool with the provided |ectool_args| in a sandboxed process. Returns
// true on success.
bool RunEctoolWithArgs(brillo::ErrorPtr* error,
const base::FilePath& seccomp_policy_path,
const std::vector<std::string> ectool_args,
std::string* output) {
if (!base::PathExists(seccomp_policy_path)) {
DEBUGD_ADD_ERROR(error, kErrorPath,
"Sandbox info is missing for this architecture.");
return false;
}
// Minijail setup for ectool.
std::vector<std::string> parsed_args;
parsed_args.push_back("-c");
parsed_args.push_back("cap_sys_rawio=e");
parsed_args.push_back("-b");
parsed_args.push_back("/dev/cros_ec");
ProcessWithOutput process;
process.SandboxAs(kRunAs, kRunAs);
process.SetSeccompFilterPolicyFile(seccomp_policy_path.MaybeAsASCII());
process.InheritUsergroups();
if (!process.Init(parsed_args)) {
DEBUGD_ADD_ERROR(error, kErrorPath, "Process initialization failure.");
return false;
}
process.AddArg(kEctoolBinary);
for (const auto& arg : ectool_args)
process.AddArg(arg);
if (process.Run() != EXIT_SUCCESS) {
DEBUGD_ADD_ERROR(error, kErrorPath, "Failed to run process.");
return false;
}
if (!process.GetOutput(output)) {
DEBUGD_ADD_ERROR(error, kErrorPath, "Failed to get output from process.");
return false;
}
return true;
}
} // namespace
// Note that this is a short-term solution to retrieving battery metrics.
// A long term solution is being discussed at: crbug.com/1047277.
bool CrosHealthdTool::CollectSmartBatteryMetric(brillo::ErrorPtr* error,
const std::string& metric_name,
std::string* output) {
std::string model_name;
if (!base::GetAppOutputAndError({"cros_config", "/", "name"}, &model_name)) {
DEBUGD_ADD_ERROR(error, kErrorPath,
base::StringPrintf("Failed to run cros_config: %s",
model_name.c_str()));
return false;
}
auto it = kModelToPort.find(model_name);
if (it == kModelToPort.end()) {
DEBUGD_ADD_ERROR(
error, kErrorPath,
base::StringPrintf("Failed to find port for model: %s and metric: %s",
model_name.c_str(), metric_name.c_str()));
return false;
}
const std::string port_number = it->second;
auto metric_name_it = kMetricNameToOffset.find(metric_name);
if (metric_name_it == kMetricNameToOffset.end()) {
DEBUGD_ADD_ERROR(
error, kErrorPath,
base::StringPrintf("Failed to find offset for model: %s and metric: %s",
model_name.c_str(), metric_name.c_str()));
return false;
}
const std::string offset = metric_name_it->second;
std::vector<std::string> ectool_args = {
kI2cReadCommand, kNumBits, port_number, kBatteryI2cAddress, offset};
const auto seccomp_policy_path =
base::FilePath(kSandboxDirPath)
.Append(GetEctoolPolicyFile(kI2cReadCommand));
if (!RunEctoolWithArgs(error, seccomp_policy_path, ectool_args, output))
return false;
return true;
}
} // namespace debugd