Add mptutil, a basic utility for managing MPT SCSI/SATA/SAS controllers.

Drive and controller status can be reported, basic attributes changed,
and arrays and spares can be created and deleted.

Approved by:	re
Obtained from:	Yahoo! Inc.
This commit is contained in:
Scott Long 2009-08-14 13:13:12 +00:00
parent 6d600732eb
commit fc58801ccc
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=196212
12 changed files with 4429 additions and 0 deletions

View File

@ -104,6 +104,7 @@ SUBDIR= ${_ac} \
${_mount_smbfs} \
${_moused} \
${_mptable} \
mptutil \
mtest \
mtree \
${_named} \

19
usr.sbin/mptutil/Makefile Normal file
View File

@ -0,0 +1,19 @@
# $FreeBSD$
PROG= mptutil
SRCS= mptutil.c mpt_cam.c mpt_cmd.c mpt_config.c mpt_drive.c mpt_evt.c \
mpt_show.c mpt_volume.c
# mpt_flash.c
MAN= mptutil.8
WARNS?= 3
DPADD+= ${LIBCAM} ${LIBUTIL}
LDADD+= -lcam -lutil
# Here be dragons
.ifdef DEBUG
CFLAGS+= -DDEBUG
.endif
.include <bsd.prog.mk>

569
usr.sbin/mptutil/mpt_cam.c Normal file
View File

