// Copyright 2016 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 "aemu/base/Compiler.h"

namespace android {

// In QEMU2, each virtual CPU runs on its own host threads, but all these
// threads are synchronized through a global mutex, which allows the virtual
// device code to not care about them.
//
// However, if you have to call, from any other thread, a low-level QEMU
// function that operate on virtual devices (e.g. some Android pipe-related
// functions), you must acquire the global mutex before doing so, and release
// it after that.

// This header provides a convenience interface class you can use to do
// just that, i.e.:
//
// 1) To operate on the lock, call VmLock::get() to retrieve the
//    current VmLock instance, then invoke its lock() and unlock()
//    methods.
//
// 2) Glue code should call VmLock::set() to inject their own implementation
//    into the process. The default implementation doesn't do anything.
//

class VmLock {
    DISALLOW_COPY_ASSIGN_AND_MOVE(VmLock);
public:
    VmLock() = default;
    virtual ~VmLock();

    // Lock the VM global mutex.
    virtual void lock() {}

    // Unlock the VM global mutex.
    virtual void unlock() {}

    // Returns true iff the lock is held by the current thread, false
    // otherwise. Note that for a correct implementation, that doesn't
    // only depend on the number of times that VmLock::lock() and
    // VmLock::unlock() were called, but also on other QEMU threads that
    // act on the global lock.
    virtual bool isLockedBySelf() const { return true; }

    // Return current VmLock instance. Cannot return nullptr.
    // NOT thread-safe, but we don't expect multiple threads to call this
    // concurrently at init time, and the worst that can happen is to leak
    // a single instance.
    static VmLock* get();

    // Returns whether or not there is a VmLock.
    // Does not instantiate a VmLock.
    static bool hasInstance();

    // Set new VmLock instance. Return old value, which cannot be nullptr and
    // can be deleted by the caller. If |vmLock| is nullptr, a new default
    // instance is created. NOTE: not thread-safe with regards to get().
    static VmLock* set(VmLock* vmLock);
};

// Convenience class to perform scoped VM locking.
class ScopedVmLock {
    DISALLOW_COPY_ASSIGN_AND_MOVE(ScopedVmLock);
public:
    ScopedVmLock(VmLock* vmLock = VmLock::get()) : mVmLock(vmLock) {
        mVmLock->lock();
    }

    ~ScopedVmLock() {
        mVmLock->unlock();
    }

private:
    VmLock* const mVmLock;
};

// Convenience class to perform scoped VM locking (but does not try
// to lock twice).
class RecursiveScopedVmLock {
    DISALLOW_COPY_ASSIGN_AND_MOVE(RecursiveScopedVmLock);
public:
    RecursiveScopedVmLock(VmLock* vmLock = VmLock::get()) {
        if (vmLock->isLockedBySelf()) {
            mVmLock = nullptr;
        } else {
            mVmLock = vmLock;
            vmLock->lock();
        }
    }

    ~RecursiveScopedVmLock() {
        if (mVmLock) {
            mVmLock->unlock();
        }
    }

private:
    VmLock* mVmLock;
};

// Convenience class to perform scoped VM locking (but does not try
// to lock twice), but no-ops if there is no instance.
class RecursiveScopedVmLockIfInstance {
    DISALLOW_COPY_ASSIGN_AND_MOVE(RecursiveScopedVmLockIfInstance);
public:
    RecursiveScopedVmLockIfInstance() {
        if (!VmLock::hasInstance()) return;

        VmLock* vmLock = VmLock::get();

        if (vmLock->isLockedBySelf()) {
            mVmLock = nullptr;
        } else {
            mVmLock = vmLock;
            vmLock->lock();
        }
    }

    ~RecursiveScopedVmLockIfInstance() {
        if (mVmLock) {
            mVmLock->unlock();
        }
    }

private:
    VmLock* mVmLock = nullptr;;
};

// Another convenience class for a code that may run either under a lock or not
// but needs to ensure that some part of it runs without a VmLock.
class ScopedVmUnlock {
    DISALLOW_COPY_ASSIGN_AND_MOVE(ScopedVmUnlock);
public:
    ScopedVmUnlock(VmLock* vmLock = VmLock::get()) {
        if (vmLock->isLockedBySelf()) {
            mVmLock = vmLock;
            vmLock->unlock();
        } else {
            mVmLock = nullptr;
        }
    }

    ~ScopedVmUnlock() {
        if (mVmLock) {
            mVmLock->lock();
        }
    }

private:
    VmLock* mVmLock;
};

}  // namespace android
