xref: /haiku/src/kits/media/SharedBufferList.cpp (revision 68ea01249e1e2088933cb12f9c28d4e5c5d1c9ef)
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 
28 static BPrivate::SharedBufferList* sList = NULL;
29 static area_id sArea = -1;
30 static int32 sRefCount = 0;
31 static BLocker sLocker("shared buffer list");
32 
33 
34 namespace BPrivate {
35 
36 
37 /*static*/ area_id
38 SharedBufferList::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*
63 SharedBufferList::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
94 SharedBufferList::Invalidate()
95 {
96 	delete_area(sArea);
97 	sList = NULL;
98 }
99 
100 
101 void
102 SharedBufferList::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 */
115 void
116 SharedBufferList::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 
141 status_t
142 SharedBufferList::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 
156 status_t
157 SharedBufferList::Unlock()
158 {
159 	if (atomic_add(&fAtom, -1) > 1)
160 		return release_sem(fSemaphore);
161 
162 	return B_OK;
163 }
164 
165 
166 status_t
167 SharedBufferList::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 
206 status_t
207 SharedBufferList::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 
228 status_t
229 SharedBufferList::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 
248 status_t
249 SharedBufferList::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 
341 status_t
342 SharedBufferList::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 status_t
382 SharedBufferList::RemoveBuffer(BBuffer* buffer)
383 {
384 	CALLED();
385 
386 	media_buffer_id id = buffer->ID();
387 
388 	if (Lock() != B_OK)
389 		return B_ERROR;
390 
391 	int32 notRemovedCount = 0;
392 
393 	for (int32 i = 0; i < fCount; i++) {
394 		// find the buffer id, and remove it in all groups it belongs to
395 		if (fInfos[i].id == id) {
396 			if (!fInfos[i].reclaimed) {
397 				notRemovedCount++;
398 				ERROR("SharedBufferList::RequestBuffer, BBuffer %p, id = %"
399 					B_PRId32 " not reclaimed\n", buffer, id);
400 				DEBUG_ONLY(debugger("buffer not reclaimed"));
401 				continue;
402 			}
403 			fInfos[i].buffer = NULL;
404 			fInfos[i].id = -1;
405 			fInfos[i].reclaim_sem = -1;
406 		}
407 	}
408 
409 	if (Unlock() != B_OK)
410 		return B_ERROR;
411 
412 	if (notRemovedCount != 0) {
413 		ERROR("SharedBufferList::RemoveBuffer, BBuffer %p, id = %" B_PRId32
414 			" not reclaimed\n", buffer, id);
415 		return B_ERROR;
416 	}
417 
418 	return B_OK;
419 }
420 
421 
422 
423 /*!	Returns exactly \a bufferCount buffers from the group specified via its
424 	\a groupReclaimSem if successful.
425 */
426 status_t
427 SharedBufferList::GetBufferList(sem_id groupReclaimSem, int32 bufferCount,
428 	BBuffer** buffers)
429 {
430 	CALLED();
431 
432 	if (Lock() != B_OK)
433 		return B_ERROR;
434 
435 	int32 found = 0;
436 
437 	for (int32 i = 0; i < fCount; i++)
438 		if (fInfos[i].reclaim_sem == groupReclaimSem) {
439 			buffers[found++] = fInfos[i].buffer;
440 			if (found == bufferCount)
441 				break;
442 		}
443 
444 	if (Unlock() != B_OK)
445 		return B_ERROR;
446 
447 	return found == bufferCount ? B_OK : B_ERROR;
448 }
449 
450 
451 status_t
452 SharedBufferList::_Init()
453 {
454 	CALLED();
455 
456 	fSemaphore = create_sem(0, "shared buffer list lock");
457 	if (fSemaphore < 0)
458 		return fSemaphore;
459 
460 	fAtom = 0;
461 
462 	for (int32 i = 0; i < kMaxBuffers; i++) {
463 		fInfos[i].id = -1;
464 	}
465 	fCount = 0;
466 
467 	return B_OK;
468 }
469 
470 
471 /*!	Used by RequestBuffer, call this one with the list locked!
472 */
473 void
474 SharedBufferList::_RequestBufferInOtherGroups(sem_id groupReclaimSem,
475 	media_buffer_id id)
476 {
477 	for (int32 i = 0; i < fCount; i++) {
478 		// find buffers with same id, but belonging to other groups
479 		if (fInfos[i].id == id && fInfos[i].reclaim_sem != groupReclaimSem) {
480 			// and mark them as requested
481 			// TODO: this can deadlock if BBuffers with same media_buffer_id
482 			// exist in more than one BBufferGroup, and RequestBuffer()
483 			// is called on both groups (which should not be done).
484 			status_t status;
485 			do {
486 				status = acquire_sem(fInfos[i].reclaim_sem);
487 			} while (status == B_INTERRUPTED);
488 
489 			// try to skip entries that belong to crashed teams
490 			if (status != B_OK)
491 				continue;
492 
493 			if (fInfos[i].reclaimed == false) {
494 				ERROR("SharedBufferList:: RequestBufferInOtherGroups BBuffer "
495 					"%p, id = %" B_PRId32 " not reclaimed while requesting\n",
496 					fInfos[i].buffer, id);
497 				continue;
498 			}
499 
500 			fInfos[i].reclaimed = false;
501 		}
502 	}
503 }
504 
505 
506 }	// namespace BPrivate
507