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