diff options
Diffstat (limited to 'usr.sbin/mfiutil')
-rw-r--r-- | usr.sbin/mfiutil/Makefile | 18 | ||||
-rw-r--r-- | usr.sbin/mfiutil/Makefile.depend | 19 | ||||
-rw-r--r-- | usr.sbin/mfiutil/mfi_bbu.c | 249 | ||||
-rw-r--r-- | usr.sbin/mfiutil/mfi_cmd.c | 351 | ||||
-rw-r--r-- | usr.sbin/mfiutil/mfi_config.c | 1287 | ||||
-rw-r--r-- | usr.sbin/mfiutil/mfi_drive.c | 772 | ||||
-rw-r--r-- | usr.sbin/mfiutil/mfi_evt.c | 701 | ||||
-rw-r--r-- | usr.sbin/mfiutil/mfi_flash.c | 192 | ||||
-rw-r--r-- | usr.sbin/mfiutil/mfi_foreign.c | 364 | ||||
-rw-r--r-- | usr.sbin/mfiutil/mfi_patrol.c | 336 | ||||
-rw-r--r-- | usr.sbin/mfiutil/mfi_properties.c | 171 | ||||
-rw-r--r-- | usr.sbin/mfiutil/mfi_show.c | 788 | ||||
-rw-r--r-- | usr.sbin/mfiutil/mfi_volume.c | 498 | ||||
-rw-r--r-- | usr.sbin/mfiutil/mfiutil.8 | 723 | ||||
-rw-r--r-- | usr.sbin/mfiutil/mfiutil.c | 185 | ||||
-rw-r--r-- | usr.sbin/mfiutil/mfiutil.h | 183 |
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__ */ |