xref: /haiku/src/kits/media/SharedBufferList.cpp (revision 1e60bdeab63fa7a57bc9a55b032052e95a18bd2c)
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, 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*
62 SharedBufferList::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
93 SharedBufferList::Invalidate()
94 {
95 	delete_area(sArea);
96 	sList = NULL;
97 }
98 
99 
100 void
101 SharedBufferList::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 */
114 void
115 SharedBufferList::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 
140 status_t
141 SharedBufferList::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 
155 status_t
156 SharedBufferList::Unlock()
157 {
158 	if (atomic_add(&fAtom, -1) > 1)
159 		return release_sem(fSemaphore);
160 
161 	return B_OK;
162 }
163 
164 
165 status_t
166 SharedBufferList::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 
205 status_t
206 SharedBufferList::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 
227 status_t
228 SharedBufferList::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 
247 status_t
248 SharedBufferList::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 
340 status_t
341 SharedBufferList::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 */
383 status_t
384 SharedBufferList::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 
408 status_t
409 SharedBufferList::_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 */
430 void
431 SharedBufferList::_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