1 /*- 2 * Copyright (c) 2008 Yahoo!, Inc. 3 * All rights reserved. 4 * Written by: John Baldwin <jhb@FreeBSD.org> 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. Neither the name of the author nor the names of any co-contributors 15 * may be used to endorse or promote products derived from this software 16 * without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 * SUCH DAMAGE. 29 */ 30 31 #include <sys/cdefs.h> 32 __RCSID("$FreeBSD$"); 33 34 #include <sys/param.h> 35 #include <err.h> 36 #include <errno.h> 37 #include <fcntl.h> 38 #include <stdio.h> 39 #include <stdlib.h> 40 #include <string.h> 41 42 #include <camlib.h> 43 #include <cam/scsi/scsi_message.h> 44 #include <cam/scsi/scsi_pass.h> 45 46 #include "mptutil.h" 47 48 static int xptfd; 49 50 static int 51 xpt_open(void) 52 { 53 54 if (xptfd == 0) 55 xptfd = open(XPT_DEVICE, O_RDWR); 56 return (xptfd); 57 } 58 59 /* Fetch the path id of bus 0 for the opened mpt controller. */ 60 static int 61 fetch_path_id(path_id_t *path_id) 62 { 63 struct bus_match_pattern *b; 64 union ccb ccb; 65 size_t bufsize; 66 67 if (xpt_open() < 0) 68 return (ENXIO); 69 70 /* First, find the path id of bus 0 for this mpt controller. */ 71 bzero(&ccb, sizeof(ccb)); 72 73 ccb.ccb_h.func_code = XPT_DEV_MATCH; 74 75 bufsize = sizeof(struct dev_match_result) * 1; 76 ccb.cdm.num_matches = 0; 77 ccb.cdm.match_buf_len = bufsize; 78 ccb.cdm.matches = calloc(1, bufsize); 79 80 bufsize = sizeof(struct dev_match_pattern) * 1; 81 ccb.cdm.num_patterns = 1; 82 ccb.cdm.pattern_buf_len = bufsize; 83 ccb.cdm.patterns = calloc(1, bufsize); 84 85 /* Match mptX bus 0. */ 86 ccb.cdm.patterns[0].type = DEV_MATCH_BUS; 87 b = &ccb.cdm.patterns[0].pattern.bus_pattern; 88 snprintf(b->dev_name, sizeof(b->dev_name), "mpt"); 89 b->unit_number = mpt_unit; 90 b->bus_id = 0; 91 b->flags = BUS_MATCH_NAME | BUS_MATCH_UNIT | BUS_MATCH_BUS_ID; 92 93 if (ioctl(xptfd, CAMIOCOMMAND, &ccb) < 0) { 94 free(ccb.cdm.matches); 95 free(ccb.cdm.patterns); 96 return (errno); 97 } 98 free(ccb.cdm.patterns); 99 100 if (((ccb.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) || 101 (ccb.cdm.status != CAM_DEV_MATCH_LAST)) { 102 warnx("fetch_path_id got CAM error %#x, CDM error %d\n", 103 ccb.ccb_h.status, ccb.cdm.status); 104 free(ccb.cdm.matches); 105 return (EIO); 106 } 107 108 /* We should have exactly 1 match for the bus. */ 109 if (ccb.cdm.num_matches != 1 || 110 ccb.cdm.matches[0].type != DEV_MATCH_BUS) { 111 free(ccb.cdm.matches); 112 return (ENOENT); 113 } 114 *path_id = ccb.cdm.matches[0].result.bus_result.path_id; 115 free(ccb.cdm.matches); 116 return (0); 117 } 118 119 int 120 mpt_query_disk(U8 VolumeBus, U8 VolumeID, struct mpt_query_disk *qd) 121 { 122 struct periph_match_pattern *p; 123 struct periph_match_result *r; 124 union ccb ccb; 125 path_id_t path_id; 126 size_t bufsize; 127 int error, i; 128 129 /* mpt(4) only handles devices on bus 0. */ 130 if (VolumeBus != 0) 131 return (ENXIO); 132 133 if (xpt_open() < 0) 134 return (ENXIO); 135 136 /* Find the path ID of bus 0. */ 137 error = fetch_path_id(&path_id); 138 if (error) 139 return (error); 140 141 bzero(&ccb, sizeof(ccb)); 142 143 ccb.ccb_h.func_code = XPT_DEV_MATCH; 144 ccb.ccb_h.path_id = CAM_XPT_PATH_ID; 145 ccb.ccb_h.target_id = CAM_TARGET_WILDCARD; 146 ccb.ccb_h.target_lun = CAM_LUN_WILDCARD; 147 148 bufsize = sizeof(struct dev_match_result) * 5; 149 ccb.cdm.num_matches = 0; 150 ccb.cdm.match_buf_len = bufsize; 151 ccb.cdm.matches = calloc(1, bufsize); 152 153 bufsize = sizeof(struct dev_match_pattern) * 1; 154 ccb.cdm.num_patterns = 1; 155 ccb.cdm.pattern_buf_len = bufsize; 156 ccb.cdm.patterns = calloc(1, bufsize); 157 158 /* Look for a "da" device at the specified target and lun. */ 159 ccb.cdm.patterns[0].type = DEV_MATCH_PERIPH; 160 p = &ccb.cdm.patterns[0].pattern.periph_pattern; 161 p->path_id = path_id; 162 snprintf(p->periph_name, sizeof(p->periph_name), "da"); 163 p->target_id = VolumeID; 164 p->flags = PERIPH_MATCH_PATH | PERIPH_MATCH_NAME | PERIPH_MATCH_TARGET; 165 166 if (ioctl(xptfd, CAMIOCOMMAND, &ccb) < 0) { 167 i = errno; 168 free(ccb.cdm.matches); 169 free(ccb.cdm.patterns); 170 return (i); 171 } 172 free(ccb.cdm.patterns); 173 174 if (((ccb.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) || 175 (ccb.cdm.status != CAM_DEV_MATCH_LAST)) { 176 warnx("mpt_query_disk got CAM error %#x, CDM error %d\n", 177 ccb.ccb_h.status, ccb.cdm.status); 178 free(ccb.cdm.matches); 179 return (EIO); 180 } 181 182 /* 183 * We should have exactly 1 match for the peripheral. 184 * However, if we don't get a match, don't print an error 185 * message and return ENOENT. 186 */ 187 if (ccb.cdm.num_matches == 0) { 188 free(ccb.cdm.matches); 189 return (ENOENT); 190 } 191 if (ccb.cdm.num_matches != 1) { 192 warnx("mpt_query_disk got %d matches, expected 1", 193 ccb.cdm.num_matches); 194 free(ccb.cdm.matches); 195 return (EIO); 196 } 197 if (ccb.cdm.matches[0].type != DEV_MATCH_PERIPH) { 198 warnx("mpt_query_disk got wrong CAM match"); 199 free(ccb.cdm.matches); 200 return (EIO); 201 } 202 203 /* Copy out the data. */ 204 r = &ccb.cdm.matches[1].result.periph_result; 205 snprintf(qd->devname, sizeof(qd->devname), "%s%d", r->periph_name, 206 r->unit_number); 207 free(ccb.cdm.matches); 208 209 return (0); 210 } 211 212 static int 213 periph_is_volume(CONFIG_PAGE_IOC_2 *ioc2, struct periph_match_result *r) 214 { 215 CONFIG_PAGE_IOC_2_RAID_VOL *vol; 216 int i; 217 218 if (ioc2 == NULL) 219 return (0); 220 vol = ioc2->RaidVolume; 221 for (i = 0; i < ioc2->NumActiveVolumes; vol++, i++) { 222 if (vol->VolumeBus == 0 && vol->VolumeID == r->target_id) 223 return (1); 224 } 225 return (0); 226 } 227 228 /* Much borrowed from scsireadcapacity() in src/sbin/camcontrol/camcontrol.c. */ 229 static int 230 fetch_scsi_capacity(struct cam_device *dev, struct mpt_standalone_disk *disk) 231 { 232 struct scsi_read_capacity_data rcap; 233 struct scsi_read_capacity_data_long rcaplong; 234 union ccb *ccb; 235 int error; 236 237 ccb = cam_getccb(dev); 238 if (ccb == NULL) 239 return (ENOMEM); 240 241 /* Zero the rest of the ccb. */ 242 bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio) - 243 sizeof(struct ccb_hdr)); 244 245 scsi_read_capacity(&ccb->csio, 1, NULL, MSG_SIMPLE_Q_TAG, &rcap, 246 SSD_FULL_SIZE, 5000); 247 248 /* Disable freezing the device queue */ 249 ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; 250 251 if (cam_send_ccb(dev, ccb) < 0) { 252 error = errno; 253 cam_freeccb(ccb); 254 return (error); 255 } 256 257 if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { 258 cam_freeccb(ccb); 259 return (EIO); 260 } 261 cam_freeccb(ccb); 262 263 /* 264 * A last block of 2^32-1 means that the true capacity is over 2TB, 265 * and we need to issue the long READ CAPACITY to get the real 266 * capacity. Otherwise, we're all set. 267 */ 268 if (scsi_4btoul(rcap.addr) != 0xffffffff) { 269 disk->maxlba = scsi_4btoul(rcap.addr); 270 return (0); 271 } 272 273 /* Zero the rest of the ccb. */ 274 bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio) - 275 sizeof(struct ccb_hdr)); 276 277 scsi_read_capacity_16(&ccb->csio, 1, NULL, MSG_SIMPLE_Q_TAG, 0, 0, 0, 278 &rcaplong, SSD_FULL_SIZE, 5000); 279 280 /* Disable freezing the device queue */ 281 ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; 282 283 if (cam_send_ccb(dev, ccb) < 0) { 284 error = errno; 285 cam_freeccb(ccb); 286 return (error); 287 } 288 289 if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { 290 cam_freeccb(ccb); 291 return (EIO); 292 } 293 cam_freeccb(ccb); 294 295 disk->maxlba = scsi_8btou64(rcaplong.addr); 296 return (0); 297 } 298 299 /* Borrowed heavily from scsi_all.c:scsi_print_inquiry(). */ 300 static void 301 format_scsi_inquiry(struct mpt_standalone_disk *disk, 302 struct scsi_inquiry_data *inq_data) 303 { 304 char vendor[16], product[48], revision[16], rstr[12]; 305 306 if (SID_QUAL_IS_VENDOR_UNIQUE(inq_data)) 307 return; 308 if (SID_TYPE(inq_data) != T_DIRECT) 309 return; 310 if (SID_QUAL(inq_data) != SID_QUAL_LU_CONNECTED) 311 return; 312 313 cam_strvis(vendor, inq_data->vendor, sizeof(inq_data->vendor), 314 sizeof(vendor)); 315 cam_strvis(product, inq_data->product, sizeof(inq_data->product), 316 sizeof(product)); 317 cam_strvis(revision, inq_data->revision, sizeof(inq_data->revision), 318 sizeof(revision)); 319 320 /* Hack for SATA disks, no idea how to tell speed. */ 321 if (strcmp(vendor, "ATA") == 0) { 322 snprintf(disk->inqstring, sizeof(disk->inqstring), 323 "<%s %s> SATA", product, revision); 324 return; 325 } 326 327 switch (SID_ANSI_REV(inq_data)) { 328 case SCSI_REV_CCS: 329 strcpy(rstr, "SCSI-CCS"); 330 break; 331 case 5: 332 strcpy(rstr, "SAS"); 333 break; 334 default: 335 snprintf(rstr, sizeof (rstr), "SCSI-%d", 336 SID_ANSI_REV(inq_data)); 337 break; 338 } 339 snprintf(disk->inqstring, sizeof(disk->inqstring), "<%s %s %s> %s", 340 vendor, product, revision, rstr); 341 } 342 343 /* Much borrowed from scsiinquiry() in src/sbin/camcontrol/camcontrol.c. */ 344 static int 345 fetch_scsi_inquiry(struct cam_device *dev, struct mpt_standalone_disk *disk) 346 { 347 struct scsi_inquiry_data *inq_buf; 348 union ccb *ccb; 349 int error; 350 351 ccb = cam_getccb(dev); 352 if (ccb == NULL) 353 return (ENOMEM); 354 355 /* Zero the rest of the ccb. */ 356 bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio) - 357 sizeof(struct ccb_hdr)); 358 359 inq_buf = calloc(1, sizeof(*inq_buf)); 360 if (inq_buf == NULL) { 361 cam_freeccb(ccb); 362 return (ENOMEM); 363 } 364 scsi_inquiry(&ccb->csio, 1, NULL, MSG_SIMPLE_Q_TAG, (void *)inq_buf, 365 SHORT_INQUIRY_LENGTH, 0, 0, SSD_FULL_SIZE, 5000); 366 367 /* Disable freezing the device queue */ 368 ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; 369 370 if (cam_send_ccb(dev, ccb) < 0) { 371 error = errno; 372 free(inq_buf); 373 cam_freeccb(ccb); 374 return (error); 375 } 376 377 if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { 378 free(inq_buf); 379 cam_freeccb(ccb); 380 return (EIO); 381 } 382 383 cam_freeccb(ccb); 384 format_scsi_inquiry(disk, inq_buf); 385 free(inq_buf); 386 return (0); 387 } 388 389 int 390 mpt_fetch_disks(int fd, int *ndisks, struct mpt_standalone_disk **disksp) 391 { 392 CONFIG_PAGE_IOC_2 *ioc2; 393 struct mpt_standalone_disk *disks; 394 struct periph_match_pattern *p; 395 struct periph_match_result *r; 396 struct cam_device *dev; 397 union ccb ccb; 398 path_id_t path_id; 399 size_t bufsize; 400 u_int i; 401 int count, error; 402 403 if (xpt_open() < 0) 404 return (ENXIO); 405 406 error = fetch_path_id(&path_id); 407 if (error) 408 return (error); 409 410 for (count = 100;; count+= 100) { 411 /* Try to fetch 'count' disks in one go. */ 412 bzero(&ccb, sizeof(ccb)); 413 414 ccb.ccb_h.func_code = XPT_DEV_MATCH; 415 416 bufsize = sizeof(struct dev_match_result) * (count + 1); 417 ccb.cdm.num_matches = 0; 418 ccb.cdm.match_buf_len = bufsize; 419 ccb.cdm.matches = calloc(1, bufsize); 420 421 bufsize = sizeof(struct dev_match_pattern) * 1; 422 ccb.cdm.num_patterns = 1; 423 ccb.cdm.pattern_buf_len = bufsize; 424 ccb.cdm.patterns = calloc(1, bufsize); 425 426 /* Match any "da" peripherals. */ 427 ccb.cdm.patterns[0].type = DEV_MATCH_PERIPH; 428 p = &ccb.cdm.patterns[0].pattern.periph_pattern; 429 p->path_id = path_id; 430 snprintf(p->periph_name, sizeof(p->periph_name), "da"); 431 p->flags = PERIPH_MATCH_PATH | PERIPH_MATCH_NAME; 432 433 if (ioctl(xptfd, CAMIOCOMMAND, &ccb) < 0) { 434 i = errno; 435 free(ccb.cdm.matches); 436 free(ccb.cdm.patterns); 437 return (i); 438 } 439 free(ccb.cdm.patterns); 440 441 /* Check for CCB errors. */ 442 if ((ccb.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { 443 free(ccb.cdm.matches); 444 return (EIO); 445 } 446 447 /* If we need a longer list, try again. */ 448 if (ccb.cdm.status == CAM_DEV_MATCH_MORE) { 449 free(ccb.cdm.matches); 450 continue; 451 } 452 453 /* If we got an error, abort. */ 454 if (ccb.cdm.status != CAM_DEV_MATCH_LAST) { 455 free(ccb.cdm.matches); 456 return (EIO); 457 } 458 break; 459 } 460 461 /* Shortcut if we don't have any "da" devices. */ 462 if (ccb.cdm.num_matches == 0) { 463 free(ccb.cdm.matches); 464 *ndisks = 0; 465 *disksp = NULL; 466 return (0); 467 } 468 469 /* We should have N matches, 1 for each "da" device. */ 470 for (i = 0; i < ccb.cdm.num_matches; i++) { 471 if (ccb.cdm.matches[i].type != DEV_MATCH_PERIPH) { 472 warnx("mpt_fetch_disks got wrong CAM matches"); 473 free(ccb.cdm.matches); 474 return (EIO); 475 } 476 } 477 478 /* 479 * Some of the "da" peripherals may be for RAID volumes, so 480 * fetch the IOC 2 page (list of RAID volumes) so we can 481 * exclude them from the list. 482 */ 483 ioc2 = mpt_read_ioc_page(fd, 2, NULL); 484 disks = calloc(ccb.cdm.num_matches, sizeof(*disks)); 485 count = 0; 486 for (i = 0; i < ccb.cdm.num_matches; i++) { 487 r = &ccb.cdm.matches[i].result.periph_result; 488 if (periph_is_volume(ioc2, r)) 489 continue; 490 disks[count].bus = 0; 491 disks[count].target = r->target_id; 492 snprintf(disks[count].devname, sizeof(disks[count].devname), 493 "%s%d", r->periph_name, r->unit_number); 494 495 dev = cam_open_device(disks[count].devname, O_RDWR); 496 if (dev != NULL) { 497 fetch_scsi_capacity(dev, &disks[count]); 498 fetch_scsi_inquiry(dev, &disks[count]); 499 cam_close_device(dev); 500 } 501 count++; 502 } 503 free(ccb.cdm.matches); 504 free(ioc2); 505 506 *ndisks = count; 507 *disksp = disks; 508 return (0); 509 } 510 511 /* 512 * Instruct the mpt(4) device to rescan its busses to find new devices 513 * such as disks whose RAID physdisk page was removed or volumes that 514 * were created. If id is -1, the entire bus is rescanned. 515 * Otherwise, only devices at the specified ID are rescanned. If bus 516 * is -1, then all busses are scanned instead of the specified bus. 517 * Note that currently, only bus 0 is supported. 518 */ 519 int 520 mpt_rescan_bus(int bus, int id) 521 { 522 union ccb ccb; 523 path_id_t path_id; 524 int error; 525 526 /* mpt(4) only handles devices on bus 0. */ 527 if (bus != -1 && bus != 0) 528 return (EINVAL); 529 530 if (xpt_open() < 0) 531 return (ENXIO); 532 533 error = fetch_path_id(&path_id); 534 if (error) 535 return (error); 536 537 /* Perform the actual rescan. */ 538 bzero(&ccb, sizeof(ccb)); 539 ccb.ccb_h.path_id = path_id; 540 if (id == -1) { 541 ccb.ccb_h.func_code = XPT_SCAN_BUS; 542 ccb.ccb_h.target_id = CAM_TARGET_WILDCARD; 543 ccb.ccb_h.target_lun = CAM_LUN_WILDCARD; 544 ccb.ccb_h.timeout = 5000; 545 } else { 546 ccb.ccb_h.func_code = XPT_SCAN_LUN; 547 ccb.ccb_h.target_id = id; 548 ccb.ccb_h.target_lun = 0; 549 } 550 ccb.crcn.flags = CAM_FLAG_NONE; 551 552 /* Run this at a low priority. */ 553 ccb.ccb_h.pinfo.priority = 5; 554 555 if (ioctl(xptfd, CAMIOCOMMAND, &ccb) == -1) 556 return (errno); 557 558 if ((ccb.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { 559 warnx("mpt_rescan_bus rescan got CAM error %#x\n", 560 ccb.ccb_h.status & CAM_STATUS_MASK); 561 return (EIO); 562 } 563 564 return (0); 565 } 566