diff options
author | Bill Richardson <wfrichar@google.com> | 2019-03-07 23:10:38 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2019-03-07 23:10:38 +0000 |
commit | d1daae4510fe8c952fa4c983d4acaf89848c8e44 (patch) | |
tree | 802be90ab4fc2ade22b49c74127170b7c626c605 | |
parent | 18e6bd786dcd1ef8796bee3dc786f3f74d2e8646 (diff) | |
parent | 17d3407ed21af615f5be449d9ebd1be27add5a57 (diff) | |
download | android-d1daae4510fe8c952fa4c983d4acaf89848c8e44.tar.gz |
Merge "Merge remote-tracking branch 'goog/upstream-master' into bill-release"
-rw-r--r-- | citadel/citadeld/Android.bp | 1 | ||||
-rw-r--r-- | citadel/citadeld/aidl/android/hardware/citadel/ICitadeld.aidl | 3 | ||||
-rw-r--r-- | citadel/citadeld/main.cpp | 248 |
3 files changed, 239 insertions, 13 deletions
diff --git a/citadel/citadeld/Android.bp b/citadel/citadeld/Android.bp index f57d672..835845e 100644 --- a/citadel/citadeld/Android.bp +++ b/citadel/citadeld/Android.bp @@ -28,6 +28,7 @@ cc_defaults { "libbinder", "libnos", "libutils", + "//hardware/google/pixel:pixelpowerstats_provider_aidl_interface-cpp", ], } diff --git a/citadel/citadeld/aidl/android/hardware/citadel/ICitadeld.aidl b/citadel/citadeld/aidl/android/hardware/citadel/ICitadeld.aidl index 2fc1a3b..ee59982 100644 --- a/citadel/citadeld/aidl/android/hardware/citadel/ICitadeld.aidl +++ b/citadel/citadeld/aidl/android/hardware/citadel/ICitadeld.aidl @@ -30,4 +30,7 @@ interface ICitadeld { /** Reset Citadel by pulling the reset line. */ boolean reset(); + + /** Get cached low-power stats */ + void getCachedStats(out byte[] response); } diff --git a/citadel/citadeld/main.cpp b/citadel/citadeld/main.cpp index 2afbdb6..de8b6b2 100644 --- a/citadel/citadeld/main.cpp +++ b/citadel/citadeld/main.cpp @@ -14,27 +14,38 @@ * limitations under the License. */ +#include <algorithm> +#include <chrono> +#include <condition_variable> +#include <functional> +#include <future> #include <limits> -#include <thread> #include <mutex> +#include <thread> #include <android-base/logging.h> +#include <binder/IBinder.h> #include <binder/IPCThreadState.h> #include <binder/IServiceManager.h> -#include <nos/device.h> +#include <app_nugget.h> #include <nos/NuggetClient.h> +#include <nos/device.h> #include <android/hardware/citadel/BnCitadeld.h> -using ::android::OK; +#include <android/vendor/powerstats/BnPixelPowerStatsCallback.h> +#include <android/vendor/powerstats/BnPixelPowerStatsProvider.h> +#include <android/vendor/powerstats/StateResidencyData.h> + using ::android::defaultServiceManager; -using ::android::sp; -using ::android::status_t; using ::android::IPCThreadState; using ::android::IServiceManager; +using ::android::OK; using ::android::ProcessState; - +using ::android::sp; +using ::android::status_t; +using ::android::wp; using ::android::binder::Status; using ::nos::NuggetClient; @@ -42,32 +53,172 @@ using ::nos::NuggetClient; using ::android::hardware::citadel::BnCitadeld; using ::android::hardware::citadel::ICitadeld; +using android::IBinder; +using android::vendor::powerstats::BnPixelPowerStatsCallback; +using android::vendor::powerstats::IPixelPowerStatsProvider; +using android::vendor::powerstats::StateResidencyData; + namespace { +using namespace std::chrono_literals; + +// This attaches a timer to a function call. Call .schedule() to start the +// timer, and the function will be called (once) after the time has elapsed. If +// you call .schedule() again before that happens, it just restarts the timer. +// There's no way to cancel the function call after it's scheduled; you can only +// postpone it. +class DeferredCallback { + public: + DeferredCallback(std::chrono::milliseconds delay, std::function<void()> fn) + : _armed(false), + _delay(delay), + _func(fn), + _waiter_thread(std::bind(&DeferredCallback::waiter_task, this)) {} + ~DeferredCallback() {} + + // [re]start the timer for the delayed call + void schedule() { + std::unique_lock<std::mutex> _lock(_cv_mutex); + _armed = true; + _cv.notify_one(); + } + + private: + void waiter_task(void) { + std::unique_lock<std::mutex> _lock(_cv_mutex); + while (true) { + if (!_armed) { + _cv.wait(_lock); + } + auto timeout = std::chrono::steady_clock::now() + _delay; + if (_cv.wait_until(_lock, timeout) == std::cv_status::timeout) { + _func(); + _armed = false; + } + } + } + + bool _armed; + const std::chrono::milliseconds _delay; + const std::function<void()> _func; + std::thread _waiter_thread; + std::mutex _cv_mutex; + std::condition_variable _cv; +}; + +// This provides a Binder interface for the powerstats service to fetch our +// power stats info from. This is a secondary function of citadeld. Failures +// here must not block or delay AP/Citadel communication. +class StatsDelegate : public BnPixelPowerStatsCallback, + public IBinder::DeathRecipient { + public: + StatsDelegate(std::function<Status(std::vector<StateResidencyData>*)> fn) + : func_(fn) {} + + // methods from BnPixelPowerStatsCallback + virtual Status getStats(std::vector<StateResidencyData>* stats) override { + return func_(stats); + } + + // methods from IBinder::DeathRecipient + virtual IBinder* onAsBinder() override { return this; } + virtual void binderDied(const wp<IBinder>& who) override { + LOG(INFO) << "powerstats service died"; + const sp<IBinder>& service = who.promote(); + if (service != nullptr) { + service->unlinkToDeath(this); + } + sp<IBinder> powerstats_service = WaitForPowerStatsService(); + registerWithPowerStats(powerstats_service); + } + + // post-creation init (Binder calls inside constructor are troublesome) + void registerWithPowerStats(sp<IBinder>& powerstats_service) { + sp<IPixelPowerStatsProvider> powerstats_provider = + android::interface_cast<IPixelPowerStatsProvider>( + powerstats_service); + + LOG(INFO) << "signing up for a notification if powerstats dies"; + auto ret = asBinder(powerstats_provider) + ->linkToDeath(this, 0u /* cookie */); + if (ret != android::OK) { + LOG(ERROR) << "linkToDeath() returned " << ret + << " - we will NOT be notified on powerstats death"; + } + + LOG(INFO) << "registering our callback with powerstats service"; + Status status = powerstats_provider->registerCallback("Citadel", this); + if (!status.isOk()) { + LOG(ERROR) << "failed to register callback: " << status.toString8(); + } + } + + // static helper function + static sp<IBinder> WaitForPowerStatsService() { + LOG(INFO) << "waiting for powerstats service to appear"; + sp<IBinder> svc; + while (true) { + svc = defaultServiceManager()->checkService( + android::String16("power.stats-vendor")); + if (svc != nullptr) { + LOG(INFO) << "A wild powerstats service has appeared!"; + return svc; + } + sleep(1); + } + } + + // Creates a new StatsDelegate only after a powerstats service becomes + // available for it to register with. + static sp<StatsDelegate> MakeOne( + std::function<Status(std::vector<StateResidencyData>*)> fn) { + sp<IBinder> powerstats_service = + StatsDelegate::WaitForPowerStatsService(); + sp<StatsDelegate> sd = new StatsDelegate(fn); + sd->registerWithPowerStats(powerstats_service); + return sd; + } + + private: + const std::function<Status(std::vector<StateResidencyData>*)> func_; +}; + class CitadelProxy : public BnCitadeld { -public: - CitadelProxy(NuggetClient& client) : _client{client} {} + public: + CitadelProxy(NuggetClient& client) + : _client{client}, + _stats_collection(500ms, std::bind(&CitadelProxy::cacheStats, this)) { + } ~CitadelProxy() override = default; - Status callApp(const int32_t _appId, const int32_t _arg, const std::vector<uint8_t>& request, - std::vector<uint8_t>* const response, int32_t* const _aidl_return) override { - // AIDL doesn't support integers less than 32-bit so validate it before casting + // methods from BnCitadeld + + Status callApp(const int32_t _appId, const int32_t _arg, + const std::vector<uint8_t>& request, + std::vector<uint8_t>* const response, + int32_t* const _aidl_return) override { + // AIDL doesn't support integers less than 32-bit so validate it before + // casting if (_appId < 0 || _appId > kMaxAppId) { LOG(ERROR) << "App ID " << _appId << " is outside the app ID range"; return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT); } if (_arg < 0 || _arg > std::numeric_limits<uint16_t>::max()) { - LOG(ERROR) << "Argument " << _arg << " is outside the unsigned 16-bit range"; + LOG(ERROR) << "Argument " << _arg + << " is outside the unsigned 16-bit range"; return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT); } const uint8_t appId = static_cast<uint32_t>(_appId); - const uint16_t arg = static_cast<uint16_t>(_arg); + const uint16_t arg = static_cast<uint16_t>(_arg); uint32_t* const appStatus = reinterpret_cast<uint32_t*>(_aidl_return); // Make the call to the app while holding the lock for that app std::unique_lock<std::mutex> lock(_appLocks[appId]); *appStatus = _client.CallApp(appId, arg, request, response); + + _stats_collection.schedule(); + return Status::ok(); } @@ -79,11 +230,69 @@ public: return Status::ok(); } + Status getCachedStats(std::vector<uint8_t>* const response) override { + std::unique_lock<std::mutex> lock(_stats_mutex); + response->resize(sizeof(_stats)); + memcpy(response->data(), &_stats, sizeof(_stats)); + return Status::ok(); + } + + // Interaction with the powerstats service is handled by the StatsDelegate + // class, but its getStats() method calls this to access our cached stats. + Status onGetStats(std::vector<StateResidencyData>* stats) { + std::unique_lock<std::mutex> lock(_stats_mutex); + + StateResidencyData data1; + data1.state = "Reset"; + data1.totalTimeInStateMs = _stats.time_since_hard_reset / 1000; + data1.totalStateEntryCount = _stats.hard_reset_count; + data1.lastEntryTimestampMs = 0; + stats->emplace_back(data1); + + StateResidencyData data2; + data2.state = "Active"; + data2.totalTimeInStateMs = _stats.time_spent_awake / 1000; + data2.totalStateEntryCount = _stats.wake_count; + data2.lastEntryTimestampMs = _stats.time_at_last_wake / 1000; + stats->emplace_back(data2); + + StateResidencyData data3; + data3.state = "Deep-Sleep"; + data3.totalTimeInStateMs = _stats.time_spent_in_deep_sleep / 1000; + data3.totalStateEntryCount = _stats.deep_sleep_count; + data3.lastEntryTimestampMs = _stats.time_at_last_deep_sleep / 1000; + stats->emplace_back(data3); + + return Status::ok(); + } + private: static constexpr auto kMaxAppId = std::numeric_limits<uint8_t>::max(); NuggetClient& _client; std::mutex _appLocks[kMaxAppId + 1]; + struct nugget_app_low_power_stats _stats; + DeferredCallback _stats_collection; + std::mutex _stats_mutex; + + void cacheStats(void) { + std::vector<uint8_t> buffer; + buffer.reserve(sizeof(_stats)); + uint32_t rv; + + { + std::unique_lock<std::mutex> lock(_appLocks[APP_ID_NUGGET]); + rv = _client.CallApp(APP_ID_NUGGET, + NUGGET_PARAM_GET_LOW_POWER_STATS, buffer, + &buffer); + } + + if (rv == APP_SUCCESS) { + std::unique_lock<std::mutex> lock(_stats_mutex); + memcpy(&_stats, buffer.data(), + std::min(sizeof(_stats), buffer.size())); + } + } }; [[noreturn]] void CitadelEventDispatcher(const nos_device& device) { @@ -122,14 +331,27 @@ int main() { const status_t status = defaultServiceManager()->addService(ICitadeld::descriptor, proxy); if (status != OK) { LOG(FATAL) << "Failed to register citadeld as a service (status " << status << ")"; + return 1; } // Handle interrupts triggered by Citadel and dispatch any events to // registered listeners. std::thread event_dispatcher(CitadelEventDispatcher, *citadel.Device()); + // We'll create a StatsDelegate object to talk to the powerstats service, + // but it will need a function to access the stats we've cached in the + // CitadelProxy object. + std::function<Status(std::vector<StateResidencyData>*)> fn = + std::bind(&CitadelProxy::onGetStats, proxy, std::placeholders::_1); + + // Use a separate thread to wait for the powerstats service to appear, so + // the Citadel proxy can start working ASAP. + std::future<sp<StatsDelegate>> sd = + std::async(std::launch::async, StatsDelegate::MakeOne, fn); + // Start handling binder requests with multiple threads ProcessState::self()->startThreadPool(); IPCThreadState::self()->joinThreadPool(); + return 0; } |