xref: /haiku/src/system/kernel/messaging/MessagingService.cpp (revision 8a0c9d52c62918442ede4cc8361bdbfb9560e713)
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