thermal: support cooling device status change notification

1. support the powercap in CoolingDevice
2. support the notification when CoolingDevice status changed

Bug: 312540064
Test: atest VtsHalThermalTargetTest
Change-Id: I58e39c0a2c070317b574f1a5d4abfa582cc56f34
This commit is contained in:
TeYuan Wang 2023-12-11 14:55:39 -08:00
parent e91038dfc4
commit 2bbd1ec30c
9 changed files with 294 additions and 0 deletions

View file

@ -38,4 +38,7 @@ parcelable CoolingDevice {
android.hardware.thermal.CoolingType type;
String name;
long value;
long powerLimitMw;
long powerMw;
long timeWindowMs;
}

View file

@ -0,0 +1,39 @@
/*
* Copyright (C) 2023 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.
*/
///////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
// two cases:
// 1). this is a frozen version file - do not edit this in any case.
// 2). this is a 'current' file. If you make a backwards compatible change to
// the interface (from the latest frozen version), the build system will
// prompt you to update this file with `m <name>-update-api`.
//
// You must not make a backward incompatible change to any AIDL file built
// with the aidl_interface module type with versions property set. The module
// type is used to build AIDL files in a way that they can be used across
// independently updatable components of the system. If a device is shipped
// with such a backward incompatible change, it has a high risk of breaking
// later when a module using the interface is updated, e.g., Mainline modules.
package android.hardware.thermal;
/* @hide */
@VintfStability
interface ICoolingDeviceChangedCallback {
oneway void notifyCoolingDeviceChanged(in android.hardware.thermal.CoolingDevice coolingDevice);
}

View file

@ -44,4 +44,6 @@ interface IThermal {
void registerThermalChangedCallback(in android.hardware.thermal.IThermalChangedCallback callback);
void registerThermalChangedCallbackWithType(in android.hardware.thermal.IThermalChangedCallback callback, in android.hardware.thermal.TemperatureType type);
void unregisterThermalChangedCallback(in android.hardware.thermal.IThermalChangedCallback callback);
void registerCoolingDeviceChangedCallbackWithType(in android.hardware.thermal.ICoolingDeviceChangedCallback callback, in android.hardware.thermal.CoolingType type);
void unregisterCoolingDeviceChangedCallback(in android.hardware.thermal.ICoolingDeviceChangedCallback callback);
}

View file

