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