1 /* 2 * Copyright 2005-2008, Ingo Weinhold, ingo_weinhold@gmx.de. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 // kernel-side implementation of the messaging service 7 8 #include <new> 9 10 #include <AutoDeleter.h> 11 #include <KernelExport.h> 12 #include <KMessage.h> 13 #include <messaging.h> 14 #include <MessagingServiceDefs.h> 15 16 #include "MessagingService.h" 17 18 //#define TRACE_MESSAGING_SERVICE 19 #ifdef TRACE_MESSAGING_SERVICE 20 # define PRINT(x) dprintf x 21 #else 22 # define PRINT(x) ; 23 #endif 24 25 26 using namespace std; 27 28 static MessagingService *sMessagingService = NULL; 29 30 static const int32 kMessagingAreaSize = B_PAGE_SIZE * 4; 31 32 33 // #pragma mark - MessagingArea 34 35 36 // constructor 37 MessagingArea::MessagingArea() 38 { 39 } 40 41 42 // destructor 43 MessagingArea::~MessagingArea() 44 { 45 if (fID >= 0) 46 delete_area(fID); 47 } 48 49 50 // Create 51 MessagingArea * 52 MessagingArea::Create(sem_id lockSem, sem_id counterSem) 53 { 54 // allocate the object on the heap 55 MessagingArea *area = new(nothrow) MessagingArea; 56 if (!area) 57 return NULL; 58 59 // create the area 60 area->fID = create_area("messaging", (void**)&area->fHeader, 61 B_ANY_KERNEL_ADDRESS, kMessagingAreaSize, B_FULL_LOCK, 62 B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA | B_USER_CLONEABLE_AREA); 63 if (area->fID < 0) { 64 delete area; 65 return NULL; 66 } 67 68 // finish the initialization of the object 69 area->fSize = kMessagingAreaSize; 70 area->fLockSem = lockSem; 71 area->fCounterSem = counterSem; 72 area->fNextArea = NULL; 73 area->InitHeader(); 74 75 return area; 76 } 77 78 79 // InitHeader 80 void 81 MessagingArea::InitHeader() 82 { 83 fHeader->lock_counter = 1; // create locked 84 fHeader->size = fSize; 85 fHeader->kernel_area = fID; 86 fHeader->next_kernel_area = (fNextArea ? fNextArea->ID() : -1); 87 fHeader->command_count = 0; 88 fHeader->first_command = 0; 89 fHeader->last_command = 0; 90 } 91 92 93 // CheckCommandSize 94 bool 95 MessagingArea::CheckCommandSize(int32 dataSize) 96 { 97 int32 size = sizeof(messaging_command) + dataSize; 98 99 return (dataSize >= 0 100 && size <= kMessagingAreaSize - (int32)sizeof(messaging_area_header)); 101 } 102 103 104 // Lock 105 bool 106 MessagingArea::Lock() 107 { 108 // benaphore-like locking 109 if (atomic_add(&fHeader->lock_counter, 1) == 0) 110 return true; 111 112 return (acquire_sem(fLockSem) == B_OK); 113 } 114 115 116 // Unlock 117 void 118 MessagingArea::Unlock() 119 { 120 if (atomic_add(&fHeader->lock_counter, -1) > 1) 121 release_sem(fLockSem); 122 } 123 124 125 // ID 126 area_id 127 MessagingArea::ID() const 128 { 129 return fID; 130 } 131 132 133 // Size 134 int32 135 MessagingArea::Size() const 136 { 137 return fSize; 138 } 139 140 141 // AllocateCommand 142 void * 143 MessagingArea::AllocateCommand(uint32 commandWhat, int32 dataSize, 144 bool &wasEmpty) 145 { 146 int32 size = sizeof(messaging_command) + dataSize; 147 148 if (dataSize < 0 || size > fSize - (int32)sizeof(messaging_area_header)) 149 return NULL; 150 151 // the area is used as a ring buffer 152 int32 startOffset = sizeof(messaging_area_header); 153 154 // the simple case first: the area is empty 155 int32 commandOffset; 156 wasEmpty = (fHeader->command_count == 0); 157 if (wasEmpty) { 158 commandOffset = startOffset; 159 160 // update the header 161 fHeader->command_count++; 162 fHeader->first_command = fHeader->last_command = commandOffset; 163 } else { 164 int32 firstCommandOffset = fHeader->first_command; 165 int32 lastCommandOffset = fHeader->last_command; 166 int32 firstCommandSize; 167 int32 lastCommandSize; 168 messaging_command *firstCommand = _CheckCommand(firstCommandOffset, 169 firstCommandSize); 170 messaging_command *lastCommand = _CheckCommand(lastCommandOffset, 171 lastCommandSize); 172 if (!firstCommand || !lastCommand) { 173 // something has been screwed up 174 return NULL; 175 } 176 177 // find space for the command 178 if (firstCommandOffset < lastCommandOffset) { 179 // not wrapped 180 // try to allocate after the last command 181 if (size <= fSize - (lastCommandOffset + lastCommandSize)) { 182 commandOffset = (lastCommandOffset + lastCommandSize); 183 } else { 184 // is there enough space before the first command? 185 if (size > firstCommandOffset - startOffset) 186 return NULL; 187 commandOffset = startOffset; 188 } 189 } else { 190 // wrapped: we can only allocate between the last and the first 191 // command 192 commandOffset = lastCommandOffset + lastCommandSize; 193 if (size > firstCommandOffset - commandOffset) 194 return NULL; 195 } 196 197 // update the header and the last command 198 fHeader->command_count++; 199 lastCommand->next_command = fHeader->last_command = commandOffset; 200 } 201 202 // init the command 203 messaging_command *command 204 = (messaging_command*)((char*)fHeader + commandOffset); 205 command->next_command = 0; 206 command->command = commandWhat; 207 command->size = size; 208 209 return command->data; 210 } 211 212 213 // CommitCommand 214 void 215 MessagingArea::CommitCommand() 216 { 217 // TODO: If invoked while locked, we should supply B_DO_NOT_RESCHEDULE. 218 release_sem(fCounterSem); 219 } 220 221 222 // SetNextArea 223 void 224 MessagingArea::SetNextArea(MessagingArea *area) 225 { 226 fNextArea = area; 227 fHeader->next_kernel_area = (fNextArea ? fNextArea->ID() : -1); 228 } 229 230 231 // NextArea 232 MessagingArea * 233 MessagingArea::NextArea() const 234 { 235 return fNextArea; 236 } 237 238 239 // _CheckCommand 240 messaging_command * 241 MessagingArea::_CheckCommand(int32 offset, int32 &size) 242 { 243 // check offset 244 if (offset < (int32)sizeof(messaging_area_header) 245 || offset + (int32)sizeof(messaging_command) > fSize 246 || (offset & 0x3)) { 247 return NULL; 248 } 249 250 // get and check size 251 messaging_command *command = (messaging_command*)((char*)fHeader + offset); 252 size = command->size; 253 if (size < (int32)sizeof(messaging_command)) 254 return NULL; 255 size = (size + 3) & 0x3; // align 256 if (offset + size > fSize) 257 return NULL; 258 259 return command; 260 } 261 262 263 // #pragma mark - MessagingService 264 265 266 // constructor 267 MessagingService::MessagingService() 268 : fLock("messaging service"), 269 fFirstArea(NULL), 270 fLastArea(NULL) 271 { 272 } 273 274 275 // destructor 276 MessagingService::~MessagingService() 277 { 278 // Should actually never be called. Once created the service stays till the 279 // bitter end. 280 } 281 282 283 // InitCheck 284 status_t 285 MessagingService::InitCheck() const 286 { 287 if (fLock.Sem() < 0) 288 return fLock.Sem(); 289 return B_OK; 290 } 291 292 293 // Lock 294 bool 295 MessagingService::Lock() 296 { 297 return fLock.Lock(); 298 } 299 300 301 // Unlock 302 void 303 MessagingService::Unlock() 304 { 305 fLock.Unlock(); 306 } 307 308 309 // RegisterService 310 status_t 311 MessagingService::RegisterService(sem_id lockSem, sem_id counterSem, 312 area_id &areaID) 313 { 314 // check, if a service is already registered 315 if (fFirstArea) 316 return B_BAD_VALUE; 317 318 status_t error = B_OK; 319 320 // check, if the semaphores are valid and belong to the calling team 321 thread_info threadInfo; 322 error = get_thread_info(find_thread(NULL), &threadInfo); 323 324 sem_info lockSemInfo; 325 if (error == B_OK) 326 error = get_sem_info(lockSem, &lockSemInfo); 327 328 sem_info counterSemInfo; 329 if (error == B_OK) 330 error = get_sem_info(counterSem, &counterSemInfo); 331 332 if (error != B_OK) 333 return error; 334 335 if (threadInfo.team != lockSemInfo.team 336 || threadInfo.team != counterSemInfo.team) { 337 return B_BAD_VALUE; 338 } 339 340 // create an area 341 fFirstArea = fLastArea = MessagingArea::Create(lockSem, counterSem); 342 if (!fFirstArea) 343 return B_NO_MEMORY; 344 345 areaID = fFirstArea->ID(); 346 fFirstArea->Unlock(); 347 348 // store the server team and the semaphores 349 fServerTeam = threadInfo.team; 350 fLockSem = lockSem; 351 fCounterSem = counterSem; 352 353 return B_OK; 354 } 355 356 357 // UnregisterService 358 status_t 359 MessagingService::UnregisterService() 360 { 361 // check, if the team calling this function is indeed the server team 362 thread_info threadInfo; 363 status_t error = get_thread_info(find_thread(NULL), &threadInfo); 364 if (error != B_OK) 365 return error; 366 367 if (threadInfo.team != fServerTeam) 368 return B_BAD_VALUE; 369 370 // delete all areas 371 while (fFirstArea) { 372 MessagingArea *area = fFirstArea; 373 fFirstArea = area->NextArea(); 374 delete area; 375 } 376 fLastArea = NULL; 377 378 // unset the other members 379 fLockSem = -1; 380 fCounterSem = -1; 381 fServerTeam = -1; 382 383 return B_OK; 384 } 385 386 387 // SendMessage 388 status_t 389 MessagingService::SendMessage(const void *message, int32 messageSize, 390 const messaging_target *targets, int32 targetCount) 391 { 392 PRINT(("MessagingService::SendMessage(%p, %ld, %p, %ld)\n", message, 393 messageSize, targets, targetCount)); 394 if (!message || messageSize <= 0 || !targets || targetCount <= 0) 395 return B_BAD_VALUE; 396 397 int32 dataSize = sizeof(messaging_command_send_message) 398 + targetCount * sizeof(messaging_target) + messageSize; 399 400 // allocate space for the command 401 MessagingArea *area; 402 void *data; 403 bool wasEmpty; 404 status_t error = _AllocateCommand(MESSAGING_COMMAND_SEND_MESSAGE, dataSize, 405 area, data, wasEmpty); 406 if (error != B_OK) { 407 PRINT(("MessagingService::SendMessage(): Failed to allocate space for " 408 "send message command.\n")); 409 return error; 410 } 411 PRINT((" Allocated space for send message command: area: %p, data: %p, " 412 "wasEmpty: %d\n", area, data, wasEmpty)); 413 414 // prepare the command 415 messaging_command_send_message *command 416 = (messaging_command_send_message*)data; 417 command->message_size = messageSize; 418 command->target_count = targetCount; 419 memcpy(command->targets, targets, sizeof(messaging_target) * targetCount); 420 memcpy((char*)command + (dataSize - messageSize), message, messageSize); 421 422 // shoot 423 area->Unlock(); 424 if (wasEmpty) 425 area->CommitCommand(); 426 427 return B_OK; 428 } 429 430 431 // _AllocateCommand 432 status_t 433 MessagingService::_AllocateCommand(int32 commandWhat, int32 size, 434 MessagingArea *&area, void *&data, bool &wasEmpty) 435 { 436 if (!fFirstArea) 437 return B_NO_INIT; 438 439 if (!MessagingArea::CheckCommandSize(size)) 440 return B_BAD_VALUE; 441 442 // delete the discarded areas (save one) 443 ObjectDeleter<MessagingArea> discardedAreaDeleter; 444 MessagingArea *discardedArea = NULL; 445 while (fFirstArea != fLastArea) { 446 area = fFirstArea; 447 area->Lock(); 448 if (area->Size() != 0) { 449 area->Unlock(); 450 break; 451 } 452 453 PRINT(("MessagingService::_AllocateCommand(): Discarding area: %p\n", 454 area)); 455 456 fFirstArea = area->NextArea(); 457 area->SetNextArea(NULL); 458 discardedArea = area; 459 discardedAreaDeleter.SetTo(area); 460 } 461 462 // allocate space for the command in the last area 463 area = fLastArea; 464 area->Lock(); 465 data = area->AllocateCommand(commandWhat, size, wasEmpty); 466 467 if (!data) { 468 // not enough space in the last area: create a new area or reuse a 469 // discarded one 470 if (discardedArea) { 471 area = discardedAreaDeleter.Detach(); 472 area->InitHeader(); 473 PRINT(("MessagingService::_AllocateCommand(): Not enough space " 474 "left in current area. Recycling discarded one: %p\n", area)); 475 } else { 476 area = MessagingArea::Create(fLockSem, fCounterSem); 477 PRINT(("MessagingService::_AllocateCommand(): Not enough space " 478 "left in current area. Allocated new one: %p\n", area)); 479 } 480 if (!area) { 481 fLastArea->Unlock(); 482 return B_NO_MEMORY; 483 } 484 485 // add the new area 486 fLastArea->SetNextArea(area); 487 fLastArea->Unlock(); 488 fLastArea = area; 489 490 // allocate space for the command 491 data = area->AllocateCommand(commandWhat, size, wasEmpty); 492 493 if (!data) { 494 // that should never happen 495 area->Unlock(); 496 return B_NO_MEMORY; 497 } 498 } 499 500 return B_OK; 501 } 502 503 504 // #pragma mark - kernel private 505 506 507 // send_message 508 status_t 509 send_message(const void *message, int32 messageSize, 510 const messaging_target *targets, int32 targetCount) 511 { 512 // check, if init_messaging_service() has been called yet 513 if (!sMessagingService) 514 return B_NO_INIT; 515 516 if (!sMessagingService->Lock()) 517 return B_BAD_VALUE; 518 519 status_t error = sMessagingService->SendMessage(message, messageSize, 520 targets, targetCount); 521 522 sMessagingService->Unlock(); 523 524 return error; 525 } 526 527 528 // send_message 529 status_t 530 send_message(const KMessage *message, const messaging_target *targets, 531 int32 targetCount) 532 { 533 if (!message) 534 return B_BAD_VALUE; 535 536 return send_message(message->Buffer(), message->ContentSize(), targets, 537 targetCount); 538 } 539 540 541 // init_messaging_service 542 status_t 543 init_messaging_service() 544 { 545 static char buffer[sizeof(MessagingService)]; 546 547 if (!sMessagingService) 548 sMessagingService = new(buffer) MessagingService; 549 550 status_t error = sMessagingService->InitCheck(); 551 552 // cleanup on error 553 if (error != B_OK) { 554 dprintf("ERROR: Failed to init messaging service: %s\n", 555 strerror(error)); 556 sMessagingService->~MessagingService(); 557 sMessagingService = NULL; 558 } 559 560 return error; 561 } 562 563 564 // #pragma mark - syscalls 565 566 567 // _user_register_messaging_service 568 /** \brief Called by the userland server to register itself as a messaging 569 service for the kernel. 570 \param lockingSem A semaphore used for locking the shared data. Semaphore 571 counter must be initialized to 0. 572 \param counterSem A semaphore released every time the kernel pushes a 573 command into an empty area. Semaphore counter must be initialized 574 to 0. 575 \return 576 - The ID of the kernel area used for communication, if everything went fine, 577 - an error code otherwise. 578 */ 579 area_id 580 _user_register_messaging_service(sem_id lockSem, sem_id counterSem) 581 { 582 // check, if init_messaging_service() has been called yet 583 if (!sMessagingService) 584 return B_NO_INIT; 585 586 if (!sMessagingService->Lock()) 587 return B_BAD_VALUE; 588 589 area_id areaID; 590 status_t error = sMessagingService->RegisterService(lockSem, counterSem, 591 areaID); 592 593 sMessagingService->Unlock(); 594 595 return (error != B_OK ? error : areaID); 596 } 597 598 599 // _user_unregister_messaging_service 600 status_t 601 _user_unregister_messaging_service() 602 { 603 // check, if init_messaging_service() has been called yet 604 if (!sMessagingService) 605 return B_NO_INIT; 606 607 if (!sMessagingService->Lock()) 608 return B_BAD_VALUE; 609 610 status_t error = sMessagingService->UnregisterService(); 611 612 sMessagingService->Unlock(); 613 614 return error; 615 } 616