/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* 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$
*/
#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_REBUILDASYNC");
}
}
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 */