@ -40,4 +40,16 @@ parcelable CoolingDevice {
* means deeper throttling.
*/
long value;
/**
* Power budget (mW) of the cooling device.
*/
long powerLimitMw;
/**
* Target cooling device's AVG power for the last time_window_ms.
*/
long powerMw;
/**
* The time window (millisecond) to calculate the power consumption
*/
long timeWindowMs;
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (C) 2023 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 android.hardware.thermal;
import android.hardware.thermal.CoolingDevice;
import android.hardware.thermal.Temperature;
/**
* ICoolingDeviceChangedCallback send cooling device change notification to clients.
* @hide
*/
@VintfStability
interface ICoolingDeviceChangedCallback {
/**
* Send a cooling device change event to all ThermalHAL
* cooling device event listeners.
*
* @param cooling_device The cooling device information associated with the
* change event.
*/
oneway void notifyCoolingDeviceChanged(in CoolingDevice coolingDevice);
}

View file

@ -18,6 +18,7 @@ package android.hardware.thermal;
import android.hardware.thermal.CoolingDevice;
import android.hardware.thermal.CoolingType;
import android.hardware.thermal.ICoolingDeviceChangedCallback;
import android.hardware.thermal.IThermalChangedCallback;
import android.hardware.thermal.Temperature;
import android.hardware.thermal.TemperatureThreshold;
@ -188,4 +189,40 @@ interface IThermal {
* getMessage() must be populated with human-readable error message.
*/
void unregisterThermalChangedCallback(in IThermalChangedCallback callback);
/**
* Register an ICoolingDeviceChangedCallback for a given CoolingType, used by
* the Thermal HAL to receive CDEV events when cooling device status
* changed.
* Multiple registrations with different ICoolingDeviceChangedCallback must be allowed.
* Multiple registrations with same ICoolingDeviceChangedCallback is not allowed, client
* should unregister the given ICoolingDeviceChangedCallback first.
*
* @param callback the ICoolingChangedCallback to use for receiving
* cooling device events. If nullptr callback is given, the status code will be
* STATUS_BAD_VALUE and the operation will fail.
* @param type the type to be filtered.
*
* @throws EX_ILLEGAL_ARGUMENT If the callback is given nullptr or already registered. And the
* getMessage() must be populated with human-readable error message.
* @throws EX_ILLEGAL_STATE If the Thermal HAL is not initialized successfully. And the
* getMessage() must be populated with human-readable error message.
*/
void registerCoolingDeviceChangedCallbackWithType(
in ICoolingDeviceChangedCallback callback, in CoolingType type);
/**
* Unregister an ICoolingDeviceChangedCallback, used by the Thermal HAL
* to receive CDEV events when cooling device status changed.
*
* @param callback the ICoolingDeviceChangedCallback to use for receiving
* cooling device events. if nullptr callback is given, the status code will be
* STATUS_BAD_VALUE and the operation will fail.
*
* @throws EX_ILLEGAL_ARGUMENT If the callback is given nullptr or not previously registered.
* And the getMessage() must be populated with human-readable error message.
* @throws EX_ILLEGAL_STATE If the Thermal HAL is not initialized successfully. And the
* getMessage() must be populated with human-readable error message.
*/
void unregisterCoolingDeviceChangedCallback(in ICoolingDeviceChangedCallback callback);
}

View file

@ -142,4 +142,53 @@ ScopedAStatus Thermal::unregisterThermalChangedCallback(
return ScopedAStatus::ok();
}
ScopedAStatus Thermal::registerCoolingDeviceChangedCallbackWithType(
const std::shared_ptr<ICoolingDeviceChangedCallback>& in_callback, CoolingType in_type) {
LOG(VERBOSE) << __func__ << " ICoolingDeviceChangedCallback: " << in_callback
<< ", CoolingType: " << static_cast<int32_t>(in_type);
if (in_callback == nullptr) {
return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
"Invalid nullptr callback");
}
{
std::lock_guard<std::mutex> _lock(cdev_callback_mutex_);
if (std::any_of(cdev_callbacks_.begin(), cdev_callbacks_.end(),
[&](const std::shared_ptr<ICoolingDeviceChangedCallback>& c) {
return interfacesEqual(c, in_callback);
})) {
return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
"Callback already registered");
}
cdev_callbacks_.push_back(in_callback);
}
return ScopedAStatus::ok();
}
ScopedAStatus Thermal::unregisterCoolingDeviceChangedCallback(
const std::shared_ptr<ICoolingDeviceChangedCallback>& in_callback) {
LOG(VERBOSE) << __func__ << " ICoolingDeviceChangedCallback: " << in_callback;
if (in_callback == nullptr) {
return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
"Invalid nullptr callback");
}
{
std::lock_guard<std::mutex> _lock(cdev_callback_mutex_);
bool removed = false;
cdev_callbacks_.erase(
std::remove_if(cdev_callbacks_.begin(), cdev_callbacks_.end(),
[&](const std::shared_ptr<ICoolingDeviceChangedCallback>& c) {
if (interfacesEqual(c, in_callback)) {
removed = true;
return true;
}
return false;
}),
cdev_callbacks_.end());
if (!removed) {
return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
"Callback wasn't registered");
}
}
return ScopedAStatus::ok();
}
} // namespace aidl::android::hardware::thermal::impl::example

View file

