summaryrefslogtreecommitdiff
path: root/usr.sbin/mptutil
diff options
context:
space:
mode:
authorScott Long <scottl@FreeBSD.org>2009-08-14 13:13:12 +0000
committerScott Long <scottl@FreeBSD.org>2009-08-14 13:13:12 +0000
commitfc58801ccc318e391722e23c01826884edd25bf9 (patch)
tree0183f7ea0315a5ab4ebbe7eaca64082dbe89e351 /usr.sbin/mptutil
parent6d600732ebfcb814df88e9e29e66619262609bc4 (diff)
downloadsrc-test-fc58801ccc318e391722e23c01826884edd25bf9.tar.gz
src-test-fc58801ccc318e391722e23c01826884edd25bf9.zip
Add mptutil, a basic utility for managing MPT SCSI/SATA/SAS controllers.
Drive and controller status can be reported, basic attributes changed, and arrays and spares can be created and deleted. Approved by: re Obtained from: Yahoo! Inc.
Notes
Notes: svn path=/head/; revision=196212
Diffstat (limited to 'usr.sbin/mptutil')
-rw-r--r--usr.sbin/mptutil/Makefile19
-rw-r--r--usr.sbin/mptutil/mpt_cam.c569
-rw-r--r--usr.sbin/mptutil/mpt_cmd.c639
-rw-r--r--usr.sbin/mptutil/mpt_config.c1160
-rw-r--r--usr.sbin/mptutil/mpt_drive.c395
-rw-r--r--usr.sbin/mptutil/mpt_evt.c155
-rw-r--r--usr.sbin/mptutil/mpt_show.c559
-rw-r--r--usr.sbin/mptutil/mpt_volume.c248
-rw-r--r--usr.sbin/mptutil/mptutil.8383
-rw-r--r--usr.sbin/mptutil/mptutil.c123
-rw-r--r--usr.sbin/mptutil/mptutil.h178
11 files changed, 4428 insertions, 0 deletions
diff --git a/usr.sbin/mptutil/Makefile b/usr.sbin/mptutil/Makefile
new file mode 100644
index 0000000000000..4abf66e4657dd
--- /dev/null
+++ b/usr.sbin/mptutil/Makefile
@@ -0,0 +1,19 @@
+# $FreeBSD$
+
+PROG= mptutil
+SRCS= mptutil.c mpt_cam.c mpt_cmd.c mpt_config.c mpt_drive.c mpt_evt.c \
+ mpt_show.c mpt_volume.c
+# mpt_flash.c
+MAN= mptutil.8
+
+WARNS?= 3
+
+DPADD+= ${LIBCAM} ${LIBUTIL}
+LDADD+= -lcam -lutil
+
+# Here be dragons
+.ifdef DEBUG
+CFLAGS+= -DDEBUG
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/mptutil/mpt_cam.c b/usr.sbin/mptutil/mpt_cam.c
new file mode 100644
index 0000000000000..0d20c7db60194
--- /dev/null
+++ b/usr.sbin/mptutil/mpt_cam.c
@@ -0,0 +1,569 @@
+/*-
+ * Copyright (c) 2008 Yahoo!, Inc.
+ * All rights reserved.
+ * Written by: John Baldwin <jhb@FreeBSD.org>
+ *
+ * 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.
+ * 3. Neither the name of the author nor the names of any co-contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * 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.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <camlib.h>
+#include <cam/scsi/scsi_message.h>
+#include <cam/scsi/scsi_pass.h>
+
+#include "mptutil.h"
+
+static int xptfd;
+
+static int
+xpt_open(void)
+{
+
+ if (xptfd == 0)
+ xptfd = open(XPT_DEVICE, O_RDWR);
+ return (xptfd);
+}
+
+int
+mpt_query_disk(U8 VolumeBus, U8 VolumeID, struct mpt_query_disk *qd)
+{
+ struct bus_match_pattern *b;
+ struct periph_match_pattern *p;
+ struct periph_match_result *r;
+ union ccb ccb;
+ size_t bufsize;
+ int i;
+
+ /* mpt(4) only handles devices on bus 0. */
+ if (VolumeBus != 0)
+ return (ENXIO);
+
+ if (xpt_open() < 0)
+ return (ENXIO);
+
+ bzero(&ccb, sizeof(ccb));
+
+ ccb.ccb_h.func_code = XPT_DEV_MATCH;
+ ccb.ccb_h.path_id = CAM_XPT_PATH_ID;
+ ccb.ccb_h.target_id = CAM_TARGET_WILDCARD;
+ ccb.ccb_h.target_lun = CAM_LUN_WILDCARD;
+
+ bufsize = sizeof(struct dev_match_result) * 5;
+ ccb.cdm.num_matches = 0;
+ ccb.cdm.match_buf_len = bufsize;
+ ccb.cdm.matches = calloc(1, bufsize);
+
+ bufsize = sizeof(struct dev_match_pattern) * 2;
+ ccb.cdm.num_patterns = 2;
+ ccb.cdm.pattern_buf_len = bufsize;
+ ccb.cdm.patterns = calloc(1, bufsize);
+
+ /* Match mptX bus 0. */
+ ccb.cdm.patterns[0].type = DEV_MATCH_BUS;
+ b = &ccb.cdm.patterns[0].pattern.bus_pattern;
+ snprintf(b->dev_name, sizeof(b->dev_name), "mpt");
+ b->unit_number = mpt_unit;
+ b->bus_id = 0;
+ b->flags = BUS_MATCH_NAME | BUS_MATCH_UNIT | BUS_MATCH_BUS_ID;
+
+ /* Look for a "da" device at the specified target and lun. */
+ ccb.cdm.patterns[1].type = DEV_MATCH_PERIPH;
+ p = &ccb.cdm.patterns[1].pattern.periph_pattern;
+ snprintf(p->periph_name, sizeof(p->periph_name), "da");
+ p->target_id = VolumeID;
+ p->flags = PERIPH_MATCH_NAME | PERIPH_MATCH_TARGET;
+
+ if (ioctl(xptfd, CAMIOCOMMAND, &ccb) < 0) {
+ i = errno;
+ free(ccb.cdm.matches);
+ free(ccb.cdm.patterns);
+ return (i);
+ }
+ free(ccb.cdm.patterns);
+
+ if (((ccb.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) ||
+ (ccb.cdm.status != CAM_DEV_MATCH_LAST)) {
+ warnx("mpt_query_disk got CAM error %#x, CDM error %d\n",
+ ccb.ccb_h.status, ccb.cdm.status);
+ free(ccb.cdm.matches);
+ return (EIO);
+ }
+
+ /*
+ * We should have exactly 2 matches, 1 for the bus and 1 for
+ * the peripheral. However, if we only have 1 match and it is
+ * for the bus, don't print an error message and return
+ * ENOENT.
+ */
+ if (ccb.cdm.num_matches == 1 &&
+ ccb.cdm.matches[0].type == DEV_MATCH_BUS) {
+ free(ccb.cdm.matches);
+ return (ENOENT);
+ }
+ if (ccb.cdm.num_matches != 2) {
+ warnx("mpt_query_disk got %d matches, expected 2",
+ ccb.cdm.num_matches);
+ free(ccb.cdm.matches);
+ return (EIO);
+ }
+ if (ccb.cdm.matches[0].type != DEV_MATCH_BUS ||
+ ccb.cdm.matches[1].type != DEV_MATCH_PERIPH) {
+ warnx("mpt_query_disk got wrong CAM matches");
+ free(ccb.cdm.matches);
+ return (EIO);
+ }
+
+ /* Copy out the data. */
+ r = &ccb.cdm.matches[1].result.periph_result;
+ snprintf(qd->devname, sizeof(qd->devname), "%s%d", r->periph_name,
+ r->unit_number);
+ free(ccb.cdm.matches);
+
+ return (0);
+}
+
+static int
+periph_is_volume(CONFIG_PAGE_IOC_2 *ioc2, struct periph_match_result *r)
+{
+ CONFIG_PAGE_IOC_2_RAID_VOL *vol;
+ int i;
+
+ if (ioc2 == NULL)
+ return (0);
+ vol = ioc2->RaidVolume;
+ for (i = 0; i < ioc2->NumActiveVolumes; vol++, i++) {
+ if (vol->VolumeBus == 0 && vol->VolumeID == r->target_id)
+ return (1);
+ }
+ return (0);
+}
+
+/* Much borrowed from scsireadcapacity() in src/sbin/camcontrol/camcontrol.c. */
+static int
+fetch_scsi_capacity(struct cam_device *dev, struct mpt_standalone_disk *disk)
+{
+ struct scsi_read_capacity_data rcap;
+ struct scsi_read_capacity_data_long rcaplong;
+ union ccb *ccb;
+ int error;
+
+ ccb = cam_getccb(dev);
+ if (ccb == NULL)
+ return (ENOMEM);
+
+ /* Zero the rest of the ccb. */
+ bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio) -
+ sizeof(struct ccb_hdr));
+
+ scsi_read_capacity(&ccb->csio, 1, NULL, MSG_SIMPLE_Q_TAG, &rcap,
+ SSD_FULL_SIZE, 5000);
+
+ /* Disable freezing the device queue */
+ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+
+ if (cam_send_ccb(dev, ccb) < 0) {
+ error = errno;
+ cam_freeccb(ccb);
+ return (error);
+ }
+
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ cam_freeccb(ccb);
+ return (EIO);
+ }
+ cam_freeccb(ccb);
+
+ /*
+ * A last block of 2^32-1 means that the true capacity is over 2TB,
+ * and we need to issue the long READ CAPACITY to get the real
+ * capacity. Otherwise, we're all set.
+ */
+ if (scsi_4btoul(rcap.addr) != 0xffffffff) {
+ disk->maxlba = scsi_4btoul(rcap.addr);
+ return (0);
+ }
+
+ /* Zero the rest of the ccb. */
+ bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio) -
+ sizeof(struct ccb_hdr));
+
+ scsi_read_capacity_16(&ccb->csio, 1, NULL, MSG_SIMPLE_Q_TAG, 0, 0, 0,
+ &rcaplong, SSD_FULL_SIZE, 5000);
+
+ /* Disable freezing the device queue */
+ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+
+ if (cam_send_ccb(dev, ccb) < 0) {
+ error = errno;
+ cam_freeccb(ccb);
+ return (error);
+ }
+
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ cam_freeccb(ccb);
+ return (EIO);
+ }
+ cam_freeccb(ccb);
+
+ disk->maxlba = scsi_8btou64(rcaplong.addr);
+ return (0);
+}
+
+/* Borrowed heavily from scsi_all.c:scsi_print_inquiry(). */
+static void
+format_scsi_inquiry(struct mpt_standalone_disk *disk,
+ struct scsi_inquiry_data *inq_data)
+{
+ char vendor[16], product[48], revision[16], rstr[12];
+
+ if (SID_QUAL_IS_VENDOR_UNIQUE(inq_data))
+ return;
+ if (SID_TYPE(inq_data) != T_DIRECT)
+ return;
+ if (SID_QUAL(inq_data) != SID_QUAL_LU_CONNECTED)
+ return;
+
+ cam_strvis(vendor, inq_data->vendor, sizeof(inq_data->vendor),
+ sizeof(vendor));
+ cam_strvis(product, inq_data->product, sizeof(inq_data->product),
+ sizeof(product));
+ cam_strvis(revision, inq_data->revision, sizeof(inq_data->revision),
+ sizeof(revision));
+
+ /* Hack for SATA disks, no idea how to tell speed. */
+ if (strcmp(vendor, "ATA") == 0) {
+ snprintf(disk->inqstring, sizeof(disk->inqstring),
+ "<%s %s> SATA", product, revision);
+ return;
+ }
+
+ switch (SID_ANSI_REV(inq_data)) {
+ case SCSI_REV_CCS:
+ strcpy(rstr, "SCSI-CCS");
+ break;
+ case 5:
+ strcpy(rstr, "SAS");
+ break;
+ default:
+ snprintf(rstr, sizeof (rstr), "SCSI-%d",
+ SID_ANSI_REV(inq_data));
+ break;
+ }
+ snprintf(disk->inqstring, sizeof(disk->inqstring), "<%s %s %s> %s",
+ vendor, product, revision, rstr);
+}
+
+/* Much borrowed from scsiinquiry() in src/sbin/camcontrol/camcontrol.c. */
+static int
+fetch_scsi_inquiry(struct cam_device *dev, struct mpt_standalone_disk *disk)
+{
+ struct scsi_inquiry_data *inq_buf;
+ union ccb *ccb;
+ int error;
+
+ ccb = cam_getccb(dev);
+ if (ccb == NULL)
+ return (ENOMEM);
+
+ /* Zero the rest of the ccb. */
+ bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio) -
+ sizeof(struct ccb_hdr));
+
+ inq_buf = calloc(1, sizeof(*inq_buf));
+ if (inq_buf == NULL) {
+ cam_freeccb(ccb);
+ return (ENOMEM);
+ }
+ scsi_inquiry(&ccb->csio, 1, NULL, MSG_SIMPLE_Q_TAG, (void *)inq_buf,
+ SHORT_INQUIRY_LENGTH, 0, 0, SSD_FULL_SIZE, 5000);
+
+ /* Disable freezing the device queue */
+ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+
+ if (cam_send_ccb(dev, ccb) < 0) {
+ error = errno;
+ free(inq_buf);
+ cam_freeccb(ccb);
+ return (error);
+ }
+
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ free(inq_buf);
+ cam_freeccb(ccb);
+ return (EIO);
+ }
+
+ cam_freeccb(ccb);
+ format_scsi_inquiry(disk, inq_buf);
+ free(inq_buf);
+ return (0);
+}
+
+int
+mpt_fetch_disks(int fd, int *ndisks, struct mpt_standalone_disk **disksp)
+{
+ CONFIG_PAGE_IOC_2 *ioc2;
+ struct mpt_standalone_disk *disks;
+ struct bus_match_pattern *b;
+ struct periph_match_pattern *p;
+ struct periph_match_result *r;
+ struct cam_device *dev;
+ union ccb ccb;
+ size_t bufsize;
+ u_int i;
+ int count;
+
+ if (xpt_open() < 0)
+ return (ENXIO);
+
+ for (count = 100;; count+= 100) {
+ /* Try to fetch 'count' disks in one go. */
+ bzero(&ccb, sizeof(ccb));
+
+ ccb.ccb_h.func_code = XPT_DEV_MATCH;
+
+ bufsize = sizeof(struct dev_match_result) * (count + 2);
+ ccb.cdm.num_matches = 0;
+ ccb.cdm.match_buf_len = bufsize;
+ ccb.cdm.matches = calloc(1, bufsize);
+
+ bufsize = sizeof(struct dev_match_pattern) * 2;
+ ccb.cdm.num_patterns = 2;
+ ccb.cdm.pattern_buf_len = bufsize;
+ ccb.cdm.patterns = calloc(1, bufsize);
+
+ /* Match mptX bus 0. */
+ ccb.cdm.patterns[0].type = DEV_MATCH_BUS;
+ b = &ccb.cdm.patterns[0].pattern.bus_pattern;
+ snprintf(b->dev_name, sizeof(b->dev_name), "mpt");
+ b->unit_number = mpt_unit;
+ b->bus_id = 0;
+ b->flags = BUS_MATCH_NAME | BUS_MATCH_UNIT | BUS_MATCH_BUS_ID;
+
+ /* Match any "da" peripherals. */
+ ccb.cdm.patterns[1].type = DEV_MATCH_PERIPH;
+ p = &ccb.cdm.patterns[1].pattern.periph_pattern;
+ snprintf(p->periph_name, sizeof(p->periph_name), "da");
+ p->flags = PERIPH_MATCH_NAME;
+
+ if (ioctl(xptfd, CAMIOCOMMAND, &ccb) < 0) {
+ i = errno;
+ free(ccb.cdm.matches);
+ free(ccb.cdm.patterns);
+ return (i);
+ }
+ free(ccb.cdm.patterns);
+
+ /* Check for CCB errors. */
+ if ((ccb.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ free(ccb.cdm.matches);
+ return (EIO);
+ }
+
+ /* If we need a longer list, try again. */
+ if (ccb.cdm.status == CAM_DEV_MATCH_MORE) {
+ free(ccb.cdm.matches);
+ continue;
+ }
+
+ /* If we got an error, abort. */
+ if (ccb.cdm.status != CAM_DEV_MATCH_LAST) {
+ free(ccb.cdm.matches);
+ return (EIO);
+ }
+ break;
+ }
+
+ /*
+ * We should have N + 1 matches, 1 for the bus and 1 for each
+ * "da" device.
+ */
+ if (ccb.cdm.num_matches < 1) {
+ warnx("mpt_fetch_disks didn't get any matches");
+ free(ccb.cdm.matches);
+ return (EIO);
+ }
+ if (ccb.cdm.matches[0].type != DEV_MATCH_BUS) {
+ warnx("mpt_fetch_disks got wrong CAM matches");
+ free(ccb.cdm.matches);
+ return (EIO);
+ }
+ for (i = 1; i < ccb.cdm.num_matches; i++) {
+ if (ccb.cdm.matches[i].type != DEV_MATCH_PERIPH) {
+ warnx("mpt_fetch_disks got wrong CAM matches");
+ free(ccb.cdm.matches);
+ return (EIO);
+ }
+ }
+
+ /* Shortcut if we don't have any "da" devices. */
+ if (ccb.cdm.num_matches == 1) {
+ free(ccb.cdm.matches);
+ *ndisks = 0;
+ *disksp = NULL;
+ return (0);
+ }
+
+ /*
+ * Some of the "da" peripherals may be for RAID volumes, so
+ * fetch the IOC 2 page (list of RAID volumes) so we can
+ * exclude them from the list.
+ */
+ ioc2 = mpt_read_ioc_page(fd, 2, NULL);
+ disks = calloc(ccb.cdm.num_matches, sizeof(*disks));
+ count = 0;
+ for (i = 1; i < ccb.cdm.num_matches; i++) {
+ r = &ccb.cdm.matches[i].result.periph_result;
+ if (periph_is_volume(ioc2, r))
+ continue;
+ disks[count].bus = 0;
+ disks[count].target = r->target_id;
+ snprintf(disks[count].devname, sizeof(disks[count].devname),
+ "%s%d", r->periph_name, r->unit_number);
+
+ dev = cam_open_device(disks[count].devname, O_RDWR);
+ if (dev != NULL) {
+ fetch_scsi_capacity(dev, &disks[count]);
+ fetch_scsi_inquiry(dev, &disks[count]);
+ cam_close_device(dev);
+ }
+ count++;
+ }
+ free(ccb.cdm.matches);
+ free(ioc2);
+
+ *ndisks = count;
+ *disksp = disks;
+ return (0);
+}
+
+/*
+ * Instruct the mpt(4) device to rescan its busses to find new devices
+ * such as disks whose RAID physdisk page was removed or volumes that
+ * were created. If id is -1, the entire bus is rescanned.
+ * Otherwise, only devices at the specified ID are rescanned. If bus
+ * is -1, then all busses are scanned instead of the specified bus.
+ * Note that currently, only bus 0 is supported.
+ */
+int
+mpt_rescan_bus(int bus, int id)
+{
+ struct bus_match_pattern *b;
+ union ccb ccb;
+ path_id_t path_id;
+ size_t bufsize;
+
+ /* mpt(4) only handles devices on bus 0. */
+ if (bus != -1 && bus != 0)
+ return (EINVAL);
+
+ if (xpt_open() < 0)
+ return (ENXIO);
+
+ /* First, find the path id of bus 0 for this mpt controller. */
+ bzero(&ccb, sizeof(ccb));
+
+ ccb.ccb_h.func_code = XPT_DEV_MATCH;
+
+ bufsize = sizeof(struct dev_match_result) * 1;
+ ccb.cdm.num_matches = 0;
+ ccb.cdm.match_buf_len = bufsize;
+ ccb.cdm.matches = calloc(1, bufsize);
+
+ bufsize = sizeof(struct dev_match_pattern) * 1;
+ ccb.cdm.num_patterns = 1;
+ ccb.cdm.pattern_buf_len = bufsize;
+ ccb.cdm.patterns = calloc(1, bufsize);
+
+ /* Match mptX bus 0. */
+ ccb.cdm.patterns[0].type = DEV_MATCH_BUS;
+ b = &ccb.cdm.patterns[0].pattern.bus_pattern;
+ snprintf(b->dev_name, sizeof(b->dev_name), "mpt");
+ b->unit_number = mpt_unit;
+ b->bus_id = 0;
+ b->flags = BUS_MATCH_NAME | BUS_MATCH_UNIT | BUS_MATCH_BUS_ID;
+
+ if (ioctl(xptfd, CAMIOCOMMAND, &ccb) < 0) {
+ free(ccb.cdm.matches);
+ free(ccb.cdm.patterns);
+ return (errno);
+ }
+ free(ccb.cdm.patterns);
+
+ if (((ccb.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) ||
+ (ccb.cdm.status != CAM_DEV_MATCH_LAST)) {
+ warnx("mpt_rescan_bus got CAM error %#x, CDM error %d\n",
+ ccb.ccb_h.status, ccb.cdm.status);
+ free(ccb.cdm.matches);
+ return (EIO);
+ }
+
+ /* We should have exactly 1 match for the bus. */
+ if (ccb.cdm.num_matches != 1 ||
+ ccb.cdm.matches[0].type != DEV_MATCH_BUS) {
+ free(ccb.cdm.matches);
+ return (ENOENT);
+ }
+ path_id = ccb.cdm.matches[0].result.bus_result.path_id;
+ free(ccb.cdm.matches);
+
+ /* Now perform the actual rescan. */
+ ccb.ccb_h.path_id = path_id;
+ if (id == -1) {
+ ccb.ccb_h.func_code = XPT_SCAN_BUS;
+ ccb.ccb_h.target_id = CAM_TARGET_WILDCARD;
+ ccb.ccb_h.target_lun = CAM_LUN_WILDCARD;
+ ccb.ccb_h.timeout = 5000;
+ } else {
+ ccb.ccb_h.func_code = XPT_SCAN_LUN;
+ ccb.ccb_h.target_id = id;
+ ccb.ccb_h.target_lun = 0;
+ }
+ ccb.crcn.flags = CAM_FLAG_NONE;
+
+ /* Run this at a low priority. */
+ ccb.ccb_h.pinfo.priority = 5;
+
+ if (ioctl(xptfd, CAMIOCOMMAND, &ccb) == -1)
+ return (errno);
+
+ if ((ccb.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ warnx("mpt_rescan_bus rescan got CAM error %#x\n",
+ ccb.ccb_h.status & CAM_STATUS_MASK);
+ return (EIO);
+ }
+
+ return (0);
+}
diff --git a/usr.sbin/mptutil/mpt_cmd.c b/usr.sbin/mptutil/mpt_cmd.c
new file mode 100644
index 0000000000000..2d6000c773139
--- /dev/null
+++ b/usr.sbin/mptutil/mpt_cmd.c
@@ -0,0 +1,639 @@
+/*-
+ * Copyright (c) 2008 Yahoo!, Inc.
+ * All rights reserved.
+ * Written by: John Baldwin <jhb@FreeBSD.org>
+ *
+ * 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.
+ * 3. Neither the name of the author nor the names of any co-contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * 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.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/errno.h>
+#include <sys/ioctl.h>
+#include <sys/mpt_ioctl.h>
+#include <sys/sysctl.h>
+#include <sys/uio.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mptutil.h"
+
+static const char *mpt_ioc_status_codes[] = {
+ "Success", /* 0x0000 */
+ "Invalid function",
+ "Busy",
+ "Invalid scatter-gather list",
+ "Internal error",
+ "Reserved",
+ "Insufficient resources",
+ "Invalid field",
+ "Invalid state", /* 0x0008 */
+ "Operation state not supported",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL, /* 0x0010 */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL, /* 0x0018 */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "Invalid configuration action", /* 0x0020 */
+ "Invalid configuration type",
+ "Invalid configuration page",
+ "Invalid configuration data",
+ "No configuration defaults",
+ "Unable to commit configuration change",
+ NULL,
+ NULL,
+ NULL, /* 0x0028 */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL, /* 0x0030 */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL, /* 0x0038 */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "Recovered SCSI error", /* 0x0040 */
+ "Invalid SCSI bus",
+ "Invalid SCSI target ID",
+ "SCSI device not there",
+ "SCSI data overrun",
+ "SCSI data underrun",
+ "SCSI I/O error",
+ "SCSI protocol error",
+ "SCSI task terminated", /* 0x0048 */
+ "SCSI residual mismatch",
+ "SCSI task management failed",
+ "SCSI I/O controller terminated",
+ "SCSI external controller terminated",
+ "EEDP guard error",
+ "EEDP reference tag error",
+ "EEDP application tag error",
+ NULL, /* 0x0050 */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL, /* 0x0058 */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "SCSI target priority I/O", /* 0x0060 */
+ "Invalid SCSI target port",
+ "Invalid SCSI target I/O index",
+ "SCSI target aborted",
+ "No connection retryable",
+ "No connection",
+ "FC aborted",
+ "Invalid FC receive ID",
+ "FC did invalid", /* 0x0068 */
+ "FC node logged out",
+ "Transfer count mismatch",
+ "STS data not set",
+ "FC exchange canceled",
+ "Data offset error",
+ "Too much write data",
+ "IU too short",
+ "ACK NAK timeout", /* 0x0070 */
+ "NAK received",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL, /* 0x0078 */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "LAN device not found", /* 0x0080 */
+ "LAN device failure",
+ "LAN transmit error",
+ "LAN transmit aborted",
+ "LAN receive error",
+ "LAN receive aborted",
+ "LAN partial packet",
+ "LAN canceled",
+ NULL, /* 0x0088 */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "SAS SMP request failed", /* 0x0090 */
+ "SAS SMP data overrun",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "Inband aborted", /* 0x0098 */
+ "No inband connection",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "Diagnostic released", /* 0x00A0 */
+};
+
+static const char *mpt_raid_action_status_codes[] = {
+ "Success",
+ "Invalid action",
+ "Failure",
+ "Operation in progress",
+};
+
+const char *
+mpt_ioc_status(U16 IOCStatus)
+{
+ static char buffer[16];
+
+ IOCStatus &= MPI_IOCSTATUS_MASK;
+ if (IOCStatus < sizeof(mpt_ioc_status_codes) / sizeof(char *) &&
+ mpt_ioc_status_codes[IOCStatus] != NULL)
+ return (mpt_ioc_status_codes[IOCStatus]);
+ snprintf(buffer, sizeof(buffer), "Status: 0x%04x", IOCStatus);
+ return (buffer);
+}
+
+const char *
+mpt_raid_status(U16 ActionStatus)
+{
+ static char buffer[16];
+
+ if (ActionStatus < sizeof(mpt_raid_action_status_codes) /
+ sizeof(char *))
+ return (mpt_raid_action_status_codes[ActionStatus]);
+ snprintf(buffer, sizeof(buffer), "Status: 0x%04x", ActionStatus);
+ return (buffer);
+}
+
+const char *
+mpt_raid_level(U8 VolumeType)
+{
+ static char buf[16];
+
+ switch (VolumeType) {
+ case MPI_RAID_VOL_TYPE_IS:
+ return ("RAID-0");
+ case MPI_RAID_VOL_TYPE_IM:
+ return ("RAID-1");
+ case MPI_RAID_VOL_TYPE_IME:
+ return ("RAID-1E");
+ case MPI_RAID_VOL_TYPE_RAID_5:
+ return ("RAID-5");
+ case MPI_RAID_VOL_TYPE_RAID_6:
+ return ("RAID-6");
+ case MPI_RAID_VOL_TYPE_RAID_10:
+ return ("RAID-10");
+ case MPI_RAID_VOL_TYPE_RAID_50:
+ return ("RAID-50");
+ default:
+ sprintf(buf, "LVL 0x%02x", VolumeType);
+ return (buf);
+ }
+}
+
+const char *
+mpt_volume_name(U8 VolumeBus, U8 VolumeID)
+{
+ static struct mpt_query_disk info;
+ static char buf[16];
+
+ if (mpt_query_disk(VolumeBus, VolumeID, &info) != 0) {
+ /*
+ * We only print out the bus number if it is non-zero
+ * since mpt(4) only supports devices on bus zero
+ * anyway.
+ */
+ if (VolumeBus == 0)
+ snprintf(buf, sizeof(buf), "%d", VolumeID);
+ else
+ snprintf(buf, sizeof(buf), "%d:%d", VolumeBus,
+ VolumeID);
+ return (buf);
+ }
+ return (info.devname);
+}
+
+int
+mpt_lookup_volume(int fd, const char *name, U8 *VolumeBus, U8 *VolumeID)
+{
+ CONFIG_PAGE_IOC_2 *ioc2;
+ CONFIG_PAGE_IOC_2_RAID_VOL *vol;
+ struct mpt_query_disk info;
+ char *cp;
+ long bus, id;
+ int i;
+
+ /*
+ * Check for a raw [<bus>:]<id> string. If the bus is not
+ * specified, assume bus 0.
+ */
+ bus = strtol(name, &cp, 0);
+ if (*cp == ':') {
+ id = strtol(cp + 1, &cp, 0);
+ if (*cp == '\0') {
+ if (bus < 0 || bus > 0xff || id < 0 || id > 0xff) {
+ errno = EINVAL;
+ return (-1);
+ }
+ *VolumeBus = bus;
+ *VolumeID = id;
+ return (0);
+ }
+ } else if (*cp == '\0') {
+ if (bus < 0 || bus > 0xff) {
+ errno = EINVAL;
+ return (-1);
+ }
+ *VolumeBus = 0;
+ *VolumeID = bus;
+ return (0);
+ }
+
+ ioc2 = mpt_read_ioc_page(fd, 2, NULL);
+ if (ioc2 == NULL)
+ return (-1);
+
+ vol = ioc2->RaidVolume;
+ for (i = 0; i < ioc2->NumActiveVolumes; vol++, i++) {
+ if (mpt_query_disk(vol->VolumeBus, vol->VolumeID, &info) != 0)
+ continue;
+ if (strcmp(name, info.devname) == 0) {
+ *VolumeBus = vol->VolumeBus;
+ *VolumeID = vol->VolumeID;
+ free(ioc2);
+ return (0);
+ }
+ }
+ free(ioc2);
+ errno = EINVAL;
+ return (-1);
+}
+
+int
+mpt_read_config_page_header(int fd, U8 PageType, U8 PageNumber, U32 PageAddress,
+ CONFIG_PAGE_HEADER *header, U16 *IOCStatus)
+{
+ struct mpt_cfg_page_req req;
+
+ if (IOCStatus != NULL)
+ *IOCStatus = MPI_IOCSTATUS_SUCCESS;
+ bzero(&req, sizeof(req));
+ req.header.PageType = PageType;
+ req.header.PageNumber = PageNumber;
+ req.page_address = PageAddress;
+ if (ioctl(fd, MPTIO_READ_CFG_HEADER, &req) < 0)
+ return (-1);
+ if (!IOC_STATUS_SUCCESS(req.ioc_status)) {
+ if (IOCStatus != NULL)
+ *IOCStatus = req.ioc_status;
+ else
+ warnx("Reading config page header failed: %s",
+ mpt_ioc_status(req.ioc_status));
+ errno = EIO;
+ return (-1);
+ }
+ *header = req.header;
+ return (0);
+}
+
+void *
+mpt_read_config_page(int fd, U8 PageType, U8 PageNumber, U32 PageAddress,
+ U16 *IOCStatus)
+{
+ struct mpt_cfg_page_req req;
+ void *buf;
+ int save_errno;
+
+ if (IOCStatus != NULL)
+ *IOCStatus = MPI_IOCSTATUS_SUCCESS;
+ bzero(&req, sizeof(req));
+ req.header.PageType = PageType;
+ req.header.PageNumber = PageNumber;
+ req.page_address = PageAddress;
+ if (ioctl(fd, MPTIO_READ_CFG_HEADER, &req) < 0)
+ return (NULL);
+ if (!IOC_STATUS_SUCCESS(req.ioc_status)) {
+ if (IOCStatus != NULL)
+ *IOCStatus = req.ioc_status;
+ else
+ warnx("Reading config page header failed: %s",
+ mpt_ioc_status(req.ioc_status));
+ errno = EIO;
+ return (NULL);
+ }
+ req.len = req.header.PageLength * 4;
+ buf = malloc(req.len);
+ req.buf = buf;
+ bcopy(&req.header, buf, sizeof(req.header));
+ if (ioctl(fd, MPTIO_READ_CFG_PAGE, &req) < 0) {
+ save_errno = errno;
+ free(buf);
+ errno = save_errno;
+ return (NULL);
+ }
+ if (!IOC_STATUS_SUCCESS(req.ioc_status)) {
+ if (IOCStatus != NULL)
+ *IOCStatus = req.ioc_status;
+ else
+ warnx("Reading config page failed: %s",
+ mpt_ioc_status(req.ioc_status));
+ free(buf);
+ errno = EIO;
+ return (NULL);
+ }
+ return (buf);
+}
+
+void *
+mpt_read_extended_config_page(int fd, U8 ExtPageType, U8 PageVersion,
+ U8 PageNumber, U32 PageAddress, U16 *IOCStatus)
+{
+ struct mpt_ext_cfg_page_req req;
+ void *buf;
+ int save_errno;
+
+ if (IOCStatus != NULL)
+ *IOCStatus = MPI_IOCSTATUS_SUCCESS;
+ bzero(&req, sizeof(req));
+ req.header.PageVersion = PageVersion;
+ req.header.PageNumber = PageNumber;
+ req.header.ExtPageType = ExtPageType;
+ req.page_address = PageAddress;
+ if (ioctl(fd, MPTIO_READ_EXT_CFG_HEADER, &req) < 0)
+ return (NULL);
+ if (!IOC_STATUS_SUCCESS(req.ioc_status)) {
+ if (IOCStatus != NULL)
+ *IOCStatus = req.ioc_status;
+ else
+ warnx("Reading extended config page header failed: %s",
+ mpt_ioc_status(req.ioc_status));
+ errno = EIO;
+ return (NULL);
+ }
+ req.len = req.header.ExtPageLength * 4;
+ buf = malloc(req.len);
+ req.buf = buf;
+ bcopy(&req.header, buf, sizeof(req.header));
+ if (ioctl(fd, MPTIO_READ_EXT_CFG_PAGE, &req) < 0) {
+ save_errno = errno;
+ free(buf);
+ errno = save_errno;
+ return (NULL);
+ }
+ if (!IOC_STATUS_SUCCESS(req.ioc_status)) {
+ if (IOCStatus != NULL)
+ *IOCStatus = req.ioc_status;
+ else
+ warnx("Reading extended config page failed: %s",
+ mpt_ioc_status(req.ioc_status));
+ free(buf);
+ errno = EIO;
+ return (NULL);
+ }
+ return (buf);
+}
+
+int
+mpt_write_config_page(int fd, void *buf, U16 *IOCStatus)
+{
+ CONFIG_PAGE_HEADER *hdr;
+ struct mpt_cfg_page_req req;
+
+ if (IOCStatus != NULL)
+ *IOCStatus = MPI_IOCSTATUS_SUCCESS;
+ bzero(&req, sizeof(req));
+ req.buf = buf;
+ hdr = buf;
+ req.len = hdr->PageLength * 4;
+ if (ioctl(fd, MPTIO_WRITE_CFG_PAGE, &req) < 0)
+ return (-1);
+ if (!IOC_STATUS_SUCCESS(req.ioc_status)) {
+ if (IOCStatus != NULL) {
+ *IOCStatus = req.ioc_status;
+ return (0);
+ }
+ warnx("Writing config page failed: %s",
+ mpt_ioc_status(req.ioc_status));
+ errno = EIO;
+ return (-1);
+ }
+ return (0);
+}
+
+int
+mpt_raid_action(int fd, U8 Action, U8 VolumeBus, U8 VolumeID, U8 PhysDiskNum,
+ U32 ActionDataWord, void *buf, int len, RAID_VOL0_STATUS *VolumeStatus,
+ U32 *ActionData, int datalen, U16 *IOCStatus, U16 *ActionStatus, int write)
+{
+ struct mpt_raid_action raid_act;
+
+ if (IOCStatus != NULL)
+ *IOCStatus = MPI_IOCSTATUS_SUCCESS;
+ if (datalen < 0 || (unsigned)datalen > sizeof(raid_act.action_data)) {
+ errno = EINVAL;
+ return (-1);
+ }
+ bzero(&raid_act, sizeof(raid_act));
+ raid_act.action = Action;
+ raid_act.volume_bus = VolumeBus;
+ raid_act.volume_id = VolumeID;
+ raid_act.phys_disk_num = PhysDiskNum;
+ raid_act.action_data_word = ActionDataWord;
+ if (buf != NULL && len != 0) {
+ raid_act.buf = buf;
+ raid_act.len = len;
+ raid_act.write = write;
+ }
+
+ if (ioctl(fd, MPTIO_RAID_ACTION, &raid_act) < 0)
+ return (-1);
+
+ if (!IOC_STATUS_SUCCESS(raid_act.ioc_status)) {
+ if (IOCStatus != NULL) {
+ *IOCStatus = raid_act.ioc_status;
+ return (0);
+ }
+ warnx("RAID action failed: %s",
+ mpt_ioc_status(raid_act.ioc_status));
+ errno = EIO;
+ return (-1);
+ }
+
+ if (ActionStatus != NULL)
+ *ActionStatus = raid_act.action_status;
+ if (raid_act.action_status != MPI_RAID_ACTION_ASTATUS_SUCCESS) {
+ if (ActionStatus != NULL)
+ return (0);
+ warnx("RAID action failed: %s",
+ mpt_raid_status(raid_act.action_status));
+ errno = EIO;
+ return (-1);
+ }
+
+ if (VolumeStatus != NULL)
+ *((U32 *)VolumeStatus) = raid_act.volume_status;
+ if (ActionData != NULL)
+ bcopy(raid_act.action_data, ActionData, datalen);
+ return (0);
+}
+
+int
+mpt_open(int unit)
+{
+ char path[MAXPATHLEN];
+
+ snprintf(path, sizeof(path), "/dev/mpt%d", unit);
+ return (open(path, O_RDWR));
+}
+
+int
+mpt_table_handler(struct mptutil_command **start, struct mptutil_command **end,
+ int ac, char **av)
+{
+ struct mptutil_command **cmd;
+
+ if (ac < 2) {
+ warnx("The %s command requires a sub-command.", av[0]);
+ return (EINVAL);
+ }
+ for (cmd = start; cmd < end; cmd++) {
+ if (strcmp((*cmd)->name, av[1]) == 0)
+ return ((*cmd)->handler(ac - 1, av + 1));
+ }
+
+ warnx("%s is not a valid sub-command of %s.", av[1], av[0]);
+ return (ENOENT);
+}
+
+#ifdef DEBUG
+void
+hexdump(const void *ptr, int length, const char *hdr, int flags)
+{
+ int i, j, k;
+ int cols;
+ const unsigned char *cp;
+ char delim;
+
+ if ((flags & HD_DELIM_MASK) != 0)
+ delim = (flags & HD_DELIM_MASK) >> 8;
+ else
+ delim = ' ';
+
+ if ((flags & HD_COLUMN_MASK) != 0)
+ cols = flags & HD_COLUMN_MASK;
+ else
+ cols = 16;
+
+ cp = ptr;
+ for (i = 0; i < length; i+= cols) {
+ if (hdr != NULL)
+ printf("%s", hdr);
+
+ if ((flags & HD_OMIT_COUNT) == 0)
+ printf("%04x ", i);
+
+ if ((flags & HD_OMIT_HEX) == 0) {
+ for (j = 0; j < cols; j++) {
+ k = i + j;
+ if (k < length)
+ printf("%c%02x", delim, cp[k]);
+ else
+ printf(" ");
+ }
+ }
+
+ if ((flags & HD_OMIT_CHARS) == 0) {
+ printf(" |");
+ for (j = 0; j < cols; j++) {
+ k = i + j;
+ if (k >= length)
+ printf(" ");
+ else if (cp[k] >= ' ' && cp[k] <= '~')
+ printf("%c", cp[k]);
+ else
+ printf(".");
+ }
+ printf("|");
+ }
+ printf("\n");
+ }
+}
+#endif
diff --git a/usr.sbin/mptutil/mpt_config.c b/usr.sbin/mptutil/mpt_config.c
new file mode 100644
index 0000000000000..3874df301379f
--- /dev/null
+++ b/usr.sbin/mptutil/mpt_config.c
@@ -0,0 +1,1160 @@
+/*-
+ * Copyright (c) 2008 Yahoo!, Inc.
+ * All rights reserved.
+ * Written by: John Baldwin <jhb@FreeBSD.org>
+ *
+ * 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.
+ * 3. Neither the name of the author nor the names of any co-contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * 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.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/errno.h>
+#include <err.h>
+#include <fcntl.h>
+#include <libutil.h>
+#include <paths.h>
+#ifdef DEBUG
+#include <stdint.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "mptutil.h"
+
+#ifdef DEBUG
+static void dump_config(CONFIG_PAGE_RAID_VOL_0 *vol);
+#endif
+
+#define powerof2(x) ((((x)-1)&(x))==0)
+
+static long
+dehumanize(const char *value)
+{
+ char *vtp;
+ long iv;
+
+ if (value == NULL)
+ return (0);
+ iv = strtoq(value, &vtp, 0);
+ if (vtp == value || (vtp[0] != '\0' && vtp[1] != '\0')) {
+ return (0);
+ }
+ switch (vtp[0]) {
+ case 't': case 'T':
+ iv *= 1024;
+ case 'g': case 'G':
+ iv *= 1024;
+ case 'm': case 'M':
+ iv *= 1024;
+ case 'k': case 'K':
+ iv *= 1024;
+ case '\0':
+ break;
+ default:
+ return (0);
+ }
+ return (iv);
+}
+
+/*
+ * Lock the volume by opening its /dev device read/write. This will
+ * only work if nothing else has it opened (including mounts). We
+ * leak the fd on purpose since this application is not long-running.
+ */
+int
+mpt_lock_volume(U8 VolumeBus, U8 VolumeID)
+{
+ char path[MAXPATHLEN];
+ struct mpt_query_disk qd;
+ int error, vfd;
+
+ error = mpt_query_disk(VolumeBus, VolumeID, &qd);
+ if (error == ENOENT)
+ /*
+ * This means there isn't a CAM device associated with
+ * the volume, and thus it is already implicitly
+ * locked, so just return.
+ */
+ return (0);
+ if (error) {
+ errno = error;
+ warn("Unable to lookup volume device name");
+ return (-1);
+ }
+ snprintf(path, sizeof(path), "%s%s", _PATH_DEV, qd.devname);
+ vfd = open(path, O_RDWR);
+ if (vfd < 0) {
+ warn("Unable to lock volume %s", qd.devname);
+ return (-1);
+ }
+ return (0);
+}
+
+static int
+mpt_lock_physdisk(struct mpt_standalone_disk *disk)
+{
+ char path[MAXPATHLEN];
+ int dfd;
+
+ snprintf(path, sizeof(path), "%s%s", _PATH_DEV, disk->devname);
+ dfd = open(path, O_RDWR);
+ if (dfd < 0) {
+ warn("Unable to lock disk %s", disk->devname);
+ return (-1);
+ }
+ return (0);
+}
+
+static int
+mpt_lookup_standalone_disk(const char *name, struct mpt_standalone_disk *disks,
+ int ndisks, int *index)
+{
+ char *cp;
+ long bus, id;
+ int i;
+
+ /* Check for a raw <bus>:<id> string. */
+ bus = strtol(name, &cp, 0);
+ if (*cp == ':') {
+ id = strtol(cp + 1, &cp, 0);
+ if (*cp == '\0') {
+ if (bus < 0 || bus > 0xff || id < 0 || id > 0xff) {
+ errno = EINVAL;
+ return (-1);
+ }
+ for (i = 0; i < ndisks; i++) {
+ if (disks[i].bus == (U8)bus &&
+ disks[i].target == (U8)id) {
+ *index = i;
+ return (0);
+ }
+ }
+ errno = ENOENT;
+ return (-1);
+ }
+ }
+
+ if (name[0] == 'd' && name[1] == 'a') {
+ for (i = 0; i < ndisks; i++) {
+ if (strcmp(name, disks[i].devname) == 0) {
+ *index = i;
+ return (0);
+ }
+ }
+ errno = ENOENT;
+ return (-1);
+ }
+
+ errno = EINVAL;
+ return (-1);
+}
+
+/*
+ * Mark a standalone disk as being a physical disk.
+ */
+static int
+mpt_create_physdisk(int fd, struct mpt_standalone_disk *disk, U8 *PhysDiskNum)
+{
+ CONFIG_PAGE_HEADER header;
+ CONFIG_PAGE_RAID_PHYS_DISK_0 *config_page;
+ U32 ActionData;
+
+ if (mpt_read_config_page_header(fd, MPI_CONFIG_PAGETYPE_RAID_PHYSDISK,
+ 0, 0, &header, NULL) < 0)
+ return (-1);
+ if (header.PageVersion > MPI_RAIDPHYSDISKPAGE0_PAGEVERSION) {
+ warnx("Unsupported RAID physdisk page 0 version %d",
+ header.PageVersion);
+ errno = EOPNOTSUPP;
+ return (-1);
+ }
+ config_page = calloc(1, sizeof(CONFIG_PAGE_RAID_PHYS_DISK_0));
+ config_page->Header.PageType = MPI_CONFIG_PAGETYPE_RAID_PHYSDISK;
+ config_page->Header.PageNumber = 0;
+ config_page->Header.PageLength = sizeof(CONFIG_PAGE_RAID_PHYS_DISK_0) /
+ 4;
+ config_page->PhysDiskIOC = 0; /* XXX */
+ config_page->PhysDiskBus = disk->bus;
+ config_page->PhysDiskID = disk->target;
+
+ /* XXX: Enclosure info for PhysDiskSettings? */
+ if (mpt_raid_action(fd, MPI_RAID_ACTION_CREATE_PHYSDISK, 0, 0, 0, 0,
+ config_page, sizeof(CONFIG_PAGE_RAID_PHYS_DISK_0), NULL,
+ &ActionData, sizeof(ActionData), NULL, NULL, 1) < 0)
+ return (-1);
+ *PhysDiskNum = ActionData & 0xff;
+ return (0);
+}
+
+static int
+mpt_delete_physdisk(int fd, U8 PhysDiskNum)
+{
+
+ return (mpt_raid_action(fd, MPI_RAID_ACTION_DELETE_PHYSDISK, 0, 0,
+ PhysDiskNum, 0, NULL, 0, NULL, NULL, 0, NULL, NULL, 0));
+}
+
+/*
+ * MPT's firmware does not have a clear command. Instead, we
+ * implement it by deleting each array and disk by hand.
+ */
+static int
+clear_config(int ac, char **av)
+{
+ CONFIG_PAGE_IOC_2 *ioc2;
+ CONFIG_PAGE_IOC_2_RAID_VOL *vol;
+ CONFIG_PAGE_IOC_3 *ioc3;
+ IOC_3_PHYS_DISK *disk;
+ CONFIG_PAGE_IOC_5 *ioc5;
+ IOC_5_HOT_SPARE *spare;
+ int ch, fd, i;
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ warn("mpt_open");
+ return (errno);
+ }
+
+ ioc2 = mpt_read_ioc_page(fd, 2, NULL);
+ if (ioc2 == NULL) {
+ warn("Failed to fetch volume list");
+ return (errno);
+ }
+
+ /* Lock all the volumes first. */
+ vol = ioc2->RaidVolume;
+ for (i = 0; i < ioc2->NumActiveVolumes; vol++, i++) {
+ if (mpt_lock_volume(vol->VolumeBus, vol->VolumeID) < 0) {
+ warnx("Volume %s is busy and cannot be deleted",
+ mpt_volume_name(vol->VolumeBus, vol->VolumeID));
+ return (EBUSY);
+ }
+ }
+
+ printf(
+ "Are you sure you wish to clear the configuration on mpt%u? [y/N] ",
+ mpt_unit);
+ ch = getchar();
+ if (ch != 'y' && ch != 'Y') {
+ printf("\nAborting\n");
+ return (0);
+ }
+
+ /* Delete all the volumes. */
+ vol = ioc2->RaidVolume;
+ for (i = 0; i < ioc2->NumActiveVolumes; vol++, i++)
+ if (mpt_raid_action(fd, MPI_RAID_ACTION_DELETE_VOLUME,
+ vol->VolumeBus, vol->VolumeID, 0,
+ MPI_RAID_ACTION_ADATA_DEL_PHYS_DISKS |
+ MPI_RAID_ACTION_ADATA_ZERO_LBA0, NULL, 0, NULL, NULL, 0,
+ NULL, NULL, 0) < 0)
+ warn("Failed to delete volume %s",
+ mpt_volume_name(vol->VolumeBus, vol->VolumeID));
+ free(ioc2);
+
+ /* Delete all the spares. */
+ ioc5 = mpt_read_ioc_page(fd, 5, NULL);
+ if (ioc5 == NULL)
+ warn("Failed to fetch spare list");
+ else {
+ spare = ioc5->HotSpare;
+ for (i = 0; i < ioc5->NumHotSpares; spare++, i++)
+ if (mpt_delete_physdisk(fd, spare->PhysDiskNum) < 0)
+ warn("Failed to delete physical disk %d",
+ spare->PhysDiskNum);
+ free(ioc5);
+ }
+
+ /* Delete any RAID physdisks that may be left. */
+ ioc3 = mpt_read_ioc_page(fd, 3, NULL);
+ if (ioc3 == NULL)
+ warn("Failed to fetch drive list");
+ else {
+ disk = ioc3->PhysDisk;
+ for (i = 0; i < ioc3->NumPhysDisks; disk++, i++)
+ if (mpt_delete_physdisk(fd, disk->PhysDiskNum) < 0)
+ warn("Failed to delete physical disk %d",
+ disk->PhysDiskNum);
+ free(ioc3);
+ }
+
+ printf("mpt%d: Configuration cleared\n", mpt_unit);
+ mpt_rescan_bus(-1, -1);
+ close(fd);
+
+ return (0);
+}
+MPT_COMMAND(top, clear, clear_config);
+
+#define RT_RAID0 0
+#define RT_RAID1 1
+#define RT_RAID1E 2
+
+static struct raid_type_entry {
+ const char *name;
+ int raid_type;
+} raid_type_table[] = {
+ { "raid0", RT_RAID0 },
+ { "raid-0", RT_RAID0 },
+ { "raid1", RT_RAID1 },
+ { "raid-1", RT_RAID1 },
+ { "mirror", RT_RAID1 },
+ { "raid1e", RT_RAID1E },
+ { "raid-1e", RT_RAID1E },
+ { NULL, 0 },
+};
+
+struct config_id_state {
+ struct mpt_standalone_disk *sdisks;
+ struct mpt_drive_list *list;
+ CONFIG_PAGE_IOC_2 *ioc2;
+ U8 target_id;
+ int nsdisks;
+};
+
+struct drive_info {
+ CONFIG_PAGE_RAID_PHYS_DISK_0 *info;
+ struct mpt_standalone_disk *sdisk;
+};
+
+struct volume_info {
+ int drive_count;
+ struct drive_info *drives;
+};
+
+/* Parse a comma-separated list of drives for a volume. */
+static int
+parse_volume(int fd, int raid_type, struct config_id_state *state,
+ char *volume_str, struct volume_info *info)
+{
+ struct drive_info *dinfo;
+ U8 PhysDiskNum;
+ char *cp;
+ int count, error, i;
+
+ cp = volume_str;
+ for (count = 0; cp != NULL; count++) {
+ cp = strchr(cp, ',');
+ if (cp != NULL) {
+ cp++;
+ if (*cp == ',') {
+ warnx("Invalid drive list '%s'", volume_str);
+ return (EINVAL);
+ }
+ }
+ }
+
+ /* Validate the number of drives for this volume. */
+ switch (raid_type) {
+ case RT_RAID0:
+ if (count < 2) {
+ warnx("RAID0 requires at least 2 drives in each "
+ "array");
+ return (EINVAL);
+ }
+ break;
+ case RT_RAID1:
+ if (count != 2) {
+ warnx("RAID1 requires exactly 2 drives in each "
+ "array");
+ return (EINVAL);
+ }
+ break;
+ case RT_RAID1E:
+ if (count < 3) {
+ warnx("RAID1E requires at least 3 drives in each "
+ "array");
+ return (EINVAL);
+ }
+ break;
+ }
+
+ /* Validate each drive. */
+ info->drives = calloc(count, sizeof(struct drive_info));
+ info->drive_count = count;
+ for (dinfo = info->drives; (cp = strsep(&volume_str, ",")) != NULL;
+ dinfo++) {
+ /* If this drive is already a RAID phys just fetch the info. */
+ error = mpt_lookup_drive(state->list, cp, &PhysDiskNum);
+ if (error == 0) {
+ dinfo->info = mpt_pd_info(fd, PhysDiskNum, NULL);
+ if (dinfo->info == NULL)
+ return (errno);
+ continue;
+ }
+
+ /* See if it is a standalone disk. */
+ if (mpt_lookup_standalone_disk(cp, state->sdisks,
+ state->nsdisks, &i) < 0) {
+ warn("Unable to lookup drive %s", cp);
+ return (errno);
+ }
+ dinfo->sdisk = &state->sdisks[i];
+
+ /* Lock the disk, we will create phys disk pages later. */
+ if (mpt_lock_physdisk(dinfo->sdisk) < 0)
+ return (errno);
+ }
+
+ return (0);
+}
+
+/*
+ * Add RAID physdisk pages for any standalone disks that a volume is
+ * going to use.
+ */
+static int
+add_drives(int fd, struct volume_info *info, int verbose)
+{
+ struct drive_info *dinfo;
+ U8 PhysDiskNum;
+ int i;
+
+ for (i = 0, dinfo = info->drives; i < info->drive_count;
+ i++, dinfo++) {
+ if (dinfo->info == NULL) {
+ if (mpt_create_physdisk(fd, dinfo->sdisk,
+ &PhysDiskNum) < 0) {
+ warn(
+ "Failed to create physical disk page for %s",
+ dinfo->sdisk->devname);
+ return (errno);
+ }
+ if (verbose)
+ printf("Added drive %s with PhysDiskNum %u\n",
+ dinfo->sdisk->devname, PhysDiskNum);
+
+ dinfo->info = mpt_pd_info(fd, PhysDiskNum, NULL);
+ if (dinfo->info == NULL)
+ return (errno);
+ }
+ }
+ return (0);
+}
+
+/*
+ * Find the next free target ID assuming that 'target_id' is the last
+ * one used. 'target_id' should be 0xff for the initial test.
+ */
+static U8
+find_next_volume(struct config_id_state *state)
+{
+ CONFIG_PAGE_IOC_2_RAID_VOL *vol;
+ int i;
+
+restart:
+ /* Assume the current one is used. */
+ state->target_id++;
+
+ /* Search drives first. */
+ for (i = 0; i < state->nsdisks; i++)
+ if (state->sdisks[i].target == state->target_id)
+ goto restart;
+ for (i = 0; i < state->list->ndrives; i++)
+ if (state->list->drives[i]->PhysDiskID == state->target_id)
+ goto restart;
+
+ /* Seach volumes second. */
+ vol = state->ioc2->RaidVolume;
+ for (i = 0; i < state->ioc2->NumActiveVolumes; vol++, i++)
+ if (vol->VolumeID == state->target_id)
+ goto restart;
+
+ return (state->target_id);
+}
+
+/* Create a volume and populate it with drives. */
+static CONFIG_PAGE_RAID_VOL_0 *
+build_volume(int fd, struct volume_info *info, int raid_type, long stripe_size,
+ struct config_id_state *state, int verbose)
+{
+ CONFIG_PAGE_HEADER header;
+ CONFIG_PAGE_RAID_VOL_0 *vol;
+ RAID_VOL0_PHYS_DISK *rdisk;
+ struct drive_info *dinfo;
+ U32 MinLBA;
+ uint64_t MaxLBA;
+ size_t page_size;
+ int i;
+
+ if (mpt_read_config_page_header(fd, MPI_CONFIG_PAGETYPE_RAID_VOLUME,
+ 0, 0, &header, NULL) < 0)
+ return (NULL);
+ if (header.PageVersion > MPI_RAIDVOLPAGE0_PAGEVERSION) {
+ warnx("Unsupported RAID volume page 0 version %d",
+ header.PageVersion);
+ errno = EOPNOTSUPP;
+ return (NULL);
+ }
+ page_size = sizeof(CONFIG_PAGE_RAID_VOL_0) +
+ sizeof(RAID_VOL0_PHYS_DISK) * (info->drive_count - 1);
+ vol = calloc(1, page_size);
+
+ /* Header */
+ vol->Header.PageType = MPI_CONFIG_PAGETYPE_RAID_VOLUME;
+ vol->Header.PageNumber = 0;
+ vol->Header.PageLength = page_size / 4;
+
+ /* Properties */
+ vol->VolumeID = find_next_volume(state);
+ vol->VolumeBus = 0;
+ vol->VolumeIOC = 0; /* XXX */
+ vol->VolumeStatus.Flags = MPI_RAIDVOL0_STATUS_FLAG_ENABLED;
+ vol->VolumeStatus.State = MPI_RAIDVOL0_STATUS_STATE_OPTIMAL;
+ vol->VolumeSettings.Settings = MPI_RAIDVOL0_SETTING_USE_DEFAULTS;
+ vol->VolumeSettings.HotSparePool = MPI_RAID_HOT_SPARE_POOL_0;
+ vol->NumPhysDisks = info->drive_count;
+
+ /* Find the smallest drive. */
+ MinLBA = info->drives[0].info->MaxLBA;
+ for (i = 1; i < info->drive_count; i++)
+ if (info->drives[i].info->MaxLBA < MinLBA)
+ MinLBA = info->drives[i].info->MaxLBA;
+
+ /*
+ * Now chop off 512MB at the end to leave room for the
+ * metadata. The controller might only use 64MB, but we just
+ * chop off the max to be simple.
+ */
+ MinLBA -= (512 * 1024 * 1024) / 512;
+
+ switch (raid_type) {
+ case RT_RAID0:
+ vol->VolumeType = MPI_RAID_VOL_TYPE_IS;
+ vol->StripeSize = stripe_size / 512;
+ MaxLBA = MinLBA * info->drive_count;
+ break;
+ case RT_RAID1:
+ vol->VolumeType = MPI_RAID_VOL_TYPE_IM;
+ MaxLBA = MinLBA * (info->drive_count / 2);
+ break;
+ case RT_RAID1E:
+ vol->VolumeType = MPI_RAID_VOL_TYPE_IME;
+ vol->StripeSize = stripe_size / 512;
+ MaxLBA = MinLBA * info->drive_count / 2;
+ break;
+ default:
+ /* Pacify gcc. */
+ abort();
+ }
+
+ /*
+ * If the controller doesn't support 64-bit addressing and the
+ * new volume is larger than 2^32 blocks, warn the user and
+ * truncate the volume.
+ */
+ if (MaxLBA >> 32 != 0 &&
+ !(state->ioc2->CapabilitiesFlags &
+ MPI_IOCPAGE2_CAP_FLAGS_RAID_64_BIT_ADDRESSING)) {
+ warnx(
+ "Controller does not support volumes > 2TB, truncating volume.");
+ MaxLBA = 0xffffffff;
+ }
+ vol->MaxLBA = MaxLBA;
+ vol->MaxLBAHigh = MaxLBA >> 32;
+
+ /* Populate drives. */
+ for (i = 0, dinfo = info->drives, rdisk = vol->PhysDisk;
+ i < info->drive_count; i++, dinfo++, rdisk++) {
+ if (verbose)
+ printf("Adding drive %u (%u:%u) to volume %u:%u\n",
+ dinfo->info->PhysDiskNum, dinfo->info->PhysDiskBus,
+ dinfo->info->PhysDiskID, vol->VolumeBus,
+ vol->VolumeID);
+ if (raid_type == RT_RAID1) {
+ if (i == 0)
+ rdisk->PhysDiskMap =
+ MPI_RAIDVOL0_PHYSDISK_PRIMARY;
+ else
+ rdisk->PhysDiskMap =
+ MPI_RAIDVOL0_PHYSDISK_SECONDARY;
+ } else
+ rdisk->PhysDiskMap = i;
+ rdisk->PhysDiskNum = dinfo->info->PhysDiskNum;
+ }
+
+ return (vol);
+}
+
+static int
+create_volume(int ac, char **av)
+{
+ CONFIG_PAGE_RAID_VOL_0 *vol;
+ struct config_id_state state;
+ struct volume_info *info;
+ int ch, error, fd, i, raid_type, verbose, quick;
+ long stripe_size;
+#ifdef DEBUG
+ int dump;
+#endif
+
+ if (ac < 2) {
+ warnx("create: volume type required");
+ return (EINVAL);
+ }
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ warn("mpt_open");
+ return (errno);
+ }
+
+ /* Lookup the RAID type first. */
+ raid_type = -1;
+ for (i = 0; raid_type_table[i].name != NULL; i++)
+ if (strcasecmp(raid_type_table[i].name, av[1]) == 0) {
+ raid_type = raid_type_table[i].raid_type;
+ break;
+ }
+
+ if (raid_type == -1) {
+ warnx("Unknown or unsupported volume type %s", av[1]);
+ return (EINVAL);
+ }
+
+ /* Parse any options. */
+ optind = 2;
+#ifdef DEBUG
+ dump = 0;
+#endif
+ quick = 0;
+ verbose = 0;
+ stripe_size = 64 * 1024;
+
+ while ((ch = getopt(ac, av, "dqs:v")) != -1) {
+ switch (ch) {
+#ifdef DEBUG
+ case 'd':
+ dump = 1;
+ break;
+#endif
+ case 'q':
+ quick = 1;
+ break;
+ case 's':
+ stripe_size = dehumanize(optarg);
+ if ((stripe_size < 512) || (!powerof2(stripe_size))) {
+ warnx("Invalid stripe size %s", optarg);
+ return (EINVAL);
+ }
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ case '?':
+ default:
+ return (EINVAL);
+ }
+ }
+ ac -= optind;
+ av += optind;
+
+ /* Fetch existing config data. */
+ state.ioc2 = mpt_read_ioc_page(fd, 2, NULL);
+ if (state.ioc2 == NULL) {
+ warn("Failed to read volume list");
+ return (errno);
+ }
+ state.list = mpt_pd_list(fd);
+ if (state.list == NULL)
+ return (errno);
+ error = mpt_fetch_disks(fd, &state.nsdisks, &state.sdisks);
+ if (error) {
+ warn("Failed to fetch standalone disk list");
+ return (error);
+ }
+ state.target_id = 0xff;
+
+ /* Parse the drive list. */
+ if (ac != 1) {
+ warnx("Exactly one drive list is required");
+ return (EINVAL);
+ }
+ info = calloc(1, sizeof(*info));
+ error = parse_volume(fd, raid_type, &state, av[0], info);
+ if (error)
+ return (error);
+
+ /* Create RAID physdisk pages for standalone disks. */
+ error = add_drives(fd, info, verbose);
+ if (error)
+ return (error);
+
+ /* Build the volume. */
+ vol = build_volume(fd, info, raid_type, stripe_size, &state, verbose);
+
+#ifdef DEBUG
+ if (dump) {
+ dump_config(vol);
+ goto skip;
+ }
+#endif
+
+ /* Send the new volume to the controller. */
+ if (mpt_raid_action(fd, MPI_RAID_ACTION_CREATE_VOLUME, vol->VolumeBus,
+ vol->VolumeID, 0, quick ? MPI_RAID_ACTION_ADATA_DO_NOT_SYNC : 0,
+ vol, vol->Header.PageLength * 4, NULL, NULL, 0, NULL, NULL, 1) <
+ 0) {
+ warn("Failed to add volume");
+ return (errno);
+ }
+
+#ifdef DEBUG
+skip:
+#endif
+ mpt_rescan_bus(vol->VolumeBus, vol->VolumeID);
+
+ /* Clean up. */
+ free(vol);
+ free(info);
+ free(state.sdisks);
+ mpt_free_pd_list(state.list);
+ free(state.ioc2);
+ close(fd);
+
+ return (0);
+}
+MPT_COMMAND(top, create, create_volume);
+
+static int
+delete_volume(int ac, char **av)
+{
+ U8 VolumeBus, VolumeID;
+ int fd;
+
+ if (ac != 2) {
+ warnx("delete: volume required");
+ return (EINVAL);
+ }
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ warn("mpt_open");
+ return (errno);
+ }
+
+ if (mpt_lookup_volume(fd, av[1], &VolumeBus, &VolumeID) < 0) {
+ warn("Invalid volume %s", av[1]);
+ return (errno);
+ }
+
+ if (mpt_lock_volume(VolumeBus, VolumeID) < 0)
+ return (errno);
+
+ if (mpt_raid_action(fd, MPI_RAID_ACTION_DELETE_VOLUME, VolumeBus,
+ VolumeID, 0, MPI_RAID_ACTION_ADATA_DEL_PHYS_DISKS |
+ MPI_RAID_ACTION_ADATA_ZERO_LBA0, NULL, 0, NULL, NULL, 0, NULL,
+ NULL, 0) < 0) {
+ warn("Failed to delete volume");
+ return (errno);
+ }
+
+ mpt_rescan_bus(-1, -1);
+ close(fd);
+
+ return (0);
+}
+MPT_COMMAND(top, delete, delete_volume);
+
+static int
+find_volume_spare_pool(int fd, const char *name, int *pool)
+{
+ CONFIG_PAGE_RAID_VOL_0 *info;
+ CONFIG_PAGE_IOC_2 *ioc2;
+ CONFIG_PAGE_IOC_2_RAID_VOL *vol;
+ U8 VolumeBus, VolumeID;
+ int i, j, new_pool, pool_count[7];
+
+ if (mpt_lookup_volume(fd, name, &VolumeBus, &VolumeID) < 0) {
+ warn("Invalid volume %s", name);
+ return (-1);
+ }
+
+ info = mpt_vol_info(fd, VolumeBus, VolumeID, NULL);
+ if (info == NULL)
+ return (-1);
+
+ /*
+ * Check for an existing pool other than pool 0 (used for
+ * global spares).
+ */
+ if ((info->VolumeSettings.HotSparePool & ~MPI_RAID_HOT_SPARE_POOL_0) !=
+ 0) {
+ *pool = 1 << (ffs(info->VolumeSettings.HotSparePool &
+ ~MPI_RAID_HOT_SPARE_POOL_0) - 1);
+ return (0);
+ }
+ free(info);
+
+ /*
+ * Try to find a free pool. First, figure out which pools are
+ * in use.
+ */
+ ioc2 = mpt_read_ioc_page(fd, 2, NULL);
+ if (ioc2 == NULL) {
+ warn("Failed to fetch volume list");
+ return (-1);
+ }
+ bzero(pool_count, sizeof(pool_count));
+ vol = ioc2->RaidVolume;
+ for (i = 0; i < ioc2->NumActiveVolumes; vol++, i++) {
+ info = mpt_vol_info(fd, vol->VolumeBus, vol->VolumeID, NULL);
+ if (info == NULL)
+ return (-1);
+ for (j = 0; j < 7; j++)
+ if (info->VolumeSettings.HotSparePool & (1 << (j + 1)))
+ pool_count[j]++;
+ free(info);
+ }
+ free(ioc2);
+
+ /* Find the pool with the lowest use count. */
+ new_pool = 0;
+ for (i = 1; i < 7; i++)
+ if (pool_count[i] < pool_count[new_pool])
+ new_pool = i;
+ new_pool++;
+
+ /* Add this pool to the volume. */
+ info = mpt_vol_info(fd, VolumeBus, VolumeID, NULL);
+ if (info == NULL)
+ return (-1);
+ info->VolumeSettings.HotSparePool |= (1 << new_pool);
+ if (mpt_raid_action(fd, MPI_RAID_ACTION_CHANGE_VOLUME_SETTINGS,
+ VolumeBus, VolumeID, 0, *(U32 *)&info->VolumeSettings, NULL, 0,
+ NULL, NULL, 0, NULL, NULL, 0) < 0) {
+ warnx("Failed to add spare pool %d to %s", new_pool,
+ mpt_volume_name(VolumeBus, VolumeID));
+ return (-1);
+ }
+ free(info);
+
+ *pool = (1 << new_pool);
+ return (0);
+}
+
+static int
+add_spare(int ac, char **av)
+{
+ CONFIG_PAGE_RAID_PHYS_DISK_0 *info;
+ struct mpt_standalone_disk *sdisks;
+ struct mpt_drive_list *list;
+ U8 PhysDiskNum;
+ int error, fd, i, nsdisks, pool;
+
+ if (ac < 2) {
+ warnx("add spare: drive required");
+ return (EINVAL);
+ }
+ if (ac > 3) {
+ warnx("add spare: extra arguments");
+ return (EINVAL);
+ }
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ warn("mpt_open");
+ return (errno);
+ }
+
+ if (ac == 3) {
+ if (find_volume_spare_pool(fd, av[2], &pool) < 0)
+ return (errno);
+ } else
+ pool = MPI_RAID_HOT_SPARE_POOL_0;
+
+ list = mpt_pd_list(fd);
+ if (list == NULL)
+ return (errno);
+
+ error = mpt_lookup_drive(list, av[1], &PhysDiskNum);
+ if (error) {
+ error = mpt_fetch_disks(fd, &nsdisks, &sdisks);
+ if (error != 0) {
+ warn("Failed to fetch standalone disk list");
+ return (error);
+ }
+
+ if (mpt_lookup_standalone_disk(av[1], sdisks, nsdisks, &i) <
+ 0) {
+ warn("Unable to lookup drive %s", av[1]);
+ return (errno);
+ }
+
+ if (mpt_lock_physdisk(&sdisks[i]) < 0)
+ return (errno);
+
+ if (mpt_create_physdisk(fd, &sdisks[i], &PhysDiskNum) < 0) {
+ warn("Failed to create physical disk page");
+ return (errno);
+ }
+ free(sdisks);
+ }
+ mpt_free_pd_list(list);
+
+ info = mpt_pd_info(fd, PhysDiskNum, NULL);
+ if (info == NULL) {
+ warn("Failed to fetch drive info");
+ return (errno);
+ }
+
+ info->PhysDiskSettings.HotSparePool = pool;
+ error = mpt_raid_action(fd, MPI_RAID_ACTION_CHANGE_PHYSDISK_SETTINGS, 0,
+ 0, PhysDiskNum, *(U32 *)&info->PhysDiskSettings, NULL, 0, NULL,
+ NULL, 0, NULL, NULL, 0);
+ if (error) {
+ warn("Failed to assign spare");
+ return (errno);
+ }
+
+ free(info);
+ close(fd);
+
+ return (0);
+}
+MPT_COMMAND(top, add, add_spare);
+
+static int
+remove_spare(int ac, char **av)
+{
+ CONFIG_PAGE_RAID_PHYS_DISK_0 *info;
+ struct mpt_drive_list *list;
+ U8 PhysDiskNum;
+ int error, fd;
+
+ if (ac != 2) {
+ warnx("remove spare: drive required");
+ return (EINVAL);
+ }
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ warn("mpt_open");
+ return (errno);
+ }
+
+ list = mpt_pd_list(fd);
+ if (list == NULL)
+ return (errno);
+
+ error = mpt_lookup_drive(list, av[1], &PhysDiskNum);
+ if (error) {
+ warn("Failed to find drive %s", av[1]);
+ return (error);
+ }
+ mpt_free_pd_list(list);
+
+
+ info = mpt_pd_info(fd, PhysDiskNum, NULL);
+ if (info == NULL) {
+ warn("Failed to fetch drive info");
+ return (errno);
+ }
+
+ if (info->PhysDiskSettings.HotSparePool == 0) {
+ warnx("Drive %u is not a hot spare", PhysDiskNum);
+ return (EINVAL);
+ }
+
+ if (mpt_delete_physdisk(fd, PhysDiskNum) < 0) {
+ warn("Failed to delete physical disk page");
+ return (errno);
+ }
+
+ mpt_rescan_bus(info->PhysDiskBus, info->PhysDiskID);
+ free(info);
+ close(fd);
+
+ return (0);
+}
+MPT_COMMAND(top, remove, remove_spare);
+
+#ifdef DEBUG
+MPT_TABLE(top, pd);
+
+static int
+pd_create(int ac, char **av)
+{
+ struct mpt_standalone_disk *disks;
+ int error, fd, i, ndisks;
+ U8 PhysDiskNum;
+
+ if (ac != 2) {
+ warnx("pd create: drive required");
+ return (EINVAL);
+ }
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ warn("mpt_open");
+ return (errno);
+ }
+
+ error = mpt_fetch_disks(fd, &ndisks, &disks);
+ if (error != 0) {
+ warn("Failed to fetch standalone disk list");
+ return (error);
+ }
+
+ if (mpt_lookup_standalone_disk(av[1], disks, ndisks, &i) < 0) {
+ warn("Unable to lookup drive");
+ return (errno);
+ }
+
+ if (mpt_lock_physdisk(&disks[i]) < 0)
+ return (errno);
+
+ if (mpt_create_physdisk(fd, &disks[i], &PhysDiskNum) < 0) {
+ warn("Failed to create physical disk page");
+ return (errno);
+ }
+ free(disks);
+
+ printf("Added drive %s with PhysDiskNum %u\n", av[1], PhysDiskNum);
+
+ close(fd);
+
+ return (0);
+}
+MPT_COMMAND(pd, create, pd_create);
+
+static int
+pd_delete(int ac, char **av)
+{
+ CONFIG_PAGE_RAID_PHYS_DISK_0 *info;
+ struct mpt_drive_list *list;
+ int fd;
+ U8 PhysDiskNum;
+
+ if (ac != 2) {
+ warnx("pd delete: drive required");
+ return (EINVAL);
+ }
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ warn("mpt_open");
+ return (errno);
+ }
+
+ list = mpt_pd_list(fd);
+ if (list == NULL)
+ return (errno);
+
+ if (mpt_lookup_drive(list, av[1], &PhysDiskNum) < 0) {
+ warn("Failed to find drive %s", av[1]);
+ return (errno);
+ }
+ mpt_free_pd_list(list);
+
+ info = mpt_pd_info(fd, PhysDiskNum, NULL);
+ if (info == NULL) {
+ warn("Failed to fetch drive info");
+ return (errno);
+ }
+
+ if (mpt_delete_physdisk(fd, PhysDiskNum) < 0) {
+ warn("Failed to delete physical disk page");
+ return (errno);
+ }
+
+ mpt_rescan_bus(info->PhysDiskBus, info->PhysDiskID);
+ free(info);
+ close(fd);
+
+ return (0);
+}
+MPT_COMMAND(pd, delete, pd_delete);
+
+/* Display raw data about a volume config. */
+static void
+dump_config(CONFIG_PAGE_RAID_VOL_0 *vol)
+{
+ int i;
+
+ printf("Volume Configuration (Debug):\n");
+ printf(
+ " Page Header: Type 0x%02x Number 0x%02x Length 0x%02x(%u) Version 0x%02x\n",
+ vol->Header.PageType, vol->Header.PageNumber,
+ vol->Header.PageLength, vol->Header.PageLength * 4,
+ vol->Header.PageVersion);
+ printf(" Address: %d:%d IOC %d\n", vol->VolumeBus, vol->VolumeID,
+ vol->VolumeIOC);
+ printf(" Type: %d (%s)\n", vol->VolumeType,
+ mpt_raid_level(vol->VolumeType));
+ printf(" Status: %s (Flags 0x%02x)\n",
+ mpt_volstate(vol->VolumeStatus.State), vol->VolumeStatus.Flags);
+ printf(" Settings: 0x%04x (Spare Pools 0x%02x)\n",
+ vol->VolumeSettings.Settings, vol->VolumeSettings.HotSparePool);
+ printf(" MaxLBA: %ju\n", (uintmax_t)vol->MaxLBAHigh << 32 |
+ vol->MaxLBA);
+ printf(" Stripe Size: %ld\n", (long)vol->StripeSize * 512);
+ printf(" %d Disks:\n", vol->NumPhysDisks);
+
+ for (i = 0; i < vol->NumPhysDisks; i++)
+ printf(" Disk %d: Num 0x%02x Map 0x%02x\n", i,
+ vol->PhysDisk[i].PhysDiskNum, vol->PhysDisk[i].PhysDiskMap);
+}
+
+static int
+debug_config(int ac, char **av)
+{
+ CONFIG_PAGE_RAID_VOL_0 *vol;
+ U8 VolumeBus, VolumeID;
+ int fd;
+
+ if (ac != 2) {
+ warnx("debug: volume required");
+ return (EINVAL);
+ }
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ warn("mpt_open");
+ return (errno);
+ }
+
+ if (mpt_lookup_volume(fd, av[1], &VolumeBus, &VolumeID) < 0) {
+ warn("Invalid volume: %s", av[1]);
+ return (errno);
+ }
+
+ vol = mpt_vol_info(fd, VolumeBus, VolumeID, NULL);
+ if (vol == NULL) {
+ warn("Failed to get volume info");
+ return (errno);
+ }
+
+ dump_config(vol);
+ free(vol);
+ close(fd);
+
+ return (0);
+}
+MPT_COMMAND(top, debug, debug_config);
+#endif
diff --git a/usr.sbin/mptutil/mpt_drive.c b/usr.sbin/mptutil/mpt_drive.c
new file mode 100644
index 0000000000000..e941b967e5be6
--- /dev/null
+++ b/usr.sbin/mptutil/mpt_drive.c
@@ -0,0 +1,395 @@
+/*-
+ * Copyright (c) 2008 Yahoo!, Inc.
+ * All rights reserved.
+ * Written by: John Baldwin <jhb@FreeBSD.org>
+ *
+ * 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.
+ * 3. Neither the name of the author nor the names of any co-contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * 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.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/errno.h>
+#include <ctype.h>
+#include <err.h>
+#include <libutil.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include <camlib.h>
+#include <cam/scsi/scsi_all.h>
+
+#include "mptutil.h"
+
+const char *
+mpt_pdstate(CONFIG_PAGE_RAID_PHYS_DISK_0 *info)
+{
+ static char buf[16];
+
+ switch (info->PhysDiskStatus.State) {
+ case MPI_PHYSDISK0_STATUS_ONLINE:
+ if ((info->PhysDiskStatus.Flags &
+ MPI_PHYSDISK0_STATUS_FLAG_OUT_OF_SYNC) &&
+ info->PhysDiskSettings.HotSparePool == 0)
+ return ("REBUILD");
+ else
+ return ("ONLINE");
+ case MPI_PHYSDISK0_STATUS_MISSING:
+ return ("MISSING");
+ case MPI_PHYSDISK0_STATUS_NOT_COMPATIBLE:
+ return ("NOT COMPATIBLE");
+ case MPI_PHYSDISK0_STATUS_FAILED:
+ return ("FAILED");
+ case MPI_PHYSDISK0_STATUS_INITIALIZING:
+ return ("INITIALIZING");
+ case MPI_PHYSDISK0_STATUS_OFFLINE_REQUESTED:
+ return ("OFFLINE REQUESTED");
+ case MPI_PHYSDISK0_STATUS_FAILED_REQUESTED:
+ return ("FAILED REQUESTED");
+ case MPI_PHYSDISK0_STATUS_OTHER_OFFLINE:
+ return ("OTHER OFFLINE");
+ default:
+ sprintf(buf, "PSTATE 0x%02x", info->PhysDiskStatus.State);
+ return (buf);
+ }
+}
+
+/*
+ * There are several ways to enumerate physical disks. Unfortunately,
+ * none of them are truly complete, so we have to build a union of all of
+ * them. Specifically:
+ *
+ * - IOC2 : This gives us a list of volumes, and by walking the volumes we
+ * can enumerate all of the drives attached to volumes including
+ * online drives and failed drives.
+ * - IOC3 : This gives us a list of all online physical drives including
+ * drives that are not part of a volume nor a spare drive. It
+ * does not include any failed drives.
+ * - IOC5 : This gives us a list of all spare drives including failed
+ * spares.
+ *
+ * The specific edge cases are that 1) a failed volume member can only be
+ * found via IOC2, 2) a drive that is neither a volume member nor a spare
+ * can only be found via IOC3, and 3) a failed spare can only be found via
+ * IOC5.
+ *
+ * To handle this, walk all of the three lists and use the following
+ * routine to add each drive encountered. It quietly succeeds if the
+ * drive is already present in the list. It also sorts the list as it
+ * inserts new drives.
+ */
+static int
+mpt_pd_insert(int fd, struct mpt_drive_list *list, U8 PhysDiskNum)
+{
+ int i, j;
+
+ /*
+ * First, do a simple linear search to see if we have already
+ * seen this drive.
+ */
+ for (i = 0; i < list->ndrives; i++) {
+ if (list->drives[i]->PhysDiskNum == PhysDiskNum)
+ return (0);
+ if (list->drives[i]->PhysDiskNum > PhysDiskNum)
+ break;
+ }
+
+ /*
+ * 'i' is our slot for the 'new' drive. Make room and then
+ * read the drive info.
+ */
+ for (j = list->ndrives - 1; j >= i; j--)
+ list->drives[j + 1] = list->drives[j];
+ list->drives[i] = mpt_pd_info(fd, PhysDiskNum, NULL);
+ if (list->drives[i] == NULL)
+ return (-1);
+ list->ndrives++;
+ return (0);
+}
+
+struct mpt_drive_list *
+mpt_pd_list(int fd)
+{
+ CONFIG_PAGE_IOC_2 *ioc2;
+ CONFIG_PAGE_IOC_2_RAID_VOL *vol;
+ CONFIG_PAGE_RAID_VOL_0 **volumes;
+ RAID_VOL0_PHYS_DISK *rdisk;
+ CONFIG_PAGE_IOC_3 *ioc3;
+ IOC_3_PHYS_DISK *disk;
+ CONFIG_PAGE_IOC_5 *ioc5;
+ IOC_5_HOT_SPARE *spare;
+ struct mpt_drive_list *list;
+ int count, i, j;
+
+ ioc2 = mpt_read_ioc_page(fd, 2, NULL);
+ if (ioc2 == NULL) {
+ warn("Failed to fetch volume list");
+ return (NULL);
+ }
+
+ ioc3 = mpt_read_ioc_page(fd, 3, NULL);
+ if (ioc3 == NULL) {
+ warn("Failed to fetch drive list");
+ free(ioc2);
+ return (NULL);
+ }
+
+ ioc5 = mpt_read_ioc_page(fd, 5, NULL);
+ if (ioc5 == NULL) {
+ warn("Failed to fetch spare list");
+ free(ioc3);
+ free(ioc2);
+ return (NULL);
+ }
+
+ /*
+ * Go ahead and read the info for all the volumes. For this
+ * pass we figure out how many physical drives there are.
+ */
+ volumes = malloc(sizeof(*volumes) * ioc2->NumActiveVolumes);
+ count = 0;
+ vol = ioc2->RaidVolume;
+ for (i = 0; i < ioc2->NumActiveVolumes; vol++, i++) {
+ volumes[i] = mpt_vol_info(fd, vol->VolumeBus, vol->VolumeID,
+ NULL);
+ if (volumes[i] == NULL) {
+ warn("Failed to read volume info");
+ return (NULL);
+ }
+ count += volumes[i]->NumPhysDisks;
+ }
+ count += ioc3->NumPhysDisks;
+ count += ioc5->NumHotSpares;
+
+ /* Walk the various lists enumerating drives. */
+ list = malloc(sizeof(*list) + sizeof(CONFIG_PAGE_RAID_PHYS_DISK_0) *
+ count);
+ list->ndrives = 0;
+
+ for (i = 0; i < ioc2->NumActiveVolumes; i++) {
+ rdisk = volumes[i]->PhysDisk;
+ for (j = 0; j < volumes[i]->NumPhysDisks; rdisk++, j++)
+ if (mpt_pd_insert(fd, list, rdisk->PhysDiskNum) < 0)
+ return (NULL);
+ free(volumes[i]);
+ }
+ free(ioc2);
+ free(volumes);
+
+ spare = ioc5->HotSpare;
+ for (i = 0; i < ioc5->NumHotSpares; spare++, i++)
+ if (mpt_pd_insert(fd, list, spare->PhysDiskNum) < 0)
+ return (NULL);
+ free(ioc5);
+
+ disk = ioc3->PhysDisk;
+ for (i = 0; i < ioc3->NumPhysDisks; disk++, i++)
+ if (mpt_pd_insert(fd, list, disk->PhysDiskNum) < 0)
+ return (NULL);
+ free(ioc3);
+
+ return (list);
+}
+
+void
+mpt_free_pd_list(struct mpt_drive_list *list)
+{
+ int i;
+
+ for (i = 0; i < list->ndrives; i++)
+ free(list->drives[i]);
+ free(list);
+}
+
+int
+mpt_lookup_drive(struct mpt_drive_list *list, const char *drive,
+ U8 *PhysDiskNum)
+{
+ long val;
+ uint8_t bus, id;
+ char *cp;
+
+ /* Look for a raw device id first. */
+ val = strtol(drive, &cp, 0);
+ if (*cp == '\0') {
+ if (val < 0 || val > 0xff)
+ goto bad;
+ *PhysDiskNum = val;
+ return (0);
+ }
+
+ /* Look for a <bus>:<id> string. */
+ if (*cp == ':') {
+ if (val < 0 || val > 0xff)
+ goto bad;
+ bus = val;
+ val = strtol(cp + 1, &cp, 0);
+ if (*cp != '\0')
+ goto bad;
+ if (val < 0 || val > 0xff)
+ goto bad;
+ id = val;
+
+ for (val = 0; val < list->ndrives; val++) {
+ if (list->drives[val]->PhysDiskBus == bus &&
+ list->drives[val]->PhysDiskID == id) {
+ *PhysDiskNum = list->drives[val]->PhysDiskNum;
+ return (0);
+ }
+ }
+ errno = ENOENT;
+ return (-1);
+ }
+
+bad:
+ errno = EINVAL;
+ return (-1);
+}
+
+/* Borrowed heavily from scsi_all.c:scsi_print_inquiry(). */
+const char *
+mpt_pd_inq_string(CONFIG_PAGE_RAID_PHYS_DISK_0 *pd_info)
+{
+ RAID_PHYS_DISK0_INQUIRY_DATA *inq_data;
+ u_char vendor[9], product[17], revision[5];
+ static char inq_string[64];
+
+ inq_data = &pd_info->InquiryData;
+ cam_strvis(vendor, inq_data->VendorID, sizeof(inq_data->VendorID),
+ sizeof(vendor));
+ cam_strvis(product, inq_data->ProductID, sizeof(inq_data->ProductID),
+ sizeof(product));
+ cam_strvis(revision, inq_data->ProductRevLevel,
+ sizeof(inq_data->ProductRevLevel), sizeof(revision));
+
+ /* Total hack. */
+ if (strcmp(vendor, "ATA") == 0)
+ snprintf(inq_string, sizeof(inq_string), "<%s %s> SATA",
+ product, revision);
+ else
+ snprintf(inq_string, sizeof(inq_string), "<%s %s %s> SAS",
+ vendor, product, revision);
+ return (inq_string);
+}
+
+/* Helper function to set a drive to a given state. */
+static int
+drive_set_state(char *drive, U8 Action, U8 State, const char *name)
+{
+ CONFIG_PAGE_RAID_PHYS_DISK_0 *info;
+ struct mpt_drive_list *list;
+ U8 PhysDiskNum;
+ int fd;
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ warn("mpt_open");
+ return (errno);
+ }
+
+ list = mpt_pd_list(fd);
+ if (list == NULL)
+ return (errno);
+
+ if (mpt_lookup_drive(list, drive, &PhysDiskNum) < 0) {
+ warn("Failed to find drive %s", drive);
+ return (errno);
+ }
+ mpt_free_pd_list(list);
+
+ /* Get the info for this drive. */
+ info = mpt_pd_info(fd, PhysDiskNum, NULL);
+ if (info == NULL) {
+ warn("Failed to fetch info for drive %u", PhysDiskNum);
+ return (errno);
+ }
+
+ /* Try to change the state. */
+ if (info->PhysDiskStatus.State == State) {
+ warnx("Drive %u is already in the desired state", PhysDiskNum);
+ return (EINVAL);
+ }
+
+ if (mpt_raid_action(fd, Action, 0, 0, PhysDiskNum, 0, NULL, 0, NULL,
+ NULL, 0, NULL, NULL, 0) < 0) {
+ warn("Failed to set drive %u to %s", PhysDiskNum, name);
+ return (errno);
+ }
+
+ free(info);
+ close(fd);
+
+ return (0);
+}
+
+static int
+fail_drive(int ac, char **av)
+{
+
+ if (ac != 2) {
+ warnx("fail: %s", ac > 2 ? "extra arguments" :
+ "drive required");
+ return (EINVAL);
+ }
+
+ return (drive_set_state(av[1], MPI_RAID_ACTION_FAIL_PHYSDISK,
+ MPI_PHYSDISK0_STATUS_FAILED_REQUESTED, "FAILED"));
+}
+MPT_COMMAND(top, fail, fail_drive);
+
+static int
+online_drive(int ac, char **av)
+{
+
+ if (ac != 2) {
+ warnx("online: %s", ac > 2 ? "extra arguments" :
+ "drive required");
+ return (EINVAL);
+ }
+
+ return (drive_set_state(av[1], MPI_RAID_ACTION_PHYSDISK_ONLINE,
+ MPI_PHYSDISK0_STATUS_ONLINE, "ONLINE"));
+}
+MPT_COMMAND(top, online, online_drive);
+
+static int
+offline_drive(int ac, char **av)
+{
+
+ if (ac != 2) {
+ warnx("offline: %s", ac > 2 ? "extra arguments" :
+ "drive required");
+ return (EINVAL);
+ }
+
+ return (drive_set_state(av[1], MPI_RAID_ACTION_PHYSDISK_OFFLINE,
+ MPI_PHYSDISK0_STATUS_OFFLINE_REQUESTED, "OFFLINE"));
+}
+MPT_COMMAND(top, offline, offline_drive);
diff --git a/usr.sbin/mptutil/mpt_evt.c b/usr.sbin/mptutil/mpt_evt.c
new file mode 100644
index 0000000000000..4b64352e42df7
--- /dev/null
+++ b/usr.sbin/mptutil/mpt_evt.c
@@ -0,0 +1,155 @@
+/*-
+ * Copyright (c) 2008 Yahoo!, Inc.
+ * All rights reserved.
+ * Written by: John Baldwin <jhb@FreeBSD.org>
+ *
+ * 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.
+ * 3. Neither the name of the author nor the names of any co-contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * 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.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/errno.h>
+#include <ctype.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include "mptutil.h"
+
+static CONFIG_PAGE_LOG_0 *
+mpt_get_events(int fd, U16 *IOCStatus)
+{
+
+ return (mpt_read_extended_config_page(fd, MPI_CONFIG_EXTPAGETYPE_LOG,
+ 0, 0, 0, IOCStatus));
+}
+
+/*
+ * 1 2 3 4 5 6 7
+ * 1234567890123456789012345678901234567890123456789012345678901234567890
+ * < ID> < time > <ty> <X XX XX XX XX XX XX XX XX XX XX XX XX XX |..............|
+ * ID Time Type Log Data
+ */
+static void
+mpt_print_event(MPI_LOG_0_ENTRY *entry, int verbose)
+{
+ int i;
+
+ printf("%5d %7ds %4x ", entry->LogSequence, entry->TimeStamp,
+ entry->LogEntryQualifier);
+ for (i = 0; i < 14; i++)
+ printf("%02x ", entry->LogData[i]);
+ printf("|");
+ for (i = 0; i < 14; i++)
+ printf("%c", isprint(entry->LogData[i]) ? entry->LogData[i] :
+ '.');
+ printf("|\n");
+ printf(" ");
+ for (i = 0; i < 14; i++)
+ printf("%02x ", entry->LogData[i + 14]);
+ printf("|");
+ for (i = 0; i < 14; i++)
+ printf("%c", isprint(entry->LogData[i + 14]) ?
+ entry->LogData[i + 14] : '.');
+ printf("|\n");
+}
+
+static int
+event_compare(const void *first, const void *second)
+{
+ MPI_LOG_0_ENTRY * const *one;
+ MPI_LOG_0_ENTRY * const *two;
+
+ one = first;
+ two = second;
+ return ((*one)->LogSequence - ((*two)->LogSequence));
+}
+
+static int
+show_events(int ac, char **av)
+{
+ CONFIG_PAGE_LOG_0 *log;
+ MPI_LOG_0_ENTRY **entries;
+ int ch, fd, i, num_events, verbose;
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ warn("mpt_open");
+ return (errno);
+ }
+
+ log = mpt_get_events(fd, NULL);
+ if (log == NULL) {
+ warn("Failed to get event log info");
+ return (errno);
+ }
+
+ /* Default settings. */
+ verbose = 0;
+
+ /* Parse any options. */
+ optind = 1;
+ while ((ch = getopt(ac, av, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ verbose = 1;
+ break;
+ case '?':
+ default:
+ return (EINVAL);
+ }
+ }
+ ac -= optind;
+ av += optind;
+
+ /* Build a list of valid entries and sort them by sequence. */
+ entries = malloc(sizeof(MPI_LOG_0_ENTRY *) * log->NumLogEntries);
+ num_events = 0;
+ for (i = 0; i < log->NumLogEntries; i++) {
+ if (log->LogEntry[i].LogEntryQualifier ==
+ MPI_LOG_0_ENTRY_QUAL_ENTRY_UNUSED)
+ continue;
+ entries[num_events] = &log->LogEntry[i];
+ num_events++;
+ }
+
+ qsort(entries, num_events, sizeof(MPI_LOG_0_ENTRY *), event_compare);
+
+ if (num_events == 0)
+ printf("Event log is empty\n");
+ else {
+ printf(" ID Time Type Log Data\n");
+ for (i = 0; i < num_events; i++)
+ mpt_print_event(entries[i], verbose);
+ }
+
+ free(entries);
+ close(fd);
+
+ return (0);
+}
+MPT_COMMAND(show, events, show_events);
diff --git a/usr.sbin/mptutil/mpt_show.c b/usr.sbin/mptutil/mpt_show.c
new file mode 100644
index 0000000000000..e0b6b74d136d6
--- /dev/null
+++ b/usr.sbin/mptutil/mpt_show.c
@@ -0,0 +1,559 @@
+/*-
+ * Copyright (c) 2008 Yahoo!, Inc.
+ * All rights reserved.
+ * Written by: John Baldwin <jhb@FreeBSD.org>
+ *
+ * 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.
+ * 3. Neither the name of the author nor the names of any co-contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * 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.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/errno.h>
+#include <err.h>
+#include <libutil.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "mptutil.h"
+
+MPT_TABLE(top, show);
+
+#define STANDALONE_STATE "ONLINE"
+
+static void
+format_stripe(char *buf, size_t buflen, U32 stripe)
+{
+
+ humanize_number(buf, buflen, stripe * 512, "", HN_AUTOSCALE,
+ HN_B | HN_NOSPACE);
+}
+
+static void
+display_stripe_map(const char *label, U32 StripeMap)
+{
+ char stripe[5];
+ int comma, i;
+
+ comma = 0;
+ printf("%s: ", label);
+ for (i = 0; StripeMap != 0; i++, StripeMap >>= 1)
+ if (StripeMap & 1) {
+ format_stripe(stripe, sizeof(stripe), 1 << i);
+ if (comma)
+ printf(", ");
+ printf("%s", stripe);
+ comma = 1;
+ }
+ printf("\n");
+}
+
+static int
+show_adapter(int ac, char **av)
+{
+ CONFIG_PAGE_MANUFACTURING_0 *man0;
+ CONFIG_PAGE_IOC_2 *ioc2;
+ CONFIG_PAGE_IOC_6 *ioc6;
+ int fd, comma;
+
+ if (ac != 1) {
+ warnx("show adapter: extra arguments");
+ return (EINVAL);
+ }
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ warn("mpt_open");
+ return (errno);
+ }
+
+ man0 = mpt_read_man_page(fd, 0, NULL);
+ if (man0 == NULL) {
+ warn("Failed to get controller info");
+ return (errno);
+ }
+ if (man0->Header.PageLength < sizeof(*man0) / 4) {
+ warn("Invalid controller info");
+ return (EINVAL);
+ }
+ printf("mpt%d Adapter:\n", mpt_unit);
+ printf(" Board Name: %.16s\n", man0->BoardName);
+ printf(" Board Assembly: %.16s\n", man0->BoardAssembly);
+ printf(" Chip Name: %.16s\n", man0->ChipName);
+ printf(" Chip Revision: %.16s\n", man0->ChipRevision);
+
+ free(man0);
+
+ ioc2 = mpt_read_ioc_page(fd, 2, NULL);
+ if (ioc2 != NULL) {
+ printf(" RAID Levels:");
+ comma = 0;
+ if (ioc2->CapabilitiesFlags &
+ MPI_IOCPAGE2_CAP_FLAGS_IS_SUPPORT) {
+ printf(" RAID0");
+ comma = 1;
+ }
+ if (ioc2->CapabilitiesFlags &
+ MPI_IOCPAGE2_CAP_FLAGS_IM_SUPPORT) {
+ printf("%s RAID1", comma ? "," : "");
+ comma = 1;
+ }
+ if (ioc2->CapabilitiesFlags &
+ MPI_IOCPAGE2_CAP_FLAGS_IME_SUPPORT) {
+ printf("%s RAID1E", comma ? "," : "");
+ comma = 1;
+ }
+ if (ioc2->CapabilitiesFlags &
+ MPI_IOCPAGE2_CAP_FLAGS_RAID_5_SUPPORT) {
+ printf("%s RAID5", comma ? "," : "");
+ comma = 1;
+ }
+ if (ioc2->CapabilitiesFlags &
+ MPI_IOCPAGE2_CAP_FLAGS_RAID_6_SUPPORT) {
+ printf("%s RAID6", comma ? "," : "");
+ comma = 1;
+ }
+ if (ioc2->CapabilitiesFlags &
+ MPI_IOCPAGE2_CAP_FLAGS_RAID_10_SUPPORT) {
+ printf("%s RAID10", comma ? "," : "");
+ comma = 1;
+ }
+ if (ioc2->CapabilitiesFlags &
+ MPI_IOCPAGE2_CAP_FLAGS_RAID_50_SUPPORT) {
+ printf("%s RAID50", comma ? "," : "");
+ comma = 1;
+ }
+ if (!comma)
+ printf(" none");
+ printf("\n");
+ free(ioc2);
+ }
+
+ ioc6 = mpt_read_ioc_page(fd, 6, NULL);
+ if (ioc6 != NULL) {
+ display_stripe_map(" RAID0 Stripes",
+ ioc6->SupportedStripeSizeMapIS);
+ display_stripe_map(" RAID1E Stripes",
+ ioc6->SupportedStripeSizeMapIME);
+ printf(" RAID0 Drives/Vol: %u", ioc6->MinDrivesIS);
+ if (ioc6->MinDrivesIS != ioc6->MaxDrivesIS)
+ printf("-%u", ioc6->MaxDrivesIS);
+ printf("\n");
+ printf(" RAID1 Drives/Vol: %u", ioc6->MinDrivesIM);
+ if (ioc6->MinDrivesIM != ioc6->MaxDrivesIM)
+ printf("-%u", ioc6->MaxDrivesIM);
+ printf("\n");
+ printf("RAID1E Drives/Vol: %u", ioc6->MinDrivesIME);
+ if (ioc6->MinDrivesIME != ioc6->MaxDrivesIME)
+ printf("-%u", ioc6->MaxDrivesIME);
+ printf("\n");
+ free(ioc6);
+ }
+
+ /* TODO: Add an ioctl to fetch IOC_FACTS and print firmware version. */
+
+ close(fd);
+
+ return (0);
+}
+MPT_COMMAND(show, adapter, show_adapter);
+
+static void
+print_vol(CONFIG_PAGE_RAID_VOL_0 *info, int state_len)
+{
+ uint64_t size;
+ const char *level, *state;
+ char buf[6], stripe[5];
+
+ size = ((uint64_t)info->MaxLBAHigh << 32) | info->MaxLBA;
+ humanize_number(buf, sizeof(buf), (size + 1) * 512, "", HN_AUTOSCALE,
+ HN_B | HN_NOSPACE | HN_DECIMAL);
+ if (info->VolumeType == MPI_RAID_VOL_TYPE_IM)
+ stripe[0] = '\0';
+ else
+ format_stripe(stripe, sizeof(stripe), info->StripeSize);
+ level = mpt_raid_level(info->VolumeType);
+ state = mpt_volstate(info->VolumeStatus.State);
+ if (state_len > 0)
+ printf("(%6s) %-8s %6s %-*s", buf, level, stripe, state_len,
+ state);
+ else if (stripe[0] != '\0')
+ printf("(%s) %s %s %s", buf, level, stripe, state);
+ else
+ printf("(%s) %s %s", buf, level, state);
+}
+
+static void
+print_pd(CONFIG_PAGE_RAID_PHYS_DISK_0 *info, int state_len, int location)
+{
+ const char *inq, *state;
+ char buf[6];
+
+ humanize_number(buf, sizeof(buf), ((uint64_t)info->MaxLBA + 1) * 512,
+ "", HN_AUTOSCALE, HN_B | HN_NOSPACE |HN_DECIMAL);
+ state = mpt_pdstate(info);
+ if (state_len > 0)
+ printf("(%6s) %-*s", buf, state_len, state);
+ else
+ printf("(%s) %s", buf, state);
+ inq = mpt_pd_inq_string(info);
+ if (inq != NULL)
+ printf(" %s", inq);
+ if (!location)
+ return;
+ printf(" bus %d id %d", info->PhysDiskBus, info->PhysDiskID);
+}
+
+static void
+print_standalone(struct mpt_standalone_disk *disk, int state_len, int location)
+{
+ char buf[6];
+
+ humanize_number(buf, sizeof(buf), (disk->maxlba + 1) * 512,
+ "", HN_AUTOSCALE, HN_B | HN_NOSPACE |HN_DECIMAL);
+ if (state_len > 0)
+ printf("(%6s) %-*s", buf, state_len, STANDALONE_STATE);
+ else
+ printf("(%s) %s", buf, STANDALONE_STATE);
+ if (disk->inqstring[0] != '\0')
+ printf(" %s", disk->inqstring);
+ if (!location)
+ return;
+ printf(" bus %d id %d", disk->bus, disk->target);
+}
+
+static void
+print_spare_pools(U8 HotSparePool)
+{
+ int i;
+
+ if (HotSparePool == 0) {
+ printf("none");
+ return;
+ }
+ for (i = 0; HotSparePool != 0; i++) {
+ if (HotSparePool & 1) {
+ printf("%d", i);
+ if (HotSparePool == 1)
+ break;
+ printf(", ");
+ }
+ HotSparePool >>= 1;
+ }
+}
+
+static int
+show_config(int ac, char **av)
+{
+ CONFIG_PAGE_IOC_2 *ioc2;
+ CONFIG_PAGE_IOC_2_RAID_VOL *vol;
+ CONFIG_PAGE_IOC_5 *ioc5;
+ IOC_5_HOT_SPARE *spare;
+ CONFIG_PAGE_RAID_VOL_0 *vinfo;
+ RAID_VOL0_PHYS_DISK *disk;
+ CONFIG_PAGE_RAID_VOL_1 *vnames;
+ CONFIG_PAGE_RAID_PHYS_DISK_0 *pinfo;
+ struct mpt_standalone_disk *sdisks;
+ int fd, i, j, nsdisks;
+
+ if (ac != 1) {
+ warnx("show config: extra arguments");
+ return (EINVAL);
+ }
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ warn("mpt_open");
+ return (errno);
+ }
+
+ /* Get the config from the controller. */
+ ioc2 = mpt_read_ioc_page(fd, 2, NULL);
+ ioc5 = mpt_read_ioc_page(fd, 5, NULL);
+ if (ioc2 == NULL || ioc5 == NULL) {
+ warn("Failed to get config");
+ return (errno);
+ }
+ if (mpt_fetch_disks(fd, &nsdisks, &sdisks) < 0) {
+ warn("Failed to get standalone drive list");
+ return (errno);
+ }
+
+ /* Dump out the configuration. */
+ printf("mpt%d Configuration: %d volumes, %d drives\n",
+ mpt_unit, ioc2->NumActiveVolumes, ioc2->NumActivePhysDisks +
+ nsdisks);
+ vol = ioc2->RaidVolume;
+ for (i = 0; i < ioc2->NumActiveVolumes; vol++, i++) {
+ printf(" volume %s ", mpt_volume_name(vol->VolumeBus,
+ vol->VolumeID));
+ vinfo = mpt_vol_info(fd, vol->VolumeBus, vol->VolumeID, NULL);
+ if (vinfo == NULL) {
+ printf("%s UNKNOWN", mpt_raid_level(vol->VolumeType));
+ } else
+ print_vol(vinfo, -1);
+ vnames = mpt_vol_names(fd, vol->VolumeBus, vol->VolumeID, NULL);
+ if (vnames != NULL) {
+ if (vnames->Name[0] != '\0')
+ printf(" <%s>", vnames->Name);
+ free(vnames);
+ }
+ if (vinfo == NULL) {
+ printf("\n");
+ continue;
+ }
+ printf(" spans:\n");
+ disk = vinfo->PhysDisk;
+ for (j = 0; j < vinfo->NumPhysDisks; disk++, j++) {
+ printf(" drive %u ", disk->PhysDiskNum);
+ pinfo = mpt_pd_info(fd, disk->PhysDiskNum, NULL);
+ if (pinfo != NULL) {
+ print_pd(pinfo, -1, 0);
+ free(pinfo);
+ }
+ printf("\n");
+ }
+ if (vinfo->VolumeSettings.HotSparePool != 0) {
+ printf(" spare pools: ");
+ print_spare_pools(vinfo->VolumeSettings.HotSparePool);
+ printf("\n");
+ }
+ free(vinfo);
+ }
+
+ spare = ioc5->HotSpare;
+ for (i = 0; i < ioc5->NumHotSpares; spare++, i++) {
+ printf(" spare %u ", spare->PhysDiskNum);
+ pinfo = mpt_pd_info(fd, spare->PhysDiskNum, NULL);
+ if (pinfo != NULL) {
+ print_pd(pinfo, -1, 0);
+ free(pinfo);
+ }
+ printf(" backs pool %d\n", ffs(spare->HotSparePool) - 1);
+ }
+ for (i = 0; i < nsdisks; i++) {
+ printf(" drive %s ", sdisks[i].devname);
+ print_standalone(&sdisks[i], -1, 0);
+ printf("\n");
+ }
+ free(ioc2);
+ free(ioc5);
+ free(sdisks);
+ close(fd);
+
+ return (0);
+}
+MPT_COMMAND(show, config, show_config);
+
+static int
+show_volumes(int ac, char **av)
+{
+ CONFIG_PAGE_IOC_2 *ioc2;
+ CONFIG_PAGE_IOC_2_RAID_VOL *vol;
+ CONFIG_PAGE_RAID_VOL_0 **volumes;
+ CONFIG_PAGE_RAID_VOL_1 *vnames;
+ int fd, i, len, state_len;
+
+ if (ac != 1) {
+ warnx("show volumes: extra arguments");
+ return (EINVAL);
+ }
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ warn("mpt_open");
+ return (errno);
+ }
+
+ /* Get the volume list from the controller. */
+ ioc2 = mpt_read_ioc_page(fd, 2, NULL);
+ if (ioc2 == NULL) {
+ warn("Failed to get volume list");
+ return (errno);
+ }
+
+ /*
+ * Go ahead and read the info for all the volumes and figure
+ * out the maximum width of the state field.
+ */
+ volumes = malloc(sizeof(*volumes) * ioc2->NumActiveVolumes);
+ state_len = strlen("State");
+ vol = ioc2->RaidVolume;
+ for (i = 0; i < ioc2->NumActiveVolumes; vol++, i++) {
+ volumes[i] = mpt_vol_info(fd, vol->VolumeBus, vol->VolumeID,
+ NULL);
+ if (volumes[i] == NULL)
+ len = strlen("UNKNOWN");
+ else
+ len = strlen(mpt_volstate(
+ volumes[i]->VolumeStatus.State));
+ if (len > state_len)
+ state_len = len;
+ }
+ printf("mpt%d Volumes:\n", mpt_unit);
+ printf(" Id Size Level Stripe ");
+ len = state_len - strlen("State");
+ for (i = 0; i < (len + 1) / 2; i++)
+ printf(" ");
+ printf("State");
+ for (i = 0; i < len / 2; i++)
+ printf(" ");
+ printf(" Write-Cache Name\n");
+ vol = ioc2->RaidVolume;
+ for (i = 0; i < ioc2->NumActiveVolumes; vol++, i++) {
+ printf("%6s ", mpt_volume_name(vol->VolumeBus, vol->VolumeID));
+ if (volumes[i] != NULL)
+ print_vol(volumes[i], state_len);
+ else
+ printf(" %-8s %-*s",
+ mpt_raid_level(vol->VolumeType), state_len,
+ "UNKNOWN");
+ if (volumes[i] != NULL) {
+ if (volumes[i]->VolumeSettings.Settings &
+ MPI_RAIDVOL0_SETTING_WRITE_CACHING_ENABLE)
+ printf(" Enabled ");
+ else
+ printf(" Disabled ");
+ } else
+ printf(" ");
+ free(volumes[i]);
+ vnames = mpt_vol_names(fd, vol->VolumeBus, vol->VolumeID, NULL);
+ if (vnames != NULL) {
+ if (vnames->Name[0] != '\0')
+ printf(" <%s>", vnames->Name);
+ free(vnames);
+ }
+ printf("\n");
+ }
+ free(ioc2);
+ close(fd);
+
+ return (0);
+}
+MPT_COMMAND(show, volumes, show_volumes);
+
+static int
+show_drives(int ac, char **av)
+{
+ struct mpt_drive_list *list;
+ struct mpt_standalone_disk *sdisks;
+ int fd, i, len, nsdisks, state_len;
+
+ if (ac != 1) {
+ warnx("show drives: extra arguments");
+ return (EINVAL);
+ }
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ warn("mpt_open");
+ return (errno);
+ }
+
+ /* Get the drive list. */
+ list = mpt_pd_list(fd);
+ if (list == NULL) {
+ warn("Failed to get drive list");
+ return (errno);
+ }
+
+ /* Fetch the list of standalone disks for this controller. */
+ state_len = 0;
+ if (mpt_fetch_disks(fd, &nsdisks, &sdisks) != 0) {
+ nsdisks = 0;
+ sdisks = NULL;
+ }
+ if (nsdisks != 0)
+ state_len = strlen(STANDALONE_STATE);
+
+ /* Walk the drive list to determine width of state column. */
+ for (i = 0; i < list->ndrives; i++) {
+ len = strlen(mpt_pdstate(list->drives[i]));
+ if (len > state_len)
+ state_len = len;
+ }
+
+ /* List the drives. */
+ printf("mpt%d Physical Drives:\n", mpt_unit);
+ for (i = 0; i < list->ndrives; i++) {
+ printf("%4u ", list->drives[i]->PhysDiskNum);
+ print_pd(list->drives[i], state_len, 1);
+ printf("\n");
+ }
+ mpt_free_pd_list(list);
+ for (i = 0; i < nsdisks; i++) {
+ printf("%4s ", sdisks[i].devname);
+ print_standalone(&sdisks[i], state_len, 1);
+ printf("\n");
+ }
+ free(sdisks);
+
+ close(fd);
+
+ return (0);
+}
+MPT_COMMAND(show, drives, show_drives);
+
+#ifdef DEBUG
+static int
+show_physdisks(int ac, char **av)
+{
+ CONFIG_PAGE_RAID_PHYS_DISK_0 *pinfo;
+ U16 IOCStatus;
+ int fd, i;
+
+ if (ac != 1) {
+ warnx("show drives: extra arguments");
+ return (EINVAL);
+ }
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ warn("mpt_open");
+ return (errno);
+ }
+
+ /* Try to find each possible phys disk page. */
+ for (i = 0; i <= 0xff; i++) {
+ pinfo = mpt_pd_info(fd, i, &IOCStatus);
+ if (pinfo == NULL) {
+ if (IOCStatus != MPI_IOCSTATUS_CONFIG_INVALID_PAGE)
+ warnx("mpt_pd_info(%d): %s", i,
+ mpt_ioc_status(IOCStatus));
+ continue;
+ }
+ printf("%3u ", i);
+ print_pd(pinfo, -1, 1);
+ printf("\n");
+ }
+
+ close(fd);
+
+ return (0);
+}
+MPT_COMMAND(show, pd, show_physdisks);
+#endif
diff --git a/usr.sbin/mptutil/mpt_volume.c b/usr.sbin/mptutil/mpt_volume.c
new file mode 100644
index 0000000000000..04adcb7c9874d
--- /dev/null
+++ b/usr.sbin/mptutil/mpt_volume.c
@@ -0,0 +1,248 @@
+/*-
+ * Copyright (c) 2008 Yahoo!, Inc.
+ * All rights reserved.
+ * Written by: John Baldwin <jhb@FreeBSD.org>
+ *
+ * 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.
+ * 3. Neither the name of the author nor the names of any co-contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * 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.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/errno.h>
+#include <err.h>
+#include <libutil.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+#include "mptutil.h"
+
+MPT_TABLE(top, volume);
+
+const char *
+mpt_volstate(U8 State)
+{
+ static char buf[16];
+
+ switch (State) {
+ case MPI_RAIDVOL0_STATUS_STATE_OPTIMAL:
+ return ("OPTIMAL");
+ case MPI_RAIDVOL0_STATUS_STATE_DEGRADED:
+ return ("DEGRADED");
+ case MPI_RAIDVOL0_STATUS_STATE_FAILED:
+ return ("FAILED");
+ case MPI_RAIDVOL0_STATUS_STATE_MISSING:
+ return ("MISSING");
+ default:
+ sprintf(buf, "VSTATE 0x%02x", State);
+ return (buf);
+ }
+}
+
+static int
+volume_name(int ac, char **av)
+{
+ CONFIG_PAGE_RAID_VOL_1 *vnames;
+ U8 VolumeBus, VolumeID;
+ int fd;
+
+ if (ac != 3) {
+ warnx("name: volume and name required");
+ return (EINVAL);
+ }
+
+ if (strlen(av[2]) >= sizeof(vnames->Name)) {
+ warnx("name: new name is too long");
+ return (ENOSPC);
+ }
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ warn("mpt_open");
+ return (errno);
+ }
+
+ if (mpt_lookup_volume(fd, av[1], &VolumeBus, &VolumeID) < 0) {
+ warn("Invalid volume: %s", av[1]);
+ return (errno);
+ }
+
+ vnames = mpt_vol_names(fd, VolumeBus, VolumeID, NULL);
+ if (vnames == NULL) {
+ warn("Failed to fetch volume names");
+ return (errno);
+ }
+
+ if (vnames->Header.PageType != MPI_CONFIG_PAGEATTR_CHANGEABLE) {
+ warnx("Volume name is read only");
+ return (EOPNOTSUPP);
+ }
+ printf("mpt%u changing volume %s name from \"%s\" to \"%s\"\n",
+ mpt_unit, mpt_volume_name(VolumeBus, VolumeID), vnames->Name,
+ av[2]);
+ bzero(vnames->Name, sizeof(vnames->Name));
+ strcpy(vnames->Name, av[2]);
+
+ if (mpt_write_config_page(fd, vnames, NULL) < 0) {
+ warn("Failed to set volume name");
+ return (errno);
+ }
+
+ free(vnames);
+ close(fd);
+
+ return (0);
+}
+MPT_COMMAND(top, name, volume_name);
+
+static int
+volume_status(int ac, char **av)
+{
+ MPI_RAID_VOL_INDICATOR prog;
+ RAID_VOL0_STATUS VolumeStatus;
+ uint64_t total, remaining;
+ float pct;
+ U8 VolumeBus, VolumeID;
+ int fd;
+
+ if (ac != 2) {
+ warnx("volume status: %s", ac > 2 ? "extra arguments" :
+ "volume required");
+ return (EINVAL);
+ }
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ warn("mpt_open");
+ return (errno);
+ }
+
+ if (mpt_lookup_volume(fd, av[1], &VolumeBus, &VolumeID) < 0) {
+ warn("Invalid volume: %s", av[1]);
+ return (errno);
+ }
+
+ if (mpt_raid_action(fd, MPI_RAID_ACTION_INDICATOR_STRUCT, VolumeBus,
+ VolumeID, 0, 0, NULL, 0, &VolumeStatus, (U32 *)&prog, sizeof(prog),
+ NULL, NULL, 0) < 0) {
+ warn("Fetching volume status failed");
+ return (errno);
+ }
+
+ printf("Volume %s status:\n", mpt_volume_name(VolumeBus, VolumeID));
+ printf(" state: %s\n", mpt_volstate(VolumeStatus.State));
+ printf(" flags:");
+ if (VolumeStatus.Flags & MPI_RAIDVOL0_STATUS_FLAG_ENABLED)
+ printf(" ENABLED");
+ else
+ printf(" DISABLED");
+ if (VolumeStatus.Flags & MPI_RAIDVOL0_STATUS_FLAG_QUIESCED)
+ printf(", QUIESCED");
+ if (VolumeStatus.Flags & MPI_RAIDVOL0_STATUS_FLAG_RESYNC_IN_PROGRESS)
+ printf(", REBUILDING");
+ if (VolumeStatus.Flags & MPI_RAIDVOL0_STATUS_FLAG_VOLUME_INACTIVE)
+ printf(", INACTIVE");
+ if (VolumeStatus.Flags & MPI_RAIDVOL0_STATUS_FLAG_BAD_BLOCK_TABLE_FULL)
+ printf(", BAD BLOCK TABLE FULL");
+ printf("\n");
+ if (VolumeStatus.Flags & MPI_RAIDVOL0_STATUS_FLAG_RESYNC_IN_PROGRESS) {
+ total = (uint64_t)prog.TotalBlocks.High << 32 |
+ prog.TotalBlocks.Low;
+ remaining = (uint64_t)prog.BlocksRemaining.High << 32 |
+ prog.BlocksRemaining.Low;
+ pct = (float)(total - remaining) * 100 / total;
+ printf(" resync: %.2f%% complete\n", pct);
+ }
+
+ close(fd);
+ return (0);
+}
+MPT_COMMAND(volume, status, volume_status);
+
+static int
+volume_cache(int ac, char **av)
+{
+ CONFIG_PAGE_RAID_VOL_0 *volume;
+ U32 Settings, NewSettings;
+ U8 VolumeBus, VolumeID;
+ char *s1;
+ int fd;
+
+ if (ac != 3) {
+ warnx("volume cache: %s", ac > 3 ? "extra arguments" :
+ "volume required");
+ return (EINVAL);
+ }
+
+ for (s1 = av[2]; *s1 != '\0'; s1++)
+ *s1 = tolower(*s1);
+ if ((strcmp(av[2], "enable")) && (strcmp(av[2], "enabled")) &&
+ (strcmp(av[2], "disable")) && (strcmp(av[2], "disabled"))) {
+ warnx("volume cache: invalid flag, must be 'enable' or 'disable'\n");
+ return (EINVAL);
+ }
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ warn("mpt_open");
+ return (errno);
+ }
+
+ if (mpt_lookup_volume(fd, av[1], &VolumeBus, &VolumeID) < 0) {
+ warn("Invalid volume: %s", av[1]);
+ return (errno);
+ }
+
+ volume = mpt_vol_info(fd, VolumeBus, VolumeID, NULL);
+ if (volume == NULL)
+ return (-1);
+
+ Settings = volume->VolumeSettings.Settings;
+
+ NewSettings = Settings;
+ if (strncmp(av[2], "enable", sizeof("enable")) == 0)
+ NewSettings |= 0x01;
+ if (strncmp(av[2], "disable", sizeof("disable")) == 0)
+ NewSettings &= ~0x01;
+
+ if (NewSettings == Settings) {
+ warnx("volume cache unchanged\n");
+ close(fd);
+ return (0);
+ }
+
+ volume->VolumeSettings.Settings = NewSettings;
+ if (mpt_raid_action(fd, MPI_RAID_ACTION_CHANGE_VOLUME_SETTINGS,
+ VolumeBus, VolumeID, 0, *(U32 *)&volume->VolumeSettings, NULL, 0,
+ NULL, NULL, 0, NULL, NULL, 0) < 0)
+ warnx("volume cache change failed, errno= %d\n", errno);
+
+ close(fd);
+ return (0);
+}
+MPT_COMMAND(volume, cache, volume_cache);
diff --git a/usr.sbin/mptutil/mptutil.8 b/usr.sbin/mptutil/mptutil.8
new file mode 100644
index 0000000000000..fcc34e8a16233
--- /dev/null
+++ b/usr.sbin/mptutil/mptutil.8
@@ -0,0 +1,383 @@
+.\"
+.\" Copyright (c) 2008 Yahoo!, Inc.
+.\" All rights reserved.
+.\" Written by: John Baldwin <jhb@FreeBSD.org>
+.\"
+.\" 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.
+.\" 3. Neither the name of the author nor the names of any co-contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" 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$
+.\"
+.Dd August 22, 2008
+.Dt MPTUTIL 8
+.Os
+.Sh NAME
+.Nm mptutil
+.Nd Utility for managing LSI Fusion-MPT controllers
+.Sh SYNOPSIS
+.Nm
+.Cm version
+.Nm
+.Op Fl u Ar unit
+.Cm show adapter
+.Nm
+.Op Fl u Ar unit
+.Cm show config
+.Nm
+.Op Fl u Ar unit
+.Cm show drives
+.Nm
+.Op Fl u Ar unit
+.Cm show events
+.Nm
+.Op Fl u Ar unit
+.Cm show volumes
+.Nm
+.Op Fl u Ar unit
+.Cm fail Ar drive
+.Nm
+.Op Fl u Ar unit
+.Cm online Ar drive
+.Nm
+.Op Fl u Ar unit
+.Cm offline Ar drive
+.Nm
+.Op Fl u Ar unit
+.Cm name Ar volume Ar name
+.Nm
+.Op Fl u Ar unit
+.Cm volume status Ar volume
+.Nm
+.Op Fl u Ar unit
+.Cm volume cache Ar volume
+.Ar enable|disable
+.Nm
+.Op Fl u Ar unit
+.Cm clear
+.Nm
+.Op Fl u Ar unit
+.Cm create Ar type
+.Op Fl q
+.Op Fl v
+.Op Fl s Ar stripe_size
+.Ar drive Ns Op \&, Ns Ar drive Ns Op ",..."
+.Nm
+.Op Fl u Ar unit
+.Cm delete Ar volume
+.Nm
+.Op Fl u Ar unit
+.Cm add Ar drive Op Ar volume
+.Nm
+.Op Fl u Ar unit
+.Cm remove Ar drive
+.Sh DESCRIPTION
+The
+.Nm
+utility can be used to display or modify various parameters on LSI
+Fusion-MPT controllers.
+Each invocation of
+.Nm
+consists of zero or more global options followed by a command.
+Commands may support additional optional or required arguments after the
+command.
+.Pp
+Currently one global option is supported:
+.Bl -tag -width indent
+.It Fl u Ar unit
+.Ar unit
+specifies the unit of the controller to work with.
+If no unit is specified,
+then unit 0 is used.
+.El
+.Pp
+Volumes may be specified in two forms.
+First,
+a volume may be identified by its location as
+.Sm off
+.Op Ar xx Ns \&:
+.Ar yy
+.Sm on
+where
+.Ar xx
+is the bus ID and
+.Ar yy
+is the target ID.
+If the bus ID is ommitted,
+the volume is assumed to be on bus 0.
+Second,
+on the volume may be specified by the corresponding
+.Em daX
+device,
+such as
+.Em da0 .
+.Pp
+The
+.Xr mpt 4
+controller divides drives up into two categories.
+Configured drives belong to a RAID volume either as a member drive or as a hot
+spare.
+Each configured drive is assigned a unique device ID such as 0 or 1 that is
+show in
+.Cm show config ,
+and in the first column of
+.Cm show drives .
+Any drive not associated with a RAID volume as either a member or a hot spare
+is a standalone drive.
+Standalone drives are visible to the operating system as SCSI disk devices.
+As a result, drives may be specified in three forms.
+First,
+a configured drive may be identified by its device ID.
+Second,
+any drive may be identified by its location as
+.Sm off
+.Ar xx Ns \&:
+.Ar yy
+.Sm on
+where
+.Ar xx
+is the bus ID and
+.Ar yy
+is the target ID for each drive as displayed in
+.Cm show drives .
+Note that unlike volumes,
+a drive location always requires the bus ID to avoid confusion with device IDs.
+Third,
+a standalone drive that is not part of a volume may be identified by its
+corresponding
+.Em daX
+device as displayed in
+.Cm show drives .
+.Pp
+The
+.Nm
+utility supports several different groups of commands.
+The first group of commands provide information about the controller,
+the volumes it manages, and the drives it controls.
+The second group of commands are used to manage the physical drives
+attached to the controller.
+The third group of commands are used to manage the logical volumes
+managed by the controller.
+The fourth group of commands are used to manage the drive configuration for
+the controller.
+.Pp
+The informational commands include:
+.Bl -tag -width indent
+.It Cm version
+Displays the version of
+.Nm .
+.It Cm show adapter
+Displays information about the RAID controller such as the model number.
+.It Cm show config
+Displays the volume and drive configuration for the controller.
+Each volume is listed along with the physical drives that the volume spans.
+If any hot spare drives are configured, then they are listed as well.
+.It Cm show drives
+Lists all of the physical drives attached to the controller.
+.It Cm show events
+Display all the entries from the controller's event log.
+Due to lack of documentation this command isn't very useful currently and
+just dumps each log entry in hex.
+.It Cm show volumes
+Lists all of the logical volumes managed by the controller.
+.El
+.Pp
+The physical drive management commands include:
+.Bl -tag -width indent
+.It Cm fail Ar drive
+Mark
+.Ar drive
+as
+.Dq failed requested .
+Note that this state is different from the
+.Dq failed
+state that is used when the firmware fails a drive.
+.Ar Drive
+must be a configured drive.
+.It Cm online Ar drive
+Mark
+.Ar drive
+as an online drive.
+.Ar Drive
+must be part a configured drive in either the
+.Dq offline
+or
+.Dq failed requested
+states.
+.It Cm offline Ar drive
+Mark
+.Ar drive
+as offline.
+.Ar Drive
+must be a configured, online drive.
+.El
+.Pp
+The logical volume management commands include:
+.Bl -tag -width indent
+.It Cm name Ar volume Ar name
+Sets the name of
+.Ar volume
+to
+.Ar name .
+.It Cm volume cache Ar volume Ar enable|disable
+Enables or disables the drive write cache for the member drives of
+.Ar volume .
+.It Cm volume status Ar volume
+Display more detailed status about a single volume including the current
+progress of a rebuild operation if one is being performed.
+.El
+.Pp
+The configuration commands include:
+.Bl -tag -width indent
+.It Cm clear
+Delete the entire configuration including all volumes and spares.
+All drives will become standalone drives.
+.It Xo Cm create Ar type
+.Op Fl q
+.Op Fl v
+.Op Fl s Ar stripe_size
+.Ar drive Ns Op \&, Ns Ar drive Ns Op ",..."
+.Xc
+Create a new volume.
+The
+.Ar type
+specifies the type of volume to create.
+Currently supported types include:
+.Bl -tag -width indent
+.It Cm raid0
+Creates one RAID0 volume spanning the drives listed in the single drive list.
+.It Cm raid1
+Creates one RAID1 volume spanning the drives listed in the single drive list.
+.It Cm raid1e
+Creates one RAID1E volume spanning the drives listed in the single drive list.
+.El
+.Pp
+.Sy Note:
+Not all volume types are supported by all controllers.
+.Pp
+If the
+.Fl q
+flag is specified after
+.Ar type ,
+then a
+.Dq quick
+initialization of the volume will be done.
+This is useful when the drives do not contain any existing data that need
+to be preserved.
+.Pp
+If the
+.Fl v
+flag is specified after
+.Ar type ,
+then more verbose output will be enabled.
+Currently this just provides notification as drives are added to volumes
+when building the configuration.
+.Pp
+The
+.Fl s
+.Ar stripe_size
+parameter allows the stripe size of the array to be set.
+By default a stripe size of 64K is used.
+The list of valid values for a given
+.Ar type
+are listed in the output of
+.Cm show adapter .
+.It Cm delete Ar volume
+Delete the volume
+.Ar volume .
+Member drives will become standalone drives.
+.It Cm add Ar drive Op Ar volume
+Mark
+.Ar drive
+as a hot spare.
+.Ar Drive
+must not be a member of a volume.
+If
+.Ar volume
+is specified,
+then the hot spare will be dedicated to that volume.
+Otherwise,
+.Ar drive
+will be used as a global hot spare backing all volumes for this controller.
+Note that
+.Ar drive
+must be as large as the smallest drive in all of the volumes it is going to
+back.
+.It Cm remove Ar drive
+Remove the hot spare
+.Ar drive
+from service.
+It will become a standalone drive.
+.El
+.Sh EXAMPLES
+Mark the drive at bus 0 target 4 as offline:
+.Pp
+.Dl Nm Cm offline 0:4
+.Pp
+Create a RAID1 array from the two standalone drives
+.Va da1
+and
+.Va da2 :
+.Pp
+.Dl Nm Cm create raid1 da1,da2
+.Pp
+Mark standalone drive
+.Va da3
+as a global hot spare:
+.Pp
+.Dl Nm Cm add da3
+.Sh SEE ALSO
+.Xr mpt 4
+.Sh BUGS
+Deleting volumes usually provokes a kernel crash in OS versions older than
+.Dv 6.3-YAHOO-20080722 .
+.Pp
+The handling of spare drives appears to be unreliable.
+The
+.Xr mpt 4
+firmware manages spares via spare drive
+.Dq pools .
+There are eight pools numbered 0 through 7.
+Each spare drive can only be assigned to a single pool.
+Each volume can be backed by any combination of zero or more spare pools.
+The
+.Nm
+utility attempts to use the following algorithm for managing spares.
+Global spares are always assigned to pool 0,
+and all volumes are always backed by pool 0.
+For dedicated spares,
+.Nm
+assigns one of the remaining 7 pools to each volume and
+assigns dedicated drives to that pool.
+In practice however, it seems that assigning a drive as a spare does not
+take effect until the box has been rebooted.
+Also, the firmware renumbers the spare pool assignments after a reboot
+which undoes the effects of the algorithm above.
+Simple cases such as assigning global spares seem to work ok
+.Pq albeit requiring a reboot to take effect
+but more
+.Dq exotic
+configurations may not work reliably.
+.Pp
+Drive configuration commands result in an excessive flood of messages on the
+console.
diff --git a/usr.sbin/mptutil/mptutil.c b/usr.sbin/mptutil/mptutil.c
new file mode 100644
index 0000000000000..e7b58060ef1ad
--- /dev/null
+++ b/usr.sbin/mptutil/mptutil.c
@@ -0,0 +1,123 @@
+/*-
+ * Copyright (c) 2008 Yahoo!, Inc.
+ * All rights reserved.
+ * Written by: John Baldwin <jhb@FreeBSD.org>
+ *
+ * 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.
+ * 3. Neither the name of the author nor the names of any co-contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * 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.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/errno.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "mptutil.h"
+
+SET_DECLARE(MPT_DATASET(top), struct mptutil_command);
+
+int mpt_unit;
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "usage: mptutil [-u unit] <command> ...\n\n");
+ fprintf(stderr, "Commands include:\n");
+ fprintf(stderr, " version\n");
+ fprintf(stderr, " show adapter - display controller information\n");
+ fprintf(stderr, " show config - display RAID configuration\n");
+ fprintf(stderr, " show drives - list physical drives\n");
+ fprintf(stderr, " show events - display event log\n");
+ fprintf(stderr, " show volumes - list logical volumes\n");
+ fprintf(stderr, " fail <drive> - fail a physical drive\n");
+ fprintf(stderr, " online <drive> - bring an offline physical drive online\n");
+ fprintf(stderr, " offline <drive> - mark a physical drive offline\n");
+ fprintf(stderr, " name <volume> <name>\n");
+ fprintf(stderr, " volume status <volume> - display volume status\n");
+ fprintf(stderr, " volume cache <volume> <enable|disable>\n");
+ fprintf(stderr, " - Enable or disable the volume drive caches\n");
+ fprintf(stderr, " clear - clear volume configuration\n");
+ fprintf(stderr, " create <type> [-vq] [-s stripe] <drive>[,<drive>[,...]]\n");
+ fprintf(stderr, " delete <volume>\n");
+ fprintf(stderr, " add <drive> [volume] - add a hot spare\n");
+ fprintf(stderr, " remove <drive> - remove a hot spare\n");
+#ifdef DEBUG
+ fprintf(stderr, " pd create <drive> - create RAID physdisk\n");
+ fprintf(stderr, " pd delete <drive> - delete RAID physdisk\n");
+ fprintf(stderr, " debug - debug 'show config'\n");
+#endif
+ exit(1);
+}
+
+static int
+version(int ac, char **av)
+{
+
+ printf("mptutil version 1.0.3");
+#ifdef DEBUG
+ printf(" (DEBUG)");
+#endif
+ printf("\n");
+ return (0);
+}
+MPT_COMMAND(top, version, version);
+
+int
+main(int ac, char **av)
+{
+ struct mptutil_command **cmd;
+ int ch;
+
+ while ((ch = getopt(ac, av, "u:")) != -1) {
+ switch (ch) {
+ case 'u':
+ mpt_unit = atoi(optarg);
+ break;
+ case '?':
+ usage();
+ }
+ }
+
+ av += optind;
+ ac -= optind;
+
+ /* getopt() eats av[0], so we can't use mpt_table_handler() directly. */
+ if (ac == 0)
+ usage();
+
+ SET_FOREACH(cmd, MPT_DATASET(top)) {
+ if (strcmp((*cmd)->name, av[0]) == 0) {
+ (*cmd)->handler(ac, av);
+ return (0);
+ }
+ }
+ warnx("Unknown command %s.", av[0]);
+ return (0);
+}
diff --git a/usr.sbin/mptutil/mptutil.h b/usr.sbin/mptutil/mptutil.h
new file mode 100644
index 0000000000000..3951235933434
--- /dev/null
+++ b/usr.sbin/mptutil/mptutil.h
@@ -0,0 +1,178 @@
+/*-
+ * Copyright (c) 2008 Yahoo!, Inc.
+ * All rights reserved.
+ * Written by: John Baldwin <jhb@FreeBSD.org>
+ *
+ * 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.
+ * 3. Neither the name of the author nor the names of any co-contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * 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$
+ */
+
+#ifndef __MPTUTIL_H__
+#define __MPTUTIL_H__
+
+#include <sys/cdefs.h>
+#include <sys/linker_set.h>
+
+#include <dev/mpt/mpilib/mpi_type.h>
+#include <dev/mpt/mpilib/mpi.h>
+#include <dev/mpt/mpilib/mpi_cnfg.h>
+#include <dev/mpt/mpilib/mpi_raid.h>
+
+#define IOC_STATUS_SUCCESS(status) \
+ (((status) & MPI_IOCSTATUS_MASK) == MPI_IOCSTATUS_SUCCESS)
+
+struct mpt_query_disk {
+ char devname[SPECNAMELEN + 1];
+};
+
+struct mpt_standalone_disk {
+ uint64_t maxlba;
+ char inqstring[64];
+ char devname[SPECNAMELEN + 1];
+ u_int bus;
+ u_int target;
+};
+
+struct mpt_drive_list {
+ int ndrives;
+ CONFIG_PAGE_RAID_PHYS_DISK_0 *drives[0];
+};
+
+struct mptutil_command {
+ const char *name;
+ int (*handler)(int ac, char **av);
+};
+
+#define MPT_DATASET(name) mptutil_ ## name ## _table
+
+#define MPT_COMMAND(set, name, function) \
+ static struct mptutil_command function ## _mptutil_command = \
+ { #name, function }; \
+ DATA_SET(MPT_DATASET(set), function ## _mptutil_command)
+
+#define MPT_TABLE(set, name) \
+ SET_DECLARE(MPT_DATASET(name), struct mptutil_command); \
+ \
+ static int \
+ mptutil_ ## name ## _table_handler(int ac, char **av) \
+ { \
+ return (mpt_table_handler(SET_BEGIN(MPT_DATASET(name)), \
+ SET_LIMIT(MPT_DATASET(name)), ac, av)); \
+ } \
+ MPT_COMMAND(set, name, mptutil_ ## name ## _table_handler)
+
+extern int mpt_unit;
+
+#ifdef DEBUG
+void hexdump(const void *ptr, int length, const char *hdr, int flags);
+#define HD_COLUMN_MASK 0xff
+#define HD_DELIM_MASK 0xff00
+#define HD_OMIT_COUNT (1 << 16)
+#define HD_OMIT_HEX (1 << 17)
+#define HD_OMIT_CHARS (1 << 18)
+#endif
+
+int mpt_table_handler(struct mptutil_command **start,
+ struct mptutil_command **end, int ac, char **av);
+int mpt_read_config_page_header(int fd, U8 PageType, U8 PageNumber,
+ U32 PageAddress, CONFIG_PAGE_HEADER *header, U16 *IOCStatus);
+void *mpt_read_config_page(int fd, U8 PageType, U8 PageNumber,
+ U32 PageAddress, U16 *IOCStatus);
+void *mpt_read_extended_config_page(int fd, U8 ExtPageType, U8 PageVersion,
+ U8 PageNumber, U32 PageAddress, U16 *IOCStatus);
+int mpt_write_config_page(int fd, void *buf, U16 *IOCStatus);
+const char *mpt_ioc_status(U16 IOCStatus);
+int mpt_raid_action(int fd, U8 Action, U8 VolumeBus, U8 VolumeID,
+ U8 PhysDiskNum, U32 ActionDataWord, void *buf, int len,
+ RAID_VOL0_STATUS *VolumeStatus, U32 *ActionData, int datalen,
+ U16 *IOCStatus, U16 *ActionStatus, int write);
+const char *mpt_raid_status(U16 ActionStatus);
+int mpt_open(int unit);
+const char *mpt_raid_level(U8 VolumeType);
+const char *mpt_volstate(U8 State);
+const char *mpt_pdstate(CONFIG_PAGE_RAID_PHYS_DISK_0 *info);
+const char *mpt_pd_inq_string(CONFIG_PAGE_RAID_PHYS_DISK_0 *pd_info);
+struct mpt_drive_list *mpt_pd_list(int fd);
+void mpt_free_pd_list(struct mpt_drive_list *list);
+int mpt_query_disk(U8 VolumeBus, U8 VolumeID, struct mpt_query_disk *qd);
+const char *mpt_volume_name(U8 VolumeBus, U8 VolumeID);
+int mpt_fetch_disks(int fd, int *ndisks,
+ struct mpt_standalone_disk **disksp);
+int mpt_lock_volume(U8 VolumeBus, U8 VolumeID);
+int mpt_lookup_drive(struct mpt_drive_list *list, const char *drive,
+ U8 *PhysDiskNum);
+int mpt_lookup_volume(int fd, const char *name, U8 *VolumeBus,
+ U8 *VolumeID);
+int mpt_rescan_bus(int bus, int id);
+
+static __inline void *
+mpt_read_man_page(int fd, U8 PageNumber, U16 *IOCStatus)
+{
+
+ return (mpt_read_config_page(fd, MPI_CONFIG_PAGETYPE_MANUFACTURING,
+ PageNumber, 0, IOCStatus));
+}
+
+static __inline void *
+mpt_read_ioc_page(int fd, U8 PageNumber, U16 *IOCStatus)
+{
+
+ return (mpt_read_config_page(fd, MPI_CONFIG_PAGETYPE_IOC, PageNumber,
+ 0, IOCStatus));
+}
+
+static __inline U32
+mpt_vol_pageaddr(U8 VolumeBus, U8 VolumeID)
+{
+
+ return (VolumeBus << 8 | VolumeID);
+}
+
+static __inline CONFIG_PAGE_RAID_VOL_0 *
+mpt_vol_info(int fd, U8 VolumeBus, U8 VolumeID, U16 *IOCStatus)
+{
+
+ return (mpt_read_config_page(fd, MPI_CONFIG_PAGETYPE_RAID_VOLUME, 0,
+ mpt_vol_pageaddr(VolumeBus, VolumeID), IOCStatus));
+}
+
+static __inline CONFIG_PAGE_RAID_VOL_1 *
+mpt_vol_names(int fd, U8 VolumeBus, U8 VolumeID, U16 *IOCStatus)
+{
+
+ return (mpt_read_config_page(fd, MPI_CONFIG_PAGETYPE_RAID_VOLUME, 1,
+ mpt_vol_pageaddr(VolumeBus, VolumeID), IOCStatus));
+}
+
+static __inline CONFIG_PAGE_RAID_PHYS_DISK_0 *
+mpt_pd_info(int fd, U8 PhysDiskNum, U16 *IOCStatus)
+{
+
+ return (mpt_read_config_page(fd, MPI_CONFIG_PAGETYPE_RAID_PHYSDISK, 0,
+ PhysDiskNum, IOCStatus));
+}
+
+#endif /* !__MPTUTIL_H__ */