/*
* 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 <new>
#include <AutoDeleter.h>
#include <BytePointer.h>
#include <KernelExport.h>
#include <KMessage.h>
#include <messaging.h>
#include <MessagingServiceDefs.h>
#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_USER_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<messaging_command> 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<messaging_command> 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<MessagingArea> 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;
}
↑ V558 Function returns the pointer to temporary local object: & command.
↑ V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fServerTeam, fLockSem, fCounterSem.