@ -46,6 +46,7 @@ class Thermal : public BnThermal {
ndk::ScopedAStatus registerThermalChangedCallback(
const std::shared_ptr<IThermalChangedCallback>& in_callback) override;
ndk::ScopedAStatus registerThermalChangedCallbackWithType(
const std::shared_ptr<IThermalChangedCallback>& in_callback,
TemperatureType in_type) override;
@ -53,9 +54,18 @@ class Thermal : public BnThermal {
ndk::ScopedAStatus unregisterThermalChangedCallback(
const std::shared_ptr<IThermalChangedCallback>& in_callback) override;
ndk::ScopedAStatus registerCoolingDeviceChangedCallbackWithType(
const std::shared_ptr<ICoolingDeviceChangedCallback>& in_callback,
CoolingType in_type) override;
ndk::ScopedAStatus unregisterCoolingDeviceChangedCallback(
const std::shared_ptr<ICoolingDeviceChangedCallback>& in_callback) override;
private:
std::mutex thermal_callback_mutex_;
std::vector<std::shared_ptr<IThermalChangedCallback>> thermal_callbacks_;
std::mutex cdev_callback_mutex_;
std::vector<std::shared_ptr<ICoolingDeviceChangedCallback>> cdev_callbacks_;
};
} // namespace example

View file

