FreeBSD-5.3/usr.sbin/mlxcontrol/command.c

Compare this file to the similar file:
Show the results in this format:

/*-
 * 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.
 *
 *	$FreeBSD: src/usr.sbin/mlxcontrol/command.c,v 1.2 2000/04/11 23:04:17 msmith Exp $
 */

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

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

#include "mlxcontrol.h"

static int	cmd_status(int argc, char *argv[]);
static int	cmd_rescan(int argc, char *argv[]);
static int	cmd_detach(int argc, char *argv[]);
static int	cmd_check(int argc, char *argv[]);
static int	cmd_rebuild(int argc, char *argv[]);
#ifdef SUPPORT_PAUSE
static int	cmd_pause(int argc, char *argv[]);
#endif
static int	cmd_help(int argc, char *argv[]);

extern int	cmd_config(int argc, char *argv[]);


struct 
{
    char	*cmd;
    int		(*func)(int argc, char *argv[]);
    char	*desc;
    char	*text;
} commands[] = {
    {"status",	cmd_status, 
     "displays device status",
     "  status [-qv] [<drive>...]\n"
     "      Display status for <drive> or all drives if none is listed\n"
     "  -q    Suppress output.\n"
     "  -v    Display verbose information.\n"
     "  Returns 0 if all drives tested are online, 1 if one or more are\n"
     "  critical, and 2 if one or more are offline."},
    {"rescan",	cmd_rescan, 
     "scan for new system drives",
     "  rescan <controller> [<controller>...]\n"
     "      Rescan <controller> for system drives.\n"
     "  rescan -a\n"
     "      Rescan all controllers for system drives."},
    {"detach",	cmd_detach,
     "detach system drives",
     "  detach <drive> [<drive>...]\n"
     "      Detaches <drive> from the controller.\n"
     "  detach -a <controller>\n"
     "      Detaches all drives on <controller>."},
    {"check",	cmd_check,
     "consistency-check a system drive",
     "  check <drive>\n"
     "      Requests a check and rebuild of the parity information on <drive>.\n"
     "      Note that each controller can only check one system drive at a time."},
    {"rebuild",	cmd_rebuild,
     "initiate a rebuild of a dead physical drive",
     "  rebuild <controller> <physdrive>\n"
     "      All system drives using space on the physical drive <physdrive>\n"
     "      are rebuilt, reconstructing all data on the drive.\n"
     "      Note that each controller can only perform one rebuild at a time."},
#ifdef SUPPORT_PAUSE
    {"pause",	cmd_pause,
     "pauses controller channels",
     "  pause [-t <howlong>] [-d <delay>] <controller> [<channel>...]\n"
     "      Pauses SCSI I/O on <channel> and <controller>.  If no channel is specified,\n"
     "      all channels are paused.\n"
     "  <howlong>   How long (seconds) to pause for (default 30).\n"
     "  <delay>     How long (seconds) to wait before pausing (default 30).\n"
     "  pause <controller> -c\n"
     "      Cancels any pending pause operation on <controller>."},
#endif
    {"config",	cmd_config,
     "examine and update controller configuration",
     "  config <controller>\n"
     "      Print configuration for <controller>."},
    {"help",	cmd_help,   
     "give help on usage",
     ""},
    {NULL, NULL, NULL, NULL}
};

/********************************************************************************
 * Command dispatch and global options parsing.
 */

int
main(int argc, char *argv[])
{
    int		ch, i, oargc;
    char	**oargv;
    
    oargc = argc;
    oargv = argv;
    while ((ch = getopt(argc, argv, "")) != -1)
	switch(ch) {
	default:
	    return(cmd_help(0, NULL));
	}

    argc -= optind;
    argv += optind;
    
    if (argc > 0)
	for (i = 0; commands[i].cmd != NULL; i++)
	    if (!strcmp(argv[0], commands[i].cmd))
		return(commands[i].func(argc, argv));

    return(cmd_help(oargc, oargv));
}

/********************************************************************************
 * Helptext output
 */
