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