/*	$NetBSD: cmds.c,v 1.13 2011/08/14 17:57:44 christos Exp $	*/

/*-
 * Copyright (c) 2001 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Andrew Doran.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*-
 * Copyright (c) 1999 Michael Smith
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * from FreeBSD: command.c,v 1.2 2000/04/11 23:04:17 msmith Exp
 */

#ifndef lint
#include <sys/cdefs.h>
__RCSID("$NetBSD: cmds.c,v 1.13 2011/08/14 17:57:44 christos Exp $");
#endif /* not lint */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/queue.h>
#include <sys/endian.h>

#include <dev/ic/mlxreg.h>
#include <dev/ic/mlxio.h>

#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "extern.h"

static void	cmd_status0(struct mlx_disk *);
static void	cmd_check0(struct mlx_disk *);
static void	cmd_detach0(struct mlx_disk *);

static struct	mlx_rebuild_status rs;
static int	rstatus;

static struct {
	int	hwid;
	const char	*name;
} const mlx_ctlr_names[] = {
	{ 0x00, "960E/960M" },
	{ 0x01,	"960P/PD" },
	{ 0x02,	"960PL" },
	{ 0x10,	"960PG" },
	{ 0x11,	"960PJ" },
	{ 0x12,	"960PR" },
	{ 0x13,	"960PT" }, 
	{ 0x14,	"960PTL0" },
	{ 0x15,	"960PRL" } ,
	{ 0x16,	"960PTL1" },
	{ 0x20,	"1100PVX" },
	{ -1,	NULL },
};

/*
 * Status output
 */
static void
cmd_status0(struct mlx_disk *md)
{
	int result;
 
	result = md->hwunit;
	if (ioctl(mlxfd, MLXD_STATUS, &result) < 0)
		err(EXIT_FAILURE, "ioctl(MLXD_STATUS)");

	switch(result) {
	case MLX_SYSD_ONLINE:
		printf("%s: online\n", md->name);
		break;

	case MLX_SYSD_CRITICAL:
		printf("%s: critical\n", md->name);
		if (!rstatus)
			rstatus = 1;
		break;

	case MLX_SYSD_OFFLINE:
		printf("%s: offline\n", md->name);
		rstatus = 2;
		break;

	default:
		printf("%s: unknown status 0x%02x\n", md->name, result);
		break;
	}

	/* Rebuild/check in progress on this drive? */
	if (rs.rs_drive == md->hwunit &&
	    rs.rs_code != MLX_REBUILDSTAT_IDLE) {
		switch(rs.rs_code) {
		case MLX_REBUILDSTAT_REBUILDCHECK:
			printf(" [consistency check");
			break;

		case MLX_REBUILDSTAT_ADDCAPACITY:
			printf(" [add capacity");
			break;

		case MLX_REBUILDSTAT_ADDCAPACITYINIT:
			printf(" [add capacity init");
			break;

		default:
			printf(" [unknown operation");
			break;
		}

		printf(": %d/%d, %d%% complete]\n", rs.rs_remaining, rs.rs_size,
		   ((rs.rs_size - rs.rs_remaining) / (rs.rs_size / 100)));
	}
}

int
cmd_status(char **argv)
{

	if (ioctl(mlxfd, MLX_REBUILDSTAT, &rs) < 0)
		err(EXIT_FAILURE, "ioctl(MLX_REBUILDSTAT)");

	mlx_disk_iterate(cmd_status0);
	return (rstatus);
}

/*
 * Display controller status.
 */
