
/*
 * This software may be used and distributed according to the terms
 * of the Lesser GNU Public License, incorporated herein by reference
 *
 * Copyright (C) IBM Corporation, 2004
 *
 * Author: Max Asböck <amax@us.ibm.com>
 */


#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <signal.h>
#include <sys/ioctl.h>

#include "libibmasm.h"
#include "rsa.h"

struct spnode { 
	unsigned int	opened;
	int		event_fd;
	int		heartbeat_fd;
	unsigned int	io_buffer_length;
	char 		command_path[RSA_NAME_SIZE];
	char 		event_path[RSA_NAME_SIZE];
	char 		heartbeat_path[RSA_NAME_SIZE];
};

#define MAX_SP_NODES	16

static struct spnode *node_array[MAX_SP_NODES];
static unsigned int num_nodes = 0;
static int api_initialized = 0;

#define HANDLE_ADD 1		// handles must not be 0, so we always add 1
#define handle2node(h)	h - HANDLE_ADD
#define node2handle(n)	n + HANDLE_ADD

static inline struct spnode *get_spnode(int node)
{
	if (node < 0 || node >= num_nodes)
		return NULL;

	return node_array[node];
}

#define get_spnode_from_handle(h)		get_spnode(handle2node(h))

static int get_ibmasmfs_mount_point(char *mount_point)
{
	FILE *f;
	char line[RSA_NAME_SIZE];
	char *first, *mount, *type;


	f = fopen("/proc/mounts", "r");
	if (f == NULL)
		return 1;

	while(fgets(line, 1024, f)) {
		first = strtok(line, " ");
		mount = strtok(NULL, " ");
		type = strtok(NULL, " ");
		if (!strcmp(type, "ibmasmfs")) {
			strncpy(mount_point, mount, RSA_NAME_SIZE - 1); 
			fclose(f);
			return 0;
		}
	}
	fclose(f);
	return 1;
}

static unsigned int api_init()
{ 
	DIR *dir;
	struct dirent *dirent;
	char mount_point[RSA_NAME_SIZE];
	struct spnode *nodes[MAX_SP_NODES];
	int n = 0;
	int i;

	// find out where ibmasmfs is mounted
	if (get_ibmasmfs_mount_point(mount_point) != 0)
		return 1;

	dir = opendir(mount_point);
	if (dir == NULL)
		return 1;

	// the ibmasmfs root directory contains a directory
	// entry for each service processor. Look them up
	// and allocate a data structure to save away 
	// path names and state information for each service
	// processor node.
	while ((dirent = readdir(dir))) {
		struct spnode *node;

		if (!strcmp(dirent->d_name, "."))
			continue;
		if (!strcmp(dirent->d_name, ".."))
			continue;

		// allocate memory: if we fail on one node
		// back out completely 
		node = malloc(sizeof(struct spnode));
		if (node == NULL) {
			for (i=0; i<n; i++)
				free(nodes[i]);
			n = 0;
			break;
		}
		// we found a service processor node
		// set up the path names for the command, event
		// and reverse heartbeat files
		snprintf(node->command_path, RSA_NAME_SIZE, "%s/%s/%s",
		    mount_point, dirent->d_name, RSA_COMMAND);
		snprintf(node->event_path, RSA_NAME_SIZE, "%s/%s/%s",
		    mount_point, dirent->d_name, RSA_EVENT);
		snprintf(node->heartbeat_path, RSA_NAME_SIZE, "%s/%s/%s",
		    mount_point, dirent->d_name, RSA_HEARTBEAT);

		node->opened = 0;
		nodes[n] = node;
		n++;
		if (n >= MAX_SP_NODES)
			break;
	}
	closedir(dir);

	// we didn't find any nodes or malloc failed
	if (n == 0)
		return 1;

	// we found at least one service processor node.
	// initialize the global node array and node number
	for (i=0; i<n; i++) 
		node_array[i] = nodes[i];
	num_nodes = n;
	return 0;
}

