blob: 4614293ca5b352d8e4ccda7416ec256ab836f204 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "pixelstats-uevent"
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/strings.h>
#include <android/frameworks/stats/1.0/IStats.h>
#include <cutils/uevent.h>
#include <hardware/google/pixel/pixelstats/pixelatoms.pb.h>
#include <log/log.h>
#include <pixelstats/UeventListener.h>
#include <unistd.h>
#include <utils/StrongPointer.h>
#include <thread>
using android::sp;
using android::base::ReadFileToString;
using android::base::WriteStringToFile;
using android::frameworks::stats::V1_0::HardwareFailed;
using android::frameworks::stats::V1_0::IStats;
using android::frameworks::stats::V1_0::UsbPortOverheatEvent;
using android::frameworks::stats::V1_0::VendorAtom;
using android::hardware::google::pixel::PixelAtoms::ChargeStats;
using android::hardware::google::pixel::PixelAtoms::VoltageTierStats;
namespace android {
namespace hardware {
namespace google {
namespace pixel {
constexpr int32_t UEVENT_MSG_LEN = 2048; // it's 2048 in all other users.
bool UeventListener::ReadFileToInt(const std::string &path, int *val) {
return ReadFileToInt(path.c_str(), val);
}
bool UeventListener::ReadFileToInt(const char *const path, int *val) {
std::string file_contents;
if (!ReadFileToString(path, &file_contents)) {
ALOGE("Unable to read %s - %s", path, strerror(errno));
return false;
} else if (sscanf(file_contents.c_str(), "%d", val) != 1) {
ALOGE("Unable to convert %s to int - %s", path, strerror(errno));
return false;
}
return true;
}
void UeventListener::ReportMicBrokenOrDegraded(const int mic, const bool isbroken) {
sp<IStats> stats_client = IStats::tryGetService();
if (stats_client) {
HardwareFailed failure = {
.hardwareType = HardwareFailed::HardwareType::MICROPHONE,
.hardwareLocation = mic,
.errorCode = isbroken ? HardwareFailed::HardwareErrorCode::COMPLETE
: HardwareFailed::HardwareErrorCode::DEGRADE};
Return<void> ret = stats_client->reportHardwareFailed(failure);
if (!ret.isOk())
ALOGE("Unable to report physical drop to Stats service");
} else
ALOGE("Unable to connect to Stats service");
}
void UeventListener::ReportMicStatusUevents(const char *devpath, const char *mic_status) {
if (!devpath || !mic_status)
return;
if (!strcmp(devpath, ("DEVPATH=" + kAudioUevent).c_str())) {
std::vector<std::string> value = android::base::Split(mic_status, "=");
bool isbroken;
if (value.size() == 2) {
if (!value[0].compare("MIC_BREAK_STATUS"))
isbroken = true;
else if (!value[0].compare("MIC_DEGRADE_STATUS"))
isbroken = false;
else
return;
if (!value[1].compare("true"))
ReportMicBrokenOrDegraded(0, isbroken);
else {
int mic_status = atoi(value[1].c_str());
if (mic_status > 0 && mic_status <= 7) {
for (int mic_bit = 0; mic_bit < 3; mic_bit++)
if (mic_status & (0x1 << mic_bit))
ReportMicBrokenOrDegraded(mic_bit, isbroken);
} else if (mic_status == 0) {
// mic is ok
return;
} else {
// should not enter here
ALOGE("invalid mic status");
return;
}
}
}
}
}
void UeventListener::ReportUsbPortOverheatEvent(const char *driver) {
UsbPortOverheatEvent event = {};
std::string file_contents;
if (!driver || strcmp(driver, "DRIVER=google,overheat_mitigation")) {
return;
}
ReadFileToInt((kUsbPortOverheatPath + "/plug_temp"), &event.plugTemperatureDeciC);
ReadFileToInt((kUsbPortOverheatPath + "/max_temp"), &event.maxTemperatureDeciC);
ReadFileToInt((kUsbPortOverheatPath + "/trip_time"), &event.timeToOverheat);
ReadFileToInt((kUsbPortOverheatPath + "/hysteresis_time"), &event.timeToHysteresis);
ReadFileToInt((kUsbPortOverheatPath + "/cleared_time"), &event.timeToInactive);
sp<IStats> stats_client = IStats::tryGetService();
if (stats_client) {
stats_client->reportUsbPortOverheatEvent(event);
}
}
void UeventListener::ReportChargeStats(sp<IStats> &stats_client, const char *line) {
std::vector<int> charge_stats_fields = {
ChargeStats::kAdapterTypeFieldNumber, ChargeStats::kAdapterVoltageFieldNumber,
ChargeStats::kAdapterAmperageFieldNumber, ChargeStats::kSsocInFieldNumber,
ChargeStats::kVoltageInFieldNumber, ChargeStats::kSsocOutFieldNumber,
ChargeStats::kVoltageOutFieldNumber};
std::vector<VendorAtom::Value> values(charge_stats_fields.size());
VendorAtom::Value val;
int32_t i = 0, tmp[7] = {0};
ALOGD("ChargeStats: processing %s", line);
if (sscanf(line, "%d,%d,%d, %d,%d,%d,%d", &tmp[0], &tmp[1], &tmp[2], &tmp[3], &tmp[4], &tmp[5],
&tmp[6]) != 7) {
ALOGE("Couldn't process %s", line);
return;
}
for (i = 0; i < charge_stats_fields.size(); i++) {
val.intValue(tmp[i]);
values[charge_stats_fields[i] - kVendorAtomOffset] = val;
}
VendorAtom event = {.reverseDomainName = PixelAtoms::ReverseDomainNames().pixel(),
.atomId = PixelAtoms::Ids::CHARGE_STATS,
.values = values};
Return<void> ret = stats_client->reportVendorAtom(event);
if (!ret.isOk())
ALOGE("Unable to report ChargeStats to Stats service");
}
void UeventListener::ReportVoltageTierStats(sp<IStats> &stats_client, const char *line) {
std::vector<int> voltage_tier_stats_fields = {VoltageTierStats::kVoltageTierFieldNumber,
VoltageTierStats::kSocInFieldNumber,
VoltageTierStats::kCcInFieldNumber,
VoltageTierStats::kTempInFieldNumber,
VoltageTierStats::kTimeFastSecsFieldNumber,
VoltageTierStats::kTimeTaperSecsFieldNumber,
VoltageTierStats::kTimeOtherSecsFieldNumber,
VoltageTierStats::kTempMinFieldNumber,
VoltageTierStats::kTempAvgFieldNumber,
VoltageTierStats::kTempMaxFieldNumber,
VoltageTierStats::kIbattMinFieldNumber,
VoltageTierStats::kIbattAvgFieldNumber,
VoltageTierStats::kIbattMaxFieldNumber,
VoltageTierStats::kIclMinFieldNumber,
VoltageTierStats::kIclAvgFieldNumber,
VoltageTierStats::kIclMaxFieldNumber};
std::vector<VendorAtom::Value> values(voltage_tier_stats_fields.size());
VendorAtom::Value val;
float ssoc_tmp;
int32_t i = 0, tmp[15] = {0};
ALOGD("VoltageTierStats: processing %s", line);
if (sscanf(line, "%d, %f,%d,%d, %d,%d,%d, %d,%d,%d, %d,%d,%d, %d,%d,%d", &tmp[0], &ssoc_tmp,
&tmp[1], &tmp[2], &tmp[3], &tmp[4], &tmp[5], &tmp[6], &tmp[7], &tmp[8], &tmp[9],
&tmp[10], &tmp[11], &tmp[12], &tmp[13], &tmp[14]) != 16) {
ALOGE("Couldn't process %s", line);
return;
}
val.intValue(tmp[0]);
values[voltage_tier_stats_fields[0] - kVendorAtomOffset] = val;
val.floatValue(ssoc_tmp);
values[voltage_tier_stats_fields[1] - kVendorAtomOffset] = val;
for (i = 2; i < voltage_tier_stats_fields.size(); i++) {
val.intValue(tmp[i - 1]);
values[voltage_tier_stats_fields[i] - kVendorAtomOffset] = val;
}
VendorAtom event = {.reverseDomainName = PixelAtoms::ReverseDomainNames().pixel(),
.atomId = PixelAtoms::Ids::VOLTAGE_TIER_STATS,
.values = values};
Return<void> ret = stats_client->reportVendorAtom(event);
if (!ret.isOk())
ALOGE("Unable to report VoltageTierStats to Stats service");
}
void UeventListener::ReportChargeMetricsEvent(const char *driver) {
if (!driver || strcmp(driver, "DRIVER=google,battery")) {
return;
}
std::string file_contents, line;
std::istringstream ss;
if (!ReadFileToString(kChargeMetricsPath.c_str(), &file_contents)) {
ALOGE("Unable to read %s - %s", kChargeMetricsPath.c_str(), strerror(errno));
return;
}
ss.str(file_contents);
if (!std::getline(ss, line)) {
ALOGE("Unable to read first line");
return;
}
if (!WriteStringToFile(kChargeMetricsPath.c_str(), std::to_string(0))) {
ALOGE("Couldn't clear %s", kChargeMetricsPath.c_str());
}
sp<IStats> stats_client = IStats::tryGetService();
if (!stats_client) {
ALOGE("Couldn't connect to IStats service");
return;
}
ReportChargeStats(stats_client, line.c_str());
while (std::getline(ss, line)) {
ReportVoltageTierStats(stats_client, line.c_str());
}
}
bool UeventListener::ProcessUevent() {
char msg[UEVENT_MSG_LEN + 2];
char *cp;
const char *action, *power_supply_typec_mode, *driver, *product;
const char *mic_break_status, *mic_degrade_status;
const char *devpath;
int n;
if (uevent_fd_ < 0) {
uevent_fd_ = uevent_open_socket(64 * 1024, true);
if (uevent_fd_ < 0) {
ALOGE("uevent_init: uevent_open_socket failed\n");
return false;
}
}
n = uevent_kernel_multicast_recv(uevent_fd_, msg, UEVENT_MSG_LEN);
if (n <= 0 || n >= UEVENT_MSG_LEN)
return false;
// Ensure double-null termination of msg.
msg[n] = '\0';
msg[n + 1] = '\0';
action = power_supply_typec_mode = driver = product = NULL;
mic_break_status = mic_degrade_status = devpath = NULL;
/**
* msg is a sequence of null-terminated strings.
* Iterate through and record positions of string/value pairs of interest.
* Double null indicates end of the message. (enforced above).
*/
cp = msg;
while (*cp) {
if (!strncmp(cp, "ACTION=", strlen("ACTION="))) {
action = cp;
} else if (!strncmp(cp, "POWER_SUPPLY_TYPEC_MODE=", strlen("POWER_SUPPLY_TYPEC_MODE="))) {
power_supply_typec_mode = cp;
} else if (!strncmp(cp, "DRIVER=", strlen("DRIVER="))) {
driver = cp;
} else if (!strncmp(cp, "PRODUCT=", strlen("PRODUCT="))) {
product = cp;
} else if (!strncmp(cp, "MIC_BREAK_STATUS=", strlen("MIC_BREAK_STATUS="))) {
mic_break_status = cp;
} else if (!strncmp(cp, "MIC_DEGRADE_STATUS=", strlen("MIC_DEGRADE_STATUS="))) {
mic_degrade_status = cp;
} else if (!strncmp(cp, "DEVPATH=", strlen("DEVPATH="))) {
devpath = cp;
}
/* advance to after the next \0 */
while (*cp++) {
}
}
/* Process the strings recorded. */
ReportMicStatusUevents(devpath, mic_break_status);
ReportMicStatusUevents(devpath, mic_degrade_status);
ReportUsbPortOverheatEvent(driver);
ReportChargeMetricsEvent(driver);
return true;
}
UeventListener::UeventListener(const std::string audio_uevent, const std::string overheat_path,
const std::string charge_metrics_path)
: kAudioUevent(audio_uevent),
kUsbPortOverheatPath(overheat_path),
kChargeMetricsPath(charge_metrics_path),
uevent_fd_(-1) {}
/* Thread function to continuously monitor uevents.
* Exit after kMaxConsecutiveErrors to prevent spinning. */
void UeventListener::ListenForever() {
constexpr int kMaxConsecutiveErrors = 10;
int consecutive_errors = 0;
while (1) {
if (ProcessUevent()) {
consecutive_errors = 0;
} else {
if (++consecutive_errors >= kMaxConsecutiveErrors) {
ALOGE("Too many ProcessUevent errors; exiting UeventListener.");
return;
}
}
}
}
} // namespace pixel
} // namespace google
} // namespace hardware
} // namespace android