⛏️ index : haiku.git

/*
 * Copyright 2007-2010, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Philippe Houdoin, philippe.houdoin@free.fr
 */


//!	Deferred Procedure Call support kernel module


#include <KernelExport.h>

#include <stdio.h>
#include <stdlib.h>

#include <dpc.h>


// Private DPC queue structures
typedef struct {
	dpc_func 	function;
	void 		*arg;
} dpc_slot;


typedef struct {
	thread_id	thread;
	sem_id		wakeup_sem;
	spinlock	lock;
	int 		size;
	int			count;
	int			head;
	int 		tail;
	dpc_slot	slots[0];
	// size * slots follow
} dpc_queue;

#define DPC_QUEUE_SIZE 64

static int32
dpc_thread(void *arg)
{
	dpc_queue *queue = arg;
	dpc_slot dpc;

	// Let's wait forever/until semaphore death for new DPC slot to show up
	while (acquire_sem(queue->wakeup_sem) == B_OK) {
		cpu_status former;

		// grab the next dpc slot
		former = disable_interrupts();
		acquire_spinlock(&queue->lock);

		dpc = queue->slots[queue->head];
		queue->head = (queue->head + 1) % queue->size;
		queue->count--;

		release_spinlock(&queue->lock);
		restore_interrupts(former);

		dpc.function(dpc.arg);
	}

	// Let's finish the pending DPCs, if any.
	// Otherwise, resource could leak...
	while (queue->count--) {
		dpc = queue->slots[queue->head];
		queue->head = (queue->head + 1) % queue->size;
		dpc.function(dpc.arg);
	}

	// Now, let's die quietly, ignored by all... sigh.
	return 0;
}


// #pragma mark - public API


static status_t
new_dpc_queue(void **handle, const char *name, int32 priority)
{
	char str[64];
	dpc_queue *queue;

	if (!handle)
		return B_BAD_VALUE;

	queue = malloc(sizeof(dpc_queue) + DPC_QUEUE_SIZE * sizeof(dpc_slot));
	if (!queue)
		return B_NO_MEMORY;

	queue->head = queue->tail = 0;
	queue->size = DPC_QUEUE_SIZE;
	queue->count = 0;
	B_INITIALIZE_SPINLOCK(&queue->lock);	// Init the spinlock

	snprintf(str, sizeof(str), "%.*s_wakeup_sem",
		(int) sizeof(str) - 11, name);

	queue->wakeup_sem = create_sem(0, str);
	if (queue->wakeup_sem < B_OK) {
		status_t status = queue->wakeup_sem;
		free(queue);
		return status;
	}

	// Fire a kernel thread to actually handle (aka call them!)
	// the queued/deferred procedure calls
	queue->thread = spawn_kernel_thread(dpc_thread, name, priority, queue);
	if (queue->thread < 0) {
		status_t status = queue->thread;
		delete_sem(queue->wakeup_sem);
		free(queue);
		return status;
	}
	resume_thread(queue->thread);

	*handle = queue;

	return B_OK;
}


static status_t
delete_dpc_queue(void *handle)
{
	dpc_queue *queue = handle;
	thread_id thread;
	status_t exit_value;
	cpu_status former;

	if (!queue)
		return B_BAD_VALUE;

	// Close the queue: queue_dpc() should know we're closing:
	former = disable_interrupts();
	acquire_spinlock(&queue->lock);

	thread = queue->thread;
	queue->thread = -1;

	release_spinlock(&queue->lock);
	restore_interrupts(former);

	// Wakeup the thread by murdering its favorite semaphore
	delete_sem(queue->wakeup_sem);
	wait_for_thread(thread, &exit_value);

	free(queue);

	return B_OK;
}


static status_t
queue_dpc(void *handle, dpc_func function, void *arg)
{
	dpc_queue *queue = handle;
	cpu_status former;
	status_t status = B_OK;

	if (!queue || !function)
		return B_BAD_VALUE;

	// Try to be safe being called from interrupt handlers:
	former = disable_interrupts();
	acquire_spinlock(&queue->lock);

	if (queue->thread < 0) {
		// Queue thread is dying...
		status = B_CANCELED;
	} else if (queue->count == queue->size)
		// This DPC queue is full, sorry
		status = B_NO_MEMORY;
	else {
		queue->slots[queue->tail].function = function;
		queue->slots[queue->tail].arg      = arg;
		queue->tail = (queue->tail + 1) % queue->size;
		queue->count++;
	}

	release_spinlock(&queue->lock);
	restore_interrupts(former);

	if (status == B_OK)
		// Wake up the corresponding dpc thread
		// Notice that interrupt handlers should return B_INVOKE_SCHEDULER to
		// shorten DPC latency as much as possible...
		status = release_sem_etc(queue->wakeup_sem, 1, B_DO_NOT_RESCHEDULE);

	return status;
}


static status_t
std_ops(int32 op, ...)
{
	switch (op) {
		case B_MODULE_INIT:
			return B_OK;
		case B_MODULE_UNINIT:
			return B_OK;

		default:
			return B_ERROR;
	}
}


static dpc_module_info sDPCModule = {
	{
		B_DPC_MODULE_NAME,
		0,
		std_ops
	},

	new_dpc_queue,
	delete_dpc_queue,
	queue_dpc
};


module_info *modules[] = {
	(module_info *) &sDPCModule,
	NULL
};