diff options
Diffstat (limited to 'usr.sbin/mfiutil')
| -rw-r--r-- | usr.sbin/mfiutil/Makefile | 19 | ||||
| -rw-r--r-- | usr.sbin/mfiutil/Makefile.depend | 16 | ||||
| -rw-r--r-- | usr.sbin/mfiutil/mfi_bbu.c | 244 | ||||
| -rw-r--r-- | usr.sbin/mfiutil/mfi_cmd.c | 373 | ||||
| -rw-r--r-- | usr.sbin/mfiutil/mfi_config.c | 1288 | ||||
| -rw-r--r-- | usr.sbin/mfiutil/mfi_drive.c | 762 | ||||
| -rw-r--r-- | usr.sbin/mfiutil/mfi_evt.c | 703 | ||||
| -rw-r--r-- | usr.sbin/mfiutil/mfi_flash.c | 209 | ||||
| -rw-r--r-- | usr.sbin/mfiutil/mfi_foreign.c | 364 | ||||
| -rw-r--r-- | usr.sbin/mfiutil/mfi_patrol.c | 336 | ||||
| -rw-r--r-- | usr.sbin/mfiutil/mfi_properties.c | 171 | ||||
| -rw-r--r-- | usr.sbin/mfiutil/mfi_show.c | 791 | ||||
| -rw-r--r-- | usr.sbin/mfiutil/mfi_volume.c | 498 | ||||
| -rw-r--r-- | usr.sbin/mfiutil/mfiutil.8 | 825 | ||||
| -rw-r--r-- | usr.sbin/mfiutil/mfiutil.c | 218 | ||||
| -rw-r--r-- | usr.sbin/mfiutil/mfiutil.h | 188 | 
16 files changed, 7005 insertions, 0 deletions
| diff --git a/usr.sbin/mfiutil/Makefile b/usr.sbin/mfiutil/Makefile new file mode 100644 index 000000000000..49c0e688e8e2 --- /dev/null +++ b/usr.sbin/mfiutil/Makefile @@ -0,0 +1,19 @@ +PROG=	mfiutil +LINKS=	${BINDIR}/mfiutil ${BINDIR}/mrsasutil + +SRCS=	mfiutil.c mfi_bbu.c mfi_cmd.c mfi_config.c mfi_drive.c mfi_evt.c \ +	mfi_flash.c mfi_patrol.c mfi_show.c mfi_volume.c mfi_foreign.c \ +	mfi_properties.c +MAN=	mfiutil.8 +MLINKS=	mfiutil.8 mrsasutil.8 + +CFLAGS.gcc+= -fno-builtin-strftime + +LIBADD=	sbuf util + +# Here be dragons +.ifdef DEBUG +CFLAGS+= -DDEBUG +.endif + +.include <bsd.prog.mk> diff --git a/usr.sbin/mfiutil/Makefile.depend b/usr.sbin/mfiutil/Makefile.depend new file mode 100644 index 000000000000..678747db6f2c --- /dev/null +++ b/usr.sbin/mfiutil/Makefile.depend @@ -0,0 +1,16 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ +	include \ +	include/xlocale \ +	lib/${CSU_DIR} \ +	lib/libc \ +	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/mfiutil/mfi_bbu.c b/usr.sbin/mfiutil/mfi_bbu.c new file mode 100644 index 000000000000..3e78e791dfc2 --- /dev/null +++ b/usr.sbin/mfiutil/mfi_bbu.c @@ -0,0 +1,244 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2013 Sandvine Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/param.h> +#include <sys/errno.h> +#include <sys/stat.h> +#include <err.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include "mfiutil.h" + +/* The autolearn period is given in seconds. */ +void +mfi_autolearn_period(FILE *fp, uint32_t period) +{ +	unsigned int d, h; + +	d = period / (24 * 3600); +	h = (period % (24 * 3600)) / 3600; + +	if (d != 0) { +		fprintf(fp, "%u day%s", d, d == 1 ? "" : "s"); +		if (h != 0) +			fprintf(fp, ", "); +	} +	if (h != 0) +		fprintf(fp, "%u hour%s", h, h == 1 ? "" : "s"); + +	if (d == 0 && h == 0) +		fprintf(fp, "less than 1 hour"); +} + +/* The time to the next relearn is given in seconds since 1/1/2000. */ +void +mfi_next_learn_time(uint32_t next_learn_time, char *buf, size_t sz) +{ +	time_t basetime; +	struct tm tm; +	size_t len; + +	memset(&tm, 0, sizeof(tm)); +	tm.tm_year = 100; +	basetime = timegm(&tm); +	basetime += (time_t)next_learn_time; +	len = strlcpy(buf, ctime(&basetime), sz); +	if (len < sz) +		/* Get rid of the newline added by ctime(3). */ +		buf[len - 1] = '\0'; +} + +void +mfi_autolearn_mode(FILE *fp, uint8_t mode) +{ + +	switch (mode) { +	case 0: +		fprintf(fp, "enabled"); +		break; +	case 1: +		fprintf(fp, "disabled"); +		break; +	case 2: +		fprintf(fp, "warn via event"); +		break; +	default: +		fprintf(fp, "mode 0x%02x", mode); +		break; +	} +} + +int +mfi_bbu_get_props(int fd, struct mfi_bbu_properties *props, uint8_t *statusp) +{ + +	return (mfi_dcmd_command(fd, MFI_DCMD_BBU_GET_PROP, props, +	    sizeof(*props), NULL, 0, statusp)); +} + +int +mfi_bbu_set_props(int fd, struct mfi_bbu_properties *props, uint8_t *statusp) +{ + +	return (mfi_dcmd_command(fd, MFI_DCMD_BBU_SET_PROP, props, +	    sizeof(*props), NULL, 0, statusp)); +} + +static int +start_bbu_learn(int ac, char **av __unused) +{ +	uint8_t status; +	int error, fd; + +	status = MFI_STAT_OK; +	error = 0; + +	if (ac != 1) { +		warnx("start learn: unexpected arguments"); +		return (EINVAL); +	} + +	fd = mfi_open(mfi_device, O_RDWR); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	if (mfi_dcmd_command(fd, MFI_DCMD_BBU_START_LEARN, NULL, 0, NULL, 0, +	    &status) < 0) { +		error = errno; +		warn("Failed to start BBU learn"); +	} else if (status != MFI_STAT_OK) { +		warnx("Failed to start BBU learn: %s", mfi_status(status)); +		error = EIO; +	} + +	return (error); +} +MFI_COMMAND(start, learn, start_bbu_learn); + +static int +update_bbu_props(int ac, char **av) +{ +	struct mfi_bbu_properties props; +	unsigned long delay; +	uint8_t status; +	int error, fd; +	char *mode, *endptr; + +	status = MFI_STAT_OK; +	error = 0; + +	if (ac != 3) { +		warnx("bbu: property and value required"); +		return (EINVAL); +	} + +	fd = mfi_open(mfi_device, O_RDWR); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	if (mfi_bbu_get_props(fd, &props, &status) < 0) { +		error = errno; +		warn("Failed to get BBU properties"); +		goto done; +	} else if (status != MFI_STAT_OK) { +		warnx("Failed to get BBU properties: %s", mfi_status(status)); +		error = EIO; +		goto done; +	} + +	if (strcmp(av[1], "learn-delay") == 0) { +		delay = strtoul(av[2], &endptr, 10); +		if (strlen(av[2]) == 0 || *endptr != '\0' || delay > 255) { +			warnx("Invalid learn delay '%s'", av[2]); +			error = EINVAL; +			goto done; +		} + +		props.learn_delay_interval = delay; +	} else if (strcmp(av[1], "autolearn-mode") == 0) { +		mode = av[2]; + +		if (strcmp(av[2], "enable") == 0) +			props.auto_learn_mode = 0; +		else if (strcmp(av[2], "disable") == 0) +			props.auto_learn_mode = 1; +		else if (mode[0] >= '0' && mode[0] <= '2' && mode[1] == '\0') +			props.auto_learn_mode = mode[0] - '0'; +		else { +			warnx("Invalid mode '%s'", mode); +			error = EINVAL; +			goto done; +		} +	} else if (strcmp(av[1], "bbu-mode") == 0) { +		if (props.bbu_mode == 0) { +			warnx("This BBU does not implement different modes"); +			error = EINVAL; +			goto done; +		} + +		/* The mode must be an integer between 1 and 5. */ +		mode = av[2]; +		if (mode[0] < '1' || mode[0] > '5' || mode[1] != '\0') { +			warnx("Invalid mode '%s'", mode); +			error = EINVAL; +			goto done; +		} + +		props.bbu_mode = mode[0] - '0'; +	} else { +		warnx("bbu: Invalid command '%s'", av[1]); +		error = EINVAL; +		goto done; +	} + +	if (mfi_bbu_set_props(fd, &props, &status) < 0) { +		error = errno; +		warn("Failed to set BBU properties"); +		goto done; +	} else if (status != MFI_STAT_OK) { +		warnx("Failed to set BBU properties: %s", mfi_status(status)); +		error = EIO; +		goto done; +	} + +done: +	close(fd); + +	return (error); +} +MFI_COMMAND(top, bbu, update_bbu_props); diff --git a/usr.sbin/mfiutil/mfi_cmd.c b/usr.sbin/mfiutil/mfi_cmd.c new file mode 100644 index 000000000000..8e0a0aca32c4 --- /dev/null +++ b/usr.sbin/mfiutil/mfi_cmd.c @@ -0,0 +1,373 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2008, 2009 Yahoo!, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * 3. The names of the authors may not 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/ioctl.h> +#include <sys/sysctl.h> +#include <sys/uio.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <paths.h> + +#include "mfiutil.h" +#include <dev/mfi/mfi_ioctl.h> + +static const char *mfi_status_codes[] = { +	"Command completed successfully", +	"Invalid command", +	"Invalid DMCD opcode", +	"Invalid parameter", +	"Invalid Sequence Number", +	"Abort isn't possible for the requested command", +	"Application 'host' code not found", +	"Application in use", +	"Application not initialized", +	"Array index invalid", +	"Array row not empty", +	"Configuration resource conflict", +	"Device not found", +	"Drive too small", +	"Flash memory allocation failed", +	"Flash download already in progress", +	"Flash operation failed", +	"Bad flash image", +	"Incomplete flash image", +	"Flash not open", +	"Flash not started", +	"Flush failed", +	"Specified application doesn't have host-resident code", +	"Volume consistency check in progress", +	"Volume initialization in progress", +	"Volume LBA out of range", +	"Maximum number of volumes are already configured", +	"Volume is not OPTIMAL", +	"Volume rebuild in progress", +	"Volume reconstruction in progress", +	"Volume RAID level is wrong for requested operation", +	"Too many spares assigned", +	"Scratch memory not available", +	"Error writing MFC data to SEEPROM", +	"Required hardware is missing", +	"Item not found", +	"Volume drives are not within an enclosure", +	"Drive clear in progress", +	"Drive type mismatch (SATA vs SAS)", +	"Patrol read disabled", +	"Invalid row index", +	"SAS Config - Invalid action", +	"SAS Config - Invalid data", +	"SAS Config - Invalid page", +	"SAS Config - Invalid type", +	"SCSI command completed with error", +	"SCSI I/O request failed", +	"SCSI RESERVATION_CONFLICT", +	"One or more flush operations during shutdown failed", +	"Firmware time is not set", +	"Wrong firmware or drive state", +	"Volume is offline", +	"Peer controller rejected request", +	"Unable to inform peer of communication changes", +	"Volume reservation already in progress", +	"I2C errors were detected", +	"PCI errors occurred during XOR/DMA operation", +	"Diagnostics failed", +	"Unable to process command as boot messages are pending", +	"Foreign configuration is incomplete" +}; + +const char * +mfi_status(u_int status_code) +{ +	static char buffer[16]; + +	if (status_code == MFI_STAT_INVALID_STATUS) +		return ("Invalid status"); +	if (status_code < sizeof(mfi_status_codes) / sizeof(char *)) +		return (mfi_status_codes[status_code]); +	snprintf(buffer, sizeof(buffer), "Status: 0x%02x", status_code); +	return (buffer); +} + +const char * +mfi_raid_level(uint8_t primary_level, uint8_t secondary_level) +{ +	static char buf[16]; + +	switch (primary_level) { +	case DDF_RAID0: +		return ("RAID-0"); +	case DDF_RAID1: +		if (secondary_level != 0) +			return ("RAID-10"); +		else +			return ("RAID-1"); +	case DDF_RAID1E: +		return ("RAID-1E"); +	case DDF_RAID3: +		return ("RAID-3"); +	case DDF_RAID5: +		if (secondary_level != 0) +			return ("RAID-50"); +		else +			return ("RAID-5"); +	case DDF_RAID5E: +		return ("RAID-5E"); +	case DDF_RAID5EE: +		return ("RAID-5EE"); +	case DDF_RAID6: +		if (secondary_level != 0) +			return ("RAID-60"); +		else +			return ("RAID-6"); +	case DDF_JBOD: +		return ("JBOD"); +	case DDF_CONCAT: +		return ("CONCAT"); +	default: +		sprintf(buf, "LVL 0x%02x", primary_level); +		return (buf); +	} +} + +static int +mfi_query_disk(int fd, uint8_t target_id, struct mfi_query_disk *info) +{ + +	bzero(info, sizeof(*info)); +	info->array_id = target_id; +	if (ioctl(fd, MFIIO_QUERY_DISK, info) < 0) +		return (-1); +	if (!info->present) { +		errno = ENXIO; +		return (-1); +	} +	return (0); +} + +const char * +mfi_volume_name(int fd, uint8_t target_id) +{ +	static struct mfi_query_disk info; +	static char buf[4]; + +	if (mfi_query_disk(fd, target_id, &info) < 0) { +		snprintf(buf, sizeof(buf), "%d", target_id); +		return (buf); +	} +	return (info.devname); +} + +int +mfi_volume_busy(int fd, uint8_t target_id) +{ +	struct mfi_query_disk info; + +	/* Assume it isn't mounted if we can't get information. */ +	if (mfi_query_disk(fd, target_id, &info) < 0) +		return (0); +	return (info.open != 0); +} + +/* + * Check if the running kernel supports changing the RAID + * configuration of the mfi controller. + */ +int +mfi_reconfig_supported(const char *dev) +{ +	char mibname[64]; +	const char *cp; +	size_t len; +	int dummy, mfi_unit; + +	cp = dev + strlen(_PATH_DEV); +	if (strncmp(cp, MRSAS_TYPE, strlen(MRSAS_TYPE)) == 0) +		return (1); + +	cp += strlen(MFI_TYPE); +	mfi_unit = strtol(cp, NULL, 10); + +	len = sizeof(dummy); +	snprintf(mibname, sizeof(mibname), +	    "dev.mfi.%d.delete_busy_volumes", mfi_unit); +	return (sysctlbyname(mibname, &dummy, &len, NULL, 0) == 0); +} + +int +mfi_lookup_volume(int fd, const char *name, uint8_t *target_id) +{ +	struct mfi_query_disk info; +	struct mfi_ld_list list; +	char *cp; +	long val; +	u_int i; + +	/* If it's a valid number, treat it as a raw target ID. */ +	val = strtol(name, &cp, 0); +	if (*cp == '\0') { +		*target_id = val; +		return (0); +	} + +	if (mfi_dcmd_command(fd, MFI_DCMD_LD_GET_LIST, &list, sizeof(list), +	    NULL, 0, NULL) < 0) +		return (-1); + +	for (i = 0; i < list.ld_count; i++) { +		if (mfi_query_disk(fd, list.ld_list[i].ld.v.target_id, +		    &info) < 0) +			continue; +		if (strcmp(name, info.devname) == 0) { +			*target_id = list.ld_list[i].ld.v.target_id; +			return (0); +		} +	} +	errno = EINVAL; +	return (-1); +} + +int +mfi_dcmd_command(int fd, uint32_t opcode, void *buf, size_t bufsize, +    uint8_t *mbox, size_t mboxlen, uint8_t *statusp) +{ +	struct mfi_ioc_passthru ioc; +	struct mfi_dcmd_frame *dcmd; +	int r; + +	if ((mbox != NULL && (mboxlen == 0 || mboxlen > MFI_MBOX_SIZE)) || +	    (mbox == NULL && mboxlen != 0)) { +		errno = EINVAL; +		return (-1); +	} + +	bzero(&ioc, sizeof(ioc)); +	dcmd = &ioc.ioc_frame; +	if (mbox) +		bcopy(mbox, dcmd->mbox, mboxlen); +	dcmd->header.cmd = MFI_CMD_DCMD; +	dcmd->header.timeout = 0; +	dcmd->header.flags = 0; +	dcmd->header.data_len = bufsize; +	dcmd->opcode = opcode; + +	ioc.buf = buf; +	ioc.buf_size = bufsize; +	r = ioctl(fd, MFIIO_PASSTHRU, &ioc); +	if (r < 0) +		return (r); + +	if (statusp != NULL) +		*statusp = dcmd->header.cmd_status; +	else if (dcmd->header.cmd_status != MFI_STAT_OK) { +		warnx("Command failed: %s", +		    mfi_status(dcmd->header.cmd_status)); +		errno = EIO; +		return (-1); +	} +	return (0); +} + +int +mfi_ctrl_get_info(int fd, struct mfi_ctrl_info *info, uint8_t *statusp) +{ + +	return (mfi_dcmd_command(fd, MFI_DCMD_CTRL_GETINFO, info, +	    sizeof(struct mfi_ctrl_info), NULL, 0, statusp)); +} + +int +mfi_open(char *dev, int acs) +{ +	int ret; + +	ret = open(dev, acs); +	if (ret < 0) +		warn("Couldn't open %s", dev); +	return (ret); +} + +static void +print_time_humanized(uint seconds) +{ + +	if (seconds > 3600) { +		printf("%u:", seconds / 3600); +	} +	if (seconds > 60) { +		seconds %= 3600; +		printf("%02u:%02u", seconds / 60, seconds % 60); +	} else { +		printf("%us", seconds); +	} +} + +void +mfi_display_progress(const char *label, struct mfi_progress *prog) +{ +	uint seconds; + +	printf("%s: %.2f%% complete after ", label, +	    (float)prog->progress * 100 / 0xffff); +	print_time_humanized(prog->elapsed_seconds); +	if (prog->progress != 0 && prog->elapsed_seconds > 10) { +		printf(" finished in "); +		seconds = (0x10000 * (uint32_t)prog->elapsed_seconds) / +		    prog->progress - prog->elapsed_seconds; +		print_time_humanized(seconds); +	} +	printf("\n"); +} + +int +mfi_table_handler(struct mfiutil_command **start, struct mfiutil_command **end, +    int ac, char **av) +{ +	struct mfiutil_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); +} diff --git a/usr.sbin/mfiutil/mfi_config.c b/usr.sbin/mfiutil/mfi_config.c new file mode 100644 index 000000000000..49c7fea16d31 --- /dev/null +++ b/usr.sbin/mfiutil/mfi_config.c @@ -0,0 +1,1288 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2008, 2009 Yahoo!, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * 3. The names of the authors may not 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> +#ifdef DEBUG +#include <sys/sysctl.h> +#endif +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <libutil.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include "mfiutil.h" + +static int	add_spare(int ac, char **av); +static int	remove_spare(int ac, char **av); + +static long +dehumanize(const char *value) +{ +	char    *vtp; +	long    iv; + +	if (value == NULL) +		return (0); +	iv = strtoq(value, &vtp, 0); +	if (vtp == value || (vtp[0] != '\0' && vtp[1] != '\0')) { +		return (0); +	} +	switch (vtp[0]) { +	case 't': case 'T': +		iv *= 1024; +	case 'g': case 'G': +		iv *= 1024; +	case 'm': case 'M': +		iv *= 1024; +	case 'k': case 'K': +		iv *= 1024; +	case '\0': +		break; +	default: +		return (0); +	} +	return (iv); +} + +int +mfi_config_read(int fd, struct mfi_config_data **configp) +{ +	return mfi_config_read_opcode(fd, MFI_DCMD_CFG_READ, configp, NULL, 0); +} + +int +mfi_config_read_opcode(int fd, uint32_t opcode, struct mfi_config_data **configp, +	uint8_t *mbox, size_t mboxlen) +{ +	struct mfi_config_data *config; +	uint32_t config_size; +	int error; + +	/* +	 * Keep fetching the config in a loop until we have a large enough +	 * buffer to hold the entire configuration. +	 */ +	config = NULL; +	config_size = 1024; +fetch: +	config = reallocf(config, config_size); +	if (config == NULL) +		return (-1); +	if (mfi_dcmd_command(fd, opcode, config, +	    config_size, mbox, mboxlen, NULL) < 0) { +		error = errno; +		free(config); +		errno = error; +		return (-1); +	} + +	if (config->size > config_size) { +		config_size = config->size; +		goto fetch; +	} + +	*configp = config; +	return (0); +} + +static struct mfi_array * +mfi_config_lookup_array(struct mfi_config_data *config, uint16_t array_ref) +{ +	struct mfi_array *ar; +	char *p; +	int i; + +	p = (char *)config->array; +	for (i = 0; i < config->array_count; i++) { +		ar = (struct mfi_array *)p; +		if (ar->array_ref == array_ref) +			return (ar); +		p += config->array_size; +	} + +	return (NULL); +} + +static struct mfi_ld_config * +mfi_config_lookup_volume(struct mfi_config_data *config, uint8_t target_id) +{ +	struct mfi_ld_config *ld; +	char *p; +	int i; + +	p = (char *)config->array + config->array_count * config->array_size; +	for (i = 0; i < config->log_drv_count; i++) { +		ld = (struct mfi_ld_config *)p; +		if (ld->properties.ld.v.target_id == target_id) +			return (ld); +		p += config->log_drv_size; +	} + +	return (NULL); +} + +static int +clear_config(int ac __unused, char **av __unused) +{ +	struct mfi_ld_list list; +	int ch, error, fd; +	u_int i; + +	fd = mfi_open(mfi_device, O_RDWR); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	if (!mfi_reconfig_supported(mfi_device)) { +		warnx("The current %s driver does not support " +		    "configuration changes.", mfi_device); +		close(fd); +		return (EOPNOTSUPP); +	} + +	if (mfi_ld_get_list(fd, &list, NULL) < 0) { +		error = errno; +		warn("Failed to get volume list"); +		close(fd); +		return (error); +	} + +	for (i = 0; i < list.ld_count; i++) { +		if (mfi_volume_busy(fd, list.ld_list[i].ld.v.target_id)) { +			warnx("Volume %s is busy and cannot be deleted", +			    mfi_volume_name(fd, list.ld_list[i].ld.v.target_id)); +			close(fd); +			return (EBUSY); +		} +	} + +	printf( +	    "Are you sure you wish to clear the configuration on %s? [y/N] ", +	    mfi_device); +	ch = getchar(); +	if (ch != 'y' && ch != 'Y') { +		printf("\nAborting\n"); +		close(fd); +		return (0); +	} + +	if (mfi_dcmd_command(fd, MFI_DCMD_CFG_CLEAR, NULL, 0, NULL, 0, NULL) < 0) { +		error = errno; +		warn("Failed to clear configuration"); +		close(fd); +		return (error); +	} + +	printf("%s: Configuration cleared\n", mfi_device); +	close(fd); + +	return (0); +} +MFI_COMMAND(top, clear, clear_config); + +#define MAX_DRIVES_PER_ARRAY MFI_MAX_ROW_SIZE +#define MFI_ARRAY_SIZE sizeof(struct mfi_array) + +#define	RT_RAID0	0 +#define	RT_RAID1	1 +#define	RT_RAID5	2 +#define	RT_RAID6	3 +#define	RT_JBOD		4 +#define	RT_CONCAT	5 +#define	RT_RAID10	6 +#define	RT_RAID50	7 +#define	RT_RAID60	8 + +static int +compare_int(const void *one, const void *two) +{ +	int first, second; + +	first = *(const int *)one; +	second = *(const int *)two; + +	return (first - second); +} + +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 }, +	{ "raid5",	RT_RAID5 }, +	{ "raid-5",	RT_RAID5 }, +	{ "raid6",	RT_RAID6 }, +	{ "raid-6",	RT_RAID6 }, +	{ "jbod",	RT_JBOD }, +	{ "concat",	RT_CONCAT }, +	{ "raid10",	RT_RAID10 }, +	{ "raid1+0",	RT_RAID10 }, +	{ "raid-10",	RT_RAID10 }, +	{ "raid-1+0",	RT_RAID10 }, +	{ "raid50",	RT_RAID50 }, +	{ "raid5+0",	RT_RAID50 }, +	{ "raid-50",	RT_RAID50 }, +	{ "raid-5+0",	RT_RAID50 }, +	{ "raid60",	RT_RAID60 }, +	{ "raid6+0",	RT_RAID60 }, +	{ "raid-60",	RT_RAID60 }, +	{ "raid-6+0",	RT_RAID60 }, +	{ NULL,		0 }, +}; + +struct config_id_state { +	int	array_count; +	int	log_drv_count; +	int	*arrays; +	int	*volumes; +	uint16_t array_ref; +	uint8_t	target_id; +}; + +struct array_info { +	int	drive_count; +	struct mfi_pd_info *drives; +	struct mfi_array *array; +}; + +/* Parse a comma-separated list of drives for an array. */ +static int +parse_array(int fd, int raid_type, char *array_str, struct array_info *info) +{ +	struct mfi_pd_info *pinfo; +	uint16_t device_id; +	char *cp; +	u_int count; +	int error; + +	cp = array_str; +	for (count = 0; cp != NULL; count++) { +		cp = strchr(cp, ','); +		if (cp != NULL) { +			cp++; +			if (*cp == ',') { +				warnx("Invalid drive list '%s'", array_str); +				return (EINVAL); +			} +		} +	} + +	/* Validate the number of drives for this array. */ +	if (count >= MAX_DRIVES_PER_ARRAY) { +		warnx("Too many drives for a single array: max is %d", +		    MAX_DRIVES_PER_ARRAY); +		return (EINVAL); +	} +	switch (raid_type) { +	case RT_RAID1: +	case RT_RAID10: +		if (count % 2 != 0) { +			warnx("RAID1 and RAID10 require an even number of " +			    "drives in each array"); +			return (EINVAL); +		} +		break; +	case RT_RAID5: +	case RT_RAID50: +		if (count < 3) { +			warnx("RAID5 and RAID50 require at least 3 drives in " +			    "each array"); +			return (EINVAL); +		} +		break; +	case RT_RAID6: +	case RT_RAID60: +		if (count < 4) { +			warnx("RAID6 and RAID60 require at least 4 drives in " +			    "each array"); +			return (EINVAL); +		} +		break; +	} + +	/* Validate each drive. */ +	info->drives = calloc(count, sizeof(struct mfi_pd_info)); +	if (info->drives == NULL) { +		warnx("malloc failed"); +		return (ENOMEM); +	} +	info->drive_count = count; +	for (pinfo = info->drives; (cp = strsep(&array_str, ",")) != NULL; +	     pinfo++) { +		error = mfi_lookup_drive(fd, cp, &device_id); +		if (error) { +			free(info->drives); +			info->drives = NULL; +			return (error); +		} + +		if (mfi_pd_get_info(fd, device_id, pinfo, NULL) < 0) { +			error = errno; +			warn("Failed to fetch drive info for drive %s", cp); +			free(info->drives); +			info->drives = NULL; +			return (error); +		} + +		if (pinfo->fw_state != MFI_PD_STATE_UNCONFIGURED_GOOD) { +			warnx("Drive %u is not available", device_id); +			free(info->drives); +			info->drives = NULL; +			return (EINVAL); +		} + +		if (pinfo->state.ddf.v.pd_type.is_foreign) { +			warnx("Drive %u is foreign", device_id); +			free(info->drives); +			info->drives = NULL; +			return (EINVAL); +		} +	} + +	return (0); +} + +/* + * Find the next free array ref assuming that 'array_ref' is the last + * one used.  'array_ref' should be 0xffff for the initial test. + */ +static uint16_t +find_next_array(struct config_id_state *state) +{ +	int i; + +	/* Assume the current one is used. */ +	state->array_ref++; + +	/* Find the next free one. */ +	for (i = 0; i < state->array_count; i++) +		if (state->arrays[i] == state->array_ref) +			state->array_ref++; +	return (state->array_ref); +} + +/* + * Find the next free volume ID assuming that 'target_id' is the last + * one used.  'target_id' should be 0xff for the initial test. + */ +static uint8_t +find_next_volume(struct config_id_state *state) +{ +	int i; + +	/* Assume the current one is used. */ +	state->target_id++; + +	/* Find the next free one. */ +	for (i = 0; i < state->log_drv_count; i++) +		if (state->volumes[i] == state->target_id) +			state->target_id++; +	return (state->target_id); +} + +/* Populate an array with drives. */ +static void +build_array(int fd __unused, char *arrayp, struct array_info *array_info, +    struct config_id_state *state, int verbose) +{ +	struct mfi_array *ar = (struct mfi_array *)arrayp; +	int i; + +	ar->size = array_info->drives[0].coerced_size; +	ar->num_drives = array_info->drive_count; +	ar->array_ref = find_next_array(state); +	for (i = 0; i < array_info->drive_count; i++) { +		if (verbose) +			printf("Adding drive %s to array %u\n", +			    mfi_drive_name(NULL, +			    array_info->drives[i].ref.v.device_id, +			    MFI_DNAME_DEVICE_ID|MFI_DNAME_HONOR_OPTS), +			    ar->array_ref); +		if (ar->size > array_info->drives[i].coerced_size) +			ar->size = array_info->drives[i].coerced_size; +		ar->pd[i].ref = array_info->drives[i].ref; +		ar->pd[i].fw_state = MFI_PD_STATE_ONLINE; +	} +	array_info->array = ar; +} + +/* + * Create a volume that spans one or more arrays. + */ +static void +build_volume(char *volumep, int narrays, struct array_info *arrays, +    int raid_type, long stripe_size, struct config_id_state *state, int verbose) +{ +	struct mfi_ld_config *ld = (struct mfi_ld_config *)volumep; +	struct mfi_array *ar; +	int i; + +	/* properties */ +	ld->properties.ld.v.target_id = find_next_volume(state); +	ld->properties.ld.v.seq = 0; +	ld->properties.default_cache_policy = MR_LD_CACHE_ALLOW_WRITE_CACHE | +	    MR_LD_CACHE_WRITE_BACK; +	ld->properties.access_policy = MFI_LD_ACCESS_RW; +	ld->properties.disk_cache_policy = MR_PD_CACHE_UNCHANGED; +	ld->properties.current_cache_policy = MR_LD_CACHE_ALLOW_WRITE_CACHE | +	    MR_LD_CACHE_WRITE_BACK; +	ld->properties.no_bgi = 0; + +	/* params */ +	switch (raid_type) { +	case RT_RAID0: +	case RT_JBOD: +		ld->params.primary_raid_level = DDF_RAID0; +		ld->params.raid_level_qualifier = 0; +		ld->params.secondary_raid_level = 0; +		break; +	case RT_RAID1: +		ld->params.primary_raid_level = DDF_RAID1; +		ld->params.raid_level_qualifier = 0; +		ld->params.secondary_raid_level = 0; +		break; +	case RT_RAID5: +		ld->params.primary_raid_level = DDF_RAID5; +		ld->params.raid_level_qualifier = 3; +		ld->params.secondary_raid_level = 0; +		break; +	case RT_RAID6: +		ld->params.primary_raid_level = DDF_RAID6; +		ld->params.raid_level_qualifier = 3; +		ld->params.secondary_raid_level = 0; +		break; +	case RT_CONCAT: +		ld->params.primary_raid_level = DDF_CONCAT; +		ld->params.raid_level_qualifier = 0; +		ld->params.secondary_raid_level = 0; +		break; +	case RT_RAID10: +		ld->params.primary_raid_level = DDF_RAID1; +		ld->params.raid_level_qualifier = 0; +		ld->params.secondary_raid_level = 3; /* XXX? */ +		break; +	case RT_RAID50: +		/* +		 * XXX: This appears to work though the card's BIOS +		 * complains that the configuration is foreign.  The +		 * BIOS setup does not allow for creation of RAID-50 +		 * or RAID-60 arrays.  The only nested array +		 * configuration it allows for is RAID-10. +		 */ +		ld->params.primary_raid_level = DDF_RAID5; +		ld->params.raid_level_qualifier = 3; +		ld->params.secondary_raid_level = 3; /* XXX? */ +		break; +	case RT_RAID60: +		ld->params.primary_raid_level = DDF_RAID6; +		ld->params.raid_level_qualifier = 3; +		ld->params.secondary_raid_level = 3; /* XXX? */ +		break; +	} + +	/* +	 * Stripe size is encoded as (2 ^ N) * 512 = stripe_size.  Use +	 * ffs() to simulate log2(stripe_size). +	 */ +	ld->params.stripe_size = ffs(stripe_size) - 1 - 9; +	ld->params.num_drives = arrays[0].array->num_drives; +	ld->params.span_depth = narrays; +	ld->params.state = MFI_LD_STATE_OPTIMAL; +	ld->params.init_state = MFI_LD_PARAMS_INIT_NO; +	ld->params.is_consistent = 0; + +	/* spans */ +	for (i = 0; i < narrays; i++) { +		ar = arrays[i].array; +		if (verbose) +			printf("Adding array %u to volume %u\n", ar->array_ref, +			    ld->properties.ld.v.target_id); +		ld->span[i].start_block = 0; +		ld->span[i].num_blocks = ar->size; +		ld->span[i].array_ref = ar->array_ref; +	} +} + +static int +create_volume(int ac, char **av) +{ +	struct mfi_config_data *config; +	struct mfi_array *ar; +	struct mfi_ld_config *ld; +	struct config_id_state state; +	size_t config_size; +	char *p, *cfg_arrays, *cfg_volumes; +	int error, fd, i, raid_type; +	int narrays, nvolumes, arrays_per_volume; +	struct array_info *arrays; +	long stripe_size; +#ifdef DEBUG +	int dump; +#endif +	int ch, verbose; + +	/* +	 * Backwards compat.  Map 'create volume' to 'create' and +	 * 'create spare' to 'add'. +	 */ +	if (ac > 1) { +		if (strcmp(av[1], "volume") == 0) { +			av++; +			ac--; +		} else if (strcmp(av[1], "spare") == 0) { +			av++; +			ac--; +			return (add_spare(ac, av)); +		} +	} + +	if (ac < 2) { +		warnx("create volume: volume type required"); +		return (EINVAL); +	} + +	bzero(&state, sizeof(state)); +	config = NULL; +	arrays = NULL; +	narrays = 0; +	error = 0; + +	fd = mfi_open(mfi_device, O_RDWR); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	if (!mfi_reconfig_supported(mfi_device)) { +		warnx("The current %s(4) driver does not support " +		    "configuration changes.", mfi_device); +		error = EOPNOTSUPP; +		goto 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]); +		error = EINVAL; +		goto error; +	} + +	/* Parse any options. */ +	optind = 2; +#ifdef DEBUG +	dump = 0; +#endif +	verbose = 0; +	stripe_size = 64 * 1024; + +	while ((ch = getopt(ac, av, "ds:v")) != -1) { +		switch (ch) { +#ifdef DEBUG +		case 'd': +			dump = 1; +			break; +#endif +		case 's': +			stripe_size = dehumanize(optarg); +			if ((stripe_size < 512) || (!powerof2(stripe_size))) +				stripe_size = 64 * 1024; +			break; +		case 'v': +			verbose = 1; +			break; +		case '?': +		default: +			error = EINVAL; +			goto error; +		} +	} +	ac -= optind; +	av += optind; + +	/* Parse all the arrays. */ +	narrays = ac; +	if (narrays == 0) { +		warnx("At least one drive list is required"); +		error = EINVAL; +		goto error; +	} +	switch (raid_type) { +	case RT_RAID0: +	case RT_RAID1: +	case RT_RAID5: +	case RT_RAID6: +	case RT_CONCAT: +		if (narrays != 1) { +			warnx("Only one drive list can be specified"); +			error = EINVAL; +			goto error; +		} +		break; +	case RT_RAID10: +	case RT_RAID50: +	case RT_RAID60: +		if (narrays < 1) { +			warnx("RAID10, RAID50, and RAID60 require at least " +			    "two drive lists"); +			error = EINVAL; +			goto error; +		} +		if (narrays > MFI_MAX_SPAN_DEPTH) { +			warnx("Volume spans more than %d arrays", +			    MFI_MAX_SPAN_DEPTH); +			error = EINVAL; +			goto error; +		} +		break; +	} +	arrays = calloc(narrays, sizeof(*arrays)); +	if (arrays == NULL) { +		warnx("malloc failed"); +		error = ENOMEM; +		goto error; +	} +	for (i = 0; i < narrays; i++) { +		error = parse_array(fd, raid_type, av[i], &arrays[i]); +		if (error) +			goto error; +	} + +	switch (raid_type) { +	case RT_RAID10: +	case RT_RAID50: +	case RT_RAID60: +		for (i = 1; i < narrays; i++) { +			if (arrays[i].drive_count != arrays[0].drive_count) { +				warnx("All arrays must contain the same " +				    "number of drives"); +				error = EINVAL; +				goto error; +			} +		} +		break; +	} + +	/* +	 * Fetch the current config and build sorted lists of existing +	 * array and volume identifiers. +	 */ +	if (mfi_config_read(fd, &config) < 0) { +		error = errno; +		warn("Failed to read configuration"); +		goto error; +	} +	p = (char *)config->array; +	state.array_ref = 0xffff; +	state.target_id = 0xff; +	state.array_count = config->array_count; +	if (config->array_count > 0) { +		state.arrays = calloc(config->array_count, sizeof(int)); +		if (state.arrays == NULL) { +			warnx("malloc failed"); +			error = ENOMEM; +			goto error; +		} +		for (i = 0; i < config->array_count; i++) { +			ar = (struct mfi_array *)p; +			state.arrays[i] = ar->array_ref; +			p += config->array_size; +		} +		qsort(state.arrays, config->array_count, sizeof(int), +		    compare_int); +	} else +		state.arrays = NULL; +	state.log_drv_count = config->log_drv_count; +	if (config->log_drv_count) { +		state.volumes = calloc(config->log_drv_count, sizeof(int)); +		if (state.volumes == NULL) { +			warnx("malloc failed"); +			error = ENOMEM; +			goto error; +		} +		for (i = 0; i < config->log_drv_count; i++) { +			ld = (struct mfi_ld_config *)p; +			state.volumes[i] = ld->properties.ld.v.target_id; +			p += config->log_drv_size; +		} +		qsort(state.volumes, config->log_drv_count, sizeof(int), +		    compare_int); +	} else +		state.volumes = NULL; +	free(config); + +	/* Determine the size of the configuration we will build. */ +	switch (raid_type) { +	case RT_RAID0: +	case RT_RAID1: +	case RT_RAID5: +	case RT_RAID6: +	case RT_CONCAT: +	case RT_JBOD: +		/* Each volume spans a single array. */ +		nvolumes = narrays; +		break; +	case RT_RAID10: +	case RT_RAID50: +	case RT_RAID60: +		/* A single volume spans multiple arrays. */ +		nvolumes = 1; +		break; +	default: +		/* Pacify gcc. */ +		abort(); +	} + +	config_size = sizeof(struct mfi_config_data) + +	    sizeof(struct mfi_ld_config) * nvolumes + MFI_ARRAY_SIZE * narrays; +	config = calloc(1, config_size); +	if (config == NULL) { +		warnx("malloc failed"); +		error = ENOMEM; +		goto error; +	} +	config->size = config_size; +	config->array_count = narrays; +	config->array_size = MFI_ARRAY_SIZE;	/* XXX: Firmware hardcode */ +	config->log_drv_count = nvolumes; +	config->log_drv_size = sizeof(struct mfi_ld_config); +	config->spares_count = 0; +	config->spares_size = 40;		/* XXX: Firmware hardcode */ +	cfg_arrays = (char *)config->array; +	cfg_volumes = cfg_arrays + config->array_size * narrays; + +	/* Build the arrays. */ +	for (i = 0; i < narrays; i++) { +		build_array(fd, cfg_arrays, &arrays[i], &state, verbose); +		cfg_arrays += config->array_size; +	} + +	/* Now build the volume(s). */ +	arrays_per_volume = narrays / nvolumes; +	for (i = 0; i < nvolumes; i++) { +		build_volume(cfg_volumes, arrays_per_volume, +		    &arrays[i * arrays_per_volume], raid_type, stripe_size, +		    &state, verbose); +		cfg_volumes += config->log_drv_size; +	} + +#ifdef DEBUG +	if (dump) +		dump_config(fd, config, NULL); +#endif + +	/* Send the new config to the controller. */ +	if (mfi_dcmd_command(fd, MFI_DCMD_CFG_ADD, config, config_size, +	    NULL, 0, NULL) < 0) { +		error = errno; +		warn("Failed to add volume"); +		/* FALLTHROUGH */ +	} + +error: +	/* Clean up. */ +	free(config); +	free(state.volumes); +	free(state.arrays); +	if (arrays != NULL) { +		for (i = 0; i < narrays; i++) +			free(arrays[i].drives); +		free(arrays); +	} +	close(fd); + +	return (error); +} +MFI_COMMAND(top, create, create_volume); + +static int +delete_volume(int ac, char **av) +{ +	struct mfi_ld_info info; +	int error, fd; +	uint8_t target_id, mbox[4]; + +	/* +	 * Backwards compat.  Map 'delete volume' to 'delete' and +	 * 'delete spare' to 'remove'. +	 */ +	if (ac > 1) { +		if (strcmp(av[1], "volume") == 0) { +			av++; +			ac--; +		} else if (strcmp(av[1], "spare") == 0) { +			av++; +			ac--; +			return (remove_spare(ac, av)); +		} +	} + +	if (ac != 2) { +		warnx("delete volume: volume required"); +		return (EINVAL); +	} + +	fd = mfi_open(mfi_device, O_RDWR); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	if (!mfi_reconfig_supported(mfi_device)) { +		warnx("The current %s(4) driver does not support " +		    "configuration changes.", mfi_device); +		close(fd); +		return (EOPNOTSUPP); +	} + +	if (mfi_lookup_volume(fd, av[1], &target_id) < 0) { +		error = errno; +		warn("Invalid volume %s", av[1]); +		close(fd); +		return (error); +	} + +	if (mfi_ld_get_info(fd, target_id, &info, NULL) < 0) { +		error = errno; +		warn("Failed to get info for volume %d", target_id); +		close(fd); +		return (error); +	} + +	if (mfi_volume_busy(fd, target_id)) { +		warnx("Volume %s is busy and cannot be deleted", +		    mfi_volume_name(fd, target_id)); +		close(fd); +		return (EBUSY); +	} + +	mbox_store_ldref(mbox, &info.ld_config.properties.ld); +	if (mfi_dcmd_command(fd, MFI_DCMD_LD_DELETE, NULL, 0, mbox, +	    sizeof(mbox), NULL) < 0) { +		error = errno; +		warn("Failed to delete volume"); +		close(fd); +		return (error); +	} + +	close(fd); + +	return (0); +} +MFI_COMMAND(top, delete, delete_volume); + +static int +add_spare(int ac, char **av) +{ +	struct mfi_pd_info info; +	struct mfi_config_data *config; +	struct mfi_array *ar; +	struct mfi_ld_config *ld; +	struct mfi_spare *spare; +	uint16_t device_id; +	uint8_t target_id; +	char *p; +	int error, fd, i; + +	if (ac < 2) { +		warnx("add spare: drive required"); +		return (EINVAL); +	} + +	fd = mfi_open(mfi_device, O_RDWR); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	config = NULL; +	spare = NULL; +	error = mfi_lookup_drive(fd, av[1], &device_id); +	if (error) +		goto error; + +	if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) { +		error = errno; +		warn("Failed to fetch drive info"); +		goto error; +	} + +	if (info.fw_state != MFI_PD_STATE_UNCONFIGURED_GOOD) { +		warnx("Drive %u is not available", device_id); +		error = EINVAL; +		goto error; +	} + +	if (ac > 2) { +		if (mfi_lookup_volume(fd, av[2], &target_id) < 0) { +			error = errno; +			warn("Invalid volume %s", av[2]); +			goto error; +		} +	} + +	if (mfi_config_read(fd, &config) < 0) { +		error = errno; +		warn("Failed to read configuration"); +		goto error; +	} + +	spare = malloc(sizeof(struct mfi_spare) + sizeof(uint16_t) * +	    config->array_count); +	if (spare == NULL) { +		warnx("malloc failed"); +		error = ENOMEM; +		goto error; +	} +	bzero(spare, sizeof(struct mfi_spare)); +	spare->ref = info.ref; + +	if (ac == 2) { +		/* Global spare backs all arrays. */ +		p = (char *)config->array; +		for (i = 0; i < config->array_count; i++) { +			ar = (struct mfi_array *)p; +			if (ar->size > info.coerced_size) { +				warnx("Spare isn't large enough for array %u", +				    ar->array_ref); +				error = EINVAL; +				goto error; +			} +			p += config->array_size; +		} +		spare->array_count = 0; +	} else  { +		/* +		 * Dedicated spares only back the arrays for a +		 * specific volume. +		 */ +		ld = mfi_config_lookup_volume(config, target_id); +		if (ld == NULL) { +			warnx("Did not find volume %d", target_id); +			error = EINVAL; +			goto error; +		} + +		spare->spare_type |= MFI_SPARE_DEDICATED; +		spare->array_count = ld->params.span_depth; +		for (i = 0; i < ld->params.span_depth; i++) { +			ar = mfi_config_lookup_array(config, +			    ld->span[i].array_ref); +			if (ar == NULL) { +				warnx("Missing array; inconsistent config?"); +				error = ENXIO; +				goto error; +			} +			if (ar->size > info.coerced_size) { +				warnx("Spare isn't large enough for array %u", +				    ar->array_ref); +				error = EINVAL; +				goto error; +			} +			spare->array_ref[i] = ar->array_ref; +		} +	} + +	if (mfi_dcmd_command(fd, MFI_DCMD_CFG_MAKE_SPARE, spare, +	    sizeof(struct mfi_spare) + sizeof(uint16_t) * spare->array_count, +	    NULL, 0, NULL) < 0) { +		error = errno; +		warn("Failed to assign spare"); +		/* FALLTHROUGH. */ +	} + +error: +	free(spare); +	free(config); +	close(fd); + +	return (error); +} +MFI_COMMAND(top, add, add_spare); + +static int +remove_spare(int ac, char **av) +{ +	struct mfi_pd_info info; +	int error, fd; +	uint16_t device_id; +	uint8_t mbox[4]; + +	if (ac != 2) { +		warnx("remove spare: drive required"); +		return (EINVAL); +	} + +	fd = mfi_open(mfi_device, O_RDWR); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	error = mfi_lookup_drive(fd, av[1], &device_id); +	if (error) { +		close(fd); +		return (error); +	} + +	/* Get the info for this drive. */ +	if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) { +		error = errno; +		warn("Failed to fetch info for drive %u", device_id); +		close(fd); +		return (error); +	} + +	if (info.fw_state != MFI_PD_STATE_HOT_SPARE) { +		warnx("Drive %u is not a hot spare", device_id); +		close(fd); +		return (EINVAL); +	} + +	mbox_store_pdref(mbox, &info.ref); +	if (mfi_dcmd_command(fd, MFI_DCMD_CFG_REMOVE_SPARE, NULL, 0, mbox, +	    sizeof(mbox), NULL) < 0) { +		error = errno; +		warn("Failed to delete spare"); +		close(fd); +		return (error); +	} + +	close(fd); + +	return (0); +} +MFI_COMMAND(top, remove, remove_spare); + +/* Display raw data about a config. */ +void +dump_config(int fd, struct mfi_config_data *config, const char *msg_prefix) +{ +	struct mfi_array *ar; +	struct mfi_ld_config *ld; +	struct mfi_spare *sp; +	struct mfi_pd_info pinfo; +	uint16_t device_id; +	char *p; +	int i, j; + +	if (NULL == msg_prefix) +		msg_prefix = "Configuration (Debug)"; + +	printf( +	    "%s %s: %d arrays, %d volumes, %d spares\n", mfi_device, +	    msg_prefix, config->array_count, config->log_drv_count, +	    config->spares_count); +	printf("  array size: %u\n", config->array_size); +	printf("  volume size: %u\n", config->log_drv_size); +	printf("  spare size: %u\n", config->spares_size); +	p = (char *)config->array; + +	for (i = 0; i < config->array_count; i++) { +		ar = (struct mfi_array *)p; +		printf("    array %u of %u drives:\n", ar->array_ref, +		    ar->num_drives); +		printf("      size = %ju\n", (uintmax_t)ar->size); +		for (j = 0; j < ar->num_drives; j++) { +			device_id = ar->pd[j].ref.v.device_id; +			if (device_id == 0xffff) +				printf("        drive MISSING\n"); +			else { +				printf("        drive %u %s\n", device_id, +				    mfi_pdstate(ar->pd[j].fw_state)); +				if (mfi_pd_get_info(fd, device_id, &pinfo, +				    NULL) >= 0) { +					printf("          raw size: %ju\n", +					    (uintmax_t)pinfo.raw_size); +					printf("          non-coerced size: %ju\n", +					    (uintmax_t)pinfo.non_coerced_size); +					printf("          coerced size: %ju\n", +					    (uintmax_t)pinfo.coerced_size); +				} +			} +		} +		p += config->array_size; +	} + +	for (i = 0; i < config->log_drv_count; i++) { +		ld = (struct mfi_ld_config *)p; +		printf("    volume %s ", +		    mfi_volume_name(fd, ld->properties.ld.v.target_id)); +		printf("%s %s", +		    mfi_raid_level(ld->params.primary_raid_level, +			ld->params.secondary_raid_level), +		    mfi_ldstate(ld->params.state)); +		if (ld->properties.name[0] != '\0') +			printf(" <%s>", ld->properties.name); +		printf("\n"); +		printf("      primary raid level: %u\n", +		    ld->params.primary_raid_level); +		printf("      raid level qualifier: %u\n", +		    ld->params.raid_level_qualifier); +		printf("      secondary raid level: %u\n", +		    ld->params.secondary_raid_level); +		printf("      stripe size: %u\n", ld->params.stripe_size); +		printf("      num drives: %u\n", ld->params.num_drives); +		printf("      init state: %u\n", ld->params.init_state); +		printf("      consistent: %u\n", ld->params.is_consistent); +		printf("      no bgi: %u\n", ld->properties.no_bgi); +		printf("      spans:\n"); +		for (j = 0; j < ld->params.span_depth; j++) { +			printf("        array %u @ ", ld->span[j].array_ref); +			printf("%ju : %ju\n", +			    (uintmax_t)ld->span[j].start_block, +			    (uintmax_t)ld->span[j].num_blocks); +		} +		p += config->log_drv_size; +	} + +	for (i = 0; i < config->spares_count; i++) { +		sp = (struct mfi_spare *)p; +		printf("    %s spare %u ", +		    sp->spare_type & MFI_SPARE_DEDICATED ? "dedicated" : +		    "global", sp->ref.v.device_id); +		printf("%s", mfi_pdstate(MFI_PD_STATE_HOT_SPARE)); +		printf(" backs:\n"); +		for (j = 0; j < sp->array_count; j++) +			printf("        array %u\n", sp->array_ref[j]); +		p += config->spares_size; +	} +} + +#ifdef DEBUG +static int +debug_config(int ac, char **av) +{ +	struct mfi_config_data *config; +	int error, fd; + +	if (ac != 1) { +		warnx("debug: extra arguments"); +		return (EINVAL); +	} + +	fd = mfi_open(mfi_device, O_RDWR); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	/* Get the config from the controller. */ +	if (mfi_config_read(fd, &config) < 0) { +		error = errno; +		warn("Failed to get config"); +		close(fd); +		return (error); +	} + +	/* Dump out the configuration. */ +	dump_config(fd, config, NULL); +	free(config); +	close(fd); + +	return (0); +} +MFI_COMMAND(top, debug, debug_config); + +static int +dump(int ac, char **av) +{ +	struct mfi_config_data *config; +	char buf[64]; +	size_t len; +	int error, fd; + +	if (ac != 1) { +		warnx("dump: extra arguments"); +		return (EINVAL); +	} + +	fd = mfi_open(mfi_device, O_RDWR); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	/* Get the stashed copy of the last dcmd from the driver. */ +	snprintf(buf, sizeof(buf), "dev.mfi.%d.debug_command", mfi_unit); +	if (sysctlbyname(buf, NULL, &len, NULL, 0) < 0) { +		error = errno; +		warn("Failed to read debug command"); +		if (error == ENOENT) +			error = EOPNOTSUPP; +		close(fd); +		return (error); +	} + +	config = malloc(len); +	if (config == NULL) { +		warnx("malloc failed"); +		close(fd); +		return (ENOMEM); +	} +	if (sysctlbyname(buf, config, &len, NULL, 0) < 0) { +		error = errno; +		warn("Failed to read debug command"); +		free(config); +		close(fd); +		return (error); +	} +	dump_config(fd, config, NULL); +	free(config); +	close(fd); + +	return (0); +} +MFI_COMMAND(top, dump, dump); +#endif diff --git a/usr.sbin/mfiutil/mfi_drive.c b/usr.sbin/mfiutil/mfi_drive.c new file mode 100644 index 000000000000..c7c5aeb02f14 --- /dev/null +++ b/usr.sbin/mfiutil/mfi_drive.c @@ -0,0 +1,762 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2008, 2009 Yahoo!, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * 3. The names of the authors may not 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/types.h> +#include <sys/errno.h> +#include <sys/sbuf.h> +#include <ctype.h> +#include <err.h> +#include <fcntl.h> +#include <libutil.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <unistd.h> +#include <cam/scsi/scsi_all.h> +#include "mfiutil.h" + +MFI_TABLE(top, drive); + +/* + * Print the name of a drive either by drive number as %2u or by enclosure:slot + * as Exx:Sxx (or both).  Use default unless command line options override it + * and the command allows this (which we usually do unless we already print + * both).  We prefer pinfo if given, otherwise try to look it up by device_id. + */ +const char * +mfi_drive_name(struct mfi_pd_info *pinfo, uint16_t device_id, uint32_t def) +{ +	struct mfi_pd_info info; +	struct sbuf sb; +	static char buf[16]; +	int fd; + +	if ((def & MFI_DNAME_HONOR_OPTS) != 0 && +	    (mfi_opts & (MFI_DNAME_ES|MFI_DNAME_DEVICE_ID)) != 0) +		def = mfi_opts & (MFI_DNAME_ES|MFI_DNAME_DEVICE_ID); + +	buf[0] = '\0'; +	if (pinfo == NULL && def & MFI_DNAME_ES) { +		/* Fallback in case of error, just ignore flags. */ +		if (device_id == 0xffff) +			snprintf(buf, sizeof(buf), "MISSING"); +		else +			snprintf(buf, sizeof(buf), "%2u", device_id); + +		fd = mfi_open(mfi_device, O_RDWR); +		if (fd < 0) { +			warn("mfi_open"); +			return (buf); +		} + +		/* Get the info for this drive. */ +		if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) { +			warn("Failed to fetch info for drive %2u", device_id); +			close(fd); +			return (buf); +		} + +		close(fd); +		pinfo = &info; +	} + +	sbuf_new(&sb, buf, sizeof(buf), SBUF_FIXEDLEN); +	if (def & MFI_DNAME_DEVICE_ID) { +		if (device_id == 0xffff) +			sbuf_printf(&sb, "MISSING"); +		else +			sbuf_printf(&sb, "%2u", device_id); +	} +	if ((def & (MFI_DNAME_ES|MFI_DNAME_DEVICE_ID)) == +	    (MFI_DNAME_ES|MFI_DNAME_DEVICE_ID)) { +		sbuf_cat(&sb, " "); +	} +	if (def & MFI_DNAME_ES) { +		if (pinfo->encl_device_id == 0xffff) +			sbuf_printf(&sb, "S%u", +			    pinfo->slot_number); +		else if (pinfo->encl_device_id == pinfo->ref.v.device_id) +			sbuf_printf(&sb, "E%u", +			    pinfo->encl_index); +		else +			sbuf_printf(&sb, "E%u:S%u", +			    pinfo->encl_index, pinfo->slot_number); +	} +	sbuf_finish(&sb); + +	return (buf); +} + +const char * +mfi_pdstate(enum mfi_pd_state state) +{ +	static char buf[16]; + +	switch (state) { +	case MFI_PD_STATE_UNCONFIGURED_GOOD: +		return ("UNCONFIGURED GOOD"); +	case MFI_PD_STATE_UNCONFIGURED_BAD: +		return ("UNCONFIGURED BAD"); +	case MFI_PD_STATE_HOT_SPARE: +		return ("HOT SPARE"); +	case MFI_PD_STATE_OFFLINE: +		return ("OFFLINE"); +	case MFI_PD_STATE_FAILED: +		return ("FAILED"); +	case MFI_PD_STATE_REBUILD: +		return ("REBUILD"); +	case MFI_PD_STATE_ONLINE: +		return ("ONLINE"); +	case MFI_PD_STATE_COPYBACK: +		return ("COPYBACK"); +	case MFI_PD_STATE_SYSTEM: +		return ("JBOD"); +	default: +		sprintf(buf, "PSTATE 0x%04x", state); +		return (buf); +	} +} + +int +mfi_lookup_drive(int fd, char *drive, uint16_t *device_id) +{ +	struct mfi_pd_list *list; +	long val; +	int error; +	u_int i; +	char *cp; +	uint8_t encl, slot; + +	/* Look for a raw device id first. */ +	val = strtol(drive, &cp, 0); +	if (*cp == '\0') { +		if (val < 0 || val >= 0xffff) +			goto bad; +		*device_id = val; +		return (0); +	} + +	/* Support for MegaCli style [Exx]:Syy notation. */ +	if (toupper(drive[0]) == 'E' || toupper(drive[0]) == 'S') { +		if (drive[1] == '\0') +			goto bad; +		cp = drive; +		if (toupper(drive[0]) == 'E') { +			cp++;			/* Eat 'E' */ +			val = strtol(cp, &cp, 0); +			if (val < 0 || val > 0xff || *cp != ':') +				goto bad; +			encl = val; +			cp++;			/* Eat ':' */ +			if (toupper(*cp) != 'S') +				goto bad; +		} else +			encl = 0xff; +		cp++;				/* Eat 'S' */ +		if (*cp == '\0') +			goto bad; +		val = strtol(cp, &cp, 0); +		if (val < 0 || val > 0xff || *cp != '\0') +			goto bad; +		slot = val; + +		if (mfi_pd_get_list(fd, &list, NULL) < 0) { +			error = errno; +			warn("Failed to fetch drive list"); +			return (error); +		} + +		for (i = 0; i < list->count; i++) { +			if (list->addr[i].scsi_dev_type != 0) +				continue; + +			if (((encl == 0xff && +			    list->addr[i].encl_device_id == 0xffff) || +			    list->addr[i].encl_index == encl) && +			    list->addr[i].slot_number == slot) { +				*device_id = list->addr[i].device_id; +				free(list); +				return (0); +			} +		} +		free(list); +		warnx("Unknown drive %s", drive); +		return (EINVAL); +	} + +bad: +	warnx("Invalid drive number %s", drive); +	return (EINVAL); +} + +static void +mbox_store_device_id(uint8_t *mbox, uint16_t device_id) +{ + +	mbox[0] = device_id & 0xff; +	mbox[1] = device_id >> 8; +} + +void +mbox_store_pdref(uint8_t *mbox, union mfi_pd_ref *ref) +{ + +	mbox[0] = ref->v.device_id & 0xff; +	mbox[1] = ref->v.device_id >> 8; +	mbox[2] = ref->v.seq_num & 0xff; +	mbox[3] = ref->v.seq_num >> 8; +} + +int +mfi_pd_get_list(int fd, struct mfi_pd_list **listp, uint8_t *statusp) +{ +	struct mfi_pd_list *list; +	uint32_t list_size; + +	/* +	 * Keep fetching the list in a loop until we have a large enough +	 * buffer to hold the entire list. +	 */ +	list = NULL; +	list_size = 1024; +fetch: +	list = reallocf(list, list_size); +	if (list == NULL) +		return (-1); +	if (mfi_dcmd_command(fd, MFI_DCMD_PD_GET_LIST, list, list_size, NULL, +	    0, statusp) < 0) { +		free(list); +		return (-1); +	} + +	if (list->size > list_size) { +		list_size = list->size; +		goto fetch; +	} + +	*listp = list; +	return (0); +} + +int +mfi_pd_get_info(int fd, uint16_t device_id, struct mfi_pd_info *info, +    uint8_t *statusp) +{ +	uint8_t mbox[2]; + +	mbox_store_device_id(&mbox[0], device_id); +	return (mfi_dcmd_command(fd, MFI_DCMD_PD_GET_INFO, info, +	    sizeof(struct mfi_pd_info), mbox, 2, statusp)); +} + +static void +cam_strvis(char *dst, const char *src, int srclen, int dstlen) +{ + +	/* Trim leading/trailing spaces, nulls. */ +	while (srclen > 0 && src[0] == ' ') +		src++, srclen--; +	while (srclen > 0 +	    && (src[srclen-1] == ' ' || src[srclen-1] == '\0')) +		srclen--; + +	while (srclen > 0 && dstlen > 1) { +		char *cur_pos = dst; + +		if (*src < 0x20) { +			/* SCSI-II Specifies that these should never occur. */ +			/* non-printable character */ +			if (dstlen > 4) { +				*cur_pos++ = '\\'; +				*cur_pos++ = ((*src & 0300) >> 6) + '0'; +				*cur_pos++ = ((*src & 0070) >> 3) + '0'; +				*cur_pos++ = ((*src & 0007) >> 0) + '0'; +			} else { +				*cur_pos++ = '?'; +			} +		} else { +			/* normal character */ +			*cur_pos++ = *src; +		} +		src++; +		srclen--; +		dstlen -= cur_pos - dst; +		dst = cur_pos; +	} +	*dst = '\0'; +} + +/* Borrowed heavily from scsi_all.c:scsi_print_inquiry(). */ +const char * +mfi_pd_inq_string(struct mfi_pd_info *info) +{ +	struct scsi_inquiry_data iqd, *inq_data = &iqd; +	char vendor[16], product[48], revision[16], rstr[12], serial[SID_VENDOR_SPECIFIC_0_SIZE]; +	static char inq_string[64]; + +	memcpy(inq_data, info->inquiry_data, +	    (sizeof (iqd) <  sizeof (info->inquiry_data))? +	    sizeof (iqd) : sizeof (info->inquiry_data)); +	if (SID_QUAL_IS_VENDOR_UNIQUE(inq_data)) +		return (NULL); +	if (SID_TYPE(inq_data) != T_DIRECT) +		return (NULL); +	if (SID_QUAL(inq_data) != SID_QUAL_LU_CONNECTED) +		return (NULL); + +	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)); +	cam_strvis(serial, (char *)inq_data->vendor_specific0, sizeof(inq_data->vendor_specific0), +	    sizeof(serial)); + +	/* Hack for SATA disks, no idea how to tell speed. */ +	if (strcmp(vendor, "ATA") == 0) { +		snprintf(inq_string, sizeof(inq_string), "<%s %s serial=%s> SATA", +		    product, revision, serial); +		return (inq_string); +	} + +	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(inq_string, sizeof(inq_string), "<%s %s %s serial=%s> %s", vendor, +	    product, revision, serial, rstr); +	return (inq_string); +} + +/* Helper function to set a drive to a given state. */ +static int +drive_set_state(char *drive, uint16_t new_state) +{ +	struct mfi_pd_info info; +	uint16_t device_id; +	uint8_t mbox[6]; +	int error, fd; + +	fd = mfi_open(mfi_device, O_RDWR); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	error = mfi_lookup_drive(fd, drive, &device_id); +	if (error) { +		close(fd); +		return (error); +	} + +	/* Get the info for this drive. */ +	if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) { +		error = errno; +		warn("Failed to fetch info for drive %u", device_id); +		close(fd); +		return (error); +	} + +	/* Try to change the state. */ +	if (info.fw_state == new_state) { +		warnx("Drive %u is already in the desired state", device_id); +		close(fd); +		return (EINVAL); +	} + +	mbox_store_pdref(&mbox[0], &info.ref); +	mbox[4] = new_state & 0xff; +	mbox[5] = new_state >> 8; +	if (mfi_dcmd_command(fd, MFI_DCMD_PD_STATE_SET, NULL, 0, mbox, 6, +	    NULL) < 0) { +		error = errno; +		warn("Failed to set drive %u to %s", device_id, +		    mfi_pdstate(new_state)); +		close(fd); +		return (error); +	} + +	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], MFI_PD_STATE_FAILED)); +} +MFI_COMMAND(top, fail, fail_drive); + +static int +good_drive(int ac, char **av) +{ + +	if (ac != 2) { +		warnx("good: %s", ac > 2 ? "extra arguments" : +		    "drive required"); +		return (EINVAL); +	} + +	return (drive_set_state(av[1], MFI_PD_STATE_UNCONFIGURED_GOOD)); +} +MFI_COMMAND(top, good, good_drive); + +static int +rebuild_drive(int ac, char **av) +{ + +	if (ac != 2) { +		warnx("rebuild: %s", ac > 2 ? "extra arguments" : +		    "drive required"); +		return (EINVAL); +	} + +	return (drive_set_state(av[1], MFI_PD_STATE_REBUILD)); +} +MFI_COMMAND(top, rebuild, rebuild_drive); + +static int +syspd_drive(int ac, char **av) +{ + +	if (ac != 2) { +		warnx("syspd: %s", ac > 2 ? "extra arguments" : +		    "drive required"); +		return (EINVAL); +	} + +	return (drive_set_state(av[1], MFI_PD_STATE_SYSTEM)); +} +MFI_COMMAND(top, syspd, syspd_drive); + +static int +start_rebuild(int ac, char **av) +{ +	struct mfi_pd_info info; +	uint16_t device_id; +	uint8_t mbox[4]; +	int error, fd; + +	if (ac != 2) { +		warnx("start rebuild: %s", ac > 2 ? "extra arguments" : +		    "drive required"); +		return (EINVAL); +	} + +	fd = mfi_open(mfi_device, O_RDWR); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	error = mfi_lookup_drive(fd, av[1], &device_id); +	if (error) { +		close(fd); +		return (error); +	} + +	/* Get the info for this drive. */ +	if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) { +		error = errno; +		warn("Failed to fetch info for drive %u", device_id); +		close(fd); +		return (error); +	} + +	/* Check the state, must be REBUILD. */ +	if (info.fw_state != MFI_PD_STATE_REBUILD) { +		warnx("Drive %d is not in the REBUILD state", device_id); +		close(fd); +		return (EINVAL); +	} + +	/* Start the rebuild. */ +	mbox_store_pdref(&mbox[0], &info.ref); +	if (mfi_dcmd_command(fd, MFI_DCMD_PD_REBUILD_START, NULL, 0, mbox, 4, +	    NULL) < 0) { +		error = errno; +		warn("Failed to start rebuild on drive %u", device_id); +		close(fd); +		return (error); +	} +	close(fd); + +	return (0); +} +MFI_COMMAND(start, rebuild, start_rebuild); + +static int +abort_rebuild(int ac, char **av) +{ +	struct mfi_pd_info info; +	uint16_t device_id; +	uint8_t mbox[4]; +	int error, fd; + +	if (ac != 2) { +		warnx("abort rebuild: %s", ac > 2 ? "extra arguments" : +		    "drive required"); +		return (EINVAL); +	} + +	fd = mfi_open(mfi_device, O_RDWR); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	error = mfi_lookup_drive(fd, av[1], &device_id); +	if (error) { +		close(fd); +		return (error); +	} + +	/* Get the info for this drive. */ +	if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) { +		error = errno; +		warn("Failed to fetch info for drive %u", device_id); +		close(fd); +		return (error); +	} + +	/* Check the state, must be REBUILD. */ +	if (info.fw_state != MFI_PD_STATE_REBUILD) { +		warn("Drive %d is not in the REBUILD state", device_id); +		close(fd); +		return (EINVAL); +	} + +	/* Abort the rebuild. */ +	mbox_store_pdref(&mbox[0], &info.ref); +	if (mfi_dcmd_command(fd, MFI_DCMD_PD_REBUILD_ABORT, NULL, 0, mbox, 4, +	    NULL) < 0) { +		error = errno; +		warn("Failed to abort rebuild on drive %u", device_id); +		close(fd); +		return (error); +	} +	close(fd); + +	return (0); +} +MFI_COMMAND(abort, rebuild, abort_rebuild); + +static int +drive_progress(int ac, char **av) +{ +	struct mfi_pd_info info; +	uint16_t device_id; +	int error, fd; + +	if (ac != 2) { +		warnx("drive progress: %s", ac > 2 ? "extra arguments" : +		    "drive required"); +		return (EINVAL); +	} + +	fd = mfi_open(mfi_device, O_RDWR); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	error = mfi_lookup_drive(fd, av[1], &device_id); +	if (error) { +		close(fd); +		return (error); +	} + +	/* Get the info for this drive. */ +	if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) { +		error = errno; +		warn("Failed to fetch info for drive %u", device_id); +		close(fd); +		return (error); +	} +	close(fd); + +	/* Display any of the active events. */ +	if (info.prog_info.active & MFI_PD_PROGRESS_REBUILD) +		mfi_display_progress("Rebuild", &info.prog_info.rbld); +	if (info.prog_info.active & MFI_PD_PROGRESS_PATROL) +		mfi_display_progress("Patrol Read", &info.prog_info.patrol); +	if (info.prog_info.active & MFI_PD_PROGRESS_CLEAR) +		mfi_display_progress("Clear", &info.prog_info.clear); +	if ((info.prog_info.active & (MFI_PD_PROGRESS_REBUILD | +	    MFI_PD_PROGRESS_PATROL | MFI_PD_PROGRESS_CLEAR)) == 0) +		printf("No activity in progress for drive %s.\n", +		mfi_drive_name(NULL, device_id, +		    MFI_DNAME_DEVICE_ID|MFI_DNAME_HONOR_OPTS)); + +	return (0); +} +MFI_COMMAND(drive, progress, drive_progress); + +static int +drive_clear(int ac, char **av) +{ +	struct mfi_pd_info info; +	uint32_t opcode; +	uint16_t device_id; +	uint8_t mbox[4]; +	char *s1; +	int error, fd; + +	if (ac != 3) { +		warnx("drive clear: %s", ac > 3 ? "extra arguments" : +		    "drive and action requires"); +		return (EINVAL); +	} + +	for (s1 = av[2]; *s1 != '\0'; s1++) +		*s1 = tolower(*s1); +	if (strcmp(av[2], "start") == 0) +		opcode = MFI_DCMD_PD_CLEAR_START; +	else if ((strcmp(av[2], "stop") == 0) || (strcmp(av[2], "abort") == 0)) +		opcode = MFI_DCMD_PD_CLEAR_ABORT; +	else { +		warnx("drive clear: invalid action, must be 'start' or 'stop'\n"); +		return (EINVAL); +	} + +	fd = mfi_open(mfi_device, O_RDWR); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	error = mfi_lookup_drive(fd, av[1], &device_id); +	if (error) { +		close(fd); +		return (error); +	} + +	/* Get the info for this drive. */ +	if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) { +		error = errno; +		warn("Failed to fetch info for drive %u", device_id); +		close(fd); +		return (error); +	} + +	mbox_store_pdref(&mbox[0], &info.ref); +	if (mfi_dcmd_command(fd, opcode, NULL, 0, mbox, 4, NULL) < 0) { +		error = errno; +		warn("Failed to %s clear on drive %u", +		    opcode == MFI_DCMD_PD_CLEAR_START ? "start" : "stop", +		    device_id); +		close(fd); +		return (error); +	} + +	close(fd); +	return (0); +} +MFI_COMMAND(drive, clear, drive_clear); + +static int +drive_locate(int ac, char **av) +{ +	uint16_t device_id; +	uint32_t opcode; +	int error, fd; +	uint8_t mbox[4]; + +	if (ac != 3) { +		warnx("locate: %s", ac > 3 ? "extra arguments" : +		    "drive and state required"); +		return (EINVAL); +	} + +	if (strcasecmp(av[2], "on") == 0 || strcasecmp(av[2], "start") == 0) +		opcode = MFI_DCMD_PD_LOCATE_START; +	else if (strcasecmp(av[2], "off") == 0 || +	    strcasecmp(av[2], "stop") == 0) +		opcode = MFI_DCMD_PD_LOCATE_STOP; +	else { +		warnx("locate: invalid state %s", av[2]); +		return (EINVAL); +	} + +	fd = mfi_open(mfi_device, O_RDWR); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	error = mfi_lookup_drive(fd, av[1], &device_id); +	if (error) { +		close(fd); +		return (error); +	} + + +	mbox_store_device_id(&mbox[0], device_id); +	mbox[2] = 0; +	mbox[3] = 0; +	if (mfi_dcmd_command(fd, opcode, NULL, 0, mbox, 4, NULL) < 0) { +		error = errno; +		warn("Failed to %s locate on drive %u", +		    opcode == MFI_DCMD_PD_LOCATE_START ? "start" : "stop", +		    device_id); +		close(fd); +		return (error); +	} +	close(fd); + +	return (0); +} +MFI_COMMAND(top, locate, drive_locate); diff --git a/usr.sbin/mfiutil/mfi_evt.c b/usr.sbin/mfiutil/mfi_evt.c new file mode 100644 index 000000000000..d4eb48d9dcb4 --- /dev/null +++ b/usr.sbin/mfiutil/mfi_evt.c @@ -0,0 +1,703 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2008, 2009 Yahoo!, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * 3. The names of the authors may not 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/types.h> +#include <sys/errno.h> +#include <err.h> +#include <fcntl.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <strings.h> +#include <time.h> +#include <unistd.h> +#include "mfiutil.h" + +static int +mfi_event_get_info(int fd, struct mfi_evt_log_state *info, uint8_t *statusp) +{ + +	return (mfi_dcmd_command(fd, MFI_DCMD_CTRL_EVENT_GETINFO, info, +	    sizeof(struct mfi_evt_log_state), NULL, 0, statusp)); +} + +static int +mfi_get_events(int fd, struct mfi_evt_list *list, int num_events, +    union mfi_evt filter, uint32_t start_seq, uint8_t *statusp) +{ +	uint32_t mbox[2]; +	size_t size; + +	mbox[0] = start_seq; +	mbox[1] = filter.word; +	size = sizeof(struct mfi_evt_list) + sizeof(struct mfi_evt_detail) * +	    (num_events - 1); +	return (mfi_dcmd_command(fd, MFI_DCMD_CTRL_EVENT_GET, list, size, +	    (uint8_t *)&mbox, sizeof(mbox), statusp)); +} + +static int +show_logstate(int ac, char **av __unused) +{ +	struct mfi_evt_log_state info; +	int error, fd; + +	if (ac != 1) { +		warnx("show logstate: extra arguments"); +		return (EINVAL); +	} + +	fd = mfi_open(mfi_device, O_RDWR); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	if (mfi_event_get_info(fd, &info, NULL) < 0) { +		error = errno; +		warn("Failed to get event log info"); +		close(fd); +		return (error); +	} + +	printf("%s Event Log Sequence Numbers:\n", mfi_device); +	printf("  Newest Seq #: %u\n", info.newest_seq_num); +	printf("  Oldest Seq #: %u\n", info.oldest_seq_num); +	printf("   Clear Seq #: %u\n", info.clear_seq_num); +	printf("Shutdown Seq #: %u\n", info.shutdown_seq_num); +	printf("    Boot Seq #: %u\n", info.boot_seq_num); + +	close(fd); + +	return (0); +} +MFI_COMMAND(show, logstate, show_logstate); + +static int +parse_seq(struct mfi_evt_log_state *info, char *arg, uint32_t *seq) +{ +	char *cp; +	long val; + +	if (strcasecmp(arg, "newest") == 0) { +		*seq = info->newest_seq_num; +		return (0); +	} +	if (strcasecmp(arg, "oldest") == 0) { +		*seq = info->oldest_seq_num; +		return (0); +	} +	if (strcasecmp(arg, "clear") == 0) { +		*seq = info->clear_seq_num; +		return (0); +	} +	if (strcasecmp(arg, "shutdown") == 0) { +		*seq = info->shutdown_seq_num; +		return (0); +	} +	if (strcasecmp(arg, "boot") == 0) { +		*seq = info->boot_seq_num; +		return (0); +	} +	val = strtol(arg, &cp, 0); +	if (*cp != '\0' || val < 0) { +		errno = EINVAL; +		return (-1); +	} +	*seq = val; +	return (0); +} + +static int +parse_locale(char *arg, uint16_t *locale) +{ +	char *cp; +	long val; + +	if (strncasecmp(arg, "vol", 3) == 0 || strcasecmp(arg, "ld") == 0) { +		*locale = MFI_EVT_LOCALE_LD; +		return (0); +	} +	if (strncasecmp(arg, "drive", 5) == 0 || strcasecmp(arg, "pd") == 0) { +		*locale = MFI_EVT_LOCALE_PD; +		return (0); +	} +	if (strncasecmp(arg, "encl", 4) == 0) { +		*locale = MFI_EVT_LOCALE_ENCL; +		return (0); +	} +	if (strncasecmp(arg, "batt", 4) == 0 || +	    strncasecmp(arg, "bbu", 3) == 0) { +		*locale = MFI_EVT_LOCALE_BBU; +		return (0); +	} +	if (strcasecmp(arg, "sas") == 0) { +		*locale = MFI_EVT_LOCALE_SAS; +		return (0); +	} +	if (strcasecmp(arg, "ctrl") == 0 || strncasecmp(arg, "cont", 4) == 0) { +		*locale = MFI_EVT_LOCALE_CTRL; +		return (0); +	} +	if (strcasecmp(arg, "config") == 0) { +		*locale = MFI_EVT_LOCALE_CONFIG; +		return (0); +	} +	if (strcasecmp(arg, "cluster") == 0) { +		*locale = MFI_EVT_LOCALE_CLUSTER; +		return (0); +	} +	if (strcasecmp(arg, "all") == 0) { +		*locale = MFI_EVT_LOCALE_ALL; +		return (0); +	} +	val = strtol(arg, &cp, 0); +	if (*cp != '\0' || val < 0 || val > 0xffff) { +		errno = EINVAL; +		return (-1); +	} +	*locale = val; +	return (0); +} + +static int +parse_class(char *arg, int8_t *class) +{ +	char *cp; +	long val; + +	if (strcasecmp(arg, "debug") == 0) { +		*class = MFI_EVT_CLASS_DEBUG; +		return (0); +	} +	if (strncasecmp(arg, "prog", 4) == 0) { +		*class = MFI_EVT_CLASS_PROGRESS; +		return (0); +	} +	if (strncasecmp(arg, "info", 4) == 0) { +		*class = MFI_EVT_CLASS_INFO; +		return (0); +	} +	if (strncasecmp(arg, "warn", 4) == 0) { +		*class = MFI_EVT_CLASS_WARNING; +		return (0); +	} +	if (strncasecmp(arg, "crit", 4) == 0) { +		*class = MFI_EVT_CLASS_CRITICAL; +		return (0); +	} +	if (strcasecmp(arg, "fatal") == 0) { +		*class = MFI_EVT_CLASS_FATAL; +		return (0); +	} +	if (strcasecmp(arg, "dead") == 0) { +		*class = MFI_EVT_CLASS_DEAD; +		return (0); +	} +	val = strtol(arg, &cp, 0); +	if (*cp != '\0' || val < -128 || val > 127) { +		errno = EINVAL; +		return (-1); +	} +	*class = val; +	return (0); +} + +/* + * The timestamp is the number of seconds since 00:00 Jan 1, 2000.  If + * the bits in 24-31 are all set, then it is the number of seconds since + * boot. + */ +static const char * +format_timestamp(uint32_t timestamp) +{ +	static char buffer[32]; +	static time_t base; +	time_t t; +	struct tm tm; + +	if ((timestamp & 0xff000000) == 0xff000000) { +		snprintf(buffer, sizeof(buffer), "boot + %us", timestamp & +		    0x00ffffff); +		return (buffer); +	} + +	if (base == 0) { +		/* Compute 00:00 Jan 1, 2000 offset. */ +		bzero(&tm, sizeof(tm)); +		tm.tm_mday = 1; +		tm.tm_year = (2000 - 1900); +		base = mktime(&tm); +	} +	if (base == -1) { +		snprintf(buffer, sizeof(buffer), "%us", timestamp); +		return (buffer); +	} +	t = base + timestamp; +	strftime(buffer, sizeof(buffer), "%+", localtime(&t)); +	return (buffer); +} + +static const char * +format_locale(uint16_t locale) +{ +	static char buffer[8]; + +	switch (locale) { +	case MFI_EVT_LOCALE_LD: +		return ("VOLUME"); +	case MFI_EVT_LOCALE_PD: +		return ("DRIVE"); +	case MFI_EVT_LOCALE_ENCL: +		return ("ENCL"); +	case MFI_EVT_LOCALE_BBU: +		return ("BATTERY"); +	case MFI_EVT_LOCALE_SAS: +		return ("SAS"); +	case MFI_EVT_LOCALE_CTRL: +		return ("CTRL"); +	case MFI_EVT_LOCALE_CONFIG: +		return ("CONFIG"); +	case MFI_EVT_LOCALE_CLUSTER: +		return ("CLUSTER"); +	case MFI_EVT_LOCALE_ALL: +		return ("ALL"); +	default: +		snprintf(buffer, sizeof(buffer), "0x%04x", locale); +		return (buffer); +	} +} + +static const char * +format_class(int8_t class) +{ +	static char buffer[6]; + +	switch (class) { +	case MFI_EVT_CLASS_DEBUG: +		return ("debug"); +	case MFI_EVT_CLASS_PROGRESS: +		return ("progress"); +	case MFI_EVT_CLASS_INFO: +		return ("info"); +	case MFI_EVT_CLASS_WARNING: +		return ("WARN"); +	case MFI_EVT_CLASS_CRITICAL: +		return ("CRIT"); +	case MFI_EVT_CLASS_FATAL: +		return ("FATAL"); +	case MFI_EVT_CLASS_DEAD: +		return ("DEAD"); +	default: +		snprintf(buffer, sizeof(buffer), "%d", class); +		return (buffer); +	} +} + +/* Simulates %D from kernel printf(9). */ +static void +simple_hex(void *ptr, size_t length, const char *separator) +{ +	unsigned char *cp; +	u_int i; + +	if (length == 0) +		return; +	cp = ptr; +	printf("%02x", cp[0]); +	for (i = 1; i < length; i++) +		printf("%s%02x", separator, cp[i]); +} + +static const char * +pdrive_location(struct mfi_evt_pd *pd) +{ +	static char buffer[16]; + +	if (pd->enclosure_index == 0) +		snprintf(buffer, sizeof(buffer), "%02d(s%d)", pd->device_id, +		    pd->slot_number); +	else +		snprintf(buffer, sizeof(buffer), "%02d(e%d/s%d)", pd->device_id, +		    pd->enclosure_index, pd->slot_number); +	return (buffer); +} + +static const char * +volume_name(int fd, struct mfi_evt_ld *ld) +{ + +	return (mfi_volume_name(fd, ld->target_id)); +} + +/* Ripped from sys/dev/mfi/mfi.c. */ +static void +mfi_decode_evt(int fd, struct mfi_evt_detail *detail, int verbose) +{ + +	printf("%5d (%s/%s/%s) - ", detail->seq, format_timestamp(detail->time), +	    format_locale(detail->evt_class.members.locale), +	    format_class(detail->evt_class.members.evt_class)); +	switch (detail->arg_type) { +	case MR_EVT_ARGS_NONE: +		break; +	case MR_EVT_ARGS_CDB_SENSE: +		if (verbose) { +			printf("PD %s CDB ", +			    pdrive_location(&detail->args.cdb_sense.pd) +			    ); +			simple_hex(detail->args.cdb_sense.cdb, +			    detail->args.cdb_sense.cdb_len, ":"); +			printf(" Sense "); +			simple_hex(detail->args.cdb_sense.sense, +			    detail->args.cdb_sense.sense_len, ":"); +			printf(":\n "); +		} +		break; +	case MR_EVT_ARGS_LD: +		printf("VOL %s event: ", volume_name(fd, &detail->args.ld)); +		break; +	case MR_EVT_ARGS_LD_COUNT: +		printf("VOL %s", volume_name(fd, &detail->args.ld_count.ld)); +		if (verbose) { +			printf(" count %lld: ", +			    (long long)detail->args.ld_count.count); +		} +		printf(": "); +		break; +	case MR_EVT_ARGS_LD_LBA: +		printf("VOL %s", volume_name(fd, &detail->args.ld_lba.ld)); +		if (verbose) { +			printf(" lba %lld", +			    (long long)detail->args.ld_lba.lba); +		} +		printf(": "); +		break; +	case MR_EVT_ARGS_LD_OWNER: +		printf("VOL %s", volume_name(fd, &detail->args.ld_owner.ld)); +		if (verbose) { +			printf(" owner changed: prior %d, new %d", +			    detail->args.ld_owner.pre_owner, +			    detail->args.ld_owner.new_owner); +		} +		printf(": "); +		break; +	case MR_EVT_ARGS_LD_LBA_PD_LBA: +		printf("VOL %s", volume_name(fd, &detail->args.ld_lba_pd_lba.ld)); +		if (verbose) { +			printf(" lba %lld, physical drive PD %s lba %lld", +			    (long long)detail->args.ld_lba_pd_lba.ld_lba, +			    pdrive_location(&detail->args.ld_lba_pd_lba.pd), +			    (long long)detail->args.ld_lba_pd_lba.pd_lba); +		} +		printf(": "); +		break; +	case MR_EVT_ARGS_LD_PROG: +		printf("VOL %s", volume_name(fd, &detail->args.ld_prog.ld)); +		if (verbose) { +			printf(" progress %d%% in %ds", +			    detail->args.ld_prog.prog.progress/655, +			    detail->args.ld_prog.prog.elapsed_seconds); +		} +		printf(": "); +		break; +	case MR_EVT_ARGS_LD_STATE: +		printf("VOL %s", volume_name(fd, &detail->args.ld_state.ld)); +		if (verbose) { +			printf(" state prior %s new %s", +			    mfi_ldstate(detail->args.ld_state.prev_state), +			    mfi_ldstate(detail->args.ld_state.new_state)); +		} +		printf(": "); +		break; +	case MR_EVT_ARGS_LD_STRIP: +		printf("VOL %s", volume_name(fd, &detail->args.ld_strip.ld)); +		if (verbose) { +			printf(" strip %lld", +			    (long long)detail->args.ld_strip.strip); +		} +		printf(": "); +		break; +	case MR_EVT_ARGS_PD: +		if (verbose) { +			printf("PD %s event: ", +			    pdrive_location(&detail->args.pd)); +		} +		break; +	case MR_EVT_ARGS_PD_ERR: +		if (verbose) { +			printf("PD %s err %d: ", +			    pdrive_location(&detail->args.pd_err.pd), +			    detail->args.pd_err.err); +		} +		break; +	case MR_EVT_ARGS_PD_LBA: +		if (verbose) { +			printf("PD %s lba %lld: ", +			    pdrive_location(&detail->args.pd_lba.pd), +			    (long long)detail->args.pd_lba.lba); +		} +		break; +	case MR_EVT_ARGS_PD_LBA_LD: +		if (verbose) { +			printf("PD %s lba %lld VOL %s: ", +			    pdrive_location(&detail->args.pd_lba_ld.pd), +			    (long long)detail->args.pd_lba.lba, +			    volume_name(fd, &detail->args.pd_lba_ld.ld)); +		} +		break; +	case MR_EVT_ARGS_PD_PROG: +		if (verbose) { +			printf("PD %s progress %d%% seconds %ds: ", +			    pdrive_location(&detail->args.pd_prog.pd), +			    detail->args.pd_prog.prog.progress/655, +			    detail->args.pd_prog.prog.elapsed_seconds); +		} +		break; +	case MR_EVT_ARGS_PD_STATE: +		if (verbose) { +			printf("PD %s state prior %s new %s: ", +			    pdrive_location(&detail->args.pd_state.pd), +			    mfi_pdstate(detail->args.pd_state.prev_state), +			    mfi_pdstate(detail->args.pd_state.new_state)); +		} +		break; +	case MR_EVT_ARGS_PCI: +		if (verbose) { +			printf("PCI 0x%04x 0x%04x 0x%04x 0x%04x: ", +			    detail->args.pci.venderId, +			    detail->args.pci.deviceId, +			    detail->args.pci.subVenderId, +			    detail->args.pci.subDeviceId); +		} +		break; +	case MR_EVT_ARGS_RATE: +		if (verbose) { +			printf("Rebuild rate %d: ", detail->args.rate); +		} +		break; +	case MR_EVT_ARGS_TIME: +		if (verbose) { +			printf("Adapter time %s; %d seconds since power on: ", +			    format_timestamp(detail->args.time.rtc), +			    detail->args.time.elapsedSeconds); +		} +		break; +	case MR_EVT_ARGS_ECC: +		if (verbose) { +			printf("Adapter ECC %x,%x: %s: ", +			    detail->args.ecc.ecar, +			    detail->args.ecc.elog, +			    detail->args.ecc.str); +		} +		break; +	default: +		if (verbose) { +			printf("Type %d: ", detail->arg_type); +		} +		break; +	} +	printf("%s\n", detail->description); +} + +static int +show_events(int ac, char **av) +{ +	struct mfi_evt_log_state info; +	struct mfi_evt_list *list; +	union mfi_evt filter; +	bool first; +	long val; +	char *cp; +	ssize_t size; +	uint32_t seq, start, stop; +	uint16_t locale; +	uint8_t status; +	int ch, error, fd, num_events, verbose; +	u_int i; + +	fd = mfi_open(mfi_device, O_RDWR); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	if (mfi_event_get_info(fd, &info, NULL) < 0) { +		error = errno; +		warn("Failed to get event log info"); +		close(fd); +		return (error); +	} + +	/* Default settings. */ +	num_events = 15; +	filter.members.reserved = 0; +	filter.members.locale = MFI_EVT_LOCALE_ALL; +	filter.members.evt_class = MFI_EVT_CLASS_WARNING; +	start = info.boot_seq_num; +	stop = info.newest_seq_num; +	verbose = 0; + +	/* Parse any options. */ +	optind = 1; +	while ((ch = getopt(ac, av, "c:l:n:v")) != -1) { +		switch (ch) { +		case 'c': +			if (parse_class(optarg, &filter.members.evt_class) < 0) { +				error = errno; +				warn("Error parsing event class"); +				close(fd); +				return (error); +			} +			break; +		case 'l': +			if (parse_locale(optarg, &locale) < 0) { +				error = errno; +				warn("Error parsing event locale"); +				close(fd); +				return (error); +			} +			filter.members.locale = locale; +			break; +		case 'n': +			val = strtol(optarg, &cp, 0); +			if (*cp != '\0' || val <= 0) { +				warnx("Invalid event count"); +				close(fd); +				return (EINVAL); +			} +			num_events = val; +			break; +		case 'v': +			verbose = 1; +			break; +		case '?': +		default: +			close(fd); +			return (EINVAL); +		} +	} +	ac -= optind; +	av += optind; + +	/* Determine buffer size and validate it. */ +	size = sizeof(struct mfi_evt_list) + sizeof(struct mfi_evt_detail) * +	    (num_events - 1); +	if (size > getpagesize()) { +		warnx("Event count is too high"); +		close(fd); +		return (EINVAL); +	} + +	/* Handle optional start and stop sequence numbers. */ +	if (ac > 2) { +		warnx("show events: extra arguments"); +		close(fd); +		return (EINVAL); +	} +	if (ac > 0 && parse_seq(&info, av[0], &start) < 0) { +		error = errno; +		warn("Error parsing starting sequence number"); +		close(fd); +		return (error); +	} +	if (ac > 1 && parse_seq(&info, av[1], &stop) < 0) { +		error = errno; +		warn("Error parsing ending sequence number"); +		close(fd); +		return (error); +	} + +	list = malloc(size); +	if (list == NULL) { +		warnx("malloc failed"); +		close(fd); +		return (ENOMEM); +	} +	first = true; +	seq = start; +	for (;;) { +		if (mfi_get_events(fd, list, num_events, filter, seq, +		    &status) < 0) { +			error = errno; +			warn("Failed to fetch events"); +			free(list); +			close(fd); +			return (error); +		} +		if (status == MFI_STAT_NOT_FOUND) { +			break; +		} +		if (status != MFI_STAT_OK) { +			warnx("Error fetching events: %s", mfi_status(status)); +			free(list); +			close(fd); +			return (EIO); +		} + +		for (i = 0; i < list->count; i++) { +			/* +			 * If this event is newer than 'stop_seq' then +			 * break out of the loop.  Note that the log +			 * is a circular buffer so we have to handle +			 * the case that our stop point is earlier in +			 * the buffer than our start point. +			 */ +			if (list->event[i].seq > stop) { +				if (start <= stop) +					goto finish; +				else if (list->event[i].seq < start) +					goto finish; +			} +			mfi_decode_evt(fd, &list->event[i], verbose); +			first = false; +		} + +		/* +		 * XXX: If the event's seq # is the end of the buffer +		 * then this probably won't do the right thing.  We +		 * need to know the size of the buffer somehow. +		 */ +		seq = list->event[list->count - 1].seq + 1; + +	} +finish: +	if (first) +		warnx("No matching events found"); + +	free(list); +	close(fd); + +	return (0); +} +MFI_COMMAND(show, events, show_events); diff --git a/usr.sbin/mfiutil/mfi_flash.c b/usr.sbin/mfiutil/mfi_flash.c new file mode 100644 index 000000000000..4d1930af4941 --- /dev/null +++ b/usr.sbin/mfiutil/mfi_flash.c @@ -0,0 +1,209 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2008, 2009 Yahoo!, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * 3. The names of the authors may not 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/stat.h> +#include <err.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include "mfiutil.h" + +#define	FLASH_BUF_SIZE	(64 * 1024) + +static int +display_pending_firmware(int fd) +{ +	struct mfi_ctrl_info info; +	struct mfi_info_component header; +	int error; +	u_int i; + +	if (mfi_ctrl_get_info(fd, &info, NULL) < 0) { +		error = errno; +		warn("Failed to get controller info"); +		return (error); +	} + +	printf("%s Pending Firmware Images:\n", mfi_device); +	strcpy(header.name, "Name"); +	strcpy(header.version, "Version"); +	strcpy(header.build_date, "Date"); +	strcpy(header.build_time, "Time"); +	scan_firmware(&header); +	if (info.pending_image_component_count > 8) +		info.pending_image_component_count = 8; +	for (i = 0; i < info.pending_image_component_count; i++) +		scan_firmware(&info.pending_image_component[i]); +	display_firmware(&header, ""); +	for (i = 0; i < info.pending_image_component_count; i++) +		display_firmware(&info.pending_image_component[i], ""); + +	return (0); +} + +static void +mbox_store_word(uint8_t *mbox, uint32_t val) +{ + +	mbox[0] = val & 0xff; +	mbox[1] = val >> 8 & 0xff; +	mbox[2] = val >> 16 & 0xff; +	mbox[3] = val >> 24; +} + +static int +flash_adapter(int ac, char **av) +{ +	struct mfi_progress dummy; +	off_t offset; +	size_t nread; +	char *buf; +	struct stat sb; +	int error, fd, flash; +	uint8_t mbox[4], status; + +	if (ac != 2) { +		warnx("flash: Firmware file required"); +		return (EINVAL); +	} + +	flash = open(av[1], O_RDONLY); +	if (flash < 0) { +		error = errno; +		warn("flash: Failed to open %s", av[1]); +		return (error); +	} + +	buf = NULL; +	fd = -1; + +	if (fstat(flash, &sb) < 0) { +		error = errno; +		warn("fstat(%s)", av[1]); +		goto error; +	} +	if (sb.st_size % 1024 != 0 || sb.st_size > 0x7fffffff) { +		warnx("Invalid flash file size"); +		error = EINVAL; +		goto error; +	} + +	fd = mfi_open(mfi_device, O_RDWR); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		goto error; +	} + +	/* First, ask the firmware to allocate space for the flash file. */ +	mbox_store_word(mbox, sb.st_size); +	if (mfi_dcmd_command(fd, MFI_DCMD_FLASH_FW_OPEN, NULL, 0, mbox, 4, +	    &status) < 0) { +		error = errno; +		warn("Failed to allocate flash memory"); +		goto error; +	} +	if (status != MFI_STAT_OK) { +		warnx("Failed to allocate flash memory: %s", +		    mfi_status(status)); +		error = EIO; +		goto error; +	} + +	/* Upload the file 64k at a time. */ +	buf = malloc(FLASH_BUF_SIZE); +	if (buf == NULL) { +		warnx("malloc failed"); +		error = ENOMEM; +		goto error; +	} +	offset = 0; +	while (sb.st_size > 0) { +		nread = read(flash, buf, FLASH_BUF_SIZE); +		if (nread <= 0 || nread % 1024 != 0) { +			warnx("Bad read from flash file"); +			if (mfi_dcmd_command(fd, MFI_DCMD_FLASH_FW_CLOSE, +			    NULL, 0, NULL, 0, NULL) < 0) { +				warn("Failed to discard flash memory"); +			} +			error = ENXIO; +			goto error; +		} + +		mbox_store_word(mbox, offset); +		if (mfi_dcmd_command(fd, MFI_DCMD_FLASH_FW_DOWNLOAD, buf, nread, +		    mbox, 4, &status) < 0) { +			error = errno; +			warn("Failed to download firmware"); +			goto error; +		} +		if (status != MFI_STAT_OK) { +			warnx("Failed to download firmware: %s", +			    mfi_status(status)); +			mfi_dcmd_command(fd, MFI_DCMD_FLASH_FW_CLOSE, NULL, +			    0, NULL, 0, NULL); +			error = ENXIO; +			goto error; +		} +		sb.st_size -= nread; +		offset += nread; +	} + +	/* Kick off the flash. */ +	printf("WARNING: Firmware flash in progress, do not reboot machine... "); +	fflush(stdout); +	if (mfi_dcmd_command(fd, MFI_DCMD_FLASH_FW_FLASH, &dummy, sizeof(dummy), +	    NULL, 0, &status) < 0) { +		error = errno; +		printf("failed:\n\t%s\n", strerror(error)); +		goto error; +	} +	if (status != MFI_STAT_OK) { +		printf("failed:\n\t%s\n", mfi_status(status)); +		error = ENXIO; +		goto error; +	} +	printf("finished\n"); +	error = display_pending_firmware(fd); + +error: +	free(buf); +	if (fd >= 0) +		close(fd); +	close(flash); + +	return (error); +} +MFI_COMMAND(top, flash, flash_adapter); diff --git a/usr.sbin/mfiutil/mfi_foreign.c b/usr.sbin/mfiutil/mfi_foreign.c new file mode 100644 index 000000000000..81b9933937c6 --- /dev/null +++ b/usr.sbin/mfiutil/mfi_foreign.c @@ -0,0 +1,364 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2013 smh@freebsd.org + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include <sys/param.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <libutil.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include "mfiutil.h" + +MFI_TABLE(top, foreign); + +static int +foreign_clear(__unused int ac, __unused char **av) +{ +	int ch, error, fd; + +	fd = mfi_open(mfi_device, O_RDWR); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	printf( +	    "Are you sure you wish to clear ALL foreign configurations" +	    " on %s? [y/N] ", mfi_device); + +	ch = getchar(); +	if (ch != 'y' && ch != 'Y') { +		printf("\nAborting\n"); +		close(fd); +		return (0); +	} + +	if (mfi_dcmd_command(fd, MFI_DCMD_CFG_FOREIGN_CLEAR, NULL, 0, NULL, +	    0, NULL) < 0) { +		error = errno; +		warn("Failed to clear foreign configuration"); +		close(fd); +		return (error); +	} + +	printf("%s: Foreign configuration cleared\n", mfi_device); +	close(fd); +	return (0); +} +MFI_COMMAND(foreign, clear, foreign_clear); + +static int +foreign_scan(__unused int ac, __unused char **av) +{ +	struct mfi_foreign_scan_info info; +	int error, fd; + +	fd = mfi_open(mfi_device, O_RDONLY); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	if (mfi_dcmd_command(fd, MFI_DCMD_CFG_FOREIGN_SCAN, &info, +	    sizeof(info), NULL, 0, NULL) < 0) { +		error = errno; +		warn("Failed to scan foreign configuration"); +		close(fd); +		return (error); +	} + +	printf("%s: Found %d foreign configurations\n", mfi_device, +	       info.count); +	close(fd); +	return (0); +} +MFI_COMMAND(foreign, scan, foreign_scan); + +static int +foreign_show_cfg(int fd, uint32_t opcode, uint8_t cfgidx, int diagnostic) +{ +	struct mfi_config_data *config; +	char prefix[64]; +	int error; +	uint8_t mbox[4]; + +	bzero(mbox, sizeof(mbox)); +	mbox[0] = cfgidx; +	if (mfi_config_read_opcode(fd, opcode, &config, mbox, sizeof(mbox)) < 0) { +		error = errno; +		warn("Failed to get foreign config %d", error); +		close(fd); +		return (error); +	} + +	if (opcode == MFI_DCMD_CFG_FOREIGN_PREVIEW) +		sprintf(prefix, "Foreign configuration preview %d", cfgidx); +	else +		sprintf(prefix, "Foreign configuration %d", cfgidx); +	/* +	 * MegaCli uses DCMD opcodes: 0x03100200 (which fails) followed by +	 * 0x1a721880 which returns what looks to be drive / volume info +	 * but we have no real information on what these are or what they do +	 * so we're currently relying solely on the config returned above +	 */ +	if (diagnostic) +		dump_config(fd, config, prefix); +	else { +		char *ld_list; +		int i; + +		ld_list = (char *)(config->array); + +		printf("%s: %d arrays, %d volumes, %d spares\n", prefix, +		       config->array_count, config->log_drv_count, +		       config->spares_count); + + +		for (i = 0; i < config->array_count; i++) +			 ld_list += config->array_size; + +		for (i = 0; i < config->log_drv_count; i++) { +			const char *level; +			char size[6], stripe[5]; +			struct mfi_ld_config *ld; + +			ld = (struct mfi_ld_config *)ld_list; + +			format_stripe(stripe, sizeof(stripe), +			    ld->params.stripe_size); +			/* +			 * foreign configs don't seem to have a secondary raid level +			 * but, we can use span depth here as if a LD spans multiple +			 * arrays of disks (2 raid 1 sets for example), we will have an +			 * indication based on the spam depth. swb +			 */ +			level = mfi_raid_level(ld->params.primary_raid_level, +			    (ld->params.span_depth - 1)); + +			humanize_number(size, sizeof(size), ld->span[0].num_blocks * 512, +			    "", HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL); + +			printf(" ID%d ", i); +			printf("(%6s) %-8s |", +				size, level); +			printf("volume spans %d %s\n",	ld->params.span_depth, +							(ld->params.span_depth > 1) ? "arrays" : "array"); +			for (int j = 0; j < ld->params.span_depth; j++) { +				char *ar_list; +				struct mfi_array *ar; +				uint16_t device_id; + +				printf("      array %u @ ", ld->span[j].array_ref); +				humanize_number(size, sizeof(size), ld->span[j].num_blocks * 512, +				    "", HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL); + +				printf("(%6s)\n",size); +				ar_list = (char *)config->array + (ld->span[j].array_ref * config->array_size); + +				ar = (struct mfi_array *)ar_list; +				for (int k = 0; k < ar->num_drives; k++) { +					device_id = ar->pd[k].ref.v.device_id; +					if (device_id == 0xffff) +						printf("        drive MISSING\n"); +					else { +						printf("        drive %u %s\n", device_id, +						    mfi_pdstate(ar->pd[k].fw_state)); +					} +				} + +			} +			ld_list += config->log_drv_size; +		} +	} + +	free(config); + +	return (0); +} + +int +display_format(int ac, char **av, int diagnostic, mfi_dcmd_t display_cmd) +{ +	struct mfi_foreign_scan_info info; +	uint8_t i; +	int error, fd; + +	if (ac > 2) { +		warnx("foreign display: extra arguments"); +                return (EINVAL); +	} + +	fd = mfi_open(mfi_device, O_RDONLY); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	if (mfi_dcmd_command(fd, MFI_DCMD_CFG_FOREIGN_SCAN, &info, +	    sizeof(info), NULL, 0, NULL) < 0) { +		error = errno; +		warn("Failed to scan foreign configuration"); +		close(fd); +		return (error); +	} + +	if (info.count == 0) { +		warnx("foreign display: no foreign configs found"); +		close(fd); +		return (EINVAL); +	} + +	if (ac == 1) { +		for (i = 0; i < info.count; i++) { +			error = foreign_show_cfg(fd, +				display_cmd, i, diagnostic); +			if(error != 0) { +				close(fd); +				return (error); +			} +			if (i < info.count - 1) +				printf("\n"); +		} +	} else if (ac == 2) { +		error = foreign_show_cfg(fd, +			display_cmd, atoi(av[1]), diagnostic); +		if (error != 0) { +			close(fd); +			return (error); +		} +	} + +	close(fd); +	return (0); +} + +static int +foreign_display(int ac, char **av) +{ +	return(display_format(ac, av, 1/*diagnostic output*/, MFI_DCMD_CFG_FOREIGN_DISPLAY)); +} +MFI_COMMAND(foreign, diag, foreign_display); + +static int +foreign_preview(int ac, char **av) +{ +	return(display_format(ac, av, 1/*diagnostic output*/, MFI_DCMD_CFG_FOREIGN_PREVIEW)); +} +MFI_COMMAND(foreign, preview, foreign_preview); + +static int +foreign_import(int ac, char **av) +{ +	struct mfi_foreign_scan_info info; +	int ch, error, fd; +	uint8_t cfgidx; +	uint8_t mbox[4]; + +	if (ac > 2) { +		warnx("foreign preview: extra arguments"); +                return (EINVAL); +	} + +	fd = mfi_open(mfi_device, O_RDWR); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	if (mfi_dcmd_command(fd, MFI_DCMD_CFG_FOREIGN_SCAN, &info, +	    sizeof(info), NULL, 0, NULL) < 0) { +		error = errno; +		warn("Failed to scan foreign configuration"); +		close(fd); +		return (error); +	} + +	if (info.count == 0) { +		warnx("foreign import: no foreign configs found"); +		close(fd); +		return (EINVAL); +	} + +	if (ac == 1) { +		cfgidx = 0xff; +		printf("Are you sure you wish to import ALL foreign " +		       "configurations on %s? [y/N] ", mfi_device); +	} else { +		/* +		 * While this is docmmented for MegaCli this failed with +		 * exit code 0x03 on the test controller which was a Supermicro +		 * SMC2108 with firmware 12.12.0-0095 which is a LSI 2108 based +		 * controller. +		 */ +		cfgidx = atoi(av[1]); +		if (cfgidx >= info.count) { +			warnx("Invalid foreign config %d specified max is %d", +			      cfgidx, info.count - 1); +			close(fd); +			return (EINVAL); +		} +		printf("Are you sure you wish to import the foreign " +		       "configuration %d on %s? [y/N] ", cfgidx, mfi_device); +	} + +	ch = getchar(); +	if (ch != 'y' && ch != 'Y') { +		printf("\nAborting\n"); +		close(fd); +		return (0); +	} + +	bzero(mbox, sizeof(mbox)); +	mbox[0] = cfgidx; +	if (mfi_dcmd_command(fd, MFI_DCMD_CFG_FOREIGN_IMPORT, NULL, 0, mbox, +	    sizeof(mbox), NULL) < 0) { +		error = errno; +		warn("Failed to import foreign configuration"); +		close(fd); +		return (error); +	} + +	if (ac == 1) +		printf("%s: All foreign configurations imported\n", +		    mfi_device); +	else +		printf("%s: Foreign configuration %d imported\n", +		    mfi_device, cfgidx); +	close(fd); +	return (0); +} +MFI_COMMAND(foreign, import, foreign_import); diff --git a/usr.sbin/mfiutil/mfi_patrol.c b/usr.sbin/mfiutil/mfi_patrol.c new file mode 100644 index 000000000000..bbaea57ea020 --- /dev/null +++ b/usr.sbin/mfiutil/mfi_patrol.c @@ -0,0 +1,336 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2008, 2009 Yahoo!, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * 3. The names of the authors may not 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/types.h> +#include <sys/errno.h> +#include <err.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include "mfiutil.h" + +static char * +adapter_time(time_t now, uint32_t at_now, uint32_t at) +{ +	time_t t; + +	t = (now - at_now) + at; +	return (ctime(&t)); +} + +static void +mfi_get_time(int fd, uint32_t *at) +{ + +	if (mfi_dcmd_command(fd, MFI_DCMD_TIME_SECS_GET, at, sizeof(*at), NULL, +	    0, NULL) < 0) { +		warn("Couldn't fetch adapter time"); +		at = 0; +	} +} + +static int +patrol_get_props(int fd, struct mfi_pr_properties *prop) +{ +	int error; + +	if (mfi_dcmd_command(fd, MFI_DCMD_PR_GET_PROPERTIES, prop, +	    sizeof(*prop), NULL, 0, NULL) < 0) { +		error = errno; +		warn("Failed to get patrol read properties"); +		return (error); +	} +	return (0); +} + +static int +show_patrol(int ac __unused, char **av __unused) +{ +	struct mfi_pr_properties prop; +	struct mfi_pr_status status; +	struct mfi_pd_list *list; +	struct mfi_pd_info info; +	char label[24]; +	time_t now; +	uint32_t at; +	int error, fd; +	u_int i; + +	fd = mfi_open(mfi_device, O_RDWR); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	time(&now); +	mfi_get_time(fd, &at); +	error = patrol_get_props(fd, &prop); +	if (error) { +		close(fd); +		return (error); +	} +	printf("Operation Mode: "); +	switch (prop.op_mode) { +	case MFI_PR_OPMODE_AUTO: +		printf("auto\n"); +		break; +	case MFI_PR_OPMODE_MANUAL: +		printf("manual\n"); +		break; +	case MFI_PR_OPMODE_DISABLED: +		printf("disabled\n"); +		break; +	default: +		printf("??? (%02x)\n", prop.op_mode); +		break; +	} +	if (prop.op_mode == MFI_PR_OPMODE_AUTO) { +		if (at != 0 && prop.next_exec) +			printf("    Next Run Starts: %s", adapter_time(now, at, +			    prop.next_exec)); +		if (prop.exec_freq == 0xffffffff) +			printf("    Runs Execute Continuously\n"); +		else if (prop.exec_freq != 0) +			printf("    Runs Start Every %u seconds\n", +			    prop.exec_freq); +	} + +	if (mfi_dcmd_command(fd, MFI_DCMD_PR_GET_STATUS, &status, +	    sizeof(status), NULL, 0, NULL) < 0) { +		error = errno; +		warn("Failed to get patrol read properties"); +		close(fd); +		return (error); +	} +	printf("Runs Completed: %u\n", status.num_iteration); +	printf("Current State: "); +	switch (status.state) { +	case MFI_PR_STATE_STOPPED: +		printf("stopped\n"); +		break; +	case MFI_PR_STATE_READY: +		printf("ready\n"); +		break; +	case MFI_PR_STATE_ACTIVE: +		printf("active\n"); +		break; +	case MFI_PR_STATE_ABORTED: +		printf("aborted\n"); +		break; +	default: +		printf("??? (%02x)\n", status.state); +		break; +	} +	if (status.state == MFI_PR_STATE_ACTIVE) { +		if (mfi_pd_get_list(fd, &list, NULL) < 0) { +			error = errno; +			warn("Failed to get drive list"); +			close(fd); +			return (error); +		} + +		for (i = 0; i < list->count; i++) { +			if (list->addr[i].scsi_dev_type != 0) +				continue; + +			if (mfi_pd_get_info(fd, list->addr[i].device_id, &info, +			    NULL) < 0) { +				error = errno; +				warn("Failed to fetch info for drive %u", +				    list->addr[i].device_id); +				free(list); +				close(fd); +				return (error); +			} +			if (info.prog_info.active & MFI_PD_PROGRESS_PATROL) { +				snprintf(label, sizeof(label), "    Drive %s", +				    mfi_drive_name(NULL, +				    list->addr[i].device_id, +				    MFI_DNAME_DEVICE_ID|MFI_DNAME_HONOR_OPTS)); +				mfi_display_progress(label, +				    &info.prog_info.patrol); +			} +		} +		free(list); +	} + +	close(fd); + +	return (0); +} +MFI_COMMAND(show, patrol, show_patrol); + +static int +start_patrol(int ac __unused, char **av __unused) +{ +	int error, fd; + +	fd = mfi_open(mfi_device, O_RDWR); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	if (mfi_dcmd_command(fd, MFI_DCMD_PR_START, NULL, 0, NULL, 0, NULL) < +	    0) { +		error = errno; +		warn("Failed to start patrol read"); +		close(fd); +		return (error); +	} + +	close(fd); + +	return (0); +} +MFI_COMMAND(start, patrol, start_patrol); + +static int +stop_patrol(int ac __unused, char **av __unused) +{ +	int error, fd; + +	fd = mfi_open(mfi_device, O_RDWR); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	if (mfi_dcmd_command(fd, MFI_DCMD_PR_STOP, NULL, 0, NULL, 0, NULL) < +	    0) { +		error = errno; +		warn("Failed to stop patrol read"); +		close(fd); +		return (error); +	} + +	close(fd); + +	return (0); +} +MFI_COMMAND(stop, patrol, stop_patrol); + +static int +patrol_config(int ac, char **av) +{ +	struct mfi_pr_properties prop; +	long val; +	time_t now; +	int error, fd; +	uint32_t at, next_exec, exec_freq; +	char *cp; +	uint8_t op_mode; + +	exec_freq = 0;	/* GCC too stupid */ +	next_exec = 0; +	if (ac < 2) { +		warnx("patrol: command required"); +		return (EINVAL); +	} +	if (strcasecmp(av[1], "auto") == 0) { +		op_mode = MFI_PR_OPMODE_AUTO; +		if (ac > 2) { +			if (strcasecmp(av[2], "continuously") == 0) +				exec_freq = 0xffffffff; +			else { +				val = strtol(av[2], &cp, 0); +				if (*cp != '\0') { +					warnx("patrol: Invalid interval %s", +					    av[2]); +					return (EINVAL); +				} +				exec_freq = val; +			} +		} +		if (ac > 3) { +			val = strtol(av[3], &cp, 0); +			if (*cp != '\0' || val < 0) { +				warnx("patrol: Invalid start time %s", av[3]); +				return (EINVAL); +			} +			next_exec = val; +		} +	} else if (strcasecmp(av[1], "manual") == 0) +		op_mode = MFI_PR_OPMODE_MANUAL; +	else if (strcasecmp(av[1], "disable") == 0) +		op_mode = MFI_PR_OPMODE_DISABLED; +	else { +		warnx("patrol: Invalid command %s", av[1]); +		return (EINVAL); +	} + +	fd = mfi_open(mfi_device, O_RDWR); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	error = patrol_get_props(fd, &prop); +	if (error) { +		close(fd); +		return (error); +	} +	prop.op_mode = op_mode; +	if (op_mode == MFI_PR_OPMODE_AUTO) { +		if (ac > 2) +			prop.exec_freq = exec_freq; +		if (ac > 3) { +			time(&now); +			mfi_get_time(fd, &at); +			if (at == 0) { +				close(fd); +				return (ENXIO); +			} +			prop.next_exec = at + next_exec; +			printf("Starting next patrol read at %s", +			    adapter_time(now, at, prop.next_exec)); +		} +	} +	if (mfi_dcmd_command(fd, MFI_DCMD_PR_SET_PROPERTIES, &prop, +	    sizeof(prop), NULL, 0, NULL) < 0) { +		error = errno; +		warn("Failed to set patrol read properties"); +		close(fd); +		return (error); +	} + +	close(fd); + +	return (0); +} +MFI_COMMAND(top, patrol, patrol_config); diff --git a/usr.sbin/mfiutil/mfi_properties.c b/usr.sbin/mfiutil/mfi_properties.c new file mode 100644 index 000000000000..3b2eeca079f1 --- /dev/null +++ b/usr.sbin/mfiutil/mfi_properties.c @@ -0,0 +1,171 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2013 Yahoo!, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include <sys/errno.h> +#include <sys/ioctl.h> +#include <sys/param.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 "mfiutil.h" +#include <dev/mfi/mfi_ioctl.h> + +MFI_TABLE(top, ctrlprop); + +static int +mfi_ctrl_get_properties(int fd, struct mfi_ctrl_props *info) +{ + +	return (mfi_dcmd_command(fd, MFI_DCMD_CTRL_GET_PROPS, info, +	    sizeof(struct mfi_ctrl_props), NULL, 0, NULL)); +} + +static int +mfi_ctrl_set_properties(int fd, struct mfi_ctrl_props *info) +{ + +	return (mfi_dcmd_command(fd, MFI_DCMD_CTRL_SET_PROPS, info, +	    sizeof(struct mfi_ctrl_props), NULL, 0, NULL)); +} + +/* + * Acquire the controller properties data structure, modify the + * rebuild rate if requested and then return + */ +static int +mfi_ctrl_rebuild_rate(int ac, char **av) +{ +	int error, fd; +	struct mfi_ctrl_props ctrl_props; + +	if (ac > 2) { +		warn("mfi_ctrl_set_rebuild_rate"); +		return(-1); +	} + +	fd = mfi_open(mfi_device, O_RDWR); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	error = mfi_ctrl_get_properties(fd, &ctrl_props); +	if ( error < 0) { +		error = errno; +		warn("Failed to get controller properties"); +		close(fd); +		return (error); +	} +	/* +	 * User requested a change to the rebuild rate +	 */ +	if (ac > 1) { +		ctrl_props.rebuild_rate = atoi(av[ac - 1]); +		error = mfi_ctrl_set_properties(fd, &ctrl_props); +		if ( error < 0) { +			error = errno; +			warn("Failed to set controller properties"); +			close(fd); +			return (error); +		} + +		error = mfi_ctrl_get_properties(fd, &ctrl_props); +		if ( error < 0) { +			error = errno; +			warn("Failed to get controller properties"); +			close(fd); +			return (error); +		} +	} +	printf ("controller rebuild rate: %%%u \n", +		ctrl_props.rebuild_rate); +	return (0); +} +MFI_COMMAND(ctrlprop, rebuild, mfi_ctrl_rebuild_rate); + +static int +mfi_ctrl_alarm_enable(int ac, char **av) +{ +	int error, fd; +	struct mfi_ctrl_props ctrl_props; + +	if (ac > 2) { +		warn("mfi_ctrl_alarm_enable"); +		return(-1); +	} + +	fd = mfi_open(mfi_device, O_RDWR); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	error = mfi_ctrl_get_properties(fd, &ctrl_props); +	if ( error < 0) { +		error = errno; +		warn("Failed to get controller properties"); +		close(fd); +		return (error); +	} +	printf ("controller alarm was : %s\n", +		(ctrl_props.alarm_enable ? "enabled" : "disabled")); + +	if (ac > 1) { +		ctrl_props.alarm_enable = atoi(av[ac - 1]); +		error = mfi_ctrl_set_properties(fd, &ctrl_props); +		if ( error < 0) { +			error = errno; +			warn("Failed to set controller properties"); +			close(fd); +			return (error); +		} + +		error = mfi_ctrl_get_properties(fd, &ctrl_props); +		if ( error < 0) { +			error = errno; +			warn("Failed to get controller properties"); +			close(fd); +			return (error); +		} +	} +	printf ("controller alarm was : %s\n", +		(ctrl_props.alarm_enable ? "enabled" : "disabled")); +	return (0); +} + +MFI_COMMAND(ctrlprop, alarm, mfi_ctrl_alarm_enable); diff --git a/usr.sbin/mfiutil/mfi_show.c b/usr.sbin/mfiutil/mfi_show.c new file mode 100644 index 000000000000..2d413f2a46b4 --- /dev/null +++ b/usr.sbin/mfiutil/mfi_show.c @@ -0,0 +1,791 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2008, 2009 Yahoo!, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * 3. The names of the authors may not 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/types.h> +#include <sys/errno.h> +#include <err.h> +#include <fcntl.h> +#include <libutil.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include "mfiutil.h" + +static const char* foreign_state = " (FOREIGN)"; + +MFI_TABLE(top, show); + +void +format_stripe(char *buf, size_t buflen, uint8_t stripe) +{ + +	humanize_number(buf, buflen, (1 << stripe) * 512, "", HN_AUTOSCALE, +	    HN_B | HN_NOSPACE); +} + +static int +show_adapter(int ac, char **av __unused) +{ +	struct mfi_ctrl_info info; +	char stripe[5]; +	int error, fd, comma; + +	if (ac != 1) { +		warnx("show adapter: extra arguments"); +		return (EINVAL); +	} + +	fd = mfi_open(mfi_device, O_RDONLY); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	if (mfi_ctrl_get_info(fd, &info, NULL) < 0) { +		error = errno; +		warn("Failed to get controller info"); +		close(fd); +		return (error); +	} +	printf("%s Adapter:\n", mfi_device); +	printf("    Product Name: %.80s\n", info.product_name); +	printf("   Serial Number: %.32s\n", info.serial_number); +	if (info.package_version[0] != '\0') +		printf("        Firmware: %s\n", info.package_version); +	printf("     RAID Levels:"); +#ifdef DEBUG +	printf(" (%#x)", info.raid_levels); +#endif +	comma = 0; +	if (info.raid_levels & MFI_INFO_RAID_0) { +		printf(" JBOD, RAID0"); +		comma = 1; +	} +	if (info.raid_levels & MFI_INFO_RAID_1) { +		printf("%s RAID1", comma ? "," : ""); +		comma = 1; +	} +	if (info.raid_levels & MFI_INFO_RAID_5) { +		printf("%s RAID5", comma ? "," : ""); +		comma = 1; +	} +	if (info.raid_levels & MFI_INFO_RAID_1E) { +		printf("%s RAID1E", comma ? "," : ""); +		comma = 1; +	} +	if (info.raid_levels & MFI_INFO_RAID_6) { +		printf("%s RAID6", comma ? "," : ""); +		comma = 1; +	} +	if ((info.raid_levels & (MFI_INFO_RAID_0 | MFI_INFO_RAID_1)) == +	    (MFI_INFO_RAID_0 | MFI_INFO_RAID_1)) { +		printf("%s RAID10", comma ? "," : ""); +		comma = 1; +	} +	if ((info.raid_levels & (MFI_INFO_RAID_0 | MFI_INFO_RAID_5)) == +	    (MFI_INFO_RAID_0 | MFI_INFO_RAID_5)) { +		printf("%s RAID50", comma ? "," : ""); +		comma = 1; +	} +	printf("\n"); +	printf("  Battery Backup: "); +	if (info.hw_present & MFI_INFO_HW_BBU) +		printf("present\n"); +	else +		printf("not present\n"); +	if (info.hw_present & MFI_INFO_HW_NVRAM) +		printf("           NVRAM: %uK\n", info.nvram_size); +	printf("  Onboard Memory: %uM\n", info.memory_size); +	format_stripe(stripe, sizeof(stripe), info.stripe_sz_ops.min); +	printf("  Minimum Stripe: %s\n", stripe); +	format_stripe(stripe, sizeof(stripe), info.stripe_sz_ops.max); +	printf("  Maximum Stripe: %s\n", stripe); + +	close(fd); + +	return (0); +} +MFI_COMMAND(show, adapter, show_adapter); + +static int +show_battery(int ac, char **av __unused) +{ +	struct mfi_bbu_capacity_info cap; +	struct mfi_bbu_design_info design; +	struct mfi_bbu_properties props; +	struct mfi_bbu_status stat; +	uint8_t status; +	int comma, error, fd, show_capacity, show_props; +	char buf[32]; + +	if (ac != 1) { +		warnx("show battery: extra arguments"); +		return (EINVAL); +	} + +	fd = mfi_open(mfi_device, O_RDONLY); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	if (mfi_dcmd_command(fd, MFI_DCMD_BBU_GET_CAPACITY_INFO, &cap, +	    sizeof(cap), NULL, 0, &status) < 0) { +		error = errno; +		warn("Failed to get capacity info"); +		close(fd); +		return (error); +	} +	if (status == MFI_STAT_NO_HW_PRESENT) { +		printf("%s: No battery present\n", mfi_device); +		close(fd); +		return (0); +	} +	show_capacity = (status == MFI_STAT_OK); + +	if (mfi_dcmd_command(fd, MFI_DCMD_BBU_GET_DESIGN_INFO, &design, +	    sizeof(design), NULL, 0, NULL) < 0) { +		error = errno; +		warn("Failed to get design info"); +		close(fd); +		return (error); +	} + +	if (mfi_dcmd_command(fd, MFI_DCMD_BBU_GET_STATUS, &stat, sizeof(stat), +	    NULL, 0, NULL) < 0) { +		error = errno; +		warn("Failed to get status"); +		close(fd); +		return (error); +	} + +	if (mfi_bbu_get_props(fd, &props, &status) < 0) { +		error = errno; +		warn("Failed to get properties"); +		close(fd); +		return (error); +	} +	show_props = (status == MFI_STAT_OK); + +	printf("%s: Battery State:\n", mfi_device); +	printf("     Manufacture Date: %d/%d/%d\n", design.mfg_date >> 5 & 0x0f, +	    design.mfg_date & 0x1f, design.mfg_date >> 9 & 0xffff); +	printf("        Serial Number: %d\n", design.serial_number); +	printf("         Manufacturer: %s\n", design.mfg_name); +	printf("                Model: %s\n", design.device_name); +	printf("            Chemistry: %s\n", design.device_chemistry); +	printf("      Design Capacity: %d mAh\n", design.design_capacity); +	if (show_capacity) { +		printf(" Full Charge Capacity: %d mAh\n", +		    cap.full_charge_capacity); +		printf("     Current Capacity: %d mAh\n", +		    cap.remaining_capacity); +		printf("        Charge Cycles: %d\n", cap.cycle_count); +		printf("       Current Charge: %d%%\n", cap.relative_charge); +	} +	printf("       Design Voltage: %d mV\n", design.design_voltage); +	printf("      Current Voltage: %d mV\n", stat.voltage); +	printf("          Temperature: %d C\n", stat.temperature); +	if (show_props) { +		printf("     Autolearn period: "); +		mfi_autolearn_period(stdout, props.auto_learn_period); +		printf("\n"); +		if (props.auto_learn_mode != 0) +			snprintf(buf, sizeof(buf), "never"); +		else +			mfi_next_learn_time(props.next_learn_time, buf, +			    sizeof(buf)); +		printf("      Next learn time: %s\n", buf); +		printf(" Learn delay interval: %u hour%s\n", +		    props.learn_delay_interval, +		    props.learn_delay_interval != 1 ? "s" : ""); +		printf("       Autolearn mode: "); +		mfi_autolearn_mode(stdout, props.auto_learn_mode); +		printf("\n"); +		if (props.bbu_mode != 0) +			printf("             BBU Mode: %d\n", props.bbu_mode); +	} +	printf("               Status:"); +	comma = 0; +	if (stat.fw_status & MFI_BBU_STATE_PACK_MISSING) { +		printf(" PACK_MISSING"); +		comma = 1; +	} +	if (stat.fw_status & MFI_BBU_STATE_VOLTAGE_LOW) { +		printf("%s VOLTAGE_LOW", comma ? "," : ""); +		comma = 1; +	} +	if (stat.fw_status & MFI_BBU_STATE_TEMPERATURE_HIGH) { +		printf("%s TEMPERATURE_HIGH", comma ? "," : ""); +		comma = 1; +	} +	if (stat.fw_status & MFI_BBU_STATE_CHARGE_ACTIVE) { +		printf("%s CHARGING", comma ? "," : ""); +		comma = 1; +	} +	if (stat.fw_status & MFI_BBU_STATE_DISCHARGE_ACTIVE) { +		printf("%s DISCHARGING", comma ? "," : ""); +		comma = 1; +	} +	if (stat.fw_status & MFI_BBU_STATE_LEARN_CYC_REQ) { +		printf("%s LEARN_CYCLE_REQUESTED", comma ? "," : ""); +		comma = 1; +	} +	if (stat.fw_status & MFI_BBU_STATE_LEARN_CYC_ACTIVE) { +		printf("%s LEARN_CYCLE_ACTIVE", comma ? "," : ""); +		comma = 1; +	} +	if (stat.fw_status & MFI_BBU_STATE_LEARN_CYC_FAIL) { +		printf("%s LEARN_CYCLE_FAIL", comma ? "," : ""); +		comma = 1; +	} +	if (stat.fw_status & MFI_BBU_STATE_LEARN_CYC_TIMEOUT) { +		printf("%s LEARN_CYCLE_TIMEOUT", comma ? "," : ""); +		comma = 1; +	} +	if (stat.fw_status & MFI_BBU_STATE_I2C_ERR_DETECT) { +		printf("%s I2C_ERROR_DETECT", comma ? "," : ""); +		comma = 1; +	} + +	if (!comma) +		printf(" normal"); +	printf("\n"); +	switch (stat.battery_type) { +	case MFI_BBU_TYPE_BBU: +		printf("      State of Health: %s\n", +		    stat.detail.bbu.is_SOH_good ? "good" : "bad"); +		break; +	} + +	close(fd); + +	return (0); +} +MFI_COMMAND(show, battery, show_battery); + +void +print_ld(struct mfi_ld_info *info, int state_len) +{ +	struct mfi_ld_params *params = &info->ld_config.params; +	const char *level; +	char size[6], stripe[5]; + +	humanize_number(size, sizeof(size), info->size * 512, +	    "", HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL); +	format_stripe(stripe, sizeof(stripe), +	    info->ld_config.params.stripe_size); +	level = mfi_raid_level(params->primary_raid_level, +	    params->secondary_raid_level); +	if (state_len > 0) +		printf("(%6s) %-8s %6s %-*s", size, level, stripe, state_len, +		    mfi_ldstate(params->state)); +	else +		printf("(%s) %s %s %s", size, level, stripe, +		    mfi_ldstate(params->state)); +} + +void +print_pd(struct mfi_pd_info *info, int state_len) +{ +	const char *s; +	char buf[256]; + +	humanize_number(buf, 6, info->raw_size * 512, "", +	    HN_AUTOSCALE, HN_B | HN_NOSPACE |HN_DECIMAL); +	printf("(%6s) ", buf); +	if (info->state.ddf.v.pd_type.is_foreign) { +		sprintf(buf, "%s%s", mfi_pdstate(info->fw_state), foreign_state); +		s = buf; +	} else +		s = mfi_pdstate(info->fw_state); +	if (state_len > 0) +		printf("%-*s", state_len, s); +	else +		printf("%s",s); +	s = mfi_pd_inq_string(info); +	if (s != NULL) +		printf(" %s", s); +} + +static int +show_config(int ac, char **av __unused) +{ +	struct mfi_config_data *config; +	struct mfi_array *ar; +	struct mfi_ld_config *ld; +	struct mfi_spare *sp; +	struct mfi_ld_info linfo; +	struct mfi_pd_info pinfo; +	uint16_t device_id; +	char *p; +	int error, fd, i, j; + +	if (ac != 1) { +		warnx("show config: extra arguments"); +		return (EINVAL); +	} + +	fd = mfi_open(mfi_device, O_RDONLY); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	/* Get the config from the controller. */ +	if (mfi_config_read(fd, &config) < 0) { +		error = errno; +		warn("Failed to get config"); +		close(fd); +		return (error); +	} + +	/* Dump out the configuration. */ +	printf("%s Configuration: %d arrays, %d volumes, %d spares\n", +	    mfi_device, config->array_count, config->log_drv_count, +	    config->spares_count); +	p = (char *)config->array; + +	for (i = 0; i < config->array_count; i++) { +		ar = (struct mfi_array *)p; +		printf("    array %u of %u drives:\n", ar->array_ref, +		    ar->num_drives); +		for (j = 0; j < ar->num_drives; j++) { +			device_id = ar->pd[j].ref.v.device_id; +			printf("        drive %s ", mfi_drive_name(NULL, +			    device_id, +			    MFI_DNAME_DEVICE_ID|MFI_DNAME_HONOR_OPTS)); +			if (device_id != 0xffff) { +				if (mfi_pd_get_info(fd, device_id, &pinfo, +				    NULL) < 0) +					printf("%s", +					    mfi_pdstate(ar->pd[j].fw_state)); +				else +					print_pd(&pinfo, -1); +			} +			printf("\n"); +		} +		p += config->array_size; +	} + +	for (i = 0; i < config->log_drv_count; i++) { +		ld = (struct mfi_ld_config *)p; +		printf("    volume %s ", +		    mfi_volume_name(fd, ld->properties.ld.v.target_id)); +		if (mfi_ld_get_info(fd, ld->properties.ld.v.target_id, &linfo, +		    NULL) < 0) { +			printf("%s %s", +			    mfi_raid_level(ld->params.primary_raid_level, +				ld->params.secondary_raid_level), +			    mfi_ldstate(ld->params.state)); +		} else +			print_ld(&linfo, -1); +		if (ld->properties.name[0] != '\0') +			printf(" <%s>", ld->properties.name); +		printf(" spans:\n"); +		for (j = 0; j < ld->params.span_depth; j++) +			printf("        array %u\n", ld->span[j].array_ref); +		p += config->log_drv_size; +	} + +	for (i = 0; i < config->spares_count; i++) { +		sp = (struct mfi_spare *)p; +		printf("    %s spare %s ", +		    sp->spare_type & MFI_SPARE_DEDICATED ? "dedicated" : +		    "global", mfi_drive_name(NULL, sp->ref.v.device_id, +		    MFI_DNAME_DEVICE_ID|MFI_DNAME_HONOR_OPTS)); +		if (mfi_pd_get_info(fd, sp->ref.v.device_id, &pinfo, NULL) < 0) +			printf("%s", mfi_pdstate(MFI_PD_STATE_HOT_SPARE)); +		else +			print_pd(&pinfo, -1); +		if (sp->spare_type & MFI_SPARE_DEDICATED) { +			printf(" backs:\n"); +			for (j = 0; j < sp->array_count; j++) +				printf("        array %u\n", sp->array_ref[j]); +		} else +			printf("\n"); +		p += config->spares_size; +	} +	free(config); +	close(fd); + +	return (0); +} +MFI_COMMAND(show, config, show_config); + +static int +show_volumes(int ac, char **av __unused) +{ +	struct mfi_ld_list list; +	struct mfi_ld_info info; +	int error, fd; +	u_int i, len, state_len; + +	if (ac != 1) { +		warnx("show volumes: extra arguments"); +		return (EINVAL); +	} + +	fd = mfi_open(mfi_device, O_RDONLY); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	/* Get the logical drive list from the controller. */ +	if (mfi_ld_get_list(fd, &list, NULL) < 0) { +		error = errno; +		warn("Failed to get volume list"); +		close(fd); +		return (error); +	} + +	/* List the volumes. */ +	printf("%s Volumes:\n", mfi_device); +	state_len = strlen("State"); +	for (i = 0; i < list.ld_count; i++) { +		len = strlen(mfi_ldstate(list.ld_list[i].state)); +		if (len > state_len) +			state_len = len; +	} +	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("  Cache   Name\n"); +	for (i = 0; i < list.ld_count; i++) { +		if (mfi_ld_get_info(fd, list.ld_list[i].ld.v.target_id, &info, +		    NULL) < 0) { +			error = errno; +			warn("Failed to get info for volume %d", +			    list.ld_list[i].ld.v.target_id); +			close(fd); +			return (error); +		} +		printf("%6s ", +		    mfi_volume_name(fd, list.ld_list[i].ld.v.target_id)); +		print_ld(&info, state_len); +		switch (info.ld_config.properties.current_cache_policy & +		    (MR_LD_CACHE_ALLOW_WRITE_CACHE | +		    MR_LD_CACHE_ALLOW_READ_CACHE)) { +		case 0: +			printf(" Disabled"); +			break; +		case MR_LD_CACHE_ALLOW_READ_CACHE: +			printf(" Reads   "); +			break; +		case MR_LD_CACHE_ALLOW_WRITE_CACHE: +			printf(" Writes  "); +			break; +		case MR_LD_CACHE_ALLOW_WRITE_CACHE | +		    MR_LD_CACHE_ALLOW_READ_CACHE: +			printf(" Enabled "); +			break; +		} +		if (info.ld_config.properties.name[0] != '\0') +			printf(" <%s>", info.ld_config.properties.name); +		printf("\n"); +	} +	close(fd); + +	return (0); +} +MFI_COMMAND(show, volumes, show_volumes); + +static int +show_drives(int ac, char **av __unused) +{ +	struct mfi_pd_list *list; +	struct mfi_pd_info info; +	u_int i, len, state_len; +	int error, fd; + +	if (ac != 1) { +		warnx("show drives: extra arguments"); +		return (EINVAL); +	} + +	fd = mfi_open(mfi_device, O_RDONLY); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	list = NULL; +	if (mfi_pd_get_list(fd, &list, NULL) < 0) { +		error = errno; +		warn("Failed to get drive list"); +		goto error; +	} + +	/* Walk the list of drives to determine width of state column. */ +	state_len = 0; +	for (i = 0; i < list->count; i++) { +		if (list->addr[i].scsi_dev_type != 0) +			continue; + +		if (mfi_pd_get_info(fd, list->addr[i].device_id, &info, +		    NULL) < 0) { +			error = errno; +			warn("Failed to fetch info for drive %u", +			    list->addr[i].device_id); +			goto error; +		} +		len = strlen(mfi_pdstate(info.fw_state)); +		if (info.state.ddf.v.pd_type.is_foreign) +			len += strlen(foreign_state); +		if (len > state_len) +			state_len = len; +	} + +	/* List the drives. */ +	printf("%s Physical Drives:\n", mfi_device); +	for (i = 0; i < list->count; i++) { + +		/* Skip non-hard disks. */ +		if (list->addr[i].scsi_dev_type != 0) +			continue; + +		/* Fetch details for this drive. */ +		if (mfi_pd_get_info(fd, list->addr[i].device_id, &info, +		    NULL) < 0) { +			error = errno; +			warn("Failed to fetch info for drive %u", +			    list->addr[i].device_id); +			goto error; +		} + +		printf("%s ", mfi_drive_name(&info, list->addr[i].device_id, +		    MFI_DNAME_DEVICE_ID)); +		print_pd(&info, state_len); +		printf(" %s", mfi_drive_name(&info, list->addr[i].device_id, +		    MFI_DNAME_ES)); +		printf("\n"); +	} +	error = 0; +error: +	free(list); +	close(fd); + +	return (error); +} +MFI_COMMAND(show, drives, show_drives); + +static int +show_firmware(int ac, char **av __unused) +{ +	struct mfi_ctrl_info info; +	struct mfi_info_component header; +	int error, fd; +	u_int i; + +	if (ac != 1) { +		warnx("show firmware: extra arguments"); +		return (EINVAL); +	} + +	fd = mfi_open(mfi_device, O_RDONLY); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	if (mfi_ctrl_get_info(fd, &info, NULL) < 0) { +		error = errno; +		warn("Failed to get controller info"); +		close(fd); +		return (error); +	} + +	if (info.package_version[0] != '\0') +		printf("%s Firmware Package Version: %s\n", mfi_device, +		    info.package_version); +	printf("%s Firmware Images:\n", mfi_device); +	strcpy(header.name, "Name"); +	strcpy(header.version, "Version"); +	strcpy(header.build_date, "Date"); +	strcpy(header.build_time, "Time"); +	scan_firmware(&header); +	if (info.image_component_count > 8) +		info.image_component_count = 8; +	for (i = 0; i < info.image_component_count; i++) +		scan_firmware(&info.image_component[i]); +	if (info.pending_image_component_count > 8) +		info.pending_image_component_count = 8; +	for (i = 0; i < info.pending_image_component_count; i++) +		scan_firmware(&info.pending_image_component[i]); +	display_firmware(&header, "Status"); +	for (i = 0; i < info.image_component_count; i++) +		display_firmware(&info.image_component[i], "active"); +	for (i = 0; i < info.pending_image_component_count; i++) +		display_firmware(&info.pending_image_component[i], "pending"); + +	close(fd); + +	return (0); +} +MFI_COMMAND(show, firmware, show_firmware); + +static int +show_progress(int ac, char **av __unused) +{ +	struct mfi_ld_list llist; +	struct mfi_pd_list *plist; +	struct mfi_ld_info linfo; +	struct mfi_pd_info pinfo; +	int busy, error, fd; +	u_int i; +	uint16_t device_id; +	uint8_t target_id; + +	if (ac != 1) { +		warnx("show progress: extra arguments"); +		return (EINVAL); +	} + +	fd = mfi_open(mfi_device, O_RDONLY); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	if (mfi_ld_get_list(fd, &llist, NULL) < 0) { +		error = errno; +		warn("Failed to get volume list"); +		close(fd); +		return (error); +	} +	if (mfi_pd_get_list(fd, &plist, NULL) < 0) { +		error = errno; +		warn("Failed to get drive list"); +		close(fd); +		return (error); +	} + +	busy = 0; +	for (i = 0; i < llist.ld_count; i++) { +		target_id = llist.ld_list[i].ld.v.target_id; +		if (mfi_ld_get_info(fd, target_id, &linfo, NULL) < 0) { +			error = errno; +			warn("Failed to get info for volume %s", +			    mfi_volume_name(fd, target_id)); +			free(plist); +			close(fd); +			return (error); +		} +		if (linfo.progress.active & MFI_LD_PROGRESS_CC) { +			printf("volume %s ", mfi_volume_name(fd, target_id)); +			mfi_display_progress("Consistency Check", +			    &linfo.progress.cc); +			busy = 1; +		} +		if (linfo.progress.active & MFI_LD_PROGRESS_BGI) { +			printf("volume %s ", mfi_volume_name(fd, target_id)); +			mfi_display_progress("Background Init", +			    &linfo.progress.bgi); +			busy = 1; +		} +		if (linfo.progress.active & MFI_LD_PROGRESS_FGI) { +			printf("volume %s ", mfi_volume_name(fd, target_id)); +			mfi_display_progress("Foreground Init", +			    &linfo.progress.fgi); +			busy = 1; +		} +		if (linfo.progress.active & MFI_LD_PROGRESS_RECON) { +			printf("volume %s ", mfi_volume_name(fd, target_id)); +			mfi_display_progress("Reconstruction", +			    &linfo.progress.recon); +			busy = 1; +		} +	} + +	for (i = 0; i < plist->count; i++) { +		if (plist->addr[i].scsi_dev_type != 0) +			continue; + +		device_id = plist->addr[i].device_id; +		if (mfi_pd_get_info(fd, device_id, &pinfo, NULL) < 0) { +			error = errno; +			warn("Failed to fetch info for drive %u", device_id); +			free(plist); +			close(fd); +			return (error); +		} + +		if (pinfo.prog_info.active & MFI_PD_PROGRESS_REBUILD) { +			printf("drive %s ", mfi_drive_name(NULL, device_id, +			    MFI_DNAME_DEVICE_ID|MFI_DNAME_HONOR_OPTS)); +			mfi_display_progress("Rebuild", &pinfo.prog_info.rbld); +			busy = 1; +		} +		if (pinfo.prog_info.active & MFI_PD_PROGRESS_PATROL) { +			printf("drive %s ", mfi_drive_name(NULL, device_id, +			    MFI_DNAME_DEVICE_ID|MFI_DNAME_HONOR_OPTS)); +			mfi_display_progress("Patrol Read", +			    &pinfo.prog_info.patrol); +			busy = 1; +		} +		if (pinfo.prog_info.active & MFI_PD_PROGRESS_CLEAR) { +			printf("drive %s ", mfi_drive_name(NULL, device_id, +			    MFI_DNAME_DEVICE_ID|MFI_DNAME_HONOR_OPTS)); +			mfi_display_progress("Clear", &pinfo.prog_info.clear); +			busy = 1; +		} +	} + +	free(plist); +	close(fd); + +	if (!busy) +		printf("No activity in progress for adapter %s\n", +		    mfi_device); + +	return (0); +} +MFI_COMMAND(show, progress, show_progress); + +static int +show_foreign(int ac, char **av) +{ +	return(display_format(ac, av, 0/*normal display*/, MFI_DCMD_CFG_FOREIGN_DISPLAY)); +} +MFI_COMMAND(show, foreign, show_foreign); diff --git a/usr.sbin/mfiutil/mfi_volume.c b/usr.sbin/mfiutil/mfi_volume.c new file mode 100644 index 000000000000..8cf2c95b5927 --- /dev/null +++ b/usr.sbin/mfiutil/mfi_volume.c @@ -0,0 +1,498 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2008, 2009 Yahoo!, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * 3. The names of the authors may not 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/types.h> +#include <sys/errno.h> +#include <err.h> +#include <fcntl.h> +#include <libutil.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include "mfiutil.h" + +MFI_TABLE(top, volume); + +const char * +mfi_ldstate(enum mfi_ld_state state) +{ +	static char buf[16]; + +	switch (state) { +	case MFI_LD_STATE_OFFLINE: +		return ("OFFLINE"); +	case MFI_LD_STATE_PARTIALLY_DEGRADED: +		return ("PARTIALLY DEGRADED"); +	case MFI_LD_STATE_DEGRADED: +		return ("DEGRADED"); +	case MFI_LD_STATE_OPTIMAL: +		return ("OPTIMAL"); +	default: +		sprintf(buf, "LSTATE 0x%02x", state); +		return (buf); +	} +} + +void +mbox_store_ldref(uint8_t *mbox, union mfi_ld_ref *ref) +{ + +	mbox[0] = ref->v.target_id; +	mbox[1] = ref->v.reserved; +	mbox[2] = ref->v.seq & 0xff; +	mbox[3] = ref->v.seq >> 8; +} + +int +mfi_ld_get_list(int fd, struct mfi_ld_list *list, uint8_t *statusp) +{ + +	return (mfi_dcmd_command(fd, MFI_DCMD_LD_GET_LIST, list, +		sizeof(struct mfi_ld_list), NULL, 0, statusp)); +} + +int +mfi_ld_get_info(int fd, uint8_t target_id, struct mfi_ld_info *info, +    uint8_t *statusp) +{ +	uint8_t mbox[1]; + +	mbox[0] = target_id; +	return (mfi_dcmd_command(fd, MFI_DCMD_LD_GET_INFO, info, +	    sizeof(struct mfi_ld_info), mbox, 1, statusp)); +} + +static int +mfi_ld_get_props(int fd, uint8_t target_id, struct mfi_ld_props *props) +{ +	uint8_t mbox[1]; + +	mbox[0] = target_id; +	return (mfi_dcmd_command(fd, MFI_DCMD_LD_GET_PROP, props, +	    sizeof(struct mfi_ld_props), mbox, 1, NULL)); +} + +static int +mfi_ld_set_props(int fd, struct mfi_ld_props *props) +{ +	uint8_t mbox[4]; + +	mbox_store_ldref(mbox, &props->ld); +	return (mfi_dcmd_command(fd, MFI_DCMD_LD_SET_PROP, props, +	    sizeof(struct mfi_ld_props), mbox, 4, NULL)); +} + +static int +update_cache_policy(int fd, struct mfi_ld_props *old, struct mfi_ld_props *new) +{ +	int error; +	uint8_t changes, policy; + +	if (old->default_cache_policy == new->default_cache_policy && +	    old->disk_cache_policy == new->disk_cache_policy) +		return (0); +	policy = new->default_cache_policy; +	changes = policy ^ old->default_cache_policy; +	if (changes & MR_LD_CACHE_ALLOW_WRITE_CACHE) +		printf("%s caching of I/O writes\n", +		    policy & MR_LD_CACHE_ALLOW_WRITE_CACHE ? "Enabling" : +		    "Disabling"); +	if (changes & MR_LD_CACHE_ALLOW_READ_CACHE) +		printf("%s caching of I/O reads\n", +		    policy & MR_LD_CACHE_ALLOW_READ_CACHE ? "Enabling" : +		    "Disabling"); +	if (changes & MR_LD_CACHE_WRITE_BACK) +		printf("Setting write cache policy to %s\n", +		    policy & MR_LD_CACHE_WRITE_BACK ? "write-back" : +		    "write-through"); +	if (changes & (MR_LD_CACHE_READ_AHEAD | MR_LD_CACHE_READ_ADAPTIVE)) +		printf("Setting read ahead policy to %s\n", +		    policy & MR_LD_CACHE_READ_AHEAD ? +		    (policy & MR_LD_CACHE_READ_ADAPTIVE ? +		    "adaptive" : "always") : "none"); +	if (changes & MR_LD_CACHE_WRITE_CACHE_BAD_BBU) +		printf("%s write caching with bad BBU\n", +		    policy & MR_LD_CACHE_WRITE_CACHE_BAD_BBU ? "Enabling" : +		    "Disabling"); +	if (old->disk_cache_policy != new->disk_cache_policy) { +		switch (new->disk_cache_policy) { +		case MR_PD_CACHE_ENABLE: +			printf("Enabling write-cache on physical drives\n"); +			break; +		case MR_PD_CACHE_DISABLE: +			printf("Disabling write-cache on physical drives\n"); +			break; +		case MR_PD_CACHE_UNCHANGED: +			printf("Using default write-cache setting on physical drives\n"); +			break; +		} +	} + +	if (mfi_ld_set_props(fd, new) < 0) { +		error = errno; +		warn("Failed to set volume properties"); +		return (error); +	} +	return (0); +} + +static void +stage_cache_setting(struct mfi_ld_props *props, uint8_t new_policy, +    uint8_t mask) +{ + +	props->default_cache_policy &= ~mask; +	props->default_cache_policy |= new_policy; +} + +/* + * Parse a single cache directive modifying the passed in policy. + * Returns -1 on a parse error and the number of arguments consumed + * on success. + */ +static int +process_cache_command(int ac, char **av, struct mfi_ld_props *props) +{ +	uint8_t policy; + +	/* I/O cache settings. */ +	if (strcmp(av[0], "all") == 0 || strcmp(av[0], "enable") == 0) { +		stage_cache_setting(props, MR_LD_CACHE_ALLOW_READ_CACHE | +		    MR_LD_CACHE_ALLOW_WRITE_CACHE, +		    MR_LD_CACHE_ALLOW_READ_CACHE | +		    MR_LD_CACHE_ALLOW_WRITE_CACHE); +		return (1); +	} +	if (strcmp(av[0], "none") == 0 || strcmp(av[0], "disable") == 0) { +		stage_cache_setting(props, 0, MR_LD_CACHE_ALLOW_READ_CACHE | +		    MR_LD_CACHE_ALLOW_WRITE_CACHE); +		return (1); +	} +	if (strcmp(av[0], "reads") == 0) { + 		stage_cache_setting(props, MR_LD_CACHE_ALLOW_READ_CACHE, +		    MR_LD_CACHE_ALLOW_READ_CACHE | +		    MR_LD_CACHE_ALLOW_WRITE_CACHE); +		return (1); +	} +	if (strcmp(av[0], "writes") == 0) { +		stage_cache_setting(props, MR_LD_CACHE_ALLOW_WRITE_CACHE, +		    MR_LD_CACHE_ALLOW_READ_CACHE | +		    MR_LD_CACHE_ALLOW_WRITE_CACHE); +		return (1); +	} + +	/* Write cache behavior. */ +	if (strcmp(av[0], "write-back") == 0) { +		stage_cache_setting(props, MR_LD_CACHE_WRITE_BACK, +		    MR_LD_CACHE_WRITE_BACK); +		return (1); +	} +	if (strcmp(av[0], "write-through") == 0) { +		stage_cache_setting(props, 0, MR_LD_CACHE_WRITE_BACK); +		return (1); +	} +	if (strcmp(av[0], "bad-bbu-write-cache") == 0) { +		if (ac < 2) { +			warnx("cache: bad BBU setting required"); +			return (-1); +		} +		if (strcmp(av[1], "enable") == 0) +			policy = MR_LD_CACHE_WRITE_CACHE_BAD_BBU; +		else if (strcmp(av[1], "disable") == 0) +			policy = 0; +		else { +			warnx("cache: invalid bad BBU setting"); +			return (-1); +		} +		stage_cache_setting(props, policy, +		    MR_LD_CACHE_WRITE_CACHE_BAD_BBU); +		return (2); +	} + +	/* Read cache behavior. */ +	if (strcmp(av[0], "read-ahead") == 0) { +		if (ac < 2) { +			warnx("cache: read-ahead setting required"); +			return (-1); +		} +		if (strcmp(av[1], "none") == 0) +			policy = 0; +		else if (strcmp(av[1], "always") == 0) +			policy = MR_LD_CACHE_READ_AHEAD; +		else if (strcmp(av[1], "adaptive") == 0) +			policy = MR_LD_CACHE_READ_AHEAD | +			    MR_LD_CACHE_READ_ADAPTIVE; +		else { +			warnx("cache: invalid read-ahead setting"); +			return (-1); +		} +		stage_cache_setting(props, policy, MR_LD_CACHE_READ_AHEAD | +			    MR_LD_CACHE_READ_ADAPTIVE); +		return (2); +	} + +	/* Drive write-cache behavior. */ +	if (strcmp(av[0], "write-cache") == 0) { +		if (ac < 2) { +			warnx("cache: write-cache setting required"); +			return (-1); +		} +		if (strcmp(av[1], "enable") == 0) +			props->disk_cache_policy = MR_PD_CACHE_ENABLE; +		else if (strcmp(av[1], "disable") == 0) +			props->disk_cache_policy = MR_PD_CACHE_DISABLE; +		else if (strcmp(av[1], "default") == 0) +			props->disk_cache_policy = MR_PD_CACHE_UNCHANGED; +		else { +			warnx("cache: invalid write-cache setting"); +			return (-1); +		} +		return (2); +	} + +	warnx("cache: Invalid command"); +	return (-1); +} + +static int +volume_cache(int ac, char **av) +{ +	struct mfi_ld_props props, new; +	int error, fd, consumed; +	uint8_t target_id; + +	if (ac < 2) { +		warnx("cache: volume required"); +		return (EINVAL); +	} + +	fd = mfi_open(mfi_device, O_RDWR); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	if (mfi_lookup_volume(fd, av[1], &target_id) < 0) { +		error = errno; +		warn("Invalid volume: %s", av[1]); +		close(fd); +		return (error); +	} + +	if (mfi_ld_get_props(fd, target_id, &props) < 0) { +		error = errno; +		warn("Failed to fetch volume properties"); +		close(fd); +		return (error); +	} + +	if (ac == 2) { +		printf("%s volume %s cache settings:\n", mfi_device, +		    mfi_volume_name(fd, target_id)); +		printf("             I/O caching: "); +		switch (props.default_cache_policy & +		    (MR_LD_CACHE_ALLOW_WRITE_CACHE | +		    MR_LD_CACHE_ALLOW_READ_CACHE)) { +		case 0: +			printf("disabled\n"); +			break; +		case MR_LD_CACHE_ALLOW_WRITE_CACHE: +			printf("writes\n"); +			break; +		case MR_LD_CACHE_ALLOW_READ_CACHE: +			printf("reads\n"); +			break; +		case MR_LD_CACHE_ALLOW_WRITE_CACHE | +		    MR_LD_CACHE_ALLOW_READ_CACHE: +			printf("writes and reads\n"); +			break; +		} +		printf("           write caching: %s\n", +		    props.default_cache_policy & MR_LD_CACHE_WRITE_BACK ? +		    "write-back" : "write-through"); +		printf("write cache with bad BBU: %s\n", +		    props.default_cache_policy & +		    MR_LD_CACHE_WRITE_CACHE_BAD_BBU ? "enabled" : "disabled"); +		printf("              read ahead: %s\n", +		    props.default_cache_policy & MR_LD_CACHE_READ_AHEAD ? +		    (props.default_cache_policy & MR_LD_CACHE_READ_ADAPTIVE ? +		    "adaptive" : "always") : "none"); +		printf("       drive write cache: "); +		switch (props.disk_cache_policy) { +		case MR_PD_CACHE_UNCHANGED: +			printf("default\n"); +			break; +		case MR_PD_CACHE_ENABLE: +			printf("enabled\n"); +			break; +		case MR_PD_CACHE_DISABLE: +			printf("disabled\n"); +			break; +		default: +			printf("??? %d\n", props.disk_cache_policy); +			break; +		} +		if (props.default_cache_policy != props.current_cache_policy) +			printf( +	"Cache disabled due to dead battery or ongoing battery relearn\n"); +		error = 0; +	} else { +		new = props; +		av += 2; +		ac -= 2; +		while (ac > 0) { +			consumed = process_cache_command(ac, av, &new); +			if (consumed < 0) { +				close(fd); +				return (EINVAL); +			} +			av += consumed; +			ac -= consumed; +		} +		error = update_cache_policy(fd, &props, &new); +	} +	close(fd); + +	return (error); +} +MFI_COMMAND(top, cache, volume_cache); + +static int +volume_name(int ac, char **av) +{ +	struct mfi_ld_props props; +	int error, fd; +	uint8_t target_id; + +	if (ac != 3) { +		warnx("name: volume and name required"); +		return (EINVAL); +	} + +	if (strlen(av[2]) >= sizeof(props.name)) { +		warnx("name: new name is too long"); +		return (ENOSPC); +	} + +	fd = mfi_open(mfi_device, O_RDWR); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	if (mfi_lookup_volume(fd, av[1], &target_id) < 0) { +		error = errno; +		warn("Invalid volume: %s", av[1]); +		close(fd); +		return (error); +	} + +	if (mfi_ld_get_props(fd, target_id, &props) < 0) { +		error = errno; +		warn("Failed to fetch volume properties"); +		close(fd); +		return (error); +	} + +	printf("%s volume %s name changed from \"%s\" to \"%s\"\n", mfi_device, +	    mfi_volume_name(fd, target_id), props.name, av[2]); +	bzero(props.name, sizeof(props.name)); +	strcpy(props.name, av[2]); +	if (mfi_ld_set_props(fd, &props) < 0) { +		error = errno; +		warn("Failed to set volume properties"); +		close(fd); +		return (error); +	} + +	close(fd); + +	return (0); +} +MFI_COMMAND(top, name, volume_name); + +static int +volume_progress(int ac, char **av) +{ +	struct mfi_ld_info info; +	int error, fd; +	uint8_t target_id; + +	if (ac != 2) { +		warnx("volume progress: %s", ac > 2 ? "extra arguments" : +		    "volume required"); +		return (EINVAL); +	} + +	fd = mfi_open(mfi_device, O_RDONLY); +	if (fd < 0) { +		error = errno; +		warn("mfi_open"); +		return (error); +	} + +	if (mfi_lookup_volume(fd, av[1], &target_id) < 0) { +		error = errno; +		warn("Invalid volume: %s", av[1]); +		close(fd); +		return (error); +	} + +	/* Get the info for this drive. */ +	if (mfi_ld_get_info(fd, target_id, &info, NULL) < 0) { +		error = errno; +		warn("Failed to fetch info for volume %s", +		    mfi_volume_name(fd, target_id)); +		close(fd); +		return (error); +	} + +	/* Display any of the active events. */ +	if (info.progress.active & MFI_LD_PROGRESS_CC) +		mfi_display_progress("Consistency Check", &info.progress.cc); +	if (info.progress.active & MFI_LD_PROGRESS_BGI) +		mfi_display_progress("Background Init", &info.progress.bgi); +	if (info.progress.active & MFI_LD_PROGRESS_FGI) +		mfi_display_progress("Foreground Init", &info.progress.fgi); +	if (info.progress.active & MFI_LD_PROGRESS_RECON) +		mfi_display_progress("Reconstruction", &info.progress.recon); +	if ((info.progress.active & (MFI_LD_PROGRESS_CC | MFI_LD_PROGRESS_BGI | +	    MFI_LD_PROGRESS_FGI | MFI_LD_PROGRESS_RECON)) == 0) +		printf("No activity in progress for volume %s.\n", +		    mfi_volume_name(fd, target_id)); +	close(fd); + +	return (0); +} +MFI_COMMAND(volume, progress, volume_progress); diff --git a/usr.sbin/mfiutil/mfiutil.8 b/usr.sbin/mfiutil/mfiutil.8 new file mode 100644 index 000000000000..9f8cacf7f16d --- /dev/null +++ b/usr.sbin/mfiutil/mfiutil.8 @@ -0,0 +1,825 @@ +.\" Copyright (c) 2008, 2009 Yahoo!, Inc. +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\"    notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\"    notice, this list of conditions and the following disclaimer in the +.\"    documentation and/or other materials provided with the distribution. +.\" 3. The names of the authors may not 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 September 2, 2011 +.Dt MFIUTIL 8 +.Os +.Sh NAME +.Nm mfiutil , +.Nm mrsasutil +.Nd Utility for managing LSI MegaRAID SAS controllers +.Sh SYNOPSIS +.Nm +.Cm version +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm show adapter +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm show battery +.Nm +.Op Fl d +.Op Fl e +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm show config +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm show drives +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm show events +.Op Fl c Ar class +.Op Fl l Ar locale +.Op Fl n Ar count +.Op Fl v +.Op Ar start Op Ar stop +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm show firmware +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm show foreign Op Ar volume +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm show logstate +.Nm +.Op Fl d +.Op Fl e +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm show patrol +.Nm +.Op Fl d +.Op Fl e +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm show progress +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm show volumes +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm fail Ar drive +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm good Ar drive +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm rebuild Ar drive +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm syspd Ar drive +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm drive progress Ar drive +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm drive clear Ar drive Brq "start | stop" +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm start rebuild Ar drive +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm abort rebuild Ar drive +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm locate Ar drive Brq "on | off" +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm cache Ar volume Op Ar setting Oo Ar value Oc Op ... +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm name Ar volume Ar name +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm volume progress Ar volume +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm clear +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm create Ar type +.Op Fl v +.Op Fl s Ar stripe_size +.Ar drive Ns Op \&, Ns Ar drive Ns Op ",..." +.Op Ar drive Ns Op \&, Ns Ar drive Ns Op ",..." +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm delete Ar volume +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm add Ar drive Op Ar volume +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm remove Ar drive +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm start patrol +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm stop patrol +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm patrol Ar command Op Ar interval Op Ar start +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm foreign scan +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm foreign clear Op Ar config +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm foreign diag Op Ar config +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm foreign preview Op Ar config +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm foreign import Op Ar config +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm flash Ar file +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm start learn +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm bbu Ar setting Ar value +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm ctrlprop Ar rebuild Op Ar rate +.Nm +.Op Fl D Ar device +.Op Fl t Ar type +.Op Fl u Ar unit +.Cm ctrlprop Ar alarm Op Ar 0/1 +.Sh DESCRIPTION +The +.Nm +utility can be used to display or modify various parameters on LSI +MegaRAID SAS RAID 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 three global options are supported: +.Bl -tag -width indent +.It Fl D Ar device +.Ar device +specifies the device node of the controller to use. +.Pa /dev/ +will be added to the device node if needed. +If no device node is specified, +then device will be made of the type and device. +.It Fl t Ar type +.Ar type +specifies the type of the controller to work with either +.Xr mfi 4 +or +.Xr mrsas 4 . +If no type is specified, +then the name of the invoked tool used to derive the type. +.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 +Various commands accept either or both of the two options: +.Bl -tag -width indent +.It Fl d +Print numeric device IDs as drive identifier. +This is the default. +Useful in combination with +.Fl e +to print both, numeric device IDs and enclosure:slot information. +.It Fl e +Print drive identifiers in enclosure:slot form. +See next paragraph on format details in context of input rather than +output. +.El +.Pp +Drives may be specified in two forms. +First, +a drive may be identified by its device ID. +The device ID for configured drives can be found in +.Cm show config . +Second, +a drive may be identified by its location as +.Sm off +.Op E Ar xx Ns \&: +.Li S Ns Ar yy +.Sm on +where +.Ar xx +is the enclosure +and +.Ar yy +is the slot for each drive as displayed in +.Cm show drives . +.Pp +Volumes may be specified in two forms. +First, +a volume may be identified by its target ID. +Second, +on the volume may be specified by the corresponding +.Em mfidX +device, +such as +.Em mfid0 . +.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. +The fifth group of commands are used to manage controller-wide operations. +.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 battery +Displays information about the battery from the battery backup unit. +.It Cm show config +Displays the volume and drive configuration for the controller. +Each array is listed along with the physical drives the array is built from. +Each volume is listed along with the arrays 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 Xo Cm show events +.Op Fl c Ar class +.Op Fl l Ar locale +.Op Fl n Ar count +.Op Fl v +.Op Ar start Op Ar stop +.Xc +Display entries from the controller's event log. +The controller maintains a circular buffer of events. +Each event is tagged with a class and locale. +.Pp +The +.Ar class +parameter limits the output to entries at the specified class or higher. +The default class is +.Dq warn . +The available classes from lowest priority to highest are: +.Bl -tag -width indent +.It Cm debug +Debug messages. +.It Cm progress +Periodic progress updates for long-running operations such as background +initializations, array rebuilds, or patrol reads. +.It Cm info +Informational messages such as drive insertions and volume creations. +.It Cm warn +Indicates that some component may be close to failing. +.It Cm crit +A component has failed, but no data is lost. +For example, a volume becoming degraded due to a drive failure. +.It Cm fatal +A component has failed resulting in data loss. +.It Cm dead +The controller itself has died. +.El +.Pp +The +.Ar locale +parameter limits the output to entries for the specified part of the controller. +The default locale is +.Dq all . +The available locales are +.Dq volume , +.Dq drive , +.Dq enclosure , +.Dq battery , +.Dq sas , +.Dq controller , +.Dq config , +.Dq cluster , +and +.Dq all . +.Pp +The +.Ar count +parameter is a debugging aid that specifies the number of events to fetch from +the controller for each low-level request. +The default is 15 events. +.Pp +By default, matching event log entries from the previous shutdown up to the +present are displayed. +This range can be adjusted via the +.Ar start +and +.Ar stop +parameters. +Each of these parameters can either be specified as a log entry number or as +one of the following aliases: +.Bl -tag -width indent +.It Cm newest +The newest entry in the event log. +.It Cm oldest +The oldest entry in the event log. +.It Cm clear +The first entry since the event log was cleared. +.It Cm shutdown +The entry in the event log corresponding to the last time the controller was +cleanly shut down. +.It Cm boot +The entry in the event log corresponding to the most recent boot. +.El +.It Cm show firmware +Lists all of the firmware images present on the controller. +.It Cm show foreign +Displays detected foreign configurations on disks for importation or removal. +.It Cm show logstate +Display the various sequence numbers associated with the event log. +.It Cm show patrol +Display the status of the controller's patrol read operation. +.It Cm show progress +Report the current progress and estimated completion time for active +operations on all volumes and drives. +.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 failed. +.Ar Drive +must be an online drive that is part of an array. +.It Cm good Ar drive +Mark +.Ar drive +as an unconfigured good drive. +.Ar Drive +must not be part of an existing array. +.It Cm rebuild Ar drive +Mark a failed +.Ar drive +that is still part of an array as a good drive suitable for a rebuild. +The firmware should kick off an array rebuild on its own if a failed drive +is marked as a rebuild drive. +.It Cm syspd Ar drive +Present the drive to the host operating system as a disk SYSPD block device in +the format /dev/mfisyspdX. +Clear this flag with +.Cm good +.Ar drive +.It Cm drive progress Ar drive +Report the current progress and estimated completion time of drive operations +such as rebuilds or patrol reads. +.It Cm drive clear Ar drive Brq "start | stop" +Start or stop the writing of all 0x00 characters to a drive. +.It Cm start rebuild Ar drive +Manually start a rebuild on +.Ar drive . +.It Cm abort rebuild Ar drive +Abort an in-progress rebuild operation on +.Ar drive . +It can be resumed with the +.Cm start rebuild +command. +.It Cm locate Ar drive Brq "on | off" +Change the state of the external LED associated with +.Ar drive . +.El +.Pp +The logical volume management commands include: +.Bl -tag -width indent +.It Cm cache Ar volume Op Ar setting Oo Ar value Oc Op ... +If no +.Ar setting +arguments are supplied, then the current cache policy for +.Ar volume +is displayed; +otherwise, +the cache policy for +.Ar volume +is modified. +One or more +.Ar setting +arguments may be given. +Some settings take an additional +.Ar value +argument as noted below. +The valid settings are: +.Bl -tag -width indent +.It Cm enable +Enable caching for both read and write I/O operations. +.It Cm disable +Disable caching for both read and write I/O operations. +.It Cm reads +Enable caching only for read I/O operations. +.It Cm writes +Enable caching only for write I/O operations. +.It Cm write-back +Use write-back policy for cached writes. +.It Cm write-through +Use write-through policy for cached writes. +.It Cm read-ahead Ar value +Set the read ahead policy for cached reads. +The +.Ar value +argument can be set to either +.Dq none , +.Dq adaptive , +or +.Dq always . +.It Cm bad-bbu-write-cache Ar value +Control the behavior of I/O write caching if the battery is dead or +missing. +The +.Ar value +argument can be set to either +.Dq disable +or +.Dq enable . +In general this setting should be left disabled to avoid data loss when +the system loses power. +.It Cm write-cache Ar value +Control the write caches on the physical drives backing +.Ar volume . +The +.Ar value +argument can be set to either +.Dq disable , +.Dq enable , +or +.Dq default . +.Pp +In general this setting should be left disabled to avoid data loss when the +physical drives lose power. +The battery backup of the RAID controller does not save data in the write +caches of the physical drives. +.El +.It Cm name Ar volume Ar name +Sets the name of +.Ar volume +to +.Ar name . +.It Cm volume progress Ar volume +Report the current progress and estimated completion time of volume operations +such as consistency checks and initializations. +.El +.Pp +The configuration commands include: +.Bl -tag -width indent +.It Cm clear +Delete the entire configuration including all volumes, arrays, and spares. +.It Xo Cm create Ar type +.Op Fl v +.Op Fl s Ar stripe_size +.Ar drive Ns Op \&, Ns Ar drive Ns Op ",..." +.Op 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 jbod +Creates a RAID0 volume for each drive specified. +Each drive must be specified as a separate argument. +.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 raid5 +Creates one RAID5 volume spanning the drives listed in the single drive list. +.It Cm raid6 +Creates one RAID6 volume spanning the drives listed in the single drive list. +.It Cm raid10 +Creates one RAID10 volume spanning multiple RAID1 arrays. +The drives for each RAID1 array are specified as a single drive list. +.It Cm raid50 +Creates one RAID50 volume spanning multiple RAID5 arrays. +The drives for each RAID5 array are specified as a single drive list. +.It Cm raid60 +Creates one RAID60 volume spanning multiple RAID6 arrays. +The drives for each RAID6 array are specified as a single drive list. +.It Cm concat +Creates a single volume by concatenating all of the drives in the single drive +list. +.El +.Pp +.Sy Note : +Not all volume types are supported by all controllers. +.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 arrays and +arrays 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. +Valid values are 512 through 1M, though the MFI firmware may reject some +values. +.It Cm delete Ar volume +Delete the volume +.Ar volume . +.It Cm add Ar drive Op Ar volume +Mark +.Ar drive +as a hot spare. +.Ar Drive +must be in the unconfigured good state. +If +.Ar volume +is specified, +then the hot spare will be dedicated to arrays backing that volume. +Otherwise, +.Ar drive +will be used as a global hot spare backing all arrays for this controller. +Note that +.Ar drive +must be as large as the smallest drive in all of the arrays it is going to +back. +.It Cm remove Ar drive +Remove the hot spare +.Ar drive +from service. +It will be placed in the unconfigured good state. +.El +.Pp +The controller management commands include: +.Bl -tag -width indent +.It Cm patrol Ar command Op Ar interval Op Ar start +Set the patrol read operation mode. +The +.Ar command +argument can be one of the following values: +.Bl -tag -width indent +.It Cm disable +Disable patrol reads. +.It Cm auto +Enable periodic patrol reads initiated by the firmware. +The optional +.Ar interval +argument specifies the interval in seconds between patrol reads. +If patrol reads should be run continuously, +then +.Ar interval +should consist of the word +.Dq continuously . +The optional +.Ar start +argument specifies a non-negative, relative start time for the next patrol read. +If an interval or start time is not specified, +then the existing setting will be used. +.It Cm manual +Enable manual patrol reads that are only initiated by the user. +.El +.It Cm start patrol +Start a patrol read operation. +.It Cm stop patrol +Stop a currently running patrol read operation. +.It Cm foreign scan +Scan for foreign configurations and display the number found. +The +.Ar config +argument for the commands below takes the form of a number from 0 to the total +configurations found. +.It Cm foreign clear Op config +Clear the specified foreign +.Ar config +or all if no +.Ar config +argument is provided. +.It Cm foreign diag Op config +Display a diagnostic display of the specified foreign +.Ar config +or all if no +.Ar config +argument is provided. +.It Cm foreign preview Op config +Preview the specified foreign +.Ar config +after import or all if no +.Ar config +argument is provided. +.It Cm foreign import Op config +Import the specified foreign +.Ar config +or all if no +.Ar config +argument is provided. +.It Cm flash Ar file +Updates the flash on the controller with the firmware stored in +.Ar file . +A reboot is required for the new firmware to take effect. +.It Cm start learn +Start a battery relearn. +Note that this seems to always result in the battery being completely drained, +regardless of the BBU mode. +In particular, the controller write cache will be disabled during the relearn +even if transparent learning mode is enabled. +.It Cm bbu Ar setting Ar value +Update battery backup unit (BBU) properties related to battery relearning. +The following settings are configurable: +.Bl -tag -width indent +.It Cm learn-delay +Add a delay to the next scheduled battery relearn event. +This setting is given in hours and must lie in the range of 0 to 255. +.It Cm autolearn-mode +Enable or disable automatic periodic battery relearning. +The setting may be set to +.Dq enable +or +.Dq disable +to respectively enable or disable the relearn cycle. +Alternatively, a mode of 0, 1 or 2 may be given. +Mode 0 enables periodic relearning, mode 1 disables it, and mode 2 disables +it and logs a warning to the event log when it detects that a battery relearn +should be performed. +.It Cm bbu-mode +Set the BBU's mode of operation. +This setting is not supported by all BBUs. +Where it is supported, the possible values are the integers between 1 and 5 +inclusive. +Modes 1, 2 and 3 enable a transparent learn cycle, whereas modes 4 and 5 do not. +The BBU's data retention time is greater when transparent learning is not used. +.El +.It Cm ctrlprop Ar rebuild Op Ar rate +With no arguments display the rate of rebuild (percentage)a for volumes. +With an integer argument (0-100), set that value as the new rebuild rate for volumes. +.It Cm ctrlprop Ar alarm Op Ar 0/1 +With no arguments display the current alarm enable/disable status. +With a 0, disable alarms. +With a 1, enable alarms. +.El +.Sh EXAMPLES +Configure the cache for volume mfid0 to cache only writes: +.Pp +.Dl Nm Cm cache mfid0 writes +.Dl Nm Cm cache mfid0 write-back +.Pp +Create a RAID5 array spanning the first four disks in the second enclosure: +.Pp +.Dl Nm Cm create raid5 e1:s0,e1:s1,e1:s2,e1:s4 +.Pp +Configure the first three disks on a controller as JBOD: +.Pp +.Dl Nm Cm create jbod 0 1 2 +.Pp +Create a RAID10 volume that spans two arrays each of which contains two disks +from two different enclosures: +.Pp +.Dl Nm Cm create raid10 e1:s0,e1:s1 e2:s0,e2:s1 +.Pp +Add drive with the device ID of 4 as a global hot spare: +.Pp +.Dl Nm Cm add 4 +.Pp +Add the drive in slot 2 in the main chassis as a hot spare for volume mfid0: +.Pp +.Dl Nm Cm add s2 mfid0 +.Pp +Reconfigure a disk as a SYSPD block device with no RAID +.Pp +.Dl Nm Cm syspd 0 +.Pp +Configure the adapter to run periodic patrol reads once a week with the first +patrol read starting in 5 minutes: +.Pp +.Dl Nm Cm patrol auto 604800 300 +.Pp +Display the second detected foreign configuration: +.Pp +.Dl Nm Cm show foreign 1 +.Pp +Set the current rebuild rate for volumes to 40%: +.Dl Nm Cm ctrlprop rebuild 40 +.Sh SEE ALSO +.Xr mfi 4 , +.Xr mrsas 4 +.Sh HISTORY +The +.Nm +utility first appeared in +.Fx 8.0 . diff --git a/usr.sbin/mfiutil/mfiutil.c b/usr.sbin/mfiutil/mfiutil.c new file mode 100644 index 000000000000..c6df9908b634 --- /dev/null +++ b/usr.sbin/mfiutil/mfiutil.c @@ -0,0 +1,218 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2008, 2009 Yahoo!, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * 3. The names of the authors may not 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <paths.h> +#include "mfiutil.h" + +SET_DECLARE(MFI_DATASET(top), struct mfiutil_command); + +MFI_TABLE(top, start); +MFI_TABLE(top, stop); +MFI_TABLE(top, abort); + +char *mfi_device = NULL; +u_int mfi_opts; +static int fw_name_width, fw_version_width, fw_date_width, fw_time_width; + +static void +usage(void) +{ + +	fprintf(stderr, "usage: %s [-de] [-D device] [-u unit] [-t type] <command> ...\n\n", getprogname()); +	fprintf(stderr, "Commands include:\n"); +	fprintf(stderr, "    version\n"); +	fprintf(stderr, "    show adapter              - display controller information\n"); +	fprintf(stderr, "    show battery              - display battery 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 firmware             - list firmware images\n"); +	fprintf(stderr, "    show foreign              - display detected foreign volumes\n"); +	fprintf(stderr, "    show logstate             - display event log sequence numbers\n"); +	fprintf(stderr, "    show volumes              - list logical volumes\n"); +	fprintf(stderr, "    show patrol               - display patrol read status\n"); +	fprintf(stderr, "    show progress             - display status of active operations\n"); +	fprintf(stderr, "    fail <drive>              - fail a physical drive\n"); +	fprintf(stderr, "    good <drive>              - set a failed/SYSPD drive as UNCONFIGURED\n"); +	fprintf(stderr, "    rebuild <drive>           - mark failed drive ready for rebuild\n"); +	fprintf(stderr, "    syspd <drive>             - set drive into use as SYSPD JBOD\n"); +	fprintf(stderr, "    drive progress <drive>    - display status of active operations\n"); +	fprintf(stderr, "    drive clear <drive> <start|stop> - clear a drive with all 0x00\n"); +	fprintf(stderr, "    start rebuild <drive>\n"); +	fprintf(stderr, "    abort rebuild <drive>\n"); +	fprintf(stderr, "    locate <drive> <on|off>   - toggle drive LED\n"); +	fprintf(stderr, "    cache <volume> [command [setting]]\n"); +	fprintf(stderr, "    name <volume> <name>\n"); +	fprintf(stderr, "    volume progress <volume>  - display status of active operations\n"); +	fprintf(stderr, "    clear                     - clear volume configuration\n"); +	fprintf(stderr, "    create <type> [-v] [-s stripe_size] <drive>[,<drive>[,...]] [<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"); +	fprintf(stderr, "    patrol <disable|auto|manual> [interval [start]]\n"); +	fprintf(stderr, "    start patrol              - start a patrol read\n"); +	fprintf(stderr, "    stop patrol               - stop a patrol read\n"); +	fprintf(stderr, "    foreign scan              - scan for foreign configurations\n"); +	fprintf(stderr, "    foreign clear [volume]    - clear foreign configurations (default all)\n"); +	fprintf(stderr, "    foreign diag [volume]     - diagnostic display foreign configurations (default all)\n"); +	fprintf(stderr, "    foreign preview [volume]  - preview foreign configurations (default all)\n"); +	fprintf(stderr, "    foreign import [volume]   - import foreign configurations (default all)\n"); +	fprintf(stderr, "    flash <firmware>\n"); +	fprintf(stderr, "    start learn               - start a BBU relearn\n"); +	fprintf(stderr, "    bbu <setting> <value>     - set BBU properties\n"); +	fprintf(stderr, "    ctrlprop rebuild [rate]   - get/set the volume rebuild rate\n"); +	fprintf(stderr, "    ctrlprop alarm [0/1]      - enable/disable controller alarms\n"); +#ifdef DEBUG +	fprintf(stderr, "    debug                     - debug 'show config'\n"); +	fprintf(stderr, "    dump                      - display 'saved' config\n"); +#endif +	exit(1); +} + +static int +version(int ac __unused, char **av __unused) +{ + +	printf("mfiutil version 1.0.15"); +#ifdef DEBUG +	printf(" (DEBUG)"); +#endif +	printf("\n"); +	return (0); +} +MFI_COMMAND(top, version, version); + +int +main(int ac, char **av) +{ +	struct mfiutil_command **cmd; +	int ch, mfi_unit; +	const char *pn, *mfi_type; +	char *temp; + +	mfi_unit = 0; + +	pn = getprogname(); + +	if (strcmp(pn, "mrsasutil") == 0) +		mfi_type = MRSAS_TYPE; +	else +		mfi_type = MFI_TYPE; + +	while ((ch = getopt(ac, av, "D:det:u:")) != -1) { +		switch (ch) { +		case 'D': +			mfi_device = optarg; +			break; +		case 'd': +			mfi_opts |= MFI_DNAME_DEVICE_ID; +			break; +		case 'e': +			mfi_opts |= MFI_DNAME_ES; +			break; +		case 'u': +			mfi_unit = atoi(optarg); +			break; +		case 't': +			mfi_type = optarg; +			break; +		case '?': +			usage(); +		} +	} + +	if (mfi_device == NULL) { +		if (asprintf(&mfi_device, "%s%s%d", _PATH_DEV, mfi_type, +		    mfi_unit) < 0) +			errx(1, "Can't allocate memory for device name\n"); +	} else { +		if (strncmp(mfi_device, _PATH_DEV, strlen(_PATH_DEV)) != 0) { +			if (asprintf(&temp, "%s%s%d", _PATH_DEV, mfi_type, +			    mfi_unit) < 0) +				errx(1, "Can't allocate memory for device name\n"); +			mfi_device = temp; +		} +	} + +	av += optind; +	ac -= optind; + +	/* getopt() eats av[0], so we can't use mfi_table_handler() directly. */ +	if (ac == 0) +		usage(); + +	SET_FOREACH(cmd, MFI_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); +} + +void +scan_firmware(struct mfi_info_component *comp) +{ +	int len; + +	len = strlen(comp->name); +	if (fw_name_width < len) +		fw_name_width = len; +	len = strlen(comp->version); +	if (fw_version_width < len) +		fw_version_width = len; +	len = strlen(comp->build_date); +	if (fw_date_width < len) +		fw_date_width = len; +	len = strlen(comp->build_time); +	if (fw_time_width < len) +		fw_time_width = len; +} + +void +display_firmware(struct mfi_info_component *comp, const char *tag) +{ + +	printf("%-*s  %-*s  %-*s  %-*s  %s\n", fw_name_width, comp->name, +	    fw_version_width, comp->version, fw_date_width, comp->build_date, +	    fw_time_width, comp->build_time, tag); +} diff --git a/usr.sbin/mfiutil/mfiutil.h b/usr.sbin/mfiutil/mfiutil.h new file mode 100644 index 000000000000..86b03998163c --- /dev/null +++ b/usr.sbin/mfiutil/mfiutil.h @@ -0,0 +1,188 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2008, 2009 Yahoo!, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * 3. The names of the authors may not 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 __MFIUTIL_H__ +#define	__MFIUTIL_H__ + +#include <sys/cdefs.h> +#include <sys/linker_set.h> + +#include <dev/mfi/mfireg.h> + +#define MRSAS_TYPE	"mrsas" +#define MFI_TYPE	"mfi" + +/* 4.x compat */ +#ifndef SET_DECLARE + +/* <sys/cdefs.h> */ +#define	__used +#define	__section(x)	__attribute__((__section__(x))) + +/* <sys/linker_set.h> */ +#undef __MAKE_SET +#undef DATA_SET + +#define __MAKE_SET(set, sym)						\ +	static void const * const __set_##set##_sym_##sym 		\ +	__section("set_" #set) __used = &sym + +#define DATA_SET(set, sym)	__MAKE_SET(set, sym) + +#define SET_DECLARE(set, ptype)						\ +	extern ptype *__CONCAT(__start_set_,set);			\ +	extern ptype *__CONCAT(__stop_set_,set) + +#define SET_BEGIN(set)							\ +	(&__CONCAT(__start_set_,set)) +#define SET_LIMIT(set)							\ +	(&__CONCAT(__stop_set_,set)) + +#define	SET_FOREACH(pvar, set)						\ +	for (pvar = SET_BEGIN(set); pvar < SET_LIMIT(set); pvar++) + +int	humanize_number(char *_buf, size_t _len, int64_t _number, +	    const char *_suffix, int _scale, int _flags); + +/* humanize_number(3) */ +#define HN_DECIMAL		0x01 +#define HN_NOSPACE		0x02 +#define HN_B			0x04 +#define HN_DIVISOR_1000		0x08 + +#define HN_GETSCALE		0x10 +#define HN_AUTOSCALE		0x20 + +#endif + +/* Constants for DDF RAID levels. */ +#define	DDF_RAID0		0x00 +#define	DDF_RAID1		0x01 +#define	DDF_RAID3		0x03 +#define	DDF_RAID5		0x05 +#define	DDF_RAID6		0x06 +#define	DDF_RAID1E		0x11 +#define	DDF_JBOD		0x0f +#define	DDF_CONCAT		0x1f +#define	DDF_RAID5E		0x15 +#define	DDF_RAID5EE		0x25 + +struct mfiutil_command { +	const char *name; +	int (*handler)(int ac, char **av); +}; + +#define	MFI_DATASET(name)	mfiutil_ ## name ## _table + +#define	MFI_COMMAND(set, name, function)				\ +	static struct mfiutil_command function ## _mfiutil_command =	\ +	{ #name, function };						\ +	DATA_SET(MFI_DATASET(set), function ## _mfiutil_command) + +#define	MFI_TABLE(set, name)						\ +	SET_DECLARE(MFI_DATASET(name), struct mfiutil_command);		\ +									\ +	static int							\ +	mfiutil_ ## name ## _table_handler(int ac, char **av)		\ +	{								\ +		return (mfi_table_handler(SET_BEGIN(MFI_DATASET(name)), \ +		    SET_LIMIT(MFI_DATASET(name)), ac, av));		\ +	}								\ +	MFI_COMMAND(set, name, mfiutil_ ## name ## _table_handler) + +/* Drive name printing options */ +#define	MFI_DNAME_ES		0x0001	/* E%u:S%u */ +#define	MFI_DNAME_DEVICE_ID	0x0002	/* %u */ +#define	MFI_DNAME_HONOR_OPTS	0x8000	/* Allow cmd line to override default */ + +extern char *mfi_device; + +extern u_int mfi_opts; + +/* We currently don't know the full details of the following struct */ +struct mfi_foreign_scan_cfg { +        char data[24]; +}; + +struct mfi_foreign_scan_info { +        uint32_t count; /* Number of foreign configs found */ +        struct mfi_foreign_scan_cfg cfgs[8]; +}; + +void	mbox_store_ldref(uint8_t *mbox, union mfi_ld_ref *ref); +void	mbox_store_pdref(uint8_t *mbox, union mfi_pd_ref *ref); +void	mfi_display_progress(const char *label, struct mfi_progress *prog); +int	mfi_table_handler(struct mfiutil_command **start, +    struct mfiutil_command **end, int ac, char **av); +const char *mfi_raid_level(uint8_t primary_level, uint8_t secondary_level); +const char *mfi_ldstate(enum mfi_ld_state state); +const char *mfi_pdstate(enum mfi_pd_state state); +const char *mfi_pd_inq_string(struct mfi_pd_info *info); +const char *mfi_volume_name(int fd, uint8_t target_id); +int	mfi_volume_busy(int fd, uint8_t target_id); +int	mfi_config_read(int fd, struct mfi_config_data **configp); +int	mfi_config_read_opcode(int fd, uint32_t opcode, +    struct mfi_config_data **configp, uint8_t *mbox, size_t mboxlen); +int	mfi_lookup_drive(int fd, char *drive, uint16_t *device_id); +int	mfi_lookup_volume(int fd, const char *name, uint8_t *target_id); +int	mfi_dcmd_command(int fd, uint32_t opcode, void *buf, size_t bufsize, +    uint8_t *mbox, size_t mboxlen, uint8_t *statusp); +int	mfi_open(char *dev, int acs); +int	mfi_ctrl_get_info(int fd, struct mfi_ctrl_info *info, uint8_t *statusp); +int	mfi_ld_get_info(int fd, uint8_t target_id, struct mfi_ld_info *info, +    uint8_t *statusp); +int	mfi_ld_get_list(int fd, struct mfi_ld_list *list, uint8_t *statusp); +int	mfi_pd_get_info(int fd, uint16_t device_id, struct mfi_pd_info *info, +    uint8_t *statusp); +int	mfi_pd_get_list(int fd, struct mfi_pd_list **listp, uint8_t *statusp); +int	mfi_reconfig_supported(const char *mfi_device); +const char *mfi_status(u_int status_code); +const char *mfi_drive_name(struct mfi_pd_info *pinfo, uint16_t device_id, +    uint32_t def); +void	format_stripe(char *buf, size_t buflen, uint8_t stripe); +void	print_ld(struct mfi_ld_info *info, int state_len); +void	print_pd(struct mfi_pd_info *info, int state_len); +void	dump_config(int fd, struct mfi_config_data *config, const char *msg_prefix); +int	mfi_bbu_get_props(int fd, struct mfi_bbu_properties *props, +	    uint8_t *statusp); +int	mfi_bbu_set_props(int fd, struct mfi_bbu_properties *props, +	    uint8_t *statusp); +void	mfi_autolearn_period(FILE *, uint32_t); +void	mfi_next_learn_time(uint32_t, char *, size_t); +void	mfi_autolearn_mode(FILE *, uint8_t); +int	get_mfi_unit(const char *dev); +char	*get_mfi_type(const char *dev); + +void	scan_firmware(struct mfi_info_component *comp); +void	display_firmware(struct mfi_info_component *comp, const char *tag); + +int	display_format(int ac, char **av, int diagnostic, mfi_dcmd_t display_cmd); +#endif /* !__MFIUTIL_H__ */ | 
