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