root/src/kits/media/SharedBufferList.cpp
/*
 * Copyright 2009-2012, Axel Dörfler, axeld@pinc-software.de.
 * Copyright 2002, Marcus Overhagen. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 */


/*!     Used for BBufferGroup and BBuffer management across teams.
        Created in the media server, cloned into each BBufferGroup (visible in
        all address spaces).
*/

// TODO: don't use a simple list!


#include <SharedBufferList.h>

#include <string.h>

#include <Autolock.h>
#include <Buffer.h>
#include <Locker.h>

#include <MediaDebug.h>
#include <DataExchange.h>


static BPrivate::SharedBufferList* sList = NULL;
static area_id sArea = -1;
static int32 sRefCount = 0;
static BLocker sLocker("shared buffer list");


namespace BPrivate {


/*static*/ area_id
SharedBufferList::Create(SharedBufferList** _list)
{
        CALLED();

        size_t size = (sizeof(SharedBufferList) + (B_PAGE_SIZE - 1))
                & ~(B_PAGE_SIZE - 1);
        SharedBufferList* list;

        area_id area = create_area("shared buffer list", (void**)&list,
                B_ANY_ADDRESS, size, B_LAZY_LOCK,
                B_READ_AREA | B_WRITE_AREA | B_CLONEABLE_AREA);
        if (area < 0)
                return area;

        status_t status = list->_Init();
        if (status != B_OK) {
                delete_area(area);
                return status;
        }

        return area;
}


/*static*/ SharedBufferList*
SharedBufferList::Get()
{
        CALLED();

        BAutolock _(sLocker);

        if (atomic_add(&sRefCount, 1) > 0 && sList != NULL)
                return sList;

        // ask media_server to get the area_id of the shared buffer list
        server_get_shared_buffer_area_request areaRequest;
        server_get_shared_buffer_area_reply areaReply;
        if (QueryServer(SERVER_GET_SHARED_BUFFER_AREA, &areaRequest,
                        sizeof(areaRequest), &areaReply, sizeof(areaReply)) != B_OK) {
                ERROR("SharedBufferList::Get() SERVER_GET_SHARED_BUFFER_AREA failed\n");
                return NULL;
        }

        sArea = clone_area("shared buffer list clone", (void**)&sList,
                B_ANY_ADDRESS, B_READ_AREA | B_WRITE_AREA, areaReply.area);
        if (sArea < 0) {
                ERROR("SharedBufferList::Get() clone area %" B_PRId32 ": %s\n",
                        areaReply.area, strerror(sArea));
                return NULL;
        }

        return sList;
}


/*static*/ void
SharedBufferList::Invalidate()
{
        delete_area(sArea);
        sList = NULL;
}


void
SharedBufferList::Put()
{
        CALLED();
        BAutolock _(sLocker);

        if (atomic_add(&sRefCount, -1) == 1)
                Invalidate();
}


/*!     Deletes all BBuffers of the group specified by \a groupReclaimSem, then
        unmaps the list from memory.
*/
void
SharedBufferList::DeleteGroupAndPut(sem_id groupReclaimSem)
{
        CALLED();

        if (Lock() == B_OK) {
                for (int32 i = 0; i < fCount; i++) {
                        if (fInfos[i].reclaim_sem == groupReclaimSem) {
                                // delete the associated buffer
                                delete fInfos[i].buffer;

                                // Decrement buffer count by one, and fill the gap
                                // in the list with its last entry
                                fCount--;
                                if (fCount > 0)
                                        fInfos[i--] = fInfos[fCount];
                        }
                }

                Unlock();
        }

        Put();
}


status_t
SharedBufferList::Lock()
{
        if (atomic_add(&fAtom, 1) > 0) {
                status_t status;
                do {
                        status = acquire_sem(fSemaphore);
                } while (status == B_INTERRUPTED);

                return status;
        }
        return B_OK;
}


status_t
SharedBufferList::Unlock()
{
        if (atomic_add(&fAtom, -1) > 1)
                return release_sem(fSemaphore);

        return B_OK;
}


status_t
SharedBufferList::AddBuffer(sem_id groupReclaimSem,
        const buffer_clone_info& info, BBuffer** _buffer)
{
        status_t status = Lock();
        if (status != B_OK)
                return status;

        // Check if the id exists
        status = CheckID(groupReclaimSem, info.buffer);
        if (status != B_OK) {
                Unlock();
                return status;
        }
        BBuffer* buffer = new(std::nothrow) BBuffer(info);
        if (buffer == NULL) {
                Unlock();
                return B_NO_MEMORY;
        }

        if (buffer->Data() == NULL) {
                // BBuffer::Data() will return NULL if an error occured
                ERROR("BBufferGroup: error while creating buffer\n");
                delete buffer;
                Unlock();
                return B_ERROR;
        }

        status = AddBuffer(groupReclaimSem, buffer);
        if (status != B_OK) {
                delete buffer;
                Unlock();
                return status;
        } else if (_buffer != NULL)
                *_buffer = buffer;

        return Unlock();
}


status_t
SharedBufferList::AddBuffer(sem_id groupReclaimSem, BBuffer* buffer)
{
        CALLED();

        if (buffer == NULL)
                return B_BAD_VALUE;

        if (fCount == kMaxBuffers) {
                return B_MEDIA_TOO_MANY_BUFFERS;
        }

        fInfos[fCount].id = buffer->ID();
        fInfos[fCount].buffer = buffer;
        fInfos[fCount].reclaim_sem = groupReclaimSem;
        fInfos[fCount].reclaimed = true;
        fCount++;

        return release_sem_etc(groupReclaimSem, 1, B_DO_NOT_RESCHEDULE);
}


status_t
SharedBufferList::CheckID(sem_id groupSem, media_buffer_id id) const
{
        CALLED();

        if (id == 0)
                return B_OK;
        if (id < 0)
                return B_BAD_VALUE;

        for (int32 i = 0; i < fCount; i++) {
                if (fInfos[i].id == id
                        && fInfos[i].reclaim_sem == groupSem) {
                        return B_ERROR;
                }
        }
        return B_OK;
}


status_t
SharedBufferList::RequestBuffer(sem_id groupReclaimSem, int32 buffersInGroup,
        size_t size, media_buffer_id wantID, BBuffer** _buffer, bigtime_t timeout)
{
        CALLED();
        // We always search for a buffer from the group indicated by groupReclaimSem
        // first.
        // If "size" != 0, we search for a buffer that is "size" bytes or larger.
        // If "wantID" != 0, we search for a buffer with this ID.
        // If "*_buffer" != NULL, we search for a buffer at this address.
        //
        // If we found a buffer, we also need to mark it in all other groups as
        // requested and also once need to acquire the reclaim_sem of the other
        // groups

        uint32 acquireFlags;

        if (timeout <= 0) {
                timeout = 0;
                acquireFlags = B_RELATIVE_TIMEOUT;
        } else if (timeout == B_INFINITE_TIMEOUT) {
                acquireFlags = B_RELATIVE_TIMEOUT;
        } else {
                timeout += system_time();
                acquireFlags = B_ABSOLUTE_TIMEOUT;
        }

        // With each itaration we request one more buffer, since we need to skip
        // the buffers that don't fit the request
        int32 count = 1;

        do {
                status_t status;
                do {
                        status = acquire_sem_etc(groupReclaimSem, count, acquireFlags,
                                timeout);
                } while (status == B_INTERRUPTED);

                if (status != B_OK)
                        return status;

                // try to exit savely if the lock fails
                status = Lock();
                if (status != B_OK) {
                        ERROR("SharedBufferList:: RequestBuffer: Lock failed: %s\n",
                                strerror(status));
                        release_sem_etc(groupReclaimSem, count, 0);
                        return B_ERROR;
                }

                for (int32 i = 0; i < fCount; i++) {
                        // We need a BBuffer from the group, and it must be marked as
                        // reclaimed
                        if (fInfos[i].reclaim_sem == groupReclaimSem
                                && fInfos[i].reclaimed) {
                                if ((size != 0 && size <= fInfos[i].buffer->SizeAvailable())
                                        || (*_buffer != 0 && fInfos[i].buffer == *_buffer)
                                        || (wantID != 0 && fInfos[i].id == wantID)) {
                                        // we found a buffer
                                        fInfos[i].reclaimed = false;
                                        *_buffer = fInfos[i].buffer;

                                        // if we requested more than one buffer, release the rest
                                        if (count > 1) {
                                                release_sem_etc(groupReclaimSem, count - 1,
                                                        B_DO_NOT_RESCHEDULE);
                                        }

                                        // And mark all buffers with the same ID as requested in
                                        // all other buffer groups
                                        _RequestBufferInOtherGroups(groupReclaimSem,
                                                fInfos[i].buffer->ID());

                                        Unlock();
                                        return B_OK;
                                }
                        }
                }

                release_sem_etc(groupReclaimSem, count, B_DO_NOT_RESCHEDULE);
                if (Unlock() != B_OK) {
                        ERROR("SharedBufferList:: RequestBuffer: unlock failed\n");
                        return B_ERROR;
                }
                // prepare to request one more buffer next time
                count++;
        } while (count <= buffersInGroup);

        ERROR("SharedBufferList:: RequestBuffer: no buffer found\n");
        return B_ERROR;
}


status_t
SharedBufferList::RecycleBuffer(BBuffer* buffer)
{
        CALLED();

        media_buffer_id id = buffer->ID();

        if (Lock() != B_OK)
                return B_ERROR;

        int32 reclaimedCount = 0;

        for (int32 i = 0; i < fCount; i++) {
                // find the buffer id, and reclaim it in all groups it belongs to
                if (fInfos[i].id == id) {
                        reclaimedCount++;
                        if (fInfos[i].reclaimed) {
                                ERROR("SharedBufferList::RecycleBuffer, BBuffer %p, id = %"
                                        B_PRId32 " already reclaimed\n", buffer, id);
                                DEBUG_ONLY(debugger("buffer already reclaimed"));
                                continue;
                        }
                        fInfos[i].reclaimed = true;
                        release_sem_etc(fInfos[i].reclaim_sem, 1, B_DO_NOT_RESCHEDULE);
                }
        }

        if (Unlock() != B_OK)
                return B_ERROR;

        if (reclaimedCount == 0) {
                ERROR("shared_buffer_list::RecycleBuffer, BBuffer %p, id = %" B_PRId32
                        " NOT reclaimed\n", buffer, id);
                return B_ERROR;
        }

        return B_OK;
}


status_t
SharedBufferList::RemoveBuffer(BBuffer* buffer)
{
        CALLED();

        media_buffer_id id = buffer->ID();

        if (Lock() != B_OK)
                return B_ERROR;

        int32 notRemovedCount = 0;

        for (int32 i = 0; i < fCount; i++) {
                // find the buffer id, and remove it in all groups it belongs to
                if (fInfos[i].id == id) {
                        if (!fInfos[i].reclaimed) {
                                notRemovedCount++;
                                ERROR("SharedBufferList::RequestBuffer, BBuffer %p, id = %"
                                        B_PRId32 " not reclaimed\n", buffer, id);
                                DEBUG_ONLY(debugger("buffer not reclaimed"));
                                continue;
                        }
                        fInfos[i].buffer = NULL;
                        fInfos[i].id = -1;
                        fInfos[i].reclaim_sem = -1;
                }
        }

        if (Unlock() != B_OK)
                return B_ERROR;

        if (notRemovedCount != 0) {
                ERROR("SharedBufferList::RemoveBuffer, BBuffer %p, id = %" B_PRId32
                        " not reclaimed\n", buffer, id);
                return B_ERROR;
        }

        return B_OK;
}



/*!     Returns exactly \a bufferCount buffers from the group specified via its
        \a groupReclaimSem if successful.
*/
status_t
SharedBufferList::GetBufferList(sem_id groupReclaimSem, int32 bufferCount,
        BBuffer** buffers)
{
        CALLED();

        if (Lock() != B_OK)
                return B_ERROR;

        int32 found = 0;

        for (int32 i = 0; i < fCount; i++)
                if (fInfos[i].reclaim_sem == groupReclaimSem) {
                        buffers[found++] = fInfos[i].buffer;
                        if (found == bufferCount)
                                break;
                }

        if (Unlock() != B_OK)
                return B_ERROR;

        return found == bufferCount ? B_OK : B_ERROR;
}


status_t
SharedBufferList::_Init()
{
        CALLED();

        fSemaphore = create_sem(0, "shared buffer list lock");
        if (fSemaphore < 0)
                return fSemaphore;

        fAtom = 0;

        for (int32 i = 0; i < kMaxBuffers; i++) {
                fInfos[i].id = -1;
        }
        fCount = 0;

        return B_OK;
}


/*!     Used by RequestBuffer, call this one with the list locked!
*/
void
SharedBufferList::_RequestBufferInOtherGroups(sem_id groupReclaimSem,
        media_buffer_id id)
{
        for (int32 i = 0; i < fCount; i++) {
                // find buffers with same id, but belonging to other groups
                if (fInfos[i].id == id && fInfos[i].reclaim_sem != groupReclaimSem) {
                        // and mark them as requested
                        // TODO: this can deadlock if BBuffers with same media_buffer_id
                        // exist in more than one BBufferGroup, and RequestBuffer()
                        // is called on both groups (which should not be done).
                        status_t status;
                        do {
                                status = acquire_sem(fInfos[i].reclaim_sem);
                        } while (status == B_INTERRUPTED);

                        // try to skip entries that belong to crashed teams
                        if (status != B_OK)
                                continue;

                        if (fInfos[i].reclaimed == false) {
                                ERROR("SharedBufferList:: RequestBufferInOtherGroups BBuffer "
                                        "%p, id = %" B_PRId32 " not reclaimed while requesting\n",
                                        fInfos[i].buffer, id);
                                continue;
                        }

                        fInfos[i].reclaimed = false;
                }
        }
}


}       // namespace BPrivate