haiku/src/kits/media/SharedBufferList.cpp

507 lines
11 KiB
C++

/*
* 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