int
cmd_cstatus(char **argv)
{
	struct mlx_enquiry2 enq;
	struct mlx_phys_drv pd;
	static char buf[80];
	const char *model;
	int channel, target;
	size_t i;

	model = NULL;	/* XXXGCC -Wuninitialized */

	for (i = 0; i < sizeof(mlx_ctlr_names) / sizeof(mlx_ctlr_names[0]); i++)
		if (ci.ci_hardware_id == mlx_ctlr_names[i].hwid) {
			model = mlx_ctlr_names[i].name;
			break;
		}

	if (i == sizeof(mlx_ctlr_names) / sizeof(mlx_ctlr_names[0])) {
		snprintf(buf, sizeof(buf), " model 0x%x", ci.ci_hardware_id);
		model = buf;
	}

	printf("DAC%s, %d channel%s, firmware %d.%02d-%c-%02d",
	    model, ci.ci_nchan,
	    ci.ci_nchan > 1 ? "s" : "",
	    ci.ci_firmware_id[0], ci.ci_firmware_id[1],
	    ci.ci_firmware_id[3], ci.ci_firmware_id[2]);
	if (ci.ci_mem_size != 0)
		printf(", %dMB RAM", ci.ci_mem_size >> 20);
	printf("\n");

	if (verbosity > 0 && ci.ci_iftype > 1) {
		uint32_t hid, sid;

		mlx_enquiry(&enq);
		memcpy(&hid, enq.me_hardware_id, sizeof(hid));
		memcpy(&sid, enq.me_firmware_id, sizeof(sid));

		printf("  Hardware ID\t\t\t0x%08x\n", le32toh(hid));
		printf("  Firmware ID\t\t\t0x%08x\n", le32toh(sid));
		printf("  Configured/Actual channels\t%d/%d\n",
		    enq.me_configured_channels, enq.me_actual_channels);
		printf("  Max Targets\t\t\t%d\n", enq.me_max_targets);
		printf("  Max Tags\t\t\t%d\n", enq.me_max_tags);
		printf("  Max System Drives\t\t%d\n", enq.me_max_sys_drives);
		printf("  Max Arms\t\t\t%d\n", enq.me_max_arms);
		printf("  Max Spans\t\t\t%d\n", enq.me_max_spans);
		printf("  DRAM/cache/flash/NVRAM size\t%d/%d/%d/%d\n",
		    le32toh(enq.me_mem_size), le32toh(enq.me_cache_size),
		    le32toh(enq.me_flash_size), le32toh(enq.me_nvram_size));
		printf("  DRAM type\t\t\t%d\n", le16toh(enq.me_mem_type));
		printf("  Clock Speed\t\t\t%dns\n",
		    le16toh(enq.me_clock_speed));
		printf("  Hardware Speed\t\t%dns\n",
		    le16toh(enq.me_hardware_speed));
		printf("  Max Commands\t\t\t%d\n",
		    le16toh(enq.me_max_commands));
		printf("  Max SG Entries\t\t%d\n", le16toh(enq.me_max_sg));
		printf("  Max DP\t\t\t%d\n", le16toh(enq.me_max_dp));
		printf("  Max IOD\t\t\t%d\n", le16toh(enq.me_max_iod));
		printf("  Max Comb\t\t\t%d\n", le16toh(enq.me_max_comb));
		printf("  Latency\t\t\t%ds\n", enq.me_latency);
		printf("  SCSI Timeout\t\t\t%ds\n", enq.me_scsi_timeout);
		printf("  Min Free Lines\t\t%d\n",
		    le16toh(enq.me_min_freelines));
		printf("  Rate Constant\t\t\t%d\n", enq.me_rate_const);
		printf("  MAXBLK\t\t\t%d\n", le16toh(enq.me_maxblk));
		printf("  Blocking Factor\t\t%d sectors\n",
		    le16toh(enq.me_blocking_factor));
		printf("  Cache Line Size\t\t%d blocks\n",
		    le16toh(enq.me_cacheline));
		printf("  SCSI Capability\t\t%s%dMHz, %d bit\n", 
		      enq.me_scsi_cap & (1<<4) ? "differential " : "",
		      (1 << ((enq.me_scsi_cap >> 2) & 3)) * 10,
		      8 << (enq.me_scsi_cap & 0x3));
		printf("  Firmware Build Number\t\t%d\n",
		    le16toh(enq.me_firmware_build));
		printf("  Fault Management Type\t\t%d\n",
		    enq.me_fault_mgmt_type);
#if 0
		printf("  Features\t\t\t%b\n", enq.me_firmware_features,
		      "\20\4Background Init\3Read Ahead\2MORE\1Cluster\n");
#endif
	} else if (verbosity > 0 && ci.ci_iftype == 1)
		warnx("can't be verbose for this firmware level");

	fflush(stdout);

	if (ci.ci_firmware_id[0] < 3) {
		warnx("can't display physical drives for this firmware level");
		return (0);
	}

	/*
	 * Fetch and print physical drive data.
	 */
	for (channel = 0; channel < enq.me_configured_channels; channel++) {
		for (target = 0; target < enq.me_max_targets; target++)
			if (mlx_get_device_state(channel, target, &pd) == 0 &&
			    (pd.pd_flags1 & MLX_PHYS_DRV_PRESENT) != 0)
				mlx_print_phys_drv(&pd, channel, target, "  ");
	}

	return (0);
}

