| /*- |
| * 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_config.c 251516 2013-06-08 02:54:59Z sbruno $ |
| */ |
| |
| #define _GNU_SOURCE |
| #include <sys/param.h> |
| #if defined __FreeBSD__ && !defined __FreeBSD_kernel__ |
| #define __FreeBSD_kernel__ |
| #endif |
| #if defined DEBUG && defined HAVE_MFI_DRIVER && defined __FreeBSD_kernel__ |
| #include <sys/sysctl.h> |
| #endif |
| #include <err.h> |
| #include <errno.h> |
| #include <fcntl.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 long int |
| dehumanize(const char *value) |
| { |
| char *vtp; |
| long int long iv; |
| |
| if (value == NULL) |
| return (0); |
| iv = strtoll(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); |
| } |
| |
| static void convert_little_endian_values(struct mfi_config_data *config) { |
| unsigned int i, j; |
| for(i = 0; i < config->array_count; i++) { |
| struct mfi_array *array = (struct mfi_array *)((char *)config->array + i * config->array_size); |
| CONVLE64(&array->size); |
| CONVLE16(&array->array_ref); |
| for(j = 0; j < array->num_drives; j++) { |
| CONVLE16(&array->pd[j].ref.v.device_id); |
| CONVLE16(&array->pd[j].ref.v.seq_num); |
| CONVLE16(&array->pd[j].fw_state); |
| } |
| } |
| for(i = 0; i < config->log_drv_count; i++) { |
| struct mfi_ld_config *ld_config = |
| (struct mfi_ld_config *)((char *)config->array + config->array_count * config->array_size + i * config->log_drv_size); |
| CONVLE16(&ld_config->properties.ld.v.seq); |
| for(j = 0; j < MFI_MAX_SPAN_DEPTH; j++) { |
| struct mfi_span *span = ld_config->span + j; |
| CONVLE64(&span->start_block); |
| CONVLE64(&span->num_blocks); |
| CONVLE16(&span->array_ref); |
| } |
| } |
| for(i = 0; i < config->spares_count; i++) { |
| struct mfi_spare *spare = |
| (struct mfi_spare *)((char *)config->array + config->array_count * config->array_size + |
| config->log_drv_count * config->log_drv_size + |
| i * config->spares_size); |
| CONVLE16(&spare->ref.v.device_id); |
| CONVLE16(&spare->ref.v.seq_num); |
| for(j = 0; j < spare->array_count; j++) CONVLE16(spare->array_ref + j); |
| } |
| } |
| |
| int |
| mfi_config_read_opcode(int fd, union mfi_dcmd_opcode opcode, struct mfi_config_data **configp, |
| const 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); |
| } |
| CONVLE32(&config->size); |
| CONVLE16(&config->array_count); |
| CONVLE16(&config->array_size); |
| CONVLE16(&config->log_drv_count); |
| CONVLE16(&config->log_drv_size); |
| CONVLE16(&config->spares_count); |
| CONVLE16(&config->spares_size); |
| |
| if (config->size > config_size) { |
| config_size = config->size; |
| goto fetch; |
| } |
| |
| convert_little_endian_values(config); |
| |
| *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, const struct array_info *arrays, |
| int raid_type, long long int 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 = ffsll(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 long int 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); |
| } |
| |
| memset(&state, 0, 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 |
| |
| convert_little_endian_values(config); |
| CONVLE16(&config->array_count); |
| CONVLE16(&config->array_size); |
| CONVLE16(&config->log_drv_count); |
| CONVLE16(&config->log_drv_size); |
| CONVLE16(&config->spares_count); |
| CONVLE16(&config->spares_size); |
| CONVLE32(&config->size); |
| |
| /* 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; |
| } |
| memset(spare, 0, 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; |
| } |
| } |
| |
| CONVLE16(&spare->ref.v.device_id); |
| CONVLE16(&spare->ref.v.seq_num); |
| for(i = 0; i < spare->array_count; i++) CONVLE16(spare->array_ref + i); |
| |
| 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, const 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); |
| |
| #if defined HAVE_MFI_DRIVER && defined __FreeBSD_kernel__ |
| 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); |
| } |
| |
| if(!use_mfi) { |
| warnx("dump: mfi driver required"); |
| return EOPNOTSUPP; |
| } |
| |
| 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 |
| #endif |