static int
cmd_help(int argc, char *argv[]) 
{
    int		i;
    
    if (argc > 1)
	for (i = 0; commands[i].cmd != NULL; i++)
	    if (!strcmp(argv[1], commands[i].cmd)) {
		fprintf(stderr, "%s\n", commands[i].text);
		fflush(stderr);
		return(0);
	    }

    if (argv != NULL)
	fprintf(stderr, "Unknown command '%s'.\n", argv[1]);    
    fprintf(stderr, "Valid commands are:\n");
    for (i = 0; commands[i].cmd != NULL; i++)
	fprintf(stderr, "  %-20s %s\n", commands[i].cmd, commands[i].desc);
    fflush(stderr);
    return(0);
}

/********************************************************************************
 * Status output
 *
 * status [-qv] [<device> ...]
 *		Prints status for <device>, or all if none listed.
 *
 * -q	Suppresses output, command returns 0 if devices are OK, 1 if one or
 *	more devices are critical, 2 if one or more devices are offline.
 */
static struct mlx_rebuild_status	rs;
static int				rs_ctrlr = -1;
static int				status_result = 0;

/* XXX more verbosity! */
static void
status_print(int unit, void *arg)
{
    int				verbosity = *(int *)arg;
    int				fd, result, ctrlr, sysdrive, statvalid;
    
    /* Find which controller and what system drive we are */
    statvalid = 0;
    if (mlxd_find_ctrlr(unit, &ctrlr, &sysdrive)) {
	warnx("couldn't get controller/drive for %s", drivepath(unit));
    } else {
	/* If we don't have rebuild stats for this controller, get them */
	if (rs_ctrlr == ctrlr) {
	    statvalid = 1;
	} else {
	    if ((fd = open(ctrlrpath(ctrlr), 0)) < 0) {
		warn("can't open %s", ctrlrpath(ctrlr));
	    } else {
		if (ioctl(fd, MLX_REBUILDSTAT, &rs) < 0) {
		    warn("ioctl MLX_REBUILDSTAT");
		} else {
		    rs_ctrlr = ctrlr;
		    statvalid = 1;
		}
		close(fd);
	    }
	}
    }

    /* Get the device */
    if ((fd = open(drivepath(unit), 0)) < 0) {
	warn("can't open %s", drivepath(unit));
	return;
    }

    /* Get its status */
    if (ioctl(fd, MLXD_STATUS, &result) < 0) {
	warn("ioctl MLXD_STATUS");
    } else {
	switch(result) {
	case MLX_SYSD_ONLINE:
	    if (verbosity > 0)
		printf("%s: online", drivename(unit));
	    break;
	case MLX_SYSD_CRITICAL:
	    if (verbosity > 0)
		printf("%s: critical", drivename(unit));
	    if (status_result < 1)
		status_result = 1;
	    break;
	case MLX_SYSD_OFFLINE:
	    if (verbosity > 0)
		printf("%s: offline", drivename(unit));
	    if (status_result < 2)
		status_result = 2;
	    break;
	default:
	    if (verbosity > 0) {
		printf("%s: unknown status 0x%x", drivename(unit), result);
	    }
	}
	if (verbosity > 0) {
	    /* rebuild/check in progress on this drive? */
	    if (statvalid && (rs_ctrlr == ctrlr) && 
		(rs.rs_drive == sysdrive) && (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");
		}
		printf(": %d/%d, %d%% complete]",
		       rs.rs_remaining, rs.rs_size, 
		       ((rs.rs_size - rs.rs_remaining) / (rs.rs_size / 100)));
	    }
	    printf("\n");
	}
    }
    close(fd);
}

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

