/* * 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. */ /* If you are watching for a new uevent, uncomment the following define. * After flashing your test build, run: * adb root && adb shell * stop vendor.pixelstats_vendor * touch /data/local/tmp/uevents * /vendor/bin/pixelstats-vendor & * * then trigger any events. * If you leave adb connected, you can watch them with * tail -f /data/local/tmp/uevents * * Once you are done, * * adb pull /data/local/tmp/uevents * adb rm /data/local/tmp/uevents * adb reboot * * provide this log in the bug as support for your feature. */ // #define LOG_UEVENTS_TO_FILE_ONLY_FOR_DEVEL "/data/local/tmp/uevents" #define LOG_TAG "pixelstats-uevent" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace android { namespace hardware { namespace google { namespace pixel { using aidl::android::frameworks::stats::VendorAtom; using aidl::android::frameworks::stats::VendorAtomValue; using android::sp; using android::base::ReadFileToString; using android::base::WriteStringToFile; using android::hardware::google::pixel::PixelAtoms::GpuEvent; using android::hardware::google::pixel::PixelAtoms::PdVidPid; using android::hardware::google::pixel::PixelAtoms::ThermalSensorAbnormalityDetected; using android::hardware::google::pixel::PixelAtoms::VendorHardwareFailed; using android::hardware::google::pixel::PixelAtoms::VendorUsbPortOverheat; constexpr int32_t UEVENT_MSG_LEN = 2048; // it's 2048 in all other users. constexpr int32_t PRODUCT_TYPE_OFFSET = 23; constexpr int32_t PRODUCT_TYPE_MASK = 7; constexpr int32_t PRODUCT_TYPE_CHARGER = 3; constexpr int32_t VID_MASK = 0xffff; constexpr int32_t VID_GOOGLE = 0x18d1; constexpr int32_t PID_OFFSET = 2; constexpr int32_t PID_LENGTH = 4; constexpr uint32_t PID_P30 = 0x4f05; constexpr const char *THERMAL_ABNORMAL_INFO_EQ = "THERMAL_ABNORMAL_INFO="; constexpr const char *THERMAL_ABNORMAL_TYPE_EQ = "THERMAL_ABNORMAL_TYPE="; 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 std::shared_ptr &stats_client, const int mic, const bool isbroken) { VendorHardwareFailed failure; failure.set_hardware_type(VendorHardwareFailed::HARDWARE_FAILED_MICROPHONE); failure.set_hardware_location(mic); failure.set_failure_code(isbroken ? VendorHardwareFailed::COMPLETE : VendorHardwareFailed::DEGRADE); reportHardwareFailed(stats_client, failure); } void UeventListener::ReportMicStatusUevents(const std::shared_ptr &stats_client, const char *devpath, const char *mic_status) { if (!devpath || !mic_status) return; if (!strcmp(devpath, ("DEVPATH=" + kAudioUevent).c_str())) { std::vector 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(stats_client, 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(stats_client, 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 std::shared_ptr &stats_client, const char *driver) { if (!driver || strcmp(driver, "DRIVER=google,overheat_mitigation")) { return; } int32_t plug_temperature_deci_c = 0; int32_t max_temperature_deci_c = 0; int32_t time_to_overheat_secs = 0; int32_t time_to_hysteresis_secs = 0; int32_t time_to_inactive_secs = 0; // TODO(achant b/182941868): test return value and skip reporting in case of an error ReadFileToInt((kUsbPortOverheatPath + "/plug_temp"), &plug_temperature_deci_c); ReadFileToInt((kUsbPortOverheatPath + "/max_temp"), &max_temperature_deci_c); ReadFileToInt((kUsbPortOverheatPath + "/trip_time"), &time_to_overheat_secs); ReadFileToInt((kUsbPortOverheatPath + "/hysteresis_time"), &time_to_hysteresis_secs); ReadFileToInt((kUsbPortOverheatPath + "/cleared_time"), &time_to_inactive_secs); VendorUsbPortOverheat overheat_info; overheat_info.set_plug_temperature_deci_c(plug_temperature_deci_c); overheat_info.set_max_temperature_deci_c(max_temperature_deci_c); overheat_info.set_time_to_overheat_secs(time_to_overheat_secs); overheat_info.set_time_to_hysteresis_secs(time_to_hysteresis_secs); overheat_info.set_time_to_inactive_secs(time_to_inactive_secs); reportUsbPortOverheat(stats_client, overheat_info); } void UeventListener::ReportChargeMetricsEvent(const std::shared_ptr &stats_client, const char *driver) { if (!driver || strcmp(driver, "DRIVER=google,battery")) { return; } charge_stats_reporter_.checkAndReport(stats_client, kChargeMetricsPath); } void UeventListener::ReportFGMetricsEvent(const std::shared_ptr &stats_client, const char *driver) { if (!driver || (strcmp(driver, "DRIVER=max77779-fg") && strcmp(driver, "DRIVER=maxfg") && strcmp(driver, "DRIVER=max1720x"))) return; battery_fg_reporter_.checkAndReportFwUpdate(stats_client, kFwUpdatePath); battery_fg_reporter_.checkAndReportFGAbnormality(stats_client, kFGAbnlPath); } /** * Report raw battery capacity, system battery capacity and associated * battery capacity curves. This data is collected to verify the filter * applied on the battery capacity. This will allow debugging of issues * ranging from incorrect fuel gauge hardware calculations to issues * with the software reported battery capacity. * * The data is retrieved by parsing the battery power supply's ssoc_details. * * This atom logs data in 5 potential events: * 1. When a device is connected * 2. When a device is disconnected * 3. When a device has reached a full charge (from the UI's perspective) * 4. When there is a >= 2 percent skip in the UI reported SOC * 5. When there is a difference of >= 4 percent between the raw hardware * battery capacity and the system reported battery capacity. */ void UeventListener::ReportBatteryCapacityFGEvent(const std::shared_ptr &stats_client, const char *subsystem) { if (!subsystem || strcmp(subsystem, "SUBSYSTEM=power_supply")) { return; } // Indicates an implicit disable of the battery capacity reporting if (kBatterySSOCPath.empty()) { return; } battery_capacity_reporter_.checkAndReport(stats_client, kBatterySSOCPath); } void UeventListener::ReportTypeCPartnerId(const std::shared_ptr &stats_client) { std::string file_contents_vid, file_contents_pid; uint32_t pid, vid; if (!ReadFileToString(kTypeCPartnerVidPath.c_str(), &file_contents_vid)) { ALOGE("Unable to read %s - %s", kTypeCPartnerVidPath.c_str(), strerror(errno)); return; } if (sscanf(file_contents_vid.c_str(), "%x", &vid) != 1) { ALOGE("Unable to parse vid %s from file %s to int.", file_contents_vid.c_str(), kTypeCPartnerVidPath.c_str()); return; } if (!ReadFileToString(kTypeCPartnerPidPath.c_str(), &file_contents_pid)) { ALOGE("Unable to read %s - %s", kTypeCPartnerPidPath.c_str(), strerror(errno)); return; } if (sscanf(file_contents_pid.substr(PID_OFFSET, PID_LENGTH).c_str(), "%x", &pid) != 1) { ALOGE("Unable to parse pid %s from file %s to int.", file_contents_pid.substr(PID_OFFSET, PID_LENGTH).c_str(), kTypeCPartnerPidPath.c_str()); return; } // Upload data only for Google VID if ((VID_MASK & vid) != VID_GOOGLE) { return; } // Upload data only for chargers unless for P30 PID where the product type // isn't set to charger. if ((((vid >> PRODUCT_TYPE_OFFSET) & PRODUCT_TYPE_MASK) != PRODUCT_TYPE_CHARGER) && (pid != PID_P30)) { return; } std::vector values(2); VendorAtomValue tmp; tmp.set(vid & VID_MASK); values[PdVidPid::kVidFieldNumber - kVendorAtomOffset] = tmp; tmp.set(pid); values[PdVidPid::kPidFieldNumber - kVendorAtomOffset] = tmp; // Send vendor atom to IStats HAL VendorAtom event = {.reverseDomainName = "", .atomId = PixelAtoms::Atom::kPdVidPid, .values = std::move(values)}; const ndk::ScopedAStatus ret = stats_client->reportVendorAtom(event); if (!ret.isOk()) { ALOGE("Unable to report PD VID/PID to Stats service"); } } void UeventListener::ReportGpuEvent(const std::shared_ptr &stats_client, const char *driver, const char *gpu_event_type, const char *gpu_event_info) { if (!stats_client || !driver || strncmp(driver, "DRIVER=mali", strlen("DRIVER=mali")) || !gpu_event_type || !gpu_event_info) return; std::vector type = android::base::Split(gpu_event_type, "="); std::vector info = android::base::Split(gpu_event_info, "="); if (type.size() != 2 || info.size() != 2) return; if (type[0] != "GPU_UEVENT_TYPE" || info[0] != "GPU_UEVENT_INFO") return; auto event_type = kGpuEventTypeStrToEnum.find(type[1]); auto event_info = kGpuEventInfoStrToEnum.find(info[1]); if (event_type == kGpuEventTypeStrToEnum.end() || event_info == kGpuEventInfoStrToEnum.end()) return; VendorAtom event = {.reverseDomainName = "", .atomId = PixelAtoms::Atom::kGpuEvent, .values = {event_type->second, event_info->second}}; const ndk::ScopedAStatus ret = stats_client->reportVendorAtom(event); if (!ret.isOk()) ALOGE("Unable to report GPU event."); } /** * Report thermal abnormal event. * The data is sent as uevent environment parameters: * 1. THERMAL_ABNORMAL_TYPE={type} * 2. THERMAL_ABNORMAL_INFO=Name:{name},Val:{val} * This atom logs data in 3 potential events: * 1. thermistor or tj temperature reading stuck * 2. thermistor or tj showing very high temperature reading * 3. thermistor or tj showing very low temperature reading */ void UeventListener::ReportThermalAbnormalEvent(const std::shared_ptr &stats_client, const char *devpath, const char *thermal_abnormal_event_type, const char *thermal_abnormal_event_info) { if (!stats_client || !devpath || strncmp(devpath, "DEVPATH=/module/pixel_metrics", strlen("DEVPATH=/module/pixel_metrics")) || !thermal_abnormal_event_type || !thermal_abnormal_event_info) return; ALOGD("Thermal Abnormal Type: %s, Thermal Abnormal Info: %s", thermal_abnormal_event_type, thermal_abnormal_event_info); std::vector type_msg = android::base::Split(thermal_abnormal_event_type, "="); std::vector info_msg = android::base::Split(thermal_abnormal_event_info, "="); if (type_msg.size() != 2 || info_msg.size() != 2) { ALOGE("Invalid msg size for thermal abnormal with type(%zu) and info(%zu)", type_msg.size(), info_msg.size()); return; } if (type_msg[0] != "THERMAL_ABNORMAL_TYPE" || info_msg[0] != "THERMAL_ABNORMAL_INFO") { ALOGE("Invalid msg prefix for thermal abnormal with type(%s) and info(%s)", type_msg[0].c_str(), info_msg[0].c_str()); return; } auto abnormality_type = kThermalAbnormalityTypeStrToEnum.find(type_msg[1]); if (abnormality_type == kThermalAbnormalityTypeStrToEnum.end()) { ALOGE("Unknown thermal abnormal event type %s", type_msg[1].c_str()); return; } std::vector info_list = android::base::Split(info_msg[1], ","); if (info_list.size() != 2) { ALOGE("Thermal abnormal info(%s) split size %zu != 2", info_msg[1].c_str(), info_list.size()); return; } const auto &name_msg = info_list[0], val_msg = info_list[1]; if (!android::base::StartsWith(name_msg, "name:") || !android::base::StartsWith(val_msg, "val:")) { ALOGE("Invalid prefix for thermal abnormal info name(%s), val(%s)", name_msg.c_str(), val_msg.c_str()); return; } auto name_start_pos = std::strlen("name:"); auto name = name_msg.substr(name_start_pos); if (name.length() > THERMAL_NAME_LENGTH) { ALOGE("Invalid sensor name %s with length %zu > %d", name.c_str(), name.length(), THERMAL_NAME_LENGTH); return; } auto val_start_pos = std::strlen("val:"); auto val_str = val_msg.substr(val_start_pos); int val; if (sscanf(val_str.c_str(), "%d", &val) != 1) { ALOGE("Invalid value for thermal abnormal info: %s", val_str.c_str()); return; } ALOGI("Reporting Thermal Abnormal event of type: %s(%d) for %s with val: %d", abnormality_type->first.c_str(), abnormality_type->second, name.c_str(), val); VendorAtom event = {.reverseDomainName = "", .atomId = PixelAtoms::Atom::kThermalSensorAbnormalityDetected, .values = {abnormality_type->second, name, val}}; const ndk::ScopedAStatus ret = stats_client->reportVendorAtom(event); if (!ret.isOk()) ALOGE("Unable to report Thermal Abnormal event."); } bool UeventListener::ProcessUevent() { char msg[UEVENT_MSG_LEN + 2]; char *cp; const char *driver, *product, *subsystem; const char *mic_break_status, *mic_degrade_status; const char *devpath; bool collect_partner_id = false; const char *gpu_event_type = nullptr, *gpu_event_info = nullptr; const char *thermal_abnormal_event_type = nullptr, *thermal_abnormal_event_info = nullptr; 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; } } #ifdef LOG_UEVENTS_TO_FILE_ONLY_FOR_DEVEL if (log_fd_ < 0) { /* Intentionally no O_CREAT so no logging will happen * unless the user intentionally 'touch's the file. */ log_fd_ = open(LOG_UEVENTS_TO_FILE_ONLY_FOR_DEVEL, O_WRONLY); } #endif 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'; driver = product = subsystem = 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 (log_fd_ > 0) { write(log_fd_, cp, strlen(cp)); write(log_fd_, "\n", 1); } 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; } else if (!strncmp(cp, "SUBSYSTEM=", strlen("SUBSYSTEM="))) { subsystem = cp; } else if (!strncmp(cp, kTypeCPartnerUevent.c_str(), kTypeCPartnerUevent.size())) { collect_partner_id = true; } else if (!strncmp(cp, "GPU_UEVENT_TYPE=", strlen("GPU_UEVENT_TYPE="))) { gpu_event_type = cp; } else if (!strncmp(cp, "GPU_UEVENT_INFO=", strlen("GPU_UEVENT_INFO="))) { gpu_event_info = cp; } else if (!strncmp(cp, THERMAL_ABNORMAL_TYPE_EQ, strlen(THERMAL_ABNORMAL_TYPE_EQ))) { thermal_abnormal_event_type = cp; } else if (!strncmp(cp, THERMAL_ABNORMAL_INFO_EQ, strlen(THERMAL_ABNORMAL_INFO_EQ))) { thermal_abnormal_event_info = cp; } /* advance to after the next \0 */ while (*cp++) { } } std::shared_ptr stats_client = getStatsService(); if (!stats_client) { ALOGE("Unable to get Stats service instance."); } else { /* Process the strings recorded. */ ReportMicStatusUevents(stats_client, devpath, mic_break_status); ReportMicStatusUevents(stats_client, devpath, mic_degrade_status); ReportUsbPortOverheatEvent(stats_client, driver); ReportChargeMetricsEvent(stats_client, driver); ReportBatteryCapacityFGEvent(stats_client, subsystem); if (collect_partner_id) { ReportTypeCPartnerId(stats_client); } ReportGpuEvent(stats_client, driver, gpu_event_type, gpu_event_info); ReportThermalAbnormalEvent(stats_client, devpath, thermal_abnormal_event_type, thermal_abnormal_event_info); ReportFGMetricsEvent(stats_client, driver); } if (log_fd_ > 0) { write(log_fd_, "\n", 1); } return true; } UeventListener::UeventListener(const std::string audio_uevent, const std::string ssoc_details_path, const std::string overheat_path, const std::string charge_metrics_path, const std::string typec_partner_vid_path, const std::string typec_partner_pid_path, const std::string fw_update_path, const std::vector fg_abnl_path) : kAudioUevent(audio_uevent), kBatterySSOCPath(ssoc_details_path), kUsbPortOverheatPath(overheat_path), kChargeMetricsPath(charge_metrics_path), kTypeCPartnerUevent(typec_partner_uevent_default), kTypeCPartnerVidPath(typec_partner_vid_path), kTypeCPartnerPidPath(typec_partner_pid_path), kFwUpdatePath(fw_update_path), kFGAbnlPath(fg_abnl_path), uevent_fd_(-1), log_fd_(-1) {} UeventListener::UeventListener(const struct UeventPaths &uevents_paths) : kAudioUevent((uevents_paths.AudioUevent == nullptr) ? "" : uevents_paths.AudioUevent), kBatterySSOCPath((uevents_paths.SsocDetailsPath == nullptr) ? ssoc_details_path : uevents_paths.SsocDetailsPath), kUsbPortOverheatPath((uevents_paths.OverheatPath == nullptr) ? overheat_path_default : uevents_paths.OverheatPath), kChargeMetricsPath((uevents_paths.ChargeMetricsPath == nullptr) ? charge_metrics_path_default : uevents_paths.ChargeMetricsPath), kTypeCPartnerUevent((uevents_paths.TypeCPartnerUevent == nullptr) ? typec_partner_uevent_default : uevents_paths.TypeCPartnerUevent), kTypeCPartnerVidPath((uevents_paths.TypeCPartnerVidPath == nullptr) ? typec_partner_vid_path_default : uevents_paths.TypeCPartnerVidPath), kTypeCPartnerPidPath((uevents_paths.TypeCPartnerPidPath == nullptr) ? typec_partner_pid_path_default : uevents_paths.TypeCPartnerPidPath), kFwUpdatePath((uevents_paths.FwUpdatePath == nullptr) ? "" : uevents_paths.FwUpdatePath), kFGAbnlPath(uevents_paths.FGAbnlPath), uevent_fd_(-1), log_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