/*
 * Recscan for new or changed system drives.
 */
int
cmd_rescan(char **argv)
{

	if (ioctl(mlxfd, MLX_RESCAN_DRIVES) < 0)
		err(EXIT_FAILURE, "rescan failed");
	return (0);
}

/*
 * Detach one or more system drives from a controller.
 */
static void
cmd_detach0(struct mlx_disk *md)
{

	if (ioctl(mlxfd, MLXD_DETACH, &md->hwunit) < 0)
		warn("can't detach %s", md->name);
}

int
cmd_detach(char **argv) 
{

	mlx_disk_iterate(cmd_detach0);
	return (0);
}

/*
 * Initiate a consistency check on a system drive.
 */
static void
cmd_check0(struct mlx_disk *md)
{
	int result;

	if (ioctl(mlxfd, MLXD_CHECKASYNC, &result) == 0)
		return;

	switch (result) {
	case 0x0002:
		warnx("one or more of the SCSI disks on which %s", md->name);
		warnx("depends is DEAD.");
		break;

	case 0x0105:
		warnx("drive %s is invalid, or not a drive which ", md->name);
		warnx("can be checked.");
		break;

	case 0x0106:
		warnx("drive rebuild or consistency check is already ");
		warnx("in progress on this controller.");
		break;

	default:
		err(EXIT_FAILURE, "ioctl(MLXD_CHECKASYNC)");
		/* NOTREACHED */
	}
}

int
cmd_check(char **argv)
{

	if (ci.ci_firmware_id[0] < 3) {
		warnx("action not supported by this firmware version");
		return (1);
	}

	mlx_disk_iterate(cmd_check0);
	return (0);
}

/*
 * Initiate a physical drive rebuild.
 */
int
cmd_rebuild(char **argv)
{
	struct mlx_rebuild_request rb;
	char *p;

	if (ci.ci_firmware_id[0] < 3) {
		warnx("action not supported by this firmware version");
		return (1);
	}

	if (argv[0] == NULL || argv[1] != NULL)
		usage();

	rb.rr_channel = (int)strtol(*argv, &p, 0);
	if (p[0] != ':' || p[1] == '\0')
		usage();

	rb.rr_target = (int)strtol(*argv, &p, 0);
	if (p[0] != '\0')
		usage();

	if (ioctl(mlxfd, MLX_REBUILDASYNC, &rb) == 0)
		return (0);

	switch (rb.rr_status) {
	case 0x0002:
		warnx("the drive at %d:%d is already ONLINE", rb.rr_channel,
		    rb.rr_target);
		break;

	case 0x0004:
		warnx("drive failed during rebuild");
		break;

	case 0x0105:
		warnx("there is no drive at %d:%d", rb.rr_channel,
		    rb.rr_target);
		break;

	case 0x0106:
		warnx("drive rebuild or consistency check is already in ");
		warnx("progress on this controller");
		break;

	default:
		err(EXIT_FAILURE, "ioctl(MLXD_CHECKASYNC)");
		/* NOTREACHED */
	}

	return(0);
}