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