/**
 * OpenSPDriverNode
 * must be called first by an application, before any other API call
 * can be made.
 *
 * Return codes:
 *	RC_ALREADY_OPEN
 *	RC_INVALID_BUFFER
 *	RC_SYSTEM_ERROR
 *	RC_SUCCESS
 */
unsigned int OpenSPDriverNode(int *handle, 
		unsigned int	io_buffer_length,
		unsigned int	node,
		unsigned int	*num,
		unsigned long	reserved)
{
	struct spnode *spnode;

	if (handle == NULL && num == NULL)
		return RC_INVALID_HANDLE;

	/* ensure the the io buffer size is valid (1 - 32 Kb) */
	if ((io_buffer_length < 1) || (io_buffer_length > 32))
		return  RC_INVALID_BUFFER;

	if (!api_initialized) {
		api_initialized = 1;
		if (api_init() == 1) {
			api_initialized = 0;
			return RC_SYSTEM_ERROR;
		}
	}
	if (num != NULL)
		*num = num_nodes;

	if (handle == NULL)
		return RC_SUCCESS;

	spnode = get_spnode(node);
	if (spnode == NULL)
		return RC_OPEN_FAILED;

	if (spnode->opened)
		return RC_ALREADY_OPEN;

	spnode->event_fd = open(spnode->event_path, O_RDWR);
	if (spnode->event_fd < 0)
		goto event_open_failed;

	spnode->heartbeat_fd = open(spnode->heartbeat_path, O_RDWR);
	if (spnode->heartbeat_fd < 0)
		goto heartbeat_open_failed;

	spnode->io_buffer_length	= io_buffer_length * 1024;
	spnode->opened			= 1;
	*handle = node2handle(node);
	return RC_SUCCESS;


heartbeat_open_failed:
	close(spnode->event_fd);
event_open_failed:
	return RC_OPEN_FAILED;
}


/**
 * OpenSPDriver
 * for backwards compatibility	
 */
unsigned int OpenSPDriver(int *handle,
			unsigned int io_buffer_length,
			unsigned long reserved)
{
     return OpenSPDriverNode(handle, io_buffer_length, 0, NULL, reserved); 
}


static void io_cancel(int fd)
{
	char c = 0;

	write(fd, &c, 1);
}

/**
 * CloseSPDriver
 * must be called when an application closes.
 *
 * Return values:
 *	RC_INVALID_HANDLE
 *	RC_ALREADY_CLOSED
 *	RC_SUCCESS
 */
unsigned int CloseSPDriver(int handle, unsigned long reserved)
{
	struct spnode *node;

	node = get_spnode_from_handle(handle);
	if (node == NULL || !node->opened)
		return RC_INVALID_HANDLE;

	node->opened = 0;
	io_cancel(node->event_fd);
	io_cancel(node->heartbeat_fd);
	close(node->event_fd);
	close(node->heartbeat_fd);

	return RC_SUCCESS;
}


static int send_command(struct spnode *node, void *command)
{
	int fd;
	int len = node->io_buffer_length;
	int result;

        fd = open(node->command_path, O_RDWR);
        if (fd < 0)
		return RC_SYSTEM_ERROR;

	result = write(fd, command, len);
	if (result <= 0) {
		close(fd);
		return RC_SYSTEM_ERROR;
	}

	result = read(fd, command, len);
	if (result <= 0) {
		close(fd);
		return RC_SYSTEM_ERROR;
	}
	close(fd);
	return 0;
}
	
/**
 * SystemDataIO
 * Send a command to the service processor
 *
 * Return values:
 *      RC_INVALID_HANDLE
 *      RC_INVALID_BUFFER
 *      RC_SYSTEM_ERROR
 *      RC_TIMEOUT
 */
unsigned int SystemDataIO(int handle, void *command, unsigned long reserved)
{
	struct dot_command *dot_command = (struct dot_command *)command;
	struct spnode *node = get_spnode_from_handle(handle);

	if (node == NULL || !node->opened)
		return RC_INVALID_HANDLE;

	if (command == NULL)
		return RC_INVALID_BUFFER;

	switch(dot_command->type) {
	case dotcmd_write:
	case dotcmd_write_next:
	case dotcmd_read:
	case dotcmd_read_next:
		return send_command(node, command);
	default:
		dot_command->type = dotcmd_command_response;
		return RC_SUCCESS;
	}
}