static void
controller_print(int unit, void *arg)
{
    struct mlx_enquiry2	enq;
    struct mlx_phys_drv	pd;
    int			verbosity = *(int *)arg;
    static char		buf[80];
    char		*model;
    int			i, channel, target;

    if (verbosity == 0)
	return;

    /* fetch and print controller data */
    if (mlx_enquiry(unit, &enq)) {
	printf("mlx%d: error submitting ENQUIRY2\n", unit);
    } else {
	
	for (i = 0, model = NULL; mlx_controller_names[i].name != NULL; i++) {
	    if ((enq.me_hardware_id & 0xff) == mlx_controller_names[i].hwid) {
		model = mlx_controller_names[i].name;
		break;
	    }
	}
	if (model == NULL) {
	    sprintf(buf, " model 0x%x", enq.me_hardware_id & 0xff);
	    model = buf;
	}

	printf("mlx%d: DAC%s, %d channel%s, firmware %d.%02d-%c-%02d, %dMB RAM\n",
	       unit, model, 
	       enq.me_actual_channels, 
	       enq.me_actual_channels > 1 ? "s" : "",
	       enq.me_firmware_id & 0xff,
	       (enq.me_firmware_id >> 8) & 0xff,
	       (enq.me_firmware_id >> 16),
	       (enq.me_firmware_id >> 24) & 0xff,
	       enq.me_mem_size / (1024 * 1024));

	if (verbosity > 1) {
	    printf("  Hardware ID                 0x%08x\n", enq.me_hardware_id);
	    printf("  Firmware ID                 0x%08x\n", enq.me_firmware_id);
	    printf("  Configured/Actual channels  %d/%d\n", enq.me_configured_channels,
		      enq.me_actual_channels);
	    printf("  Max Targets                 %d\n", enq.me_max_targets);
	    printf("  Max Tags                    %d\n", enq.me_max_tags);
	    printf("  Max System Drives           %d\n", enq.me_max_sys_drives);
	    printf("  Max Arms                    %d\n", enq.me_max_arms);
	    printf("  Max Spans                   %d\n", enq.me_max_spans);
	    printf("  DRAM/cache/flash/NVRAM size %d/%d/%d/%d\n", enq.me_mem_size,
		      enq.me_cache_size, enq.me_flash_size, enq.me_nvram_size);
	    printf("  DRAM type                   %d\n", enq.me_mem_type);
	    printf("  Clock Speed                 %dns\n", enq.me_clock_speed);
	    printf("  Hardware Speed              %dns\n", enq.me_hardware_speed);
	    printf("  Max Commands                %d\n", enq.me_max_commands);
	    printf("  Max SG Entries              %d\n", enq.me_max_sg);
	    printf("  Max DP                      %d\n", enq.me_max_dp);
	    printf("  Max IOD                     %d\n", enq.me_max_iod);
	    printf("  Max Comb                    %d\n", enq.me_max_comb);
	    printf("  Latency                     %ds\n", enq.me_latency);
	    printf("  SCSI Timeout                %ds\n", enq.me_scsi_timeout);
	    printf("  Min Free Lines              %d\n", enq.me_min_freelines);
	    printf("  Rate Constant               %d\n", enq.me_rate_const);
	    printf("  MAXBLK                      %d\n", enq.me_maxblk);
	    printf("  Blocking Factor             %d sectors\n", enq.me_blocking_factor);
	    printf("  Cache Line Size             %d blocks\n", enq.me_cacheline);
	    printf("  SCSI Capability             %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       %d\n", enq.me_firmware_build);
	    printf("  Fault Management Type       %d\n", enq.me_fault_mgmt_type);
#if 0
	    printf("  Features                    %b\n", enq.me_firmware_features,
		      "\20\4Background Init\3Read Ahead\2MORE\1Cluster\n");
#endif
	}

	/* 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(unit, channel, target, &pd) == 0) &&
		    (pd.pd_flags1 & MLX_PHYS_DRV_PRESENT)) {
		    mlx_print_phys_drv(&pd, channel, target, "  ", verbosity - 1);
		    if (verbosity > 1) {
			/* XXX print device statistics? */
		    }
		}
	    }
	}
    }
}

static int
cmd_status(int argc, char *argv[])
{
    int		ch, verbosity = 1, i, unit;

    optreset = 1;
    optind = 1;
    while ((ch = getopt(argc, argv, "qv")) != -1)
	switch(ch) {
	case 'q':
	    verbosity = 0;
	    break;
	case 'v':
	    verbosity = 2;
	    break;
	default:
	    return(cmd_help(argc, argv));
	}
    argc -= optind;
    argv += optind;

    if (argc < 1) {
	mlx_foreach(controller_print, &verbosity);
	mlxd_foreach(status_print, &verbosity);
    } else {
	for (i = 0; i < argc; i++) {
	    if ((unit = driveunit(argv[i])) == -1) {
		warnx("'%s' is not a valid drive", argv[i]);
	    } else {
		status_print(unit, &verbosity);
	    }
	}
    }
    return(status_result);
}

