Merge "Add new common USB Gadget Aidl file"
diff --git a/battery_mitigation/MitigationThermalManager.cpp b/battery_mitigation/MitigationThermalManager.cpp
index b1320e7..833e294 100644
--- a/battery_mitigation/MitigationThermalManager.cpp
+++ b/battery_mitigation/MitigationThermalManager.cpp
@@ -124,6 +124,7 @@
std::stringstream oss;
oss << temperature.name << " triggered at " << temperature.value << std::endl << std::flush;
android::base::WriteStringToFd(oss.str(), fd);
+ fsync(fd);
for (int i = 0; i < NUM_OF_SAMPLES; i++) {
auto now = std::chrono::system_clock::now();
@@ -135,6 +136,7 @@
oss << std::put_time(&now_tm, "%m-%d %H:%M:%S.") << std::setw(3) << std::setfill('0')
<< ms.count() << std::endl << std::flush;
android::base::WriteStringToFd(oss.str(), fd);
+ fsync(fd);
oss.str("");
/* log System info */
for (int j = 0; j < kSystemName.size(); j++) {
diff --git a/common/pixel-common-device.mk b/common/pixel-common-device.mk
index 4bb4398..93e764d 100644
--- a/common/pixel-common-device.mk
+++ b/common/pixel-common-device.mk
@@ -27,6 +27,11 @@
PRODUCT_PACKAGES_DEBUG += wifi_perf_diag
BOARD_VENDOR_SEPOLICY_DIRS += hardware/google/pixel-sepolicy/wifi_perf_diag
+# Pixel storage tool
+PRODUCT_PACKAGES_DEBUG += \
+ sg_write_buffer \
+ sg_read_buffer
+
# Enable whole-program R8 Java optimizations for SystemUI and system_server,
# but also allow explicit overriding for testing and development.
SYSTEM_OPTIMIZE_JAVA ?= true
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index dd2f80d..ee1cda3 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -42,3 +42,31 @@
"libfstab",
],
}
+
+
+cc_binary {
+ name: "android.hardware.fastboot-service.pixel_recovery",
+ init_rc: ["android.hardware.fastboot-service.pixel_recovery.rc"],
+ vintf_fragments: ["android.hardware.fastboot-service.pixel.xml"],
+ recovery: true,
+ srcs: [
+ "Fastboot_aidl.cpp",
+ "main.cpp",
+ ],
+ relative_install_path: "hw",
+ shared_libs: [
+ "android.hardware.fastboot-V1-ndk",
+ "libbinder_ndk",
+ "libbase",
+ "libcutils",
+ "libext4_utils",
+ "libfs_mgr",
+ "liblog",
+ "libutils",
+ ],
+ static_libs: [
+ "libnos_for_fastboot",
+ "libnos_citadel_for_fastboot",
+ "libfstab",
+ ],
+}
diff --git a/fastboot/Fastboot_aidl.cpp b/fastboot/Fastboot_aidl.cpp
new file mode 100644
index 0000000..32df5f3
--- /dev/null
+++ b/fastboot/Fastboot_aidl.cpp
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+#include "include/fastboot/Fastboot_aidl.h"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <dlfcn.h>
+#include <endian.h>
+
+#include <map>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+// FS headers
+#include <ext4_utils/wipe.h>
+#include <fs_mgr.h>
+#include <fs_mgr/roots.h>
+
+// Nugget headers
+#include <app_nugget.h>
+#include <nos/NuggetClient.h>
+#include <nos/debug.h>
+
+using ndk::ScopedAStatus;
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace fastboot {
+
+constexpr const char *BRIGHTNESS_FILE = "/sys/class/backlight/panel0-backlight/brightness";
+constexpr int DISPLAY_BRIGHTNESS_DIM_THRESHOLD = 20;
+
+using OEMCommandHandler =
+ std::function<ScopedAStatus(const std::vector<std::string> &, std::string *)>;
+
+ScopedAStatus Fastboot::getPartitionType(const std::string &in_partitionName,
+ FileSystemType *_aidl_return) {
+ if (in_partitionName.empty()) {
+ return ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
+ "Invalid partition name");
+ }
+ // For bluecross devices, all partitions need to return raw.
+ *_aidl_return = FileSystemType::RAW;
+ return ScopedAStatus::ok();
+}
+
+ScopedAStatus Fastboot::getVariant(std::string *_aidl_return) {
+ *_aidl_return = "MSM USF";
+ return ScopedAStatus::ok();
+}
+
+ScopedAStatus Fastboot::getOffModeChargeState(bool *_aidl_return) {
+ constexpr const char *kDevinfoPath = "/dev/block/by-name/devinfo";
+ constexpr int kDevInfoOffModeChargeOffset = 15;
+
+ uint8_t off_mode_charge_status = 0;
+ ::android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(kDevinfoPath, O_RDONLY | O_BINARY)));
+ if (fd < 0) {
+ std::string message = "Unable to open devinfo " + std::to_string(errno);
+ return ScopedAStatus::fromServiceSpecificErrorWithMessage(BnFastboot::FAILURE_UNKNOWN,
+ message.c_str());
+ }
+ auto ret = ::android::base::ReadFullyAtOffset(fd, &off_mode_charge_status, 1 /* byte count */,
+ kDevInfoOffModeChargeOffset);
+ if (!ret) {
+ std::string message = "Reading devifo failed errno:" + std::to_string(errno) +
+ " Unable to read off-mode-charge state";
+ return ScopedAStatus::fromServiceSpecificErrorWithMessage(BnFastboot::FAILURE_UNKNOWN,
+ message.c_str());
+ } else {
+ *_aidl_return = (off_mode_charge_status != 0);
+ }
+
+ return ScopedAStatus::ok();
+}
+
+ScopedAStatus Fastboot::getBatteryVoltageFlashingThreshold(int32_t *_aidl_return) {
+ constexpr int kMinVoltageForFlashing = 3500;
+ *_aidl_return = kMinVoltageForFlashing;
+ return ScopedAStatus::ok();
+}
+
+ScopedAStatus SetBrightnessLevel(const std::vector<std::string> &args, std::string *_aidl_return) {
+ if (!args.size()) {
+ return ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
+ "Brightness level unspecified");
+ }
+
+ auto level = std::stoi(args[0]);
+
+ if (level < 0 || level > 100) {
+ return ScopedAStatus::fromExceptionCodeWithMessage(
+ EX_ILLEGAL_ARGUMENT, "Brighness level must be between 0 and 100");
+ }
+
+ // Avoid screen being dimmed too much.
+ if (level < DISPLAY_BRIGHTNESS_DIM_THRESHOLD) {
+ level = DISPLAY_BRIGHTNESS_DIM_THRESHOLD;
+ }
+
+ if (::android::base::WriteStringToFile(std::to_string(level), BRIGHTNESS_FILE)) {
+ *_aidl_return = "";
+ return ScopedAStatus::ok();
+ }
+ std::string message = "Writing to brightness file failed errno: " + std::to_string(errno) +
+ " Unable to set display brightness";
+
+ return ScopedAStatus::fromServiceSpecificErrorWithMessage(BnFastboot::FAILURE_UNKNOWN,
+ message.c_str());
+}
+
+ScopedAStatus Fastboot::doOemCommand(const std::string &in_oemCmd, std::string *_aidl_return) {
+ const std::unordered_map<std::string, OEMCommandHandler> kOEMCmdMap = {
+ {FB_OEM_SET_BRIGHTNESS, SetBrightnessLevel},
+ };
+
+ auto args = ::android::base::Split(in_oemCmd, " ");
+ if (args.size() < 2) {
+ return ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
+ "Invalid OEM command");
+ }
+
+ // args[0] will be "oem", args[1] will be the command name
+ auto cmd_handler = kOEMCmdMap.find(args[1]);
+ if (cmd_handler != kOEMCmdMap.end()) {
+ return cmd_handler->second(std::vector<std::string>(args.begin() + 2, args.end()),
+ _aidl_return);
+ } else {
+ return ScopedAStatus::fromServiceSpecificErrorWithMessage(BnFastboot::FAILURE_UNKNOWN,
+ "Unknown OEM Command");
+ }
+
+ return ScopedAStatus::ok();
+}
+
+static ::android::fs_mgr::Fstab fstab;
+enum WipeVolumeStatus {
+ WIPE_OK = 0,
+ VOL_FSTAB,
+ VOL_UNKNOWN,
+ VOL_MOUNTED,
+ VOL_BLK_DEV_OPEN,
+ WIPE_ERROR_MAX = 0xffffffff,
+};
+std::map<enum WipeVolumeStatus, std::string> wipe_vol_ret_msg{
+ {WIPE_OK, ""},
+ {VOL_FSTAB, "Unknown FS table"},
+ {VOL_UNKNOWN, "Unknown volume"},
+ {VOL_MOUNTED, "Fail to unmount volume"},
+ {VOL_BLK_DEV_OPEN, "Fail to open block device"},
+ {WIPE_ERROR_MAX, "Unknown wipe error"}};
+
+enum WipeVolumeStatus wipe_volume(const std::string &volume) {
+ if (!::android::fs_mgr::ReadDefaultFstab(&fstab)) {
+ return VOL_FSTAB;
+ }
+ const ::android::fs_mgr::FstabEntry *v = ::android::fs_mgr::GetEntryForPath(&fstab, volume);
+ if (v == nullptr) {
+ return VOL_UNKNOWN;
+ }
+ if (::android::fs_mgr::EnsurePathUnmounted(&fstab, volume) != true) {
+ return VOL_MOUNTED;
+ }
+
+ int fd = open(v->blk_device.c_str(), O_WRONLY | O_CREAT, 0644);
+ if (fd == -1) {
+ return VOL_BLK_DEV_OPEN;
+ }
+ wipe_block_device(fd, get_block_device_size(fd));
+ close(fd);
+
+ return WIPE_OK;
+}
+
+// Attempt to reuse a WipeKeys function that might be found in the recovery
+// library in order to clear any digital car keys on the secure element.
+bool WipeDigitalCarKeys(void) {
+ static constexpr const char *kDefaultLibRecoveryUIExt = "librecovery_ui_ext.so";
+ void *librecovery_ui_ext = dlopen(kDefaultLibRecoveryUIExt, RTLD_NOW);
+ if (librecovery_ui_ext == nullptr) {
+ // Dynamic library not found. Returning true since this likely
+ // means target does not support DCK.
+ return true;
+ }
+
+ bool *(*WipeKeysFunc)(void *const);
+ reinterpret_cast<void *&>(WipeKeysFunc) = dlsym(librecovery_ui_ext, "WipeKeys");
+ if (WipeKeysFunc == nullptr) {
+ // No WipeKeys implementation found. Returning true since this likely
+ // means target does not support DCK.
+ return true;
+ }
+
+ return (*WipeKeysFunc)(nullptr);
+}
+
+ScopedAStatus Fastboot::doOemSpecificErase() {
+ // Erase metadata partition along with userdata partition.
+ // Keep erasing Titan M even if failing on this case.
+ auto wipe_status = wipe_volume("/metadata");
+
+ bool dck_wipe_success = WipeDigitalCarKeys();
+
+ // Connect to Titan M
+ ::nos::NuggetClient client;
+ client.Open();
+ if (!client.IsOpen()) {
+ return ScopedAStatus::fromServiceSpecificErrorWithMessage(BnFastboot::FAILURE_UNKNOWN,
+ "open Titan M fail");
+ }
+
+ // Tell Titan M to wipe user data
+ const uint32_t magicValue = htole32(ERASE_CONFIRMATION);
+ std::vector<uint8_t> magic(sizeof(magicValue));
+ memcpy(magic.data(), &magicValue, sizeof(magicValue));
+ const uint8_t retry_count = 5;
+ uint32_t nugget_status;
+ for (uint8_t i = 0; i < retry_count; i++) {
+ nugget_status = client.CallApp(APP_ID_NUGGET, NUGGET_PARAM_NUKE_FROM_ORBIT, magic, nullptr);
+ if (nugget_status == APP_SUCCESS && wipe_status == WIPE_OK) {
+ return ScopedAStatus::ok();
+ }
+ }
+
+ // Return exactly what happened
+ if (nugget_status != APP_SUCCESS && wipe_status != WIPE_OK && !dck_wipe_success) {
+ return ScopedAStatus::fromServiceSpecificErrorWithMessage(
+ BnFastboot::FAILURE_UNKNOWN, "Fail on wiping metadata, Titan M user data, and DCK");
+ } else if (nugget_status != APP_SUCCESS && wipe_status != WIPE_OK) {
+ return ScopedAStatus::fromServiceSpecificErrorWithMessage(
+ BnFastboot::FAILURE_UNKNOWN, "Fail on wiping metadata and Titan M user data");
+ } else if (nugget_status != APP_SUCCESS && !dck_wipe_success) {
+ return ScopedAStatus::fromServiceSpecificErrorWithMessage(
+ BnFastboot::FAILURE_UNKNOWN, "Titan M user data and DCK wipe failed");
+ } else if (nugget_status != APP_SUCCESS) {
+ return ScopedAStatus::fromServiceSpecificErrorWithMessage(BnFastboot::FAILURE_UNKNOWN,
+ "Titan M user data wipe failed");
+ } else if (wipe_status != WIPE_OK && !dck_wipe_success) {
+ return ScopedAStatus::fromServiceSpecificErrorWithMessage(
+ BnFastboot::FAILURE_UNKNOWN, "Fail on wiping metadata and DCK");
+ } else if (!dck_wipe_success) {
+ return ScopedAStatus::fromServiceSpecificErrorWithMessage(BnFastboot::FAILURE_UNKNOWN,
+ "DCK wipe failed");
+ } else {
+ if (wipe_vol_ret_msg.find(wipe_status) != wipe_vol_ret_msg.end())
+ return ScopedAStatus::fromServiceSpecificErrorWithMessage(
+ BnFastboot::FAILURE_UNKNOWN, wipe_vol_ret_msg[wipe_status].c_str());
+ else // Should not reach here, but handle it anyway
+ return ScopedAStatus::fromServiceSpecificErrorWithMessage(BnFastboot::FAILURE_UNKNOWN,
+ "Unknown failure");
+ }
+
+ // Return exactly what happened
+ if (nugget_status != APP_SUCCESS && wipe_status != WIPE_OK) {
+ return ScopedAStatus::fromServiceSpecificErrorWithMessage(
+ BnFastboot::FAILURE_UNKNOWN, "Fail on wiping metadata and Titan M user data");
+
+ } else if (nugget_status != APP_SUCCESS) {
+ return ScopedAStatus::fromServiceSpecificErrorWithMessage(BnFastboot::FAILURE_UNKNOWN,
+ "Titan M user data wipe failed");
+ } else {
+ if (wipe_vol_ret_msg.find(wipe_status) != wipe_vol_ret_msg.end())
+ return ScopedAStatus::fromServiceSpecificErrorWithMessage(
+ BnFastboot::FAILURE_UNKNOWN, wipe_vol_ret_msg[wipe_status].c_str());
+ else // Should not reach here, but handle it anyway
+ return ScopedAStatus::fromServiceSpecificErrorWithMessage(BnFastboot::FAILURE_UNKNOWN,
+ "Unknown failure");
+ }
+
+ return ScopedAStatus::ok();
+}
+
+} // namespace fastboot
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/fastboot/android.hardware.fastboot-service.pixel.xml b/fastboot/android.hardware.fastboot-service.pixel.xml
new file mode 100644
index 0000000..9490f98
--- /dev/null
+++ b/fastboot/android.hardware.fastboot-service.pixel.xml
@@ -0,0 +1,8 @@
+<manifest version="1.0" type="device">
+ <hal format="aidl">
+ <name>android.hardware.fastboot</name>
+ <version>1</version>
+ <fqname>IFastboot/default</fqname>
+ </hal>
+</manifest>
+
diff --git a/fastboot/android.hardware.fastboot-service.pixel_recovery.rc b/fastboot/android.hardware.fastboot-service.pixel_recovery.rc
new file mode 100644
index 0000000..799b716
--- /dev/null
+++ b/fastboot/android.hardware.fastboot-service.pixel_recovery.rc
@@ -0,0 +1,6 @@
+service vendor.pixel-fastboot /system/bin/hw/android.hardware.fastboot-service.pixel_recovery
+ class hal
+ seclabel u:r:hal_fastboot_default:s0
+ user root
+ group root
+ interface aidl android.hardware.fastboot.IFastboot/default
diff --git a/fastboot/include/fastboot/Fastboot_aidl.h b/fastboot/include/fastboot/Fastboot_aidl.h
new file mode 100644
index 0000000..51ae738
--- /dev/null
+++ b/fastboot/include/fastboot/Fastboot_aidl.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#pragma once
+
+#include "aidl/android/hardware/fastboot/BnFastboot.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace fastboot {
+class Fastboot : public BnFastboot {
+#define FB_OEM_SET_BRIGHTNESS "setbrightness"
+ ::ndk::ScopedAStatus doOemCommand(const std::string &in_oemCmd,
+ std::string *_aidl_return) override;
+ ::ndk::ScopedAStatus doOemSpecificErase() override;
+ ::ndk::ScopedAStatus getBatteryVoltageFlashingThreshold(int32_t *_aidl_return) override;
+ ::ndk::ScopedAStatus getOffModeChargeState(bool *_aidl_return) override;
+ ::ndk::ScopedAStatus getPartitionType(
+ const std::string &in_partitionName,
+ ::aidl::android::hardware::fastboot::FileSystemType *_aidl_return) override;
+ ::ndk::ScopedAStatus getVariant(std::string *_aidl_return) override;
+};
+
+} // namespace fastboot
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/fastboot/main.cpp b/fastboot/main.cpp
new file mode 100644
index 0000000..d4c1117
--- /dev/null
+++ b/fastboot/main.cpp
@@ -0,0 +1,39 @@
+
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include "include/fastboot/Fastboot_aidl.h"
+
+using aidl::android::hardware::fastboot::Fastboot;
+using aidl::android::hardware::fastboot::IFastboot;
+
+int main(int, char *argv[]) {
+ android::base::InitLogging(argv, android::base::KernelLogger);
+ ABinderProcess_setThreadPoolMaxThreadCount(0);
+ std::shared_ptr<IFastboot> service = ndk::SharedRefBase::make<Fastboot>();
+
+ const std::string instance = std::string(IFastboot::descriptor) + "/default";
+ auto status = AServiceManager_addService(service->asBinder().get(), instance.c_str());
+ CHECK_EQ(status, STATUS_OK) << "Failed to add service " << instance << " " << status;
+ LOG(INFO) << "IFastboot AIDL service running...";
+
+ ABinderProcess_joinThreadPool();
+ return EXIT_FAILURE; // should not reach
+}
diff --git a/health/Android.bp b/health/Android.bp
index 136e7db..421fe8c 100644
--- a/health/Android.bp
+++ b/health/Android.bp
@@ -36,7 +36,7 @@
export_shared_lib_headers: [
"android.frameworks.stats-V1-ndk",
"android.hardware.health-V1-ndk",
- "pixelatoms-cpp",
+ "libpixelatoms_defs",
],
shared_libs: [
@@ -46,9 +46,8 @@
"libbinder_ndk",
"libcutils",
"libhidlbase",
- "libprotobuf-cpp-lite",
+ "libpixelatoms_defs",
"libutils",
- "pixelatoms-cpp",
],
}
diff --git a/health/BatteryDefender.cpp b/health/BatteryDefender.cpp
index 8b693f0..66876a5 100644
--- a/health/BatteryDefender.cpp
+++ b/health/BatteryDefender.cpp
@@ -239,6 +239,10 @@
return isOverrideDisabled || (isDefaultVendorChargeLevel == false) || (isCtrlEnabled == false);
}
+bool BatteryDefender::isDockDefendTrigger(void) {
+ return readFileToInt(kPathDockState, true) == 1;
+}
+
void BatteryDefender::addTimeToChargeTimers(void) {
if (mIsPowerAvailable) {
if (mHasReachedHighCapacityLevel) {
@@ -410,6 +414,11 @@
health_info->batteryHealth = BatteryHealth::OVERHEAT;
}
+ /* Do the same as above when dock-defend triggers */
+ if (mIsDockDefendTrigger) {
+ health_info->batteryHealth = BatteryHealth::OVERHEAT;
+ }
+
/**
* If the kernel is forcing the input current limit to 0, then the online status may
* need to be overwritten. Also, setting a charge limit below the current charge level
@@ -461,6 +470,7 @@
mIsDefenderDisabled = isBatteryDefenderDisabled(chargeLevelVendorStart, chargeLevelVendorStop);
mIsPowerAvailable = isChargePowerAvailable();
mTimeBetweenUpdateCalls = getDeltaTimeSeconds(&mTimePreviousSecs);
+ mIsDockDefendTrigger = isDockDefendTrigger();
// Run state machine
stateMachine_runAction(mCurrentState, *health_info);
diff --git a/health/BatteryMetricsLogger.cpp b/health/BatteryMetricsLogger.cpp
index 5e4c7fb..7f6adb3 100644
--- a/health/BatteryMetricsLogger.cpp
+++ b/health/BatteryMetricsLogger.cpp
@@ -19,17 +19,13 @@
#include <pixelhealth/HealthHelper.h>
#include <pixelhealth/StatsHelper.h>
-using aidl::android::hardware::health::BatteryStatus;
-using aidl::android::hardware::health::HealthInfo;
-
namespace hardware {
namespace google {
namespace pixel {
namespace health {
-VendorBatteryHealthSnapshot::BatterySnapshotType toBatterySnapshotType(int type) {
- return static_cast<VendorBatteryHealthSnapshot::BatterySnapshotType>(type);
-}
+using aidl::android::hardware::health::BatteryStatus;
+using aidl::android::hardware::health::HealthInfo;
BatteryMetricsLogger::BatteryMetricsLogger(const char *const batt_res, const char *const batt_ocv,
const char *const batt_avg_res, int sample_period,
@@ -57,26 +53,12 @@
if (kStatsSnapshotType[type] < 0)
return false;
- VendorBatteryHealthSnapshot min_stats_ss;
- min_stats_ss.set_type(toBatterySnapshotType(kStatsSnapshotType[type]));
- min_stats_ss.set_temperature_deci_celsius(min_[type][TEMP]);
- min_stats_ss.set_voltage_micro_volt(min_[type][VOLT]);
- min_stats_ss.set_current_micro_amps(min_[type][CURR]);
- min_stats_ss.set_open_circuit_micro_volt(min_[type][OCV]);
- min_stats_ss.set_resistance_micro_ohm(min_[type][RES]);
- min_stats_ss.set_level_percent(min_[type][SOC]);
-
- VendorBatteryHealthSnapshot max_stats_ss;
- max_stats_ss.set_type(toBatterySnapshotType(kStatsSnapshotType[type] + 1));
- max_stats_ss.set_temperature_deci_celsius(max_[type][TEMP]);
- max_stats_ss.set_voltage_micro_volt(max_[type][VOLT]);
- max_stats_ss.set_current_micro_amps(max_[type][CURR]);
- max_stats_ss.set_open_circuit_micro_volt(max_[type][OCV]);
- max_stats_ss.set_resistance_micro_ohm(max_[type][RES]);
- max_stats_ss.set_level_percent(max_[type][SOC]);
-
- reportBatteryHealthSnapshot(stats_client, min_stats_ss);
- reportBatteryHealthSnapshot(stats_client, max_stats_ss);
+ reportBatteryHealthSnapshot(stats_client, kStatsSnapshotType[type], min_[type][TEMP],
+ min_[type][VOLT], min_[type][CURR], min_[type][OCV],
+ min_[type][RES], min_[type][SOC]);
+ reportBatteryHealthSnapshot(stats_client, kStatsSnapshotType[type] + 1, max_[type][TEMP],
+ max_[type][VOLT], max_[type][CURR], max_[type][OCV],
+ max_[type][RES], max_[type][SOC]);
return true;
}
@@ -101,16 +83,9 @@
return false;
}
// Upload average metric
- VendorBatteryHealthSnapshot avg_res_ss_stats;
- avg_res_ss_stats.set_type(VendorBatteryHealthSnapshot::BATTERY_SNAPSHOT_TYPE_AVG_RESISTANCE);
- avg_res_ss_stats.set_temperature_deci_celsius(0);
- avg_res_ss_stats.set_voltage_micro_volt(0);
- avg_res_ss_stats.set_current_micro_amps(0);
- avg_res_ss_stats.set_open_circuit_micro_volt(0);
- avg_res_ss_stats.set_resistance_micro_ohm(batt_avg_res);
- avg_res_ss_stats.set_level_percent(0);
-
- reportBatteryHealthSnapshot(stats_client, avg_res_ss_stats);
+ reportBatteryHealthSnapshot(stats_client,
+ VendorBatteryHealthSnapshot::BATTERY_SNAPSHOT_TYPE_AVG_RESISTANCE,
+ 0, 0, 0, 0, batt_avg_res, 0);
return true;
}
diff --git a/health/LowBatteryShutdownMetrics.cpp b/health/LowBatteryShutdownMetrics.cpp
index 3ff606c..b3d43f8 100644
--- a/health/LowBatteryShutdownMetrics.cpp
+++ b/health/LowBatteryShutdownMetrics.cpp
@@ -52,7 +52,6 @@
}
// Process and upload comma-delimited last voltage values
- VendorBatteryCausedShutdown shutdown;
int32_t voltage_avg;
for (const auto &item : android::base::Split(prop_contents, ",")) {
if (!(voltage_avg = stoi(item))) {
@@ -60,8 +59,7 @@
continue;
}
LOG(INFO) << "Uploading voltage_avg: " << std::to_string(voltage_avg);
- shutdown.set_last_recorded_micro_volt(voltage_avg);
- reportBatteryCausedShutdown(stats_client, shutdown);
+ reportBatteryCausedShutdown(stats_client, voltage_avg);
}
// Clear property now that we've uploaded its contents
diff --git a/health/StatsHelper.cpp b/health/StatsHelper.cpp
index 22fb9f3..4900e4f 100644
--- a/health/StatsHelper.cpp
+++ b/health/StatsHelper.cpp
@@ -18,6 +18,8 @@
#include <android/binder_manager.h>
#include <pixelhealth/StatsHelper.h>
+#include "pixelatoms_defs.h"
+
#define LOG_TAG "pixelhealth-vendor"
#include <utils/Log.h>
@@ -29,7 +31,8 @@
using aidl::android::frameworks::stats::VendorAtom;
using aidl::android::frameworks::stats::VendorAtomValue;
-namespace PixelAtoms = android::hardware::google::pixel::PixelAtoms;
+
+namespace PixelAtoms = hardware::google::pixel::PixelAtoms;
std::shared_ptr<IStats> getStatsService() {
const std::string instance = std::string() + IStats::descriptor + "/default";
@@ -48,29 +51,30 @@
return IStats::fromBinder(ndk::SpAIBinder(AServiceManager_getService(instance.c_str())));
}
-void reportBatteryHealthSnapshot(const std::shared_ptr<IStats> &stats_client,
- const VendorBatteryHealthSnapshot &batteryHealthSnapshot) {
+void reportBatteryHealthSnapshot(const std::shared_ptr<IStats> &stats_client, int32_t type,
+ int32_t temperature_deci_celsius, int32_t voltage_micro_volt,
+ int32_t current_micro_amps, int32_t open_circuit_micro_volt,
+ int32_t resistance_micro_ohm, int32_t level_percent) {
// Load values array
std::vector<VendorAtomValue> values(7);
VendorAtomValue tmp;
- tmp.set<VendorAtomValue::intValue>(batteryHealthSnapshot.type());
+ tmp.set<VendorAtomValue::intValue>(type);
values[0] = tmp;
- tmp.set<VendorAtomValue::intValue>(batteryHealthSnapshot.temperature_deci_celsius());
+ tmp.set<VendorAtomValue::intValue>(temperature_deci_celsius);
values[1] = tmp;
- tmp.set<VendorAtomValue::intValue>(batteryHealthSnapshot.voltage_micro_volt());
+ tmp.set<VendorAtomValue::intValue>(voltage_micro_volt);
values[2] = tmp;
- tmp.set<VendorAtomValue::intValue>(batteryHealthSnapshot.current_micro_amps());
+ tmp.set<VendorAtomValue::intValue>(current_micro_amps);
values[3] = tmp;
- tmp.set<VendorAtomValue::intValue>(batteryHealthSnapshot.open_circuit_micro_volt());
+ tmp.set<VendorAtomValue::intValue>(open_circuit_micro_volt);
values[4] = tmp;
- tmp.set<VendorAtomValue::intValue>(batteryHealthSnapshot.resistance_micro_ohm());
+ tmp.set<VendorAtomValue::intValue>(resistance_micro_ohm);
values[5] = tmp;
- tmp.set<VendorAtomValue::intValue>(batteryHealthSnapshot.level_percent());
+ tmp.set<VendorAtomValue::intValue>(level_percent);
values[6] = tmp;
// Send vendor atom to IStats HAL
- VendorAtom event = {.reverseDomainName = "",
- .atomId = PixelAtoms::Atom::kVendorBatteryHealthSnapshot,
+ VendorAtom event = {.atomId = PixelAtoms::VENDOR_BATTERY_HEALTH_SNAPSHOT,
.values = std::move(values)};
const ndk::ScopedAStatus ret = stats_client->reportVendorAtom(event);
if (!ret.isOk())
@@ -78,16 +82,15 @@
}
void reportBatteryCausedShutdown(const std::shared_ptr<IStats> &stats_client,
- const VendorBatteryCausedShutdown &batteryCausedShutdown) {
+ int32_t last_recorded_micro_volt) {
// Load values array
std::vector<VendorAtomValue> values(1);
VendorAtomValue tmp;
- tmp.set<VendorAtomValue::intValue>(batteryCausedShutdown.last_recorded_micro_volt());
+ tmp.set<VendorAtomValue::intValue>(last_recorded_micro_volt);
values[0] = tmp;
// Send vendor atom to IStats HAL
- VendorAtom event = {.reverseDomainName = "",
- .atomId = PixelAtoms::Atom::kVendorBatteryCausedShutdown,
+ VendorAtom event = {.atomId = PixelAtoms::VENDOR_BATTERY_CAUSED_SHUTDOWN,
.values = std::move(values)};
const ndk::ScopedAStatus ret = stats_client->reportVendorAtom(event);
if (!ret.isOk())
diff --git a/health/include/pixelhealth/BatteryDefender.h b/health/include/pixelhealth/BatteryDefender.h
index 40d190b..58dd080 100644
--- a/health/include/pixelhealth/BatteryDefender.h
+++ b/health/include/pixelhealth/BatteryDefender.h
@@ -101,6 +101,7 @@
"/mnt/vendor/persist/battery/defender_charger_time";
const std::string kPathPersistDefenderActiveTime =
"/mnt/vendor/persist/battery/defender_active_time";
+ const std::string kPathDockState = "/sys/devices/platform/google,charger/dd_state";
// Properties
const char *const kPropChargeLevelVendorStart = "persist.vendor.charge.start.level";
@@ -140,6 +141,7 @@
bool mIsDockPresent = false;
bool mIsPowerAvailable = false;
bool mIsDefenderDisabled = false;
+ bool mIsDockDefendTrigger = false;
int32_t mTimeToActivateSecsModified;
// State
@@ -183,6 +185,7 @@
bool isDefaultChargeLevel(const int start, const int stop);
bool isBatteryDefenderDisabled(const int vendorStart, const int vendorStop);
void addTimeToChargeTimers(void);
+ bool isDockDefendTrigger(void);
};
} // namespace health
diff --git a/health/include/pixelhealth/BatteryMetricsLogger.h b/health/include/pixelhealth/BatteryMetricsLogger.h
index 9bf2a69..e4d4d75 100644
--- a/health/include/pixelhealth/BatteryMetricsLogger.h
+++ b/health/include/pixelhealth/BatteryMetricsLogger.h
@@ -23,20 +23,22 @@
#include <android-base/logging.h>
#include <android-base/strings.h>
#include <batteryservice/BatteryService.h>
-#include <hardware/google/pixel/pixelstats/pixelatoms.pb.h>
#include <math.h>
#include <time.h>
#include <utils/Timers.h>
#include <string>
+#include "pixelatoms_defs.h"
+
namespace hardware {
namespace google {
namespace pixel {
namespace health {
using aidl::android::frameworks::stats::IStats;
-using android::hardware::google::pixel::PixelAtoms::VendorBatteryHealthSnapshot;
+
+using hardware::google::pixel::PixelAtoms::VendorBatteryHealthSnapshot;
class BatteryMetricsLogger {
public:
diff --git a/health/include/pixelhealth/StatsHelper.h b/health/include/pixelhealth/StatsHelper.h
index 90bf985..ea49bee 100644
--- a/health/include/pixelhealth/StatsHelper.h
+++ b/health/include/pixelhealth/StatsHelper.h
@@ -18,7 +18,6 @@
#define HARDWARE_GOOGLE_PIXEL_HEALTH_STATSHELPER_H
#include <aidl/android/frameworks/stats/IStats.h>
-#include <hardware/google/pixel/pixelstats/pixelatoms.pb.h>
namespace hardware {
namespace google {
@@ -26,16 +25,16 @@
namespace health {
using aidl::android::frameworks::stats::IStats;
-using android::hardware::google::pixel::PixelAtoms::VendorBatteryCausedShutdown;
-using android::hardware::google::pixel::PixelAtoms::VendorBatteryHealthSnapshot;
std::shared_ptr<IStats> getStatsService();
-void reportBatteryHealthSnapshot(const std::shared_ptr<IStats> &stats_client,
- const VendorBatteryHealthSnapshot &batteryHealthSnapshot);
+void reportBatteryHealthSnapshot(const std::shared_ptr<IStats> &stats_client, int32_t type,
+ int32_t temperature_deci_celsius, int32_t voltage_micro_volt,
+ int32_t current_micro_amps, int32_t open_circuit_micro_volt,
+ int32_t resistance_micro_ohm, int32_t level_percent);
void reportBatteryCausedShutdown(const std::shared_ptr<IStats> &stats_client,
- const VendorBatteryCausedShutdown &batteryCausedShutdown);
+ int32_t last_recorded_micro_volt);
} // namespace health
} // namespace pixel
diff --git a/mm/device_gki.mk b/mm/device_gki.mk
index cd4fdf8..adcf227 100644
--- a/mm/device_gki.mk
+++ b/mm/device_gki.mk
@@ -1,7 +1,13 @@
PRODUCT_COPY_FILES += \
hardware/google/pixel/mm/pixel-mm-gki.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/pixel-mm-gki.rc \
hardware/google/pixel/mm/fstab.zram.2g:$(TARGET_COPY_OUT_VENDOR)/etc/fstab.zram.2g \
- hardware/google/pixel/mm/fstab.zram.3g:$(TARGET_COPY_OUT_VENDOR)/etc/fstab.zram.3g
+ hardware/google/pixel/mm/fstab.zram.3g:$(TARGET_COPY_OUT_VENDOR)/etc/fstab.zram.3g \
+ hardware/google/pixel/mm/fstab.zram.4g:$(TARGET_COPY_OUT_VENDOR)/etc/fstab.zram.4g \
+ hardware/google/pixel/mm/fstab.zram.5g:$(TARGET_COPY_OUT_VENDOR)/etc/fstab.zram.5g \
+ hardware/google/pixel/mm/fstab.zram.6g:$(TARGET_COPY_OUT_VENDOR)/etc/fstab.zram.6g \
+ hardware/google/pixel/mm/fstab.zram.40p:$(TARGET_COPY_OUT_VENDOR)/etc/fstab.zram.40p \
+ hardware/google/pixel/mm/fstab.zram.50p:$(TARGET_COPY_OUT_VENDOR)/etc/fstab.zram.50p \
+ hardware/google/pixel/mm/fstab.zram.60p:$(TARGET_COPY_OUT_VENDOR)/etc/fstab.zram.60p
ifneq (,$(filter userdebug eng, $(TARGET_BUILD_VARIANT)))
PRODUCT_PACKAGES += \
diff --git a/mm/fstab.zram.40p b/mm/fstab.zram.40p
new file mode 100644
index 0000000..c85a59b
--- /dev/null
+++ b/mm/fstab.zram.40p
@@ -0,0 +1 @@
+/dev/block/zram0 none swap defaults zramsize=40%,max_comp_streams=8,zram_backingdev_size=512M
diff --git a/mm/fstab.zram.4g b/mm/fstab.zram.4g
new file mode 100644
index 0000000..0cfad5a
--- /dev/null
+++ b/mm/fstab.zram.4g
@@ -0,0 +1 @@
+/dev/block/zram0 none swap defaults zramsize=4294967296,max_comp_streams=8,zram_backingdev_size=512M
diff --git a/mm/fstab.zram.50p b/mm/fstab.zram.50p
new file mode 100644
index 0000000..b0e1ed6
--- /dev/null
+++ b/mm/fstab.zram.50p
@@ -0,0 +1 @@
+/dev/block/zram0 none swap defaults zramsize=50%,max_comp_streams=8,zram_backingdev_size=512M
diff --git a/mm/fstab.zram.5g b/mm/fstab.zram.5g
new file mode 100644
index 0000000..8cd4d56
--- /dev/null
+++ b/mm/fstab.zram.5g
@@ -0,0 +1 @@
+/dev/block/zram0 none swap defaults zramsize=5368709120,max_comp_streams=8,zram_backingdev_size=512M
diff --git a/mm/fstab.zram.60p b/mm/fstab.zram.60p
new file mode 100644
index 0000000..0e05048
--- /dev/null
+++ b/mm/fstab.zram.60p
@@ -0,0 +1 @@
+/dev/block/zram0 none swap defaults zramsize=60%,max_comp_streams=8,zram_backingdev_size=512M
diff --git a/mm/fstab.zram.6g b/mm/fstab.zram.6g
new file mode 100644
index 0000000..04fcd49
--- /dev/null
+++ b/mm/fstab.zram.6g
@@ -0,0 +1 @@
+/dev/block/zram0 none swap defaults zramsize=6442450944,max_comp_streams=8,zram_backingdev_size=512M
diff --git a/mm/pixel-mm-gki.rc b/mm/pixel-mm-gki.rc
index e5ab7f7..fa2cd35 100644
--- a/mm/pixel-mm-gki.rc
+++ b/mm/pixel-mm-gki.rc
@@ -19,8 +19,13 @@
# khugepaged tuning
write /sys/kernel/mm/transparent_hugepage/khugepaged/scan_sleep_millisecs 60000
-on property:persist.vendor.zram.size=*
- setprop vendor.zram.size ${persist.vendor.zram.size}
+# Property from experiments - server config
+on property:persist.device_config.vendor_system_native_boot.zram_size=*
+ setprop vendor.zram.size ${persist.device_config.vendor_system_native_boot.zram_size}
+
+# Property for local test. It can overwrite the server side config
+on property:sys.boot_completed=1 && property:persist.vendor.boot.zram.size=*
+ setprop vendor.zram.size ${persist.vendor.boot.zram.size}
on property:sys.boot_completed=1
swapon_all /vendor/etc/fstab.zram.${vendor.zram.size}
diff --git a/pixelstats/Android.bp b/pixelstats/Android.bp
index f99e8e8..fa27425 100644
--- a/pixelstats/Android.bp
+++ b/pixelstats/Android.bp
@@ -51,6 +51,22 @@
],
}
+genrule {
+ name: "pixelatoms_defs.h",
+ tools: ["stats-log-api-gen"],
+ cmd: "$(location stats-log-api-gen) --header $(genDir)/pixelatoms_defs.h --namespace hardware,google,pixel,PixelAtoms --vendor-proto hardware/google/pixel/pixelstats/pixelatoms.proto",
+ out: [
+ "pixelatoms_defs.h",
+ ],
+}
+
+cc_library {
+ name: "libpixelatoms_defs",
+ vendor: true,
+ generated_headers: ["pixelatoms_defs.h"],
+ export_generated_headers: ["pixelatoms_defs.h"],
+}
+
cc_library {
name: "libpixelstats",
vendor: true,
@@ -58,7 +74,6 @@
tidy_disabled_srcs: [
"MmMetricsReporter.cpp", // b/215238264
- "WlcReporter.cpp", // b/215238264
],
srcs: [
"BatteryCapacityReporter.cpp",
@@ -68,13 +83,12 @@
"DropDetect.cpp",
"MmMetricsReporter.cpp",
"MitigationStatsReporter.cpp",
- "OrientationCollector.cpp",
"PcaChargeStats.cpp",
"StatsHelper.cpp",
"SysfsCollector.cpp",
+ "ThermalStatsReporter.cpp",
"UeventListener.cpp",
"WirelessChargeStats.cpp",
- "WlcReporter.cpp",
],
cflags: [
"-Wall",
diff --git a/pixelstats/BatteryHealthReporter.cpp b/pixelstats/BatteryHealthReporter.cpp
index 41df779..102b5fe 100644
--- a/pixelstats/BatteryHealthReporter.cpp
+++ b/pixelstats/BatteryHealthReporter.cpp
@@ -79,24 +79,28 @@
BatteryHealthStatus::kCurrentImpedanceFieldNumber,
BatteryHealthStatus::kBatteryAgeFieldNumber,
BatteryHealthStatus::kCycleCountFieldNumber,
+ BatteryHealthStatus::kBatteryDisconnectStatusFieldNumber,
};
const int32_t vtier_fields_size = std::size(health_status_stats_fields);
- static_assert(vtier_fields_size == 10, "Unexpected battery health status fields size");
+ static_assert(vtier_fields_size == 11, "Unexpected battery health status fields size");
std::vector<VendorAtomValue> values(vtier_fields_size);
VendorAtomValue val;
- int32_t i = 0, tmp[vtier_fields_size] = {0};
+ int32_t i = 0, fields_size = 0, tmp[vtier_fields_size] = {0};
// health_algo: health_status, health_index,healh_capacity_index,health_imp_index,
- // swelling_cumulative,health_full_capacity,current_impedance, battery_age,cycle_count
- if (sscanf(line, "%d: %d, %d,%d,%d %d,%d,%d %d,%d", &tmp[0], &tmp[1], &tmp[2], &tmp[3], &tmp[4],
- &tmp[5], &tmp[6], &tmp[7], &tmp[8], &tmp[9]) != vtier_fields_size) {
- /* If format isn't as expected, then ignore line on purpose */
+ // swelling_cumulative,health_full_capacity,current_impedance, battery_age,cycle_count,
+ // bpst_status
+ fields_size = sscanf(line, "%d: %d, %d,%d,%d %d,%d,%d %d,%d, %d", &tmp[0], &tmp[1], &tmp[2],
+ &tmp[3], &tmp[4], &tmp[5], &tmp[6], &tmp[7], &tmp[8], &tmp[9], &tmp[10]);
+ if (fields_size < (vtier_fields_size - 1) || fields_size > vtier_fields_size) {
+ // Whether bpst_status exists or not, it needs to be compatible
+ // If format isn't as expected, then ignore line on purpose
return;
}
ALOGD("BatteryHealthStatus: processed %s", line);
- for (i = 0; i < vtier_fields_size; i++) {
+ for (i = 0; i < fields_size; i++) {
val.set<VendorAtomValue::intValue>(tmp[i]);
values[health_status_stats_fields[i] - kVendorAtomOffset] = val;
}
diff --git a/pixelstats/ChargeStatsReporter.cpp b/pixelstats/ChargeStatsReporter.cpp
index 8bc2720..6bc2b64 100644
--- a/pixelstats/ChargeStatsReporter.cpp
+++ b/pixelstats/ChargeStatsReporter.cpp
@@ -45,21 +45,23 @@
const std::string line, const std::string wline_at,
const std::string wline_ac,
const std::string pca_line) {
- int charge_stats_fields[] = {ChargeStats::kAdapterTypeFieldNumber,
- ChargeStats::kAdapterVoltageFieldNumber,
- ChargeStats::kAdapterAmperageFieldNumber,
- ChargeStats::kSsocInFieldNumber,
- ChargeStats::kVoltageInFieldNumber,
- ChargeStats::kSsocOutFieldNumber,
- ChargeStats::kVoltageOutFieldNumber,
- ChargeStats::kAdapterCapabilities0FieldNumber,
- ChargeStats::kAdapterCapabilities1FieldNumber,
- ChargeStats::kAdapterCapabilities2FieldNumber,
- ChargeStats::kAdapterCapabilities3FieldNumber,
- ChargeStats::kAdapterCapabilities4FieldNumber,
- ChargeStats::kReceiverState0FieldNumber,
- ChargeStats::kReceiverState1FieldNumber,
- ChargeStats::kChargeCapacityFieldNumber};
+ int charge_stats_fields[] = {
+ ChargeStats::kAdapterTypeFieldNumber,
+ ChargeStats::kAdapterVoltageFieldNumber,
+ ChargeStats::kAdapterAmperageFieldNumber,
+ ChargeStats::kSsocInFieldNumber,
+ ChargeStats::kVoltageInFieldNumber,
+ ChargeStats::kSsocOutFieldNumber,
+ ChargeStats::kVoltageOutFieldNumber,
+ ChargeStats::kChargeCapacityFieldNumber,
+ ChargeStats::kAdapterCapabilities0FieldNumber,
+ ChargeStats::kAdapterCapabilities1FieldNumber,
+ ChargeStats::kAdapterCapabilities2FieldNumber,
+ ChargeStats::kAdapterCapabilities3FieldNumber,
+ ChargeStats::kAdapterCapabilities4FieldNumber,
+ ChargeStats::kReceiverState0FieldNumber,
+ ChargeStats::kReceiverState1FieldNumber,
+ };
const int32_t chg_fields_size = std::size(charge_stats_fields);
static_assert(chg_fields_size == 15, "Unexpected charge stats fields size");
const int32_t wlc_fields_size = 7;
@@ -70,7 +72,7 @@
ALOGD("processing %s", line.c_str());
if (sscanf(line.c_str(), "%d,%d,%d, %d,%d,%d,%d %d", &tmp[0], &tmp[1], &tmp[2], &tmp[3],
- &tmp[4], &tmp[5], &tmp[6], &tmp[14]) == 8) {
+ &tmp[4], &tmp[5], &tmp[6], &tmp[7]) == 8) {
/* Age Adjusted Charge Rate (AACR) logs an additional battery capacity in order to determine
* the charge curve needed to minimize battery cycle life degradation, while also minimizing
* impact to the user.
@@ -89,8 +91,8 @@
} else {
tmp[0] = wireless_charge_stats_.TranslateSysModeToAtomValue(ssoc_tmp);
ALOGD("wlc: processing %s", wline_ac.c_str());
- if (sscanf(wline_ac.c_str(), "D:%x,%x,%x,%x,%x, %x,%x", &tmp[7], &tmp[8], &tmp[9],
- &tmp[10], &tmp[11], &tmp[12], &tmp[13]) != 7)
+ if (sscanf(wline_ac.c_str(), "D:%x,%x,%x,%x,%x, %x,%x", &tmp[8], &tmp[9], &tmp[10],
+ &tmp[11], &tmp[12], &tmp[13], &tmp[14]) != 7)
ALOGE("Couldn't process %s", wline_ac.c_str());
else
fields_size = chg_fields_size; /* include wlc stats */
@@ -104,14 +106,14 @@
ALOGE("Couldn't process %s", pca_line.c_str());
} else {
fields_size = chg_fields_size; /* include pca stats */
- tmp[9] = pca_rs[2];
- tmp[10] = pca_rs[3];
- tmp[11] = pca_rs[4];
- tmp[13] = pca_rs[1];
+ tmp[10] = pca_rs[2];
+ tmp[11] = pca_rs[3];
+ tmp[12] = pca_rs[4];
+ tmp[14] = pca_rs[1];
if (wline_at.empty()) {
- tmp[7] = pca_ac[0];
- tmp[8] = pca_ac[1];
- tmp[12] = pca_rs[0];
+ tmp[8] = pca_ac[0];
+ tmp[9] = pca_ac[1];
+ tmp[13] = pca_rs[0];
}
}
}
@@ -201,11 +203,12 @@
void ChargeStatsReporter::checkAndReport(const std::shared_ptr<IStats> &stats_client,
const std::string &path) {
std::string file_contents, line, wfile_contents, wline_at, wline_ac, pca_file_contents,
- pca_line, thermal_file_contents;
+ pca_line, thermal_file_contents, gcharger_file_contents;
std::istringstream ss;
bool has_wireless = wireless_charge_stats_.CheckWirelessContentsAndAck(&wfile_contents);
bool has_pca = pca_charge_stats_.CheckPcaContentsAndAck(&pca_file_contents);
- bool has_thermal = checkThermalContentsAndAck(&thermal_file_contents);
+ bool has_thermal = checkContentsAndAck(&thermal_file_contents, kThermalChargeMetricsPath);
+ bool has_gcharger = checkContentsAndAck(&gcharger_file_contents, kGChargerMetricsPath);
if (!ReadFileToString(path.c_str(), &file_contents)) {
ALOGE("Unable to read %s - %s", path.c_str(), strerror(errno));
@@ -255,15 +258,23 @@
ReportVoltageTierStats(stats_client, line.c_str());
}
}
+
+ if (has_gcharger) {
+ std::istringstream wss;
+ wss.str(gcharger_file_contents);
+ while (std::getline(wss, line)) {
+ ReportVoltageTierStats(stats_client, line.c_str());
+ }
+ }
}
-bool ChargeStatsReporter::checkThermalContentsAndAck(std::string *file_contents) {
- if (!ReadFileToString(kThermalChargeMetricsPath.c_str(), file_contents)) {
+bool ChargeStatsReporter::checkContentsAndAck(std::string *file_contents, const std::string &path) {
+ if (!ReadFileToString(path.c_str(), file_contents)) {
return false;
}
- if (!WriteStringToFile("0", kThermalChargeMetricsPath.c_str())) {
- ALOGE("Couldn't clear %s - %s", kThermalChargeMetricsPath.c_str(), strerror(errno));
+ if (!WriteStringToFile("0", path.c_str())) {
+ ALOGE("Couldn't clear %s - %s", path.c_str(), strerror(errno));
return false;
}
return true;
diff --git a/pixelstats/MmMetricsReporter.cpp b/pixelstats/MmMetricsReporter.cpp
index 558fd2d..874cc5b 100644
--- a/pixelstats/MmMetricsReporter.cpp
+++ b/pixelstats/MmMetricsReporter.cpp
@@ -18,6 +18,7 @@
#include <aidl/android/frameworks/stats/IStats.h>
#include <android-base/file.h>
+#include <android-base/parsedouble.h>
#include <android-base/parseint.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
@@ -53,13 +54,13 @@
{"nr_anon_pages", PixelMmMetricsPerHour::kAnonPagesFieldNumber, false},
{"nr_file_pages", PixelMmMetricsPerHour::kFilePagesFieldNumber, false},
{"nr_slab_reclaimable", PixelMmMetricsPerHour::kSlabReclaimableFieldNumber, false},
+ {"nr_slab_unreclaimable", PixelMmMetricsPerHour::kSlabUnreclaimableFieldNumber, false},
{"nr_zspages", PixelMmMetricsPerHour::kZspagesFieldNumber, false},
{"nr_unevictable", PixelMmMetricsPerHour::kUnevictableFieldNumber, false},
};
const std::vector<MmMetricsReporter::MmMetricsInfo> MmMetricsReporter::kMmMetricsPerDayInfo = {
{"workingset_refault", PixelMmMetricsPerDay::kWorkingsetRefaultFieldNumber, true},
- {"workingset_refault_file", PixelMmMetricsPerDay::kWorkingsetRefaultFieldNumber, true},
{"pswpin", PixelMmMetricsPerDay::kPswpinFieldNumber, true},
{"pswpout", PixelMmMetricsPerDay::kPswpoutFieldNumber, true},
{"allocstall_dma", PixelMmMetricsPerDay::kAllocstallDmaFieldNumber, true},
@@ -78,6 +79,18 @@
{"pgalloc_costly_order", PixelMmMetricsPerDay::kPgallocHighFieldNumber, true},
{"pgcache_hit", PixelMmMetricsPerDay::kPgcacheHitFieldNumber, true},
{"pgcache_miss", PixelMmMetricsPerDay::kPgcacheMissFieldNumber, true},
+ {"workingset_refault_file", PixelMmMetricsPerDay::kWorkingsetRefaultFileFieldNumber, true},
+ {"workingset_refault_anon", PixelMmMetricsPerDay::kWorkingsetRefaultAnonFieldNumber, true},
+ {"compact_success", PixelMmMetricsPerDay::kCompactSuccessFieldNumber, true},
+ {"compact_fail", PixelMmMetricsPerDay::kCompactFailFieldNumber, true},
+ {"kswapd_low_wmark_hit_quickly", PixelMmMetricsPerDay::kKswapdLowWmarkHqFieldNumber, true},
+ {"kswapd_high_wmark_hit_quickly", PixelMmMetricsPerDay::kKswapdHighWmarkHqFieldNumber,
+ true},
+ {"thp_file_alloc", PixelMmMetricsPerDay::kThpFileAllocFieldNumber, true},
+ {"thp_zero_page_alloc", PixelMmMetricsPerDay::kThpZeroPageAllocFieldNumber, true},
+ {"thp_split_page", PixelMmMetricsPerDay::kThpSplitPageFieldNumber, true},
+ {"thp_migration_split", PixelMmMetricsPerDay::kThpMigrationSplitFieldNumber, true},
+ {"thp_deferred_split_page", PixelMmMetricsPerDay::kThpDeferredSplitPageFieldNumber, true},
};
const std::vector<MmMetricsReporter::MmMetricsInfo> MmMetricsReporter::kCmaStatusInfo = {
@@ -146,7 +159,11 @@
kIonTotalPoolsPath("/sys/kernel/dma_heap/total_pools_kb"),
kIonTotalPoolsPathForLegacy("/sys/kernel/ion/total_pools_kb"),
kGpuTotalPages("/sys/kernel/pixel_stat/gpu/mem/total_page_count"),
- kPixelStatMm("/sys/kernel/pixel_stat/mm") {
+ kCompactDuration("/sys/kernel/pixel_stat/mm/compaction/mm_compaction_duration"),
+ kDirectReclaimBasePath("/sys/kernel/pixel_stat/mm/vmscan/direct_reclaim"),
+ kPixelStatMm("/sys/kernel/pixel_stat/mm"),
+ prev_compaction_duration_(kNumCompactionDurationPrevMetrics, 0),
+ prev_direct_reclaim_(kNumDirectReclaimPrevMetrics, 0) {
is_user_build_ = checkUserBuild();
ker_mm_metrics_support_ = checkKernelMMMetricSupport();
}
@@ -170,6 +187,90 @@
return true;
}
+/*
+ * This function reads whole file and parses tokens separated by <delim> into
+ * long integers. Useful for direct reclaim & compaction duration sysfs nodes.
+ * Data write is using all or none policy: It will not write partial data unless
+ * all data values are good.
+ *
+ * path: file to open/read
+ * data: where to store the results
+ * start_idx: index into data[] where to start saving the results
+ * delim: delimiters separating different longs
+ * skip: how many resulting longs to skip before saving
+ * nonnegtive: set to true to validate positive numbers
+ *
+ * Return value: number of longs actually stored on success. negative
+ * error codes on errors.
+ */
+static int ReadFileToLongs(const std::string &path, std::vector<long> *data, int start_idx,
+ const char *delim, int skip, bool nonnegative = false) {
+ std::vector<long> out;
+ enum { err_read_file = -1, err_parse = -2 };
+ std::string file_contents;
+
+ if (!ReadFileToString(path, &file_contents)) {
+ // Don't print this log if the file doesn't exist, since logs will be printed repeatedly.
+ if (errno != ENOENT) {
+ ALOGI("Unable to read %s - %s", path.c_str(), strerror(errno));
+ }
+ return err_read_file;
+ }
+
+ file_contents = android::base::Trim(file_contents);
+ std::vector<std::string> words = android::base::Tokenize(file_contents, delim);
+ if (words.size() == 0)
+ return 0;
+
+ for (auto &w : words) {
+ if (skip) {
+ skip--;
+ continue;
+ }
+ long tmp;
+ if (!android::base::ParseInt(w, &tmp) || (nonnegative && tmp < 0))
+ return err_parse;
+ out.push_back(tmp);
+ }
+
+ int min_size = std::max(static_cast<int>(out.size()) + start_idx, 0);
+ if (min_size > data->size())
+ data->resize(min_size);
+ std::copy(out.begin(), out.end(), data->begin() + start_idx);
+
+ return out.size();
+}
+
+/*
+ * This function calls ReadFileToLongs, and checks the expected number
+ * of long integers read. Useful for direct reclaim & compaction duration
+ * sysfs nodes.
+ *
+ * path: file to open/read
+ * data: where to store the results
+ * start_idx: index into data[] where to start saving the results
+ * delim: delimiters separating different longs
+ * skip: how many resulting longs to skip before saving
+ * expected_num: number of expected longs to be read.
+ * nonnegtive: set to true to validate positive numbers
+ *
+ * Return value: true if successfully get expected number of long values.
+ * otherwise false.
+ */
+static inline bool ReadFileToLongsCheck(const std::string &path, std::vector<long> *store,
+ int start_idx, const char *delim, int skip,
+ int expected_num, bool nonnegative = false) {
+ int num = ReadFileToLongs(path, store, start_idx, delim, skip, nonnegative);
+
+ if (num == expected_num)
+ return true;
+
+ int last_idx = std::min(start_idx + expected_num, static_cast<int>(store->size()));
+ std::fill(store->begin() + start_idx, store->begin() + last_idx, -1);
+
+ return false;
+}
+
bool MmMetricsReporter::reportVendorAtom(const std::shared_ptr<IStats> &stats_client, int atom_id,
const std::vector<VendorAtomValue> &values,
const std::string &atom_name) {
@@ -272,7 +373,7 @@
if (max_idx < entry.atom_key)
max_idx = entry.atom_key;
}
- int size = max_idx - kVendorAtomOffset + 1;
+ unsigned int size = max_idx - kVendorAtomOffset + 1;
if (atom_values->size() < size)
atom_values->resize(size, tmp);
@@ -301,6 +402,10 @@
(*prev_mm_metrics) = mm_metrics;
}
+void MmMetricsReporter::aggregatePixelMmMetricsPer5Min() {
+ aggregatePressureStall();
+}
+
void MmMetricsReporter::logPixelMmMetricsPerHour(const std::shared_ptr<IStats> &stats_client) {
if (!MmMetricsSupported())
return;
@@ -312,28 +417,23 @@
uint64_t ion_total_pools = getIonTotalPools();
uint64_t gpu_memory = getGpuMemory();
- std::vector<VendorAtomValue> values;
- bool is_first_atom = (prev_hour_vmstat_.size() == 0) ? true : false;
- fillAtomValues(kMmMetricsPerHourInfo, vmstat, &prev_hour_vmstat_, &values);
-
- // resize values to add the following fields
+ // allocate enough values[] entries for the metrics.
VendorAtomValue tmp;
tmp.set<VendorAtomValue::longValue>(0);
- int size = PixelMmMetricsPerHour::kGpuMemoryFieldNumber - kVendorAtomOffset + 1;
- if (values.size() < size) {
- values.resize(size, tmp);
- }
+ int last_value_index =
+ PixelMmMetricsPerHour::kPsiMemSomeAvg300AvgFieldNumber - kVendorAtomOffset;
+ std::vector<VendorAtomValue> values(last_value_index + 1, tmp);
+
+ fillAtomValues(kMmMetricsPerHourInfo, vmstat, &prev_hour_vmstat_, &values);
tmp.set<VendorAtomValue::longValue>(ion_total_pools);
values[PixelMmMetricsPerHour::kIonTotalPoolsFieldNumber - kVendorAtomOffset] = tmp;
tmp.set<VendorAtomValue::longValue>(gpu_memory);
values[PixelMmMetricsPerHour::kGpuMemoryFieldNumber - kVendorAtomOffset] = tmp;
+ fillPressureStallAtom(&values);
- // Don't report the first atom to avoid big spike in accumulated values.
- if (!is_first_atom) {
- // Send vendor atom to IStats HAL
- reportVendorAtom(stats_client, PixelAtoms::Atom::kPixelMmMetricsPerHour, values,
- "PixelMmMetricsPerHour");
- }
+ // Send vendor atom to IStats HAL
+ reportVendorAtom(stats_client, PixelAtoms::Atom::kPixelMmMetricsPerHour, values,
+ "PixelMmMetricsPerHour");
}
void MmMetricsReporter::logPixelMmMetricsPerDay(const std::shared_ptr<IStats> &stats_client) {
@@ -344,8 +444,21 @@
if (vmstat.size() == 0)
return;
- std::vector<VendorAtomValue> values;
+ std::vector<long> direct_reclaim;
+ readDirectReclaimStat(&direct_reclaim);
+
+ std::vector<long> compaction_duration;
+ readCompactionDurationStat(&compaction_duration);
+
bool is_first_atom = (prev_day_vmstat_.size() == 0) ? true : false;
+
+ // allocate enough values[] entries for the metrics.
+ VendorAtomValue tmp;
+ tmp.set<VendorAtomValue::longValue>(0);
+ int last_value_index =
+ PixelMmMetricsPerDay::kThpDeferredSplitPageFieldNumber - kVendorAtomOffset;
+ std::vector<VendorAtomValue> values(last_value_index + 1, tmp);
+
fillAtomValues(kMmMetricsPerDayInfo, vmstat, &prev_day_vmstat_, &values);
std::map<std::string, uint64_t> pixel_vmstat =
@@ -355,6 +468,8 @@
&prev_kswapd_stime_, &values);
fillProcessStime(PixelMmMetricsPerDay::kKcompactdStimeClksFieldNumber, "kcompactd0",
&kcompactd_pid_, &prev_kcompactd_stime_, &values);
+ fillDirectReclaimStatAtom(direct_reclaim, &values);
+ fillCompactionDurationStatAtom(direct_reclaim, &values);
// Don't report the first atom to avoid big spike in accumulated values.
if (!is_first_atom) {
@@ -498,6 +613,544 @@
}
/**
+ * This function reads compaction duration sysfs node
+ * (/sys/kernel/pixel_stat/mm/compaction/mm_compaction_duration)
+ *
+ * store: vector to save compaction duration info
+ */
+void MmMetricsReporter::readCompactionDurationStat(std::vector<long> *store) {
+ static const std::string path(kCompactDuration);
+ constexpr int num_metrics = 6;
+
+ store->resize(num_metrics);
+
+ int start_idx = 0;
+ int expected_num = num_metrics;
+
+ if (!ReadFileToLongsCheck(path, store, start_idx, " ", 1, expected_num, true)) {
+ ALOGI("Unable to read %s for the direct reclaim info.", path.c_str());
+ }
+}
+
+/**
+ * This function fills atom values (values) from acquired compaction duration
+ * information from vector store
+ *
+ * store: the already collected (by readCompactionDurationStat()) compaction
+ * duration information
+ * values: the atom value vector to be filled.
+ */
+void MmMetricsReporter::fillCompactionDurationStatAtom(const std::vector<long> &store,
+ std::vector<VendorAtomValue> *values) {
+ // first metric index
+ constexpr int start_idx = PixelMmMetricsPerDay::kCompactionTotalTimeFieldNumber;
+ constexpr int num_metrics = 6;
+
+ if (!MmMetricsSupported())
+ return;
+
+ int size = start_idx + num_metrics - kVendorAtomOffset;
+ if (values->size() < size)
+ values->resize(size);
+
+ for (int i = 0; i < num_metrics; i++) {
+ VendorAtomValue tmp;
+ if (store[i] == -1) {
+ tmp.set<VendorAtomValue::longValue>(0);
+ } else {
+ tmp.set<VendorAtomValue::longValue>(store[i] - prev_compaction_duration_[i]);
+ prev_compaction_duration_[i] = store[i];
+ }
+ (*values)[start_idx + i] = tmp;
+ }
+ prev_compaction_duration_ = store;
+}
+
+/**
+ * This function reads direct reclaim sysfs node (4 files:
+ * /sys/kernel/pixel_stat/mm/vmscan/direct_reclaim/<level>/latency_stat,
+ * where <level> = native, top, visible, other.), and save total time and
+ * 4 latency information per file. Total (1+4) x 4 = 20 metrics will be
+ * saved.
+ *
+ * store: vector to save direct reclaim info
+ */
+void MmMetricsReporter::readDirectReclaimStat(std::vector<long> *store) {
+ static const std::string base_path(kDirectReclaimBasePath);
+ static const std::vector<std::string> dr_levels{"native", "top", "visible", "other"};
+ static const std::string sysfs_name = "latency_stat";
+ constexpr int num_metrics_per_file = 5;
+ int num_file = dr_levels.size();
+ int num_metrics = num_metrics_per_file * num_file;
+
+ store->resize(num_metrics);
+ int pass = -1;
+ for (auto level : dr_levels) {
+ ++pass;
+ std::string path = base_path + '/' + level + '/' + sysfs_name;
+ int start_idx = pass * num_metrics_per_file;
+ int expected_num = num_metrics_per_file;
+ if (!ReadFileToLongsCheck(path, store, start_idx, " ", 1, expected_num, true)) {
+ ALOGI("Unable to read %s for the direct reclaim info.", path.c_str());
+ }
+ }
+}
+
+/**
+ * This function fills atom values (values) from acquired direct reclaim
+ * information from vector store
+ *
+ * store: the already collected (by readDirectReclaimStat()) direct reclaim
+ * information
+ * values: the atom value vector to be filled.
+ */
+void MmMetricsReporter::fillDirectReclaimStatAtom(const std::vector<long> &store,
+ std::vector<VendorAtomValue> *values) {
+ // first metric index
+ constexpr int start_idx = PixelMmMetricsPerDay::kDirectReclaimNativeLatencyTotalTimeFieldNumber;
+ constexpr int num_metrics = 20; /* num_metrics_per_file * num_file */
+
+ if (!MmMetricsSupported())
+ return;
+
+ int size = start_idx + num_metrics - kVendorAtomOffset;
+ if (values->size() < size)
+ values->resize(size);
+
+ for (int i = 0; i < num_metrics; i++) {
+ VendorAtomValue tmp;
+ tmp.set<VendorAtomValue::longValue>(store[i] - prev_direct_reclaim_[i]);
+ (*values)[start_idx + i] = tmp;
+ }
+ prev_direct_reclaim_ = store;
+}
+
+/**
+ * This function reads pressure (PSI) files (loop thru all 3 files: cpu, io, and
+ * memory) and calls the parser to parse and store the metric values.
+ * Note that each file have two lines (except cpu has one line only): one with
+ * a leading "full", and the other with a leading "some", showing the category
+ * for that line.
+ * A category has 4 metrics, avg10, avg60, avg300, and total.
+ * i.e. the moving average % of PSI in 10s, 60s, 300s time window plus lastly
+ * the total stalled time, except that 'cpu' has no 'full' category.
+ * In total, we have 3 x 2 x 4 - 4 = 24 - 4 = 20 metrics, arranged in
+ * the order of
+ *
+ * cpu_some_avg<xyz>
+ * cpu_some_total
+ * io_full_avg<xyz>
+ * io_full_total
+ * io_some_avg<xyz>
+ * io_some_total
+ * mem_full_avg<xyz>
+ * mem_full_total
+ * mem_some_avg<xyz>
+ * mem_some_total
+ *
+ * where <xyz>=10, 60, 300 in the order as they appear.
+ *
+ * Note that for those avg values (i.e. <abc>_<def>_avg<xyz>), they
+ * are in percentage with 2-decimal digit accuracy. We will use an
+ * integer in 2-decimal fixed point format to represent the values.
+ * i.e. value x 100, or to cope with floating point errors,
+ * floor(value x 100 + 0.5)
+ *
+ * In fact, in newer kernels, "cpu" PSI has no "full" category. Some
+ * old kernel has them all zeros, to keep backward compatibility. The
+ * parse function called by this function is able to detect and ignore
+ * the "cpu, full" category.
+ *
+ * sample pressure stall files:
+ * /proc/pressure # cat cpu
+ * some avg10=2.93 avg60=3.17 avg300=3.15 total=94628150260
+ * /proc/pressure # cat io
+ * some avg10=1.06 avg60=1.15 avg300=1.18 total=37709873805
+ * full avg10=1.06 avg60=1.10 avg300=1.11 total=36592322936
+ * /proc/pressure # cat memory
+ * some avg10=0.00 avg60=0.00 avg300=0.00 total=29705314
+ * full avg10=0.00 avg60=0.00 avg300=0.00 total=17234456
+ *
+ * PSI information definitions could be found at
+ * https://www.kernel.org/doc/html/latest/accounting/psi.html
+ *
+ * basePath: the base path to the pressure stall information
+ * store: pointer to the vector to store the 20 metrics in the mentioned
+ * order
+ */
+void MmMetricsReporter::readPressureStall(const char *basePath, std::vector<long> *store) {
+ constexpr int kTypeIdxCpu = 0;
+
+ // Callers should have already prepared this, but we resize it here for safety
+ store->resize(kPsiNumAllMetrics);
+ std::fill(store->begin(), store->end(), -1);
+
+ // To make the process unified, we prepend an imaginary "cpu + full"
+ // type-category combination. Now, each file (cpu, io, memnry) contains
+ // two categories, i.e. "full" and "some".
+ // Each category has <kPsiNumNames> merics and thus need that many entries
+ // to store them, except that the first category (the imaginary one) do not
+ // need any storage. So we set the save index for the 1st file ("cpu") to
+ // -kPsiNumNames.
+ int file_save_idx = -kPsiNumNames;
+
+ // loop thru all pressure stall files: cpu, io, memory
+ for (int type_idx = 0; type_idx < kPsiNumFiles;
+ ++type_idx, file_save_idx += kPsiMetricsPerFile) {
+ std::string file_contents;
+ std::string path = std::string("") + basePath + '/' + kPsiTypes[type_idx];
+
+ if (!ReadFileToString(path, &file_contents)) {
+ // Don't print this log if the file doesn't exist, since logs will be printed
+ // repeatedly.
+ if (errno != ENOENT)
+ ALOGI("Unable to read %s - %s", path.c_str(), strerror(errno));
+ goto err_out;
+ }
+ if (!MmMetricsReporter::parsePressureStallFileContent(type_idx == kTypeIdxCpu,
+ file_contents, store, file_save_idx))
+ goto err_out;
+ }
+ return;
+
+err_out:
+ std::fill(store->begin(), store->end(), -1);
+}
+
+/*
+ * This function parses a pressure stall file, which contains two
+ * lines, i.e. the "full", and "some" lines, except that the 'cpu' file
+ * contains only one line ("some"). Refer to the function comments of
+ * readPressureStall() for pressure stall file format.
+ *
+ * For old kernel, 'cpu' file might contain an extra line for "full", which
+ * will be ignored.
+ *
+ * is_cpu: Is the data from the file 'cpu'
+ * lines: the file content
+ * store: the output vector to hold the parsed data.
+ * file_save_idx: base index to start saving 'store' vector for this file.
+ *
+ * Return value: true on success, false otherwise.
+ */
+bool MmMetricsReporter::parsePressureStallFileContent(bool is_cpu, std::string lines,
+ std::vector<long> *store, int file_save_idx) {
+ constexpr int kNumOfWords = 5; // expected number of words separated by spaces.
+ constexpr int kCategoryFull = 0;
+
+ std::istringstream data(lines);
+ std::string line;
+
+ while (std::getline(data, line)) {
+ int category_idx = 0;
+
+ line = android::base::Trim(line);
+ std::vector<std::string> words = android::base::Tokenize(line, " ");
+ if (words.size() != kNumOfWords) {
+ ALOGE("PSI parse fail: num of words = %d != expected %d",
+ static_cast<int>(words.size()), kNumOfWords);
+ return false;
+ }
+
+ // words[0] should be either "full" or "some", the category name.
+ for (auto &cat : kPsiCategories) {
+ if (words[0].compare(cat) == 0)
+ break;
+ ++category_idx;
+ }
+ if (category_idx == kPsiNumCategories) {
+ ALOGE("PSI parse fail: unknown category %s", words[0].c_str());
+ return false;
+ }
+
+ // skip (cpu, full) combination.
+ if (is_cpu && category_idx == kCategoryFull) {
+ ALOGI("kernel: old PSI sysfs node.");
+ continue;
+ }
+
+ // Now we have separated words in a vector, e.g.
+ // ["some", "avg10=2.93", "avg60=3.17", "avg300=3.15", total=94628150260"]
+ // call parsePressureStallWords to parse them.
+ int line_save_idx = file_save_idx + category_idx * kPsiNumNames;
+ if (!parsePressureStallWords(words, store, line_save_idx))
+ return false;
+ }
+ return true;
+}
+
+// This function parses the already split words, e.g.
+// ["some", "avg10=0.00", "avg60=0.00", "avg300=0.00", "total=29705314"],
+// from a line (category) in a pressure stall file.
+//
+// words: the split words in the form of "name=value"
+// store: the output vector
+// line_save_idx: the base start index to save in vector for this line (category)
+//
+// Return value: true on success, false otherwise.
+bool MmMetricsReporter::parsePressureStallWords(std::vector<std::string> words,
+ std::vector<long> *store, int line_save_idx) {
+ // Skip the first word, which is already parsed by the caller.
+ // All others are value pairs in "name=value" form.
+ // e.g. ["some", "avg10=0.00", "avg60=0.00", "avg300=0.00", "total=29705314"]
+ // "some" is skipped.
+ for (int i = 1; i < words.size(); ++i) {
+ std::vector<std::string> metric = android::base::Tokenize(words[i], "=");
+ if (metric.size() != 2) {
+ ALOGE("%s: parse error (name=value) @ idx %d", __FUNCTION__, i);
+ return false;
+ }
+ if (!MmMetricsReporter::savePressureMetrics(metric[0], metric[1], store, line_save_idx))
+ return false;
+ }
+ return true;
+}
+
+// This function parses one value pair in "name=value" format, and depending on
+// the name, save to its proper location in the store vector.
+// name = "avg10" -> save to index base_save_idx.
+// name = "avg60" -> save to index base_save_idx + 1.
+// name = "avg300" -> save to index base_save_idx + 2.
+// name = "total" -> save to index base_save_idx + 3.
+//
+// name: the metrics name
+// value: the metrics value
+// store: the output vector
+// base_save_idx: the base save index
+//
+// Return value: true on success, false otherwise.
+//
+bool MmMetricsReporter::savePressureMetrics(std::string name, std::string value,
+ std::vector<long> *store, int base_save_idx) {
+ int name_idx = 0;
+ constexpr int kNameIdxTotal = 3;
+
+ for (auto &mn : kPsiMetricNames) {
+ if (name.compare(mn) == 0)
+ break;
+ ++name_idx;
+ }
+ if (name_idx == kPsiNumNames) {
+ ALOGE("%s: parse error: unknown metric name.", __FUNCTION__);
+ return false;
+ }
+
+ long out;
+ if (name_idx == kNameIdxTotal) {
+ // 'total' metrics
+ unsigned long tmp;
+ if (!android::base::ParseUint(value, &tmp))
+ out = -1;
+ else
+ out = tmp;
+ } else {
+ // 'avg' metrics
+ double d = -1.0;
+ if (android::base::ParseDouble(value, &d))
+ out = static_cast<long>(d * 100 + 0.5);
+ else
+ out = -1;
+ }
+
+ if (base_save_idx + name_idx >= store->size()) {
+ // should never reach here
+ ALOGE("out of bound access to store[] (src line %d) @ index %d", __LINE__,
+ base_save_idx + name_idx);
+ return false;
+ } else {
+ (*store)[base_save_idx + name_idx] = out;
+ }
+ return true;
+}
+
+/**
+ * This function reads in the current pressure (PSI) information, and aggregates
+ * it (except for the "total" information, which will overwrite
+ * the previous value without aggregation.
+ *
+ * data are arranged in the following order, and must comply the order defined
+ * in the proto:
+ *
+ * // note: these 5 'total' metrics are not aggregated.
+ * cpu_some_total
+ * io_full_total
+ * io_some_total
+ * mem_full_total
+ * mem_some_total
+ *
+ * // 9 aggregated metrics as above avg<xyz>_<aggregate>
+ * // where <xyz> = 10, 60, 300; <aggregate> = min, max, sum
+ * cpu_some_avg10_min
+ * cpu_some_avg10_max
+ * cpu_some_avg10_sum
+ * cpu_some_avg60_min
+ * cpu_some_avg60_max
+ * cpu_some_avg60_sum
+ * cpu_some_avg300_min
+ * cpu_some_avg300_max
+ * cpu_some_avg300_sum
+ *
+ * // similar 9 metrics as above avg<xyz>_<aggregate>
+ * io_full_avg<xyz>_<aggregate>
+ *
+ * // similar 9 metrics as above avg<xyz>_<aggregate>
+ * io_some_avg<xyz>_<aggregate>
+ *
+ * // similar 9 metrics as above avg<xyz>_<aggregate>
+ * mem_full_avg<xyz>_<aggregate>
+ *
+ * // similar 9 metrics as above avg<xyz>_<aggregate>
+ * mem_some_avg<xyz>_<aggregate>
+ *
+ * In addition, it increases psi_data_set_count_ by 1 (in order to calculate
+ * the average from the "_sum" aggregate.)
+ */
+void MmMetricsReporter::aggregatePressureStall() {
+ constexpr int kFirstTotalOffset = kPsiNumAvgs;
+
+ if (!MmMetricsSupported())
+ return;
+
+ std::vector<long> psi(kPsiNumAllMetrics, -1);
+ readPressureStall(kPsiBasePath, &psi);
+
+ // Pre-check for possible later out of bound error, if readPressureStall()
+ // decreases the vector size.
+ // It's for safety only. The condition should never be true.
+ if (psi.size() != kPsiNumAllMetrics) {
+ ALOGE("Wrong psi[] size %d != expected %d after read.", static_cast<int>(psi.size()),
+ kPsiNumAllMetrics);
+ return;
+ }
+
+ // check raw metrics and preventively handle errors: Although we don't expect read sysfs
+ // node could fail. Discard all current readings on any error.
+ for (int i = 0; i < kPsiNumAllMetrics; ++i) {
+ if (psi[i] == -1) {
+ ALOGE("Bad data @ psi[%ld] = -1", psi[i]);
+ goto err_out;
+ }
+ }
+
+ // "total" metrics are accumulative: just replace the previous accumulation.
+ for (int i = 0; i < kPsiNumAllTotals; ++i) {
+ int psi_idx;
+
+ psi_idx = i * kPsiNumNames + kFirstTotalOffset;
+ if (psi_idx >= psi.size()) {
+ // should never reach here
+ ALOGE("out of bound access to psi[] (src line %d) @ index %d", __LINE__, psi_idx);
+ goto err_out;
+ } else {
+ psi_total_[i] = psi[psi_idx];
+ }
+ }
+
+ // "avg" metrics will be aggregated to min, max and sum
+ // later on, the sum will be divided by psi_data_set_count_ to get the average.
+ int aggr_idx;
+ aggr_idx = 0;
+ for (int psi_idx = 0; psi_idx < kPsiNumAllMetrics; ++psi_idx) {
+ if (psi_idx % kPsiNumNames == kFirstTotalOffset)
+ continue; // skip 'total' metrics, already processed.
+
+ if (aggr_idx + 3 > kPsiNumAllUploadAvgMetrics) {
+ // should never reach here
+ ALOGE("out of bound access to psi_aggregated_[] (src line %d) @ index %d ~ %d",
+ __LINE__, aggr_idx, aggr_idx + 2);
+ return; // give up avgs, but keep totals (so don't go err_out
+ }
+
+ long value = psi[psi_idx];
+ if (psi_data_set_count_ == 0) {
+ psi_aggregated_[aggr_idx++] = value;
+ psi_aggregated_[aggr_idx++] = value;
+ psi_aggregated_[aggr_idx++] = value;
+ } else {
+ psi_aggregated_[aggr_idx++] = std::min(value, psi_aggregated_[aggr_idx]);
+ psi_aggregated_[aggr_idx++] = std::max(value, psi_aggregated_[aggr_idx]);
+ psi_aggregated_[aggr_idx++] += value;
+ }
+ }
+ ++psi_data_set_count_;
+ return;
+
+err_out:
+ for (int i = 0; i < kPsiNumAllTotals; ++i) psi_total_[i] = -1;
+}
+
+/**
+ * This function fills atom values (values) from psi_aggregated_[]
+ *
+ * values: the atom value vector to be filled.
+ */
+void MmMetricsReporter::fillPressureStallAtom(std::vector<VendorAtomValue> *values) {
+ constexpr int avg_of_avg_offset = 2;
+ constexpr int total_start_idx =
+ PixelMmMetricsPerHour::kPsiCpuSomeTotalFieldNumber - kVendorAtomOffset;
+ constexpr int avg_start_idx = total_start_idx + kPsiNumAllTotals;
+
+ if (!MmMetricsSupported())
+ return;
+
+ VendorAtomValue tmp;
+
+ // The caller should have setup the correct total size,
+ // but we check and extend the size when it's too small for safety.
+ unsigned int min_value_size = total_start_idx + kPsiNumAllUploadMetrics;
+ if (values->size() < min_value_size)
+ values->resize(min_value_size);
+
+ // "total" metric
+ int metric_idx = total_start_idx;
+ for (int save = 0; save < kPsiNumAllTotals; ++save, ++metric_idx) {
+ if (psi_data_set_count_ == 0)
+ psi_total_[save] = -1; // no data: invalidate the current total
+
+ // A good difference needs a good previous value and a good current value.
+ if (psi_total_[save] != -1 && prev_psi_total_[save] != -1)
+ tmp.set<VendorAtomValue::longValue>(psi_total_[save] - prev_psi_total_[save]);
+ else
+ tmp.set<VendorAtomValue::longValue>(-1);
+
+ prev_psi_total_[save] = psi_total_[save];
+ if (metric_idx >= values->size()) {
+ // should never reach here
+ ALOGE("out of bound access to value[] for psi-total @ index %d", metric_idx);
+ goto cleanup;
+ } else {
+ (*values)[metric_idx] = tmp;
+ }
+ }
+
+ // "avg" metrics -> aggregate to min, max, and avg of the original avg
+ metric_idx = avg_start_idx;
+ for (int save = 0; save < kPsiNumAllUploadAvgMetrics; ++save, ++metric_idx) {
+ if (psi_data_set_count_) {
+ if (save % kPsiNumOfAggregatedType == avg_of_avg_offset) {
+ // avg of avg
+ tmp.set<VendorAtomValue::intValue>(psi_aggregated_[save] / psi_data_set_count_);
+ } else {
+ // min or max of avg
+ tmp.set<VendorAtomValue::intValue>(psi_aggregated_[save]);
+ }
+ } else {
+ tmp.set<VendorAtomValue::intValue>(-1);
+ }
+ if (metric_idx >= values->size()) {
+ // should never reach here
+ ALOGE("out of bound access to value[] for psi-avg @ index %d", metric_idx);
+ goto cleanup;
+ } else {
+ (*values)[metric_idx] = tmp;
+ }
+ }
+
+cleanup:
+ psi_data_set_count_ = 0;
+}
+
+/**
* This function is to collect CMA metrics and upload them.
* The CMA metrics are collected by readCmaStat(), copied into atom values
* by fillAtomValues(), and then uploaded by reportVendorAtom(). The collected
diff --git a/pixelstats/OWNERS b/pixelstats/OWNERS
index 3c862ab..124a755 100644
--- a/pixelstats/OWNERS
+++ b/pixelstats/OWNERS
@@ -1,3 +1,5 @@
-stayfan@google.com
-joaodias@google.com
achant@google.com
+apelosi@google.com
+joaodias@google.com
+stayfan@google.com
+vincentwang@google.com
diff --git a/pixelstats/OrientationCollector.cpp b/pixelstats/OrientationCollector.cpp
deleted file mode 100644
index b33d3c6..0000000
--- a/pixelstats/OrientationCollector.cpp
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2020 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-orientation"
-
-#include <android/sensor.h>
-#include <log/log.h>
-#include <pixelstats/OrientationCollector.h>
-
-namespace android {
-namespace hardware {
-namespace google {
-namespace pixel {
-
-#define GET_EVENT_TIMEOUT_MILLIS 200
-#define SENSOR_TYPE_DEVICE_ORIENTATION 27
-#define ORIENTATION_UNKNOWN -1
-
-sp<OrientationCollector> OrientationCollector::createOrientationCollector() {
- sp<OrientationCollector> orientationCollector;
- orientationCollector = new OrientationCollector();
- if (orientationCollector != nullptr && orientationCollector->init() < 0) {
- orientationCollector->disableOrientationSensor();
- return nullptr;
- }
- return orientationCollector;
-}
-
-/* pollOrientation
- * orientation: [out] orientation value from sensor Hal.
- * Return: OK, or error code from sensor Hal
- */
-int32_t OrientationCollector::pollOrientation(int *orientation) {
- int err = OK;
- int eventCount = 0;
- ASensorEvent sensorEvent;
-
- *orientation = ORIENTATION_UNKNOWN;
- /* Get sensor events. */
- /* Get sensor sample events. */
- err = getEvents(&sensorEvent, 1, &eventCount);
- if (eventCount > 0) {
- ALOGV("%s: ##event data: %f,%f,%f", __func__, sensorEvent.data[0], sensorEvent.data[1],
- sensorEvent.data[2]);
- *orientation = sensorEvent.data[0];
- }
- return err;
-}
-
-/*
- * Collects sensor samples and returns them in the sensor sample event list
- * specified by event_list. Limits the number of returned sample events to the
- * value specified by event_list_size. Returns the number of returned sample
- * events in p_event_count.
- *
- * event_list List of collected sample events.
- * event_list_size Size of sample event list.
- * event_count Returned count of the number of collected sample
- * events.
- */
-int OrientationCollector::getEvents(ASensorEvent *event_list, size_t event_list_size,
- int *event_count) {
- int rv;
- int err = OK;
-
- /* Wait for a sensor event to be available */
- /* The timeout is used to prevent blocking for long time, when sensor pool is
- * empty, for example the device is put on a horizontal wireless charger.
- */
- rv = ALooper_pollOnce(GET_EVENT_TIMEOUT_MILLIS, nullptr, nullptr, nullptr);
- if (rv == ALOOPER_POLL_ERROR) {
- ALOGI("Sensor event looper returned a poll error.\n");
- err = UNKNOWN_ERROR;
- goto out;
- }
-
- /* Get sensor events. */
- *event_count = ASensorEventQueue_getEvents(mQueue, event_list, event_list_size);
-
-out:
- return err;
-}
-
-int32_t OrientationCollector::init() {
- int err = OK;
- ALooper *looper = nullptr;
-
- // Get orientation sensor events from the NDK
- mSensorManager = ASensorManager_getInstanceForPackage(nullptr);
- if (mSensorManager == nullptr) {
- ALOGE("%s: Unable to get sensorManager.\n", __func__);
- return UNKNOWN_ERROR;
- }
- looper = ALooper_forThread();
- if (looper == nullptr) {
- looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
- }
- if (looper == nullptr) {
- ALOGE("%s: Failed to prepare an event looper.\n", __FUNCTION__);
- return UNKNOWN_ERROR;
- }
- mQueue = ASensorManager_createEventQueue(mSensorManager, looper, 0, nullptr, nullptr);
- mOrientationSensor =
- ASensorManager_getDefaultSensor(mSensorManager, SENSOR_TYPE_DEVICE_ORIENTATION);
- if (mOrientationSensor == nullptr) {
- ALOGE("%s: Unable to get orientation sensor.\n", __func__);
- return UNKNOWN_ERROR;
- }
- err = ASensorEventQueue_registerSensor(mQueue, mOrientationSensor,
- ASensor_getMinDelay(mOrientationSensor), 0);
- if (err < 0) {
- ALOGE("%s: Unable to register for orientation sensor events\n", __func__);
- }
- return err;
-}
-
-void OrientationCollector::disableOrientationSensor() {
- if (mSensorManager != nullptr && mQueue != nullptr) {
- if (mOrientationSensor != nullptr) {
- ASensorEventQueue_disableSensor(mQueue, mOrientationSensor);
- }
- ASensorManager_destroyEventQueue(mSensorManager, mQueue);
- }
-}
-
-} // namespace pixel
-} // namespace google
-} // namespace hardware
-} // namespace android
diff --git a/pixelstats/PcaChargeStats.cpp b/pixelstats/PcaChargeStats.cpp
index b81e423..125e6c6 100644
--- a/pixelstats/PcaChargeStats.cpp
+++ b/pixelstats/PcaChargeStats.cpp
@@ -33,28 +33,34 @@
using android::base::WriteStringToFile;
bool PcaChargeStats::CheckPcaContentsAndAck(std::string *file_contents) {
+ std::string path = kPcaChargeMetricsPath;
std::string line;
std::istringstream ss;
- if (!ReadFileToString(kPcaChargeMetricsPath.c_str(), file_contents)) {
- return false;
+ if (!ReadFileToString(path.c_str(), file_contents)) {
+ path = kPca94xxChargeMetricsPath;
+ if (!ReadFileToString(path.c_str(), file_contents)) {
+ return false;
+ }
}
ss.str(*file_contents);
if (!std::getline(ss, line)) {
- ALOGE("Unable to read first line %s - %s", kPcaChargeMetricsPath.c_str(), strerror(errno));
+ ALOGE("Unable to read first line %s - %s", path.c_str(), strerror(errno));
return false;
}
- if (!WriteStringToFile(std::to_string(0), kPcaChargeMetricsPath.c_str())) {
- ALOGE("Couldn't clear %s - %s", kPcaChargeMetricsPath.c_str(), strerror(errno));
+ if (!WriteStringToFile(std::to_string(0), path.c_str())) {
+ ALOGE("Couldn't clear %s - %s", path.c_str(), strerror(errno));
return false;
}
return true;
}
-PcaChargeStats::PcaChargeStats(const std::string pca_charge_metrics_path)
- : kPcaChargeMetricsPath(pca_charge_metrics_path) {}
+PcaChargeStats::PcaChargeStats(const std::string pca_charge_metrics_path,
+ const std::string pca94xx_charge_metrics_path)
+ : kPcaChargeMetricsPath(pca_charge_metrics_path),
+ kPca94xxChargeMetricsPath(pca94xx_charge_metrics_path) {}
} // namespace pixel
} // namespace google
diff --git a/pixelstats/SysfsCollector.cpp b/pixelstats/SysfsCollector.cpp
index 14cba98..391c8b1 100644
--- a/pixelstats/SysfsCollector.cpp
+++ b/pixelstats/SysfsCollector.cpp
@@ -43,12 +43,16 @@
using android::base::StartsWith;
using android::base::WriteStringToFile;
using android::hardware::google::pixel::PixelAtoms::BatteryCapacity;
+using android::hardware::google::pixel::PixelAtoms::BlockStatsReported;
using android::hardware::google::pixel::PixelAtoms::BootStatsInfo;
using android::hardware::google::pixel::PixelAtoms::F2fsCompressionInfo;
using android::hardware::google::pixel::PixelAtoms::F2fsGcSegmentInfo;
+using android::hardware::google::pixel::PixelAtoms::F2fsSmartIdleMaintEnabledStateChanged;
using android::hardware::google::pixel::PixelAtoms::F2fsStatsInfo;
using android::hardware::google::pixel::PixelAtoms::StorageUfsHealth;
using android::hardware::google::pixel::PixelAtoms::StorageUfsResetCount;
+using android::hardware::google::pixel::PixelAtoms::ThermalDfsStats;
+using android::hardware::google::pixel::PixelAtoms::VendorAudioHardwareStatsReported;
using android::hardware::google::pixel::PixelAtoms::VendorChargeCycles;
using android::hardware::google::pixel::PixelAtoms::VendorHardwareFailed;
using android::hardware::google::pixel::PixelAtoms::VendorSlowIo;
@@ -81,7 +85,10 @@
kSpeakerTemperaturePath(sysfs_paths.SpeakerTemperaturePath),
kSpeakerExcursionPath(sysfs_paths.SpeakerExcursionPath),
kSpeakerHeartbeatPath(sysfs_paths.SpeakerHeartBeatPath),
- kUFSErrStatsPath(sysfs_paths.UFSErrStatsPath) {}
+ kUFSErrStatsPath(sysfs_paths.UFSErrStatsPath),
+ kBlockStatsLength(sysfs_paths.BlockStatsLength),
+ kAmsRatePath(sysfs_paths.AmsRatePath),
+ kThermalStatsPaths(sysfs_paths.ThermalStatsPaths) {}
bool SysfsCollector::ReadFileToInt(const std::string &path, int *val) {
return ReadFileToInt(path.c_str(), val);
@@ -366,6 +373,10 @@
reportSpeakerHealthStat(stats_client, right_obj);
}
+void SysfsCollector::logThermalStats(const std::shared_ptr<IStats> &stats_client) {
+ thermal_stats_reporter_.logThermalStats(stats_client, kThermalStatsPaths);
+}
+
/**
* Report the Speech DSP state.
*/
@@ -684,10 +695,12 @@
}
void SysfsCollector::logF2fsGcSegmentInfo(const std::shared_ptr<IStats> &stats_client) {
- int reclaimed_segments_normal, reclaimed_segments_urgent_high, reclaimed_segments_urgent_low;
+ int reclaimed_segments_normal, reclaimed_segments_urgent_high;
+ int reclaimed_segments_urgent_mid, reclaimed_segments_urgent_low;
std::string gc_normal_mode = std::to_string(0); // GC normal mode
std::string gc_urgent_high_mode = std::to_string(4); // GC urgent high mode
std::string gc_urgent_low_mode = std::to_string(5); // GC urgent low mode
+ std::string gc_urgent_mid_mode = std::to_string(6); // GC urgent mid mode
if (kF2fsStatsPath == nullptr) {
ALOGV("F2fs stats path not specified");
@@ -700,9 +713,11 @@
if (reclaimed_segments_urgent_high == -1) return;
reclaimed_segments_urgent_low = getReclaimedSegments(gc_urgent_low_mode);
if (reclaimed_segments_urgent_low == -1) return;
+ reclaimed_segments_urgent_mid = getReclaimedSegments(gc_urgent_mid_mode);
+ if (reclaimed_segments_urgent_mid == -1) return;
// Load values array
- std::vector<VendorAtomValue> values(3);
+ std::vector<VendorAtomValue> values(4);
VendorAtomValue tmp;
tmp.set<VendorAtomValue::intValue>(reclaimed_segments_normal);
values[F2fsGcSegmentInfo::kReclaimedSegmentsNormalFieldNumber - kVendorAtomOffset] = tmp;
@@ -710,6 +725,8 @@
values[F2fsGcSegmentInfo::kReclaimedSegmentsUrgentHighFieldNumber - kVendorAtomOffset] = tmp;
tmp.set<VendorAtomValue::intValue>(reclaimed_segments_urgent_low);
values[F2fsGcSegmentInfo::kReclaimedSegmentsUrgentLowFieldNumber - kVendorAtomOffset] = tmp;
+ tmp.set<VendorAtomValue::intValue>(reclaimed_segments_urgent_mid);
+ values[F2fsGcSegmentInfo::kReclaimedSegmentsUrgentMidFieldNumber - kVendorAtomOffset] = tmp;
// Send vendor atom to IStats HAL
VendorAtom event = {.reverseDomainName = "",
@@ -721,6 +738,88 @@
}
}
+void SysfsCollector::logF2fsSmartIdleMaintEnabled(const std::shared_ptr<IStats> &stats_client) {
+ bool smart_idle_enabled = android::base::GetBoolProperty(
+ "persist.device_config.storage_native_boot.smart_idle_maint_enabled", false);
+
+ // Load values array
+ VendorAtomValue tmp;
+ std::vector<VendorAtomValue> values(1);
+ tmp.set<VendorAtomValue::intValue>(smart_idle_enabled);
+ values[F2fsSmartIdleMaintEnabledStateChanged::kEnabledFieldNumber - kVendorAtomOffset] = tmp;
+
+ // Send vendor atom to IStats HAL
+ VendorAtom event = {.reverseDomainName = PixelAtoms::ReverseDomainNames().pixel(),
+ .atomId = PixelAtoms::Atom::kF2FsSmartIdleMaintEnabledStateChanged,
+ .values = std::move(values)};
+ const ndk::ScopedAStatus ret = stats_client->reportVendorAtom(event);
+ if (!ret.isOk()) {
+ ALOGE("Unable to report Boot stats to Stats service");
+ }
+}
+
+void SysfsCollector::logBlockStatsReported(const std::shared_ptr<IStats> &stats_client) {
+ std::string sdaPath = "/sys/block/sda/stat";
+ std::string file_contents;
+ std::string stat;
+ std::vector<std::string> stats;
+ std::stringstream ss;
+
+ // These index comes from kernel Document
+ // Documentation/ABI/stable/sysfs-block
+ const int READ_IO_IDX = 0, READ_SEC_IDX = 2, READ_TICK_IDX = 3;
+ const int WRITE_IO_IDX = 4, WRITE_SEC_IDX = 6, WRITE_TICK_IDX = 7;
+ uint64_t read_io, read_sectors, read_ticks;
+ uint64_t write_io, write_sectors, write_ticks;
+
+ if (!ReadFileToString(sdaPath.c_str(), &file_contents)) {
+ ALOGE("Failed to read block layer stat %s", sdaPath.c_str());
+ return;
+ }
+
+ ss.str(file_contents);
+ while (ss >> stat) {
+ stats.push_back(stat);
+ }
+
+ if (stats.size() < kBlockStatsLength) {
+ ALOGE("block layer stat format is incorrect %s, length %lu/%d",
+ file_contents.c_str(), stats.size(), kBlockStatsLength);
+ return;
+ }
+
+ read_io = std::stoul(stats[READ_IO_IDX]);
+ read_sectors = std::stoul(stats[READ_SEC_IDX]);
+ read_ticks = std::stoul(stats[READ_TICK_IDX]);
+ write_io = std::stoul(stats[WRITE_IO_IDX]);
+ write_sectors = std::stoul(stats[WRITE_SEC_IDX]);
+ write_ticks = std::stoul(stats[WRITE_TICK_IDX]);
+
+ // Load values array
+ std::vector<VendorAtomValue> values(6);
+ values[BlockStatsReported::kReadIoFieldNumber - kVendorAtomOffset] =
+ VendorAtomValue::make<VendorAtomValue::longValue>(read_io);
+ values[BlockStatsReported::kReadSectorsFieldNumber - kVendorAtomOffset] =
+ VendorAtomValue::make<VendorAtomValue::longValue>(read_sectors);
+ values[BlockStatsReported::kReadTicksFieldNumber - kVendorAtomOffset] =
+ VendorAtomValue::make<VendorAtomValue::longValue>(read_ticks);
+ values[BlockStatsReported::kWriteIoFieldNumber - kVendorAtomOffset] =
+ VendorAtomValue::make<VendorAtomValue::longValue>(write_io);
+ values[BlockStatsReported::kWriteSectorsFieldNumber - kVendorAtomOffset] =
+ VendorAtomValue::make<VendorAtomValue::longValue>(write_sectors);
+ values[BlockStatsReported::kWriteTicksFieldNumber - kVendorAtomOffset] =
+ VendorAtomValue::make<VendorAtomValue::longValue>(write_ticks);
+
+ // Send vendor atom to IStats HAL
+ VendorAtom event = {.reverseDomainName = PixelAtoms::ReverseDomainNames().pixel(),
+ .atomId = PixelAtoms::Atom::kBlockStatsReported,
+ .values = std::move(values)};
+ const ndk::ScopedAStatus ret = stats_client->reportVendorAtom(event);
+ if (!ret.isOk()) {
+ ALOGE("Unable to report block layer stats to Stats service");
+ }
+}
+
void SysfsCollector::reportZramMmStat(const std::shared_ptr<IStats> &stats_client) {
std::string file_contents;
if (!kZramMmStatPath) {
@@ -878,6 +977,44 @@
}
}
+/**
+ * Report the AMS rate.
+ */
+void SysfsCollector::logVendorAudioHardwareStats(const std::shared_ptr<IStats> &stats_client) {
+ std::string file_contents;
+ if (kAmsRatePath == nullptr) {
+ ALOGD("Audio AMS_Rate path not specified");
+ return;
+ }
+
+ uint32_t milli_ams_rate;
+ if (!ReadFileToString(kAmsRatePath, &file_contents)) {
+ ALOGE("Unable to read ams_rate path %s", kAmsRatePath);
+ return;
+ }
+
+ if (sscanf(file_contents.c_str(), "%u", &milli_ams_rate) != 1) {
+ ALOGE("Unable to parse ams_rate %s", file_contents.c_str());
+ return;
+ }
+
+ ALOGD("milli_ams_rate = %s", file_contents.c_str());
+
+ std::vector<VendorAtomValue> values(1);
+ VendorAtomValue tmp;
+ tmp.set<VendorAtomValue::intValue>(milli_ams_rate);
+ values[0] = tmp;
+
+ // Send vendor atom to IStats HAL
+ VendorAtom event = {.reverseDomainName = "",
+ .atomId = PixelAtoms::Atom::kVendorAudioHardwareStatsReported,
+ .values = std::move(values)};
+
+ const ndk::ScopedAStatus ret = stats_client->reportVendorAtom(event);
+ if (!ret.isOk())
+ ALOGE("Unable to report VendorAudioHardwareStatsReported to Stats service");
+}
+
void SysfsCollector::logPerDay() {
const std::shared_ptr<IStats> stats_client = getStatsService();
if (!stats_client) {
@@ -892,11 +1029,13 @@
logBatteryChargeCycles(stats_client);
logBatteryEEPROM(stats_client);
logBatteryHealth(stats_client);
+ logBlockStatsReported(stats_client);
logCodec1Failed(stats_client);
logCodecFailed(stats_client);
logF2fsStats(stats_client);
logF2fsCompressionInfo(stats_client);
logF2fsGcSegmentInfo(stats_client);
+ logF2fsSmartIdleMaintEnabled(stats_client);
logSlowIO(stats_client);
logSpeakerImpedance(stats_client);
logSpeechDspStat(stats_client);
@@ -905,6 +1044,12 @@
logSpeakerHealthStats(stats_client);
mm_metrics_reporter_.logCmaStatus(stats_client);
mm_metrics_reporter_.logPixelMmMetricsPerDay(stats_client);
+ logVendorAudioHardwareStats(stats_client);
+ logThermalStats(stats_client);
+}
+
+void SysfsCollector::aggregatePer5Min() {
+ mm_metrics_reporter_.aggregatePixelMmMetricsPer5Min();
}
void SysfsCollector::logPerHour() {
@@ -934,17 +1079,29 @@
// Sleep for 30 seconds on launch to allow codec driver to load.
sleep(30);
+ // sample & aggregate for the first time.
+ aggregatePer5Min();
+
// Collect first set of stats on boot.
logPerHour();
logPerDay();
- // Set an one-hour timer.
struct itimerspec period;
- const int kSecondsPerHour = 60 * 60;
- int hours = 0;
- period.it_interval.tv_sec = kSecondsPerHour;
+
+ // gcd (greatest common divisor) of all the following timings
+ constexpr int kSecondsPerWake = 5 * 60;
+
+ constexpr int kWakesPer5Min = 5 * 60 / kSecondsPerWake;
+ constexpr int kWakesPerHour = 60 * 60 / kSecondsPerWake;
+ constexpr int kWakesPerDay = 24 * 60 * 60 / kSecondsPerWake;
+
+ int wake_5min = 0;
+ int wake_hours = 0;
+ int wake_days = 0;
+
+ period.it_interval.tv_sec = kSecondsPerWake;
period.it_interval.tv_nsec = 0;
- period.it_value.tv_sec = kSecondsPerHour;
+ period.it_value.tv_sec = kSecondsPerWake;
period.it_value.tv_nsec = 0;
if (timerfd_settime(timerfd, 0, &period, NULL)) {
@@ -954,22 +1111,41 @@
while (1) {
int readval;
- do {
+ union {
char buf[8];
+ uint64_t count;
+ } expire;
+
+ do {
errno = 0;
- readval = read(timerfd, buf, sizeof(buf));
+ readval = read(timerfd, expire.buf, sizeof(expire.buf));
} while (readval < 0 && errno == EINTR);
if (readval < 0) {
ALOGE("Timerfd error - %s\n", strerror(errno));
return;
}
- hours++;
- logPerHour();
- if (hours == 24) {
- // Collect stats every 24hrs after.
+ wake_5min += expire.count;
+ wake_hours += expire.count;
+ wake_days += expire.count;
+
+ if (wake_5min >= kWakesPer5Min) {
+ wake_5min %= kWakesPer5Min;
+ aggregatePer5Min();
+ }
+
+ if (wake_hours >= kWakesPerHour) {
+ if (wake_hours >= 2 * kWakesPerHour)
+ ALOGW("Hourly wake: sleep too much: expire.count=%" PRId64, expire.count);
+ wake_hours %= kWakesPerHour;
+ logPerHour();
+ }
+
+ if (wake_days >= kWakesPerDay) {
+ if (wake_hours >= 2 * kWakesPerDay)
+ ALOGW("Daily wake: sleep too much: expire.count=%" PRId64, expire.count);
+ wake_days %= kWakesPerDay;
logPerDay();
- hours = 0;
}
}
}
diff --git a/pixelstats/ThermalStatsReporter.cpp b/pixelstats/ThermalStatsReporter.cpp
new file mode 100644
index 0000000..30fb30d
--- /dev/null
+++ b/pixelstats/ThermalStatsReporter.cpp
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2021 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: ThermalStats"
+
+#include <aidl/android/frameworks/stats/IStats.h>
+#include <android-base/file.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android/binder_manager.h>
+#include <hardware/google/pixel/pixelstats/pixelatoms.pb.h>
+#include <pixelstats/ThermalStatsReporter.h>
+#include <utils/Log.h>
+
+#include <cinttypes>
+
+namespace android {
+namespace hardware {
+namespace google {
+namespace pixel {
+
+using aidl::android::frameworks::stats::IStats;
+using aidl::android::frameworks::stats::VendorAtom;
+using aidl::android::frameworks::stats::VendorAtomValue;
+using android::base::ReadFileToString;
+using android::hardware::google::pixel::PixelAtoms::ThermalDfsStats;
+
+ThermalStatsReporter::ThermalStatsReporter() {}
+
+bool ThermalStatsReporter::readDfsCount(const std::string &path, int64_t *val) {
+ std::string file_contents;
+
+ if (path.empty()) {
+ ALOGE("Empty path");
+ return false;
+ }
+
+ if (!ReadFileToString(path.c_str(), &file_contents)) {
+ ALOGE("Unable to read %s - %s", path.c_str(), strerror(errno));
+ return false;
+ } else {
+ int64_t trips[8];
+
+ if (sscanf(file_contents.c_str(),
+ "%" SCNd64 " %" SCNd64 " %" SCNd64 " %" SCNd64 " %" SCNd64 " %" SCNd64
+ " %" SCNd64 " %" SCNd64,
+ &trips[0], &trips[1], &trips[2], &trips[3], &trips[4], &trips[5], &trips[6],
+ &trips[7]) < 8) {
+ ALOGE("Unable to parse trip_counters %s from file %s", file_contents.c_str(),
+ path.c_str());
+ return false;
+ }
+
+ /* Trip#6 corresponds to DFS count */
+ *val = trips[6];
+ }
+
+ return true;
+}
+
+bool ThermalStatsReporter::captureThermalDfsStats(
+ const std::vector<std::string> &thermal_stats_paths, struct ThermalDfsCounts *pcur_data) {
+ bool report_stats = false;
+ std::string path;
+
+ if (thermal_stats_paths.size() < kNumOfThermalDfsStats) {
+ ALOGE("Number of thermal stats paths (%lu) is less than expected (%d)",
+ thermal_stats_paths.size(), kNumOfThermalDfsStats);
+ return false;
+ }
+
+ path = thermal_stats_paths[ThermalDfsStats::kBigDfsCountFieldNumber - kVendorAtomOffset];
+ if (!readDfsCount(path, &(pcur_data->big_count))) {
+ pcur_data->big_count = prev_data.big_count;
+ } else {
+ report_stats |= (pcur_data->big_count > prev_data.big_count);
+ }
+
+ path = thermal_stats_paths[ThermalDfsStats::kMidDfsCountFieldNumber - kVendorAtomOffset];
+ if (!readDfsCount(path, &(pcur_data->mid_count))) {
+ pcur_data->mid_count = prev_data.mid_count;
+ } else {
+ report_stats |= (pcur_data->mid_count > prev_data.mid_count);
+ }
+
+ path = thermal_stats_paths[ThermalDfsStats::kLittleDfsCountFieldNumber - kVendorAtomOffset];
+ if (!readDfsCount(path, &(pcur_data->little_count))) {
+ pcur_data->little_count = prev_data.little_count;
+ } else {
+ report_stats |= (pcur_data->little_count > prev_data.little_count);
+ }
+
+ path = thermal_stats_paths[ThermalDfsStats::kGpuDfsCountFieldNumber - kVendorAtomOffset];
+ if (!readDfsCount(path, &(pcur_data->gpu_count))) {
+ pcur_data->gpu_count = prev_data.gpu_count;
+ } else {
+ report_stats |= (pcur_data->gpu_count > prev_data.gpu_count);
+ }
+
+ path = thermal_stats_paths[ThermalDfsStats::kTpuDfsCountFieldNumber - kVendorAtomOffset];
+ if (!readDfsCount(path, &(pcur_data->tpu_count))) {
+ pcur_data->tpu_count = prev_data.tpu_count;
+ } else {
+ report_stats |= (pcur_data->tpu_count > prev_data.tpu_count);
+ }
+
+ path = thermal_stats_paths[ThermalDfsStats::kAurDfsCountFieldNumber - kVendorAtomOffset];
+ if (!readDfsCount(path, &(pcur_data->aur_count))) {
+ pcur_data->aur_count = prev_data.aur_count;
+ } else {
+ report_stats |= (pcur_data->aur_count > prev_data.aur_count);
+ }
+
+ return report_stats;
+}
+
+void ThermalStatsReporter::logThermalDfsStats(const std::shared_ptr<IStats> &stats_client,
+ const std::vector<std::string> &thermal_stats_paths) {
+ struct ThermalDfsCounts cur_data = prev_data;
+
+ if (!captureThermalDfsStats(thermal_stats_paths, &cur_data)) {
+ prev_data = cur_data;
+ ALOGI("No update found for thermal stats");
+ return;
+ }
+
+ VendorAtomValue tmp;
+ int64_t max_dfs_count = static_cast<int64_t>(INT32_MAX);
+ int dfs_count;
+ std::vector<VendorAtomValue> values(kNumOfThermalDfsStats);
+
+ dfs_count = std::min<int64_t>(cur_data.big_count - prev_data.big_count, max_dfs_count);
+ tmp.set<VendorAtomValue::intValue>(dfs_count);
+ values[ThermalDfsStats::kBigDfsCountFieldNumber - kVendorAtomOffset] = tmp;
+
+ dfs_count = std::min<int64_t>(cur_data.mid_count - prev_data.mid_count, max_dfs_count);
+ tmp.set<VendorAtomValue::intValue>(dfs_count);
+ values[ThermalDfsStats::kMidDfsCountFieldNumber - kVendorAtomOffset] = tmp;
+
+ dfs_count = std::min<int64_t>(cur_data.little_count - prev_data.little_count, max_dfs_count);
+ tmp.set<VendorAtomValue::intValue>(dfs_count);
+ values[ThermalDfsStats::kLittleDfsCountFieldNumber - kVendorAtomOffset] = tmp;
+
+ dfs_count = std::min<int64_t>(cur_data.gpu_count - prev_data.gpu_count, max_dfs_count);
+ tmp.set<VendorAtomValue::intValue>(dfs_count);
+ values[ThermalDfsStats::kGpuDfsCountFieldNumber - kVendorAtomOffset] = tmp;
+
+ dfs_count = std::min<int64_t>(cur_data.tpu_count - prev_data.tpu_count, max_dfs_count);
+ tmp.set<VendorAtomValue::intValue>(dfs_count);
+ values[ThermalDfsStats::kTpuDfsCountFieldNumber - kVendorAtomOffset] = tmp;
+
+ dfs_count = std::min<int64_t>(cur_data.aur_count - prev_data.aur_count, max_dfs_count);
+ tmp.set<VendorAtomValue::intValue>(dfs_count);
+ values[ThermalDfsStats::kAurDfsCountFieldNumber - kVendorAtomOffset] = tmp;
+
+ prev_data = cur_data;
+
+ ALOGD("Report updated thermal metrics to stats service");
+ // Send vendor atom to IStats HAL
+ VendorAtom event = {.reverseDomainName = "",
+ .atomId = PixelAtoms::Atom::kThermalDfsStats,
+ .values = std::move(values)};
+ const ndk::ScopedAStatus ret = stats_client->reportVendorAtom(event);
+ if (!ret.isOk())
+ ALOGE("Unable to report thermal DFS stats to Stats service");
+}
+
+void ThermalStatsReporter::logThermalStats(const std::shared_ptr<IStats> &stats_client,
+ const std::vector<std::string> &thermal_stats_paths) {
+ logThermalDfsStats(stats_client, thermal_stats_paths);
+}
+
+} // namespace pixel
+} // namespace google
+} // namespace hardware
+} // namespace android
diff --git a/pixelstats/UeventListener.cpp b/pixelstats/UeventListener.cpp
index b5f31cb..07c8c95 100644
--- a/pixelstats/UeventListener.cpp
+++ b/pixelstats/UeventListener.cpp
@@ -48,7 +48,6 @@
#include <log/log.h>
#include <pixelstats/StatsHelper.h>
#include <pixelstats/UeventListener.h>
-#include <pixelstats/WlcReporter.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
@@ -67,7 +66,6 @@
using android::sp;
using android::base::ReadFileToString;
using android::base::WriteStringToFile;
-using android::hardware::google::pixel::WlcReporter;
using android::hardware::google::pixel::PixelAtoms::PdVidPid;
using android::hardware::google::pixel::PixelAtoms::VendorHardwareFailed;
using android::hardware::google::pixel::PixelAtoms::VendorUsbPortOverheat;
@@ -185,17 +183,6 @@
charge_stats_reporter_.checkAndReport(stats_client, kChargeMetricsPath);
}
-/* ReportWlc
- * Report wireless relate metrics when wireless charging start
- */
-void UeventListener::ReportWlc(const std::shared_ptr<IStats> &stats_client, const bool pow_wireless,
- const bool online, const char *ptmc) {
- if (!pow_wireless) {
- return;
- }
-
- wlc_reporter_.checkAndReport(stats_client, online, ptmc);
-}
/**
* Report raw battery capacity, system battery capacity and associated
* battery capacity curves. This data is collected to verify the filter
@@ -291,9 +278,6 @@
const char *mic_break_status, *mic_degrade_status;
const char *devpath;
bool collect_partner_id = false;
- bool pow_online;
- bool pow_wireless;
- const char *pow_ptmc;
int n;
if (uevent_fd_ < 0) {
@@ -322,8 +306,7 @@
msg[n + 1] = '\0';
driver = product = subsystem = NULL;
- mic_break_status = mic_degrade_status = devpath = pow_ptmc = NULL;
- pow_online = pow_wireless = false;
+ mic_break_status = mic_degrade_status = devpath = NULL;
/**
* msg is a sequence of null-terminated strings.
@@ -350,15 +333,6 @@
subsystem = cp;
} else if (!strncmp(cp, kTypeCPartnerUevent.c_str(), kTypeCPartnerUevent.size())) {
collect_partner_id = true;
- } else if (!strncmp(cp, "POWER_SUPPLY_NAME=wireless",
- strlen("POWER_SUPPLY_NAME=wireless"))) {
- pow_wireless = true;
- } else if (!strncmp(cp, "POWER_SUPPLY_ONLINE=1", strlen("POWER_SUPPLY_ONLINE=1"))) {
- pow_online = true;
- } else if (!kWirelessChargerPtmcUevent.empty() &&
- !strncmp(cp, kWirelessChargerPtmcUevent.c_str(),
- strlen(kWirelessChargerPtmcUevent.c_str()))) {
- pow_ptmc = cp + strlen(kWirelessChargerPtmcUevent.c_str());
}
/* advance to after the next \0 */
while (*cp++) {
@@ -374,7 +348,6 @@
ReportMicStatusUevents(stats_client, devpath, mic_degrade_status);
ReportUsbPortOverheatEvent(stats_client, driver);
ReportChargeMetricsEvent(stats_client, driver);
- ReportWlc(stats_client, pow_wireless, pow_online, pow_ptmc);
ReportBatteryCapacityFGEvent(stats_client, subsystem);
if (collect_partner_id) {
ReportTypeCPartnerId(stats_client);
@@ -399,8 +372,6 @@
kTypeCPartnerUevent(typec_partner_uevent_default),
kTypeCPartnerVidPath(typec_partner_vid_path),
kTypeCPartnerPidPath(typec_partner_pid_path),
- kWirelessChargerPtmcUevent(""),
- kWirelessChargerPtmcPath(""),
uevent_fd_(-1),
log_fd_(-1) {}
@@ -422,12 +393,6 @@
kTypeCPartnerPidPath((uevents_paths.TypeCPartnerPidPath == nullptr)
? typec_partner_pid_path_default
: uevents_paths.TypeCPartnerPidPath),
- kWirelessChargerPtmcUevent((uevents_paths.WirelessChargerPtmcUevent == nullptr)
- ? ""
- : uevents_paths.WirelessChargerPtmcUevent),
- kWirelessChargerPtmcPath((uevents_paths.WirelessChargerPtmcPath == nullptr)
- ? ""
- : uevents_paths.WirelessChargerPtmcPath),
uevent_fd_(-1),
log_fd_(-1) {}
diff --git a/pixelstats/WlcReporter.cpp b/pixelstats/WlcReporter.cpp
deleted file mode 100644
index 5a404d3..0000000
--- a/pixelstats/WlcReporter.cpp
+++ /dev/null
@@ -1,291 +0,0 @@
-/*
- * Copyright (C) 2020 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-wlc"
-
-#include <android-base/file.h>
-#include <android-base/strings.h>
-#include <hardware/google/pixel/pixelstats/pixelatoms.pb.h>
-#include <log/log.h>
-#include <pixelstats/OrientationCollector.h>
-#include <pixelstats/WlcReporter.h>
-#include <sys/timerfd.h>
-#include <time.h>
-#include <utils/Timers.h>
-
-#include <thread>
-
-/* I set a higher rare limit ti orientation, beacuae user might try to adjust
- * orientation when start charge
- **/
-#define GOOGLE_PTMC_ID (0x72)
-#define ID_UNKNOWN (0)
-#define WLC_VENDOR_REPORT_RATE_LIMIT_MIN_SEC (60 * 60)
-#define WLC_VENDOR_REPORT_RATE_LIMIT_MAX_COUNT_PER_DAY (10)
-#define ORIENTATION_REPORT_RATE_LIMIT_MIN_SEC (0)
-#define ORIENTATION_REPORT_RATE_LIMIT_MAX_COUNT_PER_DAY (10)
-#define DAY_SECOND (86400)
-
-namespace android {
-namespace hardware {
-namespace google {
-namespace pixel {
-
-using aidl::android::frameworks::stats::IStats;
-using aidl::android::frameworks::stats::VendorAtom;
-using aidl::android::frameworks::stats::VendorAtomValue;
-using android::base::ReadFileToString;
-
-WlcReporter::WlcStatus::WlcStatus()
- : is_charging(false),
- check_charger_vendor_id(false),
- check_charger_vendor_id_scheduled(false),
- check_vendor_id_attempts(0) {}
-
-WlcReporter::ReportRecord::ReportRecord(char const *name_)
- : name(name_),
- last_reported_time_in_sec_today(0),
- last_reported_time_in_sec(0),
- count_today(0) {}
-
-WlcReporter::WlcReporter(const char *ptmc_path) : kWirelessChargerPtmcPath(ptmc_path) {}
-void WlcReporter::checkAndReport(const std::shared_ptr<IStats> &stats_client, bool online,
- const char *ptmc_uevent) {
- bool wireless_charging = online;
- bool started_wireless_charging = wireless_charging && !wlc_status_.is_charging;
- wlc_status_.is_charging = wireless_charging;
- ALOGV("reportVendorId is_charging: %s, started_wireless_charging: %s",
- (wireless_charging) ? "true" : "false", (started_wireless_charging) ? "true" : "false");
-
- if (started_wireless_charging) {
- reportOrientation(stats_client);
- wlc_status_.check_vendor_id_attempts = 0;
- if (checkRateLimit(WLC_VENDOR_REPORT_RATE_LIMIT_MIN_SEC,
- WLC_VENDOR_REPORT_RATE_LIMIT_MAX_COUNT_PER_DAY, &rec_wlc_vendor_)) {
- wlc_status_.check_charger_vendor_id = true;
- if (kWirelessChargerPtmcPath != nullptr && strlen(kWirelessChargerPtmcPath) != 0) {
- scheduleReportVendorId(stats_client);
- } else {
- ALOGV("ptmc_path not set");
- }
- }
- }
- if (!wireless_charging) {
- wlc_status_.check_charger_vendor_id = false;
- }
- if (wireless_charging) {
- checkVendorId(stats_client, ptmc_uevent);
- }
-}
-
-void WlcReporter::checkVendorId(const std::shared_ptr<IStats> &stats_client,
- const char *ptmc_uevent) {
- if (!ptmc_uevent || !wlc_status_.check_charger_vendor_id) {
- return;
- }
- if (reportVendorMayRetry(stats_client, ptmc_uevent)) {
- wlc_status_.check_charger_vendor_id = false;
- }
-}
-
-bool WlcReporter::reportVendorMayRetry(const std::shared_ptr<IStats> &stats_client,
- const char *ptmc_uevent) {
- int ptmcId = readPtmcId(ptmc_uevent);
- if (ptmcId == ID_UNKNOWN) {
- if (++(wlc_status_.check_vendor_id_attempts) < kMaxVendorIdAttempts) {
- return false;
- } /* else if ptmc not ready after retry assume ptmc not supported by charger */
- }
- ALOGV("reportVendorId from Uevent");
- reportVendor(stats_client, ptmcId);
- return true;
-}
-void WlcReporter::reportVendor(const std::shared_ptr<IStats> &stats_client, const int ptmcId) {
- int vendorCharger = (ptmcId == GOOGLE_PTMC_ID)
- ? PixelAtoms::WirelessChargingStats::VENDOR_GOOGLE
- : PixelAtoms::WirelessChargingStats::VENDOR_UNKNOWN;
- ALOGV("ptmcId: 0x%x; vendorCharger: %d", ptmcId, vendorCharger);
- VendorAtomValue tmp;
- tmp.set<VendorAtomValue::intValue>(vendorCharger);
-
- std::vector<VendorAtomValue> values(1);
- values[PixelAtoms::WirelessChargingStats::kChargerVendorFieldNumber - kVendorAtomOffset] = tmp;
-
- // Send vendor atom to IStats HAL
- VendorAtom event = {.reverseDomainName = "",
- .atomId = PixelAtoms::Atom::kWirelessChargingStats,
- .values = std::move(values)};
- const ndk::ScopedAStatus retStat = stats_client->reportVendorAtom(event);
- if (!retStat.isOk()) {
- ALOGE("Unable to report WLC_STATS to Stats service");
- }
- return;
-}
-
-int WlcReporter::readPtmcId(const char *ptmc_str) {
- int id;
- if (sscanf(ptmc_str, "%x", &id) != 1) {
- return ID_UNKNOWN;
- }
- return id;
-}
-
-void WlcReporter::scheduleReportVendorId(const std::shared_ptr<IStats> &stats_client) {
- if (wlc_status_.check_charger_vendor_id_scheduled) {
- return;
- }
-
- wlc_status_.check_charger_vendor_id_scheduled = true;
- std::thread taskThread(&WlcReporter::reportInBackground, this, stats_client,
- kWirelessChargerPtmcPath);
- taskThread.detach();
-}
-
-bool WlcReporter::ptmcWaitTimer(int timerfd) {
- const int kDelaytimeBeforeReadPtmcId = 60;
- struct itimerspec period;
- period.it_interval.tv_sec = 0;
- period.it_interval.tv_nsec = 0;
- period.it_value.tv_sec = kDelaytimeBeforeReadPtmcId;
- period.it_value.tv_nsec = 0;
-
- if (timerfd_settime(timerfd, 0, &period, nullptr)) {
- ALOGE("Unable to set timer - %s", strerror(errno));
- return false;
- }
- int readval;
- do {
- char buf[8];
- errno = 0;
- readval = read(timerfd, buf, sizeof(buf));
- } while (readval < 0 && errno == EINTR);
- if (readval < 0) {
- ALOGE("Timerfd error - %s", strerror(errno));
- return false;
- }
- return true;
-}
-
-/*
- * PTMC path use to sore wireless charger vendor id,
- * and it take some time to get ready after wireless chagre start,
- * to prevnt busy wait, I use timer and background thread to check PTMC ID
- **/
-void WlcReporter::reportInBackground(const std::shared_ptr<IStats> &stats_client,
- const char *ptmc_path) {
- int timerfd = timerfd_create(CLOCK_BOOTTIME, 0);
- if (timerfd < 0) {
- ALOGE("Unable to create timerfd - %s", strerror(errno));
- return;
- }
- if (ptmcWaitTimer(timerfd)) {
- if (!wlc_status_.is_charging) {
- ALOGV("Not charging, skip report vender id");
- } else if (!wlc_status_.check_charger_vendor_id) {
- ALOGV("id reported by uevnt, skip report vender id");
- } else {
- std::string file_contents;
- if (!ReadFileToString(ptmc_path, &file_contents)) {
- ALOGE("ReadFileToString %s fail", ptmc_path);
- } else {
- int ptmcId = readPtmcId(file_contents.c_str());
- ALOGV("reportVendorId from file");
- reportVendor(stats_client, ptmcId);
- }
- }
- }
- wlc_status_.check_charger_vendor_id_scheduled = false;
- close(timerfd);
-}
-
-/* Reference to frameworks/native/libs/ui/include/ui/DisplayInfo.h
- * translate orientation value from sensor to enum define in
- * pixelatoms.proto
- */
-int WlcReporter::translateDeviceOrientationToAtomValue(int orientation) {
- switch (orientation) {
- case 0:
- return PixelAtoms::DeviceOrientation::ORIENTATION_0;
- case 1:
- return PixelAtoms::DeviceOrientation::ORIENTATION_90;
- case 2:
- return PixelAtoms::DeviceOrientation::ORIENTATION_180;
- case 3:
- return PixelAtoms::DeviceOrientation::ORIENTATION_270;
- default:
- return PixelAtoms::DeviceOrientation::ORIENTATION_UNKNOWN;
- }
-}
-
-void WlcReporter::reportOrientation(const std::shared_ptr<IStats> &stats_client) {
- ALOGV("reportOrientation");
- if (!checkRateLimit(ORIENTATION_REPORT_RATE_LIMIT_MIN_SEC,
- ORIENTATION_REPORT_RATE_LIMIT_MAX_COUNT_PER_DAY, &rec_orientation_)) {
- return;
- }
- sp<OrientationCollector> orientationCollector =
- OrientationCollector::createOrientationCollector();
- if (orientationCollector != nullptr) {
- int orientationFromSensor = -1;
- orientationCollector->pollOrientation(&orientationFromSensor);
- VendorAtomValue tmp;
- tmp.set<VendorAtomValue::intValue>(
- translateDeviceOrientationToAtomValue(orientationFromSensor));
-
- std::vector<VendorAtomValue> values(1);
- values[PixelAtoms::DeviceOrientation::kOrientationFieldNumber - kVendorAtomOffset] = tmp;
-
- VendorAtom event = {.reverseDomainName = "",
- .atomId = PixelAtoms::Atom::kDeviceOrientation,
- .values = std::move(values)};
- const ndk::ScopedAStatus retStat = stats_client->reportVendorAtom(event);
- if (!retStat.isOk()) {
- ALOGE("Unable to report Orientation to Stats service");
- }
- orientationCollector->disableOrientationSensor();
- }
-}
-
-bool WlcReporter::checkRateLimit(int64_t minSecond, int maxCount, ReportRecord *rec) {
- if (rec == nullptr) {
- ALOGE("ReportRecord should not be NULL");
- return false;
- }
- int64_t now = nanoseconds_to_seconds(systemTime(SYSTEM_TIME_BOOTTIME));
- if (rec->last_reported_time_in_sec > 0 && now - rec->last_reported_time_in_sec < minSecond) {
- ALOGV("%s: Rate limit, min period: %ld", rec->name, minSecond);
- return false;
- }
- if (rec->last_reported_time_in_sec_today == 0) {
- rec->last_reported_time_in_sec_today = now;
- ALOGV("%s: reset day time (init)", rec->name);
- } else if (now - rec->last_reported_time_in_sec_today > DAY_SECOND) {
- rec->last_reported_time_in_sec_today = now;
- rec->count_today = 0;
- ALOGV("%s: reset day time", rec->name);
- } else if (rec->count_today >= maxCount) {
- ALOGV("%s: Rate limit, max count: %d", rec->name, maxCount);
- return false;
- }
- (rec->count_today)++;
- ALOGV("%s: checkRateLimit count: %d", rec->name, rec->count_today);
- rec->last_reported_time_in_sec = now;
- return true;
-}
-} // namespace pixel
-} // namespace google
-} // namespace hardware
-} // namespace android
diff --git a/pixelstats/include/pixelstats/ChargeStatsReporter.h b/pixelstats/include/pixelstats/ChargeStatsReporter.h
index 098154a..760c7ba 100644
--- a/pixelstats/include/pixelstats/ChargeStatsReporter.h
+++ b/pixelstats/include/pixelstats/ChargeStatsReporter.h
@@ -37,7 +37,7 @@
void checkAndReport(const std::shared_ptr<IStats> &stats_client, const std::string &path);
private:
- bool checkThermalContentsAndAck(std::string *file_contents);
+ bool checkContentsAndAck(std::string *file_contents, const std::string &path);
void ReportVoltageTierStats(const std::shared_ptr<IStats> &stats_client, const char *line,
const bool has_wireless, const std::string &wfile_contents);
void ReportChargeStats(const std::shared_ptr<IStats> &stats_client, const std::string line,
@@ -54,6 +54,8 @@
const std::string kThermalChargeMetricsPath =
"/sys/devices/platform/google,charger/thermal_stats";
+
+ const std::string kGChargerMetricsPath = "/sys/devices/platform/google,charger/charge_stats";
};
} // namespace pixel
diff --git a/pixelstats/include/pixelstats/MmMetricsReporter.h b/pixelstats/include/pixelstats/MmMetricsReporter.h
index 89f12f5..cfcfac3 100644
--- a/pixelstats/include/pixelstats/MmMetricsReporter.h
+++ b/pixelstats/include/pixelstats/MmMetricsReporter.h
@@ -37,6 +37,7 @@
class MmMetricsReporter {
public:
MmMetricsReporter();
+ void aggregatePixelMmMetricsPer5Min();
void logPixelMmMetricsPerHour(const std::shared_ptr<IStats> &stats_client);
void logPixelMmMetricsPerDay(const std::shared_ptr<IStats> &stats_client);
void logCmaStatus(const std::shared_ptr<IStats> &stats_client);
@@ -62,6 +63,43 @@
static const std::vector<MmMetricsInfo> kCmaStatusInfo;
static const std::vector<MmMetricsInfo> kCmaStatusExtInfo;
+ // raw PSI
+ static constexpr const char *kPsiBasePath = "/proc/pressure";
+ static constexpr const char *kPsiTypes[3] = {"cpu", "io", "memory"};
+ static constexpr const char *kPsiCategories[2] = {"full", "some"};
+ static constexpr const char *kPsiMetricNames[4] = {"avg10", "avg60", "avg300", "total"};
+ static constexpr int kPsiNumFiles = sizeof(kPsiTypes) / sizeof(kPsiTypes[0]);
+ static constexpr int kPsiNumCategories = sizeof(kPsiCategories) / sizeof(kPsiCategories[0]);
+ // number of statistics metric names (one total and several timed averages, per category)
+ static constexpr int kPsiNumNames = sizeof(kPsiMetricNames) / sizeof(kPsiMetricNames[0]);
+
+ // Though cpu has no 'full' category, here we assume it has
+ // So, all file will contain 2 lines x 4 metrics per line = 8 metrics total.
+ static constexpr int kPsiMetricsPerFile = kPsiNumCategories * kPsiNumNames;
+
+ // we have 1 'total' and all others 'averages' per category
+ // "total" metrics are already accumulative and thus no aggregation is needed.
+ // raw values are used.
+ static constexpr int kPsiNumTotals = 1;
+ static constexpr int kPsiNumAvgs = kPsiNumNames - kPsiNumTotals;
+
+ // -1 since "cpu" type has no "full" category
+ static constexpr int kPsiNumAllCategories = kPsiNumFiles * kPsiNumCategories - 1;
+
+ // number of raw metrics: total and avgs, and the combined all: added together.
+ static constexpr int kPsiNumAllTotals = kPsiNumAllCategories * kPsiNumTotals;
+ static constexpr int kPsiNumAllAvgs = kPsiNumAllCategories * kPsiNumAvgs;
+ static constexpr int kPsiNumAllMetrics = kPsiNumAllTotals + kPsiNumAllAvgs;
+
+ // aggregated into (1) min, (2) max, (3) average (internally the sum is kept than the average)
+ static constexpr int kPsiNumOfAggregatedType = 3;
+
+ // # of upload metrics will have a aggregation factor on all 'average' type raw metrics.
+ static constexpr int kPsiNumAllUploadAvgMetrics = kPsiNumAllAvgs * kPsiNumOfAggregatedType;
+ static constexpr int kPsiNumAllUploadTotalMetrics = kPsiNumAllTotals;
+ static constexpr int kPsiNumAllUploadMetrics =
+ kPsiNumAllUploadTotalMetrics + kPsiNumAllUploadAvgMetrics;
+
bool checkKernelMMMetricSupport();
bool MmMetricsSupported() {
@@ -80,6 +118,21 @@
bool ReadFileToUint(const char *const path, uint64_t *val);
bool reportVendorAtom(const std::shared_ptr<IStats> &stats_client, int atom_id,
const std::vector<VendorAtomValue> &values, const std::string &atom_name);
+ void readCompactionDurationStat(std::vector<long> *store);
+ void fillCompactionDurationStatAtom(const std::vector<long> &store,
+ std::vector<VendorAtomValue> *values);
+ void readDirectReclaimStat(std::vector<long> *store);
+ void fillDirectReclaimStatAtom(const std::vector<long> &store,
+ std::vector<VendorAtomValue> *values);
+ void readPressureStall(const char *basePath, std::vector<long> *store);
+ bool parsePressureStallFileContent(bool is_cpu, std::string lines, std::vector<long> *store,
+ int file_save_idx);
+ bool parsePressureStallWords(std::vector<std::string> words, std::vector<long> *store,
+ int line_save_idx);
+ bool savePressureMetrics(std::string name, std::string value, std::vector<long> *store,
+ int base_save_idx);
+ void fillPressureStallAtom(std::vector<VendorAtomValue> *values);
+ void aggregatePressureStall();
std::map<std::string, uint64_t> readVmStat(const char *path);
uint64_t getIonTotalPools();
uint64_t getGpuMemory();
@@ -103,12 +156,22 @@
const char *const kIonTotalPoolsPath;
const char *const kIonTotalPoolsPathForLegacy;
const char *const kGpuTotalPages;
+ const char *const kCompactDuration;
+ const char *const kDirectReclaimBasePath;
const char *const kPixelStatMm;
// Proto messages are 1-indexed and VendorAtom field numbers start at 2, so
// store everything in the values array at the index of the field number
// -2.
- const int kVendorAtomOffset = 2;
+ static constexpr int kVendorAtomOffset = 2;
+ static constexpr int kNumCompactionDurationPrevMetrics = 6;
+ static constexpr int kNumDirectReclaimPrevMetrics = 20;
+ std::vector<long> prev_compaction_duration_;
+ std::vector<long> prev_direct_reclaim_;
+ long prev_psi_total_[kPsiNumAllTotals];
+ long psi_total_[kPsiNumAllTotals];
+ long psi_aggregated_[kPsiNumAllUploadAvgMetrics]; // min, max and avg of original avgXXX
+ int psi_data_set_count_ = 0;
std::map<std::string, uint64_t> prev_hour_vmstat_;
std::map<std::string, uint64_t> prev_day_vmstat_;
std::map<std::string, uint64_t> prev_day_pixel_vmstat_;
diff --git a/pixelstats/include/pixelstats/OrientationCollector.h b/pixelstats/include/pixelstats/OrientationCollector.h
deleted file mode 100644
index e3ca641..0000000
--- a/pixelstats/include/pixelstats/OrientationCollector.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2020 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.
- */
-
-#include <android/hidl/base/1.0/IBase.h>
-#include <android/sensor.h>
-
-#ifndef HARDWARE_GOOGLE_PIXEL_ORIENTATION_COLLECTOR_H
-#define HARDWARE_GOOGLE_PIXEL_ORIENTATION_COLLECTOR_H
-
-namespace android {
-namespace hardware {
-namespace google {
-namespace pixel {
-
-/**
- * A class to help use Senser
- */
-class OrientationCollector : public RefBase {
- public:
- static sp<OrientationCollector> createOrientationCollector();
-
- int32_t pollOrientation(int *orientation);
- int32_t init();
- void disableOrientationSensor();
-
- private:
- ASensorEventQueue *mQueue;
- ASensorManager *mSensorManager = nullptr;
- ASensorRef mOrientationSensor;
-
- int getEvents(ASensorEvent *event_list, size_t event_list_size, int *event_count);
-};
-
-} // namespace pixel
-} // namespace google
-} // namespace hardware
-} // namespace android
-
-#endif // HARDWARE_GOOGLE_PIXEL_ORIENTATION_COLLECTOR_H
diff --git a/pixelstats/include/pixelstats/PcaChargeStats.h b/pixelstats/include/pixelstats/PcaChargeStats.h
index 90a6c3e..0e81be0 100644
--- a/pixelstats/include/pixelstats/PcaChargeStats.h
+++ b/pixelstats/include/pixelstats/PcaChargeStats.h
@@ -25,12 +25,15 @@
class PcaChargeStats {
public:
PcaChargeStats(const std::string pca_charge_metrics_path =
- "/sys/class/power_supply/pca9468-mains/device/chg_stats");
+ "/sys/class/power_supply/pca9468-mains/device/chg_stats",
+ const std::string pca94xx_charge_metrics_path =
+ "/sys/class/power_supply/pca94xx-mains/device/chg_stats");
bool CheckPcaContentsAndAck(std::string *file_contents);
private:
const std::string kPcaChargeMetricsPath;
+ const std::string kPca94xxChargeMetricsPath;
};
} // namespace pixel
diff --git a/pixelstats/include/pixelstats/SysfsCollector.h b/pixelstats/include/pixelstats/SysfsCollector.h
index 2baa53c..75b1a09 100644
--- a/pixelstats/include/pixelstats/SysfsCollector.h
+++ b/pixelstats/include/pixelstats/SysfsCollector.h
@@ -24,6 +24,7 @@
#include "BatteryHealthReporter.h"
#include "MitigationStatsReporter.h"
#include "MmMetricsReporter.h"
+#include "ThermalStatsReporter.h"
namespace android {
namespace hardware {
@@ -60,6 +61,9 @@
const char *const SpeakerExcursionPath;
const char *const SpeakerHeartBeatPath;
const std::vector<std::string> UFSErrStatsPath;
+ const int BlockStatsLength;
+ const char *const AmsRatePath;
+ const std::vector<std::string> ThermalStatsPaths;
};
SysfsCollector(const struct SysfsPaths &paths);
@@ -68,11 +72,13 @@
private:
bool ReadFileToInt(const std::string &path, int *val);
bool ReadFileToInt(const char *path, int *val);
+ void aggregatePer5Min();
void logPerDay();
void logPerHour();
void logBatteryChargeCycles(const std::shared_ptr<IStats> &stats_client);
void logBatteryHealth(const std::shared_ptr<IStats> &stats_client);
+ void logBlockStatsReported(const std::shared_ptr<IStats> &stats_client);
void logCodecFailed(const std::shared_ptr<IStats> &stats_client);
void logCodec1Failed(const std::shared_ptr<IStats> &stats_client);
void logSlowIO(const std::shared_ptr<IStats> &stats_client);
@@ -88,12 +94,15 @@
void logBootStats(const std::shared_ptr<IStats> &stats_client);
void logBatteryEEPROM(const std::shared_ptr<IStats> &stats_client);
void logSpeakerHealthStats(const std::shared_ptr<IStats> &stats_client);
+ void logF2fsSmartIdleMaintEnabled(const std::shared_ptr<IStats> &stats_client);
+ void logThermalStats(const std::shared_ptr<IStats> &stats_client);
void reportSlowIoFromFile(const std::shared_ptr<IStats> &stats_client, const char *path,
const VendorSlowIo::IoOperation &operation_s);
void reportZramMmStat(const std::shared_ptr<IStats> &stats_client);
void reportZramBdStat(const std::shared_ptr<IStats> &stats_client);
int getReclaimedSegments(const std::string &mode);
+ void logVendorAudioHardwareStats(const std::shared_ptr<IStats> &stats_client);
const char *const kSlowioReadCntPath;
const char *const kSlowioWriteCntPath;
@@ -118,10 +127,14 @@
const char *const kSpeakerExcursionPath;
const char *const kSpeakerHeartbeatPath;
const std::vector<std::string> kUFSErrStatsPath;
+ const int kBlockStatsLength;
+ const char *const kAmsRatePath;
+ const std::vector<std::string> kThermalStatsPaths;
BatteryEEPROMReporter battery_EEPROM_reporter_;
MmMetricsReporter mm_metrics_reporter_;
MitigationStatsReporter mitigation_stats_reporter_;
+ ThermalStatsReporter thermal_stats_reporter_;
BatteryHealthReporter battery_health_reporter_;
// Proto messages are 1-indexed and VendorAtom field numbers start at 2, so
diff --git a/pixelstats/include/pixelstats/ThermalStatsReporter.h b/pixelstats/include/pixelstats/ThermalStatsReporter.h
new file mode 100644
index 0000000..57f246e
--- /dev/null
+++ b/pixelstats/include/pixelstats/ThermalStatsReporter.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#ifndef HARDWARE_GOOGLE_PIXEL_PIXELSTATS_THERMALSTATSREPORTER_H
+#define HARDWARE_GOOGLE_PIXEL_PIXELSTATS_THERMALSTATSREPORTER_H
+
+#include <aidl/android/frameworks/stats/IStats.h>
+#include <hardware/google/pixel/pixelstats/pixelatoms.pb.h>
+
+#include <string>
+
+namespace android {
+namespace hardware {
+namespace google {
+namespace pixel {
+
+using aidl::android::frameworks::stats::IStats;
+using aidl::android::frameworks::stats::VendorAtomValue;
+
+/**
+ * A class to upload Pixel Thermal Stats metrics
+ */
+class ThermalStatsReporter {
+ public:
+ ThermalStatsReporter();
+ void logThermalStats(const std::shared_ptr<IStats> &stats_client,
+ const std::vector<std::string> &thermal_stats_paths);
+
+ private:
+ struct ThermalDfsCounts {
+ int64_t big_count;
+ int64_t mid_count;
+ int64_t little_count;
+ int64_t gpu_count;
+ int64_t tpu_count;
+ int64_t aur_count;
+ };
+
+ // Proto messages are 1-indexed and VendorAtom field numbers start at 2, so
+ // store everything in the values array at the index of the field number
+ // -2.
+ const int kVendorAtomOffset = 2;
+ const int kNumOfThermalDfsStats = 6;
+ struct ThermalDfsCounts prev_data;
+
+ void logThermalDfsStats(const std::shared_ptr<IStats> &stats_client,
+ const std::vector<std::string> &thermal_stats_paths);
+ bool captureThermalDfsStats(const std::vector<std::string> &thermal_stats_paths,
+ struct ThermalDfsCounts *cur_data);
+ bool readDfsCount(const std::string &path, int64_t *val);
+};
+
+} // namespace pixel
+} // namespace google
+} // namespace hardware
+} // namespace android
+
+#endif // HARDWARE_GOOGLE_PIXEL_PIXELSTATS_THERMALSTATSREPORTER_H
diff --git a/pixelstats/include/pixelstats/UeventListener.h b/pixelstats/include/pixelstats/UeventListener.h
index 876098d..5316afa 100644
--- a/pixelstats/include/pixelstats/UeventListener.h
+++ b/pixelstats/include/pixelstats/UeventListener.h
@@ -21,8 +21,6 @@
#include <android-base/chrono_utils.h>
#include <pixelstats/BatteryCapacityReporter.h>
#include <pixelstats/ChargeStatsReporter.h>
-#include <pixelstats/PcaChargeStats.h>
-#include <pixelstats/WlcReporter.h>
namespace android {
namespace hardware {
@@ -39,10 +37,6 @@
*/
class UeventListener {
public:
- /* Both WirelessChargerPtmcUevent and WirelessChargerPtmcPath is use to get
- * wireless charger ptmx id, for most case we don't need asisign both of
- * them.
- **/
struct UeventPaths {
const char *const AudioUevent;
const char *const SsocDetailsPath;
@@ -51,8 +45,8 @@
const char *const TypeCPartnerUevent;
const char *const TypeCPartnerVidPath;
const char *const TypeCPartnerPidPath;
- const char *const WirelessChargerPtmcUevent;
- const char *const WirelessChargerPtmcPath;
+ const char *const WirelessChargerPtmcUevent; // Deprecated.
+ const char *const WirelessChargerPtmcPath; // Deprecated.
};
constexpr static const char *const ssoc_details_path =
"/sys/class/power_supply/battery/ssoc_details";
@@ -91,8 +85,6 @@
void ReportVoltageTierStats(const std::shared_ptr<IStats> &stats_client, const char *line,
const bool has_wireless, const std::string wfile_contents);
void ReportChargeMetricsEvent(const std::shared_ptr<IStats> &stats_client, const char *driver);
- void ReportWlc(const std::shared_ptr<IStats> &stats_client, const bool pow_wireless,
- const bool online, const char *ptmc);
void ReportBatteryCapacityFGEvent(const std::shared_ptr<IStats> &stats_client,
const char *subsystem);
void ReportTypeCPartnerId(const std::shared_ptr<IStats> &stats_client);
@@ -104,8 +96,6 @@
const std::string kTypeCPartnerUevent;
const std::string kTypeCPartnerVidPath;
const std::string kTypeCPartnerPidPath;
- const std::string kWirelessChargerPtmcUevent;
- const std::string kWirelessChargerPtmcPath;
BatteryCapacityReporter battery_capacity_reporter_;
ChargeStatsReporter charge_stats_reporter_;
@@ -117,8 +107,6 @@
int uevent_fd_;
int log_fd_;
-
- WlcReporter wlc_reporter_ = WlcReporter(kWirelessChargerPtmcPath.c_str());
};
} // namespace pixel
diff --git a/pixelstats/include/pixelstats/WlcReporter.h b/pixelstats/include/pixelstats/WlcReporter.h
deleted file mode 100644
index e979369..0000000
--- a/pixelstats/include/pixelstats/WlcReporter.h
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2020 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.
- */
-
-#ifndef HARDWARE_GOOGLE_PIXEL_PIXELSTATS_WLCREPORTER_H
-#define HARDWARE_GOOGLE_PIXEL_PIXELSTATS_WLCREPORTER_H
-
-#include <aidl/android/frameworks/stats/IStats.h>
-#include <utils/RefBase.h>
-
-#include <cstdint>
-
-namespace android {
-namespace hardware {
-namespace google {
-namespace pixel {
-
-using aidl::android::frameworks::stats::IStats;
-
-/**
- * A class to upload wireless metrics
- */
-class WlcReporter : public RefBase {
- public:
- void checkAndReport(const std::shared_ptr<IStats> &stats_client, const bool online,
- const char *ptmc_uevent);
-
- WlcReporter(const char *ptmc_path);
-
- private:
- struct WlcStatus {
- bool is_charging;
- bool check_charger_vendor_id;
- bool check_charger_vendor_id_scheduled;
- int check_vendor_id_attempts;
- WlcStatus();
- };
- WlcStatus wlc_status_;
- struct ReportRecord {
- char const *name;
- int64_t last_reported_time_in_sec_today;
- int64_t last_reported_time_in_sec;
- int count_today;
- ReportRecord(char const *name_);
- };
- ReportRecord rec_wlc_vendor_{"wlc_vendor"};
- ReportRecord rec_orientation_{"orientation"};
-
- void checkVendorId(const std::shared_ptr<IStats> &stats_client, const char *ptmc_uevent);
- void scheduleReportVendorId(const std::shared_ptr<IStats> &stats_client);
- void reportOrientation(const std::shared_ptr<IStats> &stats_client);
- void reportVendor(const std::shared_ptr<IStats> &stats_client, const int ptmcId);
- bool reportVendorMayRetry(const std::shared_ptr<IStats> &stats_client, const char *ptmc_uevent);
- // Translate device orientation value from sensor Hal to atom enum value
- int translateDeviceOrientationToAtomValue(int orientation);
- void reportInBackground(const std::shared_ptr<IStats> &stats_client, const char *ptmc_path);
- /*
- * Wait timer for make delay before read ptmc path, return false on error
- * timerfd: fd create by timerfd_create, need create/close by caller
- **/
- bool ptmcWaitTimer(int timerfd);
- /* For some case (ex if wireless charger was connect to a low power PTMC AC
- * adapter), the wireless charger keep restaring (it might casuse will
- * check and update data in a not reasonable rare).
- * return: true, it has not hit upload rare limit
- * false, it has hit rate litmit, we should drop current
- * upload atom
- **/
- bool checkRateLimit(int64_t minSecond, int maxCount, ReportRecord *rec);
-
- // Proto messages are 1-indexed and VendorAtom field numbers start at 2, so
- // store everything in the values array at the index of the field number
- // -2.
- const int kVendorAtomOffset = 2;
- const int kMaxVendorIdAttempts = 5;
- const char *kWirelessChargerPtmcPath;
-
- int readPtmcId(const char *ptmc_str);
-};
-
-} // namespace pixel
-} // namespace google
-} // namespace hardware
-} // namespace android
-
-#endif // HARDWARE_GOOGLE_PIXEL_PIXELSTATS_WLCREPORTER_H
diff --git a/pixelstats/pixelatoms.proto b/pixelstats/pixelatoms.proto
index 9ffb708..ece1c18 100644
--- a/pixelstats/pixelatoms.proto
+++ b/pixelstats/pixelatoms.proto
@@ -43,8 +43,7 @@
ZramMmStat zram_mm_stat = 105005;
ZramBdStat zram_bd_stat = 105006;
BootStatsInfo boot_stats = 105007;
- WirelessChargingStats wireless_charging_stats = 105008;
- DeviceOrientation device_orientation = 105009;
+ // 105008 - 105009 is available.
BatteryCapacityFG fg_capacity = 105010;
PdVidPid pd_vid_pid = 105011;
BatteryEEPROM battery_eeprom = 105012;
@@ -79,6 +78,11 @@
BatteryHealthStatus battery_health_status = 105037;
BatteryHealthUsage battery_health_usage = 105038;
+ F2fsSmartIdleMaintEnabledStateChanged f2fs_smart_idle_maint_enabled_state_changed = 105039;
+ BlockStatsReported block_stats_reported = 105040;
+ VendorAudioHardwareStatsReported vendor_audio_hardware_stats_reported = 105041;
+
+ ThermalDfsStats thermal_dfs_stats = 105042;
}
// AOSP atom ID range ends at 109999
}
@@ -118,6 +122,10 @@
ADAPTER_TYPE_WPC_10W = 23;
ADAPTER_TYPE_WPC_BPP = 24;
ADAPTER_TYPE_WPC_L7 = 25;
+ ADAPTER_TYPE_EXT = 26;
+ ADAPTER_TYPE_EXT1 = 27;
+ ADAPTER_TYPE_EXT2 = 28;
+ ADAPTER_TYPE_EXT_UNKNOWN = 29;
}
optional string reverse_domain_name = 1;
/* Type of charge adapter, enumerated above. */
@@ -419,6 +427,7 @@
REBOOTED = 2;
UPGRADED = 3;
ALERT_V2 = 4;
+ SEC_CH_STATE = 5;
};
optional string reverse_domain_name = 1;
optional Event event = 2;
@@ -462,6 +471,10 @@
optional int32 temp_min = 20;
optional int32 temp_max = 21;
optional int32 bus_err = 22;
+
+ // SEC_CH_STATE-specific filed. This field corresponds to the state
+ // of GSA-GSC secure channel.
+ optional int32 sec_ch_state = 23;
}
/* A message containing the citadel firmware version. */
@@ -514,12 +527,63 @@
optional int64 unevictable = 7;
optional int64 ion_total_pools = 8;
optional int64 gpu_memory = 9;
+ optional int64 slab_unreclaimable = 10;
+ optional int64 psi_cpu_some_total = 11;
+ optional int64 psi_io_full_total = 12;
+ optional int64 psi_io_some_total = 13;
+ optional int64 psi_mem_full_total = 14;
+ optional int64 psi_mem_some_total = 15;
+ optional int32 psi_cpu_some_avg10_min = 16;
+ optional int32 psi_cpu_some_avg10_max = 17;
+ optional int32 psi_cpu_some_avg10_avg = 18;
+ optional int32 psi_cpu_some_avg60_min = 19;
+ optional int32 psi_cpu_some_avg60_max = 20;
+ optional int32 psi_cpu_some_avg60_avg = 21;
+ optional int32 psi_cpu_some_avg300_min = 22;
+ optional int32 psi_cpu_some_avg300_max = 23;
+ optional int32 psi_cpu_some_avg300_avg = 24;
+ optional int32 psi_io_full_avg10_min = 25;
+ optional int32 psi_io_full_avg10_max = 26;
+ optional int32 psi_io_full_avg10_avg = 27;
+ optional int32 psi_io_full_avg60_min = 28;
+ optional int32 psi_io_full_avg60_max = 29;
+ optional int32 psi_io_full_avg60_avg = 30;
+ optional int32 psi_io_full_avg300_min = 31;
+ optional int32 psi_io_full_avg300_max = 32;
+ optional int32 psi_io_full_avg300_avg = 33;
+ optional int32 psi_io_some_avg10_min = 34;
+ optional int32 psi_io_some_avg10_max = 35;
+ optional int32 psi_io_some_avg10_avg = 36;
+ optional int32 psi_io_some_avg60_min = 37;
+ optional int32 psi_io_some_avg60_max = 38;
+ optional int32 psi_io_some_avg60_avg = 39;
+ optional int32 psi_io_some_avg300_min = 40;
+ optional int32 psi_io_some_avg300_max = 41;
+ optional int32 psi_io_some_avg300_avg = 42;
+ optional int32 psi_mem_full_avg10_min = 43;
+ optional int32 psi_mem_full_avg10_max = 44;
+ optional int32 psi_mem_full_avg10_avg = 45;
+ optional int32 psi_mem_full_avg60_min = 46;
+ optional int32 psi_mem_full_avg60_max = 47;
+ optional int32 psi_mem_full_avg60_avg = 48;
+ optional int32 psi_mem_full_avg300_min = 49;
+ optional int32 psi_mem_full_avg300_max = 50;
+ optional int32 psi_mem_full_avg300_avg = 51;
+ optional int32 psi_mem_some_avg10_min = 52;
+ optional int32 psi_mem_some_avg10_max = 53;
+ optional int32 psi_mem_some_avg10_avg = 54;
+ optional int32 psi_mem_some_avg60_min = 55;
+ optional int32 psi_mem_some_avg60_max = 56;
+ optional int32 psi_mem_some_avg60_avg = 57;
+ optional int32 psi_mem_some_avg300_min = 58;
+ optional int32 psi_mem_some_avg300_max = 59;
+ optional int32 psi_mem_some_avg300_avg = 60;
}
/* A message containing Pixel memory metrics collected daily. */
message PixelMmMetricsPerDay {
optional string reverse_domain_name = 1;
- optional int64 workingset_refault = 2;
+ optional int64 workingset_refault = 2; /* refault_file */
optional int64 pswpin = 3;
optional int64 pswpout = 4;
optional int64 allocstall_dma = 5;
@@ -540,6 +604,43 @@
optional int64 pgcache_miss = 20;
optional int64 kswapd_stime_clks = 21;
optional int64 kcompactd_stime_clks = 22;
+ optional int64 direct_reclaim_native_latency_total_time = 23;
+ optional int64 direct_reclaim_native_latency0 = 24;
+ optional int64 direct_reclaim_native_latency1 = 25;
+ optional int64 direct_reclaim_native_latency2 = 26;
+ optional int64 direct_reclaim_native_latency3 = 27;
+ optional int64 direct_reclaim_visible_latency_total_time = 28;
+ optional int64 direct_reclaim_visible_latency0 = 29;
+ optional int64 direct_reclaim_visible_latency1 = 30;
+ optional int64 direct_reclaim_visible_latency2 = 31;
+ optional int64 direct_reclaim_visible_latency3 = 32;
+ optional int64 direct_reclaim_top_latency_total_time = 33;
+ optional int64 direct_reclaim_top_latency0 = 34;
+ optional int64 direct_reclaim_top_latency1 = 35;
+ optional int64 direct_reclaim_top_latency2 = 36;
+ optional int64 direct_reclaim_top_latency3 = 37;
+ optional int64 direct_reclaim_other_latency_total_time = 38;
+ optional int64 direct_reclaim_other_latency0 = 39;
+ optional int64 direct_reclaim_other_latency1 = 40;
+ optional int64 direct_reclaim_other_latency2 = 41;
+ optional int64 direct_reclaim_other_latency3 = 42;
+ optional int64 compaction_total_time = 43;
+ optional int64 compaction_ev_count0 = 44;
+ optional int64 compaction_ev_count1 = 45;
+ optional int64 compaction_ev_count2 = 46;
+ optional int64 compaction_ev_count3 = 47;
+ optional int64 compaction_ev_count4 = 48;
+ optional int64 workingset_refault_anon = 49;
+ optional int64 workingset_refault_file = 50;
+ optional int64 compact_success = 51;
+ optional int64 compact_fail = 52;
+ optional int64 kswapd_low_wmark_hq = 53;
+ optional int64 kswapd_high_wmark_hq = 54;
+ optional int64 thp_file_alloc = 55;
+ optional int64 thp_zero_page_alloc = 56;
+ optional int64 thp_split_page = 57;
+ optional int64 thp_migration_split = 58;
+ optional int64 thp_deferred_split_page = 59;
}
/* A message containing CMA metrics collected from dogfooding only. */
@@ -803,6 +904,25 @@
}
/**
+ * Log thermal statistics.
+ */
+message ThermalDfsStats {
+ optional string reverse_domain_name = 1;
+ // The last count of BIG cluster dfs triggers
+ optional int32 big_dfs_count = 2;
+ // The last count of MID cluster dfs triggers
+ optional int32 mid_dfs_count = 3;
+ // The last count of LITTLE cluster dfs triggers
+ optional int32 little_dfs_count = 4;
+ // The last count of GPU dfs triggers
+ optional int32 gpu_dfs_count = 5;
+ // The last count of TPU dfs triggers
+ optional int32 tpu_dfs_count = 6;
+ // The last count of DSP dfs triggers
+ optional int32 aur_dfs_count = 7;
+}
+
+/**
* Log how many segments have been reclaimed in a specific GC mode.
*/
message F2fsGcSegmentInfo {
@@ -813,6 +933,8 @@
optional int32 reclaimed_segments_urgent_high = 3;
/* Reclaimed segments in GC urgent low mode */
optional int32 reclaimed_segments_urgent_low = 4;
+ /* Reclaimed segments in GC urgent mid mode */
+ optional int32 reclaimed_segments_urgent_mid = 5;
}
/**
@@ -1068,6 +1190,16 @@
// The cycle count at the time of the log event.
optional int32 cycle_count = 11;
+
+ enum BattDisconnectStatus {
+ BPST_BATT_UNKNOWN = 0;
+ BPST_BATT_CONNECT = 1;
+ BPST_BATT_DISCONNECT = 2; // Result of single battery disconnect detection
+ BPST_BATT_CELL_FAULT = 3; // Result of cell fault detection which means actual disconnection
+ }
+
+ // Battery disconnect detection stats.
+ optional BattDisconnectStatus battery_disconnect_status = 12;
}
/**
@@ -1096,3 +1228,43 @@
// Time (s) accumulated only during discharge at the given thresholds.
optional int32 discharge_time_secs = 5;
}
+
+/*
+ * A Value of F2FS smart idle maintenance feature enabled
+ */
+message F2fsSmartIdleMaintEnabledStateChanged {
+ optional string reverse_domain_name = 1;
+ /* Smart idle maint is enabled */
+ optional bool enabled = 2;
+}
+
+/*
+ * Snapshot of the block layer read write stats
+ */
+message BlockStatsReported {
+ optional string reverse_domain_name = 1;
+ /* number of read I/Os processed */
+ optional int64 read_io = 2;
+ /* number of sectors read */
+ optional int64 read_sectors = 3;
+ /* total wait time for read requests */
+ optional int64 read_ticks = 4;
+ /* number of write I/Os processed */
+ optional int64 write_io = 5;
+ /* number of sectors written */
+ optional int64 write_sectors = 6;
+ /* total wait time for write requests */
+ optional int64 write_ticks = 7;
+}
+
+/**
+ * Logs the reported vendor audio hardware stats.
+ */
+message VendorAudioHardwareStatsReported {
+ optional string reverse_domain_name = 1;
+ /* The percentage of calls in a day where auto-mic-switch triggered.
+ * It represented as a fixed-point integer with three decimal place.
+ * E.g.:12.345% is repsented by 12345.
+ */
+ optional int32 milli_rate_of_ams_per_day = 2;
+}
diff --git a/power-libperfmgr/aidl/PowerHintSession.cpp b/power-libperfmgr/aidl/PowerHintSession.cpp
index 7998c53..14cbf01 100644
--- a/power-libperfmgr/aidl/PowerHintSession.cpp
+++ b/power-libperfmgr/aidl/PowerHintSession.cpp
@@ -265,14 +265,10 @@
}
// Remove the session from PowerSessionManager first to avoid racing.
PowerSessionManager::getInstance()->removePowerSession(this);
- setSessionUclampMin(0);
- {
- std::lock_guard<std::mutex> guard(mSessionLock);
- mSessionClosed.store(true);
- }
- mDescriptor->is_active.store(false);
mEarlyBoostHandler->setSessionDead();
mStaleTimerHandler->setSessionDead();
+ setSessionUclampMin(0);
+ mDescriptor->is_active.store(false);
updateUniveralBoostMode();
return ndk::ScopedAStatus::ok();
}
@@ -430,14 +426,13 @@
ATRACE_NAME(tag.c_str());
}
std::shared_ptr<AdpfConfig> adpfConfig = HintManager::GetInstance()->GetAdpfProfile();
- int min = std::max(mDescriptor->current_min, static_cast<int>(adpfConfig->mUclampMinInit));
- mDescriptor->current_min = min;
- PowerSessionManager::getInstance()->setUclampMinLocked(this, min);
+ mDescriptor->current_min =
+ std::max(mDescriptor->current_min, static_cast<int>(adpfConfig->mUclampMinInit));
if (ATRACE_ENABLED()) {
const std::string idstr = getIdString();
std::string sz = StringPrintf("adpf.%s-min", idstr.c_str());
- ATRACE_INT(sz.c_str(), min);
+ ATRACE_INT(sz.c_str(), mDescriptor->current_min);
}
}
@@ -506,6 +501,7 @@
}
void PowerHintSession::StaleTimerHandler::handleMessage(const Message &) {
+ std::lock_guard<std::mutex> guard(mClosedLock);
if (mIsSessionDead) {
return;
}
@@ -535,7 +531,7 @@
}
void PowerHintSession::StaleTimerHandler::setSessionDead() {
- std::lock_guard<std::mutex> guard(mStaleLock);
+ std::lock_guard<std::mutex> guard(mClosedLock);
mIsSessionDead = true;
PowerHintMonitor::getInstance()->getLooper()->removeMessages(mSession->mStaleTimerHandler);
}
diff --git a/power-libperfmgr/aidl/PowerHintSession.h b/power-libperfmgr/aidl/PowerHintSession.h
index 96b445e..9bd9a2c 100644
--- a/power-libperfmgr/aidl/PowerHintSession.h
+++ b/power-libperfmgr/aidl/PowerHintSession.h
@@ -105,7 +105,7 @@
private:
PowerHintSession *mSession;
- std::mutex mStaleLock;
+ std::mutex mClosedLock;
std::mutex mMessageLock;
std::atomic<time_point<steady_clock>> mStaleTime;
std::atomic<bool> mIsMonitoring;
diff --git a/power-libperfmgr/aidl/PowerSessionManager.cpp b/power-libperfmgr/aidl/PowerSessionManager.cpp
index 516942a..947208b 100644
--- a/power-libperfmgr/aidl/PowerSessionManager.cpp
+++ b/power-libperfmgr/aidl/PowerSessionManager.cpp
@@ -33,6 +33,7 @@
namespace impl {
namespace pixel {
+using ::android::perfmgr::AdpfConfig;
using ::android::perfmgr::HintManager;
namespace {
@@ -103,7 +104,28 @@
void PowerSessionManager::wakeSessions() {
std::lock_guard<std::mutex> guard(mLock);
- for (PowerHintSession *s : mSessions) {
+ std::shared_ptr<AdpfConfig> adpfConfig = HintManager::GetInstance()->GetAdpfProfile();
+ std::unordered_set<PowerHintSession *> wakeupList;
+ const int wakeupBoostValue = static_cast<int>(adpfConfig->mUclampMinInit);
+ for (auto &it : mTidSessionListMap) {
+ int tid = it.first;
+ int maxboost = -1;
+ // Find the max boost value among all the sessions that include the same TID.
+ for (PowerHintSession *s : it.second) {
+ if (!s->isActive())
+ continue;
+ // all active sessions need to be awakened.
+ wakeupList.insert(s);
+ if (s->isTimeout()) {
+ maxboost = std::max(maxboost, s->getUclampMin());
+ }
+ }
+ // Found the max boost and actally set to the task.
+ if (maxboost != -1) {
+ set_uclamp_min(tid, std::max(maxboost, wakeupBoostValue));
+ }
+ }
+ for (PowerHintSession *s : wakeupList) {
s->wakeup();
}
}
diff --git a/thermal/Thermal.cpp b/thermal/Thermal.cpp
index 2f41445..d3dc032 100644
--- a/thermal/Thermal.cpp
+++ b/thermal/Thermal.cpp
@@ -282,7 +282,7 @@
}
void Thermal::dumpVirtualSensorInfo(std::ostringstream *dump_buf) {
- *dump_buf << "VirtualSensorInfo:" << std::endl;
+ *dump_buf << "getVirtualSensorInfo:" << std::endl;
const auto &map = thermal_helper_.GetSensorInfoMap();
for (const auto &sensor_info_pair : map) {
if (sensor_info_pair.second.virtual_sensor_info != nullptr) {
@@ -301,11 +301,17 @@
*dump_buf << "]" << std::endl;
*dump_buf << " Offset: " << sensor_info_pair.second.virtual_sensor_info->offset
<< std::endl;
- *dump_buf << " Trigger Sensor: "
- << (sensor_info_pair.second.virtual_sensor_info->trigger_sensor.empty()
- ? "N/A"
- : sensor_info_pair.second.virtual_sensor_info->trigger_sensor)
- << std::endl;
+ *dump_buf << " Trigger Sensor: ";
+ if (sensor_info_pair.second.virtual_sensor_info->trigger_sensors.empty()) {
+ *dump_buf << "N/A" << std::endl;
+ } else {
+ for (size_t i = 0;
+ i < sensor_info_pair.second.virtual_sensor_info->trigger_sensors.size(); i++) {
+ *dump_buf << sensor_info_pair.second.virtual_sensor_info->trigger_sensors[i]
+ << " ";
+ }
+ *dump_buf << std::endl;
+ }
*dump_buf << " Formula: ";
switch (sensor_info_pair.second.virtual_sensor_info->formula) {
case FormulaOption::COUNT_THRESHOLD:
@@ -331,7 +337,7 @@
}
void Thermal::dumpThrottlingInfo(std::ostringstream *dump_buf) {
- *dump_buf << "Throttling Info:" << std::endl;
+ *dump_buf << "getThrottlingInfo:" << std::endl;
const auto &map = thermal_helper_.GetSensorInfoMap();
const auto &thermal_throttling_status_map = thermal_helper_.GetThermalThrottlingStatusMap();
for (const auto &name_info_pair : map) {
@@ -458,7 +464,7 @@
if (!thermal_throttling_status_map.size()) {
return;
}
- *dump_buf << "Throttling Request Status " << std::endl;
+ *dump_buf << "getThrottlingRequestStatus:" << std::endl;
for (const auto &thermal_throttling_status_pair : thermal_throttling_status_map) {
*dump_buf << " Name: " << thermal_throttling_status_pair.first << std::endl;
if (thermal_throttling_status_pair.second.pid_power_budget_map.size()) {
@@ -507,7 +513,7 @@
const auto &power_rail_info_map = thermal_helper_.GetPowerRailInfoMap();
const auto &power_status_map = thermal_helper_.GetPowerStatusMap();
- *dump_buf << "Power Rail Info " << std::endl;
+ *dump_buf << "getPowerRailInfo:" << std::endl;
for (const auto &power_rail_pair : power_rail_info_map) {
*dump_buf << " Power Rail: " << power_rail_pair.first << std::endl;
*dump_buf << " Power Sample Count: " << power_rail_pair.second.power_sample_count
@@ -577,7 +583,7 @@
hidl_vec<CpuUsage> cpu_usages;
dump_buf << "getCpuUsages:" << std::endl;
if (!thermal_helper_.fillCpuUsages(&cpu_usages)) {
- dump_buf << "Failed to get CPU usages." << std::endl;
+ dump_buf << " Failed to get CPU usages." << std::endl;
}
for (const auto &usage : cpu_usages) {
@@ -673,7 +679,7 @@
hidl_vec<CoolingDevice_2_0> cooling_devices;
if (!thermal_helper_.fillCurrentCoolingDevices(false, CoolingType::CPU,
&cooling_devices)) {
- dump_buf << "Failed to getCurrentCoolingDevices." << std::endl;
+ dump_buf << " Failed to getCurrentCoolingDevices." << std::endl;
}
for (const auto &c : cooling_devices) {
@@ -682,7 +688,8 @@
}
}
{
- dump_buf << "Callbacks: Total " << callbacks_.size() << std::endl;
+ dump_buf << "getCallbacks:" << std::endl;
+ dump_buf << " Total: " << callbacks_.size() << std::endl;
for (const auto &c : callbacks_) {
dump_buf << " IsFilter: " << c.is_filter_type
<< " Type: " << android::hardware::thermal::V2_0::toString(c.type)
@@ -690,7 +697,7 @@
}
}
{
- dump_buf << "SendCallback" << std::endl;
+ dump_buf << "sendCallback:" << std::endl;
dump_buf << " Enabled List: ";
const auto &map = thermal_helper_.GetSensorInfoMap();
for (const auto &name_info_pair : map) {
@@ -701,7 +708,7 @@
dump_buf << std::endl;
}
{
- dump_buf << "SendPowerHint" << std::endl;
+ dump_buf << "sendPowerHint:" << std::endl;
dump_buf << " Enabled List: ";
const auto &map = thermal_helper_.GetSensorInfoMap();
for (const auto &name_info_pair : map) {
@@ -716,11 +723,12 @@
dumpThrottlingRequestStatus(&dump_buf);
dumpPowerRailInfo(&dump_buf);
{
- dump_buf << "AIDL Power Hal exist: " << std::boolalpha
- << thermal_helper_.isAidlPowerHalExist() << std::endl;
- dump_buf << "AIDL Power Hal connected: " << std::boolalpha
+ dump_buf << "getAIDLPowerHalInfo:" << std::endl;
+ dump_buf << " Exist: " << std::boolalpha << thermal_helper_.isAidlPowerHalExist()
+ << std::endl;
+ dump_buf << " Connected: " << std::boolalpha
<< thermal_helper_.isPowerHalConnected() << std::endl;
- dump_buf << "AIDL Power Hal Ext connected: " << std::boolalpha
+ dump_buf << " Ext connected: " << std::boolalpha
<< thermal_helper_.isPowerHalExtConnected() << std::endl;
}
}
diff --git a/thermal/android.hardware.thermal@2.0-service.pixel.rc b/thermal/android.hardware.thermal@2.0-service.pixel.rc
index 0e23bef..3ed7865 100644
--- a/thermal/android.hardware.thermal@2.0-service.pixel.rc
+++ b/thermal/android.hardware.thermal@2.0-service.pixel.rc
@@ -4,11 +4,9 @@
trigger enable-thermal-hal
on enable-thermal-hal
- start vendor.thermal-hal-2-0
+ restart vendor.thermal-hal-2-0
service vendor.thermal-hal-2-0 /vendor/bin/hw/android.hardware.thermal@2.0-service.pixel
- interface android.hardware.thermal@1.0::IThermal default
- interface android.hardware.thermal@2.0::IThermal default
class hal
user system
group system
diff --git a/thermal/pid_1_0/Thermal.cpp b/thermal/pid_1_0/Thermal.cpp
index 2f41445..7e6f7c0 100644
--- a/thermal/pid_1_0/Thermal.cpp
+++ b/thermal/pid_1_0/Thermal.cpp
@@ -282,7 +282,7 @@
}
void Thermal::dumpVirtualSensorInfo(std::ostringstream *dump_buf) {
- *dump_buf << "VirtualSensorInfo:" << std::endl;
+ *dump_buf << "getVirtualSensorInfo:" << std::endl;
const auto &map = thermal_helper_.GetSensorInfoMap();
for (const auto &sensor_info_pair : map) {
if (sensor_info_pair.second.virtual_sensor_info != nullptr) {
@@ -331,7 +331,7 @@
}
void Thermal::dumpThrottlingInfo(std::ostringstream *dump_buf) {
- *dump_buf << "Throttling Info:" << std::endl;
+ *dump_buf << "getThrottlingInfo:" << std::endl;
const auto &map = thermal_helper_.GetSensorInfoMap();
const auto &thermal_throttling_status_map = thermal_helper_.GetThermalThrottlingStatusMap();
for (const auto &name_info_pair : map) {
@@ -458,7 +458,7 @@
if (!thermal_throttling_status_map.size()) {
return;
}
- *dump_buf << "Throttling Request Status " << std::endl;
+ *dump_buf << "getThrottlingRequestStatus:" << std::endl;
for (const auto &thermal_throttling_status_pair : thermal_throttling_status_map) {
*dump_buf << " Name: " << thermal_throttling_status_pair.first << std::endl;
if (thermal_throttling_status_pair.second.pid_power_budget_map.size()) {
@@ -507,7 +507,7 @@
const auto &power_rail_info_map = thermal_helper_.GetPowerRailInfoMap();
const auto &power_status_map = thermal_helper_.GetPowerStatusMap();
- *dump_buf << "Power Rail Info " << std::endl;
+ *dump_buf << "getPowerRailInfo:" << std::endl;
for (const auto &power_rail_pair : power_rail_info_map) {
*dump_buf << " Power Rail: " << power_rail_pair.first << std::endl;
*dump_buf << " Power Sample Count: " << power_rail_pair.second.power_sample_count
@@ -577,7 +577,7 @@
hidl_vec<CpuUsage> cpu_usages;
dump_buf << "getCpuUsages:" << std::endl;
if (!thermal_helper_.fillCpuUsages(&cpu_usages)) {
- dump_buf << "Failed to get CPU usages." << std::endl;
+ dump_buf << " Failed to get CPU usages." << std::endl;
}
for (const auto &usage : cpu_usages) {
@@ -673,7 +673,7 @@
hidl_vec<CoolingDevice_2_0> cooling_devices;
if (!thermal_helper_.fillCurrentCoolingDevices(false, CoolingType::CPU,
&cooling_devices)) {
- dump_buf << "Failed to getCurrentCoolingDevices." << std::endl;
+ dump_buf << " Failed to getCurrentCoolingDevices." << std::endl;
}
for (const auto &c : cooling_devices) {
@@ -682,7 +682,8 @@
}
}
{
- dump_buf << "Callbacks: Total " << callbacks_.size() << std::endl;
+ dump_buf << "getCallbacks:" << std::endl;
+ dump_buf << " Total: " << callbacks_.size() << std::endl;
for (const auto &c : callbacks_) {
dump_buf << " IsFilter: " << c.is_filter_type
<< " Type: " << android::hardware::thermal::V2_0::toString(c.type)
@@ -690,7 +691,7 @@
}
}
{
- dump_buf << "SendCallback" << std::endl;
+ dump_buf << "sendCallback:" << std::endl;
dump_buf << " Enabled List: ";
const auto &map = thermal_helper_.GetSensorInfoMap();
for (const auto &name_info_pair : map) {
@@ -701,7 +702,7 @@
dump_buf << std::endl;
}
{
- dump_buf << "SendPowerHint" << std::endl;
+ dump_buf << "sendPowerHint:" << std::endl;
dump_buf << " Enabled List: ";
const auto &map = thermal_helper_.GetSensorInfoMap();
for (const auto &name_info_pair : map) {
@@ -716,11 +717,12 @@
dumpThrottlingRequestStatus(&dump_buf);
dumpPowerRailInfo(&dump_buf);
{
- dump_buf << "AIDL Power Hal exist: " << std::boolalpha
- << thermal_helper_.isAidlPowerHalExist() << std::endl;
- dump_buf << "AIDL Power Hal connected: " << std::boolalpha
+ dump_buf << "getAIDLPowerHalInfo:" << std::endl;
+ dump_buf << " Exist: " << std::boolalpha << thermal_helper_.isAidlPowerHalExist()
+ << std::endl;
+ dump_buf << " Connected: " << std::boolalpha
<< thermal_helper_.isPowerHalConnected() << std::endl;
- dump_buf << "AIDL Power Hal Ext connected: " << std::boolalpha
+ dump_buf << " Ext connected: " << std::boolalpha
<< thermal_helper_.isPowerHalExtConnected() << std::endl;
}
}
diff --git a/thermal/pixel-thermal-symlinks.rc b/thermal/pixel-thermal-symlinks.rc
index a1f6509..132ec5f 100644
--- a/thermal/pixel-thermal-symlinks.rc
+++ b/thermal/pixel-thermal-symlinks.rc
@@ -1,11 +1,11 @@
-on early-boot
+on boot
mkdir /dev/thermal 0750 system system
mkdir /dev/thermal/tz-by-name 0750 system system
mkdir /dev/thermal/cdev-by-name 0750 system system
start vendor.thermal.symlinks
service vendor.thermal.symlinks /vendor/bin/thermal_symlinks
- class hal
user system
group system
oneshot
+ disabled
diff --git a/thermal/thermal-helper.cpp b/thermal/thermal-helper.cpp
index 8bc6741..85ccc5d 100644
--- a/thermal/thermal-helper.cpp
+++ b/thermal/thermal-helper.cpp
@@ -250,16 +250,21 @@
}
if (name_status_pair.second.virtual_sensor_info != nullptr &&
- !name_status_pair.second.virtual_sensor_info->trigger_sensor.empty() &&
+ !name_status_pair.second.virtual_sensor_info->trigger_sensors.empty() &&
name_status_pair.second.is_watch) {
- if (sensor_info_map_.count(
- name_status_pair.second.virtual_sensor_info->trigger_sensor)) {
- sensor_info_map_[name_status_pair.second.virtual_sensor_info->trigger_sensor]
- .is_watch = true;
- } else {
- LOG(FATAL) << name_status_pair.first << "'s trigger sensor: "
- << name_status_pair.second.virtual_sensor_info->trigger_sensor
- << " is invalid";
+ for (size_t i = 0;
+ i < name_status_pair.second.virtual_sensor_info->trigger_sensors.size(); i++) {
+ if (sensor_info_map_.find(
+ name_status_pair.second.virtual_sensor_info->trigger_sensors[i]) !=
+ sensor_info_map_.end()) {
+ sensor_info_map_[name_status_pair.second.virtual_sensor_info
+ ->trigger_sensors[i]]
+ .is_watch = true;
+ } else {
+ LOG(FATAL) << name_status_pair.first << "'s trigger sensor: "
+ << name_status_pair.second.virtual_sensor_info->trigger_sensors[i]
+ << " is invalid";
+ }
}
}
}
@@ -329,7 +334,8 @@
bool ThermalHelper::readTemperature(std::string_view sensor_name, Temperature_1_0 *out) {
// Return fail if the thermal sensor cannot be read.
float temp;
- if (!readThermalSensor(sensor_name, &temp, false)) {
+ std::string sensor_log;
+ if (!readThermalSensor(sensor_name, &temp, false, &sensor_log)) {
LOG(ERROR) << "readTemperature: failed to read sensor: " << sensor_name;
return false;
}
@@ -348,17 +354,22 @@
sensor_info.hot_thresholds[static_cast<size_t>(ThrottlingSeverity::SHUTDOWN)];
out->vrThrottlingThreshold = sensor_info.vr_threshold;
+ if (sensor_info.is_watch) {
+ LOG(INFO) << sensor_name.data() << ":" << out->currentValue << " raw data:[" << sensor_log
+ << "]";
+ }
return true;
}
bool ThermalHelper::readTemperature(
std::string_view sensor_name, Temperature_2_0 *out,
std::pair<ThrottlingSeverity, ThrottlingSeverity> *throtting_status,
- const bool force_sysfs) {
+ const bool force_no_cache) {
// Return fail if the thermal sensor cannot be read.
float temp;
+ std::string sensor_log;
- if (!readThermalSensor(sensor_name, &temp, force_sysfs)) {
+ if (!readThermalSensor(sensor_name, &temp, force_no_cache, &sensor_log)) {
LOG(ERROR) << "readTemperature: failed to read sensor: " << sensor_name;
return false;
}
@@ -390,7 +401,9 @@
out->throttlingStatus = static_cast<size_t>(status.first) > static_cast<size_t>(status.second)
? status.first
: status.second;
-
+ if (sensor_info.is_watch) {
+ LOG(INFO) << sensor_name.data() << ":" << out->value << " raw data:[" << sensor_log << "]";
+ }
return true;
}
@@ -779,10 +792,10 @@
}
bool ThermalHelper::readThermalSensor(std::string_view sensor_name, float *temp,
- const bool force_sysfs) {
+ const bool force_no_cache, std::string *sensor_log) {
float temp_val = 0.0;
std::string file_reading;
- std::string log_buf;
+ std::string sub_sensor_log;
boot_clock::time_point now = boot_clock::now();
ATRACE_NAME(StringPrintf("ThermalHelper::readThermalSensor - %s", sensor_name.data()).c_str());
@@ -795,11 +808,13 @@
auto &sensor_status = sensor_status_map_.at(sensor_name.data());
// Check if thermal data need to be read from buffer
- if (!force_sysfs && (sensor_status.thermal_cached.timestamp != boot_clock::time_point::min()) &&
+ if (!force_no_cache &&
+ (sensor_status.thermal_cached.timestamp != boot_clock::time_point::min()) &&
(std::chrono::duration_cast<std::chrono::milliseconds>(
now - sensor_status.thermal_cached.timestamp) < sensor_info.time_resolution) &&
!isnan(sensor_status.thermal_cached.temp)) {
*temp = sensor_status.thermal_cached.temp;
+ sensor_log->append(StringPrintf("%s:%0.f ", sensor_name.data(), *temp));
LOG(VERBOSE) << "read " << sensor_name.data() << " from buffer, value:" << *temp;
return true;
}
@@ -815,16 +830,14 @@
return false;
}
*temp = std::stof(::android::base::Trim(file_reading));
+ sensor_log->append(StringPrintf("%s:%0.f ", sensor_name.data(), *temp));
} else {
for (size_t i = 0; i < sensor_info.virtual_sensor_info->linked_sensors.size(); i++) {
float sensor_reading = 0.0;
if (!readThermalSensor(sensor_info.virtual_sensor_info->linked_sensors[i],
- &sensor_reading, force_sysfs)) {
+ &sensor_reading, force_no_cache, &sub_sensor_log)) {
return false;
}
- log_buf.append(StringPrintf("(%s: %0.2f)",
- sensor_info.virtual_sensor_info->linked_sensors[i].c_str(),
- sensor_reading));
if (std::isnan(sensor_info.virtual_sensor_info->coefficients[i])) {
return false;
}
@@ -855,8 +868,9 @@
break;
}
}
- LOG(VERBOSE) << sensor_name.data() << "'s sub sensors:" << log_buf;
*temp = (temp_val + sensor_info.virtual_sensor_info->offset);
+ sensor_log->append(
+ StringPrintf("%s:%0.f(%s) ", sensor_name.data(), *temp, sub_sensor_log.data()));
}
{
@@ -881,7 +895,7 @@
ATRACE_CALL();
for (auto &name_status_pair : sensor_status_map_) {
bool force_update = false;
- bool force_sysfs = false;
+ bool force_no_cache = false;
Temperature_2_0 temp;
TemperatureThreshold threshold;
SensorStatus &sensor_status = name_status_pair.second;
@@ -902,39 +916,45 @@
: sensor_info.polling_delay;
if (sensor_info.virtual_sensor_info != nullptr &&
- !sensor_info.virtual_sensor_info->trigger_sensor.empty()) {
- const auto trigger_sensor_status =
- sensor_status_map_.at(sensor_info.virtual_sensor_info->trigger_sensor);
- if (trigger_sensor_status.severity != ThrottlingSeverity::NONE) {
- sleep_ms = sensor_info.passive_delay;
+ !sensor_info.virtual_sensor_info->trigger_sensors.empty()) {
+ for (size_t i = 0; i < sensor_info.virtual_sensor_info->trigger_sensors.size(); i++) {
+ const auto &trigger_sensor_status =
+ sensor_status_map_.at(sensor_info.virtual_sensor_info->trigger_sensors[i]);
+ if (trigger_sensor_status.severity != ThrottlingSeverity::NONE) {
+ sleep_ms = sensor_info.passive_delay;
+ }
}
}
// Check if the sensor need to be updated
if (sensor_status.last_update_time == boot_clock::time_point::min()) {
force_update = true;
- LOG(VERBOSE) << "Force update " << name_status_pair.first
- << "'s temperature after booting";
+ force_no_cache = true;
} else {
time_elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
now - sensor_status.last_update_time);
- if (time_elapsed_ms > sleep_ms) {
- // Update the sensor because sleep timeout
+ if (uevent_sensors.size()) {
+ if (sensor_info.virtual_sensor_info != nullptr) {
+ for (size_t i = 0; i < sensor_info.virtual_sensor_info->trigger_sensors.size();
+ i++) {
+ if (uevent_sensors.find(
+ sensor_info.virtual_sensor_info->trigger_sensors[i]) !=
+ uevent_sensors.end()) {
+ force_update = true;
+ force_no_cache = true;
+ }
+ }
+ } else if (uevent_sensors.find(name_status_pair.first) != uevent_sensors.end()) {
+ force_update = true;
+ force_no_cache = true;
+ }
+ } else if (time_elapsed_ms > sleep_ms) {
force_update = true;
- } else if (uevent_sensors.size() &&
- uevent_sensors.find((sensor_info.virtual_sensor_info != nullptr)
- ? sensor_info.virtual_sensor_info->trigger_sensor
- : name_status_pair.first) !=
- uevent_sensors.end()) {
- // Force update the sensor from sysfs
- force_update = true;
- force_sysfs = true;
}
}
-
LOG(VERBOSE) << "sensor " << name_status_pair.first
<< ": time_elpased=" << time_elapsed_ms.count()
<< ", sleep_ms=" << sleep_ms.count() << ", force_update = " << force_update
- << ", force_sysfs = " << force_sysfs;
+ << ", force_no_cache = " << force_no_cache;
if (!force_update) {
auto timeout_remaining = sleep_ms - time_elapsed_ms;
@@ -947,7 +967,7 @@
}
std::pair<ThrottlingSeverity, ThrottlingSeverity> throtting_status;
- if (!readTemperature(name_status_pair.first, &temp, &throtting_status, force_sysfs)) {
+ if (!readTemperature(name_status_pair.first, &temp, &throtting_status, force_no_cache)) {
LOG(ERROR) << __func__
<< ": error reading temperature for sensor: " << name_status_pair.first;
continue;
@@ -982,10 +1002,8 @@
}
if (sensor_status.severity == ThrottlingSeverity::NONE) {
- LOG(VERBOSE) << temp.name << ": " << temp.value;
thermal_throttling_.clearThrottlingData(name_status_pair.first, sensor_info);
} else {
- LOG(INFO) << temp.name << ": " << temp.value;
// update thermal throttling request
thermal_throttling_.thermalThrottlingUpdate(
temp, sensor_info, sensor_status.severity, time_elapsed_ms,
diff --git a/thermal/thermal-helper.h b/thermal/thermal-helper.h
index 6b7cd9a..eca9805 100644
--- a/thermal/thermal-helper.h
+++ b/thermal/thermal-helper.h
@@ -150,7 +150,8 @@
ThrottlingSeverity prev_hot_severity, ThrottlingSeverity prev_cold_severity,
float value) const;
// Read temperature data according to thermal sensor's info
- bool readThermalSensor(std::string_view sensor_name, float *temp, const bool force_sysfs);
+ bool readThermalSensor(std::string_view sensor_name, float *temp, const bool force_sysfs,
+ std::string *sensor_log);
bool connectToPowerHal();
void updateSupportedPowerHints();
void updateCoolingDevices(const std::vector<std::string> &cooling_devices_to_update);
diff --git a/thermal/utils/thermal_info.cpp b/thermal/utils/thermal_info.cpp
index f01d11b..a24347f 100644
--- a/thermal/utils/thermal_info.cpp
+++ b/thermal/utils/thermal_info.cpp
@@ -136,6 +136,151 @@
}
} // namespace
+bool ParseBindedCdevInfo(const Json::Value &values,
+ std::unordered_map<std::string, BindedCdevInfo> *binded_cdev_info_map,
+ const bool support_pid, bool *support_hard_limit) {
+ for (Json::Value::ArrayIndex j = 0; j < values.size(); ++j) {
+ Json::Value sub_values;
+ const std::string &cdev_name = values[j]["CdevRequest"].asString();
+ ThrottlingArray cdev_weight_for_pid;
+ cdev_weight_for_pid.fill(NAN);
+ CdevArray cdev_ceiling;
+ cdev_ceiling.fill(std::numeric_limits<int>::max());
+ int max_release_step = std::numeric_limits<int>::max();
+ int max_throttle_step = std::numeric_limits<int>::max();
+ if (support_pid) {
+ if (!values[j]["CdevWeightForPID"].empty()) {
+ LOG(INFO) << "Star to parse " << cdev_name << "'s CdevWeightForPID";
+ if (!getFloatFromJsonValues(values[j]["CdevWeightForPID"], &cdev_weight_for_pid,
+ false, false)) {
+ LOG(ERROR) << "Failed to parse CdevWeightForPID";
+ binded_cdev_info_map->clear();
+ return false;
+ }
+ }
+ if (!values[j]["CdevCeiling"].empty()) {
+ LOG(INFO) << "Start to parse CdevCeiling: " << cdev_name;
+ if (!getIntFromJsonValues(values[j]["CdevCeiling"], &cdev_ceiling, false, false)) {
+ LOG(ERROR) << "Failed to parse CdevCeiling";
+ binded_cdev_info_map->clear();
+ return false;
+ }
+ }
+
+ if (!values[j]["MaxReleaseStep"].empty()) {
+ max_release_step = getIntFromValue(values[j]["MaxReleaseStep"]);
+ if (max_release_step < 0) {
+ LOG(ERROR) << cdev_name << " MaxReleaseStep: " << max_release_step;
+ binded_cdev_info_map->clear();
+ return false;
+ } else {
+ LOG(INFO) << cdev_name << " MaxReleaseStep: " << max_release_step;
+ }
+ }
+ if (!values[j]["MaxThrottleStep"].empty()) {
+ max_throttle_step = getIntFromValue(values[j]["MaxThrottleStep"]);
+ if (max_throttle_step < 0) {
+ LOG(ERROR) << cdev_name << " MaxThrottleStep: " << max_throttle_step;
+ binded_cdev_info_map->clear();
+ return false;
+ } else {
+ LOG(INFO) << cdev_name << " MaxThrottleStep: " << max_throttle_step;
+ }
+ }
+ }
+ CdevArray limit_info;
+ limit_info.fill(0);
+ ThrottlingArray power_thresholds;
+ power_thresholds.fill(NAN);
+ ReleaseLogic release_logic = ReleaseLogic::NONE;
+
+ sub_values = values[j]["LimitInfo"];
+ if (sub_values.size()) {
+ LOG(INFO) << "Start to parse LimitInfo: " << cdev_name;
+ if (!getIntFromJsonValues(sub_values, &limit_info, false, false)) {
+ LOG(ERROR) << "Failed to parse LimitInfo";
+ binded_cdev_info_map->clear();
+ return false;
+ }
+ *support_hard_limit = true;
+ }
+ // Parse linked power info
+ std::string power_rail;
+ bool high_power_check = false;
+ bool throttling_with_power_link = false;
+ CdevArray cdev_floor_with_power_link;
+ cdev_floor_with_power_link.fill(0);
+
+ const bool power_link_disabled =
+ android::base::GetBoolProperty(kPowerLinkDisabledProperty.data(), false);
+ if (!power_link_disabled) {
+ power_rail = values[j]["BindedPowerRail"].asString();
+
+ if (values[j]["HighPowerCheck"].asBool()) {
+ high_power_check = true;
+ }
+ LOG(INFO) << "Highpowercheck: " << std::boolalpha << high_power_check;
+
+ if (values[j]["ThrottlingWithPowerLink"].asBool()) {
+ throttling_with_power_link = true;
+ }
+ LOG(INFO) << "ThrottlingwithPowerLink: " << std::boolalpha
+ << throttling_with_power_link;
+
+ sub_values = values[j]["CdevFloorWithPowerLink"];
+ if (sub_values.size()) {
+ LOG(INFO) << "Start to parse " << cdev_name << "'s CdevFloorWithPowerLink";
+ if (!getIntFromJsonValues(sub_values, &cdev_floor_with_power_link, false, false)) {
+ LOG(ERROR) << "Failed to parse CdevFloor";
+ binded_cdev_info_map->clear();
+ return false;
+ }
+ }
+ sub_values = values[j]["PowerThreshold"];
+ if (sub_values.size()) {
+ LOG(INFO) << "Start to parse " << cdev_name << "'s PowerThreshold";
+ if (!getFloatFromJsonValues(sub_values, &power_thresholds, false, false)) {
+ LOG(ERROR) << "Failed to parse power thresholds";
+ binded_cdev_info_map->clear();
+ return false;
+ }
+ if (values[j]["ReleaseLogic"].asString() == "INCREASE") {
+ release_logic = ReleaseLogic::INCREASE;
+ LOG(INFO) << "Release logic: INCREASE";
+ } else if (values[j]["ReleaseLogic"].asString() == "DECREASE") {
+ release_logic = ReleaseLogic::DECREASE;
+ LOG(INFO) << "Release logic: DECREASE";
+ } else if (values[j]["ReleaseLogic"].asString() == "STEPWISE") {
+ release_logic = ReleaseLogic::STEPWISE;
+ LOG(INFO) << "Release logic: STEPWISE";
+ } else if (values[j]["ReleaseLogic"].asString() == "RELEASE_TO_FLOOR") {
+ release_logic = ReleaseLogic::RELEASE_TO_FLOOR;
+ LOG(INFO) << "Release logic: RELEASE_TO_FLOOR";
+ } else {
+ LOG(ERROR) << "Release logic is invalid";
+ binded_cdev_info_map->clear();
+ return false;
+ }
+ }
+ }
+
+ (*binded_cdev_info_map)[cdev_name] = {
+ .limit_info = limit_info,
+ .power_thresholds = power_thresholds,
+ .release_logic = release_logic,
+ .high_power_check = high_power_check,
+ .throttling_with_power_link = throttling_with_power_link,
+ .cdev_weight_for_pid = cdev_weight_for_pid,
+ .cdev_ceiling = cdev_ceiling,
+ .max_release_step = max_release_step,
+ .max_throttle_step = max_throttle_step,
+ .cdev_floor_with_power_link = cdev_floor_with_power_link,
+ .power_rail = power_rail,
+ };
+ }
+ return true;
+}
+
bool ParseSensorInfo(std::string_view config_path,
std::unordered_map<std::string, SensorInfo> *sensors_parsed) {
std::string json_doc;
@@ -186,10 +331,10 @@
}
bool send_cb = false;
- if (sensors[i]["Monitor"].empty() || !sensors[i]["Monitor"].isBool()) {
- LOG(INFO) << "Failed to read Sensor[" << name << "]'s Monitor, set to 'false'";
- } else if (sensors[i]["Monitor"].asBool()) {
- send_cb = true;
+ if (!sensors[i]["Monitor"].empty() && sensors[i]["Monitor"].isBool()) {
+ send_cb = sensors[i]["Monitor"].asBool();
+ } else if (!sensors[i]["SendCallback"].empty() && sensors[i]["SendCallback"].isBool()) {
+ send_cb = sensors[i]["SendCallback"].asBool();
}
LOG(INFO) << "Sensor[" << name << "]'s SendCallback: " << std::boolalpha << send_cb
<< std::noboolalpha;
@@ -223,10 +368,10 @@
std::vector<std::string> linked_sensors;
std::vector<float> coefficients;
float offset = 0;
- std::string trigger_sensor;
-
+ std::vector<std::string> trigger_sensors;
FormulaOption formula = FormulaOption::COUNT_THRESHOLD;
bool is_virtual_sensor = false;
+
if (sensors[i]["VirtualSensor"].empty() || !sensors[i]["VirtualSensor"].isBool()) {
LOG(INFO) << "Failed to read Sensor[" << name << "]'s VirtualSensor, set to 'false'";
} else {
@@ -394,19 +539,40 @@
sensors_parsed->clear();
return false;
}
-
if (linked_sensors.size() != coefficients.size()) {
LOG(ERROR) << "Sensor[" << name
<< "]'s combination size is not matched with coefficient size";
sensors_parsed->clear();
return false;
}
-
if (!sensors[i]["Offset"].empty()) {
offset = sensors[i]["Offset"].asFloat();
}
- trigger_sensor = sensors[i]["TriggerSensor"].asString();
+ values = sensors[i]["TriggerSensor"];
+ if (!values.empty()) {
+ if (values.isString()) {
+ trigger_sensors.emplace_back(values.asString());
+ LOG(INFO) << "Sensor[" << name << "]'s TriggerSensor: " << values.asString();
+ } else if (values.size()) {
+ trigger_sensors.reserve(values.size());
+ for (Json::Value::ArrayIndex j = 0; j < values.size(); ++j) {
+ if (!values[j].isString()) {
+ LOG(ERROR) << name << " TriggerSensor should be an array of string";
+ sensors_parsed->clear();
+ return false;
+ }
+ trigger_sensors.emplace_back(values[j].asString());
+ LOG(INFO) << "Sensor[" << name << "]'s TriggerSensor[" << j
+ << "]: " << trigger_sensors[j];
+ }
+ } else {
+ LOG(ERROR) << "Sensor[" << name << "]'s TriggerSensor should be a string";
+ sensors_parsed->clear();
+ return false;
+ }
+ }
+
if (sensors[i]["Formula"].asString().compare("COUNT_THRESHOLD") == 0) {
formula = FormulaOption::COUNT_THRESHOLD;
} else if (sensors[i]["Formula"].asString().compare("WEIGHTED_AVG") == 0) {
@@ -543,16 +709,14 @@
sensors_parsed->clear();
return false;
}
- LOG(INFO) << "Start to parse"
- << " Sensor[" << name << "]'s S_Power";
+ LOG(INFO) << "Start to parse Sensor[" << name << "]'s S_Power";
if (sensors[i]["PIDInfo"]["S_Power"].empty() ||
!getFloatFromJsonValues(sensors[i]["PIDInfo"]["S_Power"], &s_power, false, true)) {
LOG(ERROR) << "Sensor[" << name << "]: Failed to parse S_Power";
sensors_parsed->clear();
return false;
}
- LOG(INFO) << "Start to parse"
- << " Sensor[" << name << "]'s I_Cutoff";
+ LOG(INFO) << "Start to parse Sensor[" << name << "]'s I_Cutoff";
if (sensors[i]["PIDInfo"]["I_Cutoff"].empty() ||
!getFloatFromJsonValues(sensors[i]["PIDInfo"]["I_Cutoff"], &i_cutoff, false,
false)) {
@@ -560,13 +724,9 @@
sensors_parsed->clear();
return false;
}
- LOG(INFO) << "Start to parse"
- << " Sensor[" << name << "]'s I_Default";
i_default = getFloatFromValue(sensors[i]["PIDInfo"]["I_Default"]);
LOG(INFO) << "Sensor[" << name << "]'s I_Default: " << i_default;
- LOG(INFO) << "Start to parse"
- << " Sensor[" << name << "]'s TranCycle";
tran_cycle = getFloatFromValue(sensors[i]["PIDInfo"]["TranCycle"]);
LOG(INFO) << "Sensor[" << name << "]'s TranCycle: " << tran_cycle;
@@ -598,155 +758,11 @@
bool support_hard_limit = false;
std::unordered_map<std::string, BindedCdevInfo> binded_cdev_info_map;
values = sensors[i]["BindedCdevInfo"];
- for (Json::Value::ArrayIndex j = 0; j < values.size(); ++j) {
- Json::Value sub_values;
- const std::string &cdev_name = values[j]["CdevRequest"].asString();
- ThrottlingArray cdev_weight_for_pid;
- cdev_weight_for_pid.fill(NAN);
- CdevArray cdev_ceiling;
- cdev_ceiling.fill(std::numeric_limits<int>::max());
- int max_release_step = std::numeric_limits<int>::max();
- int max_throttle_step = std::numeric_limits<int>::max();
- if (support_pid) {
- if (!values[j]["CdevWeightForPID"].empty()) {
- LOG(INFO) << "Sensor[" << name << "]: Star to parse " << cdev_name
- << "'s CdevWeightForPID";
- if (!getFloatFromJsonValues(values[j]["CdevWeightForPID"], &cdev_weight_for_pid,
- false, false)) {
- LOG(ERROR) << "Failed to parse CdevWeightForPID";
- sensors_parsed->clear();
- return false;
- }
- }
- if (!values[j]["CdevCeiling"].empty()) {
- LOG(INFO) << "Sensor[" << name
- << "]: Start to parse CdevCeiling: " << cdev_name;
- if (!getIntFromJsonValues(values[j]["CdevCeiling"], &cdev_ceiling, false,
- false)) {
- LOG(ERROR) << "Failed to parse CdevCeiling";
- sensors_parsed->clear();
- return false;
- }
- }
- if (!values[j]["MaxReleaseStep"].empty()) {
- max_release_step = getIntFromValue(values[j]["MaxReleaseStep"]);
- if (max_release_step < 0) {
- LOG(ERROR) << "Sensor[" << name << "]'s " << cdev_name
- << " MaxReleaseStep: " << max_release_step;
- sensors_parsed->clear();
- return false;
- } else {
- LOG(INFO) << "Sensor[" << name << "]'s " << cdev_name
- << " MaxReleaseStep: " << max_release_step;
- }
- }
- if (!values[j]["MaxThrottleStep"].empty()) {
- max_throttle_step = getIntFromValue(values[j]["MaxThrottleStep"]);
- if (max_throttle_step < 0) {
- LOG(ERROR) << "Sensor[" << name << "]'s " << cdev_name
- << " MaxThrottleStep: " << max_throttle_step;
- sensors_parsed->clear();
- return false;
- } else {
- LOG(INFO) << "Sensor[" << name << "]'s " << cdev_name
- << " MaxThrottleStep: " << max_throttle_step;
- }
- }
- }
- CdevArray limit_info;
- limit_info.fill(0);
- ThrottlingArray power_thresholds;
- power_thresholds.fill(NAN);
-
- ReleaseLogic release_logic = ReleaseLogic::NONE;
-
- sub_values = values[j]["LimitInfo"];
- if (sub_values.size()) {
- LOG(INFO) << "Sensor[" << name << "]: Start to parse LimitInfo: " << cdev_name;
- if (!getIntFromJsonValues(sub_values, &limit_info, false, false)) {
- LOG(ERROR) << "Failed to parse LimitInfo";
- sensors_parsed->clear();
- return false;
- }
- support_hard_limit = true;
- }
-
- // Parse linked power info
- std::string power_rail;
- bool high_power_check = false;
- bool throttling_with_power_link = false;
- CdevArray cdev_floor_with_power_link;
- cdev_floor_with_power_link.fill(0);
-
- const bool power_link_disabled =
- android::base::GetBoolProperty(kPowerLinkDisabledProperty.data(), false);
- if (!power_link_disabled) {
- power_rail = values[j]["BindedPowerRail"].asString();
-
- if (values[j]["HighPowerCheck"].asBool()) {
- high_power_check = true;
- }
- LOG(INFO) << "Highpowercheck: " << std::boolalpha << high_power_check;
-
- if (values[j]["ThrottlingWithPowerLink"].asBool()) {
- throttling_with_power_link = true;
- }
- LOG(INFO) << "ThrottlingwithPowerLink: " << std::boolalpha
- << throttling_with_power_link;
-
- sub_values = values[j]["CdevFloorWithPowerLink"];
- if (sub_values.size()) {
- LOG(INFO) << "Sensor[" << name << "]: Start to parse " << cdev_name
- << "'s CdevFloorWithPowerLink";
- if (!getIntFromJsonValues(sub_values, &cdev_floor_with_power_link, false,
- false)) {
- LOG(ERROR) << "Failed to parse CdevFloor";
- sensors_parsed->clear();
- return false;
- }
- }
- sub_values = values[j]["PowerThreshold"];
- if (sub_values.size()) {
- LOG(INFO) << "Sensor[" << name << "]: Start to parse " << cdev_name
- << "'s PowerThreshold";
- if (!getFloatFromJsonValues(sub_values, &power_thresholds, false, false)) {
- LOG(ERROR) << "Failed to parse power thresholds";
- sensors_parsed->clear();
- return false;
- }
- if (values[j]["ReleaseLogic"].asString() == "INCREASE") {
- release_logic = ReleaseLogic::INCREASE;
- LOG(INFO) << "Release logic: INCREASE";
- } else if (values[j]["ReleaseLogic"].asString() == "DECREASE") {
- release_logic = ReleaseLogic::DECREASE;
- LOG(INFO) << "Release logic: DECREASE";
- } else if (values[j]["ReleaseLogic"].asString() == "STEPWISE") {
- release_logic = ReleaseLogic::STEPWISE;
- LOG(INFO) << "Release logic: STEPWISE";
- } else if (values[j]["ReleaseLogic"].asString() == "RELEASE_TO_FLOOR") {
- release_logic = ReleaseLogic::RELEASE_TO_FLOOR;
- LOG(INFO) << "Release logic: RELEASE_TO_FLOOR";
- } else {
- LOG(ERROR) << "Release logic is invalid";
- sensors_parsed->clear();
- return false;
- }
- }
- }
-
- binded_cdev_info_map[cdev_name] = {
- .limit_info = limit_info,
- .power_thresholds = power_thresholds,
- .release_logic = release_logic,
- .high_power_check = high_power_check,
- .throttling_with_power_link = throttling_with_power_link,
- .cdev_weight_for_pid = cdev_weight_for_pid,
- .cdev_ceiling = cdev_ceiling,
- .max_release_step = max_release_step,
- .max_throttle_step = max_throttle_step,
- .cdev_floor_with_power_link = cdev_floor_with_power_link,
- .power_rail = power_rail,
- };
+ if (!ParseBindedCdevInfo(sensors[i]["BindedCdevInfo"], &binded_cdev_info_map, support_pid,
+ &support_hard_limit)) {
+ LOG(ERROR) << "Sensor[" << name << "]: failed to parse BindedCdevInfo";
+ sensors_parsed->clear();
+ return false;
}
std::unordered_map<std::string, ThrottlingArray> excluded_power_info_map;
@@ -786,7 +802,7 @@
std::unique_ptr<VirtualSensorInfo> virtual_sensor_info;
if (is_virtual_sensor) {
virtual_sensor_info.reset(new VirtualSensorInfo{linked_sensors, coefficients, offset,
- trigger_sensor, formula});
+ trigger_sensors, formula});
}
std::shared_ptr<ThrottlingInfo> throttling_info(new ThrottlingInfo{
diff --git a/thermal/utils/thermal_info.h b/thermal/utils/thermal_info.h
index e56b358..6c7bc25 100644
--- a/thermal/utils/thermal_info.h
+++ b/thermal/utils/thermal_info.h
@@ -49,7 +49,7 @@
std::vector<std::string> linked_sensors;
std::vector<float> coefficients;
float offset;
- std::string trigger_sensor;
+ std::vector<std::string> trigger_sensors;
FormulaOption formula;
};
diff --git a/thermal/utils/thermal_throttling.cpp b/thermal/utils/thermal_throttling.cpp
index 6f2d120..fcaee8f 100644
--- a/thermal/utils/thermal_throttling.cpp
+++ b/thermal/utils/thermal_throttling.cpp
@@ -346,18 +346,21 @@
allocated_power += last_updated_avg_power;
allocated_weight += cdev_weight;
allocated_cdev.insert(binded_cdev_info_pair.first);
- log_buf.append(StringPrintf("(%s: %0.2f mW)",
- binded_cdev_info_pair.second.power_rail.c_str(),
- last_updated_avg_power));
-
+ if (!binded_cdev_info_pair.second.power_rail.empty()) {
+ log_buf.append(StringPrintf("(%s: %0.2f mW)",
+ binded_cdev_info_pair.second.power_rail.c_str(),
+ last_updated_avg_power));
+ }
LOG(VERBOSE) << temp.name << " binded " << binded_cdev_info_pair.first
<< " has been already at min state 0";
}
} else {
const CdevInfo &cdev_info = cooling_device_info_map.at(binded_cdev_info_pair.first);
- log_buf.append(StringPrintf("(%s: %0.2f mW)",
- binded_cdev_info_pair.second.power_rail.c_str(),
- last_updated_avg_power));
+ if (!binded_cdev_info_pair.second.power_rail.empty()) {
+ log_buf.append(StringPrintf("(%s: %0.2f mW)",
+ binded_cdev_info_pair.second.power_rail.c_str(),
+ last_updated_avg_power));
+ }
// Ignore the power distribution if the CDEV has no space to reduce power
if ((cdev_power_adjustment < 0 &&
thermal_throttling_status_map_[temp.name].pid_cdev_request_map.at(
diff --git a/vibrator/cs40l25/Vibrator.cpp b/vibrator/cs40l25/Vibrator.cpp
index 669258f..ba4efad 100644
--- a/vibrator/cs40l25/Vibrator.cpp
+++ b/vibrator/cs40l25/Vibrator.cpp
@@ -112,7 +112,7 @@
static constexpr float PWLE_BW_MAP_SIZE =
1 + ((PWLE_FREQUENCY_MAX_HZ - PWLE_FREQUENCY_MIN_HZ) / PWLE_FREQUENCY_RESOLUTION_HZ);
static constexpr float RAMP_DOWN_CONSTANT = 1048.576f;
-static constexpr float RAMP_DOWN_TIME_MS = 50.0f;
+static constexpr float RAMP_DOWN_TIME_MS = 0.0f;
static struct pcm_config haptic_nohost_config = {
.channels = 1,
@@ -1276,12 +1276,18 @@
// where Trd is the desired ramp down time in seconds
// pwle_ramp_down accepts only 24 bit integers values
- const float seconds = RAMP_DOWN_TIME_MS / 1000;
- const auto ramp_down_coefficient = static_cast<uint32_t>(RAMP_DOWN_CONSTANT / seconds);
-
- if (!mHwApi->setPwleRampDown(ramp_down_coefficient)) {
- ALOGE("Failed to write \"%d\" to pwle_ramp_down (%d): %s", ramp_down_coefficient, errno,
- strerror(errno));
+ if (RAMP_DOWN_TIME_MS != 0.0) {
+ const float seconds = RAMP_DOWN_TIME_MS / 1000;
+ const auto ramp_down_coefficient = static_cast<uint32_t>(RAMP_DOWN_CONSTANT / seconds);
+ if (!mHwApi->setPwleRampDown(ramp_down_coefficient)) {
+ ALOGE("Failed to write \"%d\" to pwle_ramp_down (%d): %s", ramp_down_coefficient, errno,
+ strerror(errno));
+ }
+ } else {
+ // Turn off the low level PWLE Ramp Down feature
+ if (!mHwApi->setPwleRampDown(0)) {
+ ALOGE("Failed to write 0 to pwle_ramp_down (%d): %s", errno, strerror(errno));
+ }
}
}
diff --git a/vibrator/cs40l26/Android.bp b/vibrator/cs40l26/Android.bp
index 2399a94..53764de 100644
--- a/vibrator/cs40l26/Android.bp
+++ b/vibrator/cs40l26/Android.bp
@@ -34,6 +34,13 @@
"PixelVibratorBinaryDefaults",
"android.hardware.vibrator-defaults.cs40l26",
],
+ include_dirs: [
+ "external/tinyalsa/include",
+ ],
+ shared_libs: [
+ "libcutils",
+ "libtinyalsa",
+ ],
}
cc_defaults {
@@ -42,23 +49,16 @@
"PixelVibratorTestDefaults",
"android.hardware.vibrator-defaults.cs40l26",
],
- shared_libs: ["android.hardware.vibrator-impl.cs40l26"],
- include_dirs: [
- "external/tinyalsa/include",
+ static_libs: [
+ "android.hardware.vibrator-impl.cs40l26",
+ "libtinyalsa",
],
}
-cc_library_shared {
+cc_library {
name: "android.hardware.vibrator-impl.cs40l26",
defaults: ["VibratorHalCs40l26BinaryDefaults"],
srcs: ["Vibrator.cpp"],
- include_dirs: [
- "external/tinyalsa/include",
- ],
- shared_libs: [
- "libcutils",
- "libtinyalsa",
- ],
export_include_dirs: ["."],
vendor_available: true,
visibility: [":__subpackages__"],
@@ -70,14 +70,7 @@
init_rc: ["android.hardware.vibrator-service.cs40l26.rc"],
vintf_fragments: ["android.hardware.vibrator-service.cs40l26.xml"],
srcs: ["service.cpp"],
- include_dirs: [
- "external/tinyalsa/include"
- ],
- shared_libs: [
- "android.hardware.vibrator-impl.cs40l26",
- "libcutils",
- "libtinyalsa",
- ],
+ shared_libs: ["android.hardware.vibrator-impl.cs40l26"],
proprietary: true,
}
@@ -87,14 +80,7 @@
init_rc: ["android.hardware.vibrator-service.cs40l26-dual.rc"],
vintf_fragments: ["android.hardware.vibrator-service.cs40l26-dual.xml"],
srcs: ["service.cpp"],
- include_dirs: [
- "external/tinyalsa/include"
- ],
- shared_libs: [
- "android.hardware.vibrator-impl.cs40l26",
- "libcutils",
- "libtinyalsa",
- ],
+ shared_libs: ["android.hardware.vibrator-impl.cs40l26"],
cflags: ["-DVIBRATOR_NAME=\"dual\""],
proprietary: true,
}
diff --git a/vibrator/cs40l26/Hardware.h b/vibrator/cs40l26/Hardware.h
index f0d7038..ae052ba 100644
--- a/vibrator/cs40l26/Hardware.h
+++ b/vibrator/cs40l26/Hardware.h
@@ -15,9 +15,50 @@
*/
#pragma once
+#include <algorithm>
+
#include "HardwareBase.h"
#include "Vibrator.h"
+#define PROC_SND_PCM "/proc/asound/pcm"
+#define HAPTIC_PCM_DEVICE_SYMBOL "haptic nohost playback"
+
+static struct pcm_config haptic_nohost_config = {
+ .channels = 1,
+ .rate = 48000,
+ .period_size = 80,
+ .period_count = 2,
+ .format = PCM_FORMAT_S16_LE,
+};
+
+enum WaveformIndex : uint16_t {
+ /* Physical waveform */
+ WAVEFORM_LONG_VIBRATION_EFFECT_INDEX = 0,
+ WAVEFORM_RESERVED_INDEX_1 = 1,
+ WAVEFORM_CLICK_INDEX = 2,
+ WAVEFORM_SHORT_VIBRATION_EFFECT_INDEX = 3,
+ WAVEFORM_THUD_INDEX = 4,
+ WAVEFORM_SPIN_INDEX = 5,
+ WAVEFORM_QUICK_RISE_INDEX = 6,
+ WAVEFORM_SLOW_RISE_INDEX = 7,
+ WAVEFORM_QUICK_FALL_INDEX = 8,
+ WAVEFORM_LIGHT_TICK_INDEX = 9,
+ WAVEFORM_LOW_TICK_INDEX = 10,
+ WAVEFORM_RESERVED_MFG_1,
+ WAVEFORM_RESERVED_MFG_2,
+ WAVEFORM_RESERVED_MFG_3,
+ WAVEFORM_MAX_PHYSICAL_INDEX,
+ /* OWT waveform */
+ WAVEFORM_COMPOSE = WAVEFORM_MAX_PHYSICAL_INDEX,
+ WAVEFORM_PWLE,
+ /*
+ * Refer to <linux/input.h>, the WAVEFORM_MAX_INDEX must not exceed 96.
+ * #define FF_GAIN 0x60 // 96 in decimal
+ * #define FF_MAX_EFFECTS FF_GAIN
+ */
+ WAVEFORM_MAX_INDEX,
+};
+
namespace aidl {
namespace android {
namespace hardware {
@@ -51,6 +92,165 @@
bool setF0CompEnable(bool value) override { return set(value, &mF0CompEnable); }
bool setRedcCompEnable(bool value) override { return set(value, &mRedcCompEnable); }
bool setMinOnOffInterval(uint32_t value) override { return set(value, &mMinOnOffInterval); }
+ // TODO(b/234338136): Need to add the force feedback HW API test cases
+ bool setFFGain(int fd, uint16_t value) override {
+ struct input_event gain = {
+ .type = EV_FF,
+ .code = FF_GAIN,
+ .value = value,
+ };
+ if (write(fd, (const void *)&gain, sizeof(gain)) != sizeof(gain)) {
+ return false;
+ }
+ return true;
+ }
+ bool setFFEffect(int fd, struct ff_effect *effect, uint16_t timeoutMs) override {
+ if (((*effect).replay.length != timeoutMs) || (ioctl(fd, EVIOCSFF, effect) < 0)) {
+ ALOGE("setFFEffect fail");
+ return false;
+ } else {
+ return true;
+ }
+ }
+ bool setFFPlay(int fd, int8_t index, bool value) override {
+ struct input_event play = {
+ .type = EV_FF,
+ .code = static_cast<uint16_t>(index),
+ .value = value,
+ };
+ if (write(fd, (const void *)&play, sizeof(play)) != sizeof(play)) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+ bool getHapticAlsaDevice(int *card, int *device) override {
+ std::string line;
+ std::ifstream myfile(PROC_SND_PCM);
+ if (myfile.is_open()) {
+ while (getline(myfile, line)) {
+ if (line.find(HAPTIC_PCM_DEVICE_SYMBOL) != std::string::npos) {
+ std::stringstream ss(line);
+ std::string currentToken;
+ std::getline(ss, currentToken, ':');
+ sscanf(currentToken.c_str(), "%d-%d", card, device);
+ return true;
+ }
+ }
+ myfile.close();
+ } else {
+ ALOGE("Failed to read file: %s", PROC_SND_PCM);
+ }
+ return false;
+ }
+ bool setHapticPcmAmp(struct pcm **haptic_pcm, bool enable, int card, int device) override {
+ int ret = 0;
+
+ if (enable) {
+ *haptic_pcm = pcm_open(card, device, PCM_OUT, &haptic_nohost_config);
+ if (!pcm_is_ready(*haptic_pcm)) {
+ ALOGE("cannot open pcm_out driver: %s", pcm_get_error(*haptic_pcm));
+ goto fail;
+ }
+
+ ret = pcm_prepare(*haptic_pcm);
+ if (ret < 0) {
+ ALOGE("cannot prepare haptic_pcm: %s", pcm_get_error(*haptic_pcm));
+ goto fail;
+ }
+
+ ret = pcm_start(*haptic_pcm);
+ if (ret < 0) {
+ ALOGE("cannot start haptic_pcm: %s", pcm_get_error(*haptic_pcm));
+ goto fail;
+ }
+
+ return true;
+ } else {
+ if (*haptic_pcm) {
+ pcm_close(*haptic_pcm);
+ *haptic_pcm = NULL;
+ }
+ return true;
+ }
+
+ fail:
+ pcm_close(*haptic_pcm);
+ *haptic_pcm = NULL;
+ return false;
+ }
+ bool uploadOwtEffect(int fd, uint8_t *owtData, uint32_t numBytes, struct ff_effect *effect,
+ uint32_t *outEffectIndex, int *status) override {
+ (*effect).u.periodic.custom_len = numBytes / sizeof(uint16_t);
+ delete[] ((*effect).u.periodic.custom_data);
+ (*effect).u.periodic.custom_data = new int16_t[(*effect).u.periodic.custom_len]{0x0000};
+ if ((*effect).u.periodic.custom_data == nullptr) {
+ ALOGE("Failed to allocate memory for custom data\n");
+ *status = EX_NULL_POINTER;
+ return false;
+ }
+ memcpy((*effect).u.periodic.custom_data, owtData, numBytes);
+
+ if ((*effect).id != -1) {
+ ALOGE("(*effect).id != -1");
+ }
+
+ /* Create a new OWT waveform to update the PWLE or composite effect. */
+ (*effect).id = -1;
+ if (ioctl(fd, EVIOCSFF, effect) < 0) {
+ ALOGE("Failed to upload effect %d (%d): %s", *outEffectIndex, errno, strerror(errno));
+ delete[] ((*effect).u.periodic.custom_data);
+ *status = EX_ILLEGAL_STATE;
+ return false;
+ }
+
+ if ((*effect).id >= FF_MAX_EFFECTS || (*effect).id < 0) {
+ ALOGE("Invalid waveform index after upload OWT effect: %d", (*effect).id);
+ *status = EX_ILLEGAL_ARGUMENT;
+ return false;
+ }
+ *outEffectIndex = (*effect).id;
+ *status = 0;
+ return true;
+ }
+ bool eraseOwtEffect(int fd, int8_t effectIndex, std::vector<ff_effect> *effect) override {
+ uint32_t effectCountBefore, effectCountAfter, i, successFlush = 0;
+
+ if (effectIndex < WAVEFORM_MAX_PHYSICAL_INDEX) {
+ ALOGE("Invalid waveform index for OWT erase: %d", effectIndex);
+ return false;
+ }
+
+ if (effectIndex < WAVEFORM_MAX_INDEX) {
+ /* Normal situation. Only erase the effect which we just played. */
+ if (ioctl(fd, EVIOCRMFF, effectIndex) < 0) {
+ ALOGE("Failed to erase effect %d (%d): %s", effectIndex, errno, strerror(errno));
+ }
+ for (i = WAVEFORM_MAX_PHYSICAL_INDEX; i < WAVEFORM_MAX_INDEX; i++) {
+ if ((*effect)[i].id == effectIndex) {
+ (*effect)[i].id = -1;
+ break;
+ }
+ }
+ } else {
+ /* Flush all non-prestored effects of ff-core and driver. */
+ getEffectCount(&effectCountBefore);
+ for (i = WAVEFORM_MAX_PHYSICAL_INDEX; i < FF_MAX_EFFECTS; i++) {
+ if (ioctl(fd, EVIOCRMFF, i) >= 0) {
+ successFlush++;
+ }
+ }
+ getEffectCount(&effectCountAfter);
+ ALOGW("Flushed effects: ff: %d; driver: %d -> %d; success: %d", effectIndex,
+ effectCountBefore, effectCountAfter, successFlush);
+ /* Reset all OWT effect index of HAL. */
+ for (i = WAVEFORM_MAX_PHYSICAL_INDEX; i < WAVEFORM_MAX_INDEX; i++) {
+ (*effect)[i].id = -1;
+ }
+ }
+ return true;
+ }
+
void debug(int fd) override { HwApiBase::debug(fd); }
private:
@@ -79,7 +279,7 @@
static constexpr uint32_t VERSION_DEFAULT = 2;
static constexpr int32_t DEFAULT_FREQUENCY_SHIFT = 0;
static constexpr std::array<uint32_t, 2> V_TICK_DEFAULT = {1, 100};
- static constexpr std::array<uint32_t, 2> V_CTICK_DEFAULT = {1, 100};
+ static constexpr std::array<uint32_t, 2> V_CLICK_DEFAULT = {1, 100};
static constexpr std::array<uint32_t, 2> V_LONG_DEFAULT = {1, 100};
public:
@@ -109,7 +309,7 @@
if (getPersist(CLICK_VOLTAGES_CONFIG, value)) {
return true;
}
- *value = V_CTICK_DEFAULT;
+ *value = V_CLICK_DEFAULT;
return true;
}
bool getLongVolLevels(std::array<uint32_t, 2> *value) override {
@@ -127,6 +327,16 @@
bool getSupportedPrimitives(uint32_t *value) override {
return getProperty("supported_primitives", value, (uint32_t)0);
}
+ bool isF0CompEnabled() override {
+ bool value;
+ getProperty("f0.comp.enabled", &value, true);
+ return value;
+ }
+ bool isRedcCompEnabled() override {
+ bool value;
+ getProperty("redc.comp.enabled", &value, true);
+ return value;
+ }
void debug(int fd) override { HwCalBase::debug(fd); }
};
diff --git a/vibrator/cs40l26/TEST_MAPPING b/vibrator/cs40l26/TEST_MAPPING
new file mode 100644
index 0000000..1d8ff7a
--- /dev/null
+++ b/vibrator/cs40l26/TEST_MAPPING
@@ -0,0 +1,10 @@
+{
+ "presubmit": [
+ {
+ "name": "VibratorHalCs40l26TestSuite",
+ "keywords": [
+ "nextgen"
+ ]
+ }
+ ]
+}
diff --git a/vibrator/cs40l26/Vibrator.cpp b/vibrator/cs40l26/Vibrator.cpp
index c6324c7..3af057c 100644
--- a/vibrator/cs40l26/Vibrator.cpp
+++ b/vibrator/cs40l26/Vibrator.cpp
@@ -33,9 +33,6 @@
#define ARRAY_SIZE(x) (sizeof((x)) / sizeof((x)[0]))
#endif
-#define PROC_SND_PCM "/proc/asound/pcm"
-#define HAPTIC_PCM_DEVICE_SYMBOL "haptic nohost playback"
-
namespace aidl {
namespace android {
namespace hardware {
@@ -92,14 +89,6 @@
static constexpr float PWLE_BW_MAP_SIZE =
1 + ((PWLE_FREQUENCY_MAX_HZ - PWLE_FREQUENCY_MIN_HZ) / PWLE_FREQUENCY_RESOLUTION_HZ);
-static struct pcm_config haptic_nohost_config = {
- .channels = 1,
- .rate = 48000,
- .period_size = 80,
- .period_count = 2,
- .format = PCM_FORMAT_S16_LE,
-};
-
static uint16_t amplitudeToScale(float amplitude, float maximum) {
float ratio = 100; /* Unit: % */
if (maximum != 0)
@@ -111,11 +100,6 @@
return std::round(ratio);
}
-enum class AlwaysOnId : uint32_t {
- GPIO_RISE,
- GPIO_FALL,
-};
-
enum WaveformBankID : uint8_t {
RAM_WVFRM_BANK,
ROM_WVFRM_BANK,
@@ -159,6 +143,8 @@
VIBE_STATE_ASP,
};
+std::mutex mActiveId_mutex; // protects mActiveId
+
static int min(int x, int y) {
return x < y ? x : y;
}
@@ -239,46 +225,52 @@
std::string caldata{8, '0'};
uint32_t calVer;
- glob_t inputEventPaths;
- int fd = -1;
- int ret;
- uint32_t val = 0;
- char str[20] = {0x00};
const char *inputEventName = std::getenv("INPUT_EVENT_NAME");
- for (uint8_t retry = 0; retry < 10; retry++) {
- ret = glob("/dev/input/event*", 0, nullptr, &inputEventPaths);
- if (ret) {
- ALOGE("Fail to get input event paths (%d): %s", errno, strerror(errno));
- } else {
- for (int i = 0; i < inputEventPaths.gl_pathc; i++) {
- fd = TEMP_FAILURE_RETRY(open(inputEventPaths.gl_pathv[i], O_RDWR));
- if (fd > 0) {
- if (ioctl(fd, EVIOCGBIT(0, sizeof(val)), &val) > 0 && (val & (1 << EV_FF)) &&
- ioctl(fd, EVIOCGNAME(sizeof(str)), &str) > 0 &&
- strcmp(str, inputEventName) == 0) {
- mInputFd.reset(fd);
- ALOGI("Control %s through %s", inputEventName, inputEventPaths.gl_pathv[i]);
- break;
+ const char *inputEventPathName = std::getenv("INPUT_EVENT_PATH");
+ if ((strstr(inputEventName, "cs40l26") != nullptr) ||
+ (strstr(inputEventName, "cs40l26_dual_input") != nullptr)) {
+ glob_t inputEventPaths;
+ int fd = -1;
+ int ret;
+ uint32_t val = 0;
+ char str[20] = {0x00};
+ for (uint8_t retry = 0; retry < 10; retry++) {
+ ret = glob(inputEventPathName, 0, nullptr, &inputEventPaths);
+ if (ret) {
+ ALOGE("Fail to get input event paths (%d): %s", errno, strerror(errno));
+ } else {
+ for (int i = 0; i < inputEventPaths.gl_pathc; i++) {
+ fd = TEMP_FAILURE_RETRY(open(inputEventPaths.gl_pathv[i], O_RDWR));
+ if (fd > 0) {
+ if (ioctl(fd, EVIOCGBIT(0, sizeof(val)), &val) > 0 &&
+ (val & (1 << EV_FF)) && ioctl(fd, EVIOCGNAME(sizeof(str)), &str) > 0 &&
+ strstr(str, inputEventName) != nullptr) {
+ mInputFd.reset(fd);
+ ALOGI("Control %s through %s", inputEventName,
+ inputEventPaths.gl_pathv[i]);
+ break;
+ }
+ close(fd);
}
- close(fd);
}
}
+
+ if (ret == 0) {
+ globfree(&inputEventPaths);
+ }
+ if (mInputFd.ok()) {
+ break;
+ }
+
+ sleep(1);
+ ALOGW("Retry #%d to search in %zu input devices.", retry, inputEventPaths.gl_pathc);
}
- if (ret == 0) {
- globfree(&inputEventPaths);
+ if (!mInputFd.ok()) {
+ ALOGE("Fail to get an input event with name %s", inputEventName);
}
- if (mInputFd.ok()) {
- break;
- }
-
- sleep(1);
- ALOGW("Retry #%d to search in %zu input devices.", retry, inputEventPaths.gl_pathc);
- }
-
- if (!mInputFd.ok()) {
- ALOGE("Fail to get an input event with name %s", inputEventName);
- return;
+ } else {
+ ALOGE("The input name %s is not cs40l26_input or cs40l26_dual_input", inputEventName);
}
mFfEffects.resize(WAVEFORM_MAX_INDEX);
@@ -299,9 +291,14 @@
.u.periodic.custom_data = new int16_t[2]{RAM_WVFRM_BANK, effectIndex},
.u.periodic.custom_len = FF_CUSTOM_DATA_LEN,
};
-
- if (ioctl(mInputFd, EVIOCSFF, &mFfEffects[effectIndex]) < 0) {
- ALOGE("Failed upload effect %d (%d): %s", effectIndex, errno, strerror(errno));
+ // Bypass the waveform update due to different input name
+ if ((strstr(inputEventName, "cs40l26") != nullptr) ||
+ (strstr(inputEventName, "cs40l26_dual_input") != nullptr)) {
+ if (!mHwApi->setFFEffect(
+ mInputFd, &mFfEffects[effectIndex],
+ static_cast<uint16_t>(mFfEffects[effectIndex].replay.length))) {
+ ALOGE("Failed upload effect %d (%d): %s", effectIndex, errno, strerror(errno));
+ }
}
if (mFfEffects[effectIndex].id != effectIndex) {
ALOGW("Unexpected effect index: %d -> %d", effectIndex, mFfEffects[effectIndex].id);
@@ -344,11 +341,11 @@
mHwCal->getClickVolLevels(&mClickEffectVol);
mHwCal->getLongVolLevels(&mLongEffectVol);
} else {
- ALOGW("Unsupported calibration version!");
+ ALOGD("Unsupported calibration version: %u!", calVer);
}
- mHwApi->setF0CompEnable(true);
- mHwApi->setRedcCompEnable(true);
+ mHwApi->setF0CompEnable(mHwCal->isF0CompEnabled());
+ mHwApi->setRedcCompEnable(mHwCal->isRedcCompEnabled());
mIsUnderExternalControl = false;
@@ -375,14 +372,15 @@
ATRACE_NAME("Vibrator::getCapabilities");
int32_t ret = IVibrator::CAP_ON_CALLBACK | IVibrator::CAP_PERFORM_CALLBACK |
- IVibrator::CAP_AMPLITUDE_CONTROL | IVibrator::CAP_ALWAYS_ON_CONTROL |
- IVibrator::CAP_GET_RESONANT_FREQUENCY | IVibrator::CAP_GET_Q_FACTOR;
+ IVibrator::CAP_AMPLITUDE_CONTROL | IVibrator::CAP_GET_RESONANT_FREQUENCY |
+ IVibrator::CAP_GET_Q_FACTOR;
if (hasHapticAlsaDevice()) {
ret |= IVibrator::CAP_EXTERNAL_CONTROL;
+ } else {
+ ALOGE("No haptics ALSA device");
}
if (mHwApi->hasOwtFreeSpace()) {
ret |= IVibrator::CAP_COMPOSE_EFFECTS;
-
if (mIsChirpEnabled) {
ret |= IVibrator::CAP_FREQUENCY_CONTROL | IVibrator::CAP_COMPOSE_PWLE_EFFECTS;
}
@@ -393,20 +391,23 @@
ndk::ScopedAStatus Vibrator::off() {
ATRACE_NAME("Vibrator::off");
- if (mActiveId < 0) {
- return ndk::ScopedAStatus::ok();
- }
+ bool ret{true};
+ const std::scoped_lock<std::mutex> lock(mActiveId_mutex);
- struct input_event play = {
- .type = EV_FF,
- .code = static_cast<uint16_t>(mActiveId),
- .value = 0,
- };
+ if (mActiveId >= 0) {
+ /* Stop the active effect. */
+ if (!mHwApi->setFFPlay(mInputFd, mActiveId, false)) {
+ ALOGE("Failed to stop effect %d (%d): %s", mActiveId, errno, strerror(errno));
+ ret = false;
+ }
- /* Stop the active effect. */
- if (write(mInputFd, (const void *)&play, sizeof(play)) != sizeof(play)) {
- ALOGE("Failed to stop effect %d (%d): %s", play.code, errno, strerror(errno));
- return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ if ((mActiveId >= WAVEFORM_MAX_PHYSICAL_INDEX) &&
+ (!mHwApi->eraseOwtEffect(mInputFd, mActiveId, &mFfEffects))) {
+ ALOGE("Failed to clean up the composed effect %d", mActiveId);
+ ret = false;
+ }
+ } else {
+ ALOGV("Vibrator is already off");
}
mActiveId = -1;
@@ -415,11 +416,11 @@
mHwApi->setF0Offset(0);
}
- if (play.code >= WAVEFORM_MAX_PHYSICAL_INDEX) {
- eraseOwtEffect(play.code);
+ if (ret) {
+ return ndk::ScopedAStatus::ok();
+ } else {
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
-
- return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Vibrator::on(int32_t timeoutMs,
@@ -472,11 +473,14 @@
ATRACE_NAME("Vibrator::setExternalControl");
setGlobalAmplitude(enabled);
- if (mHasHapticAlsaDevice) {
- if (!enableHapticPcmAmp(&mHapticPcm, enabled, mCard, mDevice)) {
+ if (mHasHapticAlsaDevice || mConfigHapticAlsaDeviceDone || hasHapticAlsaDevice()) {
+ if (!mHwApi->setHapticPcmAmp(&mHapticPcm, enabled, mCard, mDevice)) {
ALOGE("Failed to %s haptic pcm device: %d", (enabled ? "enable" : "disable"), mDevice);
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
+ } else {
+ ALOGE("No haptics ALSA device");
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
mIsUnderExternalControl = enabled;
@@ -602,65 +606,69 @@
ndk::ScopedAStatus Vibrator::on(uint32_t timeoutMs, uint32_t effectIndex, dspmem_chunk *ch,
const std::shared_ptr<IVibratorCallback> &callback) {
ndk::ScopedAStatus status = ndk::ScopedAStatus::ok();
- struct input_event play = {
- .type = EV_FF,
- .value = 1,
- };
if (effectIndex >= FF_MAX_EFFECTS) {
ALOGE("Invalid waveform index %d", effectIndex);
- status = ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
- goto end;
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
if (mAsyncHandle.wait_for(ASYNC_COMPLETION_TIMEOUT) != std::future_status::ready) {
ALOGE("Previous vibration pending: prev: %d, curr: %d", mActiveId, effectIndex);
- status = ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
- goto end;
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
if (ch) {
/* Upload OWT effect. */
- effectIndex = 0;
- status = uploadOwtEffect(ch->head, dspmem_chunk_bytes(ch), &effectIndex);
+ if (ch->head == nullptr) {
+ ALOGE("Invalid OWT bank");
+ delete ch;
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ bool isPwle = (*reinterpret_cast<uint16_t *>(ch->head) != 0x0000);
+ effectIndex = isPwle ? WAVEFORM_PWLE : WAVEFORM_COMPOSE;
+
+ uint32_t freeBytes;
+ mHwApi->getOwtFreeSpace(&freeBytes);
+ if (dspmem_chunk_bytes(ch) > freeBytes) {
+ ALOGE("Invalid OWT length: Effect %d: %d > %d!", effectIndex, dspmem_chunk_bytes(ch),
+ freeBytes);
+ delete ch;
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ int errorStatus;
+ if (!mHwApi->uploadOwtEffect(mInputFd, ch->head, dspmem_chunk_bytes(ch),
+ &mFfEffects[effectIndex], &effectIndex, &errorStatus)) {
+ delete ch;
+ ALOGE("Invalid uploadOwtEffect");
+ return ndk::ScopedAStatus::fromExceptionCode(errorStatus);
+ }
delete ch;
- if (!status.isOk()) {
- goto end;
- }
} else if (effectIndex == WAVEFORM_SHORT_VIBRATION_EFFECT_INDEX ||
effectIndex == WAVEFORM_LONG_VIBRATION_EFFECT_INDEX) {
/* Update duration for long/short vibration. */
mFfEffects[effectIndex].replay.length = static_cast<uint16_t>(timeoutMs);
- if (ioctl(mInputFd, EVIOCSFF, &mFfEffects[effectIndex]) < 0) {
+ if (!mHwApi->setFFEffect(mInputFd, &mFfEffects[effectIndex],
+ static_cast<uint16_t>(timeoutMs))) {
ALOGE("Failed to edit effect %d (%d): %s", effectIndex, errno, strerror(errno));
- status = ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
- goto end;
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
}
- play.code = effectIndex;
- mActiveId = play.code;
+ const std::scoped_lock<std::mutex> lock(mActiveId_mutex);
+ mActiveId = effectIndex;
/* Play the event now. */
- if (write(mInputFd, (const void *)&play, sizeof(play)) != sizeof(play)) {
- ALOGE("Failed to play effect %d (%d): %s", play.code, errno, strerror(errno));
- status = ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
- goto end;
+ if (!mHwApi->setFFPlay(mInputFd, effectIndex, true)) {
+ ALOGE("Failed to play effect %d (%d): %s", effectIndex, errno, strerror(errno));
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
mAsyncHandle = std::async(&Vibrator::waitForComplete, this, callback);
-
-end:
- return status;
+ return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Vibrator::setEffectAmplitude(float amplitude, float maximum) {
uint16_t scale = amplitudeToScale(amplitude, maximum);
- struct input_event gain = {
- .type = EV_FF,
- .code = FF_GAIN,
- .value = scale,
- };
- if (write(mInputFd, (const void *)&gain, sizeof(gain)) != sizeof(gain)) {
+ if (!mHwApi->setFFGain(mInputFd, scale)) {
ALOGE("Failed to set the gain to %u (%d): %s", scale, errno, strerror(errno));
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
@@ -675,47 +683,15 @@
return setEffectAmplitude(amplitude, VOLTAGE_SCALE_MAX);
}
-ndk::ScopedAStatus Vibrator::getSupportedAlwaysOnEffects(std::vector<Effect> *_aidl_return) {
- *_aidl_return = {Effect::TEXTURE_TICK, Effect::TICK, Effect::CLICK, Effect::HEAVY_CLICK};
- return ndk::ScopedAStatus::ok();
-}
-
-ndk::ScopedAStatus Vibrator::alwaysOnEnable(int32_t id, Effect effect, EffectStrength strength) {
- ndk::ScopedAStatus status;
- uint32_t effectIndex;
- uint32_t timeMs;
- uint32_t volLevel;
- uint16_t scale;
- status = getSimpleDetails(effect, strength, &effectIndex, &timeMs, &volLevel);
- if (!status.isOk()) {
- return status;
- }
-
- scale = amplitudeToScale(volLevel, VOLTAGE_SCALE_MAX);
-
- switch (static_cast<AlwaysOnId>(id)) {
- case AlwaysOnId::GPIO_RISE:
- // mHwApi->setGpioRiseIndex(effectIndex);
- // mHwApi->setGpioRiseScale(scale);
- return ndk::ScopedAStatus::ok();
- case AlwaysOnId::GPIO_FALL:
- // mHwApi->setGpioFallIndex(effectIndex);
- // mHwApi->setGpioFallScale(scale);
- return ndk::ScopedAStatus::ok();
- }
-
+ndk::ScopedAStatus Vibrator::getSupportedAlwaysOnEffects(std::vector<Effect> * /*_aidl_return*/) {
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
-ndk::ScopedAStatus Vibrator::alwaysOnDisable(int32_t id) {
- switch (static_cast<AlwaysOnId>(id)) {
- case AlwaysOnId::GPIO_RISE:
- // mHwApi->setGpioRiseIndex(0);
- return ndk::ScopedAStatus::ok();
- case AlwaysOnId::GPIO_FALL:
- // mHwApi->setGpioFallIndex(0);
- return ndk::ScopedAStatus::ok();
- }
+ndk::ScopedAStatus Vibrator::alwaysOnEnable(int32_t /*id*/, Effect /*effect*/,
+ EffectStrength /*strength*/) {
+ return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+}
+ndk::ScopedAStatus Vibrator::alwaysOnDisable(int32_t /*id*/) {
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
@@ -1082,79 +1058,23 @@
return STATUS_OK;
}
-bool Vibrator::findHapticAlsaDevice(int *card, int *device) {
- std::string line;
- std::ifstream myfile(PROC_SND_PCM);
- if (myfile.is_open()) {
- while (getline(myfile, line)) {
- if (line.find(HAPTIC_PCM_DEVICE_SYMBOL) != std::string::npos) {
- std::stringstream ss(line);
- std::string currentToken;
- std::getline(ss, currentToken, ':');
- sscanf(currentToken.c_str(), "%d-%d", card, device);
- return true;
- }
- }
- myfile.close();
- } else {
- ALOGE("Failed to read file: %s", PROC_SND_PCM);
- }
- return false;
-}
-
bool Vibrator::hasHapticAlsaDevice() {
// We need to call findHapticAlsaDevice once only. Calling in the
// constructor is too early in the boot process and the pcm file contents
// are empty. Hence we make the call here once only right before we need to.
- static bool configHapticAlsaDeviceDone = false;
- if (!configHapticAlsaDeviceDone) {
- if (findHapticAlsaDevice(&mCard, &mDevice)) {
+ if (!mConfigHapticAlsaDeviceDone) {
+ if (mHwApi->getHapticAlsaDevice(&mCard, &mDevice)) {
mHasHapticAlsaDevice = true;
- configHapticAlsaDeviceDone = true;
+ mConfigHapticAlsaDeviceDone = true;
} else {
ALOGE("Haptic ALSA device not supported");
}
+ } else {
+ ALOGD("Haptic ALSA device configuration done.");
}
return mHasHapticAlsaDevice;
}
-bool Vibrator::enableHapticPcmAmp(struct pcm **haptic_pcm, bool enable, int card, int device) {
- int ret = 0;
-
- if (enable) {
- *haptic_pcm = pcm_open(card, device, PCM_OUT, &haptic_nohost_config);
- if (!pcm_is_ready(*haptic_pcm)) {
- ALOGE("cannot open pcm_out driver: %s", pcm_get_error(*haptic_pcm));
- goto fail;
- }
-
- ret = pcm_prepare(*haptic_pcm);
- if (ret < 0) {
- ALOGE("cannot prepare haptic_pcm: %s", pcm_get_error(*haptic_pcm));
- goto fail;
- }
-
- ret = pcm_start(*haptic_pcm);
- if (ret < 0) {
- ALOGE("cannot start haptic_pcm: %s", pcm_get_error(*haptic_pcm));
- goto fail;
- }
-
- return true;
- } else {
- if (*haptic_pcm) {
- pcm_close(*haptic_pcm);
- *haptic_pcm = NULL;
- }
- return true;
- }
-
-fail:
- pcm_close(*haptic_pcm);
- *haptic_pcm = NULL;
- return false;
-}
-
ndk::ScopedAStatus Vibrator::getSimpleDetails(Effect effect, EffectStrength strength,
uint32_t *outEffectIndex, uint32_t *outTimeMs,
uint32_t *outVolLevel) {
@@ -1303,94 +1223,6 @@
return ndk::ScopedAStatus::ok();
}
-ndk::ScopedAStatus Vibrator::uploadOwtEffect(uint8_t *owtData, uint32_t numBytes,
- uint32_t *outEffectIndex) {
- if (owtData == nullptr) {
- ALOGE("Invalid OWT data");
- return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
- }
-
- bool isPwle = (*reinterpret_cast<uint16_t *>(owtData) != 0x0000);
- WaveformIndex targetId = isPwle ? WAVEFORM_PWLE : WAVEFORM_COMPOSE;
-
- uint32_t freeBytes;
- mHwApi->getOwtFreeSpace(&freeBytes);
- if (numBytes > freeBytes) {
- ALOGE("Invalid OWT length: Effect %d: %d > %d!", targetId, numBytes, freeBytes);
- return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
- }
-
- mFfEffects[targetId].u.periodic.custom_len = numBytes / sizeof(uint16_t);
- delete[](mFfEffects[targetId].u.periodic.custom_data);
- mFfEffects[targetId].u.periodic.custom_data =
- new int16_t[mFfEffects[targetId].u.periodic.custom_len]{0x0000};
- if (mFfEffects[targetId].u.periodic.custom_data == nullptr) {
- ALOGE("Failed to allocate memory for custom data\n");
- return ndk::ScopedAStatus::fromExceptionCode(EX_NULL_POINTER);
- }
- memcpy(mFfEffects[targetId].u.periodic.custom_data, owtData, numBytes);
-
- if (mFfEffects[targetId].id != -1) {
- ALOGE("mFfEffects[targetId].id != -1");
- }
-
- /* Create a new OWT waveform to update the PWLE or composite effect. */
- mFfEffects[targetId].id = -1;
- if (ioctl(mInputFd, EVIOCSFF, &mFfEffects[targetId]) < 0) {
- ALOGE("Failed to upload effect %d (%d): %s", targetId, errno, strerror(errno));
- delete[](mFfEffects[targetId].u.periodic.custom_data);
- return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
- }
-
- if (mFfEffects[targetId].id >= FF_MAX_EFFECTS || mFfEffects[targetId].id < 0) {
- ALOGE("Invalid waveform index after upload OWT effect: %d", mFfEffects[targetId].id);
- return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
- }
-
- *outEffectIndex = mFfEffects[targetId].id;
-
- return ndk::ScopedAStatus::ok();
-}
-
-ndk::ScopedAStatus Vibrator::eraseOwtEffect(int8_t effectIndex) {
- uint32_t effectCountBefore, effectCountAfter, i, successFlush = 0;
-
- if (effectIndex < WAVEFORM_MAX_PHYSICAL_INDEX) {
- ALOGE("Invalid waveform index for OWT erase: %d", effectIndex);
- return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
- }
-
- if (effectIndex < WAVEFORM_MAX_INDEX) {
- /* Normal situation. Only erase the effect which we just played. */
- if (ioctl(mInputFd, EVIOCRMFF, effectIndex) < 0) {
- ALOGE("Failed to erase effect %d (%d): %s", effectIndex, errno, strerror(errno));
- }
- for (i = WAVEFORM_MAX_PHYSICAL_INDEX; i < WAVEFORM_MAX_INDEX; i++) {
- if (mFfEffects[i].id == effectIndex) {
- mFfEffects[i].id = -1;
- break;
- }
- }
- } else {
- /* Flush all non-prestored effects of ff-core and driver. */
- mHwApi->getEffectCount(&effectCountBefore);
- for (i = WAVEFORM_MAX_PHYSICAL_INDEX; i < FF_MAX_EFFECTS; i++) {
- if (ioctl(mInputFd, EVIOCRMFF, i) >= 0) {
- successFlush++;
- }
- }
- mHwApi->getEffectCount(&effectCountAfter);
- ALOGW("Flushed effects: ff: %d; driver: %d -> %d; success: %d", effectIndex,
- effectCountBefore, effectCountAfter, successFlush);
- /* Reset all OWT effect index of HAL. */
- for (i = WAVEFORM_MAX_PHYSICAL_INDEX; i < WAVEFORM_MAX_INDEX; i++) {
- mFfEffects[i].id = -1;
- }
- }
-
- return ndk::ScopedAStatus::ok();
-}
-
ndk::ScopedAStatus Vibrator::performEffect(Effect effect, EffectStrength strength,
const std::shared_ptr<IVibratorCallback> &callback,
int32_t *outTimeMs) {
@@ -1399,7 +1231,6 @@
uint32_t timeMs = 0;
uint32_t volLevel;
dspmem_chunk *ch = nullptr;
-
switch (effect) {
case Effect::TEXTURE_TICK:
// fall-through
@@ -1445,8 +1276,10 @@
}
mHwApi->pollVibeState(VIBE_STATE_STOPPED);
- if (mActiveId >= WAVEFORM_MAX_PHYSICAL_INDEX) {
- eraseOwtEffect(mActiveId);
+ const std::scoped_lock<std::mutex> lock(mActiveId_mutex);
+ if ((mActiveId >= WAVEFORM_MAX_PHYSICAL_INDEX) &&
+ (!mHwApi->eraseOwtEffect(mInputFd, mActiveId, &mFfEffects))) {
+ ALOGE("Failed to clean up the composed effect %d", mActiveId);
}
mActiveId = -1;
diff --git a/vibrator/cs40l26/Vibrator.h b/vibrator/cs40l26/Vibrator.h
index 4647725..220c974 100644
--- a/vibrator/cs40l26/Vibrator.h
+++ b/vibrator/cs40l26/Vibrator.h
@@ -61,6 +61,25 @@
virtual bool setRedcCompEnable(bool value) = 0;
// Stores the minumun delay time between playback and stop effects.
virtual bool setMinOnOffInterval(uint32_t value) = 0;
+ // Indicates the number of 0.125-dB steps of attenuation to apply to
+ // waveforms triggered in response to vibration calls from the
+ // Android vibrator HAL.
+ virtual bool setFFGain(int fd, uint16_t value) = 0;
+ // Create/modify custom effects for all physical waveforms.
+ virtual bool setFFEffect(int fd, struct ff_effect *effect, uint16_t timeoutMs) = 0;
+ // Activates/deactivates the effect index after setFFGain() and setFFEffect().
+ virtual bool setFFPlay(int fd, int8_t index, bool value) = 0;
+ // Get the Alsa device for the audio coupled haptics effect
+ virtual bool getHapticAlsaDevice(int *card, int *device) = 0;
+ // Set haptics PCM amplifier before triggering audio haptics feature
+ virtual bool setHapticPcmAmp(struct pcm **haptic_pcm, bool enable, int card,
+ int device) = 0;
+ // Set OWT waveform for compose or compose PWLE request
+ virtual bool uploadOwtEffect(int fd, uint8_t *owtData, uint32_t numBytes,
+ struct ff_effect *effect, uint32_t *outEffectIndex,
+ int *status) = 0;
+ // Erase OWT waveform
+ virtual bool eraseOwtEffect(int fd, int8_t effectIndex, std::vector<ff_effect> *effect) = 0;
// Emit diagnostic information to the given file.
virtual void debug(int fd) = 0;
};
@@ -91,6 +110,10 @@
virtual bool isChirpEnabled() = 0;
// Obtains the supported primitive effects.
virtual bool getSupportedPrimitives(uint32_t *value) = 0;
+ // Checks if the f0 compensation feature needs to be enabled.
+ virtual bool isF0CompEnabled() = 0;
+ // Checks if the redc compensation feature needs to be enabled.
+ virtual bool isRedcCompEnabled() = 0;
// Emit diagnostic information to the given file.
virtual void debug(int fd) = 0;
};
@@ -145,9 +168,6 @@
ndk::ScopedAStatus getCompoundDetails(Effect effect, EffectStrength strength,
uint32_t *outTimeMs, struct dspmem_chunk *outCh);
ndk::ScopedAStatus getPrimitiveDetails(CompositePrimitive primitive, uint32_t *outEffectIndex);
- ndk::ScopedAStatus uploadOwtEffect(uint8_t *owtData, uint32_t num_bytes,
- uint32_t *outEffectIndex);
- ndk::ScopedAStatus eraseOwtEffect(int8_t effectIndex);
ndk::ScopedAStatus performEffect(Effect effect, EffectStrength strength,
const std::shared_ptr<IVibratorCallback> &callback,
int32_t *outTimeMs);
@@ -176,12 +196,13 @@
struct pcm *mHapticPcm;
int mCard;
int mDevice;
- bool mHasHapticAlsaDevice;
+ bool mHasHapticAlsaDevice{false};
bool mIsUnderExternalControl;
float mLongEffectScale = 1.0;
bool mIsChirpEnabled;
uint32_t mSupportedPrimitivesBits = 0x0;
std::vector<CompositePrimitive> mSupportedPrimitives;
+ bool mConfigHapticAlsaDeviceDone{false};
};
} // namespace vibrator
diff --git a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-dual.rc b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-dual.rc
index cd47c3a..84fd0e0 100644
--- a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-dual.rc
+++ b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-dual.rc
@@ -1,4 +1,4 @@
-on enable-thermal-hal
+on property:vendor.all.modules.ready=1
wait /sys/bus/i2c/devices/i2c-cs40l26a-dual/calibration/redc_cal_time_ms
mkdir /mnt/vendor/persist/haptics 0770 system system
@@ -26,6 +26,7 @@
group system input
setenv INPUT_EVENT_NAME cs40l26_dual_input
+ setenv INPUT_EVENT_PATH /dev/input/event*
setenv PROPERTY_PREFIX ro.vendor.vibrator.hal.
setenv CALIBRATION_FILEPATH /mnt/vendor/persist/haptics/cs40l26_dual.cal
diff --git a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.rc b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.rc
index c2b9e63..0fcca56 100644
--- a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.rc
+++ b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.rc
@@ -1,4 +1,4 @@
-on enable-thermal-hal
+on property:vendor.all.modules.ready=1
wait /sys/bus/i2c/devices/i2c-cs40l26a/calibration/redc_cal_time_ms
mkdir /mnt/vendor/persist/haptics 0770 system system
@@ -26,6 +26,7 @@
group system input
setenv INPUT_EVENT_NAME cs40l26_input
+ setenv INPUT_EVENT_PATH /dev/input/event*
setenv PROPERTY_PREFIX ro.vendor.vibrator.hal.
setenv CALIBRATION_FILEPATH /mnt/vendor/persist/haptics/cs40l26.cal
diff --git a/vibrator/cs40l26/tests/Android.bp b/vibrator/cs40l26/tests/Android.bp
new file mode 100644
index 0000000..93c9a9f
--- /dev/null
+++ b/vibrator/cs40l26/tests/Android.bp
@@ -0,0 +1,35 @@
+//
+// Copyright (C) 2022 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.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+ name: "VibratorHalCs40l26TestSuite",
+ defaults: ["VibratorHalCs40l26TestDefaults"],
+ srcs: [
+ "test-hwcal.cpp",
+ "test-hwapi.cpp",
+ "test-vibrator.cpp",
+ ],
+ static_libs: [
+ "libc++fs",
+ "libgmock",
+ ],
+ shared_libs: [
+ "libbase",
+ ],
+}
diff --git a/vibrator/cs40l26/tests/mocks.h b/vibrator/cs40l26/tests/mocks.h
new file mode 100644
index 0000000..21466a0
--- /dev/null
+++ b/vibrator/cs40l26/tests/mocks.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+#ifndef ANDROID_HARDWARE_VIBRATOR_TEST_MOCKS_H
+#define ANDROID_HARDWARE_VIBRATOR_TEST_MOCKS_H
+
+#include <aidl/android/hardware/vibrator/BnVibratorCallback.h>
+
+#include "Vibrator.h"
+
+class MockApi : public ::aidl::android::hardware::vibrator::Vibrator::HwApi {
+ public:
+ MOCK_METHOD0(destructor, void());
+ MOCK_METHOD1(setF0, bool(std::string value));
+ MOCK_METHOD1(setF0Offset, bool(uint32_t value));
+ MOCK_METHOD1(setRedc, bool(std::string value));
+ MOCK_METHOD1(setQ, bool(std::string value));
+ MOCK_METHOD1(getEffectCount, bool(uint32_t *value));
+ MOCK_METHOD2(pollVibeState, bool(uint32_t value, int32_t timeoutMs));
+ MOCK_METHOD0(hasOwtFreeSpace, bool());
+ MOCK_METHOD1(getOwtFreeSpace, bool(uint32_t *value));
+ MOCK_METHOD1(setF0CompEnable, bool(bool value));
+ MOCK_METHOD1(setRedcCompEnable, bool(bool value));
+ MOCK_METHOD1(setMinOnOffInterval, bool(uint32_t value));
+ MOCK_METHOD2(setFFGain, bool(int fd, uint16_t value));
+ MOCK_METHOD3(setFFEffect, bool(int fd, struct ff_effect *effect, uint16_t timeoutMs));
+ MOCK_METHOD3(setFFPlay, bool(int fd, int8_t index, bool value));
+ MOCK_METHOD2(getHapticAlsaDevice, bool(int *card, int *device));
+ MOCK_METHOD4(setHapticPcmAmp, bool(struct pcm **haptic_pcm, bool enable, int card, int device));
+ MOCK_METHOD6(uploadOwtEffect,
+ bool(int fd, uint8_t *owtData, uint32_t numBytes, struct ff_effect *effect,
+ uint32_t *outEffectIndex, int *status));
+ MOCK_METHOD3(eraseOwtEffect, bool(int fd, int8_t effectIndex, std::vector<ff_effect> *effect));
+ MOCK_METHOD1(debug, void(int fd));
+
+ ~MockApi() override { destructor(); };
+};
+
+class MockCal : public ::aidl::android::hardware::vibrator::Vibrator::HwCal {
+ public:
+ MOCK_METHOD0(destructor, void());
+ MOCK_METHOD1(getVersion, bool(uint32_t *value));
+ MOCK_METHOD1(getF0, bool(std::string &value));
+ MOCK_METHOD1(getRedc, bool(std::string &value));
+ MOCK_METHOD1(getQ, bool(std::string &value));
+ MOCK_METHOD1(getLongFrequencyShift, bool(int32_t *value));
+ MOCK_METHOD1(getTickVolLevels, bool(std::array<uint32_t, 2> *value));
+ MOCK_METHOD1(getClickVolLevels, bool(std::array<uint32_t, 2> *value));
+ MOCK_METHOD1(getLongVolLevels, bool(std::array<uint32_t, 2> *value));
+ MOCK_METHOD0(isChirpEnabled, bool());
+ MOCK_METHOD1(getSupportedPrimitives, bool(uint32_t *value));
+ MOCK_METHOD0(isF0CompEnabled, bool());
+ MOCK_METHOD0(isRedcCompEnabled, bool());
+ MOCK_METHOD1(debug, void(int fd));
+
+ ~MockCal() override { destructor(); };
+ // b/132668253: Workaround gMock Compilation Issue
+ bool getF0(std::string *value) { return getF0(*value); }
+ bool getRedc(std::string *value) { return getRedc(*value); }
+ bool getQ(std::string *value) { return getQ(*value); }
+};
+
+class MockVibratorCallback : public aidl::android::hardware::vibrator::BnVibratorCallback {
+ public:
+ MOCK_METHOD(ndk::ScopedAStatus, onComplete, ());
+};
+
+#endif // ANDROID_HARDWARE_VIBRATOR_TEST_MOCKS_H
diff --git a/vibrator/cs40l26/tests/test-hwapi.cpp b/vibrator/cs40l26/tests/test-hwapi.cpp
new file mode 100644
index 0000000..cc4d465
--- /dev/null
+++ b/vibrator/cs40l26/tests/test-hwapi.cpp
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include <android-base/file.h>
+#include <cutils/fs.h>
+#include <gtest/gtest.h>
+
+#include <cstdlib>
+#include <fstream>
+
+#include "Hardware.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace vibrator {
+
+using ::testing::Test;
+using ::testing::TestParamInfo;
+using ::testing::ValuesIn;
+using ::testing::WithParamInterface;
+
+class HwApiTest : public Test {
+ private:
+ static constexpr const char *FILE_NAMES[]{
+ "calibration/f0_stored",
+ "default/f0_offset",
+ "calibration/redc_stored",
+ "calibration/q_stored",
+ "default/f0_comp_enable",
+ "default/redc_comp_enable",
+ "default/owt_free_space",
+ "default/num_waves",
+ "default/delay_before_stop_playback_us",
+ };
+
+ public:
+ void SetUp() override {
+ std::string prefix;
+ for (auto n : FILE_NAMES) {
+ auto name = std::filesystem::path(n);
+ auto path = std::filesystem::path(mFilesDir.path) / name;
+ fs_mkdirs(path.c_str(), S_IRWXU);
+ std::ofstream touch{path};
+ mFileMap[name] = path;
+ }
+ prefix = std::filesystem::path(mFilesDir.path) / "";
+ setenv("HWAPI_PATH_PREFIX", prefix.c_str(), true);
+ mHwApi = std::make_unique<HwApi>();
+
+ for (auto n : FILE_NAMES) {
+ auto name = std::filesystem::path(n);
+ auto path = std::filesystem::path(mEmptyDir.path) / name;
+ }
+ prefix = std::filesystem::path(mEmptyDir.path) / "";
+ setenv("HWAPI_PATH_PREFIX", prefix.c_str(), true);
+ mNoApi = std::make_unique<HwApi>();
+ }
+
+ void TearDown() override { verifyContents(); }
+
+ static auto ParamNameFixup(std::string str) {
+ std::replace(str.begin(), str.end(), '/', '_');
+ return str;
+ }
+
+ protected:
+ // Set expected file content for a test.
+ template <typename T>
+ void expectContent(const std::string &name, const T &value) {
+ mExpectedContent[name] << value << std::endl;
+ }
+
+ // Set actual file content for an input test.
+ template <typename T>
+ void updateContent(const std::string &name, const T &value) {
+ std::ofstream(mFileMap[name]) << value << std::endl;
+ }
+
+ template <typename T>
+ void expectAndUpdateContent(const std::string &name, const T &value) {
+ expectContent(name, value);
+ updateContent(name, value);
+ }
+
+ // Compare all file contents against expected contents.
+ void verifyContents() {
+ for (auto &a : mFileMap) {
+ std::ifstream file{a.second};
+ std::string expect = mExpectedContent[a.first].str();
+ std::string actual = std::string(std::istreambuf_iterator<char>(file),
+ std::istreambuf_iterator<char>());
+ EXPECT_EQ(expect, actual) << a.first;
+ }
+ }
+
+ protected:
+ std::unique_ptr<Vibrator::HwApi> mHwApi;
+ std::unique_ptr<Vibrator::HwApi> mNoApi;
+ std::map<std::string, std::string> mFileMap;
+ TemporaryDir mFilesDir;
+ TemporaryDir mEmptyDir;
+ std::map<std::string, std::stringstream> mExpectedContent;
+};
+
+template <typename T>
+class HwApiTypedTest : public HwApiTest,
+ public WithParamInterface<std::tuple<std::string, std::function<T>>> {
+ public:
+ static auto PrintParam(const TestParamInfo<typename HwApiTypedTest::ParamType> &info) {
+ return ParamNameFixup(std::get<0>(info.param));
+ }
+ static auto MakeParam(std::string name, std::function<T> func) {
+ return std::make_tuple(name, func);
+ }
+};
+
+using HasTest = HwApiTypedTest<bool(Vibrator::HwApi &)>;
+
+TEST_P(HasTest, success_returnsTrue) {
+ auto param = GetParam();
+ auto func = std::get<1>(param);
+
+ EXPECT_TRUE(func(*mHwApi));
+}
+
+TEST_P(HasTest, success_returnsFalse) {
+ auto param = GetParam();
+ auto func = std::get<1>(param);
+
+ EXPECT_FALSE(func(*mNoApi));
+}
+
+INSTANTIATE_TEST_CASE_P(HwApiTests, HasTest,
+ ValuesIn({
+ HasTest::MakeParam("default/owt_free_space",
+ &Vibrator::HwApi::hasOwtFreeSpace),
+ }),
+ HasTest::PrintParam);
+
+using GetUint32Test = HwApiTypedTest<bool(Vibrator::HwApi &, uint32_t *)>;
+
+TEST_P(GetUint32Test, success) {
+ auto param = GetParam();
+ auto name = std::get<0>(param);
+ auto func = std::get<1>(param);
+ uint32_t expect = std::rand();
+ uint32_t actual = ~expect;
+
+ expectAndUpdateContent(name, expect);
+
+ EXPECT_TRUE(func(*mHwApi, &actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_P(GetUint32Test, failure) {
+ auto param = GetParam();
+ auto func = std::get<1>(param);
+ uint32_t value;
+
+ EXPECT_FALSE(func(*mNoApi, &value));
+}
+
+INSTANTIATE_TEST_CASE_P(HwApiTests, GetUint32Test,
+ ValuesIn({
+ GetUint32Test::MakeParam("default/num_waves",
+ &Vibrator::HwApi::getEffectCount),
+ GetUint32Test::MakeParam("default/owt_free_space",
+ &Vibrator::HwApi::getOwtFreeSpace),
+ }),
+ GetUint32Test::PrintParam);
+
+using SetBoolTest = HwApiTypedTest<bool(Vibrator::HwApi &, bool)>;
+
+TEST_P(SetBoolTest, success_returnsTrue) {
+ auto param = GetParam();
+ auto name = std::get<0>(param);
+ auto func = std::get<1>(param);
+
+ expectContent(name, "1");
+
+ EXPECT_TRUE(func(*mHwApi, true));
+}
+
+TEST_P(SetBoolTest, success_returnsFalse) {
+ auto param = GetParam();
+ auto name = std::get<0>(param);
+ auto func = std::get<1>(param);
+
+ expectContent(name, "0");
+
+ EXPECT_TRUE(func(*mHwApi, false));
+}
+
+TEST_P(SetBoolTest, failure) {
+ auto param = GetParam();
+ auto func = std::get<1>(param);
+
+ EXPECT_FALSE(func(*mNoApi, true));
+ EXPECT_FALSE(func(*mNoApi, false));
+}
+
+INSTANTIATE_TEST_CASE_P(HwApiTests, SetBoolTest,
+ ValuesIn({
+ SetBoolTest::MakeParam("default/f0_comp_enable",
+ &Vibrator::HwApi::setF0CompEnable),
+ SetBoolTest::MakeParam("default/redc_comp_enable",
+ &Vibrator::HwApi::setRedcCompEnable),
+ }),
+ SetBoolTest::PrintParam);
+
+using SetUint32Test = HwApiTypedTest<bool(Vibrator::HwApi &, uint32_t)>;
+
+TEST_P(SetUint32Test, success) {
+ auto param = GetParam();
+ auto name = std::get<0>(param);
+ auto func = std::get<1>(param);
+ uint32_t value = std::rand();
+
+ expectContent(name, value);
+
+ EXPECT_TRUE(func(*mHwApi, value));
+}
+
+TEST_P(SetUint32Test, failure) {
+ auto param = GetParam();
+ auto func = std::get<1>(param);
+ uint32_t value = std::rand();
+
+ EXPECT_FALSE(func(*mNoApi, value));
+}
+
+INSTANTIATE_TEST_CASE_P(HwApiTests, SetUint32Test,
+ ValuesIn({
+ SetUint32Test::MakeParam("default/f0_offset",
+ &Vibrator::HwApi::setF0Offset),
+ SetUint32Test::MakeParam("default/delay_before_stop_playback_us",
+ &Vibrator::HwApi::setMinOnOffInterval),
+ }),
+ SetUint32Test::PrintParam);
+
+using SetStringTest = HwApiTypedTest<bool(Vibrator::HwApi &, std::string)>;
+
+TEST_P(SetStringTest, success) {
+ auto param = GetParam();
+ auto name = std::get<0>(param);
+ auto func = std::get<1>(param);
+ std::string value = TemporaryFile().path;
+
+ expectContent(name, value);
+
+ EXPECT_TRUE(func(*mHwApi, value));
+}
+
+TEST_P(SetStringTest, failure) {
+ auto param = GetParam();
+ auto func = std::get<1>(param);
+ std::string value = TemporaryFile().path;
+
+ EXPECT_FALSE(func(*mNoApi, value));
+}
+
+INSTANTIATE_TEST_CASE_P(
+ HwApiTests, SetStringTest,
+ ValuesIn({
+ SetStringTest::MakeParam("calibration/f0_stored", &Vibrator::HwApi::setF0),
+ SetStringTest::MakeParam("calibration/redc_stored", &Vibrator::HwApi::setRedc),
+ SetStringTest::MakeParam("calibration/q_stored", &Vibrator::HwApi::setQ),
+ }),
+ SetStringTest::PrintParam);
+
+} // namespace vibrator
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/vibrator/cs40l26/tests/test-hwcal.cpp b/vibrator/cs40l26/tests/test-hwcal.cpp
new file mode 100644
index 0000000..e482b6c
--- /dev/null
+++ b/vibrator/cs40l26/tests/test-hwcal.cpp
@@ -0,0 +1,386 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include <android-base/file.h>
+#include <gtest/gtest.h>
+
+#include <fstream>
+
+#include "Hardware.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace vibrator {
+
+using ::testing::Test;
+
+class HwCalTest : public Test {
+ protected:
+ static constexpr std::array<uint32_t, 2> V_TICK_DEFAULT = {1, 100};
+ static constexpr std::array<uint32_t, 2> V_CLICK_DEFAULT = {1, 100};
+ static constexpr std::array<uint32_t, 2> V_LONG_DEFAULT = {1, 100};
+
+ public:
+ void SetUp() override { setenv("CALIBRATION_FILEPATH", mCalFile.path, true); }
+
+ private:
+ template <typename T>
+ static void pack(std::ostream &stream, const T &value, std::string lpad, std::string rpad) {
+ stream << lpad << value << rpad;
+ }
+
+ template <typename T, typename std::array<T, 0>::size_type N>
+ static void pack(std::ostream &stream, const std::array<T, N> &value, std::string lpad,
+ std::string rpad) {
+ for (auto &entry : value) {
+ pack(stream, entry, lpad, rpad);
+ }
+ }
+
+ protected:
+ void createHwCal() { mHwCal = std::make_unique<HwCal>(); }
+
+ template <typename T>
+ void write(const std::string key, const T &value, std::string lpad = " ",
+ std::string rpad = "") {
+ std::ofstream calfile{mCalFile.path, std::ios_base::app};
+ calfile << key << ":";
+ pack(calfile, value, lpad, rpad);
+ calfile << std::endl;
+ }
+
+ void unlink() { ::unlink(mCalFile.path); }
+
+ protected:
+ std::unique_ptr<Vibrator::HwCal> mHwCal;
+ TemporaryFile mCalFile;
+};
+
+TEST_F(HwCalTest, f0_measured) {
+ uint32_t randInput = std::rand();
+ std::string expect = std::to_string(randInput);
+ std::string actual = std::to_string(~randInput);
+
+ write("f0_measured", expect);
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getF0(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, f0_missing) {
+ std::string actual;
+
+ createHwCal();
+
+ EXPECT_FALSE(mHwCal->getF0(&actual));
+}
+
+TEST_F(HwCalTest, redc_measured) {
+ uint32_t randInput = std::rand();
+ std::string expect = std::to_string(randInput);
+ std::string actual = std::to_string(~randInput);
+
+ write("redc_measured", expect);
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getRedc(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, redc_missing) {
+ std::string actual;
+
+ createHwCal();
+
+ EXPECT_FALSE(mHwCal->getRedc(&actual));
+}
+
+TEST_F(HwCalTest, q_measured) {
+ uint32_t randInput = std::rand();
+ std::string expect = std::to_string(randInput);
+ std::string actual = std::to_string(~randInput);
+
+ write("q_measured", expect);
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getQ(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, q_missing) {
+ std::string actual;
+
+ createHwCal();
+
+ EXPECT_FALSE(mHwCal->getQ(&actual));
+}
+
+TEST_F(HwCalTest, v_levels) {
+ std::array<uint32_t, 2> expect;
+ std::array<uint32_t, 2> actual;
+
+ // voltage for tick effects
+ std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) {
+ e = std::rand();
+ return ~e;
+ });
+
+ write("v_tick", expect);
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getTickVolLevels(&actual));
+ EXPECT_EQ(expect, actual);
+
+ // voltage for click effects
+ std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) {
+ e = std::rand();
+ return ~e;
+ });
+
+ write("v_click", expect);
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getClickVolLevels(&actual));
+ EXPECT_EQ(expect, actual);
+
+ // voltage for long effects
+ std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) {
+ e = std::rand();
+ return ~e;
+ });
+
+ write("v_long", expect);
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getLongVolLevels(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, v_missing) {
+ std::array<uint32_t, 2> expect = V_TICK_DEFAULT;
+ std::array<uint32_t, 2> actual;
+
+ std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { return ~e; });
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getTickVolLevels(&actual));
+ EXPECT_EQ(expect, actual);
+
+ expect = V_CLICK_DEFAULT;
+
+ std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { return ~e; });
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getClickVolLevels(&actual));
+ EXPECT_EQ(expect, actual);
+
+ expect = V_LONG_DEFAULT;
+
+ std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { return ~e; });
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getLongVolLevels(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, v_short) {
+ std::array<uint32_t, 2> expect = V_TICK_DEFAULT;
+ std::array<uint32_t, 2> actual;
+
+ std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { return ~e; });
+
+ write("v_tick", std::array<uint32_t, expect.size() - 1>());
+ write("v_click", std::array<uint32_t, expect.size() - 1>());
+ write("v_long", std::array<uint32_t, expect.size() - 1>());
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getTickVolLevels(&actual));
+ EXPECT_EQ(expect, actual);
+
+ expect = V_CLICK_DEFAULT;
+ EXPECT_TRUE(mHwCal->getClickVolLevels(&actual));
+ EXPECT_EQ(expect, actual);
+
+ expect = V_LONG_DEFAULT;
+ EXPECT_TRUE(mHwCal->getLongVolLevels(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, v_long) {
+ std::array<uint32_t, 2> expect = V_TICK_DEFAULT;
+ std::array<uint32_t, 2> actual;
+
+ std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { return ~e; });
+
+ write("v_tick", std::array<uint32_t, expect.size() + 1>());
+ write("v_click", std::array<uint32_t, expect.size() + 1>());
+ write("v_long", std::array<uint32_t, expect.size() + 1>());
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getTickVolLevels(&actual));
+ EXPECT_EQ(expect, actual);
+
+ expect = V_CLICK_DEFAULT;
+ EXPECT_TRUE(mHwCal->getClickVolLevels(&actual));
+ EXPECT_EQ(expect, actual);
+
+ expect = V_LONG_DEFAULT;
+ EXPECT_TRUE(mHwCal->getLongVolLevels(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, v_nofile) {
+ std::array<uint32_t, 2> expect = V_TICK_DEFAULT;
+ std::array<uint32_t, 2> actual;
+
+ std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { return ~e; });
+
+ write("v_tick", actual);
+ write("v_click", actual);
+ write("v_long", actual);
+ unlink();
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getTickVolLevels(&actual));
+ EXPECT_EQ(expect, actual);
+
+ expect = V_CLICK_DEFAULT;
+ EXPECT_TRUE(mHwCal->getClickVolLevels(&actual));
+ EXPECT_EQ(expect, actual);
+
+ expect = V_LONG_DEFAULT;
+ EXPECT_TRUE(mHwCal->getLongVolLevels(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, multiple) {
+ uint32_t randInput = std::rand();
+ std::string f0Expect = std::to_string(randInput);
+ std::string f0Actual = std::to_string(~randInput);
+ randInput = std::rand();
+ std::string redcExpect = std::to_string(randInput);
+ std::string redcActual = std::to_string(~randInput);
+ randInput = std::rand();
+ std::string qExpect = std::to_string(randInput);
+ std::string qActual = std::to_string(~randInput);
+ std::array<uint32_t, 2> volTickExpect, volClickExpect, volLongExpect;
+ std::array<uint32_t, 2> volActual;
+
+ std::transform(volTickExpect.begin(), volTickExpect.end(), volActual.begin(), [](uint32_t &e) {
+ e = std::rand();
+ return ~e;
+ });
+
+ write("f0_measured", f0Expect);
+ write("redc_measured", redcExpect);
+ write("q_measured", qExpect);
+ write("v_tick", volTickExpect);
+ std::transform(volClickExpect.begin(), volClickExpect.end(), volActual.begin(),
+ [](uint32_t &e) {
+ e = std::rand();
+ return ~e;
+ });
+ write("v_click", volClickExpect);
+ std::transform(volLongExpect.begin(), volLongExpect.end(), volActual.begin(), [](uint32_t &e) {
+ e = std::rand();
+ return ~e;
+ });
+ write("v_long", volLongExpect);
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getF0(&f0Actual));
+ EXPECT_EQ(f0Expect, f0Actual);
+ EXPECT_TRUE(mHwCal->getRedc(&redcActual));
+ EXPECT_EQ(redcExpect, redcActual);
+ EXPECT_TRUE(mHwCal->getQ(&qActual));
+ EXPECT_EQ(qExpect, qActual);
+ EXPECT_TRUE(mHwCal->getTickVolLevels(&volActual));
+ EXPECT_EQ(volTickExpect, volActual);
+ EXPECT_TRUE(mHwCal->getClickVolLevels(&volActual));
+ EXPECT_EQ(volClickExpect, volActual);
+ EXPECT_TRUE(mHwCal->getLongVolLevels(&volActual));
+ EXPECT_EQ(volLongExpect, volActual);
+}
+
+TEST_F(HwCalTest, trimming) {
+ uint32_t randInput = std::rand();
+ std::string f0Expect = std::to_string(randInput);
+ std::string f0Actual = std::to_string(~randInput);
+ randInput = std::rand();
+ std::string redcExpect = std::to_string(randInput);
+ std::string redcActual = std::to_string(randInput);
+ randInput = std::rand();
+ std::string qExpect = std::to_string(randInput);
+ std::string qActual = std::to_string(randInput);
+ std::array<uint32_t, 2> volTickExpect, volClickExpect, volLongExpect;
+ std::array<uint32_t, 2> volActual;
+
+ std::transform(volTickExpect.begin(), volTickExpect.end(), volActual.begin(), [](uint32_t &e) {
+ e = std::rand();
+ return ~e;
+ });
+
+ write("f0_measured", f0Expect, " \t", "\t ");
+ write("redc_measured", redcExpect, " \t", "\t ");
+ write("q_measured", qExpect, " \t", "\t ");
+ write("v_tick", volTickExpect, " \t", "\t ");
+ std::transform(volClickExpect.begin(), volClickExpect.end(), volActual.begin(),
+ [](uint32_t &e) {
+ e = std::rand();
+ return ~e;
+ });
+ write("v_click", volClickExpect, " \t", "\t ");
+ std::transform(volLongExpect.begin(), volLongExpect.end(), volActual.begin(), [](uint32_t &e) {
+ e = std::rand();
+ return ~e;
+ });
+ write("v_long", volLongExpect, " \t", "\t ");
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getF0(&f0Actual));
+ EXPECT_EQ(f0Expect, f0Actual);
+ EXPECT_TRUE(mHwCal->getRedc(&redcActual));
+ EXPECT_EQ(redcExpect, redcActual);
+ EXPECT_TRUE(mHwCal->getQ(&qActual));
+ EXPECT_EQ(qExpect, qActual);
+ EXPECT_TRUE(mHwCal->getTickVolLevels(&volActual));
+ EXPECT_EQ(volTickExpect, volActual);
+ EXPECT_TRUE(mHwCal->getClickVolLevels(&volActual));
+ EXPECT_EQ(volClickExpect, volActual);
+ EXPECT_TRUE(mHwCal->getLongVolLevels(&volActual));
+ EXPECT_EQ(volLongExpect, volActual);
+}
+
+} // namespace vibrator
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/vibrator/cs40l26/tests/test-vibrator.cpp b/vibrator/cs40l26/tests/test-vibrator.cpp
new file mode 100644
index 0000000..a8bedd5
--- /dev/null
+++ b/vibrator/cs40l26/tests/test-vibrator.cpp
@@ -0,0 +1,682 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include <aidl/android/hardware/vibrator/BnVibratorCallback.h>
+#include <android-base/logging.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <linux/input.h>
+#include <linux/uinput.h>
+
+#include <future>
+
+#include "Vibrator.h"
+#include "mocks.h"
+#include "types.h"
+#include "utils.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace vibrator {
+
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::Assign;
+using ::testing::AtLeast;
+using ::testing::AtMost;
+using ::testing::Combine;
+using ::testing::DoAll;
+using ::testing::DoDefault;
+using ::testing::Exactly;
+using ::testing::Expectation;
+using ::testing::ExpectationSet;
+using ::testing::Ge;
+using ::testing::Mock;
+using ::testing::MockFunction;
+using ::testing::Range;
+using ::testing::Return;
+using ::testing::Sequence;
+using ::testing::SetArgPointee;
+using ::testing::SetArgReferee;
+using ::testing::Test;
+using ::testing::TestParamInfo;
+using ::testing::ValuesIn;
+using ::testing::WithParamInterface;
+
+// Forward Declarations
+
+static EffectQueue Queue(const QueueEffect &effect);
+static EffectQueue Queue(const QueueDelay &delay);
+template <typename T, typename U, typename... Args>
+static EffectQueue Queue(const T &first, const U &second, Args... rest);
+
+static EffectLevel Level(float intensity, float levelLow, float levelHigh);
+static EffectScale Scale(float intensity, float levelLow, float levelHigh);
+
+// Constants With Arbitrary Values
+
+static constexpr uint32_t CAL_VERSION = 2;
+static constexpr std::array<EffectLevel, 2> V_TICK_DEFAULT = {1, 100};
+static constexpr std::array<EffectLevel, 2> V_CLICK_DEFAULT{1, 100};
+static constexpr std::array<EffectLevel, 2> V_LONG_DEFAULT{1, 100};
+static constexpr std::array<EffectDuration, 14> EFFECT_DURATIONS{
+ 0, 100, 30, 1000, 300, 130, 150, 500, 100, 15, 20, 1000, 1000, 1000};
+
+// Constants With Prescribed Values
+
+static const std::map<Effect, EffectIndex> EFFECT_INDEX{
+ {Effect::CLICK, 2},
+ {Effect::TICK, 2},
+ {Effect::HEAVY_CLICK, 2},
+ {Effect::TEXTURE_TICK, 9},
+};
+static constexpr uint32_t MIN_ON_OFF_INTERVAL_US = 8500;
+static constexpr uint8_t VOLTAGE_SCALE_MAX = 100;
+static constexpr int8_t MAX_COLD_START_LATENCY_MS = 6; // I2C Transaction + DSP Return-From-Standby
+static constexpr auto POLLING_TIMEOUT = 20;
+enum WaveformIndex : uint16_t {
+ /* Physical waveform */
+ WAVEFORM_LONG_VIBRATION_EFFECT_INDEX = 0,
+ WAVEFORM_RESERVED_INDEX_1 = 1,
+ WAVEFORM_CLICK_INDEX = 2,
+ WAVEFORM_SHORT_VIBRATION_EFFECT_INDEX = 3,
+ WAVEFORM_THUD_INDEX = 4,
+ WAVEFORM_SPIN_INDEX = 5,
+ WAVEFORM_QUICK_RISE_INDEX = 6,
+ WAVEFORM_SLOW_RISE_INDEX = 7,
+ WAVEFORM_QUICK_FALL_INDEX = 8,
+ WAVEFORM_LIGHT_TICK_INDEX = 9,
+ WAVEFORM_LOW_TICK_INDEX = 10,
+ WAVEFORM_RESERVED_MFG_1,
+ WAVEFORM_RESERVED_MFG_2,
+ WAVEFORM_RESERVED_MFG_3,
+ WAVEFORM_MAX_PHYSICAL_INDEX,
+ /* OWT waveform */
+ WAVEFORM_COMPOSE = WAVEFORM_MAX_PHYSICAL_INDEX,
+ WAVEFORM_PWLE,
+ /*
+ * Refer to <linux/input.h>, the WAVEFORM_MAX_INDEX must not exceed 96.
+ * #define FF_GAIN 0x60 // 96 in decimal
+ * #define FF_MAX_EFFECTS FF_GAIN
+ */
+ WAVEFORM_MAX_INDEX,
+};
+
+static const EffectScale ON_GLOBAL_SCALE{levelToScale(V_LONG_DEFAULT[1])};
+static const EffectIndex ON_EFFECT_INDEX{0};
+
+static const std::map<EffectTuple, EffectScale> EFFECT_SCALE{
+ {{Effect::TICK, EffectStrength::LIGHT},
+ Scale(0.5f * 0.5f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])},
+ {{Effect::TICK, EffectStrength::MEDIUM},
+ Scale(0.5f * 0.7f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])},
+ {{Effect::TICK, EffectStrength::STRONG},
+ Scale(0.5f * 1.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])},
+ {{Effect::CLICK, EffectStrength::LIGHT},
+ Scale(0.7f * 0.5f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])},
+ {{Effect::CLICK, EffectStrength::MEDIUM},
+ Scale(0.7f * 0.7f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])},
+ {{Effect::CLICK, EffectStrength::STRONG},
+ Scale(0.7f * 1.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])},
+ {{Effect::HEAVY_CLICK, EffectStrength::LIGHT},
+ Scale(1.0f * 0.5f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])},
+ {{Effect::HEAVY_CLICK, EffectStrength::MEDIUM},
+ Scale(1.0f * 0.7f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])},
+ {{Effect::HEAVY_CLICK, EffectStrength::STRONG},
+ Scale(1.0f * 1.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])},
+ {{Effect::TEXTURE_TICK, EffectStrength::LIGHT},
+ Scale(0.5f * 0.5f, V_TICK_DEFAULT[0], V_TICK_DEFAULT[1])},
+ {{Effect::TEXTURE_TICK, EffectStrength::MEDIUM},
+ Scale(0.5f * 0.7f, V_TICK_DEFAULT[0], V_TICK_DEFAULT[1])},
+ {{Effect::TEXTURE_TICK, EffectStrength::STRONG},
+ Scale(0.5f * 1.0f, V_TICK_DEFAULT[0], V_TICK_DEFAULT[1])},
+};
+
+static const std::map<EffectTuple, EffectQueue> EFFECT_QUEUE{
+ {{Effect::DOUBLE_CLICK, EffectStrength::LIGHT},
+ Queue(QueueEffect{EFFECT_INDEX.at(Effect::CLICK),
+ Level(0.7f * 0.5f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])},
+ 100,
+ QueueEffect{EFFECT_INDEX.at(Effect::CLICK),
+ Level(1.0f * 0.5f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])})},
+ {{Effect::DOUBLE_CLICK, EffectStrength::MEDIUM},
+ Queue(QueueEffect{EFFECT_INDEX.at(Effect::CLICK),
+ Level(0.7f * 0.7f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])},
+ 100,
+ QueueEffect{EFFECT_INDEX.at(Effect::CLICK),
+ Level(1.0f * 0.7f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])})},
+ {{Effect::DOUBLE_CLICK, EffectStrength::STRONG},
+ Queue(QueueEffect{EFFECT_INDEX.at(Effect::CLICK),
+ Level(0.7f * 1.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])},
+ 100,
+ QueueEffect{EFFECT_INDEX.at(Effect::CLICK),
+ Level(1.0f * 1.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])})},
+};
+
+EffectQueue Queue(const QueueEffect &effect) {
+ auto index = std::get<0>(effect);
+ auto level = std::get<1>(effect);
+ auto string = std::to_string(index) + "." + std::to_string(level);
+ auto duration = EFFECT_DURATIONS[index];
+ return {string, duration};
+}
+
+EffectQueue Queue(const QueueDelay &delay) {
+ auto string = std::to_string(delay);
+ return {string, delay};
+}
+
+template <typename T, typename U, typename... Args>
+EffectQueue Queue(const T &first, const U &second, Args... rest) {
+ auto head = Queue(first);
+ auto tail = Queue(second, rest...);
+ auto string = std::get<0>(head) + "," + std::get<0>(tail);
+ auto duration = std::get<1>(head) + std::get<1>(tail);
+ return {string, duration};
+}
+
+static EffectLevel Level(float intensity, float levelLow, float levelHigh) {
+ return std::lround(intensity * (levelHigh - levelLow)) + levelLow;
+}
+
+static EffectScale Scale(float intensity, float levelLow, float levelHigh) {
+ return levelToScale(Level(intensity, levelLow, levelHigh));
+}
+
+class VibratorTest : public Test {
+ public:
+ void SetUp() override {
+ setenv("INPUT_EVENT_NAME", "CS40L26TestSuite", true);
+ std::unique_ptr<MockApi> mockapi;
+ std::unique_ptr<MockCal> mockcal;
+
+ createMock(&mockapi, &mockcal);
+ createVibrator(std::move(mockapi), std::move(mockcal));
+ }
+
+ void TearDown() override { deleteVibrator(); }
+
+ protected:
+ void createMock(std::unique_ptr<MockApi> *mockapi, std::unique_ptr<MockCal> *mockcal) {
+ *mockapi = std::make_unique<MockApi>();
+ *mockcal = std::make_unique<MockCal>();
+
+ mMockApi = mockapi->get();
+ mMockCal = mockcal->get();
+
+ ON_CALL(*mMockApi, destructor()).WillByDefault(Assign(&mMockApi, nullptr));
+
+ ON_CALL(*mMockApi, setFFGain(_, _)).WillByDefault(Return(true));
+ ON_CALL(*mMockApi, setFFEffect(_, _, _)).WillByDefault(Return(true));
+ ON_CALL(*mMockApi, setFFPlay(_, _, _)).WillByDefault(Return(true));
+ ON_CALL(*mMockApi, pollVibeState(_, _)).WillByDefault(Return(true));
+ ON_CALL(*mMockApi, uploadOwtEffect(_, _, _, _, _, _)).WillByDefault(Return(true));
+ ON_CALL(*mMockApi, eraseOwtEffect(_, _, _)).WillByDefault(Return(true));
+
+ ON_CALL(*mMockApi, getOwtFreeSpace(_))
+ .WillByDefault(DoAll(SetArgPointee<0>(11504), Return(true)));
+
+ ON_CALL(*mMockCal, destructor()).WillByDefault(Assign(&mMockCal, nullptr));
+
+ ON_CALL(*mMockCal, getVersion(_))
+ .WillByDefault(DoAll(SetArgPointee<0>(CAL_VERSION), Return(true)));
+
+ ON_CALL(*mMockCal, getTickVolLevels(_))
+ .WillByDefault(DoAll(SetArgPointee<0>(V_TICK_DEFAULT), Return(true)));
+ ON_CALL(*mMockCal, getClickVolLevels(_))
+ .WillByDefault(DoAll(SetArgPointee<0>(V_CLICK_DEFAULT), Return(true)));
+ ON_CALL(*mMockCal, getLongVolLevels(_))
+ .WillByDefault(DoAll(SetArgPointee<0>(V_LONG_DEFAULT), Return(true)));
+
+ relaxMock(false);
+ }
+
+ void createVibrator(std::unique_ptr<MockApi> mockapi, std::unique_ptr<MockCal> mockcal,
+ bool relaxed = true) {
+ if (relaxed) {
+ relaxMock(true);
+ }
+ mVibrator = ndk::SharedRefBase::make<Vibrator>(std::move(mockapi), std::move(mockcal));
+ if (relaxed) {
+ relaxMock(false);
+ }
+ }
+
+ void deleteVibrator(bool relaxed = true) {
+ if (relaxed) {
+ relaxMock(true);
+ }
+ mVibrator.reset();
+ }
+
+ private:
+ void relaxMock(bool relax) {
+ auto times = relax ? AnyNumber() : Exactly(0);
+
+ Mock::VerifyAndClearExpectations(mMockApi);
+ Mock::VerifyAndClearExpectations(mMockCal);
+
+ EXPECT_CALL(*mMockApi, destructor()).Times(times);
+ EXPECT_CALL(*mMockApi, setF0(_)).Times(times);
+ EXPECT_CALL(*mMockApi, setF0Offset(_)).Times(times);
+ EXPECT_CALL(*mMockApi, setRedc(_)).Times(times);
+ EXPECT_CALL(*mMockApi, setQ(_)).Times(times);
+ EXPECT_CALL(*mMockApi, hasOwtFreeSpace()).Times(times);
+ EXPECT_CALL(*mMockApi, getOwtFreeSpace(_)).Times(times);
+ EXPECT_CALL(*mMockApi, setF0CompEnable(_)).Times(times);
+ EXPECT_CALL(*mMockApi, setRedcCompEnable(_)).Times(times);
+ EXPECT_CALL(*mMockApi, pollVibeState(_, _)).Times(times);
+ EXPECT_CALL(*mMockApi, setFFGain(_, _)).Times(times);
+ EXPECT_CALL(*mMockApi, setFFEffect(_, _, _)).Times(times);
+ EXPECT_CALL(*mMockApi, setFFPlay(_, _, _)).Times(times);
+ EXPECT_CALL(*mMockApi, setMinOnOffInterval(_)).Times(times);
+ EXPECT_CALL(*mMockApi, getHapticAlsaDevice(_, _)).Times(times);
+ EXPECT_CALL(*mMockApi, setHapticPcmAmp(_, _, _, _)).Times(times);
+
+ EXPECT_CALL(*mMockApi, debug(_)).Times(times);
+
+ EXPECT_CALL(*mMockCal, destructor()).Times(times);
+ EXPECT_CALL(*mMockCal, getF0(_)).Times(times);
+ EXPECT_CALL(*mMockCal, getRedc(_)).Times(times);
+ EXPECT_CALL(*mMockCal, getQ(_)).Times(times);
+ EXPECT_CALL(*mMockCal, getTickVolLevels(_)).Times(times);
+ EXPECT_CALL(*mMockCal, getClickVolLevels(_)).Times(times);
+ EXPECT_CALL(*mMockCal, getLongVolLevels(_)).Times(times);
+ EXPECT_CALL(*mMockCal, isChirpEnabled()).Times(times);
+ EXPECT_CALL(*mMockCal, getLongFrequencyShift(_)).Times(times);
+ EXPECT_CALL(*mMockCal, isF0CompEnabled()).Times(times);
+ EXPECT_CALL(*mMockCal, isRedcCompEnabled()).Times(times);
+ EXPECT_CALL(*mMockCal, debug(_)).Times(times);
+ }
+
+ protected:
+ MockApi *mMockApi;
+ MockCal *mMockCal;
+ std::shared_ptr<IVibrator> mVibrator;
+ uint32_t mEffectIndex;
+};
+
+TEST_F(VibratorTest, Constructor) {
+ std::unique_ptr<MockApi> mockapi;
+ std::unique_ptr<MockCal> mockcal;
+ std::string f0Val = std::to_string(std::rand());
+ std::string redcVal = std::to_string(std::rand());
+ std::string qVal = std::to_string(std::rand());
+ uint32_t calVer;
+ uint32_t supportedPrimitivesBits = 0x0;
+ Expectation volGet;
+ Sequence f0Seq, redcSeq, qSeq, supportedPrimitivesSeq;
+
+ EXPECT_CALL(*mMockApi, destructor()).WillOnce(DoDefault());
+ EXPECT_CALL(*mMockCal, destructor()).WillOnce(DoDefault());
+
+ deleteVibrator(false);
+
+ createMock(&mockapi, &mockcal);
+
+ EXPECT_CALL(*mMockCal, getF0(_))
+ .InSequence(f0Seq)
+ .WillOnce(DoAll(SetArgReferee<0>(f0Val), Return(true)));
+ EXPECT_CALL(*mMockApi, setF0(f0Val)).InSequence(f0Seq).WillOnce(Return(true));
+
+ EXPECT_CALL(*mMockCal, getRedc(_))
+ .InSequence(redcSeq)
+ .WillOnce(DoAll(SetArgReferee<0>(redcVal), Return(true)));
+ EXPECT_CALL(*mMockApi, setRedc(redcVal)).InSequence(redcSeq).WillOnce(Return(true));
+
+ EXPECT_CALL(*mMockCal, getQ(_))
+ .InSequence(qSeq)
+ .WillOnce(DoAll(SetArgReferee<0>(qVal), Return(true)));
+ EXPECT_CALL(*mMockApi, setQ(qVal)).InSequence(qSeq).WillOnce(Return(true));
+
+ EXPECT_CALL(*mMockCal, getLongFrequencyShift(_)).WillOnce(Return(true));
+
+ mMockCal->getVersion(&calVer);
+ if (calVer == 2) {
+ volGet = EXPECT_CALL(*mMockCal, getTickVolLevels(_)).WillOnce(DoDefault());
+ volGet = EXPECT_CALL(*mMockCal, getClickVolLevels(_)).WillOnce(DoDefault());
+ volGet = EXPECT_CALL(*mMockCal, getLongVolLevels(_)).WillOnce(DoDefault());
+ }
+
+ EXPECT_CALL(*mMockCal, isF0CompEnabled()).WillOnce(Return(true));
+ EXPECT_CALL(*mMockApi, setF0CompEnable(true)).WillOnce(Return(true));
+ EXPECT_CALL(*mMockCal, isRedcCompEnabled()).WillOnce(Return(true));
+ EXPECT_CALL(*mMockApi, setRedcCompEnable(true)).WillOnce(Return(true));
+
+ EXPECT_CALL(*mMockCal, isChirpEnabled()).WillOnce(Return(true));
+ EXPECT_CALL(*mMockCal, getSupportedPrimitives(_))
+ .InSequence(supportedPrimitivesSeq)
+ .WillOnce(DoAll(SetArgPointee<0>(supportedPrimitivesBits), Return(true)));
+
+ EXPECT_CALL(*mMockApi, setMinOnOffInterval(MIN_ON_OFF_INTERVAL_US)).WillOnce(Return(true));
+ createVibrator(std::move(mockapi), std::move(mockcal), false);
+}
+
+TEST_F(VibratorTest, on) {
+ Sequence s1, s2;
+ uint16_t duration = std::rand() + 1;
+
+ EXPECT_CALL(*mMockApi, setFFGain(_, ON_GLOBAL_SCALE)).InSequence(s1).WillOnce(DoDefault());
+ EXPECT_CALL(*mMockApi, setFFEffect(_, _, duration + MAX_COLD_START_LATENCY_MS))
+ .InSequence(s2)
+ .WillOnce(DoDefault());
+ EXPECT_CALL(*mMockApi, setFFPlay(_, ON_EFFECT_INDEX, true))
+ .InSequence(s1, s2)
+ .WillOnce(DoDefault());
+ EXPECT_TRUE(mVibrator->on(duration, nullptr).isOk());
+}
+
+TEST_F(VibratorTest, off) {
+ Sequence s1;
+ EXPECT_CALL(*mMockApi, setFFGain(_, ON_GLOBAL_SCALE)).InSequence(s1).WillOnce(DoDefault());
+ EXPECT_TRUE(mVibrator->off().isOk());
+}
+
+TEST_F(VibratorTest, supportsAmplitudeControl_supported) {
+ int32_t capabilities;
+ EXPECT_CALL(*mMockApi, hasOwtFreeSpace()).WillOnce(Return(true));
+ EXPECT_CALL(*mMockApi, getHapticAlsaDevice(_, _)).WillOnce(Return(true));
+
+ EXPECT_TRUE(mVibrator->getCapabilities(&capabilities).isOk());
+ EXPECT_GT(capabilities & IVibrator::CAP_AMPLITUDE_CONTROL, 0);
+}
+
+TEST_F(VibratorTest, supportsExternalAmplitudeControl_unsupported) {
+ int32_t capabilities;
+ EXPECT_CALL(*mMockApi, hasOwtFreeSpace()).WillOnce(Return(true));
+ EXPECT_CALL(*mMockApi, getHapticAlsaDevice(_, _)).WillOnce(Return(true));
+
+ EXPECT_TRUE(mVibrator->getCapabilities(&capabilities).isOk());
+ EXPECT_EQ(capabilities & IVibrator::CAP_EXTERNAL_AMPLITUDE_CONTROL, 0);
+}
+
+TEST_F(VibratorTest, setAmplitude_supported) {
+ EffectAmplitude amplitude = static_cast<float>(std::rand()) / RAND_MAX ?: 1.0f;
+
+ EXPECT_CALL(*mMockApi, setFFGain(_, amplitudeToScale(amplitude))).WillOnce(Return(true));
+
+ EXPECT_TRUE(mVibrator->setAmplitude(amplitude).isOk());
+}
+
+TEST_F(VibratorTest, supportsExternalControl_supported) {
+ int32_t capabilities;
+ EXPECT_CALL(*mMockApi, hasOwtFreeSpace()).WillOnce(Return(true));
+ EXPECT_CALL(*mMockApi, getHapticAlsaDevice(_, _)).WillOnce(Return(true));
+
+ EXPECT_TRUE(mVibrator->getCapabilities(&capabilities).isOk());
+ EXPECT_GT(capabilities & IVibrator::CAP_EXTERNAL_CONTROL, 0);
+}
+
+TEST_F(VibratorTest, supportsExternalControl_unsupported) {
+ int32_t capabilities;
+ EXPECT_CALL(*mMockApi, hasOwtFreeSpace()).WillOnce(Return(true));
+ EXPECT_CALL(*mMockApi, getHapticAlsaDevice(_, _)).WillOnce(Return(false));
+
+ EXPECT_TRUE(mVibrator->getCapabilities(&capabilities).isOk());
+ EXPECT_EQ(capabilities & IVibrator::CAP_EXTERNAL_CONTROL, 0);
+}
+
+TEST_F(VibratorTest, setExternalControl_enable) {
+ Sequence s1, s2;
+ EXPECT_CALL(*mMockApi, setFFGain(_, ON_GLOBAL_SCALE)).InSequence(s1).WillOnce(DoDefault());
+ EXPECT_CALL(*mMockApi, getHapticAlsaDevice(_, _)).InSequence(s2).WillOnce(Return(true));
+ EXPECT_CALL(*mMockApi, setHapticPcmAmp(_, true, _, _))
+ .InSequence(s1, s2)
+ .WillOnce(Return(true));
+
+ EXPECT_TRUE(mVibrator->setExternalControl(true).isOk());
+}
+
+TEST_F(VibratorTest, setExternalControl_disable) {
+ Sequence s1, s2, s3, s4;
+
+ // The default mIsUnderExternalControl is false, so it needs to turn on the External Control
+ // to make mIsUnderExternalControl become true.
+ EXPECT_CALL(*mMockApi, setFFGain(_, ON_GLOBAL_SCALE))
+ .InSequence(s1)
+ .InSequence(s1)
+ .WillOnce(DoDefault());
+ EXPECT_CALL(*mMockApi, getHapticAlsaDevice(_, _)).InSequence(s2).WillOnce(Return(true));
+ EXPECT_CALL(*mMockApi, setHapticPcmAmp(_, true, _, _)).InSequence(s3).WillOnce(Return(true));
+
+ EXPECT_TRUE(mVibrator->setExternalControl(true).isOk());
+
+ EXPECT_CALL(*mMockApi, setFFGain(_, levelToScale(VOLTAGE_SCALE_MAX)))
+ .InSequence(s4)
+ .WillOnce(DoDefault());
+ EXPECT_CALL(*mMockApi, setHapticPcmAmp(_, false, _, _))
+ .InSequence(s1, s2, s3, s4)
+ .WillOnce(Return(true));
+
+ EXPECT_TRUE(mVibrator->setExternalControl(false).isOk());
+}
+
+class EffectsTest : public VibratorTest, public WithParamInterface<EffectTuple> {
+ public:
+ static auto PrintParam(const TestParamInfo<ParamType> &info) {
+ auto param = info.param;
+ auto effect = std::get<0>(param);
+ auto strength = std::get<1>(param);
+ return toString(effect) + "_" + toString(strength);
+ }
+};
+
+TEST_P(EffectsTest, perform) {
+ auto param = GetParam();
+ auto effect = std::get<0>(param);
+ auto strength = std::get<1>(param);
+ auto scale = EFFECT_SCALE.find(param);
+ auto queue = EFFECT_QUEUE.find(param);
+ EffectDuration duration;
+ auto callback = ndk::SharedRefBase::make<MockVibratorCallback>();
+ std::promise<void> promise;
+ std::future<void> future{promise.get_future()};
+ auto complete = [&promise] {
+ promise.set_value();
+ return ndk::ScopedAStatus::ok();
+ };
+ bool composeEffect;
+
+ ExpectationSet eSetup;
+ Expectation eActivate, ePollHaptics, ePollStop, eEraseDone;
+
+ if (scale != EFFECT_SCALE.end()) {
+ EffectIndex index = EFFECT_INDEX.at(effect);
+ duration = EFFECT_DURATIONS[index];
+
+ eSetup += EXPECT_CALL(*mMockApi, setFFGain(_, levelToScale(scale->second)))
+ .WillOnce(DoDefault());
+ eActivate = EXPECT_CALL(*mMockApi, setFFPlay(_, index, true))
+ .After(eSetup)
+ .WillOnce(DoDefault());
+ } else if (queue != EFFECT_QUEUE.end()) {
+ duration = std::get<1>(queue->second);
+ eSetup += EXPECT_CALL(*mMockApi, setFFGain(_, ON_GLOBAL_SCALE))
+ .After(eSetup)
+ .WillOnce(DoDefault());
+ eSetup += EXPECT_CALL(*mMockApi, getOwtFreeSpace(_)).WillOnce(DoDefault());
+ eSetup += EXPECT_CALL(*mMockApi, uploadOwtEffect(_, _, _, _, _, _))
+ .After(eSetup)
+ .WillOnce(DoDefault());
+ eActivate = EXPECT_CALL(*mMockApi, setFFPlay(_, WAVEFORM_COMPOSE, true))
+ .After(eSetup)
+ .WillOnce(DoDefault());
+ composeEffect = true;
+ } else {
+ duration = 0;
+ }
+
+ if (duration) {
+ ePollHaptics = EXPECT_CALL(*mMockApi, pollVibeState(1, POLLING_TIMEOUT))
+ .After(eActivate)
+ .WillOnce(DoDefault());
+ ePollStop = EXPECT_CALL(*mMockApi, pollVibeState(0, -1))
+ .After(ePollHaptics)
+ .WillOnce(DoDefault());
+ if (composeEffect) {
+ eEraseDone = EXPECT_CALL(*mMockApi, eraseOwtEffect(_, _, _))
+ .After(ePollStop)
+ .WillOnce(DoDefault());
+ EXPECT_CALL(*callback, onComplete()).After(eEraseDone).WillOnce(complete);
+ } else {
+ EXPECT_CALL(*callback, onComplete()).After(ePollStop).WillOnce(complete);
+ }
+ }
+
+ int32_t lengthMs;
+ ndk::ScopedAStatus status = mVibrator->perform(effect, strength, callback, &lengthMs);
+ if (status.isOk()) {
+ EXPECT_LE(duration, lengthMs);
+ } else {
+ EXPECT_EQ(EX_UNSUPPORTED_OPERATION, status.getExceptionCode());
+ EXPECT_EQ(0, lengthMs);
+ }
+
+ if (duration) {
+ EXPECT_EQ(future.wait_for(std::chrono::milliseconds(100)), std::future_status::ready);
+ }
+}
+
+const std::vector<Effect> kEffects{ndk::enum_range<Effect>().begin(),
+ ndk::enum_range<Effect>().end()};
+const std::vector<EffectStrength> kEffectStrengths{ndk::enum_range<EffectStrength>().begin(),
+ ndk::enum_range<EffectStrength>().end()};
+
+INSTANTIATE_TEST_CASE_P(VibratorTests, EffectsTest,
+ Combine(ValuesIn(kEffects.begin(), kEffects.end()),
+ ValuesIn(kEffectStrengths.begin(), kEffectStrengths.end())),
+ EffectsTest::PrintParam);
+
+struct PrimitiveParam {
+ CompositePrimitive primitive;
+ EffectIndex index;
+};
+
+class PrimitiveTest : public VibratorTest, public WithParamInterface<PrimitiveParam> {
+ public:
+ static auto PrintParam(const TestParamInfo<ParamType> &info) {
+ return toString(info.param.primitive);
+ }
+};
+
+const std::vector<PrimitiveParam> kPrimitiveParams = {
+ {CompositePrimitive::CLICK, 2}, {CompositePrimitive::THUD, 4},
+ {CompositePrimitive::SPIN, 5}, {CompositePrimitive::QUICK_RISE, 6},
+ {CompositePrimitive::SLOW_RISE, 7}, {CompositePrimitive::QUICK_FALL, 8},
+ {CompositePrimitive::LIGHT_TICK, 9}, {CompositePrimitive::LOW_TICK, 10},
+};
+
+TEST_P(PrimitiveTest, getPrimitiveDuration) {
+ auto param = GetParam();
+ auto primitive = param.primitive;
+ auto index = param.index;
+ int32_t duration;
+
+ EXPECT_EQ(EX_NONE, mVibrator->getPrimitiveDuration(primitive, &duration).getExceptionCode());
+ EXPECT_EQ(EFFECT_DURATIONS[index], duration);
+}
+
+INSTANTIATE_TEST_CASE_P(VibratorTests, PrimitiveTest,
+ ValuesIn(kPrimitiveParams.begin(), kPrimitiveParams.end()),
+ PrimitiveTest::PrintParam);
+
+struct ComposeParam {
+ std::string name;
+ std::vector<CompositeEffect> composite;
+ EffectQueue queue;
+};
+
+class ComposeTest : public VibratorTest, public WithParamInterface<ComposeParam> {
+ public:
+ static auto PrintParam(const TestParamInfo<ParamType> &info) { return info.param.name; }
+};
+
+TEST_P(ComposeTest, compose) {
+ auto param = GetParam();
+ auto composite = param.composite;
+ auto queue = std::get<0>(param.queue);
+ ExpectationSet eSetup;
+ Expectation eActivate, ePollHaptics, ePollStop, eEraseDone;
+ auto callback = ndk::SharedRefBase::make<MockVibratorCallback>();
+ std::promise<void> promise;
+ std::future<void> future{promise.get_future()};
+ auto complete = [&promise] {
+ promise.set_value();
+ return ndk::ScopedAStatus::ok();
+ };
+
+ eSetup += EXPECT_CALL(*mMockApi, setFFGain(_, ON_GLOBAL_SCALE))
+ .After(eSetup)
+ .WillOnce(DoDefault());
+ eSetup += EXPECT_CALL(*mMockApi, getOwtFreeSpace(_)).WillOnce(DoDefault());
+ eSetup += EXPECT_CALL(*mMockApi, uploadOwtEffect(_, _, _, _, _, _))
+ .After(eSetup)
+ .WillOnce(DoDefault());
+ eActivate = EXPECT_CALL(*mMockApi, setFFPlay(_, WAVEFORM_COMPOSE, true))
+ .After(eSetup)
+ .WillOnce(DoDefault());
+
+ ePollHaptics = EXPECT_CALL(*mMockApi, pollVibeState(1, POLLING_TIMEOUT))
+ .After(eActivate)
+ .WillOnce(DoDefault());
+ ePollStop =
+ EXPECT_CALL(*mMockApi, pollVibeState(0, -1)).After(ePollHaptics).WillOnce(DoDefault());
+ eEraseDone =
+ EXPECT_CALL(*mMockApi, eraseOwtEffect(_, _, _)).After(ePollStop).WillOnce(DoDefault());
+ EXPECT_CALL(*callback, onComplete()).After(eEraseDone).WillOnce(complete);
+
+ EXPECT_EQ(EX_NONE, mVibrator->compose(composite, callback).getExceptionCode());
+
+ EXPECT_EQ(future.wait_for(std::chrono::milliseconds(100)), std::future_status::ready);
+}
+
+const std::vector<ComposeParam> kComposeParams = {
+ {"click",
+ {{0, CompositePrimitive::CLICK, 1.0f}},
+ Queue(QueueEffect(2, Level(1.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])), 0)},
+ {"thud",
+ {{1, CompositePrimitive::THUD, 0.8f}},
+ Queue(1, QueueEffect(4, Level(0.8f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])), 0)},
+ {"spin",
+ {{2, CompositePrimitive::SPIN, 0.6f}},
+ Queue(2, QueueEffect(5, Level(0.6f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])), 0)},
+ {"quick_rise",
+ {{3, CompositePrimitive::QUICK_RISE, 0.4f}},
+ Queue(3, QueueEffect(6, Level(0.4f, V_LONG_DEFAULT[0], V_LONG_DEFAULT[1])), 0)},
+ {"slow_rise",
+ {{4, CompositePrimitive::SLOW_RISE, 0.0f}},
+ Queue(4, QueueEffect(7, Level(0.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])), 0)},
+ {"quick_fall",
+ {{5, CompositePrimitive::QUICK_FALL, 1.0f}},
+ Queue(5, QueueEffect(8, Level(1.0f, V_LONG_DEFAULT[0], V_LONG_DEFAULT[1])), 0)},
+ {"pop",
+ {{6, CompositePrimitive::SLOW_RISE, 1.0f}, {50, CompositePrimitive::THUD, 1.0f}},
+ Queue(6, QueueEffect(7, Level(1.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])), 50,
+ QueueEffect(4, Level(1.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])), 0)},
+ {"snap",
+ {{7, CompositePrimitive::QUICK_RISE, 1.0f}, {0, CompositePrimitive::QUICK_FALL, 1.0f}},
+ Queue(7, QueueEffect(6, Level(1.0f, V_LONG_DEFAULT[0], V_LONG_DEFAULT[1])),
+ QueueEffect(8, Level(1.0f, V_LONG_DEFAULT[0], V_LONG_DEFAULT[1])), 0)},
+};
+
+INSTANTIATE_TEST_CASE_P(VibratorTests, ComposeTest,
+ ValuesIn(kComposeParams.begin(), kComposeParams.end()),
+ ComposeTest::PrintParam);
+} // namespace vibrator
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/vibrator/cs40l26/tests/types.h b/vibrator/cs40l26/tests/types.h
new file mode 100644
index 0000000..e05c648
--- /dev/null
+++ b/vibrator/cs40l26/tests/types.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+#ifndef ANDROID_HARDWARE_VIBRATOR_TEST_TYPES_H
+#define ANDROID_HARDWARE_VIBRATOR_TEST_TYPES_H
+
+#include <aidl/android/hardware/vibrator/IVibrator.h>
+
+using EffectIndex = uint16_t;
+using EffectLevel = uint32_t;
+using EffectAmplitude = float;
+using EffectScale = uint16_t;
+using EffectDuration = uint32_t;
+using EffectQueue = std::tuple<std::string, EffectDuration>;
+using EffectTuple = std::tuple<::aidl::android::hardware::vibrator::Effect,
+ ::aidl::android::hardware::vibrator::EffectStrength>;
+
+using QueueEffect = std::tuple<EffectIndex, EffectLevel>;
+using QueueDelay = uint32_t;
+
+#endif // ANDROID_HARDWARE_VIBRATOR_TEST_TYPES_H
diff --git a/vibrator/cs40l26/tests/utils.h b/vibrator/cs40l26/tests/utils.h
new file mode 100644
index 0000000..e7f6055
--- /dev/null
+++ b/vibrator/cs40l26/tests/utils.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+#ifndef ANDROID_HARDWARE_VIBRATOR_TEST_UTILS_H
+#define ANDROID_HARDWARE_VIBRATOR_TEST_UTILS_H
+
+#include <cmath>
+
+#include "types.h"
+
+static inline EffectScale toScale(float amplitude, float maximum) {
+ float ratio = 100; /* Unit: % */
+ if (maximum != 0)
+ ratio = amplitude / maximum * 100;
+
+ if (maximum == 0 || ratio > 100)
+ ratio = 100;
+
+ return std::round(ratio);
+}
+
+static inline EffectScale levelToScale(EffectLevel level) {
+ return toScale(level, 100);
+}
+
+static inline EffectScale amplitudeToScale(EffectAmplitude amplitude) {
+ return toScale(amplitude, 1.0f);
+}
+
+static inline uint32_t msToCycles(EffectDuration ms) {
+ return ms * 48;
+}
+
+#endif // ANDROID_HARDWARE_VIBRATOR_TEST_UTILS_H