/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (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 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <macros.h>
#include <dirent.h>
#include <libgen.h>
#include <libdevinfo.h>
#define	CFGA_PLUGIN_LIB
#include <config_admin.h>
#include "ap.h"

#ifdef	__x86
#include <libscf_priv.h>

static int fastreboot_disabled;
#endif	/* __x86 */

static cfga_err_t
ap_suspend_check(apd_t *a, int cmd, int first, int last, int *suspend)
{
	int c;
	int rc;
	int skip;
	int check;

	skip = a->opts.skip;

	/*
	 * Check if any of the steps in the sequence
	 * may require a suspension of service and ask
	 * the user to confirm.
	 */
	for (check = 0, c = first; c <= last; c++)
		if (mask(c) & skip)
			continue;
		else if ((rc = ap_suspend_query(a, c, &check)) != CFGA_OK)
			return (rc);

	*suspend = check;

	/*
	 * If a suspend is required, ask for user confirmation.
	 * The force flag overrides the user confirmation.
	 */
	if (check && (!ap_getopt(a, OPT_FORCE)) && (!ap_confirm(a))) {
		ap_err(a, ERR_CMD_NACK, cmd);
		return (CFGA_NACK);
	}

	return (CFGA_OK);
}

#define	AP_SEQ_OK	0
#define	AP_SEQ_NULL	1
#define	AP_SEQ_FAIL	-1

/*
 * Sequence a cfgadm state change command into driver commands.
 * The rstate and ostate of the AP are needed at this point
 * in order to compute the proper sequence.
 */
static int
ap_seq_get(apd_t *a, int cmd, int *first, int *last)
{
	int done = 0;
	int f = CMD_NONE;
	int l = CMD_NONE;
	cfga_stat_t rs, os;

	ap_state(a, &rs, &os);

	switch (rs) {
	case CFGA_STAT_EMPTY:
		switch (os) {
		case CFGA_STAT_UNCONFIGURED:
			switch (cmd) {
			case CMD_UNCONFIGURE:
				done++;
				break;
			default:
				break;
			}
			break;
		default:
			break;
		}
		break;
	case CFGA_STAT_DISCONNECTED:
		switch (os) {
		case CFGA_STAT_UNCONFIGURED:
			switch (cmd) {
			case CMD_DISCONNECT:
				/*
				 * skip the disconnect command since
				 * the rstate is already disconnected
				 */
				f = CMD_DISCONNECT;
				a->opts.skip |= mask(CMD_DISCONNECT);
				l = CMD_UNASSIGN;
				break;
			case CMD_UNCONFIGURE:
				done++;
				break;
			case CMD_CONNECT:
				f = CMD_ASSIGN;
				l = CMD_CONNECT;
				break;
			case CMD_CONFIGURE:
				f = CMD_ASSIGN;
				l = CMD_RCM_CAP_ADD;
				break;
			default:
				break;
			}
			break;
		default:
			break;
		}
		break;
	case CFGA_STAT_CONNECTED:
		switch (os) {
		case CFGA_STAT_UNCONFIGURED:
			switch (cmd) {
			case CMD_CONNECT:
			case CMD_UNCONFIGURE:
				done++;
				break;
			case CMD_DISCONNECT:
				f = CMD_DISCONNECT;
				l = CMD_UNASSIGN;
				break;
			case CMD_CONFIGURE:
				f = CMD_CONFIGURE;
				l = CMD_RCM_CAP_ADD;
				break;
			default:
				break;
			}
			break;
		case CFGA_STAT_CONFIGURED:
			switch (cmd) {
			case CMD_CONNECT:
				done++;
				break;
			case CMD_DISCONNECT:
				f = CMD_SUSPEND_CHECK;
				l = CMD_UNASSIGN;
				break;
			case CMD_CONFIGURE:
				f = CMD_CONFIGURE;
				l = CMD_RCM_CAP_ADD;
				break;
			case CMD_UNCONFIGURE:
				f = CMD_SUSPEND_CHECK;
				l = CMD_RCM_CAP_NOTIFY;
				break;
			default:
				break;
			}
			break;
		default:
			break;
		}
		break;
	default:
		break;
	}

	if (f == CMD_NONE) {
		if (done)
			return (AP_SEQ_NULL);
		ap_err(a, ERR_TRANS_INVAL, cmd);
		return (AP_SEQ_FAIL);
	}

	*first = f;
	*last = l;

	DBG("ap_seq(%d, %d, %d, %p, %p) = (%d, %d)\n",
	    rs, os, cmd, (void *)first, (void *)last, f, l);

	return (AP_SEQ_OK);
}

#define	DBG_RECOVER_MSG(f, l) \
	DBG("Sequencing recovery: first = %s, last = %s\n", \
	ap_cmd_name(f), ap_cmd_name(l))

cfga_err_t
ap_seq_exec(apd_t *a, int cmd, int first, int last)
{
	int c;
	int skip;
	int suspend;
	int resume;
	cfga_err_t rc;
	int recover_f = CMD_NONE;	/* first recovery cmd */
	int recover_l = CMD_NONE;	/* last recovery cmd */


	suspend = 0;
	resume = 0;

	skip = a->opts.skip;

	/*
	 * The unassign step is skipped unless explicity requested
	 * either by a -x request or as an option to a disconnect
	 * request.
	 */
	if (cmd != CMD_UNASSIGN && ap_getopt(a, OPT_UNASSIGN) == 0)
		skip |= mask(CMD_UNASSIGN);

	/*
	 * Check for platform options
	 */
	rc = ap_platopts_check(a, first, last);

	if (rc != CFGA_OK) {
		goto done;
	}

	for (c = first; c <= last; c++) {
		if (mask(c) & skip) {
			ap_msg(a, MSG_SKIP, c, a->target);
			continue;
		}

		DBG("exec %s\n", ap_cmd_name(c));

		/*
		 * If the suspend operation does not
		 * succeed, resume any devices already
		 * suspended as well as the device on
		 * which the operation failed.
		 */
		switch (c) {
		case CMD_SUSPEND_CHECK:
			/*
			 * Check whether the user allows a suspend
			 * operation if the suspend is required.
			 * Next step is to allow RCM clients to
			 * interpose on the suspend operation.
			 */
			rc = ap_suspend_check(a, cmd,
			    first + 1, last, &suspend);
			break;
		case CMD_RCM_SUSPEND:
			if (suspend && ((rc = ap_rcm_ctl(a, c)) == CFGA_OK)) {
				/*
				 * Mark the fact that a suspend operation
				 * is required, and that RCM clients have
				 * allowed the suspend.
				 */
				ap_setopt(a, OPT_SUSPEND_OK);
				resume++;
			}
			break;
		case CMD_RCM_RESUME:
			if (resume) {
				(void) ap_rcm_ctl(a, c);
				resume--;
			}
			break;
		case CMD_RCM_OFFLINE:
		case CMD_RCM_CAP_DEL:
			rc = ap_rcm_ctl(a, c);
			break;
		case CMD_RCM_ONLINE:
		case CMD_RCM_CAP_ADD:
		case CMD_RCM_REMOVE:
		case CMD_RCM_CAP_NOTIFY:
			(void) ap_rcm_ctl(a, c);
			break;

#ifdef	__x86
		/*
		 * Disable fast reboot if a CPU/MEM/IOH hotplug event happens.
		 * Note: this is a temporary solution and will be revised when
		 * fast reboot can support CPU/MEM/IOH DR operations in the
		 * future.
		 *
		 * ACPI BIOS generates some static ACPI tables, such as MADT,
		 * SRAT and SLIT, to describe the system hardware configuration
		 * on power-on. When a CPU/MEM/IOH hotplug event happens, those
		 * static tables won't be updated and will become stale.
		 *
		 * If we reset the system by fast reboot, BIOS will have no
		 * chance to regenerate those staled static tables. Fast reboot
		 * can't tolerate such inconsistency between staled ACPI tables
		 * and real hardware configuration yet.
		 *
		 * A temporary solution is introduced to disable fast reboot if
		 * CPU/MEM/IOH hotplug event happens. This solution should be
		 * revised when fast reboot is enhanced to support CPU/MEM/IOH
		 * DR operations.
		 */
		case CMD_ASSIGN:
		case CMD_POWERON:
		case CMD_POWEROFF:
		case CMD_UNASSIGN:
			if (!fastreboot_disabled &&
			    scf_fastreboot_default_set_transient(B_FALSE) ==
			    SCF_SUCCESS) {
				fastreboot_disabled = 1;
			}
			/* FALLTHROUGH */
#endif	/* __x86 */

		default:
			rc = ap_ioctl(a, c);
			break;
		}

		if (rc != CFGA_OK)
			break;

	}
done:

	if (resume)
		(void) ap_rcm_ctl(a, CMD_RCM_RESUME);

	/*
	 * Check if any operations failed. If so, attempt to rollback
	 * to previously known states.
	 * Note: The rollback is currently limited to RCM operations.
	 */
	if (rc != CFGA_OK) {
		if (c == CMD_UNCONFIGURE ||
		    c == CMD_RCM_OFFLINE ||
		    c == CMD_RCM_CAP_DEL) {
			DBG("ap_seq_exec: %s failed\n", ap_cmd_name(c));

			switch (c) {
			case CMD_UNCONFIGURE:
				/*
				 * If the unconfigure operation fails, perform
				 * an RCM_ONLINE and RCM_CAP_NOTIFY only. This
				 * keeps RCM clients consistent with the domain.
				 */
				recover_f = CMD_RCM_ONLINE;
				recover_l = CMD_RCM_ONLINE;
				DBG_RECOVER_MSG(recover_f, recover_l);
				(void) ap_seq_exec(a, cmd, recover_f,
				    recover_l);

				recover_f = CMD_RCM_CAP_NOTIFY;
				recover_l = CMD_RCM_CAP_NOTIFY;
				DBG_RECOVER_MSG(recover_f, recover_l);
				(void) ap_seq_exec(a, cmd, recover_f,
				    recover_l);
				break;
			case CMD_RCM_OFFLINE:
				recover_f = CMD_RCM_ONLINE;
				recover_l = CMD_RCM_CAP_ADD;
				DBG_RECOVER_MSG(recover_f, recover_l);
				(void) ap_seq_exec(a, cmd, recover_f,
				    recover_l);
				break;
			case CMD_RCM_CAP_DEL:
				recover_f = CMD_RCM_CAP_ADD;
				recover_l = CMD_RCM_CAP_ADD;
				DBG_RECOVER_MSG(recover_f, recover_l);
				(void) ap_seq_exec(a, cmd, recover_f,
				    recover_l);
				break;
			default:
				break;
			}

			DBG("recovery complete!\n");
		}
	}
	return (rc);
}

cfga_err_t
ap_cmd_exec(apd_t *a, int cmd)
{
	return (ap_seq_exec(a, cmd, cmd, cmd));
}

cfga_err_t
ap_cmd_seq(apd_t *a, int cmd)
{
	int first, last;
	cfga_err_t rc;

	switch (ap_seq_get(a, cmd, &first, &last)) {
	case AP_SEQ_OK:
		rc = ap_seq_exec(a, cmd, first, last);
		break;
	case AP_SEQ_NULL:
		rc = CFGA_OK;
		break;
	case AP_SEQ_FAIL:
	default:
		rc = CFGA_LIB_ERROR;
		break;
	}

	return (rc);
}