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