/* * Copyright 2005-2008, Ingo Weinhold, ingo_weinhold@gmx.de. * Distributed under the terms of the MIT License. */ //! kernel-side implementation of the messaging service #include #include #include #include #include #include #include #include "MessagingService.h" //#define TRACE_MESSAGING_SERVICE #ifdef TRACE_MESSAGING_SERVICE # define PRINT(x) dprintf x #else # define PRINT(x) ; #endif using namespace std; static MessagingService *sMessagingService = NULL; static const int32 kMessagingAreaSize = B_PAGE_SIZE * 4; // #pragma mark - MessagingArea MessagingArea::MessagingArea() { } MessagingArea::~MessagingArea() { if (fID >= 0) delete_area(fID); } MessagingArea * MessagingArea::Create(sem_id lockSem, sem_id counterSem) { // allocate the object on the heap MessagingArea *area = new(nothrow) MessagingArea; if (!area) return NULL; // create the area area->fID = create_area("messaging", (void**)&area->fHeader, B_ANY_KERNEL_ADDRESS, kMessagingAreaSize, B_FULL_LOCK, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA | B_CLONEABLE_AREA); if (area->fID < 0) { delete area; return NULL; } // finish the initialization of the object area->fSize = kMessagingAreaSize; area->fLockSem = lockSem; area->fCounterSem = counterSem; area->fNextArea = NULL; area->InitHeader(); return area; } void MessagingArea::InitHeader() { fHeader->lock_counter = 1; // create locked fHeader->size = fSize; fHeader->kernel_area = fID; fHeader->next_kernel_area = (fNextArea ? fNextArea->ID() : -1); fHeader->command_count = 0; fHeader->first_command = 0; fHeader->last_command = 0; } bool MessagingArea::CheckCommandSize(int32 dataSize) { int32 size = sizeof(messaging_command) + dataSize; return (dataSize >= 0 && size <= kMessagingAreaSize - (int32)sizeof(messaging_area_header)); } bool MessagingArea::Lock() { // benaphore-like locking if (atomic_add(&fHeader->lock_counter, 1) == 0) return true; return (acquire_sem(fLockSem) == B_OK); } void MessagingArea::Unlock() { if (atomic_add(&fHeader->lock_counter, -1) > 1) release_sem(fLockSem); } area_id MessagingArea::ID() const { return fID; } int32 MessagingArea::Size() const { return fSize; } bool MessagingArea::IsEmpty() const { return fHeader->command_count == 0; } void * MessagingArea::AllocateCommand(uint32 commandWhat, int32 dataSize, bool &wasEmpty) { int32 size = sizeof(messaging_command) + dataSize; if (dataSize < 0 || size > fSize - (int32)sizeof(messaging_area_header)) return NULL; // the area is used as a ring buffer int32 startOffset = sizeof(messaging_area_header); // the simple case first: the area is empty int32 commandOffset; wasEmpty = (fHeader->command_count == 0); if (wasEmpty) { commandOffset = startOffset; // update the header fHeader->command_count++; fHeader->first_command = fHeader->last_command = commandOffset; } else { int32 firstCommandOffset = fHeader->first_command; int32 lastCommandOffset = fHeader->last_command; int32 firstCommandSize; int32 lastCommandSize; messaging_command *firstCommand = _CheckCommand(firstCommandOffset, firstCommandSize); messaging_command *lastCommand = _CheckCommand(lastCommandOffset, lastCommandSize); if (!firstCommand || !lastCommand) { // something has been screwed up return NULL; } // find space for the command if (firstCommandOffset <= lastCommandOffset) { // not wrapped // try to allocate after the last command if (size <= fSize - (lastCommandOffset + lastCommandSize)) { commandOffset = (lastCommandOffset + lastCommandSize); } else { // is there enough space before the first command? if (size > firstCommandOffset - startOffset) return NULL; commandOffset = startOffset; } } else { // wrapped: we can only allocate between the last and the first // command commandOffset = lastCommandOffset + lastCommandSize; if (size > firstCommandOffset - commandOffset) return NULL; } // update the header and the last command fHeader->command_count++; lastCommand->next_command = fHeader->last_command = commandOffset; } // init the command BytePointer command(fHeader); command += commandOffset; command->next_command = 0; command->command = commandWhat; command->size = size; return command->data; } void MessagingArea::CommitCommand() { // TODO: If invoked while locked, we should supply B_DO_NOT_RESCHEDULE. release_sem(fCounterSem); } void MessagingArea::SetNextArea(MessagingArea *area) { fNextArea = area; fHeader->next_kernel_area = (fNextArea ? fNextArea->ID() : -1); } MessagingArea * MessagingArea::NextArea() const { return fNextArea; } messaging_command * MessagingArea::_CheckCommand(int32 offset, int32 &size) { // check offset if (offset < (int32)sizeof(messaging_area_header) || offset + (int32)sizeof(messaging_command) > fSize || (offset & 0x3)) { return NULL; } // get and check size BytePointer command(fHeader); command += offset; size = command->size; if (size < (int32)sizeof(messaging_command)) return NULL; size = (size + 3) & ~0x3; // align if (offset + size > fSize) return NULL; return &command; } // #pragma mark - MessagingService MessagingService::MessagingService() : fFirstArea(NULL), fLastArea(NULL) { recursive_lock_init(&fLock, "messaging service"); } MessagingService::~MessagingService() { // Should actually never be called. Once created the service stays till the // bitter end. } status_t MessagingService::InitCheck() const { return B_OK; } bool MessagingService::Lock() { return recursive_lock_lock(&fLock) == B_OK; } void MessagingService::Unlock() { recursive_lock_unlock(&fLock); } status_t MessagingService::RegisterService(sem_id lockSem, sem_id counterSem, area_id &areaID) { // check, if a service is already registered if (fFirstArea) return B_BAD_VALUE; status_t error = B_OK; // check, if the semaphores are valid and belong to the calling team thread_info threadInfo; error = get_thread_info(find_thread(NULL), &threadInfo); sem_info lockSemInfo; if (error == B_OK) error = get_sem_info(lockSem, &lockSemInfo); sem_info counterSemInfo; if (error == B_OK) error = get_sem_info(counterSem, &counterSemInfo); if (error != B_OK) return error; if (threadInfo.team != lockSemInfo.team || threadInfo.team != counterSemInfo.team) { return B_BAD_VALUE; } // create an area fFirstArea = fLastArea = MessagingArea::Create(lockSem, counterSem); if (!fFirstArea) return B_NO_MEMORY; areaID = fFirstArea->ID(); fFirstArea->Unlock(); // store the server team and the semaphores fServerTeam = threadInfo.team; fLockSem = lockSem; fCounterSem = counterSem; return B_OK; } status_t MessagingService::UnregisterService() { // check, if the team calling this function is indeed the server team thread_info threadInfo; status_t error = get_thread_info(find_thread(NULL), &threadInfo); if (error != B_OK) return error; if (threadInfo.team != fServerTeam) return B_BAD_VALUE; // delete all areas while (fFirstArea) { MessagingArea *area = fFirstArea; fFirstArea = area->NextArea(); delete area; } fLastArea = NULL; // unset the other members fLockSem = -1; fCounterSem = -1; fServerTeam = -1; return B_OK; } status_t MessagingService::SendMessage(const void *message, int32 messageSize, const messaging_target *targets, int32 targetCount) { PRINT(("MessagingService::SendMessage(%p, %ld, %p, %ld)\n", message, messageSize, targets, targetCount)); if (!message || messageSize <= 0 || !targets || targetCount <= 0) return B_BAD_VALUE; int32 dataSize = sizeof(messaging_command_send_message) + targetCount * sizeof(messaging_target) + messageSize; // allocate space for the command MessagingArea *area; void *data; bool wasEmpty; status_t error = _AllocateCommand(MESSAGING_COMMAND_SEND_MESSAGE, dataSize, area, data, wasEmpty); if (error != B_OK) { PRINT(("MessagingService::SendMessage(): Failed to allocate space for " "send message command.\n")); return error; } PRINT((" Allocated space for send message command: area: %p, data: %p, " "wasEmpty: %d\n", area, data, wasEmpty)); // prepare the command messaging_command_send_message *command = (messaging_command_send_message*)data; command->message_size = messageSize; command->target_count = targetCount; memcpy(command->targets, targets, sizeof(messaging_target) * targetCount); memcpy((char*)command + (dataSize - messageSize), message, messageSize); // shoot area->Unlock(); if (wasEmpty) area->CommitCommand(); return B_OK; } status_t MessagingService::_AllocateCommand(int32 commandWhat, int32 size, MessagingArea *&area, void *&data, bool &wasEmpty) { if (!fFirstArea) return B_NO_INIT; if (!MessagingArea::CheckCommandSize(size)) return B_BAD_VALUE; // delete the discarded areas (save one) ObjectDeleter discardedAreaDeleter; MessagingArea *discardedArea = NULL; while (fFirstArea != fLastArea) { area = fFirstArea; area->Lock(); if (!area->IsEmpty()) { area->Unlock(); break; } PRINT(("MessagingService::_AllocateCommand(): Discarding area: %p\n", area)); fFirstArea = area->NextArea(); area->SetNextArea(NULL); discardedArea = area; discardedAreaDeleter.SetTo(area); } // allocate space for the command in the last area area = fLastArea; area->Lock(); data = area->AllocateCommand(commandWhat, size, wasEmpty); if (!data) { // not enough space in the last area: create a new area or reuse a // discarded one if (discardedArea) { area = discardedAreaDeleter.Detach(); area->InitHeader(); PRINT(("MessagingService::_AllocateCommand(): Not enough space " "left in current area. Recycling discarded one: %p\n", area)); } else { area = MessagingArea::Create(fLockSem, fCounterSem); PRINT(("MessagingService::_AllocateCommand(): Not enough space " "left in current area. Allocated new one: %p\n", area)); } if (!area) { fLastArea->Unlock(); return B_NO_MEMORY; } // add the new area fLastArea->SetNextArea(area); fLastArea->Unlock(); fLastArea = area; // allocate space for the command data = area->AllocateCommand(commandWhat, size, wasEmpty); if (!data) { // that should never happen area->Unlock(); return B_NO_MEMORY; } } return B_OK; } // #pragma mark - kernel private status_t send_message(const void *message, int32 messageSize, const messaging_target *targets, int32 targetCount) { // check, if init_messaging_service() has been called yet if (!sMessagingService) return B_NO_INIT; if (!sMessagingService->Lock()) return B_BAD_VALUE; status_t error = sMessagingService->SendMessage(message, messageSize, targets, targetCount); sMessagingService->Unlock(); return error; } status_t send_message(const KMessage *message, const messaging_target *targets, int32 targetCount) { if (!message) return B_BAD_VALUE; return send_message(message->Buffer(), message->ContentSize(), targets, targetCount); } status_t init_messaging_service() { static char buffer[sizeof(MessagingService)]; if (!sMessagingService) sMessagingService = new(buffer) MessagingService; status_t error = sMessagingService->InitCheck(); // cleanup on error if (error != B_OK) { dprintf("ERROR: Failed to init messaging service: %s\n", strerror(error)); sMessagingService->~MessagingService(); sMessagingService = NULL; } return error; } // #pragma mark - syscalls /** \brief Called by the userland server to register itself as a messaging service for the kernel. \param lockingSem A semaphore used for locking the shared data. Semaphore counter must be initialized to 0. \param counterSem A semaphore released every time the kernel pushes a command into an empty area. Semaphore counter must be initialized to 0. \return - The ID of the kernel area used for communication, if everything went fine, - an error code otherwise. */ area_id _user_register_messaging_service(sem_id lockSem, sem_id counterSem) { // check, if init_messaging_service() has been called yet if (!sMessagingService) return B_NO_INIT; if (!sMessagingService->Lock()) return B_BAD_VALUE; area_id areaID = 0; status_t error = sMessagingService->RegisterService(lockSem, counterSem, areaID); sMessagingService->Unlock(); return (error != B_OK ? error : areaID); } status_t _user_unregister_messaging_service() { // check, if init_messaging_service() has been called yet if (!sMessagingService) return B_NO_INIT; if (!sMessagingService->Lock()) return B_BAD_VALUE; status_t error = sMessagingService->UnregisterService(); sMessagingService->Unlock(); return error; }