@ -0,0 +1,569 @@
/*-
* Copyright (c) 2008 Yahoo!, Inc.
* All rights reserved.
* Written by: John Baldwin <jhb@FreeBSD.org>
*
* 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 <sys/cdefs.h>
__RCSID("$FreeBSD$");
#include <sys/param.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <camlib.h>
#include <cam/scsi/scsi_message.h>
#include <cam/scsi/scsi_pass.h>
#include "mptutil.h"
static int xptfd;
static int
xpt_open(void)
{
if (xptfd == 0)
xptfd = open(XPT_DEVICE, O_RDWR);
return (xptfd);
}
int
mpt_query_disk(U8 VolumeBus, U8 VolumeID, struct mpt_query_disk *qd)
{
struct bus_match_pattern *b;
struct periph_match_pattern *p;
struct periph_match_result *r;
union ccb ccb;
size_t bufsize;
int i;
/* mpt(4) only handles devices on bus 0. */
if (VolumeBus != 0)
return (ENXIO);
if (xpt_open() < 0)
return (ENXIO);
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) * 2;
ccb.cdm.num_patterns = 2;
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;
/* Look for a "da" device at the specified target and lun. */
ccb.cdm.patterns[1].type = DEV_MATCH_PERIPH;
p = &ccb.cdm.patterns[1].pattern.periph_pattern;
snprintf(p->periph_name, sizeof(p->periph_name), "da");
p->target_id = VolumeID;
p->flags = PERIPH_MATCH_NAME | PERIPH_MATCH_TARGET;
if (ioctl(xptfd, CAMIOCOMMAND, &ccb) < 0) {
i = errno;
free(ccb.cdm.matches);
free(ccb.cdm.patterns);
return (i);
}
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 2 matches, 1 for the bus and 1 for
* the peripheral. However, if we only have 1 match and it is
* for the bus, don't print an error message and return
* ENOENT.
*/
if (ccb.cdm.num_matches == 1 &&
ccb.cdm.matches[0].type == DEV_MATCH_BUS) {
free(ccb.cdm.matches);
return (ENOENT);
}
if (ccb.cdm.num_matches != 2) {
warnx("mpt_query_disk got %d matches, expected 2",
ccb.cdm.num_matches);
free(ccb.cdm.matches);
return (EIO);
}
if (ccb.cdm.matches[0].type != DEV_MATCH_BUS ||
ccb.cdm.matches[1].type != DEV_MATCH_PERIPH) {
warnx("mpt_query_disk got wrong CAM matches");
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. */
bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio) -
sizeof(struct ccb_hdr));
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);
}
cam_freeccb(ccb);
/*
* 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);
return (0);
}
/* Zero the rest of the ccb. */
bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio) -
sizeof(struct ccb_hdr));
scsi_read_capacity_16(&ccb->csio, 1, NULL, MSG_SIMPLE_Q_TAG, 0, 0, 0,
&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. */
bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio) -
sizeof(struct ccb_hdr));
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 bus_match_pattern *b;
struct periph_match_pattern *p;
struct periph_match_result *r;
struct cam_device *dev;
union ccb ccb;
size_t bufsize;
u_int i;
int count;
if (xpt_open() < 0)
return (ENXIO);
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 + 2);
ccb.cdm.num_matches = 0;
ccb.cdm.match_buf_len = bufsize;
ccb.cdm.matches = calloc(1, bufsize);
bufsize = sizeof(struct dev_match_pattern) * 2;
ccb.cdm.num_patterns = 2;
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;
/* Match any "da" peripherals. */
ccb.cdm.patterns[1].type = DEV_MATCH_PERIPH;
p = &ccb.cdm.patterns[1].pattern.periph_pattern;
snprintf(p->periph_name, sizeof(p->periph_name), "da");
p->flags = PERIPH_MATCH_NAME;
if (ioctl(xptfd, CAMIOCOMMAND, &ccb) < 0) {
i = errno;
free(ccb.cdm.matches);
free(ccb.cdm.patterns);
return (i);
}
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;
}
/*
* We should have N + 1 matches, 1 for the bus and 1 for each
* "da" device.
*/
if (ccb.cdm.num_matches < 1) {
warnx("mpt_fetch_disks didn't get any matches");
free(ccb.cdm.matches);
return (EIO);
}
if (ccb.cdm.matches[0].type != DEV_MATCH_BUS) {
warnx("mpt_fetch_disks got wrong CAM matches");
free(ccb.cdm.matches);
return (EIO);
}
for (i = 1; 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);
}
}
/* Shortcut if we don't have any "da" devices. */
if (ccb.cdm.num_matches == 1) {
free(ccb.cdm.matches);
*ndisks = 0;
*disksp = NULL;
return (0);
}
/*
* 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);
disks = calloc(ccb.cdm.num_matches, sizeof(*disks));
count = 0;
for (i = 1; 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 busses 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 busses are scanned instead of the specified bus.
* Note that currently, only bus 0 is supported.
*/
int
mpt_rescan_bus(int bus, int id)
{
struct bus_match_pattern *b;
union ccb ccb;
path_id_t path_id;
size_t bufsize;
/* mpt(4) only handles devices on bus 0. */
if (bus != -1 && bus != 0)
return (EINVAL);
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) {
free(ccb.cdm.matches);
free(ccb.cdm.patterns);
return (errno);
}
free(ccb.cdm.patterns);
if (((ccb.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) ||
(ccb.cdm.status != CAM_DEV_MATCH_LAST)) {
warnx("mpt_rescan_bus 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);
/* Now perform the actual rescan. */
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);
}

639
usr.sbin/mptutil/mpt_cmd.c Normal file
View File

@ -0,0 +1,639 @@
/*-
* Copyright (c) 2008 Yahoo!, Inc.
* All rights reserved.
* Written by: John Baldwin <jhb@FreeBSD.org>
*
* 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 <sys/cdefs.h>
__RCSID("$FreeBSD$");
#include <sys/param.h>
#include <sys/errno.h>
#include <sys/ioctl.h>
#include <sys/mpt_ioctl.h>
#include <sys/sysctl.h>
#include <sys/uio.h>
#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "mptutil.h"
static const char *mpt_ioc_status_codes[] = {
"Success", /* 0x0000 */
"Invalid function",
"Busy",
"Invalid scatter-gather list",
"Internal error",
"Reserved",
"Insufficient resources",
"Invalid field",
"Invalid state", /* 0x0008 */
"Operation state not supported",
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL, /* 0x0010 */
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL, /* 0x0018 */
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
"Invalid configuration action", /* 0x0020 */
"Invalid configuration type",
"Invalid configuration page",
"Invalid configuration data",
"No configuration defaults",
"Unable to commit configuration change",
NULL,
NULL,
NULL, /* 0x0028 */
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL, /* 0x0030 */
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL, /* 0x0038 */
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
"Recovered SCSI error", /* 0x0040 */
"Invalid SCSI bus",
"Invalid SCSI target ID",
"SCSI device not there",
"SCSI data overrun",
"SCSI data underrun",
"SCSI I/O error",
"SCSI protocol error",
"SCSI task terminated", /* 0x0048 */
"SCSI residual mismatch",
"SCSI task management failed",
"SCSI I/O controller terminated",
"SCSI external controller terminated",
"EEDP guard error",
"EEDP reference tag error",
"EEDP application tag error",
NULL, /* 0x0050 */
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL, /* 0x0058 */
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
"SCSI target priority I/O", /* 0x0060 */
"Invalid SCSI target port",
"Invalid SCSI target I/O index",
"SCSI target aborted",
"No connection retryable",
"No connection",
"FC aborted",
"Invalid FC receive ID",
"FC did invalid", /* 0x0068 */
"FC node logged out",
"Transfer count mismatch",
"STS data not set",
"FC exchange canceled",
"Data offset error",
"Too much write data",
"IU too short",
"ACK NAK timeout", /* 0x0070 */
"NAK received",
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL, /* 0x0078 */
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
"LAN device not found", /* 0x0080 */
"LAN device failure",
"LAN transmit error",
"LAN transmit aborted",
"LAN receive error",
"LAN receive aborted",
"LAN partial packet",
"LAN canceled",
NULL, /* 0x0088 */
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
"SAS SMP request failed", /* 0x0090 */
"SAS SMP data overrun",
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
"Inband aborted", /* 0x0098 */
"No inband connection",
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
"Diagnostic released", /* 0x00A0 */
};
static const char *mpt_raid_action_status_codes[] = {
"Success",
"Invalid action",
"Failure",
"Operation in progress",
};
const char *
mpt_ioc_status(U16 IOCStatus)
{
static char buffer[16];
IOCStatus &= MPI_IOCSTATUS_MASK;
if (IOCStatus < sizeof(mpt_ioc_status_codes) / sizeof(char *) &&
mpt_ioc_status_codes[IOCStatus] != NULL)
return (mpt_ioc_status_codes[IOCStatus]);
snprintf(buffer, sizeof(buffer), "Status: 0x%04x", IOCStatus);
return (buffer);
}
const char *
mpt_raid_status(U16 ActionStatus)
{
static char buffer[16];
if (ActionStatus < sizeof(mpt_raid_action_status_codes) /
sizeof(char *))
return (mpt_raid_action_status_codes[ActionStatus]);
snprintf(buffer, sizeof(buffer), "Status: 0x%04x", ActionStatus);
return (buffer);
}
const char *
mpt_raid_level(U8 VolumeType)
{
static char buf[16];
switch (VolumeType) {
case MPI_RAID_VOL_TYPE_IS:
return ("RAID-0");
case MPI_RAID_VOL_TYPE_IM:
return ("RAID-1");
case MPI_RAID_VOL_TYPE_IME:
return ("RAID-1E");
case MPI_RAID_VOL_TYPE_RAID_5:
return ("RAID-5");
case MPI_RAID_VOL_TYPE_RAID_6:
return ("RAID-6");
case MPI_RAID_VOL_TYPE_RAID_10:
return ("RAID-10");
case MPI_RAID_VOL_TYPE_RAID_50:
return ("RAID-50");
default:
sprintf(buf, "LVL 0x%02x", VolumeType);
return (buf);
}
}
const char *
mpt_volume_name(U8 VolumeBus, U8 VolumeID)
{
static struct mpt_query_disk info;
static char buf[16];
if (mpt_query_disk(VolumeBus, VolumeID, &info) != 0) {
/*
* We only print out the bus number if it is non-zero
* since mpt(4) only supports devices on bus zero
* anyway.
*/
if (VolumeBus == 0)
snprintf(buf, sizeof(buf), "%d", VolumeID);
else
snprintf(buf, sizeof(buf), "%d:%d", VolumeBus,
VolumeID);
return (buf);
}
return (info.devname);
}
int
mpt_lookup_volume(int fd, const char *name, U8 *VolumeBus, U8 *VolumeID)
{
CONFIG_PAGE_IOC_2 *ioc2;
CONFIG_PAGE_IOC_2_RAID_VOL *vol;
struct mpt_query_disk info;
char *cp;
long bus, id;
int i;
/*
* Check for a raw [<bus>:]<id> string. If the bus is not
* specified, assume bus 0.
*/
bus = strtol(name, &cp, 0);
if (*cp == ':') {
id = strtol(cp + 1, &cp, 0);
if (*cp == '\0') {
if (bus < 0 || bus > 0xff || id < 0 || id > 0xff) {
errno = EINVAL;
return (-1);
}
*VolumeBus = bus;
*VolumeID = id;
return (0);
}
} else if (*cp == '\0') {
if (bus < 0 || bus > 0xff) {
errno = EINVAL;
return (-1);
}
*VolumeBus = 0;
*VolumeID = bus;
return (0);
}
ioc2 = mpt_read_ioc_page(fd, 2, NULL);
if (ioc2 == NULL)
return (-1);
vol = ioc2->RaidVolume;
for (i = 0; i < ioc2->NumActiveVolumes; vol++, i++) {
if (mpt_query_disk(vol->VolumeBus, vol->VolumeID, &info) != 0)
continue;
if (strcmp(name, info.devname) == 0) {
*VolumeBus = vol->VolumeBus;
*VolumeID = vol->VolumeID;
free(ioc2);
return (0);
}
}
free(ioc2);
errno = EINVAL;
return (-1);
}
int
mpt_read_config_page_header(int fd, U8 PageType, U8 PageNumber, U32 PageAddress,
CONFIG_PAGE_HEADER *header, U16 *IOCStatus)
{
struct mpt_cfg_page_req req;
if (IOCStatus != NULL)
*IOCStatus = MPI_IOCSTATUS_SUCCESS;
bzero(&req, sizeof(req));
req.header.PageType = PageType;
req.header.PageNumber = PageNumber;
req.page_address = PageAddress;
if (ioctl(fd, MPTIO_READ_CFG_HEADER, &req) < 0)
return (-1);
if (!IOC_STATUS_SUCCESS(req.ioc_status)) {
if (IOCStatus != NULL)
*IOCStatus = req.ioc_status;
else
warnx("Reading config page header failed: %s",
mpt_ioc_status(req.ioc_status));
errno = EIO;
return (-1);
}
*header = req.header;
return (0);
}
void *
mpt_read_config_page(int fd, U8 PageType, U8 PageNumber, U32 PageAddress,
U16 *IOCStatus)
{
struct mpt_cfg_page_req req;
void *buf;
int save_errno;
if (IOCStatus != NULL)
*IOCStatus = MPI_IOCSTATUS_SUCCESS;
bzero(&req, sizeof(req));
req.header.PageType = PageType;
req.header.PageNumber = PageNumber;
req.page_address = PageAddress;
if (ioctl(fd, MPTIO_READ_CFG_HEADER, &req) < 0)
return (NULL);
if (!IOC_STATUS_SUCCESS(req.ioc_status)) {
if (IOCStatus != NULL)
*IOCStatus = req.ioc_status;
else
warnx("Reading config page header failed: %s",
mpt_ioc_status(req.ioc_status));
errno = EIO;
return (NULL);
}
req.len = req.header.PageLength * 4;
buf = malloc(req.len);
req.buf = buf;
bcopy(&req.header, buf, sizeof(req.header));
if (ioctl(fd, MPTIO_READ_CFG_PAGE, &req) < 0) {
save_errno = errno;
free(buf);
errno = save_errno;
return (NULL);
}
if (!IOC_STATUS_SUCCESS(req.ioc_status)) {
if (IOCStatus != NULL)
*IOCStatus = req.ioc_status;
else
warnx("Reading config page failed: %s",
mpt_ioc_status(req.ioc_status));
free(buf);
errno = EIO;
return (NULL);
}
return (buf);
}
void *
mpt_read_extended_config_page(int fd, U8 ExtPageType, U8 PageVersion,
U8 PageNumber, U32 PageAddress, U16 *IOCStatus)
{
struct mpt_ext_cfg_page_req req;
void *buf;
int save_errno;
if (IOCStatus != NULL)
*IOCStatus = MPI_IOCSTATUS_SUCCESS;
bzero(&req, sizeof(req));
req.header.PageVersion = PageVersion;
req.header.PageNumber = PageNumber;
req.header.ExtPageType = ExtPageType;
req.page_address = PageAddress;
if (ioctl(fd, MPTIO_READ_EXT_CFG_HEADER, &req) < 0)
return (NULL);
if (!IOC_STATUS_SUCCESS(req.ioc_status)) {
if (IOCStatus != NULL)
*IOCStatus = req.ioc_status;
else
warnx("Reading extended config page header failed: %s",
mpt_ioc_status(req.ioc_status));
errno = EIO;
return (NULL);
}
req.len = req.header.ExtPageLength * 4;
buf = malloc(req.len);
req.buf = buf;
bcopy(&req.header, buf, sizeof(req.header));
if (ioctl(fd, MPTIO_READ_EXT_CFG_PAGE, &req) < 0) {
save_errno = errno;
free(buf);
errno = save_errno;
return (NULL);
}
if (!IOC_STATUS_SUCCESS(req.ioc_status)) {
if (IOCStatus != NULL)
*IOCStatus = req.ioc_status;
else
warnx("Reading extended config page failed: %s",
mpt_ioc_status(req.ioc_status));
free(buf);
errno = EIO;
return (NULL);
}
return (buf);
}
int
mpt_write_config_page(int fd, void *buf, U16 *IOCStatus)
{
CONFIG_PAGE_HEADER *hdr;
struct mpt_cfg_page_req req;
if (IOCStatus != NULL)
*IOCStatus = MPI_IOCSTATUS_SUCCESS;
bzero(&req, sizeof(req));
req.buf = buf;
hdr = buf;
req.len = hdr->PageLength * 4;
if (ioctl(fd, MPTIO_WRITE_CFG_PAGE, &req) < 0)
return (-1);
if (!IOC_STATUS_SUCCESS(req.ioc_status)) {
if (IOCStatus != NULL) {
*IOCStatus = req.ioc_status;
return (0);
}
warnx("Writing config page failed: %s",
mpt_ioc_status(req.ioc_status));
errno = EIO;
return (-1);
}
return (0);
}
int
mpt_raid_action(int fd, U8 Action, U8 VolumeBus, U8 VolumeID, U8 PhysDiskNum,
U32 ActionDataWord, void *buf, int len, RAID_VOL0_STATUS *VolumeStatus,
U32 *ActionData, int datalen, U16 *IOCStatus, U16 *ActionStatus, int write)
{
struct mpt_raid_action raid_act;
if (IOCStatus != NULL)
*IOCStatus = MPI_IOCSTATUS_SUCCESS;
if (datalen < 0 || (unsigned)datalen > sizeof(raid_act.action_data)) {
errno = EINVAL;
return (-1);
}
bzero(&raid_act, sizeof(raid_act));
raid_act.action = Action;
raid_act.volume_bus = VolumeBus;
raid_act.volume_id = VolumeID;
raid_act.phys_disk_num = PhysDiskNum;
raid_act.action_data_word = ActionDataWord;
if (buf != NULL && len != 0) {
raid_act.buf = buf;
raid_act.len = len;
raid_act.write = write;
}
if (ioctl(fd, MPTIO_RAID_ACTION, &raid_act) < 0)
return (-1);
if (!IOC_STATUS_SUCCESS(raid_act.ioc_status)) {
if (IOCStatus != NULL) {
*IOCStatus = raid_act.ioc_status;
return (0);
}
warnx("RAID action failed: %s",
mpt_ioc_status(raid_act.ioc_status));
errno = EIO;
return (-1);
}
if (ActionStatus != NULL)
*ActionStatus = raid_act.action_status;
if (raid_act.action_status != MPI_RAID_ACTION_ASTATUS_SUCCESS) {
if (ActionStatus != NULL)
return (0);
warnx("RAID action failed: %s",
mpt_raid_status(raid_act.action_status));
errno = EIO;
return (-1);
}
if (VolumeStatus != NULL)
*((U32 *)VolumeStatus) = raid_act.volume_status;
if (ActionData != NULL)
bcopy(raid_act.action_data, ActionData, datalen);
return (0);
}
int
mpt_open(int unit)
{
char path[MAXPATHLEN];
snprintf(path, sizeof(path), "/dev/mpt%d", unit);
return (open(path, O_RDWR));
}
int
mpt_table_handler(struct mptutil_command **start, struct mptutil_command **end,
int ac, char **av)
{
struct mptutil_command **cmd;
if (ac < 2) {
warnx("The %s command requires a sub-command.", av[0]);
return (EINVAL);
}
for (cmd = start; cmd < end; cmd++) {
if (strcmp((*cmd)->name, av[1]) == 0)
return ((*cmd)->handler(ac - 1, av + 1));
}
warnx("%s is not a valid sub-command of %s.", av[1], av[0]);
return (ENOENT);
}
#ifdef DEBUG
void
hexdump(const void *ptr, int length, const char *hdr, int flags)
{
int i, j, k;
int cols;
const unsigned char *cp;
char delim;
if ((flags & HD_DELIM_MASK) != 0)
delim = (flags & HD_DELIM_MASK) >> 8;
else
delim = ' ';
if ((flags & HD_COLUMN_MASK) != 0)
cols = flags & HD_COLUMN_MASK;
else
cols = 16;
cp = ptr;
for (i = 0; i < length; i+= cols) {
if (hdr != NULL)
printf("%s", hdr);
if ((flags & HD_OMIT_COUNT) == 0)
printf("%04x ", i);
if ((flags & HD_OMIT_HEX) == 0) {
for (j = 0; j < cols; j++) {
k = i + j;
if (k < length)
printf("%c%02x", delim, cp[k]);
else
printf(" ");
}
}
if ((flags & HD_OMIT_CHARS) == 0) {
printf(" |");
for (j = 0; j < cols; j++) {
k = i + j;
if (k >= length)
printf(" ");
else if (cp[k] >= ' ' && cp[k] <= '~')
printf("%c", cp[k]);
else
printf(".");
}
printf("|");
}
printf("\n");
}
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,395 @@
/*-
* Copyright (c) 2008 Yahoo!, Inc.
* All rights reserved.
* Written by: John Baldwin <jhb@FreeBSD.org>
*
* 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 <sys/cdefs.h>
__RCSID("$FreeBSD$");
#include <sys/param.h>
#include <sys/errno.h>
#include <ctype.h>
#include <err.h>
#include <libutil.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <camlib.h>
#include <cam/scsi/scsi_all.h>
#include "mptutil.h"
const char *
mpt_pdstate(CONFIG_PAGE_RAID_PHYS_DISK_0 *info)
{
static char buf[16];
switch (info->PhysDiskStatus.State) {
case MPI_PHYSDISK0_STATUS_ONLINE:
if ((info->PhysDiskStatus.Flags &
MPI_PHYSDISK0_STATUS_FLAG_OUT_OF_SYNC) &&
info->PhysDiskSettings.HotSparePool == 0)
return ("REBUILD");
else
return ("ONLINE");
case MPI_PHYSDISK0_STATUS_MISSING:
return ("MISSING");
case MPI_PHYSDISK0_STATUS_NOT_COMPATIBLE:
return ("NOT COMPATIBLE");
case MPI_PHYSDISK0_STATUS_FAILED:
return ("FAILED");
case MPI_PHYSDISK0_STATUS_INITIALIZING:
return ("INITIALIZING");
case MPI_PHYSDISK0_STATUS_OFFLINE_REQUESTED:
return ("OFFLINE REQUESTED");
case MPI_PHYSDISK0_STATUS_FAILED_REQUESTED:
return ("FAILED REQUESTED");
case MPI_PHYSDISK0_STATUS_OTHER_OFFLINE:
return ("OTHER OFFLINE");
default:
sprintf(buf, "PSTATE 0x%02x", info->PhysDiskStatus.State);
return (buf);
}
}
/*
* There are several ways to enumerate physical disks. Unfortunately,
* none of them are truly complete, so we have to build a union of all of
* them. Specifically:
*
* - IOC2 : This gives us a list of volumes, and by walking the volumes we
* can enumerate all of the drives attached to volumes including
* online drives and failed drives.
* - IOC3 : This gives us a list of all online physical drives including
* drives that are not part of a volume nor a spare drive. It
* does not include any failed drives.
* - IOC5 : This gives us a list of all spare drives including failed
* spares.
*
* The specific edge cases are that 1) a failed volume member can only be
* found via IOC2, 2) a drive that is neither a volume member nor a spare
* can only be found via IOC3, and 3) a failed spare can only be found via
* IOC5.
*
* To handle this, walk all of the three lists and use the following
* routine to add each drive encountered. It quietly succeeds if the
* drive is already present in the list. It also sorts the list as it
* inserts new drives.
*/
static int
mpt_pd_insert(int fd, struct mpt_drive_list *list, U8 PhysDiskNum)
{
int i, j;
/*
* First, do a simple linear search to see if we have already
* seen this drive.
*/
for (i = 0; i < list->ndrives; i++) {
if (list->drives[i]->PhysDiskNum == PhysDiskNum)
return (0);
if (list->drives[i]->PhysDiskNum > PhysDiskNum)
break;
}
/*
* 'i' is our slot for the 'new' drive. Make room and then
* read the drive info.
*/
for (j = list->ndrives - 1; j >= i; j--)
list->drives[j + 1] = list->drives[j];
list->drives[i] = mpt_pd_info(fd, PhysDiskNum, NULL);
if (list->drives[i] == NULL)
return (-1);
list->ndrives++;
return (0);
}
struct mpt_drive_list *
mpt_pd_list(int fd)
{
CONFIG_PAGE_IOC_2 *ioc2;
CONFIG_PAGE_IOC_2_RAID_VOL *vol;
CONFIG_PAGE_RAID_VOL_0 **volumes;
RAID_VOL0_PHYS_DISK *rdisk;
CONFIG_PAGE_IOC_3 *ioc3;
IOC_3_PHYS_DISK *disk;
CONFIG_PAGE_IOC_5 *ioc5;
IOC_5_HOT_SPARE *spare;
struct mpt_drive_list *list;
int count, i, j;
ioc2 = mpt_read_ioc_page(fd, 2, NULL);
if (ioc2 == NULL) {
warn("Failed to fetch volume list");
return (NULL);
}
ioc3 = mpt_read_ioc_page(fd, 3, NULL);
if (ioc3 == NULL) {
warn("Failed to fetch drive list");
free(ioc2);
return (NULL);
}
ioc5 = mpt_read_ioc_page(fd, 5, NULL);
if (ioc5 == NULL) {
warn("Failed to fetch spare list");
free(ioc3);
free(ioc2);
return (NULL);
}
/*
* Go ahead and read the info for all the volumes. For this
* pass we figure out how many physical drives there are.
*/
volumes = malloc(sizeof(*volumes) * ioc2->NumActiveVolumes);
count = 0;
vol = ioc2->RaidVolume;
for (i = 0; i < ioc2->NumActiveVolumes; vol++, i++) {
volumes[i] = mpt_vol_info(fd, vol->VolumeBus, vol->VolumeID,
NULL);
if (volumes[i] == NULL) {
warn("Failed to read volume info");
return (NULL);
}
count += volumes[i]->NumPhysDisks;
}
count += ioc3->NumPhysDisks;
count += ioc5->NumHotSpares;
/* Walk the various lists enumerating drives. */
list = malloc(sizeof(*list) + sizeof(CONFIG_PAGE_RAID_PHYS_DISK_0) *
count);
list->ndrives = 0;
for (i = 0; i < ioc2->NumActiveVolumes; i++) {
rdisk = volumes[i]->PhysDisk;
for (j = 0; j < volumes[i]->NumPhysDisks; rdisk++, j++)
if (mpt_pd_insert(fd, list, rdisk->PhysDiskNum) < 0)
return (NULL);
free(volumes[i]);
}
free(ioc2);
free(volumes);
spare = ioc5->HotSpare;
for (i = 0; i < ioc5->NumHotSpares; spare++, i++)
if (mpt_pd_insert(fd, list, spare->PhysDiskNum) < 0)
return (NULL);
free(ioc5);
disk = ioc3->PhysDisk;
for (i = 0; i < ioc3->NumPhysDisks; disk++, i++)
if (mpt_pd_insert(fd, list, disk->PhysDiskNum) < 0)
return (NULL);
free(ioc3);
return (list);
}
void
mpt_free_pd_list(struct mpt_drive_list *list)
{
int i;
for (i = 0; i < list->ndrives; i++)
free(list->drives[i]);
free(list);
}
int
mpt_lookup_drive(struct mpt_drive_list *list, const char *drive,
U8 *PhysDiskNum)
{
long val;
uint8_t bus, id;
char *cp;
/* Look for a raw device id first. */
val = strtol(drive, &cp, 0);
if (*cp == '\0') {
if (val < 0 || val > 0xff)
goto bad;
*PhysDiskNum = val;
return (0);
}
/* Look for a <bus>:<id> string. */
if (*cp == ':') {
if (val < 0 || val > 0xff)
goto bad;
bus = val;
val = strtol(cp + 1, &cp, 0);
if (*cp != '\0')
goto bad;
if (val < 0 || val > 0xff)
goto bad;
id = val;
for (val = 0; val < list->ndrives; val++) {
if (list->drives[val]->PhysDiskBus == bus &&
list->drives[val]->PhysDiskID == id) {
*PhysDiskNum = list->drives[val]->PhysDiskNum;
return (0);
}
}
errno = ENOENT;
return (-1);
}
bad:
errno = EINVAL;
return (-1);
}
/* Borrowed heavily from scsi_all.c:scsi_print_inquiry(). */
const char *
mpt_pd_inq_string(CONFIG_PAGE_RAID_PHYS_DISK_0 *pd_info)
{
RAID_PHYS_DISK0_INQUIRY_DATA *inq_data;
u_char vendor[9], product[17], revision[5];
static char inq_string[64];
inq_data = &pd_info->InquiryData;
cam_strvis(vendor, inq_data->VendorID, sizeof(inq_data->VendorID),
sizeof(vendor));
cam_strvis(product, inq_data->ProductID, sizeof(inq_data->ProductID),
sizeof(product));
cam_strvis(revision, inq_data->ProductRevLevel,
sizeof(inq_data->ProductRevLevel), sizeof(revision));
/* Total hack. */
if (strcmp(vendor, "ATA") == 0)
snprintf(inq_string, sizeof(inq_string), "<%s %s> SATA",
product, revision);
else
snprintf(inq_string, sizeof(inq_string), "<%s %s %s> SAS",
vendor, product, revision);
return (inq_string);
}
/* Helper function to set a drive to a given state. */
static int
drive_set_state(char *drive, U8 Action, U8 State, const char *name)
{
CONFIG_PAGE_RAID_PHYS_DISK_0 *info;
struct mpt_drive_list *list;
U8 PhysDiskNum;
int fd;
fd = mpt_open(mpt_unit);
if (fd < 0) {
warn("mpt_open");
return (errno);
}
list = mpt_pd_list(fd);
if (list == NULL)
return (errno);
if (mpt_lookup_drive(list, drive, &PhysDiskNum) < 0) {
warn("Failed to find drive %s", drive);
return (errno);
}
mpt_free_pd_list(list);
/* Get the info for this drive. */
info = mpt_pd_info(fd, PhysDiskNum, NULL);
if (info == NULL) {
warn("Failed to fetch info for drive %u", PhysDiskNum);
return (errno);
}
/* Try to change the state. */
if (info->PhysDiskStatus.State == State) {
warnx("Drive %u is already in the desired state", PhysDiskNum);
return (EINVAL);
}
if (mpt_raid_action(fd, Action, 0, 0, PhysDiskNum, 0, NULL, 0, NULL,
NULL, 0, NULL, NULL, 0) < 0) {
warn("Failed to set drive %u to %s", PhysDiskNum, name);
return (errno);
}
free(info);
close(fd);
return (0);
}
static int
fail_drive(int ac, char **av)
{
if (ac != 2) {
warnx("fail: %s", ac > 2 ? "extra arguments" :
"drive required");
return (EINVAL);
}
return (drive_set_state(av[1], MPI_RAID_ACTION_FAIL_PHYSDISK,
MPI_PHYSDISK0_STATUS_FAILED_REQUESTED, "FAILED"));
}
MPT_COMMAND(top, fail, fail_drive);
static int
online_drive(int ac, char **av)
{
if (ac != 2) {
warnx("online: %s", ac > 2 ? "extra arguments" :
"drive required");
return (EINVAL);
}
return (drive_set_state(av[1], MPI_RAID_ACTION_PHYSDISK_ONLINE,
MPI_PHYSDISK0_STATUS_ONLINE, "ONLINE"));
}
MPT_COMMAND(top, online, online_drive);
static int
offline_drive(int ac, char **av)
{
if (ac != 2) {
warnx("offline: %s", ac > 2 ? "extra arguments" :
"drive required");
return (EINVAL);
}
return (drive_set_state(av[1], MPI_RAID_ACTION_PHYSDISK_OFFLINE,
MPI_PHYSDISK0_STATUS_OFFLINE_REQUESTED, "OFFLINE"));
}
MPT_COMMAND(top, offline, offline_drive);

155
usr.sbin/mptutil/mpt_evt.c Normal file
View File

@ -0,0 +1,155 @@
/*-
* Copyright (c) 2008 Yahoo!, Inc.
* All rights reserved.
* Written by: John Baldwin <jhb@FreeBSD.org>
*
* 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 <sys/cdefs.h>
__RCSID("$FreeBSD$");
#include <sys/param.h>
#include <sys/errno.h>
#include <ctype.h>
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "mptutil.h"
static CONFIG_PAGE_LOG_0 *
mpt_get_events(int fd, U16 *IOCStatus)
{
return (mpt_read_extended_config_page(fd, MPI_CONFIG_EXTPAGETYPE_LOG,
0, 0, 0, IOCStatus));
}
/*
* 1 2 3 4 5 6 7
* 1234567890123456789012345678901234567890123456789012345678901234567890
* < ID> < time > <ty> <X XX XX XX XX XX XX XX XX XX XX XX XX XX |..............|
* ID Time Type Log Data
*/
static void
mpt_print_event(MPI_LOG_0_ENTRY *entry, int verbose)
{
int i;
printf("%5d %7ds %4x ", entry->LogSequence, entry->TimeStamp,
entry->LogEntryQualifier);
for (i = 0; i < 14; i++)
printf("%02x ", entry->LogData[i]);
printf("|");
for (i = 0; i < 14; i++)
printf("%c", isprint(entry->LogData[i]) ? entry->LogData[i] :
'.');
printf("|\n");
printf(" ");
for (i = 0; i < 14; i++)
printf("%02x ", entry->LogData[i + 14]);
printf("|");
for (i = 0; i < 14; i++)
printf("%c", isprint(entry->LogData[i + 14]) ?
entry->LogData[i + 14] : '.');
printf("|\n");
}
static int
event_compare(const void *first, const void *second)
{
MPI_LOG_0_ENTRY * const *one;
MPI_LOG_0_ENTRY * const *two;
one = first;
two = second;
return ((*one)->LogSequence - ((*two)->LogSequence));
}
static int
show_events(int ac, char **av)
{
CONFIG_PAGE_LOG_0 *log;
MPI_LOG_0_ENTRY **entries;
int ch, fd, i, num_events, verbose;
fd = mpt_open(mpt_unit);
if (fd < 0) {
warn("mpt_open");
return (errno);
}
log = mpt_get_events(fd, NULL);
if (log == NULL) {
warn("Failed to get event log info");
return (errno);
}
/* Default settings. */
verbose = 0;
/* Parse any options. */
optind = 1;
while ((ch = getopt(ac, av, "v")) != -1) {
switch (ch) {
case 'v':
verbose = 1;
break;
case '?':
default:
return (EINVAL);
}
}
ac -= optind;
av += optind;
/* Build a list of valid entries and sort them by sequence. */
entries = malloc(sizeof(MPI_LOG_0_ENTRY *) * log->NumLogEntries);
num_events = 0;
for (i = 0; i < log->NumLogEntries; i++) {
if (log->LogEntry[i].LogEntryQualifier ==
MPI_LOG_0_ENTRY_QUAL_ENTRY_UNUSED)
continue;
entries[num_events] = &log->LogEntry[i];
num_events++;
}
qsort(entries, num_events, sizeof(MPI_LOG_0_ENTRY *), event_compare);
if (num_events == 0)
printf("Event log is empty\n");
else {
printf(" ID Time Type Log Data\n");
for (i = 0; i < num_events; i++)
mpt_print_event(entries[i], verbose);
}
free(entries);
close(fd);
return (0);
}
MPT_COMMAND(show, events, show_events);

559
usr.sbin/mptutil/mpt_show.c Normal file
View File

@ -0,0 +1,559 @@
/*-
* Copyright (c) 2008 Yahoo!, Inc.
* All rights reserved.
* Written by: John Baldwin <jhb@FreeBSD.org>
*
* 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 <sys/cdefs.h>
__RCSID("$FreeBSD$");
#include <sys/param.h>
#include <sys/errno.h>
#include <err.h>
#include <libutil.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "mptutil.h"
MPT_TABLE(top, show);
#define STANDALONE_STATE "ONLINE"
static void
format_stripe(char *buf, size_t buflen, U32 stripe)
{
humanize_number(buf, buflen, stripe * 512, "", HN_AUTOSCALE,
HN_B | HN_NOSPACE);
}
static void
display_stripe_map(const char *label, U32 StripeMap)
{
char stripe[5];
int comma, i;
comma = 0;
printf("%s: ", label);
for (i = 0; StripeMap != 0; i++, StripeMap >>= 1)
if (StripeMap & 1) {
format_stripe(stripe, sizeof(stripe), 1 << i);
if (comma)
printf(", ");
printf("%s", stripe);
comma = 1;
}
printf("\n");
}
static int
show_adapter(int ac, char **av)
{
CONFIG_PAGE_MANUFACTURING_0 *man0;
CONFIG_PAGE_IOC_2 *ioc2;
CONFIG_PAGE_IOC_6 *ioc6;
int fd, comma;
if (ac != 1) {
warnx("show adapter: extra arguments");
return (EINVAL);
}
fd = mpt_open(mpt_unit);
if (fd < 0) {
warn("mpt_open");
return (errno);
}
man0 = mpt_read_man_page(fd, 0, NULL);
if (man0 == NULL) {
warn("Failed to get controller info");
return (errno);
}
if (man0->Header.PageLength < sizeof(*man0) / 4) {
warn("Invalid controller info");
return (EINVAL);
}
printf("mpt%d Adapter:\n", mpt_unit);
printf(" Board Name: %.16s\n", man0->BoardName);
printf(" Board Assembly: %.16s\n", man0->BoardAssembly);
printf(" Chip Name: %.16s\n", man0->ChipName);
printf(" Chip Revision: %.16s\n", man0->ChipRevision);
free(man0);
ioc2 = mpt_read_ioc_page(fd, 2, NULL);
if (ioc2 != NULL) {
printf(" RAID Levels:");
comma = 0;
if (ioc2->CapabilitiesFlags &
MPI_IOCPAGE2_CAP_FLAGS_IS_SUPPORT) {
printf(" RAID0");
comma = 1;
}
if (ioc2->CapabilitiesFlags &
MPI_IOCPAGE2_CAP_FLAGS_IM_SUPPORT) {
printf("%s RAID1", comma ? "," : "");
comma = 1;
}
if (ioc2->CapabilitiesFlags &
MPI_IOCPAGE2_CAP_FLAGS_IME_SUPPORT) {
printf("%s RAID1E", comma ? "," : "");
comma = 1;
}
if (ioc2->CapabilitiesFlags &
MPI_IOCPAGE2_CAP_FLAGS_RAID_5_SUPPORT) {
printf("%s RAID5", comma ? "," : "");
comma = 1;
}
if (ioc2->CapabilitiesFlags &
MPI_IOCPAGE2_CAP_FLAGS_RAID_6_SUPPORT) {
printf("%s RAID6", comma ? "," : "");
comma = 1;
}
if (ioc2->CapabilitiesFlags &
MPI_IOCPAGE2_CAP_FLAGS_RAID_10_SUPPORT) {
printf("%s RAID10", comma ? "," : "");
comma = 1;
}
if (ioc2->CapabilitiesFlags &
MPI_IOCPAGE2_CAP_FLAGS_RAID_50_SUPPORT) {
printf("%s RAID50", comma ? "," : "");
comma = 1;
}
if (!comma)
printf(" none");
printf("\n");
free(ioc2);
}
ioc6 = mpt_read_ioc_page(fd, 6, NULL);
if (ioc6 != NULL) {
display_stripe_map(" RAID0 Stripes",
ioc6->SupportedStripeSizeMapIS);
display_stripe_map(" RAID1E Stripes",
ioc6->SupportedStripeSizeMapIME);
printf(" RAID0 Drives/Vol: %u", ioc6->MinDrivesIS);
if (ioc6->MinDrivesIS != ioc6->MaxDrivesIS)
printf("-%u", ioc6->MaxDrivesIS);
printf("\n");
printf(" RAID1 Drives/Vol: %u", ioc6->MinDrivesIM);
if (ioc6->MinDrivesIM != ioc6->MaxDrivesIM)
printf("-%u", ioc6->MaxDrivesIM);
printf("\n");
printf("RAID1E Drives/Vol: %u", ioc6->MinDrivesIME);
if (ioc6->MinDrivesIME != ioc6->MaxDrivesIME)
printf("-%u", ioc6->MaxDrivesIME);
printf("\n");
free(ioc6);
}
/* TODO: Add an ioctl to fetch IOC_FACTS and print firmware version. */
close(fd);
return (0);
}
MPT_COMMAND(show, adapter, show_adapter);
static void
print_vol(CONFIG_PAGE_RAID_VOL_0 *info, int state_len)
{
uint64_t size;
const char *level, *state;
char buf[6], stripe[5];
size = ((uint64_t)info->MaxLBAHigh << 32) | info->MaxLBA;
humanize_number(buf, sizeof(buf), (size + 1) * 512, "", HN_AUTOSCALE,
HN_B | HN_NOSPACE | HN_DECIMAL);
if (info->VolumeType == MPI_RAID_VOL_TYPE_IM)
stripe[0] = '\0';
else
format_stripe(stripe, sizeof(stripe), info->StripeSize);
level = mpt_raid_level(info->VolumeType);
state = mpt_volstate(info->VolumeStatus.State);
if (state_len > 0)
printf("(%6s) %-8s %6s %-*s", buf, level, stripe, state_len,
state);
else if (stripe[0] != '\0')
printf("(%s) %s %s %s", buf, level, stripe, state);
else
printf("(%s) %s %s", buf, level, state);
}
static void
print_pd(CONFIG_PAGE_RAID_PHYS_DISK_0 *info, int state_len, int location)
{
const char *inq, *state;
char buf[6];
humanize_number(buf, sizeof(buf), ((uint64_t)info->MaxLBA + 1) * 512,
"", HN_AUTOSCALE, HN_B | HN_NOSPACE |HN_DECIMAL);
state = mpt_pdstate(info);
if (state_len > 0)
printf("(%6s) %-*s", buf, state_len, state);
else
printf("(%s) %s", buf, state);
inq = mpt_pd_inq_string(info);
if (inq != NULL)
printf(" %s", inq);
if (!location)
return;
printf(" bus %d id %d", info->PhysDiskBus, info->PhysDiskID);
}
static void
print_standalone(struct mpt_standalone_disk *disk, int state_len, int location)
{
char buf[6];
humanize_number(buf, sizeof(buf), (disk->maxlba + 1) * 512,
"", HN_AUTOSCALE, HN_B | HN_NOSPACE |HN_DECIMAL);
if (state_len > 0)
printf("(%6s) %-*s", buf, state_len, STANDALONE_STATE);
else
printf("(%s) %s", buf, STANDALONE_STATE);
if (disk->inqstring[0] != '\0')
printf(" %s", disk->inqstring);
if (!location)
return;
printf(" bus %d id %d", disk->bus, disk->target);
}
static void
print_spare_pools(U8 HotSparePool)
{
int i;
if (HotSparePool == 0) {
printf("none");
return;
}
for (i = 0; HotSparePool != 0; i++) {
if (HotSparePool & 1) {
printf("%d", i);
if (HotSparePool == 1)
break;
printf(", ");
}
HotSparePool >>= 1;
}
}
static int
show_config(int ac, char **av)
{
CONFIG_PAGE_IOC_2 *ioc2;
CONFIG_PAGE_IOC_2_RAID_VOL *vol;
CONFIG_PAGE_IOC_5 *ioc5;
IOC_5_HOT_SPARE *spare;
CONFIG_PAGE_RAID_VOL_0 *vinfo;
RAID_VOL0_PHYS_DISK *disk;
CONFIG_PAGE_RAID_VOL_1 *vnames;
CONFIG_PAGE_RAID_PHYS_DISK_0 *pinfo;
struct mpt_standalone_disk *sdisks;
int fd, i, j, nsdisks;
if (ac != 1) {
warnx("show config: extra arguments");
return (EINVAL);
}
fd = mpt_open(mpt_unit);
if (fd < 0) {
warn("mpt_open");
return (errno);
}
/* Get the config from the controller. */
ioc2 = mpt_read_ioc_page(fd, 2, NULL);
ioc5 = mpt_read_ioc_page(fd, 5, NULL);
if (ioc2 == NULL || ioc5 == NULL) {
warn("Failed to get config");
return (errno);
}
if (mpt_fetch_disks(fd, &nsdisks, &sdisks) < 0) {
warn("Failed to get standalone drive list");
return (errno);
}
/* Dump out the configuration. */
printf("mpt%d Configuration: %d volumes, %d drives\n",
mpt_unit, ioc2->NumActiveVolumes, ioc2->NumActivePhysDisks +
nsdisks);
vol = ioc2->RaidVolume;
for (i = 0; i < ioc2->NumActiveVolumes; vol++, i++) {
printf(" volume %s ", mpt_volume_name(vol->VolumeBus,
vol->VolumeID));
vinfo = mpt_vol_info(fd, vol->VolumeBus, vol->VolumeID, NULL);
if (vinfo == NULL) {
printf("%s UNKNOWN", mpt_raid_level(vol->VolumeType));
} else
print_vol(vinfo, -1);
vnames = mpt_vol_names(fd, vol->VolumeBus, vol->VolumeID, NULL);
if (vnames != NULL) {
if (vnames->Name[0] != '\0')
printf(" <%s>", vnames->Name);
free(vnames);
}
if (vinfo == NULL) {
printf("\n");
continue;
}
printf(" spans:\n");
disk = vinfo->PhysDisk;
for (j = 0; j < vinfo->NumPhysDisks; disk++, j++) {
printf(" drive %u ", disk->PhysDiskNum);
pinfo = mpt_pd_info(fd, disk->PhysDiskNum, NULL);
if (pinfo != NULL) {
print_pd(pinfo, -1, 0);
free(pinfo);
}
printf("\n");
}
if (vinfo->VolumeSettings.HotSparePool != 0) {
printf(" spare pools: ");
print_spare_pools(vinfo->VolumeSettings.HotSparePool);
printf("\n");
}
free(vinfo);
}
spare = ioc5->HotSpare;
for (i = 0; i < ioc5->NumHotSpares; spare++, i++) {
printf(" spare %u ", spare->PhysDiskNum);
pinfo = mpt_pd_info(fd, spare->PhysDiskNum, NULL);
if (pinfo != NULL) {
print_pd(pinfo, -1, 0);
free(pinfo);
}
printf(" backs pool %d\n", ffs(spare->HotSparePool) - 1);
}
for (i = 0; i < nsdisks; i++) {
printf(" drive %s ", sdisks[i].devname);
print_standalone(&sdisks[i], -1, 0);
printf("\n");
}
free(ioc2);
free(ioc5);
free(sdisks);
close(fd);
return (0);
}
MPT_COMMAND(show, config, show_config);
static int
show_volumes(int ac, char **av)
{
CONFIG_PAGE_IOC_2 *ioc2;
CONFIG_PAGE_IOC_2_RAID_VOL *vol;
CONFIG_PAGE_RAID_VOL_0 **volumes;
CONFIG_PAGE_RAID_VOL_1 *vnames;
int fd, i, len, state_len;
if (ac != 1) {
warnx("show volumes: extra arguments");
return (EINVAL);
}
fd = mpt_open(mpt_unit);
if (fd < 0) {
warn("mpt_open");
return (errno);
}
/* Get the volume list from the controller. */
ioc2 = mpt_read_ioc_page(fd, 2, NULL);
if (ioc2 == NULL) {
warn("Failed to get volume list");
return (errno);
}
/*
* Go ahead and read the info for all the volumes and figure
* out the maximum width of the state field.
*/
volumes = malloc(sizeof(*volumes) * ioc2->NumActiveVolumes);
state_len = strlen("State");
vol = ioc2->RaidVolume;
for (i = 0; i < ioc2->NumActiveVolumes; vol++, i++) {
volumes[i] = mpt_vol_info(fd, vol->VolumeBus, vol->VolumeID,
NULL);
if (volumes[i] == NULL)
len = strlen("UNKNOWN");
else
len = strlen(mpt_volstate(
volumes[i]->VolumeStatus.State));
if (len > state_len)
state_len = len;
}
printf("mpt%d Volumes:\n", mpt_unit);
printf(" Id Size Level Stripe ");
len = state_len - strlen("State");
for (i = 0; i < (len + 1) / 2; i++)
printf(" ");
printf("State");
for (i = 0; i < len / 2; i++)
printf(" ");
printf(" Write-Cache Name\n");
vol = ioc2->RaidVolume;
for (i = 0; i < ioc2->NumActiveVolumes; vol++, i++) {
printf("%6s ", mpt_volume_name(vol->VolumeBus, vol->VolumeID));
if (volumes[i] != NULL)
print_vol(volumes[i], state_len);
else
printf(" %-8s %-*s",
mpt_raid_level(vol->VolumeType), state_len,
"UNKNOWN");
if (volumes[i] != NULL) {
if (volumes[i]->VolumeSettings.Settings &
MPI_RAIDVOL0_SETTING_WRITE_CACHING_ENABLE)
printf(" Enabled ");
else
printf(" Disabled ");
} else
printf(" ");
free(volumes[i]);
vnames = mpt_vol_names(fd, vol->VolumeBus, vol->VolumeID, NULL);
if (vnames != NULL) {
if (vnames->Name[0] != '\0')
printf(" <%s>", vnames->Name);
free(vnames);
}
printf("\n");
}
free(ioc2);
close(fd);
return (0);
}
MPT_COMMAND(show, volumes, show_volumes);
static int
show_drives(int ac, char **av)
{
struct mpt_drive_list *list;
struct mpt_standalone_disk *sdisks;
int fd, i, len, nsdisks, state_len;
if (ac != 1) {
warnx("show drives: extra arguments");
return (EINVAL);
}
fd = mpt_open(mpt_unit);
if (fd < 0) {
warn("mpt_open");
return (errno);
}
/* Get the drive list. */
list = mpt_pd_list(fd);
if (list == NULL) {
warn("Failed to get drive list");
return (errno);
}
/* Fetch the list of standalone disks for this controller. */
state_len = 0;
if (mpt_fetch_disks(fd, &nsdisks, &sdisks) != 0) {
nsdisks = 0;
sdisks = NULL;
}
if (nsdisks != 0)
state_len = strlen(STANDALONE_STATE);
/* Walk the drive list to determine width of state column. */
for (i = 0; i < list->ndrives; i++) {
len = strlen(mpt_pdstate(list->drives[i]));
if (len > state_len)
state_len = len;
}
/* List the drives. */
printf("mpt%d Physical Drives:\n", mpt_unit);
for (i = 0; i < list->ndrives; i++) {
printf("%4u ", list->drives[i]->PhysDiskNum);
print_pd(list->drives[i], state_len, 1);
printf("\n");
}
mpt_free_pd_list(list);
for (i = 0; i < nsdisks; i++) {
printf("%4s ", sdisks[i].devname);
print_standalone(&sdisks[i], state_len, 1);
printf("\n");
}
free(sdisks);
close(fd);
return (0);
}
MPT_COMMAND(show, drives, show_drives);
#ifdef DEBUG
static int
show_physdisks(int ac, char **av)
{
CONFIG_PAGE_RAID_PHYS_DISK_0 *pinfo;
U16 IOCStatus;
int fd, i;
if (ac != 1) {
warnx("show drives: extra arguments");
return (EINVAL);
}
fd = mpt_open(mpt_unit);
if (fd < 0) {
warn("mpt_open");
return (errno);
}
/* Try to find each possible phys disk page. */
for (i = 0; i <= 0xff; i++) {
pinfo = mpt_pd_info(fd, i, &IOCStatus);
if (pinfo == NULL) {
if (IOCStatus != MPI_IOCSTATUS_CONFIG_INVALID_PAGE)
warnx("mpt_pd_info(%d): %s", i,
mpt_ioc_status(IOCStatus));
continue;
}
printf("%3u ", i);
print_pd(pinfo, -1, 1);
printf("\n");
}
close(fd);
return (0);
}
MPT_COMMAND(show, pd, show_physdisks);
#endif

