/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <unistd.h>
#include <libdevice.h>
#include <libdevinfo.h>
#define	_KERNEL
#include <sys/dditypes.h>
#include <sys/devctl.h>
#include <sys/bofi.h>

static int online_device(char *path);
static int offline_device(char *path);
static int getstate_device(char *path);
static int getnameinst(char *path, int *instance, char *name, int namelen);
static int getpath(char *path, int instance, char *name, int pathlen);

static char buffer[50*1024];

#define	CMD_TABLE_SIZE 11
#define	BOFI_ONLINE 0
#define	BOFI_OFFLINE 1
#define	BOFI_GETSTATE 2
#define	BOFI_GETPATH 3

static struct {
	char *string;
	int val;
	int devctl_val;
} cmd_table[] = {
	{"online", -1, BOFI_ONLINE},
	{"offline", -1, BOFI_OFFLINE},
	{"getstate", -1, BOFI_GETSTATE},
	{"getpath", -1, BOFI_GETPATH},
	{"broadcast", BOFI_BROADCAST, -1},
	{"clear_acc_chk", BOFI_CLEAR_ACC_CHK, -1},
	{"clear_errors", BOFI_CLEAR_ERRORS, -1},
	{"clear_errdefs", BOFI_CLEAR_ERRDEFS, -1},
	{"start", BOFI_START, -1},
	{"stop", BOFI_STOP, -1},
	{"get_handles", BOFI_GET_HANDLES, -1}
};

int
main(int argc, char **argv)
{
	struct bofi_errctl errctl;
	struct bofi_get_handles get_handles;
	int command = -1;
	int devctl_command = -1;
	int i;
	int fd;

	char buf[MAXPATHLEN];
	char path[MAXPATHLEN];

	if (argc == 3) {
		(void) strncpy(path, argv[1], MAXPATHLEN);

		for (i = 0; i < CMD_TABLE_SIZE; i++) {
			if (strcmp(argv[2], cmd_table[i].string) == NULL) {
				command = cmd_table[i].val;
				devctl_command = cmd_table[i].devctl_val;
			}
		}
		switch (devctl_command) {
		case BOFI_ONLINE:
		case BOFI_OFFLINE:
		case BOFI_GETPATH:
		case BOFI_GETSTATE:
			break;
		default:
			if (getnameinst(argv[1], &errctl.instance, buf,
			    MAXPATHLEN) == -1) {
				(void) fprintf(stderr,
				    "th_manage - invalid path\n");
				exit(1);
			}
			(void) strncpy(errctl.name, buf, MAXNAMELEN);
			errctl.namesize = strlen(errctl.name);
		}
	} else if (argc == 4) {
		errctl.namesize = strlen(argv[1]);
		(void) strncpy(errctl.name, argv[1], MAXNAMELEN);
		errctl.instance = atoi(argv[2]);
		for (i = 0; i < CMD_TABLE_SIZE; i++) {
			if (strcmp(argv[3], cmd_table[i].string) == NULL) {
				command = cmd_table[i].val;
				devctl_command = cmd_table[i].devctl_val;
			}
		}
		switch (devctl_command) {
		case BOFI_ONLINE:
		case BOFI_OFFLINE:
		case BOFI_GETPATH:
		case BOFI_GETSTATE:
			(void) strcpy(path, "/devices/");
			if (getpath(&path[8], errctl.instance, errctl.name,
			    MAXPATHLEN) == -1) {
				(void) fprintf(stderr,
				    "th_manage - invalid name/instance\n");
				exit(1);
			}
		default:
			break;
		}
	} else {
		(void) fprintf(stderr, "usage:\n");
		(void) fprintf(stderr,
		    "    th_manage name instance state\n");
		(void) fprintf(stderr,
		    "    th_manage path state\n");
		exit(2);
	}

	if (command == -1) {
		/*
		 * might have been a devctl command
		 */
		if (devctl_command == BOFI_ONLINE) {
			while (online_device(path) != 0) {
				(void) sleep(3);
			}
			exit(0);
		}
		if (devctl_command == BOFI_OFFLINE) {
			while (offline_device(path) != 0) {
				(void) sleep(3);
			}
			exit(0);
		}
		if (devctl_command == BOFI_GETSTATE) {
			if (getstate_device(path) != 0) {
				perror("th_manage - getstate failed");
				exit(1);
			} else {
				exit(0);
			}
		}
		if (devctl_command == BOFI_GETPATH) {
			(void) fprintf(stdout, "%s\n", path);
			exit(0);
		}
		(void) fprintf(stderr,
		    "th_manage: invalid command\n");
		(void) fprintf(stderr,
		    "     Command must be one of start, stop, broadcast, "
		    "get_handles,\n");
		(void) fprintf(stderr,
		    "     clear_acc_chk, clear_errors or clear_errdefs\n");
		exit(2);
	}
	fd = open("/devices/pseudo/bofi@0:bofi,ctl", O_RDWR);
	if (fd == -1) {
		perror("th_manage - open of bofi driver");
		exit(2);
	}
	if (command == BOFI_GET_HANDLES) {
		get_handles.namesize = errctl.namesize;
		(void) strncpy(get_handles.name, errctl.name, MAXNAMELEN);
		get_handles.instance =  errctl.instance;
		get_handles.buffer = buffer;
		get_handles.count = sizeof (buffer) - 1;
		if (ioctl(fd, command, &get_handles) == -1) {
			perror("th_manage - setting state failed");
			exit(2);
		}
		buffer[sizeof (buffer) - 1] = '\0';
		(void) fprintf(stdout, "%s", buffer);
		(void) fflush(stdout);
		exit(0);
	}
	if (errctl.instance == -1) {
		struct bofi_get_hdl_info hdli;
		struct handle_info *hip, *hp;
		int i, j, *instp;

		hdli.namesize = errctl.namesize;
		(void) strncpy(hdli.name, errctl.name, MAXNAMELEN);
		hdli.hdli = 0;
		hdli.count = 0;
		/*
		 * Ask the bofi driver for all handles created by the driver
		 * under test.
		 */
		if (ioctl(fd, BOFI_GET_HANDLE_INFO, &hdli) == -1) {
			perror("driver failed to return access handles");
			exit(1);
		}
		if (hdli.count == 0) {
			exit(0); /* no handles */
		}
		if ((hip = memalign(sizeof (void *),
		    hdli.count * sizeof (*hip))) == 0) {
			perror("out of memory");
			exit(1);
		}
		hdli.hdli = (caddr_t)hip;
		if (ioctl(fd, BOFI_GET_HANDLE_INFO, &hdli) == -1) {
			perror("couldn't obtain all handles");
			exit(1);
		}
		if ((instp = malloc((hdli.count + 1) * sizeof (*instp))) == 0) {
			perror("out of memory");
			exit(1);
		}
		*instp = -1;
		for (i = 0, hp = hip; i < hdli.count; hp++, i++) {
			for (j = 0; instp[j] != -1; j++)
				if (hp->instance == instp[j])
					break;
			if (instp[j] == -1) {
				instp[j] = hp->instance;
				instp[j+1] = -1;
			}
		}
		for (i = 0; instp[i] != -1; i++) {
			errctl.instance = instp[i];
			if (ioctl(fd, command, &errctl) == -1) {
				(void) fprintf(stderr,
				    "command failed on instance %d : %s\n",
				    errctl.instance, strerror(errno));
				exit(1);
			}
		}
	} else {
		if (ioctl(fd, command, &errctl) == -1) {
			perror("th_manage - setting state failed");
			exit(1);
		}
	}
	return (0);
}


