/* tag: plugin interface for openbios forth kernel
 *
 * Copyright (C) 2003, 2004 Stefan Reinauer
 *
 * See the file "COPYING" for further information about
 * the copyright and warranty status of this work.
 */

#include "sysinclude.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>

#include "unix/plugins.h"

unsigned char *plugindir = "/usr/share/OpenBIOS/plugins";
#define PLUGINDIR  plugindir
#define PATHSIZE   256

#define CONFIG_DEBUG_PLUGINS

typedef struct iorange iorange_t;
struct iorange {
	const char *name;
	unsigned int start;
	unsigned int end;
	io_ops_t *ops;
	iorange_t *next;
};

static iorange_t *ioranges = NULL;

typedef struct plugin plugin_t;
struct plugin {
	const char *name;
	plugin_t *next;
};

static plugin_t *plugins = NULL;

io_ops_t *find_iorange(u32 reg)
{
	iorange_t *range = ioranges;
	while (range) {
		if (range->start <= reg && range->end >= reg)
			return range->ops;
		range = range->next;
	}
	return NULL;
}

int register_iorange(const char *name, io_ops_t * ops, unsigned int rstart,
		     unsigned int rend)
{
	iorange_t *newrange;

	/* intersection check */
	newrange = ioranges;
	while (newrange) {
		int fail = 0;
		/* new section swallows old section */
		if (newrange->start >= rstart && newrange->end <= rend)
			fail = -1;
		/* new section start or end point are within range */
		if (newrange->start <= rstart && newrange->end >= rstart)
			fail = -1;
		if (newrange->start <= rend && newrange->end >= rend)
			fail = -1;
		if (fail) {
			printf("Error: overlapping IO regions: %s and %s\n",
				newrange->name, name);
			return -1;
		}
		newrange = newrange->next;
	}

	newrange = malloc(sizeof(iorange_t));

	newrange->name = name;
	newrange->ops = ops;
	newrange->start = rstart;
	newrange->end = rend;
	newrange->next = ioranges;

	ioranges = newrange;

	return 0;
}

int is_loaded(const char *plugin_name)
{
	plugin_t *p = plugins;
	while (p) {
		if (!strcmp(plugin_name, p->name))
			return -1;
		p = p->next;
	}
	return 0;
}

int load_plugin(const char *plugin_name)
{
	void *handle;
	char *error;
	char path[PATHSIZE];

	int (*init_plugin) (void);
	char **deps;
	char **plugin_info;
	plugin_t *p;

	if (is_loaded(plugin_name)) {
		printf("Plugin %s already loaded.\n", plugin_name);
		return 0;
	}

	strncpy(path, PLUGINDIR, PATHSIZE);
	strncat(path, "/plugin_", PATHSIZE);
	strncat(path, plugin_name, PATHSIZE);
	strncat(path, ".so", PATHSIZE);

#if DEBUG
	printf("Opening plugin %s\n", path);
#endif

	handle = dlopen(path, RTLD_LAZY | RTLD_GLOBAL);
	if (!handle) {
		error = dlerror();
		printf("Error: Could not open plugin \"%s\": %s\n",
		       plugin_name, error);
		exit(1);
	}
#ifdef CONFIG_DEBUG_PLUGINS
	plugin_info = dlsym(handle, "plugin_author");
	if ((error = dlerror()) == NULL)
		printf("Plugin %s author:  %s\n", plugin_name, *plugin_info);
	plugin_info = dlsym(handle, "plugin_license");
	if ((error = dlerror()) == NULL)
		printf("Plugin %s license: %s\n", plugin_name, *plugin_info);
	plugin_info = dlsym(handle, "plugin_description");
	if ((error = dlerror()) == NULL)
		printf("Plugin %s descr.: %s\n", plugin_name, *plugin_info);
#endif
	p = malloc(sizeof(plugin_t));
	p->next = plugins;
	p->name = plugin_name;
	plugins = p;

	deps = dlsym(handle, "plugin_deps");
	if ((error = dlerror()) != NULL)
		deps = NULL;


	strncpy(path, "plugin_", PATHSIZE);
	strncat(path, plugin_name, PATHSIZE);
	strncat(path, "_init", PATHSIZE);

	init_plugin = dlsym(handle, path);
	if ((error = dlerror()) != NULL) {
		printf("error: %s\n", error);
		exit(1);
	}

	if (deps) {
		int i = 0;
		char *walk = deps[0];
#ifdef CONFIG_DEBUG_PLUGINS
		printf("\nPlugin %s dependencies:", plugin_name);
#endif
		while (walk) {
			printf(" %s", walk);
			if (!is_loaded(walk)) {
#ifdef CONFIG_DEBUG_PLUGINS
				printf("(loading)\n");
#endif
				load_plugin(walk);
			}
#ifdef CONFIG_DEBUG_PLUGINS
			else {
				printf("(loaded)");
			}
#endif
			walk = deps[++i];
		}
	}

	printf("\n");
#if DEBUG
	printf("Initializing module:\n");
#endif

	return init_plugin();

	// We don't dlclose the handle here since
	// we want to keep our symbols for later use.
}