/********************************************************************************
 * Recscan for system drives on one or more controllers.
 *
 * rescan <controller> [<controller>...]
 * rescan -a
 */
static void
rescan_ctrlr(int unit, void *junk)
{
    int		fd;
    
    /* Get the device */
    if ((fd = open(ctrlrpath(unit), 0)) < 0) {
	warn("can't open %s", ctrlrpath(unit));
	return;
    }

    if (ioctl(fd, MLX_RESCAN_DRIVES) < 0)
	warn("can't rescan %s", ctrlrname(unit));
    close(fd);
}

static int
cmd_rescan(int argc, char *argv[]) 
{
    int		all = 0, i, ch, unit;

    optreset = 1;
    optind = 1;
    while ((ch = getopt(argc, argv, "a")) != -1)
	switch(ch) {
	case 'a':
	    all = 1;
	    break;
	default:
	    return(cmd_help(argc, argv));
	}
    argc -= optind;
    argv += optind;

    if (all) {
	mlx_foreach(rescan_ctrlr, NULL);
    } else {
	for (i = 0; i < argc; i++) {
	    if ((unit = ctrlrunit(argv[i])) == -1) {
		warnx("'%s' is not a valid controller", argv[i]);
	    } else {
		rescan_ctrlr(unit, NULL);
	    }
	}
    }
    return(0);
}

/********************************************************************************
 * Detach one or more system drives from a controller.
 *
 * detach <drive> [<drive>...]
 *		Detach <drive>.
 *
 * detach -a <controller> [<controller>...]
 *		Detach all drives on <controller>.
 *
 */
static void
detach_drive(int unit, void *arg)
{
    int		fd;
    
    /* Get the device */
    if ((fd = open(ctrlrpath(unit), 0)) < 0) {
	warn("can't open %s", ctrlrpath(unit));
	return;
    }

    if (ioctl(fd, MLX_DETACH_DRIVE, &unit) < 0)
	warn("can't detach %s", drivename(unit));
    close(fd);
}

static int
cmd_detach(int argc, char *argv[]) 
{
    struct mlxd_foreach_action	ma;
    int				all = 0, i, ch, unit;

    optreset = 1;
    optind = 1;
    while ((ch = getopt(argc, argv, "a")) != -1)
	switch(ch) {
	case 'a':
	    all = 1;
	    break;
	default:
	    return(cmd_help(argc, argv));
	}
    argc -= optind;
    argv += optind;

    if (all) {
	ma.func = detach_drive;
	ma.arg = &unit;
	for (i = 0; i < argc; i++) {
	    if ((unit = ctrlrunit(argv[i])) == -1) {
		warnx("'%s' is not a valid controller", argv[i]);
	    } else {
		mlxd_foreach_ctrlr(unit, &ma);
	    }
	}
    } else {
	for (i = 0; i < argc; i++) {
	    if ((unit = driveunit(argv[i])) == -1) {
		warnx("'%s' is not a valid drive", argv[i]);
	    } else {
		/* run across all controllers to find this drive */
		mlx_foreach(detach_drive, &unit);
	    }
	}
    }
    return(0);
}

/********************************************************************************
 * Initiate a consistency check on a system drive.
 *
 * check [<drive>]
 *	Start a check of <drive>
 *
 */
static int
cmd_check(int argc, char *argv[])
{
    int		unit, fd, result;

    if (argc != 2)
	return(cmd_help(argc, argv));

    if ((unit = driveunit(argv[1])) == -1) {
	warnx("'%s' is not a valid drive", argv[1]);
    } else {
	
	/* Get the device */
	if ((fd = open(drivepath(unit), 0)) < 0) {
	    warn("can't open %s", drivepath(unit));
	} else {
	    /* Try to start the check */
	    if ((ioctl(fd, MLXD_CHECKASYNC, &result)) < 0) {
		switch(result) {
		case 0x0002:
		    warnx("one or more of the SCSI disks on which the drive '%s' depends is DEAD", argv[1]);
		    break;
		case 0x0105:
		    warnx("drive %s is invalid, or not a drive which can be checked", argv[1]);
		    break;
		case 0x0106:
		    warnx("drive rebuild or consistency check is already in progress on this controller");
		    break;
		default:
		    warn("ioctl MLXD_CHECKASYNC");
		}
	    }
	}
    }
    return(0);
}

