diff options
Diffstat (limited to 'usr.sbin/mptutil')
| -rw-r--r-- | usr.sbin/mptutil/Makefile | 16 | ||||
| -rw-r--r-- | usr.sbin/mptutil/Makefile.depend | 17 | ||||
| -rw-r--r-- | usr.sbin/mptutil/mpt_cam.c | 565 | ||||
| -rw-r--r-- | usr.sbin/mptutil/mpt_cmd.c | 628 | ||||
| -rw-r--r-- | usr.sbin/mptutil/mpt_config.c | 1259 | ||||
| -rw-r--r-- | usr.sbin/mptutil/mpt_drive.c | 429 | ||||
| -rw-r--r-- | usr.sbin/mptutil/mpt_evt.c | 164 | ||||
| -rw-r--r-- | usr.sbin/mptutil/mpt_show.c | 584 | ||||
| -rw-r--r-- | usr.sbin/mptutil/mpt_volume.c | 269 | ||||
| -rw-r--r-- | usr.sbin/mptutil/mptutil.8 | 395 | ||||
| -rw-r--r-- | usr.sbin/mptutil/mptutil.c | 128 | ||||
| -rw-r--r-- | usr.sbin/mptutil/mptutil.h | 178 | 
12 files changed, 4632 insertions, 0 deletions
| diff --git a/usr.sbin/mptutil/Makefile b/usr.sbin/mptutil/Makefile new file mode 100644 index 000000000000..1d1e904b8273 --- /dev/null +++ b/usr.sbin/mptutil/Makefile @@ -0,0 +1,16 @@ +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 + +LIBADD=	cam util + +# Here be dragons +.ifdef DEBUG +CFLAGS+= -DDEBUG +.endif + +.include <bsd.prog.mk> diff --git a/usr.sbin/mptutil/Makefile.depend b/usr.sbin/mptutil/Makefile.depend new file mode 100644 index 000000000000..3c2b5b10ac32 --- /dev/null +++ b/usr.sbin/mptutil/Makefile.depend @@ -0,0 +1,17 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ +	include \ +	include/xlocale \ +	lib/${CSU_DIR} \ +	lib/libc \ +	lib/libcam \ +	lib/libcompiler_rt \ +	lib/libutil \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/usr.sbin/mptutil/mpt_cam.c b/usr.sbin/mptutil/mpt_cam.c new file mode 100644 index 000000000000..87aebc8819a1 --- /dev/null +++ b/usr.sbin/mptutil/mpt_cam.c @@ -0,0 +1,565 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * 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/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); +} + +/* Fetch the path id of bus 0 for the opened mpt controller. */ +static int +fetch_path_id(path_id_t *path_id) +{ +	struct bus_match_pattern *b; +	union ccb ccb; +	size_t bufsize; +	int error; + +	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) { +		error = errno; +		free(ccb.cdm.matches); +		free(ccb.cdm.patterns); +		return (error); +	} +	free(ccb.cdm.patterns); + +	if (((ccb.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) || +	    (ccb.cdm.status != CAM_DEV_MATCH_LAST)) { +		warnx("fetch_path_id 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); +	return (0); +} + +int +mpt_query_disk(U8 VolumeBus, U8 VolumeID, struct mpt_query_disk *qd) +{ +	struct periph_match_pattern *p; +	struct periph_match_result *r; +	union ccb ccb; +	path_id_t path_id; +	size_t bufsize; +	int error; + +	/* mpt(4) only handles devices on bus 0. */ +	if (VolumeBus != 0) +		return (ENXIO); + +	if (xpt_open() < 0) +		return (ENXIO); + +	/* Find the path ID of bus 0. */ +	error = fetch_path_id(&path_id); +	if (error) +		return (error); + +	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) * 1; +	ccb.cdm.num_patterns = 1; +	ccb.cdm.pattern_buf_len = bufsize; +	ccb.cdm.patterns = calloc(1, bufsize); + +	/* Look for a "da" device at the specified target and lun. */ +	ccb.cdm.patterns[0].type = DEV_MATCH_PERIPH; +	p = &ccb.cdm.patterns[0].pattern.periph_pattern; +	p->path_id = path_id; +	snprintf(p->periph_name, sizeof(p->periph_name), "da"); +	p->target_id = VolumeID; +	p->flags = PERIPH_MATCH_PATH | PERIPH_MATCH_NAME | PERIPH_MATCH_TARGET; + +	if (ioctl(xptfd, CAMIOCOMMAND, &ccb) < 0) { +		error = errno; +		free(ccb.cdm.matches); +		free(ccb.cdm.patterns); +		return (error); +	} +	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 1 match for the peripheral. +	 * However, if we don't get a match, don't print an error +	 * message and return ENOENT. +	 */ +	if (ccb.cdm.num_matches == 0) { +		free(ccb.cdm.matches); +		return (ENOENT); +	} +	if (ccb.cdm.num_matches != 1) { +		warnx("mpt_query_disk got %d matches, expected 1", +		    ccb.cdm.num_matches); +		free(ccb.cdm.matches); +		return (EIO); +	} +	if (ccb.cdm.matches[0].type != DEV_MATCH_PERIPH) { +		warnx("mpt_query_disk got wrong CAM match"); +		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. */ +	CCB_CLEAR_ALL_EXCEPT_HDR(&ccb->csio); + +	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); +	} + +	/* +	 * 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); +		cam_freeccb(ccb); +		return (0); +	} + +	/* Zero the rest of the ccb. */ +	CCB_CLEAR_ALL_EXCEPT_HDR(&ccb->csio); + +	scsi_read_capacity_16(&ccb->csio, 1, NULL, MSG_SIMPLE_Q_TAG, 0, 0, 0, +	    (uint8_t *)&rcaplong, sizeof(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. */ +	CCB_CLEAR_ALL_EXCEPT_HDR(&ccb->csio); + +	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 periph_match_pattern *p; +	struct periph_match_result *r; +	struct cam_device *dev; +	union ccb ccb; +	path_id_t path_id; +	size_t bufsize; +	int count, error; +	uint32_t i; + +	if (xpt_open() < 0) +		return (ENXIO); + +	error = fetch_path_id(&path_id); +	if (error) +		return (error); + +	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 + 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 any "da" peripherals. */ +		ccb.cdm.patterns[0].type = DEV_MATCH_PERIPH; +		p = &ccb.cdm.patterns[0].pattern.periph_pattern; +		p->path_id = path_id; +		snprintf(p->periph_name, sizeof(p->periph_name), "da"); +		p->flags = PERIPH_MATCH_PATH | PERIPH_MATCH_NAME; + +		if (ioctl(xptfd, CAMIOCOMMAND, &ccb) < 0) { +			error = errno; +			free(ccb.cdm.matches); +			free(ccb.cdm.patterns); +			return (error); +		} +		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; +	} + +	/* Shortcut if we don't have any "da" devices. */ +	if (ccb.cdm.num_matches == 0) { +		free(ccb.cdm.matches); +		*ndisks = 0; +		*disksp = NULL; +		return (0); +	} + +	/* We should have N matches, 1 for each "da" device. */ +	for (i = 0; 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); +		} +	} + +	/* +	 * 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); +	if (ioc2 == NULL) +		return (errno); +	disks = calloc(ccb.cdm.num_matches, sizeof(*disks)); +	count = 0; +	for (i = 0; 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 buses 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 buses are scanned instead of the specified bus. + * Note that currently, only bus 0 is supported. + */ +int +mpt_rescan_bus(int bus, int id) +{ +	union ccb ccb; +	path_id_t path_id; +	int error; + +	/* mpt(4) only handles devices on bus 0. */ +	if (bus != -1 && bus != 0) +		return (EINVAL); + +	if (xpt_open() < 0) +		return (ENXIO); + +	error = fetch_path_id(&path_id); +	if (error) +		return (error); + +	/* Perform the actual rescan. */ +	bzero(&ccb, sizeof(ccb)); +	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..8e091e2ed19b --- /dev/null +++ b/usr.sbin/mptutil/mpt_cmd.c @@ -0,0 +1,628 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * 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/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) { +				return (EINVAL); +			} +			*VolumeBus = bus; +			*VolumeID = id; +			return (0); +		} +	} else if (*cp == '\0') { +		if (bus < 0 || bus > 0xff) +			return (EINVAL); +		*VolumeBus = 0; +		*VolumeID = bus; +		return (0); +	} + +	ioc2 = mpt_read_ioc_page(fd, 2, NULL); +	if (ioc2 == NULL) +		return (errno); + +	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); +	return (EINVAL); +} + +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 (errno); +	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)); +		return (EIO); +	} +	*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 error; + +	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) { +		error = errno; +		free(buf); +		errno = error; +		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 error; + +	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) { +		error = errno; +		free(buf); +		errno = error; +		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 (errno); +	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)); +		return (EIO); +	} +	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)) +		return (EINVAL); +	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 (errno); + +	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)); +		return (EIO); +	} + +	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)); +		return (EIO); +	} + +	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..066f8df28268 --- /dev/null +++ b/usr.sbin/mptutil/mpt_config.c @@ -0,0 +1,1259 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * 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/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 + +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; +                /* FALLTHROUGH */ +        case 'g': case 'G': +                iv *= 1024; +                /* FALLTHROUGH */ +        case 'm': case 'M': +                iv *= 1024; +                /* FALLTHROUGH */ +        case 'k': case 'K': +                iv *= 1024; +                /* FALLTHROUGH */ +        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) { +		warnc(error, "Unable to lookup volume device name"); +		return (error); +	} +	snprintf(path, sizeof(path), "%s%s", _PATH_DEV, qd.devname); +	vfd = open(path, O_RDWR); +	if (vfd < 0) { +		error = errno; +		warn("Unable to lock volume %s", qd.devname); +		return (error); +	} +	return (0); +} + +static int +mpt_lock_physdisk(struct mpt_standalone_disk *disk) +{ +	char path[MAXPATHLEN]; +	int dfd, error; + +	snprintf(path, sizeof(path), "%s%s", _PATH_DEV, disk->devname); +	dfd = open(path, O_RDWR); +	if (dfd < 0) { +		error = errno; +		warn("Unable to lock disk %s", disk->devname); +		return (error); +	} +	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) { +				return (EINVAL); +			} +			for (i = 0; i < ndisks; i++) { +				if (disks[i].bus == (U8)bus && +				    disks[i].target == (U8)id) { +					*index = i; +					return (0); +				} +			} +			return (ENOENT); +		} +	} + +	if (name[0] == 'd' && name[1] == 'a') { +		for (i = 0; i < ndisks; i++) { +			if (strcmp(name, disks[i].devname) == 0) { +				*index = i; +				return (0); +			} +		} +		return (ENOENT); +	} + +	return (EINVAL); +} + +/* + * 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; +	int error; +	U32 ActionData; + +	error = mpt_read_config_page_header(fd, MPI_CONFIG_PAGETYPE_RAID_PHYSDISK, +	    0, 0, &header, NULL); +	if (error) +		return (error); +	if (header.PageVersion > MPI_RAIDPHYSDISKPAGE0_PAGEVERSION) { +		warnx("Unsupported RAID physdisk page 0 version %d", +		    header.PageVersion); +		return (EOPNOTSUPP); +	}		 +	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? */ +	error = 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); +	if (error) +		return (error); +	*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, error, fd, i; + +	fd = mpt_open(mpt_unit); +	if (fd < 0) { +		error = errno; +		warn("mpt_open"); +		return (error); +	} + +	ioc2 = mpt_read_ioc_page(fd, 2, NULL); +	if (ioc2 == NULL) { +		error = errno; +		warn("Failed to fetch volume list"); +		close(fd); +		return (error); +	} + +	/* 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)); +			free(ioc2); +			close(fd); +			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"); +		free(ioc2); +		close(fd); +		return (0); +	} + +	/* Delete all the volumes. */ +	vol = ioc2->RaidVolume; +	for (i = 0; i < ioc2->NumActiveVolumes; vol++, i++) { +		error = 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); +		if (error) +			warnc(error, "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); +	error = mpt_rescan_bus(-1, -1); +	close(fd); + +	return (error); +} +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) { +			error = errno; +			warn("Unable to lookup drive %s", cp); +			return (error); +		} +		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 error, 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) { +				error = errno; +				warn( +			    "Failed to create physical disk page for %s", +				    dinfo->sdisk->devname); +				return (error); +			} +			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; + +	/* Search 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 error, i; + +	error = mpt_read_config_page_header(fd, MPI_CONFIG_PAGETYPE_RAID_VOLUME, +	    0, 0, &header, NULL); +	if (error) { +		errno = error; +		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); +	if (vol == NULL) +		return (NULL); + +	/* 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 = (uint64_t)MinLBA * info->drive_count; +		break; +	case RT_RAID1: +		vol->VolumeType = MPI_RAID_VOL_TYPE_IM; +		MaxLBA = (uint64_t)MinLBA * (info->drive_count / 2); +		break; +	case RT_RAID1E: +		vol->VolumeType = MPI_RAID_VOL_TYPE_IME; +		vol->StripeSize = stripe_size / 512; +		MaxLBA = (uint64_t)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; +	long stripe_size; +	int ch, error, fd, i, quick, raid_type, verbose; +#ifdef DEBUG +	int dump; +#endif + +	if (ac < 2) { +		warnx("create: volume type required"); +		return (EINVAL); +	} +	 +	fd = mpt_open(mpt_unit); +	if (fd < 0) { +		error = errno; +		warn("mpt_open"); +		return (error); +	} + +	/* 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]); +		close(fd); +		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); +				close(fd); +				return (EINVAL); +			} +			break; +		case 'v': +			verbose = 1; +			break; +		case '?': +		default: +			close(fd); +			return (EINVAL); +		} +	} +	ac -= optind; +	av += optind; + +	/* Fetch existing config data. */ +	state.ioc2 = mpt_read_ioc_page(fd, 2, NULL); +	if (state.ioc2 == NULL) { +		error = errno; +		warn("Failed to read volume list"); +		close(fd); +		return (error); +	} +	state.list = mpt_pd_list(fd); +	if (state.list == NULL) { +		close(fd); +		return (errno); +	} +	error = mpt_fetch_disks(fd, &state.nsdisks, &state.sdisks); +	if (error) { +		warn("Failed to fetch standalone disk list"); +		close(fd); +		return (error); +	}	 +	state.target_id = 0xff; +	 +	/* Parse the drive list. */ +	if (ac != 1) { +		warnx("Exactly one drive list is required"); +		close(fd); +		return (EINVAL); +	} +	info = calloc(1, sizeof(*info)); +	if (info == NULL) { +		close(fd); +		return (ENOMEM); +	} +	error = parse_volume(fd, raid_type, &state, av[0], info); +	if (error) { +		free(info); +		close(fd); +		return (error); +	} + +	/* Create RAID physdisk pages for standalone disks. */ +	error = add_drives(fd, info, verbose); +	if (error) { +		free(info); +		close(fd); +		return (error); +	} + +	/* Build the volume. */ +	vol = build_volume(fd, info, raid_type, stripe_size, &state, verbose); +	if (vol == NULL) { +		free(info); +		close(fd); +		return (errno); +	} + +#ifdef DEBUG +	if (dump) { +		dump_config(vol); +		goto skip; +	} +#endif + +	/* Send the new volume to the controller. */ +	error = 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); +	if (error) { +		errno = error; +		warn("Failed to add volume"); +		free(info); +		close(fd); +		return (error); +	} + +#ifdef DEBUG +skip: +#endif +	error = 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 (error); +} +MPT_COMMAND(top, create, create_volume); + +static int +delete_volume(int ac, char **av) +{ +	U8 VolumeBus, VolumeID; +	int error, fd; + +	if (ac != 2) { +		warnx("delete: volume required"); +		return (EINVAL); +	} + +	fd = mpt_open(mpt_unit); +	if (fd < 0) { +		error = errno; +		warn("mpt_open"); +		return (error); +	} + +	error = mpt_lookup_volume(fd, av[1], &VolumeBus, &VolumeID); +	if (error) { +		warnc(error, "Invalid volume %s", av[1]); +		close(fd); +		return (error); +	} + +	if (mpt_lock_volume(VolumeBus, VolumeID) < 0) { +		close(fd); +		return (errno); +	} + +	error = 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); +	if (error) { +		warnc(error, "Failed to delete volume"); +		close(fd); +		return (error); +	} + +	error = mpt_rescan_bus(-1, -1); +	close(fd); + +	return (error); +} +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 error, i, j, new_pool, pool_count[7]; + +	error = mpt_lookup_volume(fd, name, &VolumeBus, &VolumeID); +	if (error) { +		warnc(error, "Invalid volume %s", name); +		return (error); +	} + +	info = mpt_vol_info(fd, VolumeBus, VolumeID, NULL); +	if (info == NULL) +		return (errno); + +	/* +	 * 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); +		free(info); +		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) { +		error = errno; +		warn("Failed to fetch volume list"); +		return (error); +	} +	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 (errno); +		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 (error); +	info->VolumeSettings.HotSparePool |= (1 << new_pool); +	error = mpt_raid_action(fd, MPI_RAID_ACTION_CHANGE_VOLUME_SETTINGS, +	    VolumeBus, VolumeID, 0, *(U32 *)&info->VolumeSettings, NULL, 0, +	    NULL, NULL, 0, NULL, NULL, 0); +	if (error) { +		warnx("Failed to add spare pool %d to %s", new_pool, +		    mpt_volume_name(VolumeBus, VolumeID)); +		free(info); +		return (error); +	} +	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) { +		error = errno; +		warn("mpt_open"); +		return (error); +	} + +	if (ac == 3) { +		error = find_volume_spare_pool(fd, av[2], &pool); +		if (error) { +			close(fd); +			return (error); +		} +	} 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"); +			mpt_free_pd_list(list); +			close(fd); +			return (error); +		} + +		if (mpt_lookup_standalone_disk(av[1], sdisks, nsdisks, &i) < +		    0) { +			error = errno; +			warn("Unable to lookup drive %s", av[1]); +			mpt_free_pd_list(list); +			close(fd); +			return (error); +		} + +		if (mpt_lock_physdisk(&sdisks[i]) < 0) { +			mpt_free_pd_list(list); +			close(fd); +			return (errno); +		} + +		if (mpt_create_physdisk(fd, &sdisks[i], &PhysDiskNum) < 0) { +			error = errno; +			warn("Failed to create physical disk page"); +			mpt_free_pd_list(list); +			close(fd); +			return (error); +		} +		free(sdisks); +	} +	mpt_free_pd_list(list); + +	info = mpt_pd_info(fd, PhysDiskNum, NULL); +	if (info == NULL) { +		error = errno; +		warn("Failed to fetch drive info"); +		close(fd); +		return (error); +	} + +	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) { +		warnc(error, "Failed to assign spare"); +		close(fd); +		return (error); +	} + +	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) { +		error = errno; +		warn("mpt_open"); +		return (error); +	} + +	list = mpt_pd_list(fd); +	if (list == NULL) { +		close(fd); +		return (errno); +	} + +	error = mpt_lookup_drive(list, av[1], &PhysDiskNum); +	if (error) { +		warn("Failed to find drive %s", av[1]); +		close(fd); +		return (error); +	} +	mpt_free_pd_list(list); + +	 +	info = mpt_pd_info(fd, PhysDiskNum, NULL); +	if (info == NULL) { +		error = errno; +		warn("Failed to fetch drive info"); +		close(fd); +		return (error); +	} + +	if (info->PhysDiskSettings.HotSparePool == 0) { +		warnx("Drive %u is not a hot spare", PhysDiskNum); +		free(info); +		close(fd); +		return (EINVAL); +	} + +	if (mpt_delete_physdisk(fd, PhysDiskNum) < 0) { +		error = errno; +		warn("Failed to delete physical disk page"); +		free(info); +		close(fd); +		return (error); +	} + +	error = mpt_rescan_bus(info->PhysDiskBus, info->PhysDiskID); +	if (error) +		return (error); +	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) { +		error = errno; +		warn("mpt_open"); +		return (error); +	} + +	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) { +		error = errno; +		warn("Unable to lookup drive"); +		return (error); +	} + +	if (mpt_lock_physdisk(&disks[i]) < 0) +		return (errno); + +	if (mpt_create_physdisk(fd, &disks[i], &PhysDiskNum) < 0) { +		error = errno; +		warn("Failed to create physical disk page"); +		return (error); +	} +	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 error, fd; +	U8 PhysDiskNum; + +	if (ac != 2) { +		warnx("pd delete: drive required"); +		return (EINVAL); +	} + +	fd = mpt_open(mpt_unit); +	if (fd < 0) { +		error = errno; +		warn("mpt_open"); +		return (error); +	} + +	list = mpt_pd_list(fd); +	if (list == NULL) +		return (errno); + +	if (mpt_lookup_drive(list, av[1], &PhysDiskNum) < 0) { +		error = errno; +		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) { +		error = errno; +		warn("Failed to fetch drive info"); +		return (error); +	} + +	if (mpt_delete_physdisk(fd, PhysDiskNum) < 0) { +		error = errno; +		warn("Failed to delete physical disk page"); +		return (error); +	} + +	error = mpt_rescan_bus(info->PhysDiskBus, info->PhysDiskID); +	if (error) +		return (error); +	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 error, fd; + +	if (ac != 2) { +		warnx("debug: volume required"); +		return (EINVAL); +	} + +	fd = mpt_open(mpt_unit); +	if (fd < 0) { +		error = errno; +		warn("mpt_open"); +		return (error); +	} + +	error = mpt_lookup_volume(fd, av[1], &VolumeBus, &VolumeID); +	if (error) { +		warnc(error, "Invalid volume: %s", av[1]); +		return (error); +	} + +	vol = mpt_vol_info(fd, VolumeBus, VolumeID, NULL); +	if (vol == NULL) { +		error = errno; +		warn("Failed to get volume info"); +		return (error); +	} + +	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..82d5aefe8795 --- /dev/null +++ b/usr.sbin/mptutil/mpt_drive.c @@ -0,0 +1,429 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * 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/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 (errno); +	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, error, i, j; +	size_t listsize; + +	ioc2 = mpt_read_ioc_page(fd, 2, NULL); +	if (ioc2 == NULL) { +		error = errno; +		warn("Failed to fetch volume list"); +		errno = error; +		return (NULL); +	} + +	ioc3 = mpt_read_ioc_page(fd, 3, NULL); +	if (ioc3 == NULL) { +		error = errno; +		warn("Failed to fetch drive list"); +		free(ioc2); +		errno = error; +		return (NULL); +	} + +	ioc5 = mpt_read_ioc_page(fd, 5, NULL); +	if (ioc5 == NULL) { +		error = errno; +		warn("Failed to fetch spare list"); +		free(ioc3); +		free(ioc2); +		errno = error; +		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) { +			error = errno; +			warn("Failed to read volume info"); +			errno = error; +			free(volumes); +			free(ioc5); +			free(ioc3); +			free(ioc2); +			return (NULL); +		} +		count += volumes[i]->NumPhysDisks; +	} +	count += ioc3->NumPhysDisks; +	count += ioc5->NumHotSpares; + +	/* Walk the various lists enumerating drives. */ +	listsize = sizeof(*list) + sizeof(CONFIG_PAGE_RAID_PHYS_DISK_0) * count; +	list = calloc(1, listsize); + +	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) { +				mpt_free_pd_list(list); +				free(volumes); +				free(ioc5); +				free(ioc3); +				free(ioc2); +				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) { +			mpt_free_pd_list(list); +			free(ioc5); +			free(ioc3); +			return (NULL); +		} +	free(ioc5); + +	disk = ioc3->PhysDisk; +	for (i = 0; i < ioc3->NumPhysDisks; disk++, i++) +		if (mpt_pd_insert(fd, list, disk->PhysDiskNum) < 0) { +			mpt_free_pd_list(list); +			free(ioc3); +			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); +			} +		} +		return (ENOENT); +	} + +bad: +	return (EINVAL); +} + +/* 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 error, fd; + +	fd = mpt_open(mpt_unit); +	if (fd < 0) { +		error = errno; +		warn("mpt_open"); +		return (error); +	} + +	list = mpt_pd_list(fd); +	if (list == NULL) { +		close(fd); +		return (errno); +	} + +	if (mpt_lookup_drive(list, drive, &PhysDiskNum) < 0) { +		error = errno; +		warn("Failed to find drive %s", drive); +		close(fd); +		return (error); +	} +	mpt_free_pd_list(list); + +	/* Get the info for this drive. */ +	info = mpt_pd_info(fd, PhysDiskNum, NULL); +	if (info == NULL) { +		error = errno; +		warn("Failed to fetch info for drive %u", PhysDiskNum); +		close(fd); +		return (error); +	} + +	/* Try to change the state. */ +	if (info->PhysDiskStatus.State == State) { +		warnx("Drive %u is already in the desired state", PhysDiskNum); +		free(info); +		close(fd); +		return (EINVAL); +	} + +	error = mpt_raid_action(fd, Action, 0, 0, PhysDiskNum, 0, NULL, 0, NULL, +	    NULL, 0, NULL, NULL, 0); +	if (error) { +		warnc(error, "Failed to set drive %u to %s", PhysDiskNum, name); +		free(info); +		close(fd); +		return (error); +	} + +	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..a3fbf4cf7aec --- /dev/null +++ b/usr.sbin/mptutil/mpt_evt.c @@ -0,0 +1,164 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * 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/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, error, fd, i, num_events, verbose; + +	fd = mpt_open(mpt_unit); +	if (fd < 0) { +		error = errno; +		warn("mpt_open"); +		return (error); +	} + +	log = mpt_get_events(fd, NULL); +	if (log == NULL) { +		error = errno; +		warn("Failed to get event log info"); +		return (error); +	} + +	/* 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: +			free(log); +			close(fd); +			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); +	if (entries == NULL) { +		free(log); +		close(fd); +		return (ENOMEM); +	} +	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); +	free(log); +	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..f9191aa6921e --- /dev/null +++ b/usr.sbin/mptutil/mpt_show.c @@ -0,0 +1,584 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * 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/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; +	U16 IOCStatus; +	int comma, error, fd; + +	if (ac != 1) { +		warnx("show adapter: extra arguments"); +		return (EINVAL); +	} + +	fd = mpt_open(mpt_unit); +	if (fd < 0) { +		error = errno; +		warn("mpt_open"); +		return (error); +	} + +	man0 = mpt_read_man_page(fd, 0, NULL); +	if (man0 == NULL) { +		error = errno; +		warn("Failed to get controller info"); +		close(fd); +		return (error); +	} +	if (man0->Header.PageLength < sizeof(*man0) / 4) { +		warnx("Invalid controller info"); +		free(man0); +		close(fd); +		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, &IOCStatus); +	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); +	} else if ((IOCStatus & MPI_IOCSTATUS_MASK) != +	    MPI_IOCSTATUS_CONFIG_INVALID_PAGE) +		warnx("mpt_read_ioc_page(2): %s", mpt_ioc_status(IOCStatus)); + +	ioc6 = mpt_read_ioc_page(fd, 6, &IOCStatus); +	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); +	} else if ((IOCStatus & MPI_IOCSTATUS_MASK) != +	    MPI_IOCSTATUS_CONFIG_INVALID_PAGE) +		warnx("mpt_read_ioc_page(6): %s", mpt_ioc_status(IOCStatus)); + +	/* 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 error, fd, i, j, nsdisks; + +	if (ac != 1) { +		warnx("show config: extra arguments"); +		return (EINVAL); +	} + +	fd = mpt_open(mpt_unit); +	if (fd < 0) { +		error = errno; +		warn("mpt_open"); +		return (error); +	} + +	/* 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) { +		error = errno; +		warn("Failed to get config"); +		free(ioc2); +		close(fd); +		return (error); +	} +	if (mpt_fetch_disks(fd, &nsdisks, &sdisks) < 0) { +		error = errno; +		warn("Failed to get standalone drive list"); +		free(ioc5); +		free(ioc2); +		close(fd); +		return (error); +	} + +	/* 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 error, fd, i, len, state_len; + +	if (ac != 1) { +		warnx("show volumes: extra arguments"); +		return (EINVAL); +	} + +	fd = mpt_open(mpt_unit); +	if (fd < 0) { +		error = errno; +		warn("mpt_open"); +		return (error); +	} + +	/* Get the volume list from the controller. */ +	ioc2 = mpt_read_ioc_page(fd, 2, NULL); +	if (ioc2 == NULL) { +		error = errno; +		warn("Failed to get volume list"); +		return (error); +	} + +	/* +	 * 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(volumes); +	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 error, fd, i, len, nsdisks, state_len; + +	if (ac != 1) { +		warnx("show drives: extra arguments"); +		return (EINVAL); +	} + +	fd = mpt_open(mpt_unit); +	if (fd < 0) { +		error = errno; +		warn("mpt_open"); +		return (error); +	} + +	/* Get the drive list. */ +	list = mpt_pd_list(fd); +	if (list == NULL) { +		error = errno; +		close(fd); +		warn("Failed to get drive list"); +		return (error); +	} + +	/* 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 error, fd, i; + +	if (ac != 1) { +		warnx("show drives: extra arguments"); +		return (EINVAL); +	} + +	fd = mpt_open(mpt_unit); +	if (fd < 0) { +		error = errno; +		warn("mpt_open"); +		return (error); +	} + +	/* 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_MASK) != +			    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..4a7c55f9f6c6 --- /dev/null +++ b/usr.sbin/mptutil/mpt_volume.c @@ -0,0 +1,269 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * 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/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 error, 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) { +		error = errno; +		warn("mpt_open"); +		return (error); +	} + +	error = mpt_lookup_volume(fd, av[1], &VolumeBus, &VolumeID); +	if (error) { +		warnc(error, "Invalid volume: %s", av[1]); +		return (error); +	} + +	vnames = mpt_vol_names(fd, VolumeBus, VolumeID, NULL); +	if (vnames == NULL) { +		error = errno; +		warn("Failed to fetch volume names"); +		close(fd); +		return (error); +	} + +	if (vnames->Header.PageType != MPI_CONFIG_PAGEATTR_CHANGEABLE) { +		warnx("Volume name is read only"); +		free(vnames); +		close(fd); +		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) { +		error = errno; +		warn("Failed to set volume name"); +		free(vnames); +		close(fd); +		return (error); +	} + +	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 error, fd; + +	if (ac != 2) { +		warnx("volume status: %s", ac > 2 ? "extra arguments" : +		    "volume required"); +		return (EINVAL); +	} + +	fd = mpt_open(mpt_unit); +	if (fd < 0) { +		error = errno; +		warn("mpt_open"); +		return (error); +	} + +	error = mpt_lookup_volume(fd, av[1], &VolumeBus, &VolumeID); +	if (error) { +		warnc(error, "Invalid volume: %s", av[1]); +		close(fd); +		return (error); +	} + +	error = mpt_raid_action(fd, MPI_RAID_ACTION_INDICATOR_STRUCT, VolumeBus, +	    VolumeID, 0, 0, NULL, 0, &VolumeStatus, (U32 *)&prog, sizeof(prog), +	    NULL, NULL, 0); +	if (error) { +		warnc(error, "Fetching volume status failed"); +		close(fd); +		return (error); +	} + +	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 error, fd; + +	if (ac != 3) { +		warnx("volume cache: %s", ac > 3 ? "extra arguments" : +		    "missing arguments"); +		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', 'enabled', 'disable', or 'disabled'"); +		return (EINVAL); +	} + +	fd = mpt_open(mpt_unit); +	if (fd < 0) { +		error = errno; +		warn("mpt_open"); +		return (error); +	} + +	error = mpt_lookup_volume(fd, av[1], &VolumeBus, &VolumeID); +	if (error) { +		warnc(error, "Invalid volume: %s", av[1]); +		close(fd); +		return (error); +	} + +	volume = mpt_vol_info(fd, VolumeBus, VolumeID, NULL); +	if (volume == NULL) { +		close(fd); +		return (errno); +	} + +	Settings = volume->VolumeSettings.Settings; + +	NewSettings = Settings; +	if (strncmp(av[2], "enable", strlen("enable")) == 0) +		NewSettings |= 0x01; +	else if (strncmp(av[2], "disable", strlen("disable")) == 0) +		NewSettings &= ~0x01; + +	if (NewSettings == Settings) { +		warnx("volume cache unchanged"); +		free(volume); +		close(fd); +		return (0); +	} + +	volume->VolumeSettings.Settings = NewSettings; +	error = mpt_raid_action(fd, MPI_RAID_ACTION_CHANGE_VOLUME_SETTINGS, +	    VolumeBus, VolumeID, 0, *(U32 *)&volume->VolumeSettings, NULL, 0, +	    NULL, NULL, 0, NULL, NULL, 0); +	if (error) +		warnc(error, "volume cache change failed"); + +	close(fd); +	return (error); +} +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..1aeaafbfa12e --- /dev/null +++ b/usr.sbin/mptutil/mptutil.8 @@ -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. +.\" +.Dd May 24, 2023 +.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|enabled|disable|disabled +.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 omitted, +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 is not 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|enabled|disable|disabled +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 HISTORY +The +.Nm +utility first appeared in +.Fx 8.0 . +.Sh BUGS +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. +.Pp +The mpt version 1 API that is used by +.Nm +and +.Xr mpt 4 +does not support volumes above two terabytes. +This is a limitation of the API. +If you are using this adapter with volumes larger than two terabytes, use the adapter in JBOD mode. +Utilize +.Xr geom 8 , +.Xr zfs 8 , +or another software volume manager to work around this limitation. diff --git a/usr.sbin/mptutil/mptutil.c b/usr.sbin/mptutil/mptutil.c new file mode 100644 index 000000000000..ed414931339c --- /dev/null +++ b/usr.sbin/mptutil/mptutil.c @@ -0,0 +1,128 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * 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/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(); + +#if BYTE_ORDER == BIG_ENDIAN +	warnx("mptutil is known to be broken on big-endian architectures"); +#endif + +	SET_FOREACH(cmd, MPT_DATASET(top)) { +		if (strcmp((*cmd)->name, av[0]) == 0) { +			if ((*cmd)->handler(ac, av)) +				return (1); +			else +				return (0); +		} +	} +	warnx("Unknown command %s.", av[0]); +	return (1); +} diff --git a/usr.sbin/mptutil/mptutil.h b/usr.sbin/mptutil/mptutil.h new file mode 100644 index 000000000000..6148d5405ab1 --- /dev/null +++ b/usr.sbin/mptutil/mptutil.h @@ -0,0 +1,178 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * 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. + */ + +#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__ */ | 
