| /*- |
| * 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: releng/11.1/usr.sbin/mfiutil/mfi_volume.c 249257 2013-04-08 17:46:45Z markj $ |
| */ |
| |
| #include <sys/types.h> |
| #include <err.h> |
| #include <errno.h> |
| #include <fcntl.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, const 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) |
| { |
| uint32_t i; |
| int r = mfi_dcmd_command(fd, MFI_DCMD_LD_GET_LIST, list, |
| sizeof(struct mfi_ld_list), NULL, 0, statusp); |
| if(r < 0) return r; |
| CONVLE32(&list->ld_count); |
| for(i = 0; i < list->ld_count; i++) { |
| CONVLE16(&list->ld_list[i].ld.v.seq); |
| CONVLE64(&list->ld_list[i].size); |
| } |
| return r; |
| } |
| |
| int |
| mfi_ld_get_info(int fd, uint8_t target_id, struct mfi_ld_info *info, |
| uint8_t *statusp) |
| { |
| int i; |
| uint8_t mbox[1]; |
| |
| mbox[0] = target_id; |
| if(mfi_dcmd_command(fd, MFI_DCMD_LD_GET_INFO, info, |
| sizeof(struct mfi_ld_info), mbox, 1, statusp) < 0) { |
| return -1; |
| } |
| CONVLE16(&info->ld_config.properties.ld.v.seq); |
| for(i = 0; i < MFI_MAX_SPAN_DEPTH; i++) { |
| struct mfi_span *span = info->ld_config.span + i; |
| CONVLE64(&span->start_block); |
| CONVLE64(&span->num_blocks); |
| CONVLE16(&span->array_ref); |
| } |
| CONVLE64(&info->size); |
| CONVLE32(&info->progress.active); |
| struct mfi_progress *progress = &info->progress.cc; |
| do { |
| CONVLE16(&progress->progress); |
| CONVLE16(&progress->elapsed_seconds); |
| } while((char *)++progress < (char *)&info->progress + sizeof info->progress); |
| CONVLE16(&info->cluster_owner); |
| return 0; |
| } |
| |
| 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; |
| if(mfi_dcmd_command(fd, MFI_DCMD_LD_GET_PROP, props, |
| sizeof(struct mfi_ld_props), mbox, 1, NULL) < 0) { |
| return -1; |
| } |
| CONVLE16(&props->ld.v.seq); |
| return 0; |
| } |
| |
| 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, const 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%d 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("??? %u\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); |
| } |
| |
| size_t len = strlen(av[2]); |
| if(len > sizeof props.name) { |
| len = sizeof props.name; |
| av[2][len] = 0; |
| } |
| printf("mfi%d volume %s name changed from \"%s\" to \"%s\"\n", mfi_unit, |
| mfi_volume_name(fd, target_id), props.name, av[2]); |
| memcpy(props.name, av[2], len); |
| if(len < sizeof props.name) memset(props.name + len, 0, sizeof props.name - len); |
| 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 volume. */ |
| if (mfi_ld_get_info(fd, target_id, &info, NULL) < 0) { |
| error = errno; |
| warn("Failed to fetch information 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); |