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