View File

@ -0,0 +1,248 @@
/*-
* Copyright (c) 2008 Yahoo!, Inc.
* All rights reserved.
* Written by: John Baldwin <jhb@FreeBSD.org>
*
* 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 <sys/cdefs.h>
__RCSID("$FreeBSD$");
#include <sys/param.h>
#include <sys/errno.h>
#include <err.h>
#include <libutil.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include "mptutil.h"
MPT_TABLE(top, volume);
const char *
mpt_volstate(U8 State)
{
static char buf[16];
switch (State) {
case MPI_RAIDVOL0_STATUS_STATE_OPTIMAL:
return ("OPTIMAL");
case MPI_RAIDVOL0_STATUS_STATE_DEGRADED:
return ("DEGRADED");
case MPI_RAIDVOL0_STATUS_STATE_FAILED:
return ("FAILED");
case MPI_RAIDVOL0_STATUS_STATE_MISSING:
return ("MISSING");
default:
sprintf(buf, "VSTATE 0x%02x", State);
return (buf);
}
}
static int
volume_name(int ac, char **av)
{
CONFIG_PAGE_RAID_VOL_1 *vnames;
U8 VolumeBus, VolumeID;
int fd;
if (ac != 3) {
warnx("name: volume and name required");
return (EINVAL);
}
if (strlen(av[2]) >= sizeof(vnames->Name)) {
warnx("name: new name is too long");
return (ENOSPC);
}
fd = mpt_open(mpt_unit);
if (fd < 0) {
warn("mpt_open");
return (errno);
}
if (mpt_lookup_volume(fd, av[1], &VolumeBus, &VolumeID) < 0) {
warn("Invalid volume: %s", av[1]);
return (errno);
}
vnames = mpt_vol_names(fd, VolumeBus, VolumeID, NULL);
if (vnames == NULL) {
warn("Failed to fetch volume names");
return (errno);
}
if (vnames->Header.PageType != MPI_CONFIG_PAGEATTR_CHANGEABLE) {
warnx("Volume name is read only");
return (EOPNOTSUPP);
}
printf("mpt%u changing volume %s name from \"%s\" to \"%s\"\n",
mpt_unit, mpt_volume_name(VolumeBus, VolumeID), vnames->Name,
av[2]);
bzero(vnames->Name, sizeof(vnames->Name));
strcpy(vnames->Name, av[2]);
if (mpt_write_config_page(fd, vnames, NULL) < 0) {
warn("Failed to set volume name");
return (errno);
}
free(vnames);
close(fd);
return (0);
}
MPT_COMMAND(top, name, volume_name);
static int
volume_status(int ac, char **av)
{
MPI_RAID_VOL_INDICATOR prog;
RAID_VOL0_STATUS VolumeStatus;
uint64_t total, remaining;
float pct;
U8 VolumeBus, VolumeID;
int fd;
if (ac != 2) {
warnx("volume status: %s", ac > 2 ? "extra arguments" :
"volume required");
return (EINVAL);
}
fd = mpt_open(mpt_unit);
if (fd < 0) {
warn("mpt_open");
return (errno);
}
if (mpt_lookup_volume(fd, av[1], &VolumeBus, &VolumeID) < 0) {
warn("Invalid volume: %s", av[1]);
return (errno);
}
if (mpt_raid_action(fd, MPI_RAID_ACTION_INDICATOR_STRUCT, VolumeBus,
VolumeID, 0, 0, NULL, 0, &VolumeStatus, (U32 *)&prog, sizeof(prog),
NULL, NULL, 0) < 0) {
warn("Fetching volume status failed");
return (errno);
}
printf("Volume %s status:\n", mpt_volume_name(VolumeBus, VolumeID));
printf(" state: %s\n", mpt_volstate(VolumeStatus.State));
printf(" flags:");
if (VolumeStatus.Flags & MPI_RAIDVOL0_STATUS_FLAG_ENABLED)
printf(" ENABLED");
else
printf(" DISABLED");
if (VolumeStatus.Flags & MPI_RAIDVOL0_STATUS_FLAG_QUIESCED)
printf(", QUIESCED");
if (VolumeStatus.Flags & MPI_RAIDVOL0_STATUS_FLAG_RESYNC_IN_PROGRESS)
printf(", REBUILDING");
if (VolumeStatus.Flags & MPI_RAIDVOL0_STATUS_FLAG_VOLUME_INACTIVE)
printf(", INACTIVE");
if (VolumeStatus.Flags & MPI_RAIDVOL0_STATUS_FLAG_BAD_BLOCK_TABLE_FULL)
printf(", BAD BLOCK TABLE FULL");
printf("\n");
if (VolumeStatus.Flags & MPI_RAIDVOL0_STATUS_FLAG_RESYNC_IN_PROGRESS) {
total = (uint64_t)prog.TotalBlocks.High << 32 |
prog.TotalBlocks.Low;
remaining = (uint64_t)prog.BlocksRemaining.High << 32 |
prog.BlocksRemaining.Low;
pct = (float)(total - remaining) * 100 / total;
printf(" resync: %.2f%% complete\n", pct);
}
close(fd);
return (0);
}
MPT_COMMAND(volume, status, volume_status);
static int
volume_cache(int ac, char **av)
{
CONFIG_PAGE_RAID_VOL_0 *volume;
U32 Settings, NewSettings;
U8 VolumeBus, VolumeID;
char *s1;
int fd;
if (ac != 3) {
warnx("volume cache: %s", ac > 3 ? "extra arguments" :
"volume required");
return (EINVAL);
}
for (s1 = av[2]; *s1 != '\0'; s1++)
*s1 = tolower(*s1);
if ((strcmp(av[2], "enable")) && (strcmp(av[2], "enabled")) &&
(strcmp(av[2], "disable")) && (strcmp(av[2], "disabled"))) {
warnx("volume cache: invalid flag, must be 'enable' or 'disable'\n");
return (EINVAL);
}
fd = mpt_open(mpt_unit);
if (fd < 0) {
warn("mpt_open");
return (errno);
}
if (mpt_lookup_volume(fd, av[1], &VolumeBus, &VolumeID) < 0) {
warn("Invalid volume: %s", av[1]);
return (errno);
}
volume = mpt_vol_info(fd, VolumeBus, VolumeID, NULL);
if (volume == NULL)
return (-1);
Settings = volume->VolumeSettings.Settings;
NewSettings = Settings;
if (strncmp(av[2], "enable", sizeof("enable")) == 0)
NewSettings |= 0x01;
if (strncmp(av[2], "disable", sizeof("disable")) == 0)
NewSettings &= ~0x01;
if (NewSettings == Settings) {
warnx("volume cache unchanged\n");
close(fd);
return (0);
}
volume->VolumeSettings.Settings = NewSettings;
if (mpt_raid_action(fd, MPI_RAID_ACTION_CHANGE_VOLUME_SETTINGS,
VolumeBus, VolumeID, 0, *(U32 *)&volume->VolumeSettings, NULL, 0,
NULL, NULL, 0, NULL, NULL, 0) < 0)
warnx("volume cache change failed, errno= %d\n", errno);
close(fd);
return (0);
}
MPT_COMMAND(volume, cache, volume_cache);

383
usr.sbin/mptutil/mptutil.8 Normal file
View File

@ -0,0 +1,383 @@
.\"
.\" Copyright (c) 2008 Yahoo!, Inc.
.\" All rights reserved.
.\" Written by: John Baldwin <jhb@FreeBSD.org>
.\"
.\" 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.
.\"
.\" $FreeBSD$
.\"
.Dd August 22, 2008
.Dt MPTUTIL 8
.Os
.Sh NAME
.Nm mptutil
.Nd Utility for managing LSI Fusion-MPT controllers
.Sh SYNOPSIS
.Nm
.Cm version
.Nm
.Op Fl u Ar unit
.Cm show adapter
.Nm
.Op Fl u Ar unit
.Cm show config
.Nm
.Op Fl u Ar unit
.Cm show drives
.Nm
.Op Fl u Ar unit
.Cm show events
.Nm
.Op Fl u Ar unit
.Cm show volumes
.Nm
.Op Fl u Ar unit
.Cm fail Ar drive
.Nm
.Op Fl u Ar unit
.Cm online Ar drive
.Nm
.Op Fl u Ar unit
.Cm offline Ar drive
.Nm
.Op Fl u Ar unit
.Cm name Ar volume Ar name
.Nm
.Op Fl u Ar unit
.Cm volume status Ar volume
.Nm
.Op Fl u Ar unit
.Cm volume cache Ar volume
.Ar enable|disable
.Nm
.Op Fl u Ar unit
.Cm clear
.Nm
.Op Fl u Ar unit
.Cm create Ar type
.Op Fl q
.Op Fl v
.Op Fl s Ar stripe_size
.Ar drive Ns Op \&, Ns Ar drive Ns Op ",..."
.Nm
.Op Fl u Ar unit
.Cm delete Ar volume
.Nm
.Op Fl u Ar unit
.Cm add Ar drive Op Ar volume
.Nm
.Op Fl u Ar unit
.Cm remove Ar drive
.Sh DESCRIPTION
The
.Nm
utility can be used to display or modify various parameters on LSI
Fusion-MPT controllers.
Each invocation of
.Nm
consists of zero or more global options followed by a command.
Commands may support additional optional or required arguments after the
command.
.Pp
Currently one global option is supported:
.Bl -tag -width indent
.It Fl u Ar unit
.Ar unit
specifies the unit of the controller to work with.
If no unit is specified,
then unit 0 is used.
.El
.Pp
Volumes may be specified in two forms.
First,
a volume may be identified by its location as
.Sm off
.Op Ar xx Ns \&:
.Ar yy
.Sm on
where
.Ar xx
is the bus ID and
.Ar yy
is the target ID.
If the bus ID is ommitted,
the volume is assumed to be on bus 0.
Second,
on the volume may be specified by the corresponding
.Em daX
device,
such as
.Em da0 .
.Pp
The
.Xr mpt 4
controller divides drives up into two categories.
Configured drives belong to a RAID volume either as a member drive or as a hot
spare.
Each configured drive is assigned a unique device ID such as 0 or 1 that is
show in
.Cm show config ,
and in the first column of
.Cm show drives .
Any drive not associated with a RAID volume as either a member or a hot spare
is a standalone drive.
Standalone drives are visible to the operating system as SCSI disk devices.
As a result, drives may be specified in three forms.
First,
a configured drive may be identified by its device ID.
Second,
any drive may be identified by its location as
.Sm off
.Ar xx Ns \&:
.Ar yy
.Sm on
where
.Ar xx
is the bus ID and
.Ar yy
is the target ID for each drive as displayed in
.Cm show drives .
Note that unlike volumes,
a drive location always requires the bus ID to avoid confusion with device IDs.
Third,
a standalone drive that is not part of a volume may be identified by its
corresponding
.Em daX
device as displayed in
.Cm show drives .
.Pp
The
.Nm
utility supports several different groups of commands.
The first group of commands provide information about the controller,
the volumes it manages, and the drives it controls.
The second group of commands are used to manage the physical drives
attached to the controller.
The third group of commands are used to manage the logical volumes
managed by the controller.
The fourth group of commands are used to manage the drive configuration for
the controller.
.Pp
The informational commands include:
.Bl -tag -width indent
.It Cm version
Displays the version of
.Nm .
.It Cm show adapter
Displays information about the RAID controller such as the model number.
.It Cm show config
Displays the volume and drive configuration for the controller.
Each volume is listed along with the physical drives that the volume spans.
If any hot spare drives are configured, then they are listed as well.
.It Cm show drives
Lists all of the physical drives attached to the controller.
.It Cm show events
Display all the entries from the controller's event log.
Due to lack of documentation this command isn't very useful currently and
just dumps each log entry in hex.
.It Cm show volumes
Lists all of the logical volumes managed by the controller.
.El
.Pp
The physical drive management commands include:
.Bl -tag -width indent
.It Cm fail Ar drive
Mark
.Ar drive
as
.Dq failed requested .
Note that this state is different from the
.Dq failed
state that is used when the firmware fails a drive.
.Ar Drive
must be a configured drive.
.It Cm online Ar drive
Mark
.Ar drive
as an online drive.
.Ar Drive
must be part a configured drive in either the
.Dq offline
or
.Dq failed requested
states.
.It Cm offline Ar drive
Mark
.Ar drive
as offline.
.Ar Drive
must be a configured, online drive.
.El
.Pp
The logical volume management commands include:
.Bl -tag -width indent
.It Cm name Ar volume Ar name
Sets the name of
.Ar volume
to
.Ar name .
.It Cm volume cache Ar volume Ar enable|disable
Enables or disables the drive write cache for the member drives of
.Ar volume .
.It Cm volume status Ar volume
Display more detailed status about a single volume including the current
progress of a rebuild operation if one is being performed.
.El
.Pp
The configuration commands include:
.Bl -tag -width indent
.It Cm clear
Delete the entire configuration including all volumes and spares.
All drives will become standalone drives.
.It Xo Cm create Ar type
.Op Fl q
.Op Fl v
.Op Fl s Ar stripe_size
.Ar drive Ns Op \&, Ns Ar drive Ns Op ",..."
.Xc
Create a new volume.
The
.Ar type
specifies the type of volume to create.
Currently supported types include:
.Bl -tag -width indent
.It Cm raid0
Creates one RAID0 volume spanning the drives listed in the single drive list.
.It Cm raid1
Creates one RAID1 volume spanning the drives listed in the single drive list.
.It Cm raid1e
Creates one RAID1E volume spanning the drives listed in the single drive list.
.El
.Pp
.Sy Note:
Not all volume types are supported by all controllers.
.Pp
If the
.Fl q
flag is specified after
.Ar type ,
then a
.Dq quick
initialization of the volume will be done.
This is useful when the drives do not contain any existing data that need
to be preserved.
.Pp
If the
.Fl v
flag is specified after
.Ar type ,
then more verbose output will be enabled.
Currently this just provides notification as drives are added to volumes
when building the configuration.
.Pp
The
.Fl s
.Ar stripe_size
parameter allows the stripe size of the array to be set.
By default a stripe size of 64K is used.
The list of valid values for a given
.Ar type
are listed in the output of
.Cm show adapter .
.It Cm delete Ar volume
Delete the volume
.Ar volume .
Member drives will become standalone drives.
.It Cm add Ar drive Op Ar volume
Mark
.Ar drive
as a hot spare.
.Ar Drive
must not be a member of a volume.
If
.Ar volume
is specified,
then the hot spare will be dedicated to that volume.
Otherwise,
.Ar drive
will be used as a global hot spare backing all volumes for this controller.
Note that
.Ar drive
must be as large as the smallest drive in all of the volumes it is going to
back.
.It Cm remove Ar drive
Remove the hot spare
.Ar drive
from service.
It will become a standalone drive.
.El
.Sh EXAMPLES
Mark the drive at bus 0 target 4 as offline:
.Pp
.Dl Nm Cm offline 0:4
.Pp
Create a RAID1 array from the two standalone drives
.Va da1
and
.Va da2 :
.Pp
.Dl Nm Cm create raid1 da1,da2
.Pp
Mark standalone drive
.Va da3
as a global hot spare:
.Pp
.Dl Nm Cm add da3
.Sh SEE ALSO
.Xr mpt 4
.Sh BUGS
Deleting volumes usually provokes a kernel crash in OS versions older than
.Dv 6.3-YAHOO-20080722 .
.Pp
The handling of spare drives appears to be unreliable.
The
.Xr mpt 4
firmware manages spares via spare drive
.Dq pools .
There are eight pools numbered 0 through 7.
Each spare drive can only be assigned to a single pool.
Each volume can be backed by any combination of zero or more spare pools.
The
.Nm
utility attempts to use the following algorithm for managing spares.
Global spares are always assigned to pool 0,
and all volumes are always backed by pool 0.
For dedicated spares,
.Nm
assigns one of the remaining 7 pools to each volume and
assigns dedicated drives to that pool.
In practice however, it seems that assigning a drive as a spare does not
take effect until the box has been rebooted.
Also, the firmware renumbers the spare pool assignments after a reboot
which undoes the effects of the algorithm above.
Simple cases such as assigning global spares seem to work ok
.Pq albeit requiring a reboot to take effect
but more
.Dq exotic
configurations may not work reliably.
.Pp
Drive configuration commands result in an excessive flood of messages on the
console.

123
usr.sbin/mptutil/mptutil.c Normal file
View File

@ -0,0 +1,123 @@
/*-
* Copyright (c) 2008 Yahoo!, Inc.
* All rights reserved.
* Written by: John Baldwin <jhb@FreeBSD.org>
*
* 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 <sys/cdefs.h>
__RCSID("$FreeBSD$");
#include <sys/param.h>
#include <sys/errno.h>
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "mptutil.h"
SET_DECLARE(MPT_DATASET(top), struct mptutil_command);
int mpt_unit;
static void
usage(void)
{
fprintf(stderr, "usage: mptutil [-u unit] <command> ...\n\n");
fprintf(stderr, "Commands include:\n");
fprintf(stderr, " version\n");
fprintf(stderr, " show adapter - display controller information\n");
fprintf(stderr, " show config - display RAID configuration\n");
fprintf(stderr, " show drives - list physical drives\n");
fprintf(stderr, " show events - display event log\n");
fprintf(stderr, " show volumes - list logical volumes\n");
fprintf(stderr, " fail <drive> - fail a physical drive\n");
fprintf(stderr, " online <drive> - bring an offline physical drive online\n");
fprintf(stderr, " offline <drive> - mark a physical drive offline\n");
fprintf(stderr, " name <volume> <name>\n");
fprintf(stderr, " volume status <volume> - display volume status\n");
fprintf(stderr, " volume cache <volume> <enable|disable>\n");
fprintf(stderr, " - Enable or disable the volume drive caches\n");
fprintf(stderr, " clear - clear volume configuration\n");
fprintf(stderr, " create <type> [-vq] [-s stripe] <drive>[,<drive>[,...]]\n");
fprintf(stderr, " delete <volume>\n");
fprintf(stderr, " add <drive> [volume] - add a hot spare\n");
fprintf(stderr, " remove <drive> - remove a hot spare\n");
#ifdef DEBUG
fprintf(stderr, " pd create <drive> - create RAID physdisk\n");
fprintf(stderr, " pd delete <drive> - delete RAID physdisk\n");
fprintf(stderr, " debug - debug 'show config'\n");
#endif
exit(1);
}
static int
version(int ac, char **av)
{
printf("mptutil version 1.0.3");
#ifdef DEBUG
printf(" (DEBUG)");
#endif
printf("\n");
return (0);
}
MPT_COMMAND(top, version, version);
int
main(int ac, char **av)
{
struct mptutil_command **cmd;
int ch;
while ((ch = getopt(ac, av, "u:")) != -1) {
switch (ch) {
case 'u':
mpt_unit = atoi(optarg);
break;
case '?':
usage();
}
}
av += optind;
ac -= optind;
/* getopt() eats av[0], so we can't use mpt_table_handler() directly. */
if (ac == 0)
usage();
SET_FOREACH(cmd, MPT_DATASET(top)) {
if (strcmp((*cmd)->name, av[0]) == 0) {
(*cmd)->handler(ac, av);
return (0);
}
}
warnx("Unknown command %s.", av[0]);
return (0);
}