@ -26,6 +26,7 @@
#include <aidl/Gtest.h>
#include <aidl/Vintf.h>
#include <aidl/android/hardware/thermal/BnCoolingDeviceChangedCallback.h>
#include <aidl/android/hardware/thermal/BnThermal.h>
#include <aidl/android/hardware/thermal/BnThermalChangedCallback.h>
#include <android-base/logging.h>
@ -59,6 +60,15 @@ static const Temperature kThrottleTemp = {
.throttlingStatus = ThrottlingSeverity::CRITICAL,
};
static const CoolingDevice kCoolingDevice = {
.type = CoolingType::CPU,
.name = "test cooling device",
.value = 1,
.powerLimitMw = 300,
.powerMw = 500,
.timeWindowMs = 7000,
};
// Callback class for receiving thermal event notifications from main class
class ThermalCallback : public BnThermalChangedCallback {
public:
@ -85,6 +95,33 @@ class ThermalCallback : public BnThermalChangedCallback {
bool mInvoke = false;
};
// Callback class for receiving cooling device event notifications from main class
class CoolingDeviceCallback : public BnCoolingDeviceChangedCallback {
public:
ndk::ScopedAStatus notifyCoolingDeviceChanged(const CoolingDevice&) override {
{
std::lock_guard<std::mutex> lock(mMutex);
mInvoke = true;
}
mNotifyCoolingDeviceChanged.notify_all();
return ndk::ScopedAStatus::ok();
}
template <typename R, typename P>
[[nodiscard]] bool waitForCallback(std::chrono::duration<R, P> duration) {
std::unique_lock<std::mutex> lock(mMutex);
bool r = mNotifyCoolingDeviceChanged.wait_for(lock, duration,
[this] { return this->mInvoke; });
mInvoke = false;
return r;
}
private:
std::mutex mMutex;
std::condition_variable mNotifyCoolingDeviceChanged;
bool mInvoke = false;
};
// The main test class for THERMAL HIDL HAL.
class ThermalAidlTest : public testing::TestWithParam<std::string> {
public:
@ -97,19 +134,42 @@ class ThermalAidlTest : public testing::TestWithParam<std::string> {
ASSERT_NE(mThermalCallback, nullptr);
::ndk::ScopedAStatus status = mThermal->registerThermalChangedCallback(mThermalCallback);
ASSERT_TRUE(status.isOk()) << status.getMessage();
auto ret = mThermal->getInterfaceVersion(&thermal_version);
ASSERT_TRUE(ret.isOk()) << ret;
if (thermal_version > 1) {
mCoolingDeviceCallback = ndk::SharedRefBase::make<CoolingDeviceCallback>();
ASSERT_NE(mCoolingDeviceCallback, nullptr);
status = mThermal->registerCoolingDeviceChangedCallbackWithType(mCoolingDeviceCallback,
kCoolingDevice.type);
ASSERT_TRUE(status.isOk()) << status.getMessage();
}
}
void TearDown() override {
::ndk::ScopedAStatus status = mThermal->unregisterThermalChangedCallback(mThermalCallback);
ASSERT_TRUE(status.isOk()) << status.getMessage();
// Expect to fail if unregister again
status = mThermal->unregisterThermalChangedCallback(mThermalCallback);
ASSERT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode());
auto ret = mThermal->getInterfaceVersion(&thermal_version);
ASSERT_TRUE(ret.isOk()) << ret;
if (thermal_version > 1) {
status = mThermal->unregisterCoolingDeviceChangedCallback(mCoolingDeviceCallback);
ASSERT_TRUE(status.isOk()) << status.getMessage();
status = mThermal->unregisterCoolingDeviceChangedCallback(mCoolingDeviceCallback);
ASSERT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode());
}
}
// Stores thermal version
int32_t thermal_version;
protected:
std::shared_ptr<IThermal> mThermal;
std::shared_ptr<ThermalCallback> mThermalCallback;
std::shared_ptr<CoolingDeviceCallback> mCoolingDeviceCallback;
};
// Test ThermalChangedCallback::notifyThrottling().
@ -121,6 +181,21 @@ TEST_P(ThermalAidlTest, NotifyThrottlingTest) {
ASSERT_TRUE(thermalCallback->waitForCallback(200ms));
}
// Test CoolingDeviceChangedCallback::notifyCoolingDeviceChanged().
// This just calls into and back from our local CoolingDeviceChangedCallback impl.
TEST_P(ThermalAidlTest, NotifyCoolingDeviceChangedTest) {
auto ret = mThermal->getInterfaceVersion(&thermal_version);
ASSERT_TRUE(ret.isOk()) << ret;
if (thermal_version < 2) {
return;
}
std::shared_ptr<CoolingDeviceCallback> cdevCallback =
ndk::SharedRefBase::make<CoolingDeviceCallback>();
::ndk::ScopedAStatus status = cdevCallback->notifyCoolingDeviceChanged(kCoolingDevice);
ASSERT_TRUE(status.isOk()) << status.getMessage();
ASSERT_TRUE(cdevCallback->waitForCallback(200ms));
}
// Test Thermal->registerThermalChangedCallback.
TEST_P(ThermalAidlTest, RegisterThermalChangedCallbackTest) {
// Expect to fail with same callback
@ -169,6 +244,37 @@ TEST_P(ThermalAidlTest, RegisterThermalChangedCallbackWithTypeTest) {
|| status.getExceptionCode() == EX_NULL_POINTER);
}
// Test Thermal->registerCoolingDeviceChangedCallbackWithType.
TEST_P(ThermalAidlTest, RegisterCoolingDeviceChangedCallbackWithTypeTest) {
auto ret = mThermal->getInterfaceVersion(&thermal_version);
ASSERT_TRUE(ret.isOk()) << ret;
if (thermal_version < 2) {
return;
}
// Expect to fail with same callback
::ndk::ScopedAStatus status = mThermal->registerCoolingDeviceChangedCallbackWithType(
mCoolingDeviceCallback, CoolingType::CPU);
ASSERT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode());
// Expect to fail with null callback
status = mThermal->registerCoolingDeviceChangedCallbackWithType(nullptr, CoolingType::CPU);
ASSERT_TRUE(status.getExceptionCode() == EX_ILLEGAL_ARGUMENT ||
status.getExceptionCode() == EX_NULL_POINTER);
std::shared_ptr<CoolingDeviceCallback> localCoolingDeviceCallback =
ndk::SharedRefBase::make<CoolingDeviceCallback>();
// Expect to succeed with different callback
status = mThermal->registerCoolingDeviceChangedCallbackWithType(localCoolingDeviceCallback,
CoolingType::CPU);
ASSERT_TRUE(status.isOk()) << status.getMessage();
// Remove the local callback
status = mThermal->unregisterCoolingDeviceChangedCallback(localCoolingDeviceCallback);
ASSERT_TRUE(status.isOk()) << status.getMessage();
// Expect to fail with null callback
status = mThermal->unregisterCoolingDeviceChangedCallback(nullptr);
ASSERT_TRUE(status.getExceptionCode() == EX_ILLEGAL_ARGUMENT ||
status.getExceptionCode() == EX_NULL_POINTER);
}
// Test Thermal->getCurrentTemperatures().
TEST_P(ThermalAidlTest, TemperatureTest) {
std::vector<Temperature> ret;