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