/*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 2008 Yahoo!, Inc. * All rights reserved. * Written by: John Baldwin * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __RCSID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include "mptutil.h" static int xptfd; static int xpt_open(void) { if (xptfd == 0) xptfd = open(XPT_DEVICE, O_RDWR); return (xptfd); } /* Fetch the path id of bus 0 for the opened mpt controller. */ static int fetch_path_id(path_id_t *path_id) { struct bus_match_pattern *b; union ccb ccb; size_t bufsize; int error; if (xpt_open() < 0) return (ENXIO); /* First, find the path id of bus 0 for this mpt controller. */ bzero(&ccb, sizeof(ccb)); ccb.ccb_h.func_code = XPT_DEV_MATCH; bufsize = sizeof(struct dev_match_result) * 1; ccb.cdm.num_matches = 0; ccb.cdm.match_buf_len = bufsize; ccb.cdm.matches = calloc(1, bufsize); bufsize = sizeof(struct dev_match_pattern) * 1; ccb.cdm.num_patterns = 1; ccb.cdm.pattern_buf_len = bufsize; ccb.cdm.patterns = calloc(1, bufsize); /* Match mptX bus 0. */ ccb.cdm.patterns[0].type = DEV_MATCH_BUS; b = &ccb.cdm.patterns[0].pattern.bus_pattern; snprintf(b->dev_name, sizeof(b->dev_name), "mpt"); b->unit_number = mpt_unit; b->bus_id = 0; b->flags = BUS_MATCH_NAME | BUS_MATCH_UNIT | BUS_MATCH_BUS_ID; if (ioctl(xptfd, CAMIOCOMMAND, &ccb) < 0) { error = errno; free(ccb.cdm.matches); free(ccb.cdm.patterns); return (error); } free(ccb.cdm.patterns); if (((ccb.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) || (ccb.cdm.status != CAM_DEV_MATCH_LAST)) { warnx("fetch_path_id got CAM error %#x, CDM error %d\n", ccb.ccb_h.status, ccb.cdm.status); free(ccb.cdm.matches); return (EIO); } /* We should have exactly 1 match for the bus. */ if (ccb.cdm.num_matches != 1 || ccb.cdm.matches[0].type != DEV_MATCH_BUS) { free(ccb.cdm.matches); return (ENOENT); } *path_id = ccb.cdm.matches[0].result.bus_result.path_id; free(ccb.cdm.matches); return (0); } int mpt_query_disk(U8 VolumeBus, U8 VolumeID, struct mpt_query_disk *qd) { struct periph_match_pattern *p; struct periph_match_result *r; union ccb ccb; path_id_t path_id; size_t bufsize; int error; /* mpt(4) only handles devices on bus 0. */ if (VolumeBus != 0) return (ENXIO); if (xpt_open() < 0) return (ENXIO); /* Find the path ID of bus 0. */ error = fetch_path_id(&path_id); if (error) return (error); bzero(&ccb, sizeof(ccb)); ccb.ccb_h.func_code = XPT_DEV_MATCH; ccb.ccb_h.path_id = CAM_XPT_PATH_ID; ccb.ccb_h.target_id = CAM_TARGET_WILDCARD; ccb.ccb_h.target_lun = CAM_LUN_WILDCARD; bufsize = sizeof(struct dev_match_result) * 5; ccb.cdm.num_matches = 0; ccb.cdm.match_buf_len = bufsize; ccb.cdm.matches = calloc(1, bufsize); bufsize = sizeof(struct dev_match_pattern) * 1; ccb.cdm.num_patterns = 1; ccb.cdm.pattern_buf_len = bufsize; ccb.cdm.patterns = calloc(1, bufsize); /* Look for a "da" device at the specified target and lun. */ ccb.cdm.patterns[0].type = DEV_MATCH_PERIPH; p = &ccb.cdm.patterns[0].pattern.periph_pattern; p->path_id = path_id; snprintf(p->periph_name, sizeof(p->periph_name), "da"); p->target_id = VolumeID; p->flags = PERIPH_MATCH_PATH | PERIPH_MATCH_NAME | PERIPH_MATCH_TARGET; if (ioctl(xptfd, CAMIOCOMMAND, &ccb) < 0) { error = errno; free(ccb.cdm.matches); free(ccb.cdm.patterns); return (error); } free(ccb.cdm.patterns); if (((ccb.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) || (ccb.cdm.status != CAM_DEV_MATCH_LAST)) { warnx("mpt_query_disk got CAM error %#x, CDM error %d\n", ccb.ccb_h.status, ccb.cdm.status); free(ccb.cdm.matches); return (EIO); } /* * We should have exactly 1 match for the peripheral. * However, if we don't get a match, don't print an error * message and return ENOENT. */ if (ccb.cdm.num_matches == 0) { free(ccb.cdm.matches); return (ENOENT); } if (ccb.cdm.num_matches != 1) { warnx("mpt_query_disk got %d matches, expected 1", ccb.cdm.num_matches); free(ccb.cdm.matches); return (EIO); } if (ccb.cdm.matches[0].type != DEV_MATCH_PERIPH) { warnx("mpt_query_disk got wrong CAM match"); free(ccb.cdm.matches); return (EIO); } /* Copy out the data. */ r = &ccb.cdm.matches[1].result.periph_result; snprintf(qd->devname, sizeof(qd->devname), "%s%d", r->periph_name, r->unit_number); free(ccb.cdm.matches); return (0); } static int periph_is_volume(CONFIG_PAGE_IOC_2 *ioc2, struct periph_match_result *r) { CONFIG_PAGE_IOC_2_RAID_VOL *vol; int i; if (ioc2 == NULL) return (0); vol = ioc2->RaidVolume; for (i = 0; i < ioc2->NumActiveVolumes; vol++, i++) { if (vol->VolumeBus == 0 && vol->VolumeID == r->target_id) return (1); } return (0); } /* Much borrowed from scsireadcapacity() in src/sbin/camcontrol/camcontrol.c. */ static int fetch_scsi_capacity(struct cam_device *dev, struct mpt_standalone_disk *disk) { struct scsi_read_capacity_data rcap; struct scsi_read_capacity_data_long rcaplong; union ccb *ccb; int error; ccb = cam_getccb(dev); if (ccb == NULL) return (ENOMEM); /* Zero the rest of the ccb. */ CCB_CLEAR_ALL_EXCEPT_HDR(&ccb->csio); scsi_read_capacity(&ccb->csio, 1, NULL, MSG_SIMPLE_Q_TAG, &rcap, SSD_FULL_SIZE, 5000); /* Disable freezing the device queue */ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; if (cam_send_ccb(dev, ccb) < 0) { error = errno; cam_freeccb(ccb); return (error); } if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { cam_freeccb(ccb); return (EIO); } /* * A last block of 2^32-1 means that the true capacity is over 2TB, * and we need to issue the long READ CAPACITY to get the real * capacity. Otherwise, we're all set. */ if (scsi_4btoul(rcap.addr) != 0xffffffff) { disk->maxlba = scsi_4btoul(rcap.addr); cam_freeccb(ccb); return (0); } /* Zero the rest of the ccb. */ CCB_CLEAR_ALL_EXCEPT_HDR(&ccb->csio); scsi_read_capacity_16(&ccb->csio, 1, NULL, MSG_SIMPLE_Q_TAG, 0, 0, 0, (uint8_t *)&rcaplong, sizeof(rcaplong), SSD_FULL_SIZE, 5000); /* Disable freezing the device queue */ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; if (cam_send_ccb(dev, ccb) < 0) { error = errno; cam_freeccb(ccb); return (error); } if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { cam_freeccb(ccb); return (EIO); } cam_freeccb(ccb); disk->maxlba = scsi_8btou64(rcaplong.addr); return (0); } /* Borrowed heavily from scsi_all.c:scsi_print_inquiry(). */ static void format_scsi_inquiry(struct mpt_standalone_disk *disk, struct scsi_inquiry_data *inq_data) { char vendor[16], product[48], revision[16], rstr[12]; if (SID_QUAL_IS_VENDOR_UNIQUE(inq_data)) return; if (SID_TYPE(inq_data) != T_DIRECT) return; if (SID_QUAL(inq_data) != SID_QUAL_LU_CONNECTED) return; cam_strvis(vendor, inq_data->vendor, sizeof(inq_data->vendor), sizeof(vendor)); cam_strvis(product, inq_data->product, sizeof(inq_data->product), sizeof(product)); cam_strvis(revision, inq_data->revision, sizeof(inq_data->revision), sizeof(revision)); /* Hack for SATA disks, no idea how to tell speed. */ if (strcmp(vendor, "ATA") == 0) { snprintf(disk->inqstring, sizeof(disk->inqstring), "<%s %s> SATA", product, revision); return; } switch (SID_ANSI_REV(inq_data)) { case SCSI_REV_CCS: strcpy(rstr, "SCSI-CCS"); break; case 5: strcpy(rstr, "SAS"); break; default: snprintf(rstr, sizeof (rstr), "SCSI-%d", SID_ANSI_REV(inq_data)); break; } snprintf(disk->inqstring, sizeof(disk->inqstring), "<%s %s %s> %s", vendor, product, revision, rstr); } /* Much borrowed from scsiinquiry() in src/sbin/camcontrol/camcontrol.c. */ static int fetch_scsi_inquiry(struct cam_device *dev, struct mpt_standalone_disk *disk) { struct scsi_inquiry_data *inq_buf; union ccb *ccb; int error; ccb = cam_getccb(dev); if (ccb == NULL) return (ENOMEM); /* Zero the rest of the ccb. */ CCB_CLEAR_ALL_EXCEPT_HDR(&ccb->csio); inq_buf = calloc(1, sizeof(*inq_buf)); if (inq_buf == NULL) { cam_freeccb(ccb); return (ENOMEM); } scsi_inquiry(&ccb->csio, 1, NULL, MSG_SIMPLE_Q_TAG, (void *)inq_buf, SHORT_INQUIRY_LENGTH, 0, 0, SSD_FULL_SIZE, 5000); /* Disable freezing the device queue */ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; if (cam_send_ccb(dev, ccb) < 0) { error = errno; free(inq_buf); cam_freeccb(ccb); return (error); } if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { free(inq_buf); cam_freeccb(ccb); return (EIO); } cam_freeccb(ccb); format_scsi_inquiry(disk, inq_buf); free(inq_buf); return (0); } int mpt_fetch_disks(int fd, int *ndisks, struct mpt_standalone_disk **disksp) { CONFIG_PAGE_IOC_2 *ioc2; struct mpt_standalone_disk *disks; struct periph_match_pattern *p; struct periph_match_result *r; struct cam_device *dev; union ccb ccb; path_id_t path_id; size_t bufsize; int count, error; uint32_t i; if (xpt_open() < 0) return (ENXIO); error = fetch_path_id(&path_id); if (error) return (error); for (count = 100;; count+= 100) { /* Try to fetch 'count' disks in one go. */ bzero(&ccb, sizeof(ccb)); ccb.ccb_h.func_code = XPT_DEV_MATCH; bufsize = sizeof(struct dev_match_result) * (count + 1); ccb.cdm.num_matches = 0; ccb.cdm.match_buf_len = bufsize; ccb.cdm.matches = calloc(1, bufsize); bufsize = sizeof(struct dev_match_pattern) * 1; ccb.cdm.num_patterns = 1; ccb.cdm.pattern_buf_len = bufsize; ccb.cdm.patterns = calloc(1, bufsize); /* Match any "da" peripherals. */ ccb.cdm.patterns[0].type = DEV_MATCH_PERIPH; p = &ccb.cdm.patterns[0].pattern.periph_pattern; p->path_id = path_id; snprintf(p->periph_name, sizeof(p->periph_name), "da"); p->flags = PERIPH_MATCH_PATH | PERIPH_MATCH_NAME; if (ioctl(xptfd, CAMIOCOMMAND, &ccb) < 0) { error = errno; free(ccb.cdm.matches); free(ccb.cdm.patterns); return (error); } free(ccb.cdm.patterns); /* Check for CCB errors. */ if ((ccb.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { free(ccb.cdm.matches); return (EIO); } /* If we need a longer list, try again. */ if (ccb.cdm.status == CAM_DEV_MATCH_MORE) { free(ccb.cdm.matches); continue; } /* If we got an error, abort. */ if (ccb.cdm.status != CAM_DEV_MATCH_LAST) { free(ccb.cdm.matches); return (EIO); } break; } /* Shortcut if we don't have any "da" devices. */ if (ccb.cdm.num_matches == 0) { free(ccb.cdm.matches); *ndisks = 0; *disksp = NULL; return (0); } /* We should have N matches, 1 for each "da" device. */ for (i = 0; i < ccb.cdm.num_matches; i++) { if (ccb.cdm.matches[i].type != DEV_MATCH_PERIPH) { warnx("mpt_fetch_disks got wrong CAM matches"); free(ccb.cdm.matches); return (EIO); } } /* * Some of the "da" peripherals may be for RAID volumes, so * fetch the IOC 2 page (list of RAID volumes) so we can * exclude them from the list. */ ioc2 = mpt_read_ioc_page(fd, 2, NULL); if (ioc2 == NULL) return (errno); disks = calloc(ccb.cdm.num_matches, sizeof(*disks)); count = 0; for (i = 0; i < ccb.cdm.num_matches; i++) { r = &ccb.cdm.matches[i].result.periph_result; if (periph_is_volume(ioc2, r)) continue; disks[count].bus = 0; disks[count].target = r->target_id; snprintf(disks[count].devname, sizeof(disks[count].devname), "%s%d", r->periph_name, r->unit_number); dev = cam_open_device(disks[count].devname, O_RDWR); if (dev != NULL) { fetch_scsi_capacity(dev, &disks[count]); fetch_scsi_inquiry(dev, &disks[count]); cam_close_device(dev); } count++; } free(ccb.cdm.matches); free(ioc2); *ndisks = count; *disksp = disks; return (0); } /* * Instruct the mpt(4) device to rescan its buses to find new devices * such as disks whose RAID physdisk page was removed or volumes that * were created. If id is -1, the entire bus is rescanned. * Otherwise, only devices at the specified ID are rescanned. If bus * is -1, then all buses are scanned instead of the specified bus. * Note that currently, only bus 0 is supported. */ int mpt_rescan_bus(int bus, int id) { union ccb ccb; path_id_t path_id; int error; /* mpt(4) only handles devices on bus 0. */ if (bus != -1 && bus != 0) return (EINVAL); if (xpt_open() < 0) return (ENXIO); error = fetch_path_id(&path_id); if (error) return (error); /* Perform the actual rescan. */ bzero(&ccb, sizeof(ccb)); ccb.ccb_h.path_id = path_id; if (id == -1) { ccb.ccb_h.func_code = XPT_SCAN_BUS; ccb.ccb_h.target_id = CAM_TARGET_WILDCARD; ccb.ccb_h.target_lun = CAM_LUN_WILDCARD; ccb.ccb_h.timeout = 5000; } else { ccb.ccb_h.func_code = XPT_SCAN_LUN; ccb.ccb_h.target_id = id; ccb.ccb_h.target_lun = 0; } ccb.crcn.flags = CAM_FLAG_NONE; /* Run this at a low priority. */ ccb.ccb_h.pinfo.priority = 5; if (ioctl(xptfd, CAMIOCOMMAND, &ccb) == -1) return (errno); if ((ccb.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { warnx("mpt_rescan_bus rescan got CAM error %#x\n", ccb.ccb_h.status & CAM_STATUS_MASK); return (EIO); } return (0); }