summaryrefslogtreecommitdiffstats
path: root/usr.sbin/mfiutil
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/mfiutil')
-rw-r--r--usr.sbin/mfiutil/Makefile18
-rw-r--r--usr.sbin/mfiutil/Makefile.depend19
-rw-r--r--usr.sbin/mfiutil/mfi_bbu.c249
-rw-r--r--usr.sbin/mfiutil/mfi_cmd.c351
-rw-r--r--usr.sbin/mfiutil/mfi_config.c1287
-rw-r--r--usr.sbin/mfiutil/mfi_drive.c772
-rw-r--r--usr.sbin/mfiutil/mfi_evt.c701
-rw-r--r--usr.sbin/mfiutil/mfi_flash.c192
-rw-r--r--usr.sbin/mfiutil/mfi_foreign.c364
-rw-r--r--usr.sbin/mfiutil/mfi_patrol.c336
-rw-r--r--usr.sbin/mfiutil/mfi_properties.c171
-rw-r--r--usr.sbin/mfiutil/mfi_show.c788
-rw-r--r--usr.sbin/mfiutil/mfi_volume.c498
-rw-r--r--usr.sbin/mfiutil/mfiutil.8723
-rw-r--r--usr.sbin/mfiutil/mfiutil.c185
-rw-r--r--usr.sbin/mfiutil/mfiutil.h183
16 files changed, 6837 insertions, 0 deletions
diff --git a/usr.sbin/mfiutil/Makefile b/usr.sbin/mfiutil/Makefile
new file mode 100644
index 0000000..dc6f3e4
--- /dev/null
+++ b/usr.sbin/mfiutil/Makefile
@@ -0,0 +1,18 @@
+# $FreeBSD$
+PROG= mfiutil
+
+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
+MAN8= mfiutil.8
+
+CFLAGS.gcc+= -fno-builtin-strftime
+
+LIBADD= 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 0000000..58f9a33
--- /dev/null
+++ b/usr.sbin/mfiutil/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ 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 0000000..db16367
--- /dev/null
+++ b/usr.sbin/mfiutil/mfi_bbu.c
@@ -0,0 +1,249 @@
+/*-
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#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(uint32_t period, char *buf, size_t sz)
+{
+ unsigned int d, h;
+ char *tmp;
+
+ d = period / (24 * 3600);
+ h = (period % (24 * 3600)) / 3600;
+
+ tmp = buf;
+ if (d != 0) {
+ tmp += snprintf(buf, sz, "%u day%s", d, d == 1 ? "" : "s");
+ sz -= tmp - buf;
+ if (h != 0) {
+ tmp += snprintf(tmp, sz, ", ");
+ sz -= 2;
+ }
+ }
+ if (h != 0)
+ snprintf(tmp, sz, "%u hour%s", h, h == 1 ? "" : "s");
+
+ if (d == 0 && h == 0)
+ snprintf(tmp, sz, "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 = snprintf(buf, sz, "%s", ctime(&basetime));
+ if (len > 0)
+ /* Get rid of the newline added by ctime(3). */
+ buf[len - 1] = '\0';
+}
+
+void
+mfi_autolearn_mode(uint8_t mode, char *buf, size_t sz)
+{
+
+ switch (mode) {
+ case 0:
+ snprintf(buf, sz, "enabled");
+ break;
+ case 1:
+ snprintf(buf, sz, "disabled");
+ break;
+ case 2:
+ snprintf(buf, sz, "warn via event");
+ break;
+ default:
+ snprintf(buf, sz, "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_unit, 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_unit, 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 0000000..3cf703a
--- /dev/null
+++ b/usr.sbin/mfiutil/mfi_cmd.c
@@ -0,0 +1,351 @@
+/*-
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#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>
+
+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(void)
+{
+ char mibname[64];
+ size_t len;
+ int dummy;
+
+ 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(int unit, int acs)
+{
+ char path[MAXPATHLEN];
+
+ snprintf(path, sizeof(path), "/dev/mfi%d", unit);
+ return (open(path, acs));
+}
+
+void
+mfi_display_progress(const char *label, struct mfi_progress *prog)
+{
+ uint seconds;
+
+ printf("%s: %.2f%% complete, after %ds", label,
+ (float)prog->progress * 100 / 0xffff, 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;
+ if (seconds > 3600)
+ printf("%u:", seconds / 3600);
+ if (seconds > 60) {
+ seconds %= 3600;
+ printf("%02u:%02u", seconds / 60, seconds % 60);
+ } else
+ printf("%us", 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 0000000..a919214
--- /dev/null
+++ b/usr.sbin/mfiutil/mfi_config.c
@@ -0,0 +1,1287 @@
+/*-
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#ifdef DEBUG
+#include <sys/sysctl.h>
+#endif
+#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_unit, O_RDWR);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ if (!mfi_reconfig_supported()) {
+ warnx("The current mfi(4) driver does not support "
+ "configuration changes.");
+ 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 mfi%u? [y/N] ",
+ mfi_unit);
+ 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("mfi%d: Configuration cleared\n", mfi_unit);
+ 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_unit, O_RDWR);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ if (!mfi_reconfig_supported()) {
+ warnx("The current mfi(4) driver does not support "
+ "configuration changes.");
+ 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_unit, O_RDWR);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ if (!mfi_reconfig_supported()) {
+ warnx("The current mfi(4) driver does not support "
+ "configuration changes.");
+ 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_unit, 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_unit, 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(
+ "mfi%d %s: %d arrays, %d volumes, %d spares\n", mfi_unit,
+ 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_unit, 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_unit, 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 0000000..2bc902e
--- /dev/null
+++ b/usr.sbin/mfiutil/mfi_drive.c
@@ -0,0 +1,772 @@
+/*-
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/types.h>
+#include <sys/errno.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;
+ static char buf[16];
+ char *p;
+ int error, fd, len;
+
+ 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_unit, 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;
+ }
+
+ p = buf;
+ len = sizeof(buf);
+ if (def & MFI_DNAME_DEVICE_ID) {
+ if (device_id == 0xffff)
+ error = snprintf(p, len, "MISSING");
+ else
+ error = snprintf(p, len, "%2u", device_id);
+ if (error >= 0) {
+ p += error;
+ len -= error;
+ }
+ }
+ if ((def & (MFI_DNAME_ES|MFI_DNAME_DEVICE_ID)) ==
+ (MFI_DNAME_ES|MFI_DNAME_DEVICE_ID) && len >= 2) {
+ *p++ = ' ';
+ len--;
+ *p = '\0';
+ len--;
+ }
+ if (def & MFI_DNAME_ES) {
+ if (pinfo->encl_device_id == 0xffff)
+ error = snprintf(p, len, "S%u",
+ pinfo->slot_number);
+ else if (pinfo->encl_device_id == pinfo->ref.v.device_id)
+ error = snprintf(p, len, "E%u",
+ pinfo->encl_index);
+ else
+ error = snprintf(p, len, "E%u:S%u",
+ pinfo->encl_index, pinfo->slot_number);
+ if (error >= 0) {
+ p += error;
+ len -= error;
+ }
+ }
+
+ 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_unit, 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_unit, 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_unit, 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_unit, 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_unit, 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_unit, 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 0000000..901c0bd
--- /dev/null
+++ b/usr.sbin/mfiutil/mfi_evt.c
@@ -0,0 +1,701 @@
+/*-
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#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_unit, 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("mfi%d Event Log Sequence Numbers:\n", mfi_unit);
+ 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_count.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_count.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_count.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_prog.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_prog.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;
+ uint8_t status;
+ int ch, error, fd, num_events, verbose;
+ u_int i;
+
+ fd = mfi_open(mfi_unit, 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, &filter.members.locale) < 0) {
+ error = errno;
+ warn("Error parsing event locale");
+ close(fd);
+ return (error);
+ }
+ 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 0000000..d640cf7
--- /dev/null
+++ b/usr.sbin/mfiutil/mfi_flash.c
@@ -0,0 +1,192 @@
+/*-
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#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("mfi%d Pending Firmware Images:\n", mfi_unit);
+ 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_unit, 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);
+ mfi_dcmd_command(fd, MFI_DCMD_FLASH_FW_OPEN, NULL, 0, mbox, 4, &status);
+ if (status != MFI_STAT_OK) {
+ warnx("Failed to alloc 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");
+ mfi_dcmd_command(fd, MFI_DCMD_FLASH_FW_CLOSE, NULL, 0,
+ NULL, 0, NULL);
+ error = ENXIO;
+ goto error;
+ }
+
+ mbox_store_word(mbox, offset);
+ mfi_dcmd_command(fd, MFI_DCMD_FLASH_FW_DOWNLOAD, buf, nread,
+ mbox, 4, &status);
+ if (status != MFI_STAT_OK) {
+ warnx("Flash download failed: %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);
+ mfi_dcmd_command(fd, MFI_DCMD_FLASH_FW_FLASH, &dummy, sizeof(dummy),
+ NULL, 0, &status);
+ 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 0000000..25e2add
--- /dev/null
+++ b/usr.sbin/mfiutil/mfi_foreign.c
@@ -0,0 +1,364 @@
+/*
+ * 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.
+ *
+ *
+ * $FreeBSD$
+ */
+
+#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_unit, O_RDWR);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ printf(
+ "Are you sure you wish to clear ALL foreign configurations"
+ " on mfi%u? [y/N] ", mfi_unit);
+
+ 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("mfi%d: Foreign configuration cleared\n", mfi_unit);
+ 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_unit, 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("mfi%d: Found %d foreign configurations\n", mfi_unit,
+ 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_unit, 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_unit, 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 mfi%u? [y/N] ", mfi_unit);
+ } 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 mfi%u? [y/N] ", cfgidx, mfi_unit);
+ }
+
+ 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("mfi%d: All foreign configurations imported\n",
+ mfi_unit);
+ else
+ printf("mfi%d: Foreign configuration %d imported\n", mfi_unit,
+ 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 0000000..c3e47f3
--- /dev/null
+++ b/usr.sbin/mfiutil/mfi_patrol.c
@@ -0,0 +1,336 @@
+/*-
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#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_unit, 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_unit, 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_unit, 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_unit, 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 0000000..b03d522
--- /dev/null
+++ b/usr.sbin/mfiutil/mfi_properties.c
@@ -0,0 +1,171 @@
+/*-
+ * 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.
+ *
+ *
+ * $FreeBSD$
+ */
+
+#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));
+}
+
+/*
+ * aquite the controller properties data structure modify the
+ * rebuild rate if requested and then retun
+ */
+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_unit, 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_unit, 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 0000000..f541045
--- /dev/null
+++ b/usr.sbin/mfiutil/mfi_show.c
@@ -0,0 +1,788 @@
+/*-
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#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_unit, 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("mfi%d Adapter:\n", mfi_unit);
+ 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_unit, 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("mfi%d: No battery present\n", mfi_unit);
+ 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("mfi%d: Battery State:\n", mfi_unit);
+ 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) {
+ mfi_autolearn_period(props.auto_learn_period, buf, sizeof(buf));
+ printf(" Autolearn period: %s\n", buf);
+ 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" : "");
+ mfi_autolearn_mode(props.auto_learn_mode, buf, sizeof(buf));
+ printf(" Autolearn mode: %s\n", buf);
+ 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_unit, 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("mfi%d Configuration: %d arrays, %d volumes, %d spares\n",
+ mfi_unit, 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_unit, 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("mfi%d Volumes:\n", mfi_unit);
+ 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_unit, 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("mfi%d Physical Drives:\n", mfi_unit);
+ 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_unit, 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("mfi%d Firmware Package Version: %s\n", mfi_unit,
+ info.package_version);
+ printf("mfi%d Firmware Images:\n", mfi_unit);
+ 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_unit, 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 mfi%d\n", mfi_unit);
+
+ 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 0000000..2306256
--- /dev/null
+++ b/usr.sbin/mfiutil/mfi_volume.c
@@ -0,0 +1,498 @@
+/*-
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#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_unit, 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("mfi%u volume %s cache settings:\n", mfi_unit,
+ 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_unit, 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("mfi%u volume %s name changed from \"%s\" to \"%s\"\n", mfi_unit,
+ 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_unit, 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 0000000..e3adc0b
--- /dev/null
+++ b/usr.sbin/mfiutil/mfiutil.8
@@ -0,0 +1,723 @@
+.\" 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd September 2, 2011
+.Dt MFIUTIL 8
+.Os
+.Sh NAME
+.Nm mfiutil
+.Nd Utility for managing LSI MegaRAID SAS controllers
+.Sh SYNOPSIS
+.Nm
+.Cm version
+.Nm
+.Op Fl u Ar unit
+.Cm show adapter
+.Nm
+.Op Fl u Ar unit
+.Cm show battery
+.Nm
+.Op Fl d
+.Op Fl e
+.Op Fl u Ar unit
+.Cm show config
+.Nm
+.Op Fl u Ar unit
+.Cm show drives
+.Nm
+.Op Fl u Ar unit
+.Cm show events
+.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 u Ar unit
+.Cm show firmware
+.Nm
+.Op Fl u Ar unit
+.Cm show foreign Op Ar volume
+.Nm
+.Op Fl u Ar unit
+.Cm show logstate
+.Nm
+.Op Fl d
+.Op Fl e
+.Op Fl u Ar unit
+.Cm show patrol
+.Nm
+.Op Fl d
+.Op Fl e
+.Op Fl u Ar unit
+.Cm show progress
+.Nm
+.Op Fl u Ar unit
+.Cm show volumes
+.Nm
+.Op Fl u Ar unit
+.Cm fail Ar drive
+.Nm
+.Op Fl u Ar unit
+.Cm good Ar drive
+.Nm
+.Op Fl u Ar unit
+.Cm rebuild Ar drive
+.Nm
+.Op Fl u Ar unit
+.Cm syspd Ar drive
+.Nm
+.Op Fl u Ar unit
+.Cm drive progress Ar drive
+.Nm
+.Op Fl u Ar unit
+.Cm drive clear Ar drive Brq "start | stop"
+.Nm
+.Op Fl u Ar unit
+.Cm start rebuild Ar drive
+.Nm
+.Op Fl u Ar unit
+.Cm abort rebuild Ar drive
+.Nm
+.Op Fl u Ar unit
+.Cm locate Ar drive Brq "on | off"
+.Nm
+.Op Fl u Ar unit
+.Cm cache Ar volume Op Ar setting Oo Ar value Oc Op ...
+.Nm
+.Op Fl u Ar unit
+.Cm name Ar volume Ar name
+.Nm
+.Op Fl u Ar unit
+.Cm volume progress Ar volume
+.Nm
+.Op Fl u Ar unit
+.Cm clear
+.Nm
+.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 u Ar unit
+.Cm delete Ar volume
+.Nm
+.Op Fl u Ar unit
+.Cm add Ar drive Op Ar volume
+.Nm
+.Op Fl u Ar unit
+.Cm remove Ar drive
+.Nm
+.Op Fl u Ar unit
+.Cm start patrol
+.Nm
+.Op Fl u Ar unit
+.Cm stop patrol
+.Nm
+.Op Fl u Ar unit
+.Cm patrol Ar command Op Ar interval Op Ar start
+.Nm
+.Op Fl u Ar unit
+.Cm foreign scan
+.Nm
+.Op Fl u Ar unit
+.Cm foreign clear Op Ar config
+.Nm
+.Op Fl u Ar unit
+.Cm foreign diag Op Ar config
+.Nm
+.Op Fl u Ar unit
+.Cm foreign preview Op Ar config
+.Nm
+.Op Fl u Ar unit
+.Cm foreign import Op Ar config
+.Nm
+.Op Fl u Ar unit
+.Cm flash Ar file
+.Nm
+.Op Fl u Ar unit
+.Cm start learn
+.Nm
+.Op Fl u Ar unit
+.Cm bbu Ar setting Ar value
+.Nm
+.Op Fl u Ar unit
+.Cm ctrlprop Ar rebuild Op Ar rate
+.Nm
+.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 one global option is supported:
+.Bl -tag -width indent
+.It Fl u Ar unit
+.Ar unit
+specifies the unit of the controller to work with.
+If no unit is specified,
+then unit 0 is used.
+.El
+.Pp
+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
+.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 0000000..85ce25c
--- /dev/null
+++ b/usr.sbin/mfiutil/mfiutil.c
@@ -0,0 +1,185 @@
+/*-
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/errno.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "mfiutil.h"
+
+SET_DECLARE(MFI_DATASET(top), struct mfiutil_command);
+
+MFI_TABLE(top, start);
+MFI_TABLE(top, stop);
+MFI_TABLE(top, abort);
+
+int mfi_unit;
+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: mfiutil [-de] [-u unit] <command> ...\n\n");
+ fprintf(stderr, "Commands include:\n");
+ fprintf(stderr, " version\n");
+ fprintf(stderr, " show adapter - display controller information\n");
+ fprintf(stderr, " show 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;
+
+ while ((ch = getopt(ac, av, "deu:")) != -1) {
+ switch (ch) {
+ 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 '?':
+ usage();
+ }
+ }
+
+ 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 0000000..251816a
--- /dev/null
+++ b/usr.sbin/mfiutil/mfiutil.h
@@ -0,0 +1,183 @@
+/*-
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef __MFIUTIL_H__
+#define __MFIUTIL_H__
+
+#include <sys/cdefs.h>
+#include <sys/linker_set.h>
+
+#include <dev/mfi/mfireg.h>
+
+/* 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 int mfi_unit;
+
+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(int unit, 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(void);
+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(uint32_t, char *, size_t);
+void mfi_next_learn_time(uint32_t, char *, size_t);
+void mfi_autolearn_mode(uint8_t, char *, size_t);
+
+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__ */
OpenPOWER on IntegriCloud