1 /*- 2 * Copyright (c) 2008, 2009 Yahoo!, Inc. 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. The names of the authors may not be used to endorse or promote 14 * products derived from this software without specific prior written 15 * permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 * 29 * $FreeBSD$ 30 */ 31 32 #include <sys/types.h> 33 #include <sys/errno.h> 34 #include <ctype.h> 35 #include <err.h> 36 #include <libutil.h> 37 #include <limits.h> 38 #include <stdio.h> 39 #include <stdlib.h> 40 #include <string.h> 41 #include <strings.h> 42 #include <unistd.h> 43 #include <cam/scsi/scsi_all.h> 44 #include "mfiutil.h" 45 46 MFI_TABLE(top, drive); 47 48 /* 49 * Print the name of a drive either by drive number as %2u or by enclosure:slot 50 * as Exx:Sxx (or both). Use default unless command line options override it 51 * and the command allows this (which we usually do unless we already print 52 * both). We prefer pinfo if given, otherwise try to look it up by device_id. 53 */ 54 const char * 55 mfi_drive_name(struct mfi_pd_info *pinfo, uint16_t device_id, uint32_t def) 56 { 57 struct mfi_pd_info info; 58 static char buf[16]; 59 char *p; 60 int error, fd, len; 61 62 if ((def & MFI_DNAME_HONOR_OPTS) != 0 && 63 (mfi_opts & (MFI_DNAME_ES|MFI_DNAME_DEVICE_ID)) != 0) 64 def = mfi_opts & (MFI_DNAME_ES|MFI_DNAME_DEVICE_ID); 65 66 buf[0] = '\0'; 67 if (pinfo == NULL && def & MFI_DNAME_ES) { 68 /* Fallback in case of error, just ignore flags. */ 69 if (device_id == 0xffff) 70 snprintf(buf, sizeof(buf), "MISSING"); 71 else 72 snprintf(buf, sizeof(buf), "%2u", device_id); 73 74 fd = mfi_open(mfi_unit); 75 if (fd < 0) { 76 warn("mfi_open"); 77 return (buf); 78 } 79 80 /* Get the info for this drive. */ 81 if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) { 82 warn("Failed to fetch info for drive %2u", device_id); 83 close(fd); 84 return (buf); 85 } 86 87 close(fd); 88 pinfo = &info; 89 } 90 91 p = buf; 92 len = sizeof(buf); 93 if (def & MFI_DNAME_DEVICE_ID) { 94 if (device_id == 0xffff) 95 error = snprintf(p, len, "MISSING"); 96 else 97 error = snprintf(p, len, "%2u", device_id); 98 if (error >= 0) { 99 p += error; 100 len -= error; 101 } 102 } 103 if ((def & (MFI_DNAME_ES|MFI_DNAME_DEVICE_ID)) == 104 (MFI_DNAME_ES|MFI_DNAME_DEVICE_ID) && len >= 2) { 105 *p++ = ' '; 106 len--; 107 *p = '\0'; 108 len--; 109 } 110 if (def & MFI_DNAME_ES) { 111 if (pinfo->encl_device_id == 0xffff) 112 error = snprintf(p, len, "S%u", 113 pinfo->slot_number); 114 else if (pinfo->encl_device_id == pinfo->ref.v.device_id) 115 error = snprintf(p, len, "E%u", 116 pinfo->encl_index); 117 else 118 error = snprintf(p, len, "E%u:S%u", 119 pinfo->encl_index, pinfo->slot_number); 120 if (error >= 0) { 121 p += error; 122 len -= error; 123 } 124 } 125 126 return (buf); 127 } 128 129 const char * 130 mfi_pdstate(enum mfi_pd_state state) 131 { 132 static char buf[16]; 133 134 switch (state) { 135 case MFI_PD_STATE_UNCONFIGURED_GOOD: 136 return ("UNCONFIGURED GOOD"); 137 case MFI_PD_STATE_UNCONFIGURED_BAD: 138 return ("UNCONFIGURED BAD"); 139 case MFI_PD_STATE_HOT_SPARE: 140 return ("HOT SPARE"); 141 case MFI_PD_STATE_OFFLINE: 142 return ("OFFLINE"); 143 case MFI_PD_STATE_FAILED: 144 return ("FAILED"); 145 case MFI_PD_STATE_REBUILD: 146 return ("REBUILD"); 147 case MFI_PD_STATE_ONLINE: 148 return ("ONLINE"); 149 case MFI_PD_STATE_COPYBACK: 150 return ("COPYBACK"); 151 case MFI_PD_STATE_SYSTEM: 152 return ("SYSTEM"); 153 default: 154 sprintf(buf, "PSTATE 0x%04x", state); 155 return (buf); 156 } 157 } 158 159 int 160 mfi_lookup_drive(int fd, char *drive, uint16_t *device_id) 161 { 162 struct mfi_pd_list *list; 163 long val; 164 int error; 165 u_int i; 166 char *cp; 167 uint8_t encl, slot; 168 169 /* Look for a raw device id first. */ 170 val = strtol(drive, &cp, 0); 171 if (*cp == '\0') { 172 if (val < 0 || val >= 0xffff) 173 goto bad; 174 *device_id = val; 175 return (0); 176 } 177 178 /* Support for MegaCli style [Exx]:Syy notation. */ 179 if (toupper(drive[0]) == 'E' || toupper(drive[0]) == 'S') { 180 if (drive[1] == '\0') 181 goto bad; 182 cp = drive; 183 if (toupper(drive[0]) == 'E') { 184 cp++; /* Eat 'E' */ 185 val = strtol(cp, &cp, 0); 186 if (val < 0 || val > 0xff || *cp != ':') 187 goto bad; 188 encl = val; 189 cp++; /* Eat ':' */ 190 if (toupper(*cp) != 'S') 191 goto bad; 192 } else 193 encl = 0xff; 194 cp++; /* Eat 'S' */ 195 if (*cp == '\0') 196 goto bad; 197 val = strtol(cp, &cp, 0); 198 if (val < 0 || val > 0xff || *cp != '\0') 199 goto bad; 200 slot = val; 201 202 if (mfi_pd_get_list(fd, &list, NULL) < 0) { 203 error = errno; 204 warn("Failed to fetch drive list"); 205 return (error); 206 } 207 208 for (i = 0; i < list->count; i++) { 209 if (list->addr[i].scsi_dev_type != 0) 210 continue; 211 212 if (((encl == 0xff && 213 list->addr[i].encl_device_id == 0xffff) || 214 list->addr[i].encl_index == encl) && 215 list->addr[i].slot_number == slot) { 216 *device_id = list->addr[i].device_id; 217 free(list); 218 return (0); 219 } 220 } 221 free(list); 222 warnx("Unknown drive %s", drive); 223 return (EINVAL); 224 } 225 226 bad: 227 warnx("Invalid drive number %s", drive); 228 return (EINVAL); 229 } 230 231 static void 232 mbox_store_device_id(uint8_t *mbox, uint16_t device_id) 233 { 234 235 mbox[0] = device_id & 0xff; 236 mbox[1] = device_id >> 8; 237 } 238 239 void 240 mbox_store_pdref(uint8_t *mbox, union mfi_pd_ref *ref) 241 { 242 243 mbox[0] = ref->v.device_id & 0xff; 244 mbox[1] = ref->v.device_id >> 8; 245 mbox[2] = ref->v.seq_num & 0xff; 246 mbox[3] = ref->v.seq_num >> 8; 247 } 248 249 int 250 mfi_pd_get_list(int fd, struct mfi_pd_list **listp, uint8_t *statusp) 251 { 252 struct mfi_pd_list *list; 253 uint32_t list_size; 254 255 /* 256 * Keep fetching the list in a loop until we have a large enough 257 * buffer to hold the entire list. 258 */ 259 list = NULL; 260 list_size = 1024; 261 fetch: 262 list = reallocf(list, list_size); 263 if (list == NULL) 264 return (-1); 265 if (mfi_dcmd_command(fd, MFI_DCMD_PD_GET_LIST, list, list_size, NULL, 266 0, statusp) < 0) { 267 free(list); 268 return (-1); 269 } 270 271 if (list->size > list_size) { 272 list_size = list->size; 273 goto fetch; 274 } 275 276 *listp = list; 277 return (0); 278 } 279 280 int 281 mfi_pd_get_info(int fd, uint16_t device_id, struct mfi_pd_info *info, 282 uint8_t *statusp) 283 { 284 uint8_t mbox[2]; 285 286 mbox_store_device_id(&mbox[0], device_id); 287 return (mfi_dcmd_command(fd, MFI_DCMD_PD_GET_INFO, info, 288 sizeof(struct mfi_pd_info), mbox, 2, statusp)); 289 } 290 291 static void 292 cam_strvis(char *dst, const char *src, int srclen, int dstlen) 293 { 294 295 /* Trim leading/trailing spaces, nulls. */ 296 while (srclen > 0 && src[0] == ' ') 297 src++, srclen--; 298 while (srclen > 0 299 && (src[srclen-1] == ' ' || src[srclen-1] == '\0')) 300 srclen--; 301 302 while (srclen > 0 && dstlen > 1) { 303 char *cur_pos = dst; 304 305 if (*src < 0x20) { 306 /* SCSI-II Specifies that these should never occur. */ 307 /* non-printable character */ 308 if (dstlen > 4) { 309 *cur_pos++ = '\\'; 310 *cur_pos++ = ((*src & 0300) >> 6) + '0'; 311 *cur_pos++ = ((*src & 0070) >> 3) + '0'; 312 *cur_pos++ = ((*src & 0007) >> 0) + '0'; 313 } else { 314 *cur_pos++ = '?'; 315 } 316 } else { 317 /* normal character */ 318 *cur_pos++ = *src; 319 } 320 src++; 321 srclen--; 322 dstlen -= cur_pos - dst; 323 dst = cur_pos; 324 } 325 *dst = '\0'; 326 } 327 328 /* Borrowed heavily from scsi_all.c:scsi_print_inquiry(). */ 329 const char * 330 mfi_pd_inq_string(struct mfi_pd_info *info) 331 { 332 struct scsi_inquiry_data *inq_data; 333 char vendor[16], product[48], revision[16], rstr[12], serial[SID_VENDOR_SPECIFIC_0_SIZE]; 334 static char inq_string[64]; 335 336 inq_data = (struct scsi_inquiry_data *)info->inquiry_data; 337 if (SID_QUAL_IS_VENDOR_UNIQUE(inq_data)) 338 return (NULL); 339 if (SID_TYPE(inq_data) != T_DIRECT) 340 return (NULL); 341 if (SID_QUAL(inq_data) != SID_QUAL_LU_CONNECTED) 342 return (NULL); 343 344 cam_strvis(vendor, inq_data->vendor, sizeof(inq_data->vendor), 345 sizeof(vendor)); 346 cam_strvis(product, inq_data->product, sizeof(inq_data->product), 347 sizeof(product)); 348 cam_strvis(revision, inq_data->revision, sizeof(inq_data->revision), 349 sizeof(revision)); 350 cam_strvis(serial, (char *)inq_data->vendor_specific0, sizeof(inq_data->vendor_specific0), 351 sizeof(serial)); 352 353 /* Hack for SATA disks, no idea how to tell speed. */ 354 if (strcmp(vendor, "ATA") == 0) { 355 snprintf(inq_string, sizeof(inq_string), "<%s %s serial=%s> SATA", 356 product, revision, serial); 357 return (inq_string); 358 } 359 360 switch (SID_ANSI_REV(inq_data)) { 361 case SCSI_REV_CCS: 362 strcpy(rstr, "SCSI-CCS"); 363 break; 364 case 5: 365 strcpy(rstr, "SAS"); 366 break; 367 default: 368 snprintf(rstr, sizeof (rstr), "SCSI-%d", 369 SID_ANSI_REV(inq_data)); 370 break; 371 } 372 snprintf(inq_string, sizeof(inq_string), "<%s %s %s serial=%s> %s", vendor, 373 product, revision, serial, rstr); 374 return (inq_string); 375 } 376 377 /* Helper function to set a drive to a given state. */ 378 static int 379 drive_set_state(char *drive, uint16_t new_state) 380 { 381 struct mfi_pd_info info; 382 uint16_t device_id; 383 uint8_t mbox[6]; 384 int error, fd; 385 386 fd = mfi_open(mfi_unit); 387 if (fd < 0) { 388 error = errno; 389 warn("mfi_open"); 390 return (error); 391 } 392 393 error = mfi_lookup_drive(fd, drive, &device_id); 394 if (error) { 395 close(fd); 396 return (error); 397 } 398 399 /* Get the info for this drive. */ 400 if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) { 401 error = errno; 402 warn("Failed to fetch info for drive %u", device_id); 403 close(fd); 404 return (error); 405 } 406 407 /* Try to change the state. */ 408 if (info.fw_state == new_state) { 409 warnx("Drive %u is already in the desired state", device_id); 410 close(fd); 411 return (EINVAL); 412 } 413 414 mbox_store_pdref(&mbox[0], &info.ref); 415 mbox[4] = new_state & 0xff; 416 mbox[5] = new_state >> 8; 417 if (mfi_dcmd_command(fd, MFI_DCMD_PD_STATE_SET, NULL, 0, mbox, 6, 418 NULL) < 0) { 419 error = errno; 420 warn("Failed to set drive %u to %s", device_id, 421 mfi_pdstate(new_state)); 422 close(fd); 423 return (error); 424 } 425 426 close(fd); 427 428 return (0); 429 } 430 431 static int 432 fail_drive(int ac, char **av) 433 { 434 435 if (ac != 2) { 436 warnx("fail: %s", ac > 2 ? "extra arguments" : 437 "drive required"); 438 return (EINVAL); 439 } 440 441 return (drive_set_state(av[1], MFI_PD_STATE_FAILED)); 442 } 443 MFI_COMMAND(top, fail, fail_drive); 444 445 static int 446 good_drive(int ac, char **av) 447 { 448 449 if (ac != 2) { 450 warnx("good: %s", ac > 2 ? "extra arguments" : 451 "drive required"); 452 return (EINVAL); 453 } 454 455 return (drive_set_state(av[1], MFI_PD_STATE_UNCONFIGURED_GOOD)); 456 } 457 MFI_COMMAND(top, good, good_drive); 458 459 static int 460 rebuild_drive(int ac, char **av) 461 { 462 463 if (ac != 2) { 464 warnx("rebuild: %s", ac > 2 ? "extra arguments" : 465 "drive required"); 466 return (EINVAL); 467 } 468 469 return (drive_set_state(av[1], MFI_PD_STATE_REBUILD)); 470 } 471 MFI_COMMAND(top, rebuild, rebuild_drive); 472 473 static int 474 start_rebuild(int ac, char **av) 475 { 476 struct mfi_pd_info info; 477 uint16_t device_id; 478 uint8_t mbox[4]; 479 int error, fd; 480 481 if (ac != 2) { 482 warnx("start rebuild: %s", ac > 2 ? "extra arguments" : 483 "drive required"); 484 return (EINVAL); 485 } 486 487 fd = mfi_open(mfi_unit); 488 if (fd < 0) { 489 error = errno; 490 warn("mfi_open"); 491 return (error); 492 } 493 494 error = mfi_lookup_drive(fd, av[1], &device_id); 495 if (error) { 496 close(fd); 497 return (error); 498 } 499 500 /* Get the info for this drive. */ 501 if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) { 502 error = errno; 503 warn("Failed to fetch info for drive %u", device_id); 504 close(fd); 505 return (error); 506 } 507 508 /* Check the state, must be REBUILD. */ 509 if (info.fw_state != MFI_PD_STATE_REBUILD) { 510 warnx("Drive %d is not in the REBUILD state", device_id); 511 close(fd); 512 return (EINVAL); 513 } 514 515 /* Start the rebuild. */ 516 mbox_store_pdref(&mbox[0], &info.ref); 517 if (mfi_dcmd_command(fd, MFI_DCMD_PD_REBUILD_START, NULL, 0, mbox, 4, 518 NULL) < 0) { 519 error = errno; 520 warn("Failed to start rebuild on drive %u", device_id); 521 close(fd); 522 return (error); 523 } 524 close(fd); 525 526 return (0); 527 } 528 MFI_COMMAND(start, rebuild, start_rebuild); 529 530 static int 531 abort_rebuild(int ac, char **av) 532 { 533 struct mfi_pd_info info; 534 uint16_t device_id; 535 uint8_t mbox[4]; 536 int error, fd; 537 538 if (ac != 2) { 539 warnx("abort rebuild: %s", ac > 2 ? "extra arguments" : 540 "drive required"); 541 return (EINVAL); 542 } 543 544 fd = mfi_open(mfi_unit); 545 if (fd < 0) { 546 error = errno; 547 warn("mfi_open"); 548 return (error); 549 } 550 551 error = mfi_lookup_drive(fd, av[1], &device_id); 552 if (error) { 553 close(fd); 554 return (error); 555 } 556 557 /* Get the info for this drive. */ 558 if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) { 559 error = errno; 560 warn("Failed to fetch info for drive %u", device_id); 561 close(fd); 562 return (error); 563 } 564 565 /* Check the state, must be REBUILD. */ 566 if (info.fw_state != MFI_PD_STATE_REBUILD) { 567 warn("Drive %d is not in the REBUILD state", device_id); 568 close(fd); 569 return (EINVAL); 570 } 571 572 /* Abort the rebuild. */ 573 mbox_store_pdref(&mbox[0], &info.ref); 574 if (mfi_dcmd_command(fd, MFI_DCMD_PD_REBUILD_ABORT, NULL, 0, mbox, 4, 575 NULL) < 0) { 576 error = errno; 577 warn("Failed to abort rebuild on drive %u", device_id); 578 close(fd); 579 return (error); 580 } 581 close(fd); 582 583 return (0); 584 } 585 MFI_COMMAND(abort, rebuild, abort_rebuild); 586 587 static int 588 drive_progress(int ac, char **av) 589 { 590 struct mfi_pd_info info; 591 uint16_t device_id; 592 int error, fd; 593 594 if (ac != 2) { 595 warnx("drive progress: %s", ac > 2 ? "extra arguments" : 596 "drive required"); 597 return (EINVAL); 598 } 599 600 fd = mfi_open(mfi_unit); 601 if (fd < 0) { 602 error = errno; 603 warn("mfi_open"); 604 return (error); 605 } 606 607 error = mfi_lookup_drive(fd, av[1], &device_id); 608 if (error) { 609 close(fd); 610 return (error); 611 } 612 613 /* Get the info for this drive. */ 614 if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) { 615 error = errno; 616 warn("Failed to fetch info for drive %u", device_id); 617 close(fd); 618 return (error); 619 } 620 close(fd); 621 622 /* Display any of the active events. */ 623 if (info.prog_info.active & MFI_PD_PROGRESS_REBUILD) 624 mfi_display_progress("Rebuild", &info.prog_info.rbld); 625 if (info.prog_info.active & MFI_PD_PROGRESS_PATROL) 626 mfi_display_progress("Patrol Read", &info.prog_info.patrol); 627 if (info.prog_info.active & MFI_PD_PROGRESS_CLEAR) 628 mfi_display_progress("Clear", &info.prog_info.clear); 629 if ((info.prog_info.active & (MFI_PD_PROGRESS_REBUILD | 630 MFI_PD_PROGRESS_PATROL | MFI_PD_PROGRESS_CLEAR)) == 0) 631 printf("No activity in progress for drive %s.\n", 632 mfi_drive_name(NULL, device_id, 633 MFI_DNAME_DEVICE_ID|MFI_DNAME_HONOR_OPTS)); 634 635 return (0); 636 } 637 MFI_COMMAND(drive, progress, drive_progress); 638 639 static int 640 drive_clear(int ac, char **av) 641 { 642 struct mfi_pd_info info; 643 uint32_t opcode; 644 uint16_t device_id; 645 uint8_t mbox[4]; 646 char *s1; 647 int error, fd; 648 649 if (ac != 3) { 650 warnx("drive clear: %s", ac > 3 ? "extra arguments" : 651 "drive and action requires"); 652 return (EINVAL); 653 } 654 655 for (s1 = av[2]; *s1 != '\0'; s1++) 656 *s1 = tolower(*s1); 657 if (strcmp(av[2], "start") == 0) 658 opcode = MFI_DCMD_PD_CLEAR_START; 659 else if ((strcmp(av[2], "stop") == 0) || (strcmp(av[2], "abort") == 0)) 660 opcode = MFI_DCMD_PD_CLEAR_ABORT; 661 else { 662 warnx("drive clear: invalid action, must be 'start' or 'stop'\n"); 663 return (EINVAL); 664 } 665 666 fd = mfi_open(mfi_unit); 667 if (fd < 0) { 668 error = errno; 669 warn("mfi_open"); 670 return (error); 671 } 672 673 error = mfi_lookup_drive(fd, av[1], &device_id); 674 if (error) { 675 close(fd); 676 return (error); 677 } 678 679 /* Get the info for this drive. */ 680 if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) { 681 error = errno; 682 warn("Failed to fetch info for drive %u", device_id); 683 close(fd); 684 return (error); 685 } 686 687 mbox_store_pdref(&mbox[0], &info.ref); 688 if (mfi_dcmd_command(fd, opcode, NULL, 0, mbox, 4, NULL) < 0) { 689 error = errno; 690 warn("Failed to %s clear on drive %u", 691 opcode == MFI_DCMD_PD_CLEAR_START ? "start" : "stop", 692 device_id); 693 close(fd); 694 return (error); 695 } 696 697 close(fd); 698 return (0); 699 } 700 MFI_COMMAND(drive, clear, drive_clear); 701 702 static int 703 drive_locate(int ac, char **av) 704 { 705 uint16_t device_id; 706 uint32_t opcode; 707 int error, fd; 708 uint8_t mbox[4]; 709 710 if (ac != 3) { 711 warnx("locate: %s", ac > 3 ? "extra arguments" : 712 "drive and state required"); 713 return (EINVAL); 714 } 715 716 if (strcasecmp(av[2], "on") == 0 || strcasecmp(av[2], "start") == 0) 717 opcode = MFI_DCMD_PD_LOCATE_START; 718 else if (strcasecmp(av[2], "off") == 0 || 719 strcasecmp(av[2], "stop") == 0) 720 opcode = MFI_DCMD_PD_LOCATE_STOP; 721 else { 722 warnx("locate: invalid state %s", av[2]); 723 return (EINVAL); 724 } 725 726 fd = mfi_open(mfi_unit); 727 if (fd < 0) { 728 error = errno; 729 warn("mfi_open"); 730 return (error); 731 } 732 733 error = mfi_lookup_drive(fd, av[1], &device_id); 734 if (error) { 735 close(fd); 736 return (error); 737 } 738 739 740 mbox_store_device_id(&mbox[0], device_id); 741 mbox[2] = 0; 742 mbox[3] = 0; 743 if (mfi_dcmd_command(fd, opcode, NULL, 0, mbox, 4, NULL) < 0) { 744 error = errno; 745 warn("Failed to %s locate on drive %u", 746 opcode == MFI_DCMD_PD_LOCATE_START ? "start" : "stop", 747 device_id); 748 close(fd); 749 return (error); 750 } 751 close(fd); 752 753 return (0); 754 } 755 MFI_COMMAND(top, locate, drive_locate); 756