/********************************************************************************
 * Initiate a physical drive rebuild
 *
 * rebuild <controller> <channel>:<target>
 *	Start a rebuild of <controller>:<channel>:<target>
 *
 */
static int
cmd_rebuild(int argc, char *argv[])
{
    struct mlx_rebuild_request	rb;
    int				unit, fd;

    if (argc != 3)
	return(cmd_help(argc, argv));

    /* parse arguments */
    if ((unit = ctrlrunit(argv[1])) == -1) {
	warnx("'%s' is not a valid controller", argv[1]);
	return(1);
    }
    /* try diskXXXX and unknownXXXX as we report the latter for a dead drive ... */
    if ((sscanf(argv[2], "disk%2d%2d", &rb.rr_channel, &rb.rr_target) != 2) &&
	(sscanf(argv[2], "unknown%2d%2d", &rb.rr_channel, &rb.rr_target) != 2)) {	
	warnx("'%s' is not a valid physical drive", argv[2]);
	return(1);
    }
    /* get the device */
    if ((fd = open(ctrlrpath(unit), 0)) < 0) {
	warn("can't open %s", ctrlrpath(unit));
	return(1);
    }
    /* try to start the rebuild */
    if ((ioctl(fd, MLX_REBUILDASYNC, &rb)) < 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 channel %d, target %d", rb.rr_channel, rb.rr_target);
	    break;
	case 0x0106:
	    warnx("drive rebuild or consistency check is already in progress on this controller");
	    break;
	default:
	    warn("ioctl MLXD_CHECKASYNC");
	}
    }
    return(0);
}

#ifdef SUPPORT_PAUSE
/********************************************************************************
 * Pause one or more channels on a controller
 *
 * pause [-d <delay>] [-t <time>] <controller> [<channel>...]
 *		Pauses <channel> (or all channels) for <time> seconds after a
 *		delay of <delay> seconds.
 * pause <controller> -c
 *		Cancels pending pause
 */
static int
cmd_pause(int argc, char *argv[]) 
{
    struct mlx_pause	mp;
    int			unit, i, ch, fd, cancel = 0;
    char		*cp;
    int			oargc = argc;
    char		**oargv = argv;

    mp.mp_which = 0;
    mp.mp_when = 30;
    mp.mp_howlong = 30;
    optreset = 1;
    optind = 1;
    while ((ch = getopt(argc, argv, "cd:t:")) != -1)
	switch(ch) {
	case 'c':
	    cancel = 1;
	    break;
	case 'd':
	    mp.mp_when = strtol(optarg, &cp, 0);
	    if (*cp != 0)
		return(cmd_help(argc, argv));
	    break;
	case 't':
	    mp.mp_howlong = strtol(optarg, &cp, 0);
	    if (*cp != 0)
		return(cmd_help(argc, argv));
	    break;
	default:
	    return(cmd_help(argc, argv));
	}
    argc -= optind;
    argv += optind;

    /* get controller unit number that we're working on */
    if ((argc < 1) || ((unit = ctrlrunit(argv[0])) == -1))
	return(cmd_help(oargc, oargv));

    /* Get the device */
    if ((fd = open(ctrlrpath(unit), 0)) < 0) {
	warn("can't open %s", ctrlrpath(unit));
	return(1);
    }

    if (argc == 1) {
	/* controller-wide pause/cancel */
	mp.mp_which = cancel ? MLX_PAUSE_CANCEL : MLX_PAUSE_ALL;
    } else {
	for (i = 1; i < argc; i++) {
	    ch = strtol(argv[i], &cp, 0);
	    if (*cp != 0) {
		warnx("bad channel number '%s'", argv[i]);
		continue;
	    } else {
		mp.mp_which |= (1 << ch);
	    }
	}
    }
    if ((ioctl(fd, MLX_PAUSE_CHANNEL, &mp)) < 0)
	warn("couldn't %s %s", cancel ? "cancel pause on" : "pause", ctrlrname(unit));
    close(fd);
    return(0);
}
#endif	/* SUPPORT_PAUSE */