diff options
author | Scott Long <scottl@FreeBSD.org> | 2009-08-14 13:13:12 +0000 |
---|---|---|
committer | Scott Long <scottl@FreeBSD.org> | 2009-08-14 13:13:12 +0000 |
commit | fc58801ccc318e391722e23c01826884edd25bf9 (patch) | |
tree | 0183f7ea0315a5ab4ebbe7eaca64082dbe89e351 /usr.sbin/mptutil | |
parent | 6d600732ebfcb814df88e9e29e66619262609bc4 (diff) |
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/Makefile | 19 | ||||
-rw-r--r-- | usr.sbin/mptutil/mpt_cam.c | 569 | ||||
-rw-r--r-- | usr.sbin/mptutil/mpt_cmd.c | 639 | ||||
-rw-r--r-- | usr.sbin/mptutil/mpt_config.c | 1160 | ||||
-rw-r--r-- | usr.sbin/mptutil/mpt_drive.c | 395 | ||||
-rw-r--r-- | usr.sbin/mptutil/mpt_evt.c | 155 | ||||
-rw-r--r-- | usr.sbin/mptutil/mpt_show.c | 559 | ||||
-rw-r--r-- | usr.sbin/mptutil/mpt_volume.c | 248 | ||||
-rw-r--r-- | usr.sbin/mptutil/mptutil.8 | 383 | ||||
-rw-r--r-- | usr.sbin/mptutil/mptutil.c | 123 | ||||
-rw-r--r-- | usr.sbin/mptutil/mptutil.h | 178 |
11 files changed, 4428 insertions, 0 deletions
diff --git a/usr.sbin/mptutil/Makefile b/usr.sbin/mptutil/Makefile new file mode 100644 index 000000000000..4abf66e4657d --- /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 000000000000..0d20c7db6019 --- /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 000000000000..2d6000c77313 --- /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 000000000000..3874df301379 --- /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 000000000000..e941b967e5be --- /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 000000000000..4b64352e42df --- /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 000000000000..e0b6b74d136d --- /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 000000000000..04adcb7c9874 --- /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 000000000000..fcc34e8a1623 --- /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 000000000000..e7b58060ef1a --- /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 000000000000..395123593343 --- /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__ */ |