⛏️ index : haiku.git

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