blob: ae7d6daa649b0dfbfe3ce8a826370cfebbb8f0d8 [file] [log] [blame]
/*
* Copyright (C) 2014 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 "BatteryStatsService"
//#define LOG_NDEBUG 0
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <semaphore.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <android/hardware/power/1.0/IPower.h>
#include <android/hardware/power/1.1/IPower.h>
#include <android_runtime/AndroidRuntime.h>
#include <jni.h>
#include <nativehelper/ScopedLocalRef.h>
#include <nativehelper/ScopedPrimitiveArray.h>
#include <log/log.h>
#include <utils/misc.h>
#include <utils/Log.h>
#include <suspend/autosuspend.h>
using android::hardware::Return;
using android::hardware::Void;
using android::hardware::power::V1_0::IPower;
using android::hardware::power::V1_0::PowerStatePlatformSleepState;
using android::hardware::power::V1_0::PowerStateVoter;
using android::hardware::power::V1_0::Status;
using android::hardware::power::V1_1::PowerStateSubsystem;
using android::hardware::power::V1_1::PowerStateSubsystemSleepState;
using android::hardware::hidl_vec;
namespace android
{
#define LAST_RESUME_REASON "/sys/kernel/wakeup_reasons/last_resume_reason"
#define MAX_REASON_SIZE 512
static bool wakeup_init = false;
static sem_t wakeup_sem;
extern sp<android::hardware::power::V1_0::IPower> gPowerHalV1_0;
extern std::mutex gPowerHalMutex;
extern bool getPowerHal();
// Java methods used in getLowPowerStats
static jmethodID jgetAndUpdatePlatformState = NULL;
static jmethodID jgetSubsystem = NULL;
static jmethodID jputVoter = NULL;
static jmethodID jputState = NULL;
static void wakeup_callback(bool success)
{
ALOGV("In wakeup_callback: %s", success ? "resumed from suspend" : "suspend aborted");
int ret = sem_post(&wakeup_sem);
if (ret < 0) {
char buf[80];
strerror_r(errno, buf, sizeof(buf));
ALOGE("Error posting wakeup sem: %s\n", buf);
}
}
static jint nativeWaitWakeup(JNIEnv *env, jobject clazz, jobject outBuf)
{
if (outBuf == NULL) {
jniThrowException(env, "java/lang/NullPointerException", "null argument");
return -1;
}
// Register our wakeup callback if not yet done.
if (!wakeup_init) {
wakeup_init = true;
ALOGV("Creating semaphore...");
int ret = sem_init(&wakeup_sem, 0, 0);
if (ret < 0) {
char buf[80];
strerror_r(errno, buf, sizeof(buf));
ALOGE("Error creating semaphore: %s\n", buf);
jniThrowException(env, "java/lang/IllegalStateException", buf);
return -1;
}
ALOGV("Registering callback...");
set_wakeup_callback(&wakeup_callback);
}
// Wait for wakeup.
ALOGV("Waiting for wakeup...");
int ret = sem_wait(&wakeup_sem);
if (ret < 0) {
char buf[80];
strerror_r(errno, buf, sizeof(buf));
ALOGE("Error waiting on semaphore: %s\n", buf);
// Return 0 here to let it continue looping but not return results.
return 0;
}
FILE *fp = fopen(LAST_RESUME_REASON, "r");
if (fp == NULL) {
ALOGE("Failed to open %s", LAST_RESUME_REASON);
return -1;
}
char* mergedreason = (char*)env->GetDirectBufferAddress(outBuf);
int remainreasonlen = (int)env->GetDirectBufferCapacity(outBuf);
ALOGV("Reading wakeup reasons");
char* mergedreasonpos = mergedreason;
char reasonline[128];
int i = 0;
while (fgets(reasonline, sizeof(reasonline), fp) != NULL) {
char* pos = reasonline;
char* endPos;
int len;
// First field is the index or 'Abort'.
int irq = (int)strtol(pos, &endPos, 10);
if (pos != endPos) {
// Write the irq number to the merged reason string.
len = snprintf(mergedreasonpos, remainreasonlen, i == 0 ? "%d" : ":%d", irq);
} else {
// The first field is not an irq, it may be the word Abort.
const size_t abortPrefixLen = strlen("Abort:");
if (strncmp(pos, "Abort:", abortPrefixLen) != 0) {
// Ooops.
ALOGE("Bad reason line: %s", reasonline);
continue;
}
// Write 'Abort' to the merged reason string.
len = snprintf(mergedreasonpos, remainreasonlen, i == 0 ? "Abort" : ":Abort");
endPos = pos + abortPrefixLen;
}
pos = endPos;
if (len >= 0 && len < remainreasonlen) {
mergedreasonpos += len;
remainreasonlen -= len;
}
// Skip whitespace; rest of the buffer is the reason string.
while (*pos == ' ') {
pos++;
}
// Chop newline at end.
char* endpos = pos;
while (*endpos != 0) {
if (*endpos == '\n') {
*endpos = 0;
break;
}
endpos++;
}
len = snprintf(mergedreasonpos, remainreasonlen, ":%s", pos);
if (len >= 0 && len < remainreasonlen) {
mergedreasonpos += len;
remainreasonlen -= len;
}
i++;
}
ALOGV("Got %d reasons", i);
if (i > 0) {
*mergedreasonpos = 0;
}
if (fclose(fp) != 0) {
ALOGE("Failed to close %s", LAST_RESUME_REASON);
return -1;
}
return mergedreasonpos - mergedreason;
}
static void getLowPowerStats(JNIEnv* env, jobject /* clazz */, jobject jrpmStats) {
if (jrpmStats == NULL) {
jniThrowException(env, "java/lang/NullPointerException",
"The rpmstats jni input jobject jrpmStats is null.");
return;
}
if (jgetAndUpdatePlatformState == NULL || jgetSubsystem == NULL
|| jputVoter == NULL || jputState == NULL) {
ALOGE("A rpmstats jni jmethodID is null.");
return;
}
std::lock_guard<std::mutex> lock(gPowerHalMutex);
if (!getPowerHal()) {
ALOGE("Power Hal not loaded");
return;
}
Return<void> ret = gPowerHalV1_0->getPlatformLowPowerStats(
[&env, &jrpmStats](hidl_vec<PowerStatePlatformSleepState> states, Status status) {
if (status != Status::SUCCESS) return;
for (size_t i = 0; i < states.size(); i++) {
const PowerStatePlatformSleepState& state = states[i];
jobject jplatformState = env->CallObjectMethod(jrpmStats,
jgetAndUpdatePlatformState,
env->NewStringUTF(state.name.c_str()),
state.residencyInMsecSinceBoot,
state.totalTransitions);
if (jplatformState == NULL) {
ALOGE("The rpmstats jni jobject jplatformState is null.");
return;
}
for (size_t j = 0; j < state.voters.size(); j++) {
const PowerStateVoter& voter = state.voters[j];
env->CallVoidMethod(jplatformState, jputVoter,
env->NewStringUTF(voter.name.c_str()),
voter.totalTimeInMsecVotedForSinceBoot,
voter.totalNumberOfTimesVotedSinceBoot);
}
}
});
if (!ret.isOk()) {
ALOGE("getLowPowerStats() failed: power HAL service not available");
gPowerHalV1_0 = nullptr;
return;
}
//Trying to cast to IPower 1.1, this will succeed only for devices supporting 1.1
sp<android::hardware::power::V1_1::IPower> gPowerHal_1_1
= android::hardware::power::V1_1::IPower::castFrom(gPowerHalV1_0);
if (gPowerHal_1_1 == nullptr) {
//This device does not support IPower@1.1, exiting gracefully
return;
}
ret = gPowerHal_1_1->getSubsystemLowPowerStats(
[&env, &jrpmStats](hidl_vec<PowerStateSubsystem> subsystems, Status status) {
if (status != Status::SUCCESS) return;
if (subsystems.size() > 0) {
for (size_t i = 0; i < subsystems.size(); i++) {
const PowerStateSubsystem &subsystem = subsystems[i];
jobject jsubsystem = env->CallObjectMethod(jrpmStats, jgetSubsystem,
env->NewStringUTF(subsystem.name.c_str()));
if (jsubsystem == NULL) {
ALOGE("The rpmstats jni jobject jsubsystem is null.");
return;
}
for (size_t j = 0; j < subsystem.states.size(); j++) {
const PowerStateSubsystemSleepState& state = subsystem.states[j];
env->CallVoidMethod(jsubsystem, jputState,
env->NewStringUTF(state.name.c_str()),
state.residencyInMsecSinceBoot,
state.totalTransitions);
}
}
}
});
if (!ret.isOk()) {
ALOGE("getSubsystemLowPowerStats() failed: power HAL service not available");
gPowerHalV1_0 = nullptr;
}
// gPowerHalMutex released here
}
static jint getPlatformLowPowerStats(JNIEnv* env, jobject /* clazz */, jobject outBuf) {
char *output = (char*)env->GetDirectBufferAddress(outBuf);
char *offset = output;
int remaining = (int)env->GetDirectBufferCapacity(outBuf);
int total_added = -1;
if (outBuf == NULL) {
jniThrowException(env, "java/lang/NullPointerException", "null argument");
return -1;
}
{
std::lock_guard<std::mutex> lock(gPowerHalMutex);
if (!getPowerHal()) {
ALOGE("Power Hal not loaded");
return -1;
}
Return<void> ret = gPowerHalV1_0->getPlatformLowPowerStats(
[&offset, &remaining, &total_added](hidl_vec<PowerStatePlatformSleepState> states,
Status status) {
if (status != Status::SUCCESS)
return;
for (size_t i = 0; i < states.size(); i++) {
int added;
const PowerStatePlatformSleepState& state = states[i];
added = snprintf(offset, remaining,
"state_%zu name=%s time=%" PRIu64 " count=%" PRIu64 " ",
i + 1, state.name.c_str(), state.residencyInMsecSinceBoot,
state.totalTransitions);
if (added < 0) {
break;
}
if (added > remaining) {
added = remaining;
}
offset += added;
remaining -= added;
total_added += added;
for (size_t j = 0; j < state.voters.size(); j++) {
const PowerStateVoter& voter = state.voters[j];
added = snprintf(offset, remaining,
"voter_%zu name=%s time=%" PRIu64 " count=%" PRIu64 " ",
j + 1, voter.name.c_str(),
voter.totalTimeInMsecVotedForSinceBoot,
voter.totalNumberOfTimesVotedSinceBoot);
if (added < 0) {
break;
}
if (added > remaining) {
added = remaining;
}
offset += added;
remaining -= added;
total_added += added;
}
if (remaining <= 0) {
/* rewrite NULL character*/
offset--;
total_added--;
ALOGE("PowerHal: buffer not enough");
break;
}
}
}
);
if (!ret.isOk()) {
ALOGE("getPlatformLowPowerStats() failed: power HAL service not available");
gPowerHalV1_0 = nullptr;
return -1;
}
}
*offset = 0;
total_added += 1;
return total_added;
}
static jint getSubsystemLowPowerStats(JNIEnv* env, jobject /* clazz */, jobject outBuf) {
char *output = (char*)env->GetDirectBufferAddress(outBuf);
char *offset = output;
int remaining = (int)env->GetDirectBufferCapacity(outBuf);
int total_added = -1;
//This is a IPower 1.1 API
sp<android::hardware::power::V1_1::IPower> gPowerHal_1_1 = nullptr;
if (outBuf == NULL) {
jniThrowException(env, "java/lang/NullPointerException", "null argument");
return -1;
}
{
std::lock_guard<std::mutex> lock(gPowerHalMutex);
if (!getPowerHal()) {
ALOGE("Power Hal not loaded");
return -1;
}
//Trying to cast to 1.1, this will succeed only for devices supporting 1.1
gPowerHal_1_1 = android::hardware::power::V1_1::IPower::castFrom(gPowerHalV1_0);
if (gPowerHal_1_1 == nullptr) {
//This device does not support IPower@1.1, exiting gracefully
return 0;
}
Return<void> ret = gPowerHal_1_1->getSubsystemLowPowerStats(
[&offset, &remaining, &total_added](hidl_vec<PowerStateSubsystem> subsystems,
Status status) {
if (status != Status::SUCCESS)
return;
if (subsystems.size() > 0) {
int added = snprintf(offset, remaining, "SubsystemPowerState ");
offset += added;
remaining -= added;
total_added += added;
for (size_t i = 0; i < subsystems.size(); i++) {
const PowerStateSubsystem &subsystem = subsystems[i];
added = snprintf(offset, remaining,
"subsystem_%zu name=%s ", i + 1, subsystem.name.c_str());
if (added < 0) {
break;
}
if (added > remaining) {
added = remaining;
}
offset += added;
remaining -= added;
total_added += added;
for (size_t j = 0; j < subsystem.states.size(); j++) {
const PowerStateSubsystemSleepState& state = subsystem.states[j];
added = snprintf(offset, remaining,
"state_%zu name=%s time=%" PRIu64 " count=%" PRIu64 " last entry=%" PRIu64 " ",
j + 1, state.name.c_str(), state.residencyInMsecSinceBoot,
state.totalTransitions, state.lastEntryTimestampMs);
if (added < 0) {
break;
}
if (added > remaining) {
added = remaining;
}
offset += added;
remaining -= added;
total_added += added;
}
if (remaining <= 0) {
/* rewrite NULL character*/
offset--;
total_added--;
ALOGE("PowerHal: buffer not enough");
break;
}
}
}
}
);
if (!ret.isOk()) {
ALOGE("getSubsystemLowPowerStats() failed: power HAL service not available");
gPowerHalV1_0 = nullptr;
return -1;
}
}
*offset = 0;
total_added += 1;
return total_added;
}
static const JNINativeMethod method_table[] = {
{ "nativeWaitWakeup", "(Ljava/nio/ByteBuffer;)I", (void*)nativeWaitWakeup },
{ "getLowPowerStats", "(Lcom/android/internal/os/RpmStats;)V", (void*)getLowPowerStats },
{ "getPlatformLowPowerStats", "(Ljava/nio/ByteBuffer;)I", (void*)getPlatformLowPowerStats },
{ "getSubsystemLowPowerStats", "(Ljava/nio/ByteBuffer;)I", (void*)getSubsystemLowPowerStats },
};
int register_android_server_BatteryStatsService(JNIEnv *env)
{
// get java classes and methods
jclass clsRpmStats = env->FindClass("com/android/internal/os/RpmStats");
jclass clsPowerStatePlatformSleepState =
env->FindClass("com/android/internal/os/RpmStats$PowerStatePlatformSleepState");
jclass clsPowerStateSubsystem =
env->FindClass("com/android/internal/os/RpmStats$PowerStateSubsystem");
if (clsRpmStats == NULL || clsPowerStatePlatformSleepState == NULL
|| clsPowerStateSubsystem == NULL) {
ALOGE("A rpmstats jni jclass is null.");
} else {
jgetAndUpdatePlatformState = env->GetMethodID(clsRpmStats, "getAndUpdatePlatformState",
"(Ljava/lang/String;JI)Lcom/android/internal/os/RpmStats$PowerStatePlatformSleepState;");
jgetSubsystem = env->GetMethodID(clsRpmStats, "getSubsystem",
"(Ljava/lang/String;)Lcom/android/internal/os/RpmStats$PowerStateSubsystem;");
jputVoter = env->GetMethodID(clsPowerStatePlatformSleepState, "putVoter",
"(Ljava/lang/String;JI)V");
jputState = env->GetMethodID(clsPowerStateSubsystem, "putState",
"(Ljava/lang/String;JI)V");
}
return jniRegisterNativeMethods(env, "com/android/server/am/BatteryStatsService",
method_table, NELEM(method_table));
}
};