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