xref: /haiku/src/kits/media/SharedBufferList.cpp (revision 83b1a68c52ba3e0e8796282759f694b7fdddf06d)
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 
28 static BPrivate::SharedBufferList* sList;
29 static area_id sArea;
30 static int32 sRefCount;
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, 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 
196 status_t
197 SharedBufferList::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 
289 status_t
290 SharedBufferList::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 */
332 status_t
333 SharedBufferList::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 
357 status_t
358 SharedBufferList::_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 */
378 void
379 SharedBufferList::_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