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