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 <debug.h>
25#include <DataExchange.h>
26
27
28static BPrivate::SharedBufferList* sList;
29static area_id sArea;
30static int32 sRefCount;
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, BBuffer* buffer)
167{
168	CALLED();
169
170	if (buffer == NULL)
171		return B_BAD_VALUE;
172
173	status_t status = Lock();
174	if (status != B_OK)
175		return status;
176
177	if (fCount == kMaxBuffers) {
178		Unlock();
179		return B_MEDIA_TOO_MANY_BUFFERS;
180	}
181
182	fInfos[fCount].id = buffer->ID();
183	fInfos[fCount].buffer = buffer;
184	fInfos[fCount].reclaim_sem = groupReclaimSem;
185	fInfos[fCount].reclaimed = true;
186	fCount++;
187
188	status = release_sem_etc(groupReclaimSem, 1, B_DO_NOT_RESCHEDULE);
189	if (status != B_OK)
190		return status;
191
192	return Unlock();
193}
194
195
196status_t
197SharedBufferList::RequestBuffer(sem_id groupReclaimSem, int32 buffersInGroup,
198	size_t size, media_buffer_id wantID, BBuffer** _buffer, bigtime_t timeout)
199{
200	CALLED();
201	// We always search for a buffer from the group indicated by groupReclaimSem
202	// first.
203	// If "size" != 0, we search for a buffer that is "size" bytes or larger.
204	// If "wantID" != 0, we search for a buffer with this ID.
205	// If "*_buffer" != NULL, we search for a buffer at this address.
206	//
207	// If we found a buffer, we also need to mark it in all other groups as
208	// requested and also once need to acquire the reclaim_sem of the other
209	// groups
210
211	uint32 acquireFlags;
212
213	if (timeout <= 0) {
214		timeout = 0;
215		acquireFlags = B_RELATIVE_TIMEOUT;
216	} else if (timeout == B_INFINITE_TIMEOUT) {
217		acquireFlags = B_RELATIVE_TIMEOUT;
218	} else {
219		timeout += system_time();
220		acquireFlags = B_ABSOLUTE_TIMEOUT;
221	}
222
223	// With each itaration we request one more buffer, since we need to skip
224	// the buffers that don't fit the request
225	int32 count = 1;
226
227	do {
228		status_t status;
229		do {
230			status = acquire_sem_etc(groupReclaimSem, count, acquireFlags,
231				timeout);
232		} while (status == B_INTERRUPTED);
233
234		if (status != B_OK)
235			return status;
236
237		// try to exit savely if the lock fails
238		status = Lock();
239		if (status != B_OK) {
240			ERROR("SharedBufferList:: RequestBuffer: Lock failed: %s\n",
241				strerror(status));
242			release_sem_etc(groupReclaimSem, count, 0);
243			return B_ERROR;
244		}
245
246		for (int32 i = 0; i < fCount; i++) {
247			// We need a BBuffer from the group, and it must be marked as
248			// reclaimed
249			if (fInfos[i].reclaim_sem == groupReclaimSem
250				&& fInfos[i].reclaimed) {
251				if ((size != 0 && size <= fInfos[i].buffer->SizeAvailable())
252					|| (*_buffer != 0 && fInfos[i].buffer == *_buffer)
253					|| (wantID != 0 && fInfos[i].id == wantID)) {
254				   	// we found a buffer
255					fInfos[i].reclaimed = false;
256					*_buffer = fInfos[i].buffer;
257
258					// if we requested more than one buffer, release the rest
259					if (count > 1) {
260						release_sem_etc(groupReclaimSem, count - 1,
261							B_DO_NOT_RESCHEDULE);
262					}
263
264					// And mark all buffers with the same ID as requested in
265					// all other buffer groups
266					_RequestBufferInOtherGroups(groupReclaimSem,
267						fInfos[i].buffer->ID());
268
269					Unlock();
270					return B_OK;
271				}
272			}
273		}
274
275		release_sem_etc(groupReclaimSem, count, B_DO_NOT_RESCHEDULE);
276		if (Unlock() != B_OK) {
277			ERROR("SharedBufferList:: RequestBuffer: unlock failed\n");
278			return B_ERROR;
279		}
280		// prepare to request one more buffer next time
281		count++;
282	} while (count <= buffersInGroup);
283
284	ERROR("SharedBufferList:: RequestBuffer: no buffer found\n");
285	return B_ERROR;
286}
287
288
289status_t
290SharedBufferList::RecycleBuffer(BBuffer* buffer)
291{
292	CALLED();
293
294	media_buffer_id id = buffer->ID();
295
296	if (Lock() != B_OK)
297		return B_ERROR;
298
299	int32 reclaimedCount = 0;
300
301	for (int32 i = 0; i < fCount; i++) {
302		// find the buffer id, and reclaim it in all groups it belongs to
303		if (fInfos[i].id == id) {
304			reclaimedCount++;
305			if (fInfos[i].reclaimed) {
306				ERROR("SharedBufferList::RecycleBuffer, BBuffer %p, id = %"
307					B_PRId32 " already reclaimed\n", buffer, id);
308				DEBUG_ONLY(debugger("buffer already reclaimed"));
309				continue;
310			}
311			fInfos[i].reclaimed = true;
312			release_sem_etc(fInfos[i].reclaim_sem, 1, B_DO_NOT_RESCHEDULE);
313		}
314	}
315
316	if (Unlock() != B_OK)
317		return B_ERROR;
318
319	if (reclaimedCount == 0) {
320		ERROR("shared_buffer_list::RecycleBuffer, BBuffer %p, id = %" B_PRId32
321			" NOT reclaimed\n", buffer, id);
322		return B_ERROR;
323	}
324
325	return B_OK;
326}
327
328
329/*!	Returns exactly \a bufferCount buffers from the group specified via its
330	\a groupReclaimSem if successful.
331*/
332status_t
333SharedBufferList::GetBufferList(sem_id groupReclaimSem, int32 bufferCount,
334	BBuffer** buffers)
335{
336	CALLED();
337
338	if (Lock() != B_OK)
339		return B_ERROR;
340
341	int32 found = 0;
342
343	for (int32 i = 0; i < fCount; i++)
344		if (fInfos[i].reclaim_sem == groupReclaimSem) {
345			buffers[found++] = fInfos[i].buffer;
346			if (found == bufferCount)
347				break;
348		}
349
350	if (Unlock() != B_OK)
351		return B_ERROR;
352
353	return found == bufferCount ? B_OK : B_ERROR;
354}
355
356
357status_t
358SharedBufferList::_Init()
359{
360	CALLED();
361
362	fSemaphore = create_sem(0, "shared buffer list lock");
363	if (fSemaphore < 0)
364		return fSemaphore;
365
366	fAtom = 0;
367
368	for (int32 i = 0; i < kMaxBuffers; i++) {
369		fInfos[i].id = -1;
370	}
371
372	return B_OK;
373}
374
375
376/*!	Used by RequestBuffer, call this one with the list locked!
377*/
378void
379SharedBufferList::_RequestBufferInOtherGroups(sem_id groupReclaimSem,
380	media_buffer_id id)
381{
382	for (int32 i = 0; i < fCount; i++) {
383		// find buffers with same id, but belonging to other groups
384		if (fInfos[i].id == id && fInfos[i].reclaim_sem != groupReclaimSem) {
385			// and mark them as requested
386			// TODO: this can deadlock if BBuffers with same media_buffer_id
387			// exist in more than one BBufferGroup, and RequestBuffer()
388			// is called on both groups (which should not be done).
389			status_t status;
390			do {
391				status = acquire_sem(fInfos[i].reclaim_sem);
392			} while (status == B_INTERRUPTED);
393
394			// try to skip entries that belong to crashed teams
395			if (status != B_OK)
396				continue;
397
398			if (fInfos[i].reclaimed == false) {
399				ERROR("SharedBufferList:: RequestBufferInOtherGroups BBuffer "
400					"%p, id = %" B_PRId32 " not reclaimed while requesting\n",
401					fInfos[i].buffer, id);
402				continue;
403			}
404
405			fInfos[i].reclaimed = false;
406		}
407	}
408}
409
410
411}	// namespace BPrivate
412