/*
 * These functions provide access to the devctl functions,
 */
static int
online_device(char *path)
{
	devctl_hdl_t	dcp;

	if ((dcp = devctl_device_acquire(path, 0)) == NULL) {
		return (-1);
	}
	if ((devctl_device_online(dcp)) == -1) {
		devctl_release(dcp);
		return (-1);
	}
	devctl_release(dcp);
	return (0);
}


static int
offline_device(char *path)
{
	devctl_hdl_t	dcp;

	if ((dcp = devctl_device_acquire(path, 0)) == NULL) {
		return (-1);
	}
	if ((devctl_device_offline(dcp)) == -1) {
		devctl_release(dcp);
		return (-1);
	}
	devctl_release(dcp);
	return (0);
}

static int
getstate_device(char *path)
{
	devctl_hdl_t	dcp;
	uint_t		state = 0;

	if ((dcp = devctl_device_acquire(path, 0)) == NULL) {
		(void) printf("%s unknown unknown\n", path);
		return (-1);
	}
	if ((devctl_device_getstate(dcp, &state)) == -1) {
		(void) printf("%s unknown unknown\n", path);
		devctl_release(dcp);
		return (-1);
	}
	devctl_release(dcp);
	switch (state) {
	case DEVICE_DOWN:
		(void) printf("%s down not_busy\n", path);
		break;
	case DEVICE_OFFLINE:
		(void) printf("%s offline not_busy\n", path);
		break;
	case DEVICE_ONLINE:
		(void) printf("%s online not_busy\n", path);
		break;
	case (DEVICE_ONLINE | DEVICE_BUSY):
		(void) printf("%s online busy\n", path);
		break;
	case (DEVICE_DOWN | DEVICE_BUSY):
		(void) printf("%s down busy\n", path);
		break;
	default:
		(void) printf("%s unknown unknown\n", path);
		break;
	}
	return (0);
}

static int
getnameinst(char *path, int *instance, char *name, int namelen)
{
	di_node_t node;
	char *driver_name;

	if ((node = di_init(&path[8], DINFOSUBTREE)) == DI_NODE_NIL)
		return (-1);
	if ((driver_name = di_driver_name(node)) == NULL) {
		di_fini(node);
		return (-1);
	}
	*instance = di_instance(node);
	(void) strncpy(name, driver_name, namelen);
	di_fini(node);
	return (0);
}

struct walk_arg {
	char *path;
	int instance;
	char name[MAXPATHLEN];
	int found;
	int pathlen;
};

static int
walk_callback(di_node_t node, void *arg)
{
	struct walk_arg *warg = (struct walk_arg *)arg;
	char *driver_name;
	char *path;

	driver_name = di_driver_name(node);
	if (driver_name != NULL) {
		if (strcmp(driver_name, warg->name) == 0 &&
		    di_instance(node) == warg->instance) {
			path = di_devfs_path(node);
			if (path != NULL) {
				warg->found = 1;
				(void) strncpy(warg->path, path, warg->pathlen);
			}
			return (DI_WALK_TERMINATE);
		}
	}
	return (DI_WALK_CONTINUE);
}

static int
getpath(char *path, int instance, char *name, int pathlen)
{
	di_node_t node;
	struct walk_arg warg;

	warg.instance = instance;
	(void) strncpy(warg.name, name, MAXPATHLEN);
	warg.path = path;
	warg.pathlen = pathlen;
	warg.found = 0;
	if ((node = di_init("/", DINFOSUBTREE)) == DI_NODE_NIL)
		return (-1);
	if (di_walk_node(node, DI_WALK_CLDFIRST, &warg, walk_callback) == -1) {
		di_fini(node);
		return (-1);
	}
	if (warg.found == 0) {
		di_fini(node);
		return (-1);
	}
	di_fini(node);
	return (0);
}