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