diff options
author | David 'Digit' Turner <digit@google.com> | 2016-09-14 14:55:57 +0200 |
---|---|---|
committer | David 'Digit' Turner <digit@google.com> | 2016-10-18 15:44:24 +0200 |
commit | 594b225720a75e30a099ee53953ce67d1d3d0782 (patch) | |
tree | 32327784311ae71965ec013dcbb6ef800fbbbb03 | |
parent | 1b1f9139e98627ad48a4ddeb0551950f9b02dbb6 (diff) | |
download | qemu-android-594b225720a75e30a099ee53953ce67d1d3d0782.tar.gz |
android: glue: Add QEMU2 Looper support.
This patch ensures that the android::base::Looper instance is
set correctly for any QEMU2 thread.
Change-Id: I46e6f1b2a4bf121f760f9239978aea61d8c631ae
-rw-r--r-- | android-qemu2-glue/base/async/Looper.cpp | 341 | ||||
-rw-r--r-- | android-qemu2-glue/base/async/Looper.h | 27 | ||||
-rw-r--r-- | android-qemu2-glue/build/Makefile.qemu2-glue.mk | 2 | ||||
-rw-r--r-- | android-qemu2-glue/looper-qemu.cpp | 23 | ||||
-rw-r--r-- | android-qemu2-glue/looper-qemu.h | 26 | ||||
-rw-r--r-- | android-qemu2-glue/qemu-setup.cpp | 7 |
6 files changed, 426 insertions, 0 deletions
diff --git a/android-qemu2-glue/base/async/Looper.cpp b/android-qemu2-glue/base/async/Looper.cpp new file mode 100644 index 0000000000..643047f59a --- /dev/null +++ b/android-qemu2-glue/base/async/Looper.cpp @@ -0,0 +1,341 @@ +// Copyright 2014 The Android Open Source Project +// +// This software is licensed under the terms of the GNU General Public +// License version 2, as published by the Free Software Foundation, and +// may be copied, distributed, and modified under those terms. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +#include "android-qemu2-glue/base/async/Looper.h" + +#include "android/base/Log.h" +#include "android/base/containers/TailQueueList.h" +#include "android/base/containers/ScopedPointerSet.h" +#include "android/base/sockets/SocketUtils.h" +#include "android-qemu2-glue/base/files/QemuFileStream.h" +#include "android/utils/stream.h" + +extern "C" { +#include "qemu/osdep.h" +#include "qemu-common.h" +#include "qemu/timer.h" +#include "sysemu/char.h" +} // extern "C" + +namespace android { +namespace qemu { + +namespace { + +extern "C" void qemu_system_shutdown_request(void); + +typedef ::android::base::Looper BaseLooper; +typedef ::android::base::Looper::Timer BaseTimer; +typedef ::android::base::Looper::FdWatch BaseFdWatch; + +// An implementation of android::base::Looper on top of the QEMU main +// event loop. There are few important things here: +// +// 1/ There is a single global QEMU event loop, so all instances returned +// by createLooper() will really use the same state! +// +// In other words, don't call it more than once! +// +// 2/ It is not possible to call the runWithDeadlineMs() method, since +// the event loop is started in the application's main thread by +// the executable. +// +// The implementation uses a bottom-half handler to process pending +// FdWatch instances, see the comment in the declaration of FdWatch +// below to understand why. +// +class QemuLooper : public BaseLooper { +public: + QemuLooper() : + Looper(), + mQemuBh(NULL), + mPendingFdWatches(), + mTimers() { + } + + virtual ~QemuLooper() { + if (mQemuBh) { + qemu_bh_delete(mQemuBh); + } + } + + static QEMUClockType toQemuClockType(ClockType clock) { + static_assert((int) QEMU_CLOCK_HOST == (int) BaseLooper::ClockType::kHost && + (int) QEMU_CLOCK_VIRTUAL == (int) BaseLooper::ClockType::kVirtual && + (int) QEMU_CLOCK_REALTIME == (int) BaseLooper::ClockType::kRealtime, + "Values in the Looper::ClockType enumeration are out of sync with " + "QEMUClockType"); + + return static_cast<QEMUClockType>(clock); + } + + // + // F D W A T C H E S + // + + // Implementation is slightly more complicated because QEMU uses + // distinct callback functions for read/write events, so use a pending + // list to collect all FdWatch instances that have received any of such + // call, then later process it. + + class FdWatch : public BaseFdWatch { + public: + FdWatch(QemuLooper* looper, + int fd, + BaseFdWatch::Callback callback, + void* opaque) : + BaseFdWatch(looper, fd, callback, opaque), + mWantedEvents(0U), + mPendingEvents(0U), + mLink() { + } + + virtual ~FdWatch() { + clearPending(); + qemu_set_fd_handler(mFd, NULL, NULL, NULL); + } + + virtual void addEvents(unsigned events) { + events &= kEventMask; + updateEvents(mWantedEvents | events); + } + + virtual void removeEvents(unsigned events) { + events &= kEventMask; + updateEvents(mWantedEvents & ~events); + } + + virtual unsigned poll() const { + return mPendingEvents; + } + + bool isPending() const { + return mPendingEvents != 0U; + } + + void fire() { + DCHECK(mPendingEvents); + unsigned events = mPendingEvents; + mPendingEvents = 0U; + mCallback(mOpaque, mFd, events); + } + + TAIL_QUEUE_LIST_TRAITS(Traits, FdWatch, mLink); + + private: + void updateEvents(unsigned events) { + IOHandler* cbRead = (events & kEventRead) ? handleRead : NULL; + IOHandler* cbWrite = (events & kEventWrite) ? handleWrite : NULL; + qemu_set_fd_handler(mFd, cbRead, cbWrite, this); + mWantedEvents = events; + } + + void setPending(unsigned event) { + if (!mPendingEvents) { + asQemuLooper(mLooper)->addPendingFdWatch(this); + } + mPendingEvents |= event; + } + + void clearPending() { + if (mPendingEvents) { + asQemuLooper(mLooper)->delPendingFdWatch(this); + mPendingEvents = 0; + } + } + + // Called by QEMU on a read i/o event. |opaque| is a FdWatch handle. + static void handleRead(void* opaque) { + FdWatch* watch = static_cast<FdWatch*>(opaque); + watch->setPending(kEventRead); + } + + // Called by QEMU on a write i/o event. |opaque| is a FdWatch handle. + static void handleWrite(void* opaque) { + FdWatch* watch = static_cast<FdWatch*>(opaque); + watch->setPending(kEventWrite); + } + + unsigned mWantedEvents; + unsigned mPendingEvents; + ::android::base::TailQueueLink<FdWatch> mLink; + }; + + virtual BaseFdWatch* createFdWatch(int fd, + BaseFdWatch::Callback callback, + void* opaque) { + ::android::base::socketSetNonBlocking(fd); + return new FdWatch(this, fd, callback, opaque); + } + + // + // T I M E R S + // + class Timer : public BaseTimer { + public: + Timer(QemuLooper* looper, + BaseTimer::Callback callback, + void* opaque, ClockType clock) : + BaseTimer(looper, callback, opaque, clock), + mTimer(NULL) { + mTimer = ::timer_new(QemuLooper::toQemuClockType(mClockType), + SCALE_MS, + qemuTimerCallbackAdapter, + this); + } + + ~Timer() { + ::timer_free(mTimer); + } + + virtual void startRelative(Duration timeout_ms) { + if (timeout_ms == kDurationInfinite) { + timer_del(mTimer); + } else { + timeout_ms += qemu_clock_get_ms( + QemuLooper::toQemuClockType(mClockType)); + timer_mod(mTimer, timeout_ms); + } + } + + virtual void startAbsolute(Duration deadline_ms) { + if (deadline_ms == kDurationInfinite) { + timer_del(mTimer); + } else { + timer_mod(mTimer, deadline_ms); + } + } + + virtual void stop() { + ::timer_del(mTimer); + } + + virtual bool isActive() const { + return timer_pending(mTimer); + } + + void save(android::base::Stream* stream) const { + timer_put( + reinterpret_cast<android::qemu::QemuFileStream*>(stream)->file(), + mTimer); + } + + void load(android::base::Stream* stream) { + timer_get( + reinterpret_cast<android::qemu::QemuFileStream*>(stream)->file(), + mTimer); + } + + private: + static void qemuTimerCallbackAdapter(void* opaque) { + Timer* timer = static_cast<Timer*>(opaque); + timer->mCallback(timer->mOpaque, timer); + } + + QEMUTimer* mTimer; + }; + + virtual BaseTimer* createTimer(BaseTimer::Callback callback, + void* opaque, ClockType clock) { + return new QemuLooper::Timer(this, callback, opaque, clock); + } + + // + // L O O P E R + // + + virtual Duration nowMs(ClockType clockType) { + return qemu_clock_get_ms(toQemuClockType(clockType)); + } + + virtual DurationNs nowNs(ClockType clockType) { + return qemu_clock_get_ns(toQemuClockType(clockType)); + } + + virtual int runWithDeadlineMs(Duration deadline_ms) { + CHECK(false) << "User cannot call looper_run on a QEMU event loop"; + errno = ENOSYS; + return -1; + } + + virtual void forceQuit() { + qemu_system_shutdown_request(); + } + +private: + + typedef ::android::base::TailQueueList<QemuLooper::FdWatch> FdWatchList; + typedef ::android::base::ScopedPointerSet<FdWatch> FdWatchSet; + typedef ::android::base::ScopedPointerSet<Timer> TimerSet; + + static inline QemuLooper* asQemuLooper(BaseLooper* looper) { + return reinterpret_cast<QemuLooper*>(looper); + } + + + void addTimer(Timer* timer) { + mTimers.add(timer); + } + + void delTimer(Timer* timer) { + mTimers.remove(timer); + } + + void addPendingFdWatch(FdWatch* watch) { + DCHECK(watch); + DCHECK(!watch->isPending()); + + if (mPendingFdWatches.empty()) { + // Ensure the bottom-half is triggered to act on pending + // watches as soon as possible. + if (!mQemuBh) { + mQemuBh = qemu_bh_new(handleBottomHalf, this); + } + qemu_bh_schedule(mQemuBh); + } + + mPendingFdWatches.insertTail(watch); + } + + void delPendingFdWatch(FdWatch* watch) { + DCHECK(watch); + DCHECK(watch->isPending()); + mPendingFdWatches.remove(watch); + } + + // Called by QEMU as soon as the main loop has finished processed + // I/O events. Used to look at pending watches and fire them. + static void handleBottomHalf(void* opaque) { + QemuLooper* looper = reinterpret_cast<QemuLooper*>(opaque); + for (;;) { + FdWatch* watch = looper->mPendingFdWatches.front(); + if (!watch) { + break; + } + looper->delPendingFdWatch(watch); + watch->fire(); + } + } + + QEMUBH* mQemuBh; + FdWatchList mPendingFdWatches; + TimerSet mTimers; +}; + +} // namespace + +BaseLooper* createLooper() { + return new QemuLooper(); +} + +} // namespace qemu +} // namespace android + diff --git a/android-qemu2-glue/base/async/Looper.h b/android-qemu2-glue/base/async/Looper.h new file mode 100644 index 0000000000..970638bf44 --- /dev/null +++ b/android-qemu2-glue/base/async/Looper.h @@ -0,0 +1,27 @@ +// Copyright 2014 The Android Open Source Project +// +// This software is licensed under the terms of the GNU General Public +// License version 2, as published by the Free Software Foundation, and +// may be copied, distributed, and modified under those terms. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +#pragma once + +#include "android/base/async/Looper.h" + +// An implementation of android::base::Looper based on the QEMU event loop. +namespace android { +namespace qemu { + +// Create a new android::base::Looper instance that is implemented through +// the QEMU main event loop. There is only one instance, so any call will +// return an object corresponding to the same global state, even if they +// are different instances! +android::base::Looper* createLooper(); + +} // namespace qemu +} // namespace android diff --git a/android-qemu2-glue/build/Makefile.qemu2-glue.mk b/android-qemu2-glue/build/Makefile.qemu2-glue.mk index 7b9c733f46..4e6a731a44 100644 --- a/android-qemu2-glue/build/Makefile.qemu2-glue.mk +++ b/android-qemu2-glue/build/Makefile.qemu2-glue.mk @@ -11,8 +11,10 @@ LOCAL_C_INCLUDES += \ $(QEMU2_GLUE_INCLUDES) \ LOCAL_SRC_FILES := \ + base/async/Looper.cpp \ base/files/QemuFileStream.cpp \ emulation/VmLock.cpp \ + looper-qemu.cpp \ qemu-setup.cpp \ utils/stream.cpp \ diff --git a/android-qemu2-glue/looper-qemu.cpp b/android-qemu2-glue/looper-qemu.cpp new file mode 100644 index 0000000000..e9904361e3 --- /dev/null +++ b/android-qemu2-glue/looper-qemu.cpp @@ -0,0 +1,23 @@ +// Copyright 2015 The Android Open Source Project +// +// This software is licensed under the terms of the GNU General Public +// License version 2, as published by the Free Software Foundation, and +// may be copied, distributed, and modified under those terms. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +#include "android-qemu2-glue/looper-qemu.h" + +#include "android-qemu2-glue/base/async/Looper.h" +#include "android/base/async/Looper.h" +#include "android/utils/looper.h" + +typedef ::Looper CLooper; + +void qemu_looper_setForThread() { + looper_setForThreadToOwn( + reinterpret_cast<CLooper*>(::android::qemu::createLooper())); +} diff --git a/android-qemu2-glue/looper-qemu.h b/android-qemu2-glue/looper-qemu.h new file mode 100644 index 0000000000..9b68629004 --- /dev/null +++ b/android-qemu2-glue/looper-qemu.h @@ -0,0 +1,26 @@ +/* Copyright (C) 2015 The Android Open Source Project +** +** This software is licensed under the terms of the GNU General Public +** License version 2, as published by the Free Software Foundation, and +** may be copied, distributed, and modified under those terms. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +*/ + +#pragma once + +#include "android/utils/compiler.h" +#include "android/utils/looper.h" + +ANDROID_BEGIN_HEADER + +/* Create a new looper which is implemented on top of the QEMU main event + * loop. You should only use this when implementing the emulator UI and Core + * features in a single program executable. + */ +void qemu_looper_setForThread(void); + +ANDROID_END_HEADER diff --git a/android-qemu2-glue/qemu-setup.cpp b/android-qemu2-glue/qemu-setup.cpp index 296e92a843..9daf8f5156 100644 --- a/android-qemu2-glue/qemu-setup.cpp +++ b/android-qemu2-glue/qemu-setup.cpp @@ -17,16 +17,23 @@ #include "android/android.h" #include "android/base/Log.h" #include "android-qemu2-glue/emulation/VmLock.h" +#include "android-qemu2-glue/looper-qemu.h" extern "C" { #include "qemu/osdep.h" #include "qemu-common.h" +#include "qemu/thread.h" } // extern "C" using android::VmLock; bool qemu_android_emulation_early_setup() { + // Ensure that the looper is set for the main thread and for any + // future thread created by QEMU. + qemu_looper_setForThread(); + qemu_thread_register_setup_callback(qemu_looper_setForThread); + // Ensure the VmLock implementation is setup. VmLock* vmLock = new qemu2::VmLock(); VmLock* prevVmLock = VmLock::set(vmLock); CHECK(prevVmLock == nullptr) << "Another VmLock was already installed!"; |