static int wait_for_event(struct spnode *node, void *buffer)
{
	int ret;
	unsigned int size;

	if (node->io_buffer_length > IBMASM_EVENT_MAX_SIZE)
		size = IBMASM_EVENT_MAX_SIZE;
	else
		size = node->io_buffer_length;

	ret = read(node->event_fd, buffer, size);
	if (ret == 0)
		return RC_EVENT_CANCELLED;
	else if (ret < 0) {
		switch(errno) {
		case EBUSY:
			return RC_ALREADY_REGISTERED;
		case EINTR:
			return RC_SUCCESS;
		default:
			return RC_SYSTEM_ERROR;
		}
	}
	return RC_SUCCESS;
}

static int
event_ignored(void *buffer,  unsigned char **ignore_list, short ignore_count)
{
	int i;
	char *event = (char *)buffer + sizeof(struct dot_command);
	int size = ((struct dot_command *)buffer)->command_size;

	for (i=0; i<ignore_count; i++) {
		if (!strncmp(ignore_list[i], event, size))
			return 1;
	}
	return 0;
}


/**
 * RegisterForEvents
 * allows an application to register for event notification.
 *
 * Return code:
 *	RC_INVALID_HANDLE
 *	RC_INVALID_BUFFER
 *	RC_SUCCESS
 *	RC_EVENT_CANCELLED
 */
unsigned int RegisterForEvents(int handle, void *buffer, short ignore_count, unsigned char **ignore_list, unsigned long reserved)
{
	int i;
	int ret;
	struct spnode *node = get_spnode_from_handle(handle);

	if (node == NULL || !node->opened)
		return RC_INVALID_HANDLE;
	
	if (buffer == NULL)                                                   
		return RC_INVALID_BUFFER;  

	if (ignore_count && ignore_list == NULL)
		return RC_INVALID_BUFFER;

	for(i=0; i<ignore_count; i++) {
		if (ignore_list[i] == NULL)
			return RC_INVALID_BUFFER;
	}

	while ( (ret = wait_for_event(node, buffer)) == RC_SUCCESS )
		if ( !event_ignored(buffer, ignore_list, ignore_count) )
			break;
	
	return ret;
}

/**
 * DeregisterForEvents
 * Cancel event registration, i.e wake up the thread that is waiting
 * for an event.
 * 
 * Return values:
 *      RC_INVALID_HANDLE
 *      RC_SUCCESS
 */
unsigned int DeregisterForEvents(int handle, unsigned long reserved)
{
	struct spnode *node = get_spnode_from_handle(handle);

	if (node == NULL || !node->opened)
		return RC_INVALID_HANDLE;

	io_cancel(node->event_fd);
	return RC_SUCCESS;
}

unsigned int RegisterForReverseHB(int handle, unsigned long reserved)
{
	struct spnode *node = get_spnode_from_handle(handle);
	unsigned char c = 0;
	int ret;

	if (node == NULL || !node->opened)
		return RC_INVALID_HANDLE;

	ret = read(node->heartbeat_fd, &c, 1);
	if (ret == 1)
		return RC_REVERSE_HEARTBEAT_HAS_FAILED;

	switch(errno) {
	case EBUSY:
		return RC_REVERSE_HB_ALREADY_REGISTERED;
	case EINTR:
		return RC_SUCCESS;
	default:
		return RC_SYSTEM_ERROR;
	}
}

unsigned int DeRegisterForReverseHB(int handle, unsigned long reserved)
{
	struct spnode *node = get_spnode_from_handle(handle);

	if (node == NULL || !node->opened)
		return RC_INVALID_HANDLE;

	io_cancel(node->heartbeat_fd);
	return RC_SUCCESS;
}
