Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fce527c4 authored by Sungtak Lee's avatar Sungtak Lee
Browse files

Codec2: Add classes and interfaces for blocking allocator

Add classes for blocking allocator.
- C2Fence implementation for blocking allocator
- Synchronization between processes implementation

Bug: 157111613
Change-Id: Id9f122261c04e57205d78296112980f5b34343f5
parent 4751f481
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -36,9 +36,11 @@ cc_library {
        "C2Buffer.cpp",
        "C2Config.cpp",
        "C2DmaBufAllocator.cpp",
        "C2Fence.cpp",
        "C2PlatformStorePluginLoader.cpp",
        "C2Store.cpp",
        "platform/C2BqBuffer.cpp",
        "platform/C2SurfaceSyncObj.cpp",
        "types.cpp",
        "util/C2Debug.cpp",
        "util/C2InterfaceHelper.cpp",
+145 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.
 */

//#define LOG_NDEBUG 0
#define LOG_TAG "C2FenceFactory"
#include <utils/Log.h>

#include <C2FenceFactory.h>
#include <C2SurfaceSyncObj.h>

class C2Fence::Impl {
public:
    virtual c2_status_t wait(c2_nsecs_t timeoutNs) = 0;

    virtual bool valid() const = 0;

    virtual bool ready() const = 0;

    virtual int fd() const = 0;

    virtual bool isHW() const = 0;

    virtual ~Impl() = default;

    Impl() = default;
};

c2_status_t C2Fence::wait(c2_nsecs_t timeoutNs) {
    if (mImpl) {
        return mImpl->wait(timeoutNs);
    }
    // null fence is always signalled.
    return C2_OK;
}

bool C2Fence::valid() const {
    if (mImpl) {
        return mImpl->valid();
    }
    // null fence is always valid.
    return true;
}

bool C2Fence::ready() const {
    if (mImpl) {
        return mImpl->ready();
    }
    // null fence is always signalled.
    return true;
}

int C2Fence::fd() const {
    if (mImpl) {
        return mImpl->fd();
    }
    // null fence does not have fd.
    return -1;
}

bool C2Fence::isHW() const {
    if (mImpl) {
        return mImpl->isHW();
    }
    return false;
}

/**
 * Fence implementation for C2BufferQueueBlockPool based block allocation.
 * The implementation supports all C2Fence interface except fd().
 */
class _C2FenceFactory::SurfaceFenceImpl: public C2Fence::Impl {
public:
    virtual c2_status_t wait(c2_nsecs_t timeoutNs) {
        if (mPtr) {
            return mPtr->waitForChange(mWaitId, timeoutNs);
        }
        return C2_OK;
    }

    virtual bool valid() const {
        return mPtr;
    }

    virtual bool ready() const {
        uint32_t status;
        if (mPtr) {
            mPtr->lock();
            status = mPtr->getWaitIdLocked();
            mPtr->unlock();

            return status != mWaitId;
        }
        return true;
    }

    virtual int fd() const {
        // does not support fd, since this is shared mem and futex based
        return -1;
    }

    virtual bool isHW() const {
        return false;
    }

    virtual ~SurfaceFenceImpl() {};

    SurfaceFenceImpl(std::shared_ptr<C2SurfaceSyncMemory> syncMem, uint32_t waitId) :
            mSyncMem(syncMem),
            mPtr(syncMem ? syncMem->mem() : nullptr),
            mWaitId(syncMem ? waitId : 0) {}
private:
    const std::shared_ptr<const C2SurfaceSyncMemory> mSyncMem; // This is for life-cycle guarantee
    C2SyncVariables *const mPtr;
    const uint32_t mWaitId;
};

C2Fence::C2Fence(std::shared_ptr<Impl> impl) : mImpl(impl) {}

C2Fence _C2FenceFactory::CreateSurfaceFence(
        std::shared_ptr<C2SurfaceSyncMemory> syncMem,
        uint32_t waitId) {
    if (syncMem) {
        C2Fence::Impl *p
                = new _C2FenceFactory::SurfaceFenceImpl(syncMem, waitId);
        if (p->valid()) {
            return C2Fence(std::shared_ptr<C2Fence::Impl>(p));
        } else {
            delete p;
        }
    }
    return C2Fence();
}
+44 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 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.
 */

#ifndef STAGEFRIGHT_CODEC2_FENCE_FACTORY_H_
#define STAGEFRIGHT_CODEC2_FENCE_FACTORY_H_


#include <C2Buffer.h>

class C2SurfaceSyncMemory;

/**
 * C2Fence implementation factory
 */
struct _C2FenceFactory {

    class SurfaceFenceImpl;

    /*
     * Create C2Fence for BufferQueueBased blockpool.
     *
     * \param syncMem           Shared memory object for synchronization between processes.
     * \param waitId            wait id for tracking status change for C2Fence.
     */
    static C2Fence CreateSurfaceFence(
            std::shared_ptr<C2SurfaceSyncMemory> syncMem,
            uint32_t waitId);
};


#endif // STAGEFRIGHT_CODEC2_FENCE_FACTORY_H_
+232 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.
 */

#ifndef STAGEFRIGHT_CODEC2_SURFACE_SYNC_OBJ_H_
#define STAGEFRIGHT_CODEC2_SURFACE_SYNC_OBJ_H_

#include <cutils/native_handle.h>
#include <memory>
#include <atomic>

#include <C2Buffer.h>

/**
 * Futex based lock / wait implementation for sharing output buffer allocation
 * information between Framework and HAL.
 */
struct C2SyncVariables {
    enum SyncStatus : uint32_t {
           STATUS_INIT = 0,         // When surface configuration starts.
           STATUS_ACTIVE = 1,       // When surface configuration finishs.
                                    // STATUS_INIT -> STATUS_ACTIVE
           STATUS_SWITCHING = 2,    // When the surface is replaced by a new surface
                                    // during surface configuration.
                                    // STATUS_ACTIVE -> STATUS_SWITCHING
    };

    /**
     * Lock the memory region
     */
    int lock();

    /**
     * Unlock the memory region
     */
    int unlock();

    /**
     * Set initial dequeued buffer count.
     *
     * \param maxDequeueCount           Initial value of # of max dequeued buffer count
     * \param curDequeueCount           Initial value of # of current dequeued buffer count
     */
    void setInitialDequeueCount(int32_t maxDequeueCount, int32_t curDequeueCount);

    /**
     * Get a waitId which will be used to implement fence.
     */
    uint32_t getWaitIdLocked();

    /**
     * Return whether the upcoming dequeue operation is not blocked.
     * if it's blocked and waitId is non-null, waitId is returned to be used for waiting.
     *
     * \retval false    dequeue operation is blocked now.
     * \retval true     dequeue operation is possible.
     */
    bool isDequeueableLocked(uint32_t *waitId = nullptr);

    /**
     * Notify a buffer is queued. Return whether the upcoming dequeue operation
     * is not blocked. if it's blocked and waitId is non-null, waitId is returned
     * to be used for waiting.
     *
     * \retval false    dequeue operation is blocked now.
     * \retval true     dequeue operation is possible.
     */
    bool notifyQueuedLocked(uint32_t *waitId = nullptr);

    /**
     * Notify a buffer is dequeued.
     */
    void notifyDequeuedLocked();

    /**
     * Set sync status.
     */
    void setSyncStatusLocked(SyncStatus status);

    /**
     * Get sync status.
     */
    C2SyncVariables::SyncStatus getSyncStatusLocked();

    /**
     * Update current max dequeue count.
     */
    void updateMaxDequeueCountLocked(int32_t maxDequeueCount);

    /**
     * Wait until status is no longer equal to waitId, or until timeout.
     *
     * \param waitId            internal status for waiting until it is changed.
     * \param timeousNs         nano seconds to timeout.
     *
     * \retval C2_TIMEDOUT      change does not happen during waiting.
     * \retval C2_BAD_VALUE     invalid event waiting.
     * \retval C2_OK            change was signalled.
     */
    c2_status_t waitForChange(uint32_t waitId, c2_nsecs_t timeoutNs);

    C2SyncVariables() {}

private:
    /**
     * signal one waiter to wake up.
     */
    int signal();

    /**
     * signal all waiter to wake up.
     */
    int broadcast();

    /**
     * wait for signal or broadcast.
     */
    int wait();

    std::atomic<uint32_t> mLock;
    std::atomic<uint32_t> mCond;
    int32_t mMaxDequeueCount;
    int32_t mCurDequeueCount;
    SyncStatus mStatus;
};

/**
 * Shared memory in order to synchronize information for Surface(IGBP)
 * based output buffer allocation.
 */
class C2SurfaceSyncMemory {
public:
    /**
     * Shared memory handle in order to synchronize information for
     * Surface based output buffer allocation.
     */
    struct HandleSyncMem : public native_handle_t {
        HandleSyncMem(int fd, size_t size) :
            native_handle_t(cHeader),
            mFds{fd},
            mInts{int(size & 0xFFFFFFFF),
                int((uint64_t(size) >> 32) & 0xFFFFFFFF), kMagic} {}

        /** Returns a file descriptor of the shared memory
         * \return a file descriptor representing the shared memory
         */
        int memFd() const {return mFds.mMem;}

        /** Returns the size of the shared memory */
        size_t size() const {
            return size_t(unsigned(mInts.mSizeLo))
                    | size_t(uint64_t(unsigned(mInts.mSizeHi)) << 32);
        }

        /** Check whether the native handle is in the form of HandleSyncMem
         *
         * \return whether the native handle is compatible
         */
        static bool isValid(const native_handle_t * const o);

    protected:
        struct {
            int mMem;
        } mFds;
        struct {
            int mSizeLo;
            int mSizeHi;
            int mMagic;
        } mInts;
    private:
        enum {
            kMagic = 'ssm\x00',
            numFds = sizeof(mFds) / sizeof(int),
            numInts = sizeof(mInts) / sizeof(int),
            version = sizeof(native_handle_t)
        };
        const static native_handle_t cHeader;
    };

    /**
     * Imports a shared memory object from a native handle(The shared memory is already existing).
     * This is usually used after native_handle_t is passed via RPC.
     *
     * \param handle        handle representing shared memory for output buffer allocation.
     */
    static std::shared_ptr<C2SurfaceSyncMemory> Import(native_handle_t *handle);

    /**
     * Creats a shared memory object for synchronization of output buffer allocation.
     * Shared memory creation should be done explicitly.
     *
     * \param fd            file descriptor to shared memory
     * \param size          size of the shared memory
     */
    static std::shared_ptr<C2SurfaceSyncMemory> Create(int fd, size_t size);

    /**
     * Returns a handle representing the shread memory for synchronization of
     * output buffer allocation.
     */
    native_handle_t *handle();

    /**
     * Returns synchronization object which will provide synchronization primitives.
     *
     * \return a ptr to synchronization primitive class
     */
    C2SyncVariables *mem();

    ~C2SurfaceSyncMemory();

private:
    bool mInit;
    HandleSyncMem *mHandle;
    C2SyncVariables *mMem;

    C2SurfaceSyncMemory();
};

#endif // STAGEFRIGHT_CODEC2_SURFACE_SYNC_OBJ_H_
+265 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.
 */

//#define LOG_NDEBUG 0
#define LOG_TAG "C2SurfaceSyncObj"
#include <limits.h>
#include <linux/futex.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <utils/Log.h>

#include <chrono>
#include <C2SurfaceSyncObj.h>

const native_handle_t C2SurfaceSyncMemory::HandleSyncMem::cHeader = {
    C2SurfaceSyncMemory::HandleSyncMem::version,
    C2SurfaceSyncMemory::HandleSyncMem::numFds,
    C2SurfaceSyncMemory::HandleSyncMem::numInts,
    {}
};

bool C2SurfaceSyncMemory::HandleSyncMem::isValid(const native_handle_t * const o) {
    if (!o || memcmp(o, &cHeader, sizeof(cHeader))) {
        return false;
    }

    const HandleSyncMem *other = static_cast<const HandleSyncMem*>(o);
    return other->mInts.mMagic == kMagic;
}

C2SurfaceSyncMemory::C2SurfaceSyncMemory()
    : mInit(false), mHandle(nullptr), mMem(nullptr) {}

C2SurfaceSyncMemory::~C2SurfaceSyncMemory() {
    if (mInit) {
        if (mMem) {
            munmap(static_cast<void *>(mMem), mHandle->size());
        }
        if (mHandle) {
            native_handle_close(mHandle);
            native_handle_delete(mHandle);
        }
    }
}

std::shared_ptr<C2SurfaceSyncMemory> C2SurfaceSyncMemory::Import(
        native_handle_t *handle) {
    if (!HandleSyncMem::isValid(handle)) {
        return nullptr;
    }

    HandleSyncMem *o = static_cast<HandleSyncMem*>(handle);
    void *ptr = mmap(NULL, o->size(), PROT_READ | PROT_WRITE, MAP_SHARED, o->memFd(), 0);

    if (ptr == MAP_FAILED) {
        native_handle_close(handle);
        native_handle_delete(handle);
        return nullptr;
    }

    std::shared_ptr<C2SurfaceSyncMemory> syncMem(new C2SurfaceSyncMemory);
    syncMem->mInit = true;
    syncMem->mHandle = o;
    syncMem->mMem = static_cast<C2SyncVariables*>(ptr);
    return syncMem;
}

std::shared_ptr<C2SurfaceSyncMemory> C2SurfaceSyncMemory::Create(int fd, size_t size) {
    if (fd < 0 || size == 0) {
        return nullptr;
    }
    HandleSyncMem *handle = new HandleSyncMem(fd, size);

    void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (ptr == MAP_FAILED) {
        native_handle_close(handle);
        native_handle_delete(handle);
        return nullptr;
    }
    memset(ptr, 0, size);

    std::shared_ptr<C2SurfaceSyncMemory> syncMem(new C2SurfaceSyncMemory);
    syncMem->mInit = true;
    syncMem->mHandle = handle;
    syncMem->mMem = static_cast<C2SyncVariables*>(ptr);
    return syncMem;
}

native_handle_t *C2SurfaceSyncMemory::handle() {
    return !mInit ? nullptr : mHandle;
}

C2SyncVariables *C2SurfaceSyncMemory::mem() {
    return !mInit ? nullptr : mMem;
}

namespace {
    constexpr int kSpinNumForLock = 100;
    constexpr int kSpinNumForUnlock = 200;

    enum : uint32_t {
        FUTEX_UNLOCKED = 0,
        FUTEX_LOCKED_UNCONTENDED = 1,  // user-space locking
        FUTEX_LOCKED_CONTENDED = 2,    // futex locking
    };
}

int C2SyncVariables::lock() {
    uint32_t old;
    for (int i = 0; i < kSpinNumForLock; i++) {
        old = 0;
        if (mLock.compare_exchange_strong(old, FUTEX_LOCKED_UNCONTENDED)) {
            return 0;
        }
        sched_yield();
    }

    if (old == FUTEX_LOCKED_UNCONTENDED)
        old = mLock.exchange(FUTEX_LOCKED_CONTENDED);

    while (old) {
        (void) syscall(__NR_futex, &mLock, FUTEX_WAIT, FUTEX_LOCKED_CONTENDED, NULL, NULL, 0);
        old = mLock.exchange(FUTEX_LOCKED_CONTENDED);
    }
    return 0;
}

int C2SyncVariables::unlock() {
    if (mLock.exchange(FUTEX_UNLOCKED) == FUTEX_LOCKED_UNCONTENDED) return 0;

    for (int i = 0; i < kSpinNumForUnlock; i++) {
        if (mLock.load()) {
            uint32_t old = FUTEX_LOCKED_UNCONTENDED;
            mLock.compare_exchange_strong(old, FUTEX_LOCKED_CONTENDED);
            if (old) {
                return 0;
            }
        }
        sched_yield();
    }

    (void) syscall(__NR_futex, &mLock, FUTEX_WAKE, 1, NULL, NULL, 0);
    return 0;
}

void C2SyncVariables::setInitialDequeueCount(
        int32_t maxDequeueCount, int32_t curDequeueCount) {
    lock();
    mMaxDequeueCount = maxDequeueCount;
    mCurDequeueCount = curDequeueCount;
    unlock();
}

uint32_t C2SyncVariables::getWaitIdLocked() {
    return mCond.load();
}

bool C2SyncVariables::isDequeueableLocked(uint32_t *waitId) {
    if (mMaxDequeueCount <= mCurDequeueCount) {
        if (waitId) {
            *waitId = getWaitIdLocked();
        }
        return false;
    }
    return true;
}

bool C2SyncVariables::notifyQueuedLocked(uint32_t *waitId) {
    // Note. thundering herds may occur. Edge trigged signalling.
    // But one waiter will guarantee to dequeue. others may wait again.
    // Minimize futex syscall(trap) for the main use case(one waiter case).
    if (mMaxDequeueCount == mCurDequeueCount--) {
        broadcast();
        return true;
    }

    if (mCurDequeueCount >= mMaxDequeueCount) {
        if (waitId) {
            *waitId = getWaitIdLocked();
        }
        ALOGV("dequeue blocked %d/%d", mCurDequeueCount, mMaxDequeueCount);
        return false;
    }
    return true;
}

void C2SyncVariables::notifyDequeuedLocked() {
    mCurDequeueCount++;
    ALOGV("dequeue successful %d/%d", mCurDequeueCount, mMaxDequeueCount);
}

void C2SyncVariables::setSyncStatusLocked(SyncStatus status) {
    mStatus = status;
    if (mStatus == STATUS_ACTIVE) {
        broadcast();
    }
}

C2SyncVariables::SyncStatus C2SyncVariables::getSyncStatusLocked() {
    return mStatus;
}

void C2SyncVariables::updateMaxDequeueCountLocked(int32_t maxDequeueCount) {
    mMaxDequeueCount = maxDequeueCount;
    if (mStatus == STATUS_ACTIVE) {
        broadcast();
    }
}

c2_status_t C2SyncVariables::waitForChange(uint32_t waitId, c2_nsecs_t timeoutNs) {
    if (timeoutNs < 0) {
        timeoutNs = 0;
    }
    struct timespec tv;
    tv.tv_sec = timeoutNs / 1000000000;
    tv.tv_nsec = timeoutNs % 1000000000;

    int ret =  syscall(__NR_futex, &mCond, FUTEX_WAIT, waitId, &tv, NULL, 0);
    if (ret == 0 || ret == EAGAIN) {
        return C2_OK;
    }
    if (ret == EINTR || ret == ETIMEDOUT) {
        return C2_TIMED_OUT;
    }
    return C2_BAD_VALUE;
}

int C2SyncVariables::signal() {
    mCond++;

    (void) syscall(__NR_futex, &mCond, FUTEX_WAKE, 1, NULL, NULL, 0);
    return 0;
}

int C2SyncVariables::broadcast() {
    mCond++;

    (void) syscall(__NR_futex, &mCond, FUTEX_REQUEUE, 1, (void *)INT_MAX, &mLock, 0);
    return 0;
}

int C2SyncVariables::wait() {
    uint32_t old = mCond.load();
    unlock();

    (void) syscall(__NR_futex, &mCond, FUTEX_WAIT, old, NULL, NULL, 0);
    while (mLock.exchange(FUTEX_LOCKED_CONTENDED)) {
        (void) syscall(__NR_futex, &mLock, FUTEX_WAIT, FUTEX_LOCKED_CONTENDED, NULL, NULL, 0);
    }
    return 0;
}