178
usr.sbin/mptutil/mptutil.h Normal file
View File

@ -0,0 +1,178 @@
/*-
* Copyright (c) 2008 Yahoo!, Inc.
* All rights reserved.
* Written by: John Baldwin <jhb@FreeBSD.org>
*
* 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.
*
* $FreeBSD$
*/
#ifndef __MPTUTIL_H__
#define __MPTUTIL_H__
#include <sys/cdefs.h>
#include <sys/linker_set.h>
#include <dev/mpt/mpilib/mpi_type.h>
#include <dev/mpt/mpilib/mpi.h>
#include <dev/mpt/mpilib/mpi_cnfg.h>
#include <dev/mpt/mpilib/mpi_raid.h>
#define IOC_STATUS_SUCCESS(status) \
(((status) & MPI_IOCSTATUS_MASK) == MPI_IOCSTATUS_SUCCESS)
struct mpt_query_disk {
char devname[SPECNAMELEN + 1];
};
struct mpt_standalone_disk {
uint64_t maxlba;
char inqstring[64];
char devname[SPECNAMELEN + 1];
u_int bus;
u_int target;
};
struct mpt_drive_list {
int ndrives;
CONFIG_PAGE_RAID_PHYS_DISK_0 *drives[0];
};
struct mptutil_command {
const char *name;
int (*handler)(int ac, char **av);
};
#define MPT_DATASET(name) mptutil_ ## name ## _table
#define MPT_COMMAND(set, name, function) \
static struct mptutil_command function ## _mptutil_command = \
{ #name, function }; \
DATA_SET(MPT_DATASET(set), function ## _mptutil_command)
#define MPT_TABLE(set, name) \
SET_DECLARE(MPT_DATASET(name), struct mptutil_command); \
\
static int \
mptutil_ ## name ## _table_handler(int ac, char **av) \
{ \
return (mpt_table_handler(SET_BEGIN(MPT_DATASET(name)), \
SET_LIMIT(MPT_DATASET(name)), ac, av)); \
} \
MPT_COMMAND(set, name, mptutil_ ## name ## _table_handler)
extern int mpt_unit;
#ifdef DEBUG
void hexdump(const void *ptr, int length, const char *hdr, int flags);
#define HD_COLUMN_MASK 0xff
#define HD_DELIM_MASK 0xff00
#define HD_OMIT_COUNT (1 << 16)
#define HD_OMIT_HEX (1 << 17)
#define HD_OMIT_CHARS (1 << 18)
#endif
int mpt_table_handler(struct mptutil_command **start,
struct mptutil_command **end, int ac, char **av);
int mpt_read_config_page_header(int fd, U8 PageType, U8 PageNumber,
U32 PageAddress, CONFIG_PAGE_HEADER *header, U16 *IOCStatus);
void *mpt_read_config_page(int fd, U8 PageType, U8 PageNumber,
U32 PageAddress, U16 *IOCStatus);
void *mpt_read_extended_config_page(int fd, U8 ExtPageType, U8 PageVersion,
U8 PageNumber, U32 PageAddress, U16 *IOCStatus);
int mpt_write_config_page(int fd, void *buf, U16 *IOCStatus);
const char *mpt_ioc_status(U16 IOCStatus);
int mpt_raid_action(int fd, U8 Action, U8 VolumeBus, U8 VolumeID,
U8 PhysDiskNum, U32 ActionDataWord, void *buf, int len,
RAID_VOL0_STATUS *VolumeStatus, U32 *ActionData, int datalen,
U16 *IOCStatus, U16 *ActionStatus, int write);
const char *mpt_raid_status(U16 ActionStatus);
int mpt_open(int unit);
const char *mpt_raid_level(U8 VolumeType);
const char *mpt_volstate(U8 State);
const char *mpt_pdstate(CONFIG_PAGE_RAID_PHYS_DISK_0 *info);
const char *mpt_pd_inq_string(CONFIG_PAGE_RAID_PHYS_DISK_0 *pd_info);
struct mpt_drive_list *mpt_pd_list(int fd);
void mpt_free_pd_list(struct mpt_drive_list *list);
int mpt_query_disk(U8 VolumeBus, U8 VolumeID, struct mpt_query_disk *qd);
const char *mpt_volume_name(U8 VolumeBus, U8 VolumeID);
int mpt_fetch_disks(int fd, int *ndisks,
struct mpt_standalone_disk **disksp);
int mpt_lock_volume(U8 VolumeBus, U8 VolumeID);
int mpt_lookup_drive(struct mpt_drive_list *list, const char *drive,
U8 *PhysDiskNum);
int mpt_lookup_volume(int fd, const char *name, U8 *VolumeBus,
U8 *VolumeID);
int mpt_rescan_bus(int bus, int id);
static __inline void *
mpt_read_man_page(int fd, U8 PageNumber, U16 *IOCStatus)
{
return (mpt_read_config_page(fd, MPI_CONFIG_PAGETYPE_MANUFACTURING,
PageNumber, 0, IOCStatus));
}
static __inline void *
mpt_read_ioc_page(int fd, U8 PageNumber, U16 *IOCStatus)
{
return (mpt_read_config_page(fd, MPI_CONFIG_PAGETYPE_IOC, PageNumber,
0, IOCStatus));
}
static __inline U32
mpt_vol_pageaddr(U8 VolumeBus, U8 VolumeID)
{
return (VolumeBus << 8 | VolumeID);
}
static __inline CONFIG_PAGE_RAID_VOL_0 *
mpt_vol_info(int fd, U8 VolumeBus, U8 VolumeID, U16 *IOCStatus)
{
return (mpt_read_config_page(fd, MPI_CONFIG_PAGETYPE_RAID_VOLUME, 0,
mpt_vol_pageaddr(VolumeBus, VolumeID), IOCStatus));
}
static __inline CONFIG_PAGE_RAID_VOL_1 *
mpt_vol_names(int fd, U8 VolumeBus, U8 VolumeID, U16 *IOCStatus)
{
return (mpt_read_config_page(fd, MPI_CONFIG_PAGETYPE_RAID_VOLUME, 1,
mpt_vol_pageaddr(VolumeBus, VolumeID), IOCStatus));
}
static __inline CONFIG_PAGE_RAID_PHYS_DISK_0 *
mpt_pd_info(int fd, U8 PhysDiskNum, U16 *IOCStatus)
{
return (mpt_read_config_page(fd, MPI_CONFIG_PAGETYPE_RAID_PHYSDISK, 0,
PhysDiskNum, IOCStatus));
}
#endif /* !__MPTUTIL_H__ */