1/*
2 * Copyright 2009-2012, Axel D��rfler, axeld@pinc-software.de.
3 * Copyright 2002, Marcus Overhagen. All Rights Reserved.
4 * Distributed under the terms of the MIT License.
5 */
6
7
8/*!	Used for BBufferGroup and BBuffer management across teams.
9	Created in the media server, cloned into each BBufferGroup (visible in
10	all address spaces).
11*/
12
13// TODO: don't use a simple list!
14
15
16#include <SharedBufferList.h>
17
18#include <string.h>
19
20#include <Autolock.h>
21#include <Buffer.h>
22#include <Locker.h>
23
24#include <MediaDebug.h>
25#include <DataExchange.h>
26
27
28static BPrivate::SharedBufferList* sList = NULL;
29static area_id sArea = -1;
30static int32 sRefCount = 0;
31static BLocker sLocker("shared buffer list");
32
33
34namespace BPrivate {
35
36
37/*static*/ area_id
38SharedBufferList::Create(SharedBufferList** _list)
39{
40	CALLED();
41
42	size_t size = (sizeof(SharedBufferList) + (B_PAGE_SIZE - 1))
43		& ~(B_PAGE_SIZE - 1);
44	SharedBufferList* list;
45
46	area_id area = create_area("shared buffer list", (void**)&list,
47		B_ANY_ADDRESS, size, B_LAZY_LOCK,
48		B_READ_AREA | B_WRITE_AREA | B_CLONEABLE_AREA);
49	if (area < 0)
50		return area;
51
52	status_t status = list->_Init();
53	if (status != B_OK) {
54		delete_area(area);
55		return status;
56	}
57
58	return area;
59}
60
61
62/*static*/ SharedBufferList*
63SharedBufferList::Get()
64{
65	CALLED();
66
67	BAutolock _(sLocker);
68
69	if (atomic_add(&sRefCount, 1) > 0 && sList != NULL)
70		return sList;
71
72	// ask media_server to get the area_id of the shared buffer list
73	server_get_shared_buffer_area_request areaRequest;
74	server_get_shared_buffer_area_reply areaReply;
75	if (QueryServer(SERVER_GET_SHARED_BUFFER_AREA, &areaRequest,
76			sizeof(areaRequest), &areaReply, sizeof(areaReply)) != B_OK) {
77		ERROR("SharedBufferList::Get() SERVER_GET_SHARED_BUFFER_AREA failed\n");
78		return NULL;
79	}
80
81	sArea = clone_area("shared buffer list clone", (void**)&sList,
82		B_ANY_ADDRESS, B_READ_AREA | B_WRITE_AREA, areaReply.area);
83	if (sArea < 0) {
84		ERROR("SharedBufferList::Get() clone area %" B_PRId32 ": %s\n",
85			areaReply.area, strerror(sArea));
86		return NULL;
87	}
88
89	return sList;
90}
91
92
93/*static*/ void
94SharedBufferList::Invalidate()
95{
96	delete_area(sArea);
97	sList = NULL;
98}
99
100
101void
102SharedBufferList::Put()
103{
104	CALLED();
105	BAutolock _(sLocker);
106
107	if (atomic_add(&sRefCount, -1) == 1)
108		Invalidate();
109}
110
111
112/*!	Deletes all BBuffers of the group specified by \a groupReclaimSem, then
113	unmaps the list from memory.
114*/
115void
116SharedBufferList::DeleteGroupAndPut(sem_id groupReclaimSem)
117{
118	CALLED();
119
120	if (Lock() == B_OK) {
121		for (int32 i = 0; i < fCount; i++) {
122			if (fInfos[i].reclaim_sem == groupReclaimSem) {
123				// delete the associated buffer
124				delete fInfos[i].buffer;
125
126				// Decrement buffer count by one, and fill the gap
127				// in the list with its last entry
128				fCount--;
129				if (fCount > 0)
130					fInfos[i--] = fInfos[fCount];
131			}
132		}
133
134		Unlock();
135	}
136
137	Put();
138}
139
140
141status_t
142SharedBufferList::Lock()
143{
144	if (atomic_add(&fAtom, 1) > 0) {
145		status_t status;
146		do {
147			status = acquire_sem(fSemaphore);
148		} while (status == B_INTERRUPTED);
149
150		return status;
151	}
152	return B_OK;
153}
154
155
156status_t
157SharedBufferList::Unlock()
158{
159	if (atomic_add(&fAtom, -1) > 1)
160		return release_sem(fSemaphore);
161
162	return B_OK;
163}
164
165
166status_t
167SharedBufferList::AddBuffer(sem_id groupReclaimSem,
168	const buffer_clone_info& info, BBuffer** _buffer)
169{
170	status_t status = Lock();
171	if (status != B_OK)
172		return status;
173
174	// Check if the id exists
175	status = CheckID(groupReclaimSem, info.buffer);
176	if (status != B_OK) {
177		Unlock();
178		return status;
179	}
180	BBuffer* buffer = new(std::nothrow) BBuffer(info);
181	if (buffer == NULL) {
182		Unlock();
183		return B_NO_MEMORY;
184	}
185
186	if (buffer->Data() == NULL) {
187		// BBuffer::Data() will return NULL if an error occured
188		ERROR("BBufferGroup: error while creating buffer\n");
189		delete buffer;
190		Unlock();
191		return B_ERROR;
192	}
193
194	status = AddBuffer(groupReclaimSem, buffer);
195	if (status != B_OK) {
196		delete buffer;
197		Unlock();
198		return status;
199	} else if (_buffer != NULL)
200		*_buffer = buffer;
201
202	return Unlock();
203}
204
205
206status_t
207SharedBufferList::AddBuffer(sem_id groupReclaimSem, BBuffer* buffer)
208{
209	CALLED();
210
211	if (buffer == NULL)
212		return B_BAD_VALUE;
213
214	if (fCount == kMaxBuffers) {
215		return B_MEDIA_TOO_MANY_BUFFERS;
216	}
217
218	fInfos[fCount].id = buffer->ID();
219	fInfos[fCount].buffer = buffer;
220	fInfos[fCount].reclaim_sem = groupReclaimSem;
221	fInfos[fCount].reclaimed = true;
222	fCount++;
223
224	return release_sem_etc(groupReclaimSem, 1, B_DO_NOT_RESCHEDULE);
225}
226
227
228status_t
229SharedBufferList::CheckID(sem_id groupSem, media_buffer_id id) const
230{
231	CALLED();
232
233	if (id == 0)
234		return B_OK;
235	if (id < 0)
236		return B_BAD_VALUE;
237
238	for (int32 i = 0; i < fCount; i++) {
239		if (fInfos[i].id == id
240			&& fInfos[i].reclaim_sem == groupSem) {
241			return B_ERROR;
242		}
243	}
244	return B_OK;
245}
246
247
248status_t
249SharedBufferList::RequestBuffer(sem_id groupReclaimSem, int32 buffersInGroup,
250	size_t size, media_buffer_id wantID, BBuffer** _buffer, bigtime_t timeout)
251{
252	CALLED();
253	// We always search for a buffer from the group indicated by groupReclaimSem
254	// first.
255	// If "size" != 0, we search for a buffer that is "size" bytes or larger.
256	// If "wantID" != 0, we search for a buffer with this ID.
257	// If "*_buffer" != NULL, we search for a buffer at this address.
258	//
259	// If we found a buffer, we also need to mark it in all other groups as
260	// requested and also once need to acquire the reclaim_sem of the other
261	// groups
262
263	uint32 acquireFlags;
264
265	if (timeout <= 0) {
266		timeout = 0;
267		acquireFlags = B_RELATIVE_TIMEOUT;
268	} else if (timeout == B_INFINITE_TIMEOUT) {
269		acquireFlags = B_RELATIVE_TIMEOUT;
270	} else {
271		timeout += system_time();
272		acquireFlags = B_ABSOLUTE_TIMEOUT;
273	}
274
275	// With each itaration we request one more buffer, since we need to skip
276	// the buffers that don't fit the request
277	int32 count = 1;
278
279	do {
280		status_t status;
281		do {
282			status = acquire_sem_etc(groupReclaimSem, count, acquireFlags,
283				timeout);
284		} while (status == B_INTERRUPTED);
285
286		if (status != B_OK)
287			return status;
288
289		// try to exit savely if the lock fails
290		status = Lock();
291		if (status != B_OK) {
292			ERROR("SharedBufferList:: RequestBuffer: Lock failed: %s\n",
293				strerror(status));
294			release_sem_etc(groupReclaimSem, count, 0);
295			return B_ERROR;
296		}
297
298		for (int32 i = 0; i < fCount; i++) {
299			// We need a BBuffer from the group, and it must be marked as
300			// reclaimed
301			if (fInfos[i].reclaim_sem == groupReclaimSem
302				&& fInfos[i].reclaimed) {
303				if ((size != 0 && size <= fInfos[i].buffer->SizeAvailable())
304					|| (*_buffer != 0 && fInfos[i].buffer == *_buffer)
305					|| (wantID != 0 && fInfos[i].id == wantID)) {
306				   	// we found a buffer
307					fInfos[i].reclaimed = false;
308					*_buffer = fInfos[i].buffer;
309
310					// if we requested more than one buffer, release the rest
311					if (count > 1) {
312						release_sem_etc(groupReclaimSem, count - 1,
313							B_DO_NOT_RESCHEDULE);
314					}
315
316					// And mark all buffers with the same ID as requested in
317					// all other buffer groups
318					_RequestBufferInOtherGroups(groupReclaimSem,
319						fInfos[i].buffer->ID());
320
321					Unlock();
322					return B_OK;
323				}
324			}
325		}
326
327		release_sem_etc(groupReclaimSem, count, B_DO_NOT_RESCHEDULE);
328		if (Unlock() != B_OK) {
329			ERROR("SharedBufferList:: RequestBuffer: unlock failed\n");
330			return B_ERROR;
331		}
332		// prepare to request one more buffer next time
333		count++;
334	} while (count <= buffersInGroup);
335
336	ERROR("SharedBufferList:: RequestBuffer: no buffer found\n");
337	return B_ERROR;
338}
339
340
341status_t
342SharedBufferList::RecycleBuffer(BBuffer* buffer)
343{
344	CALLED();
345
346	media_buffer_id id = buffer->ID();
347
348	if (Lock() != B_OK)
349		return B_ERROR;
350
351	int32 reclaimedCount = 0;
352
353	for (int32 i = 0; i < fCount; i++) {
354		// find the buffer id, and reclaim it in all groups it belongs to
355		if (fInfos[i].id == id) {
356			reclaimedCount++;
357			if (fInfos[i].reclaimed) {
358				ERROR("SharedBufferList::RecycleBuffer, BBuffer %p, id = %"
359					B_PRId32 " already reclaimed\n", buffer, id);
360				DEBUG_ONLY(debugger("buffer already reclaimed"));
361				continue;
362			}
363			fInfos[i].reclaimed = true;
364			release_sem_etc(fInfos[i].reclaim_sem, 1, B_DO_NOT_RESCHEDULE);
365		}
366	}
367
368	if (Unlock() != B_OK)
369		return B_ERROR;
370
371	if (reclaimedCount == 0) {
372		ERROR("shared_buffer_list::RecycleBuffer, BBuffer %p, id = %" B_PRId32
373			" NOT reclaimed\n", buffer, id);
374		return B_ERROR;
375	}
376
377	return B_OK;
378}
379
380
381/*!	Returns exactly \a bufferCount buffers from the group specified via its
382	\a groupReclaimSem if successful.
383*/
384status_t
385SharedBufferList::GetBufferList(sem_id groupReclaimSem, int32 bufferCount,
386	BBuffer** buffers)
387{
388	CALLED();
389
390	if (Lock() != B_OK)
391		return B_ERROR;
392
393	int32 found = 0;
394
395	for (int32 i = 0; i < fCount; i++)
396		if (fInfos[i].reclaim_sem == groupReclaimSem) {
397			buffers[found++] = fInfos[i].buffer;
398			if (found == bufferCount)
399				break;
400		}
401
402	if (Unlock() != B_OK)
403		return B_ERROR;
404
405	return found == bufferCount ? B_OK : B_ERROR;
406}
407
408
409status_t
410SharedBufferList::_Init()
411{
412	CALLED();
413
414	fSemaphore = create_sem(0, "shared buffer list lock");
415	if (fSemaphore < 0)
416		return fSemaphore;
417
418	fAtom = 0;
419
420	for (int32 i = 0; i < kMaxBuffers; i++) {
421		fInfos[i].id = -1;
422	}
423	fCount = 0;
424
425	return B_OK;
426}
427
428
429/*!	Used by RequestBuffer, call this one with the list locked!
430*/
431void
432SharedBufferList::_RequestBufferInOtherGroups(sem_id groupReclaimSem,
433	media_buffer_id id)
434{
435	for (int32 i = 0; i < fCount; i++) {
436		// find buffers with same id, but belonging to other groups
437		if (fInfos[i].id == id && fInfos[i].reclaim_sem != groupReclaimSem) {
438			// and mark them as requested
439			// TODO: this can deadlock if BBuffers with same media_buffer_id
440			// exist in more than one BBufferGroup, and RequestBuffer()
441			// is called on both groups (which should not be done).
442			status_t status;
443			do {
444				status = acquire_sem(fInfos[i].reclaim_sem);
445			} while (status == B_INTERRUPTED);
446
447			// try to skip entries that belong to crashed teams
448			if (status != B_OK)
449				continue;
450
451			if (fInfos[i].reclaimed == false) {
452				ERROR("SharedBufferList:: RequestBufferInOtherGroups BBuffer "
453					"%p, id = %" B_PRId32 " not reclaimed while requesting\n",
454					fInfos[i].buffer, id);
455				continue;
456			}
457
458			fInfos[i].reclaimed = false;
459		}
460	}
461}
462
463
464}	// namespace BPrivate
465