// Copyright 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. #pragma once #include "base/Lock.h" #include "host-common/VmLock.h" #include #include #include #include namespace android { // All operations that change the global VM state (e.g. // virtual device operations) should happen in a thread // that holds the global VM lock. // // DeviceContextRunner is a helper template class used // to ensure that a given operation is always performed // in such a thread. more specifically: // - If the current thread already owns the lock, // the operation is performed as-is. // - Otherwise, it is queued and will be run in the // main-loop thread as soon as possible. // // Usage is the following: // // - Define a custom type |OP| corresponding to // the state of each operation. It must be copyable. // // - Define a derived class of // |DeviceContextRunner| that must implement the // abstract performDeviceOperation(const OP& op) method. // // - Create a DeviceContextRunner instance, and call // its init() method, passing a valid android::VmLock // instance to it. NOTE: This should be called from // the main loop thread, during emulation setup time!! // // - Whenever you want to perform an operation, call // queueDeviceOperation() on it. If the current // thread holds the lock, it will be // performed immediately. Otherwise, it will be queued // and run later. Hence the void return type of // queueDeviceOperation; it is run asynchronously, so // you cannot expect a return value. enum class ContextRunMode { DeferIfNotLocked, DeferAlways }; template class DeviceContextRunner { public: struct TimerInterface { std::function)> installFunc; std::function uninstallFunc; std::function startWithTimeoutFunc; }; using AutoLock = android::base::AutoLock; using Lock = android::base::Lock; using VmLock = android::VmLock; using PendingList = std::vector; // Looper parameter is for unit testing purposes. void init(VmLock* vmLock, TimerInterface timerInterface) { mVmLock = vmLock; mTimerInterface = timerInterface; // TODO(digit): Find a better event abstraction. // // Operating on Looper::Timer objects is not supposed to be // thread-safe, but it appears that their QEMU1 and QEMU2 specific // implementation *is* (see qemu-timer.c:timer_mod()). // // This means that in practice the code below is safe when used // in the context of an Android emulation engine. However, we probably // need a better abstraction that also works with the generic // Looper implementation (for unit-testing) or any other kind of // runtime environment, should we one day link AndroidEmu to a // different emulation engine. // // Solution: Feed a callback interface that acts depending on the timer object mTimerInterface.installFunc(this, [this]() { this->onTimerEvent(); }); } void setContextRunMode(ContextRunMode mode) { AutoLock lock(mLock); mContextRunMode = mode; } protected: // Disable delete-through-interface. ~DeviceContextRunner() { mTimerInterface.uninstallFunc(this); } // To be implemented by the class that derives DeviceContextRunner: // the method that actually touches the virtual device. virtual void performDeviceOperation(const T& op) = 0; // queueDeviceOperation: If the VM lock is currently held, // we are OK to actually perform device operations. // Otherwise, we need to add the request to a pending // set of requests, to be finished later when we do have the VM lock. void queueDeviceOperation(const T& op) { if (mContextRunMode == ContextRunMode::DeferIfNotLocked && mVmLock->isLockedBySelf()) { // Perform the operation correctly since the current thread // already holds the lock that protects the global VM state. performDeviceOperation(op); } else { // Queue the operation in the mPendingMap structure, then // restart the timer. AutoLock lock(mLock); mPending.push_back(op); lock.unlock(); // NOTE: See TODO above why this is thread-safe when used with // QEMU1 and QEMU2. mTimerInterface.startWithTimeoutFunc(this, 0); } } // Remove all pending operations that match the passed predicate |op|. template void removeAllPendingOperations(const Predicate& op) { AutoLock lock(mLock); mPending.erase(std::remove_if(mPending.begin(), mPending.end(), op), mPending.end()); } // Run the passed functor |op| for all pending operations. template void forEachPendingOperation(const Func& op) const { AutoLock lock(mLock); for (const auto& p : mPending) { op(p); } } protected: size_t numPending() const { AutoLock lock(mLock); return mPending.size(); } private: void onTimerEvent() { AutoLock lock(mLock); for (const auto& elt : mPending) { performDeviceOperation(elt); } mPending.clear(); } VmLock* mVmLock = nullptr; ContextRunMode mContextRunMode = ContextRunMode::DeferIfNotLocked; mutable Lock mLock; PendingList mPending; TimerInterface mTimerInterface; }; } // namespace android