mpt_cam.c revision 204090
1169689Skan/*- 2169689Skan * Copyright (c) 2008 Yahoo!, Inc. 3169689Skan * All rights reserved. 4169689Skan * Written by: John Baldwin <jhb@FreeBSD.org> 5169689Skan * 6169689Skan * Redistribution and use in source and binary forms, with or without 7169689Skan * modification, are permitted provided that the following conditions 8169689Skan * are met: 9169689Skan * 1. Redistributions of source code must retain the above copyright 10169689Skan * notice, this list of conditions and the following disclaimer. 11169689Skan * 2. Redistributions in binary form must reproduce the above copyright 12169689Skan * notice, this list of conditions and the following disclaimer in the 13169689Skan * documentation and/or other materials provided with the distribution. 14169689Skan * 3. Neither the name of the author nor the names of any co-contributors 15169689Skan * may be used to endorse or promote products derived from this software 16169689Skan * without specific prior written permission. 17169689Skan * 18169689Skan * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19169689Skan * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20169689Skan * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21169689Skan * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22169689Skan * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23169689Skan * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24169689Skan * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25169689Skan * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26169689Skan * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27169689Skan * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28169689Skan * SUCH DAMAGE. 29169689Skan */ 30169689Skan 31169689Skan#include <sys/cdefs.h> 32169689Skan__RCSID("$FreeBSD: head/usr.sbin/mptutil/mpt_cam.c 204090 2010-02-19 15:16:00Z jhb $"); 33169689Skan 34169689Skan#include <sys/param.h> 35169689Skan#include <err.h> 36169689Skan#include <errno.h> 37169689Skan#include <fcntl.h> 38169689Skan#include <stdio.h> 39169689Skan#include <stdlib.h> 40169689Skan#include <string.h> 41169689Skan 42169689Skan#include <camlib.h> 43169689Skan#include <cam/scsi/scsi_message.h> 44169689Skan#include <cam/scsi/scsi_pass.h> 45169689Skan 46169689Skan#include "mptutil.h" 47169689Skan 48169689Skanstatic int xptfd; 49169689Skan 50169689Skanstatic int 51169689Skanxpt_open(void) 52169689Skan{ 53169689Skan 54169689Skan if (xptfd == 0) 55169689Skan xptfd = open(XPT_DEVICE, O_RDWR); 56169689Skan return (xptfd); 57169689Skan} 58169689Skan 59169689Skan/* Fetch the path id of bus 0 for the opened mpt controller. */ 60169689Skanstatic int 61169689Skanfetch_path_id(path_id_t *path_id) 62169689Skan{ 63169689Skan struct bus_match_pattern *b; 64169689Skan union ccb ccb; 65169689Skan size_t bufsize; 66169689Skan 67169689Skan if (xpt_open() < 0) 68169689Skan return (ENXIO); 69169689Skan 70169689Skan /* First, find the path id of bus 0 for this mpt controller. */ 71169689Skan bzero(&ccb, sizeof(ccb)); 72169689Skan 73169689Skan ccb.ccb_h.func_code = XPT_DEV_MATCH; 74169689Skan 75169689Skan bufsize = sizeof(struct dev_match_result) * 1; 76169689Skan ccb.cdm.num_matches = 0; 77169689Skan ccb.cdm.match_buf_len = bufsize; 78169689Skan ccb.cdm.matches = calloc(1, bufsize); 79169689Skan 80169689Skan bufsize = sizeof(struct dev_match_pattern) * 1; 81169689Skan ccb.cdm.num_patterns = 1; 82169689Skan ccb.cdm.pattern_buf_len = bufsize; 83169689Skan ccb.cdm.patterns = calloc(1, bufsize); 84169689Skan 85169689Skan /* Match mptX bus 0. */ 86169689Skan ccb.cdm.patterns[0].type = DEV_MATCH_BUS; 87169689Skan b = &ccb.cdm.patterns[0].pattern.bus_pattern; 88169689Skan snprintf(b->dev_name, sizeof(b->dev_name), "mpt"); 89169689Skan b->unit_number = mpt_unit; 90169689Skan b->bus_id = 0; 91169689Skan b->flags = BUS_MATCH_NAME | BUS_MATCH_UNIT | BUS_MATCH_BUS_ID; 92169689Skan 93169689Skan if (ioctl(xptfd, CAMIOCOMMAND, &ccb) < 0) { 94169689Skan free(ccb.cdm.matches); 95169689Skan free(ccb.cdm.patterns); 96169689Skan return (errno); 97169689Skan } 98169689Skan free(ccb.cdm.patterns); 99169689Skan 100169689Skan if (((ccb.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) || 101169689Skan (ccb.cdm.status != CAM_DEV_MATCH_LAST)) { 102169689Skan warnx("fetch_path_id got CAM error %#x, CDM error %d\n", 103169689Skan ccb.ccb_h.status, ccb.cdm.status); 104169689Skan free(ccb.cdm.matches); 105169689Skan return (EIO); 106169689Skan } 107169689Skan 108169689Skan /* We should have exactly 1 match for the bus. */ 109169689Skan if (ccb.cdm.num_matches != 1 || 110169689Skan ccb.cdm.matches[0].type != DEV_MATCH_BUS) { 111169689Skan free(ccb.cdm.matches); 112169689Skan return (ENOENT); 113169689Skan } 114169689Skan *path_id = ccb.cdm.matches[0].result.bus_result.path_id; 115169689Skan free(ccb.cdm.matches); 116169689Skan return (0); 117169689Skan} 118169689Skan 119169689Skanint 120169689Skanmpt_query_disk(U8 VolumeBus, U8 VolumeID, struct mpt_query_disk *qd) 121169689Skan{ 122169689Skan struct periph_match_pattern *p; 123169689Skan struct periph_match_result *r; 124169689Skan union ccb ccb; 125169689Skan path_id_t path_id; 126169689Skan size_t bufsize; 127169689Skan int error, i; 128169689Skan 129169689Skan /* mpt(4) only handles devices on bus 0. */ 130169689Skan if (VolumeBus != 0) 131169689Skan return (ENXIO); 132169689Skan 133169689Skan if (xpt_open() < 0) 134169689Skan return (ENXIO); 135169689Skan 136169689Skan /* Find the path ID of bus 0. */ 137169689Skan error = fetch_path_id(&path_id); 138169689Skan if (error) 139169689Skan return (error); 140169689Skan 141169689Skan bzero(&ccb, sizeof(ccb)); 142169689Skan 143169689Skan ccb.ccb_h.func_code = XPT_DEV_MATCH; 144169689Skan ccb.ccb_h.path_id = CAM_XPT_PATH_ID; 145169689Skan ccb.ccb_h.target_id = CAM_TARGET_WILDCARD; 146169689Skan ccb.ccb_h.target_lun = CAM_LUN_WILDCARD; 147169689Skan 148169689Skan 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 212static int 213periph_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. */ 229static int 230fetch_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(). */ 300static void 301format_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. */ 344static int 345fetch_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 389int 390mpt_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 */ 519int 520mpt_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