Index: stable/8/usr.sbin/mfiutil/mfi_show.c =================================================================== --- stable/8/usr.sbin/mfiutil/mfi_show.c (revision 220664) +++ stable/8/usr.sbin/mfiutil/mfi_show.c (revision 220665) @@ -1,576 +1,618 @@ /*- * 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 #include #include #include #include #include #include #include #include "mfiutil.h" MFI_TABLE(top, show); static 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) { 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); 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"); 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) { struct mfi_bbu_capacity_info cap; struct mfi_bbu_design_info design; + struct mfi_bbu_status stat; uint8_t status; - int error, fd; + int comma, error, fd; if (ac != 1) { warnx("show battery: extra arguments"); return (EINVAL); } fd = mfi_open(mfi_unit); 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) { if (status == MFI_STAT_NO_HW_PRESENT) { printf("mfi%d: No battery present\n", mfi_unit); return (0); } error = errno; warn("Failed to get capacity info"); return (error); } 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"); return (error); } + if (mfi_dcmd_command(fd, MFI_DCMD_BBU_GET_STATUS, &stat, sizeof(stat), + NULL, 0, NULL) < 0) { + warn("Failed to get status"); + return (errno); + } + printf("mfi%d: Battery State:\n", mfi_unit); - printf(" Manufacture Date: %d/%d/%d\n", design.mfg_date >> 5 & 0x0f, + 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); - printf(" Design Voltage: %d mV\n", design.design_voltage); - printf(" Current Charge: %d%%\n", cap.relative_charge); + 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); + 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); + 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 ? "," : ""); + } + 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); static 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)); } static void print_pd(struct mfi_pd_info *info, int state_len, int location) { const char *s; char buf[6]; humanize_number(buf, sizeof(buf), info->raw_size * 512, "", HN_AUTOSCALE, HN_B | HN_NOSPACE |HN_DECIMAL); printf("(%6s) ", buf); if (state_len > 0) printf("%-*s", state_len, mfi_pdstate(info->fw_state)); else printf("%s", mfi_pdstate(info->fw_state)); s = mfi_pd_inq_string(info); if (s != NULL) printf(" %s", s); if (!location) return; if (info->encl_device_id == 0xffff) printf(" slot %d", info->slot_number); else if (info->encl_device_id == info->ref.v.device_id) printf(" enclosure %d", info->encl_index); else printf(" enclosure %d, slot %d", info->encl_index, info->slot_number); } static int show_config(int ac, char **av) { 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); 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"); 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; if (device_id == 0xffff) printf(" drive MISSING\n"); else { printf(" drive %u ", device_id); 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, 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 %u ", sp->spare_type & MFI_SPARE_DEDICATED ? "dedicated" : "global", sp->ref.v.device_id); 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, 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; } close(fd); return (0); } MFI_COMMAND(show, config, show_config); static int show_volumes(int ac, char **av) { 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); 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"); 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); 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) { 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); if (fd < 0) { error = errno; warn("mfi_open"); return (error); } if (mfi_pd_get_list(fd, &list, NULL) < 0) { error = errno; warn("Failed to get drive list"); return (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); return (error); } len = strlen(mfi_pdstate(info.fw_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); return (error); } print_pd(&info, state_len, 1); printf("\n"); } close(fd); return (0); } MFI_COMMAND(show, drives, show_drives); int fw_name_width, fw_version_width, fw_date_width, fw_time_width; static 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; } static 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); } static int show_firmware(int ac, char **av) { struct mfi_ctrl_info info; struct mfi_info_component header; int error, fd; u_int i; if (ac != 1) { warnx("show drives: extra arguments"); return (EINVAL); } fd = mfi_open(mfi_unit); 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"); 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); Index: stable/8/usr.sbin/mfiutil/mfi_volume.c =================================================================== --- stable/8/usr.sbin/mfiutil/mfi_volume.c (revision 220664) +++ stable/8/usr.sbin/mfiutil/mfi_volume.c (revision 220665) @@ -1,424 +1,446 @@ /*- * 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 #include #include #include #include #include #include #include #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 *props, uint8_t new_policy, uint8_t mask) { int error; uint8_t changes, policy; policy = (props->default_cache_policy & ~mask) | new_policy; if (policy == props->default_cache_policy) return (0); changes = policy ^ props->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"); props->default_cache_policy = policy; if (mfi_ld_set_props(fd, props) < 0) { error = errno; warn("Failed to set volume properties"); return (error); } return (0); } static int volume_cache(int ac, char **av) { struct mfi_ld_props props; int error, fd; uint8_t target_id, policy; if (ac < 2) { warnx("cache: volume required"); return (EINVAL); } fd = mfi_open(mfi_unit); 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]); return (error); } if (mfi_ld_get_props(fd, target_id, &props) < 0) { error = errno; warn("Failed to fetch volume properties"); 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: "); + 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", + printf(" write caching: %s\n", props.default_cache_policy & MR_LD_CACHE_WRITE_BACK ? "write-back" : "write-through"); - printf(" read ahead: %s\n", + 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: "); + 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\n"); error = 0; } else { if (strcmp(av[2], "all") == 0 || strcmp(av[2], "enable") == 0) error = update_cache_policy(fd, &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); else if (strcmp(av[2], "none") == 0 || strcmp(av[2], "disable") == 0) error = update_cache_policy(fd, &props, 0, MR_LD_CACHE_ALLOW_READ_CACHE | MR_LD_CACHE_ALLOW_WRITE_CACHE); else if (strcmp(av[2], "reads") == 0) error = update_cache_policy(fd, &props, MR_LD_CACHE_ALLOW_READ_CACHE, MR_LD_CACHE_ALLOW_READ_CACHE | MR_LD_CACHE_ALLOW_WRITE_CACHE); else if (strcmp(av[2], "writes") == 0) error = update_cache_policy(fd, &props, MR_LD_CACHE_ALLOW_WRITE_CACHE, MR_LD_CACHE_ALLOW_READ_CACHE | MR_LD_CACHE_ALLOW_WRITE_CACHE); else if (strcmp(av[2], "write-back") == 0) error = update_cache_policy(fd, &props, MR_LD_CACHE_WRITE_BACK, MR_LD_CACHE_WRITE_BACK); else if (strcmp(av[2], "write-through") == 0) error = update_cache_policy(fd, &props, 0, MR_LD_CACHE_WRITE_BACK); else if (strcmp(av[2], "read-ahead") == 0) { if (ac < 4) { warnx("cache: read-ahead setting required"); return (EINVAL); } if (strcmp(av[3], "none") == 0) policy = 0; else if (strcmp(av[3], "always") == 0) policy = MR_LD_CACHE_READ_AHEAD; else if (strcmp(av[3], "adaptive") == 0) policy = MR_LD_CACHE_READ_AHEAD | MR_LD_CACHE_READ_ADAPTIVE; else { warnx("cache: invalid read-ahead setting"); return (EINVAL); } error = update_cache_policy(fd, &props, policy, MR_LD_CACHE_READ_AHEAD | MR_LD_CACHE_READ_ADAPTIVE); + } else if (strcmp(av[2], "bad-bbu-write-cache") == 0) { + if (ac < 4) { + warnx("cache: bad BBU setting required"); + return (EINVAL); + } + if (strcmp(av[3], "enable") == 0) + policy = MR_LD_CACHE_WRITE_CACHE_BAD_BBU; + else if (strcmp(av[3], "disable") == 0) + policy = 0; + else { + warnx("cache: invalid bad BBU setting"); + return (EINVAL); + } + error = update_cache_policy(fd, &props, policy, + MR_LD_CACHE_WRITE_CACHE_BAD_BBU); } else if (strcmp(av[2], "write-cache") == 0) { if (ac < 4) { warnx("cache: write-cache setting required"); return (EINVAL); } if (strcmp(av[3], "enable") == 0) policy = MR_PD_CACHE_ENABLE; else if (strcmp(av[3], "disable") == 0) policy = MR_PD_CACHE_DISABLE; else if (strcmp(av[3], "default") == 0) policy = MR_PD_CACHE_UNCHANGED; else { warnx("cache: invalid write-cache setting"); return (EINVAL); } error = 0; if (policy != props.disk_cache_policy) { switch (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; } props.disk_cache_policy = policy; if (mfi_ld_set_props(fd, &props) < 0) { error = errno; warn("Failed to set volume properties"); } } } else { warnx("cache: Invalid command"); return (EINVAL); } } 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); 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]); return (error); } if (mfi_ld_get_props(fd, target_id, &props) < 0) { error = errno; warn("Failed to fetch volume properties"); 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"); 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); 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]); 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)); 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); Index: stable/8/usr.sbin/mfiutil/mfiutil.8 =================================================================== --- stable/8/usr.sbin/mfiutil/mfiutil.8 (revision 220664) +++ stable/8/usr.sbin/mfiutil/mfiutil.8 (revision 220665) @@ -1,566 +1,577 @@ .\" 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 August 16, 2009 +.Dd April 5, 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 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 logstate .Nm .Op Fl u Ar unit .Cm show patrol .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 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 Op Ar value .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 flash Ar file .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 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 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 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 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 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 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 Op Ar value If no .Ar setting argument is supplied, then the current cache policy for .Ar volume is displayed; otherwise, the cache policy for .Ar volume is modified. The optional .Ar setting argument can be one of the following values: .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 Op Ar value +.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 write-cache Op Ar value +.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 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. .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 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 .Sh SEE ALSO .Xr mfi 4 .Sh HISTORY The .Nm utility first appeared in .Fx 8.0 . Index: stable/8/usr.sbin/mfiutil =================================================================== --- stable/8/usr.sbin/mfiutil (revision 220664) +++ stable/8/usr.sbin/mfiutil (revision 220665) Property changes on: stable/8/usr.sbin/mfiutil ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head/usr.sbin/mfiutil:r219717,220363