| /*- |
| * 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_drive.c 254906 2013-08-26 12:05:38Z sbruno $ |
| */ |
| |
| #include <sys/param.h> |
| #if defined __FreeBSD__ && !defined __FreeBSD_kernel__ |
| #define __FreeBSD_kernel__ |
| #endif |
| #include <sys/types.h> |
| #include <sys/errno.h> |
| #include <ctype.h> |
| #include <err.h> |
| #include <fcntl.h> |
| #include <limits.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <strings.h> |
| #include <unistd.h> |
| #ifdef __FreeBSD_kernel__ |
| #include <cam/scsi/scsi_all.h> |
| #endif |
| #include "mfiutil.h" |
| |
| MFI_TABLE(top, drive); |
| |
| /* |
| * Print the name of a drive either by drive number as %2u or by enclosure:slot |
| * as Exx:Sxx (or both). Use default unless command line options override it |
| * and the command allows this (which we usually do unless we already print |
| * both). We prefer pinfo if given, otherwise try to look it up by device_id. |
| */ |
| const char * |
| mfi_drive_name(struct mfi_pd_info *pinfo, uint16_t device_id, uint32_t def) |
| { |
| struct mfi_pd_info info; |
| static char buf[16]; |
| char *p; |
| int error, fd, len; |
| |
| if ((def & MFI_DNAME_HONOR_OPTS) != 0 && |
| (mfi_opts & (MFI_DNAME_ES|MFI_DNAME_DEVICE_ID)) != 0) |
| def = mfi_opts & (MFI_DNAME_ES|MFI_DNAME_DEVICE_ID); |
| |
| buf[0] = '\0'; |
| if (pinfo == NULL && def & MFI_DNAME_ES) { |
| /* Fallback in case of error, just ignore flags. */ |
| if (device_id == 0xffff) |
| snprintf(buf, sizeof(buf), "MISSING"); |
| else |
| snprintf(buf, sizeof(buf), "%2u", device_id); |
| |
| fd = mfi_open(mfi_unit, O_RDWR); |
| if (fd < 0) { |
| warn("mfi_open"); |
| return (buf); |
| } |
| |
| /* Get the info for this drive. */ |
| if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) { |
| warn("Failed to fetch info for drive %2u", device_id); |
| close(fd); |
| return (buf); |
| } |
| |
| close(fd); |
| pinfo = &info; |
| } |
| |
| p = buf; |
| len = sizeof(buf); |
| if (def & MFI_DNAME_DEVICE_ID) { |
| if (device_id == 0xffff) |
| error = snprintf(p, len, "MISSING"); |
| else |
| error = snprintf(p, len, "%2u", device_id); |
| if (error >= 0) { |
| p += error; |
| len -= error; |
| } |
| } |
| if ((def & (MFI_DNAME_ES|MFI_DNAME_DEVICE_ID)) == |
| (MFI_DNAME_ES|MFI_DNAME_DEVICE_ID) && len >= 2) { |
| *p++ = ' '; |
| len--; |
| *p = '\0'; |
| len--; |
| } |
| if (def & MFI_DNAME_ES) { |
| if (pinfo->encl_device_id == 0xffff) |
| error = snprintf(p, len, "S%u", |
| pinfo->slot_number); |
| else if (pinfo->encl_device_id == pinfo->ref.v.device_id) |
| error = snprintf(p, len, "E%u", |
| pinfo->encl_index); |
| else |
| error = snprintf(p, len, "E%u:S%u", |
| pinfo->encl_index, pinfo->slot_number); |
| if (error >= 0) { |
| p += error; |
| len -= error; |
| } |
| } |
| |
| return (buf); |
| } |
| |
| const char * |
| mfi_pdstate(enum mfi_pd_state state) |
| { |
| static char buf[16]; |
| |
| switch (state) { |
| case MFI_PD_STATE_UNCONFIGURED_GOOD: |
| return ("UNCONFIGURED GOOD"); |
| case MFI_PD_STATE_UNCONFIGURED_BAD: |
| return ("UNCONFIGURED BAD"); |
| case MFI_PD_STATE_HOT_SPARE: |
| return ("HOT SPARE"); |
| case MFI_PD_STATE_OFFLINE: |
| return ("OFFLINE"); |
| case MFI_PD_STATE_FAILED: |
| return ("FAILED"); |
| case MFI_PD_STATE_REBUILD: |
| return ("REBUILD"); |
| case MFI_PD_STATE_ONLINE: |
| return ("ONLINE"); |
| case MFI_PD_STATE_COPYBACK: |
| return ("COPYBACK"); |
| case MFI_PD_STATE_SYSTEM: |
| return ("JBOD"); |
| default: |
| sprintf(buf, "PSTATE 0x%04x", state); |
| return (buf); |
| } |
| } |
| |
| int |
| mfi_lookup_drive(int fd, char *drive, uint16_t *device_id) |
| { |
| struct mfi_pd_list *list; |
| long val; |
| int error; |
| u_int i; |
| char *cp; |
| uint8_t encl, slot; |
| |
| /* Look for a raw device id first. */ |
| val = strtol(drive, &cp, 0); |
| if (*cp == '\0') { |
| if (val < 0 || val >= 0xffff) |
| goto bad; |
| *device_id = val; |
| return (0); |
| } |
| |
| /* Support for MegaCli style [Exx]:Syy notation. */ |
| if (toupper(drive[0]) == 'E' || toupper(drive[0]) == 'S') { |
| if (drive[1] == '\0') |
| goto bad; |
| cp = drive; |
| if (toupper(drive[0]) == 'E') { |
| cp++; /* Eat 'E' */ |
| val = strtol(cp, &cp, 0); |
| if (val < 0 || val > 0xff || *cp != ':') |
| goto bad; |
| encl = val; |
| cp++; /* Eat ':' */ |
| if (toupper(*cp) != 'S') |
| goto bad; |
| } else |
| encl = 0xff; |
| cp++; /* Eat 'S' */ |
| if (*cp == '\0') |
| goto bad; |
| val = strtol(cp, &cp, 0); |
| if (val < 0 || val > 0xff || *cp != '\0') |
| goto bad; |
| slot = val; |
| |
| if (mfi_pd_get_list(fd, &list, NULL) < 0) { |
| error = errno; |
| warn("Failed to fetch drive list"); |
| return (error); |
| } |
| |
| for (i = 0; i < list->count; i++) { |
| if (list->addr[i].scsi_dev_type != 0) |
| continue; |
| |
| if (((encl == 0xff && |
| list->addr[i].encl_device_id == 0xffff) || |
| list->addr[i].encl_index == encl) && |
| list->addr[i].slot_number == slot) { |
| *device_id = list->addr[i].device_id; |
| free(list); |
| return (0); |
| } |
| } |
| free(list); |
| warnx("Unknown drive %s", drive); |
| return (EINVAL); |
| } |
| |
| bad: |
| warnx("Invalid drive number %s", drive); |
| return (EINVAL); |
| } |
| |
| static void |
| mbox_store_device_id(uint8_t *mbox, uint16_t device_id) |
| { |
| |
| mbox[0] = device_id & 0xff; |
| mbox[1] = device_id >> 8; |
| } |
| |
| void |
| mbox_store_pdref(uint8_t *mbox, union mfi_pd_ref *ref) |
| { |
| |
| mbox[0] = ref->v.device_id & 0xff; |
| mbox[1] = ref->v.device_id >> 8; |
| mbox[2] = ref->v.seq_num & 0xff; |
| mbox[3] = ref->v.seq_num >> 8; |
| } |
| |
| int |
| mfi_pd_get_list(int fd, struct mfi_pd_list **listp, uint8_t *statusp) |
| { |
| struct mfi_pd_list *list; |
| uint32_t list_size; |
| |
| /* |
| * Keep fetching the list in a loop until we have a large enough |
| * buffer to hold the entire list. |
| */ |
| list = NULL; |
| list_size = 1024; |
| fetch: |
| list = reallocf(list, list_size); |
| if (list == NULL) |
| return (-1); |
| if (mfi_dcmd_command(fd, MFI_DCMD_PD_GET_LIST, list, list_size, NULL, |
| 0, statusp) < 0) { |
| free(list); |
| return (-1); |
| } |
| |
| if (list->size > list_size) { |
| list_size = list->size; |
| goto fetch; |
| } |
| |
| *listp = list; |
| return (0); |
| } |
| |
| int |
| mfi_pd_get_info(int fd, uint16_t device_id, struct mfi_pd_info *info, |
| uint8_t *statusp) |
| { |
| uint8_t mbox[2]; |
| |
| mbox_store_device_id(&mbox[0], device_id); |
| return (mfi_dcmd_command(fd, MFI_DCMD_PD_GET_INFO, info, |
| sizeof(struct mfi_pd_info), mbox, 2, statusp)); |
| } |
| |
| #ifdef __FreeBSD_kernel__ |
| static void |
| cam_strvis(char *dst, const char *src, int srclen, int dstlen) |
| { |
| |
| /* Trim leading/trailing spaces, nulls. */ |
| while (srclen > 0 && src[0] == ' ') |
| src++, srclen--; |
| while (srclen > 0 |
| && (src[srclen-1] == ' ' || src[srclen-1] == '\0')) |
| srclen--; |
| |
| while (srclen > 0 && dstlen > 1) { |
| char *cur_pos = dst; |
| |
| if (*src < 0x20) { |
| /* SCSI-II Specifies that these should never occur. */ |
| /* non-printable character */ |
| if (dstlen > 4) { |
| *cur_pos++ = '\\'; |
| *cur_pos++ = ((*src & 0300) >> 6) + '0'; |
| *cur_pos++ = ((*src & 0070) >> 3) + '0'; |
| *cur_pos++ = ((*src & 0007) >> 0) + '0'; |
| } else { |
| *cur_pos++ = '?'; |
| } |
| } else { |
| /* normal character */ |
| *cur_pos++ = *src; |
| } |
| src++; |
| srclen--; |
| dstlen -= cur_pos - dst; |
| dst = cur_pos; |
| } |
| *dst = '\0'; |
| } |
| |
| /* Borrowed heavily from scsi_all.c:scsi_print_inquiry(). */ |
| const char * |
| mfi_pd_inq_string(struct mfi_pd_info *info) |
| { |
| struct scsi_inquiry_data iqd, *inq_data = &iqd; |
| char vendor[16], product[48], revision[16], rstr[12], serial[SID_VENDOR_SPECIFIC_0_SIZE]; |
| static char inq_string[64]; |
| |
| memcpy(inq_data, info->inquiry_data, |
| (sizeof (iqd) < sizeof (info->inquiry_data))? |
| sizeof (iqd) : sizeof (info->inquiry_data)); |
| if (SID_QUAL_IS_VENDOR_UNIQUE(inq_data)) |
| return (NULL); |
| if (SID_TYPE(inq_data) != T_DIRECT) |
| return (NULL); |
| if (SID_QUAL(inq_data) != SID_QUAL_LU_CONNECTED) |
| return (NULL); |
| |
| cam_strvis(vendor, inq_data->vendor, sizeof(inq_data->vendor), |
| sizeof(vendor)); |
| cam_strvis(product, inq_data->product, sizeof(inq_data->product), |
| sizeof(product)); |
| cam_strvis(revision, inq_data->revision, sizeof(inq_data->revision), |
| sizeof(revision)); |
| cam_strvis(serial, (char *)inq_data->vendor_specific0, sizeof(inq_data->vendor_specific0), |
| sizeof(serial)); |
| |
| /* Hack for SATA disks, no idea how to tell speed. */ |
| if (strcmp(vendor, "ATA") == 0) { |
| snprintf(inq_string, sizeof(inq_string), "<%s %s serial=%s> SATA", |
| product, revision, serial); |
| return (inq_string); |
| } |
| |
| switch (SID_ANSI_REV(inq_data)) { |
| case SCSI_REV_CCS: |
| strcpy(rstr, "SCSI-CCS"); |
| break; |
| case 5: |
| strcpy(rstr, "SAS"); |
| break; |
| default: |
| snprintf(rstr, sizeof (rstr), "SCSI-%d", |
| SID_ANSI_REV(inq_data)); |
| break; |
| } |
| snprintf(inq_string, sizeof(inq_string), "<%s %s %s serial=%s> %s", vendor, |
| product, revision, serial, rstr); |
| return (inq_string); |
| } |
| #endif |
| |
| /* Helper function to set a drive to a given state. */ |
| static int |
| drive_set_state(char *drive, uint16_t new_state) |
| { |
| struct mfi_pd_info info; |
| uint16_t device_id; |
| uint8_t mbox[6]; |
| int error, fd; |
| |
| fd = mfi_open(mfi_unit, O_RDWR); |
| if (fd < 0) { |
| error = errno; |
| warn("mfi_open"); |
| return (error); |
| } |
| |
| error = mfi_lookup_drive(fd, drive, &device_id); |
| if (error) { |
| close(fd); |
| return (error); |
| } |
| |
| /* Get the info for this drive. */ |
| if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) { |
| error = errno; |
| warn("Failed to fetch info for drive %u", device_id); |
| close(fd); |
| return (error); |
| } |
| |
| /* Try to change the state. */ |
| if (info.fw_state == new_state) { |
| warnx("Drive %u is already in the desired state", device_id); |
| close(fd); |
| return (EINVAL); |
| } |
| |
| mbox_store_pdref(&mbox[0], &info.ref); |
| mbox[4] = new_state & 0xff; |
| mbox[5] = new_state >> 8; |
| if (mfi_dcmd_command(fd, MFI_DCMD_PD_STATE_SET, NULL, 0, mbox, 6, |
| NULL) < 0) { |
| error = errno; |
| warn("Failed to set drive %u to %s", device_id, |
| mfi_pdstate(new_state)); |
| close(fd); |
| return (error); |
| } |
| |
| close(fd); |
| |
| return (0); |
| } |
| |
| static int |
| fail_drive(int ac, char **av) |
| { |
| |
| if (ac != 2) { |
| warnx("fail: %s", ac > 2 ? "extra arguments" : |
| "drive required"); |
| return (EINVAL); |
| } |
| |
| return (drive_set_state(av[1], MFI_PD_STATE_FAILED)); |
| } |
| MFI_COMMAND(top, fail, fail_drive); |
| |
| static int |
| good_drive(int ac, char **av) |
| { |
| |
| if (ac != 2) { |
| warnx("good: %s", ac > 2 ? "extra arguments" : |
| "drive required"); |
| return (EINVAL); |
| } |
| |
| return (drive_set_state(av[1], MFI_PD_STATE_UNCONFIGURED_GOOD)); |
| } |
| MFI_COMMAND(top, good, good_drive); |
| |
| static int |
| rebuild_drive(int ac, char **av) |
| { |
| |
| if (ac != 2) { |
| warnx("rebuild: %s", ac > 2 ? "extra arguments" : |
| "drive required"); |
| return (EINVAL); |
| } |
| |
| return (drive_set_state(av[1], MFI_PD_STATE_REBUILD)); |
| } |
| MFI_COMMAND(top, rebuild, rebuild_drive); |
| |
| static int |
| syspd_drive(int ac, char **av) |
| { |
| |
| if (ac != 2) { |
| warnx("syspd: %s", ac > 2 ? "extra arguments" : |
| "drive required"); |
| return (EINVAL); |
| } |
| |
| return (drive_set_state(av[1], MFI_PD_STATE_SYSTEM)); |
| } |
| MFI_COMMAND(top, syspd, syspd_drive); |
| |
| static int |
| start_rebuild(int ac, char **av) |
| { |
| struct mfi_pd_info info; |
| uint16_t device_id; |
| uint8_t mbox[4]; |
| int error, fd; |
| |
| if (ac != 2) { |
| warnx("start rebuild: %s", ac > 2 ? "extra arguments" : |
| "drive required"); |
| return (EINVAL); |
| } |
| |
| fd = mfi_open(mfi_unit, O_RDWR); |
| if (fd < 0) { |
| error = errno; |
| warn("mfi_open"); |
| return (error); |
| } |
| |
| error = mfi_lookup_drive(fd, av[1], &device_id); |
| if (error) { |
| close(fd); |
| return (error); |
| } |
| |
| /* Get the info for this drive. */ |
| if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) { |
| error = errno; |
| warn("Failed to fetch info for drive %u", device_id); |
| close(fd); |
| return (error); |
| } |
| |
| /* Check the state, must be REBUILD. */ |
| if (info.fw_state != MFI_PD_STATE_REBUILD) { |
| warnx("Drive %d is not in the REBUILD state", device_id); |
| close(fd); |
| return (EINVAL); |
| } |
| |
| /* Start the rebuild. */ |
| mbox_store_pdref(&mbox[0], &info.ref); |
| if (mfi_dcmd_command(fd, MFI_DCMD_PD_REBUILD_START, NULL, 0, mbox, 4, |
| NULL) < 0) { |
| error = errno; |
| warn("Failed to start rebuild on drive %u", device_id); |
| close(fd); |
| return (error); |
| } |
| close(fd); |
| |
| return (0); |
| } |
| MFI_COMMAND(start, rebuild, start_rebuild); |
| |
| static int |
| abort_rebuild(int ac, char **av) |
| { |
| struct mfi_pd_info info; |
| uint16_t device_id; |
| uint8_t mbox[4]; |
| int error, fd; |
| |
| if (ac != 2) { |
| warnx("abort rebuild: %s", ac > 2 ? "extra arguments" : |
| "drive required"); |
| return (EINVAL); |
| } |
| |
| fd = mfi_open(mfi_unit, O_RDWR); |
| if (fd < 0) { |
| error = errno; |
| warn("mfi_open"); |
| return (error); |
| } |
| |
| error = mfi_lookup_drive(fd, av[1], &device_id); |
| if (error) { |
| close(fd); |
| return (error); |
| } |
| |
| /* Get the info for this drive. */ |
| if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) { |
| error = errno; |
| warn("Failed to fetch info for drive %u", device_id); |
| close(fd); |
| return (error); |
| } |
| |
| /* Check the state, must be REBUILD. */ |
| if (info.fw_state != MFI_PD_STATE_REBUILD) { |
| warn("Drive %d is not in the REBUILD state", device_id); |
| close(fd); |
| return (EINVAL); |
| } |
| |
| /* Abort the rebuild. */ |
| mbox_store_pdref(&mbox[0], &info.ref); |
| if (mfi_dcmd_command(fd, MFI_DCMD_PD_REBUILD_ABORT, NULL, 0, mbox, 4, |
| NULL) < 0) { |
| error = errno; |
| warn("Failed to abort rebuild on drive %u", device_id); |
| close(fd); |
| return (error); |
| } |
| close(fd); |
| |
| return (0); |
| } |
| MFI_COMMAND(abort, rebuild, abort_rebuild); |
| |
| static int |
| drive_progress(int ac, char **av) |
| { |
| struct mfi_pd_info info; |
| uint16_t device_id; |
| int error, fd; |
| |
| if (ac != 2) { |
| warnx("drive progress: %s", ac > 2 ? "extra arguments" : |
| "drive required"); |
| return (EINVAL); |
| } |
| |
| fd = mfi_open(mfi_unit, O_RDWR); |
| if (fd < 0) { |
| error = errno; |
| warn("mfi_open"); |
| return (error); |
| } |
| |
| error = mfi_lookup_drive(fd, av[1], &device_id); |
| if (error) { |
| close(fd); |
| return (error); |
| } |
| |
| /* Get the info for this drive. */ |
| if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) { |
| error = errno; |
| warn("Failed to fetch info for drive %u", device_id); |
| close(fd); |
| return (error); |
| } |
| close(fd); |
| |
| /* Display any of the active events. */ |
| if (info.prog_info.active & MFI_PD_PROGRESS_REBUILD) |
| mfi_display_progress("Rebuild", &info.prog_info.rbld); |
| if (info.prog_info.active & MFI_PD_PROGRESS_PATROL) |
| mfi_display_progress("Patrol Read", &info.prog_info.patrol); |
| if (info.prog_info.active & MFI_PD_PROGRESS_CLEAR) |
| mfi_display_progress("Clear", &info.prog_info.clear); |
| if ((info.prog_info.active & (MFI_PD_PROGRESS_REBUILD | |
| MFI_PD_PROGRESS_PATROL | MFI_PD_PROGRESS_CLEAR)) == 0) |
| printf("No activity in progress for drive %s.\n", |
| mfi_drive_name(NULL, device_id, |
| MFI_DNAME_DEVICE_ID|MFI_DNAME_HONOR_OPTS)); |
| |
| return (0); |
| } |
| MFI_COMMAND(drive, progress, drive_progress); |
| |
| static int |
| drive_clear(int ac, char **av) |
| { |
| struct mfi_pd_info info; |
| uint32_t opcode; |
| uint16_t device_id; |
| uint8_t mbox[4]; |
| char *s1; |
| int error, fd; |
| |
| if (ac != 3) { |
| warnx("drive clear: %s", ac > 3 ? "extra arguments" : |
| "drive and action requires"); |
| return (EINVAL); |
| } |
| |
| for (s1 = av[2]; *s1 != '\0'; s1++) |
| *s1 = tolower(*s1); |
| if (strcmp(av[2], "start") == 0) |
| opcode = MFI_DCMD_PD_CLEAR_START; |
| else if ((strcmp(av[2], "stop") == 0) || (strcmp(av[2], "abort") == 0)) |
| opcode = MFI_DCMD_PD_CLEAR_ABORT; |
| else { |
| warnx("drive clear: invalid action, must be 'start' or 'stop'\n"); |
| return (EINVAL); |
| } |
| |
| fd = mfi_open(mfi_unit, O_RDWR); |
| if (fd < 0) { |
| error = errno; |
| warn("mfi_open"); |
| return (error); |
| } |
| |
| error = mfi_lookup_drive(fd, av[1], &device_id); |
| if (error) { |
| close(fd); |
| return (error); |
| } |
| |
| /* Get the info for this drive. */ |
| if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) { |
| error = errno; |
| warn("Failed to fetch info for drive %u", device_id); |
| close(fd); |
| return (error); |
| } |
| |
| mbox_store_pdref(&mbox[0], &info.ref); |
| if (mfi_dcmd_command(fd, opcode, NULL, 0, mbox, 4, NULL) < 0) { |
| error = errno; |
| warn("Failed to %s clear on drive %u", |
| opcode == MFI_DCMD_PD_CLEAR_START ? "start" : "stop", |
| device_id); |
| close(fd); |
| return (error); |
| } |
| |
| close(fd); |
| return (0); |
| } |
| MFI_COMMAND(drive, clear, drive_clear); |
| |
| static int |
| drive_locate(int ac, char **av) |
| { |
| uint16_t device_id; |
| uint32_t opcode; |
| int error, fd; |
| uint8_t mbox[4]; |
| |
| if (ac != 3) { |
| warnx("locate: %s", ac > 3 ? "extra arguments" : |
| "drive and state required"); |
| return (EINVAL); |
| } |
| |
| if (strcasecmp(av[2], "on") == 0 || strcasecmp(av[2], "start") == 0) |
| opcode = MFI_DCMD_PD_LOCATE_START; |
| else if (strcasecmp(av[2], "off") == 0 || |
| strcasecmp(av[2], "stop") == 0) |
| opcode = MFI_DCMD_PD_LOCATE_STOP; |
| else { |
| warnx("locate: invalid state %s", av[2]); |
| return (EINVAL); |
| } |
| |
| fd = mfi_open(mfi_unit, O_RDWR); |
| if (fd < 0) { |
| error = errno; |
| warn("mfi_open"); |
| return (error); |
| } |
| |
| error = mfi_lookup_drive(fd, av[1], &device_id); |
| if (error) { |
| close(fd); |
| return (error); |
| } |
| |
| |
| mbox_store_device_id(&mbox[0], device_id); |
| mbox[2] = 0; |
| mbox[3] = 0; |
| if (mfi_dcmd_command(fd, opcode, NULL, 0, mbox, 4, NULL) < 0) { |
| error = errno; |
| warn("Failed to %s locate on drive %u", |
| opcode == MFI_DCMD_PD_LOCATE_START ? "start" : "stop", |
| device_id); |
| close(fd); |
| return (error); |
| } |
| close(fd); |
| |
| return (0); |
| } |
| MFI_COMMAND(top, locate, drive_locate); |