| // 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 <algorithm> |
| #include <functional> |
| #include <memory> |
| #include <vector> |
| |
| 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<OP>| that must implement the |
| // abstract performDeviceOperation(const OP& op) method. |
| // |
| // - Create a DeviceContextRunner<OP> 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(<op>) 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 <typename T> |
| class DeviceContextRunner { |
| public: |
| struct TimerInterface { |
| std::function<void(DeviceContextRunner*, std::function<void()>)> installFunc; |
| std::function<void(DeviceContextRunner*)> uninstallFunc; |
| std::function<void(DeviceContextRunner*, uint64_t)> startWithTimeoutFunc; |
| }; |
| |
| using AutoLock = android::base::AutoLock; |
| using Lock = android::base::Lock; |
| using VmLock = android::VmLock; |
| using PendingList = std::vector<T>; |
| |
| // 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 <class Predicate> |
| 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 <class Func> |
| 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 |