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:
parent
6d600732eb
commit
fc58801ccc
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/head/; revision=196212
@ -104,6 +104,7 @@ SUBDIR= ${_ac} \
|
||||
${_mount_smbfs} \
|
||||
${_moused} \
|
||||
${_mptable} \
|
||||
mptutil \
|
||||
mtest \
|
||||
mtree \
|
||||
${_named} \
|
||||
|
19
usr.sbin/mptutil/Makefile
Normal file
19
usr.sbin/mptutil/Makefile
Normal 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
569
usr.sbin/mptutil/mpt_cam.c
Normal 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
639
usr.sbin/mptutil/mpt_cmd.c
Normal 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
|
1160
usr.sbin/mptutil/mpt_config.c
Normal file
1160
usr.sbin/mptutil/mpt_config.c
Normal file
File diff suppressed because it is too large
Load Diff
395
usr.sbin/mptutil/mpt_drive.c
Normal file
395
usr.sbin/mptutil/mpt_drive.c
Normal 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
155
usr.sbin/mptutil/mpt_evt.c
Normal 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
559
usr.sbin/mptutil/mpt_show.c
Normal 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
|
248
usr.sbin/mptutil/mpt_volume.c
Normal file
248
usr.sbin/mptutil/mpt_volume.c
Normal 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
383
usr.sbin/mptutil/mptutil.8
Normal 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
123
usr.sbin/mptutil/mptutil.c
Normal 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
178
usr.sbin/mptutil/mptutil.h
Normal 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__ */
|
Loading…
Reference in New Issue
Block a user