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
29using namespace std;
30
31static MessagingService *sMessagingService = NULL;
32
33static const int32 kMessagingAreaSize = B_PAGE_SIZE * 4;
34
35
36// #pragma mark - MessagingArea
37
38
39MessagingArea::MessagingArea()
40{
41}
42
43
44MessagingArea::~MessagingArea()
45{
46	if (fID >= 0)
47		delete_area(fID);
48}
49
50
51MessagingArea *
52MessagingArea::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
79void
80MessagingArea::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
92bool
93MessagingArea::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
102bool
103MessagingArea::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
113void
114MessagingArea::Unlock()
115{
116	if (atomic_add(&fHeader->lock_counter, -1) > 1)
117		release_sem(fLockSem);
118}
119
120
121area_id
122MessagingArea::ID() const
123{
124	return fID;
125}
126
127
128int32
129MessagingArea::Size() const
130{
131	return fSize;
132}
133
134
135bool
136MessagingArea::IsEmpty() const
137{
138	return fHeader->command_count == 0;
139}
140
141
142void *
143MessagingArea::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
213void
214MessagingArea::CommitCommand()
215{
216	// TODO: If invoked while locked, we should supply B_DO_NOT_RESCHEDULE.
217	release_sem(fCounterSem);
218}
219
220
221void
222MessagingArea::SetNextArea(MessagingArea *area)
223{
224	fNextArea = area;
225	fHeader->next_kernel_area = (fNextArea ? fNextArea->ID() : -1);
226}
227
228
229MessagingArea *
230MessagingArea::NextArea() const
231{
232	return fNextArea;
233}
234
235
236messaging_command *
237MessagingArea::_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
263MessagingService::MessagingService()
264	:
265	fFirstArea(NULL),
266	fLastArea(NULL)
267{
268	recursive_lock_init(&fLock, "messaging service");
269}
270
271
272MessagingService::~MessagingService()
273{
274	// Should actually never be called. Once created the service stays till the
275	// bitter end.
276}
277
278
279status_t
280MessagingService::InitCheck() const
281{
282	return B_OK;
283}
284
285
286bool
287MessagingService::Lock()
288{
289	return recursive_lock_lock(&fLock) == B_OK;
290}
291
292
293void
294MessagingService::Unlock()
295{
296	recursive_lock_unlock(&fLock);
297}
298
299
300status_t
301MessagingService::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
347status_t
348MessagingService::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
376status_t
377MessagingService::SendMessage(const void *message, int32 messageSize,
378	const messaging_target *targets, int32 targetCount)
379{
380PRINT(("MessagingService::SendMessage(%p, %ld, %p, %ld)\n", message,
381messageSize, 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	}
399PRINT(("  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
419status_t
420MessagingService::_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
495status_t
496send_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
515status_t
516send_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
527status_t
528init_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*/
563area_id
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
583status_t
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