freebsd-dev/usr.sbin/mptutil/mpt_config.c
Pedro F. Giffuni 8a16b7a18f General further adoption of SPDX licensing ID tags.
Mainly focus on files that use BSD 3-Clause license.

The Software Package Data Exchange (SPDX) group provides a specification
to make it easier for automated tools to detect and summarize well known
opensource licenses. We are gradually adopting the specification, noting
that the tags are considered only advisory and do not, in any way,
superceed or replace the license texts.

Special thanks to Wind River for providing access to "The Duke of
Highlander" tool: an older (2014) run over FreeBSD tree was useful as a
starting point.
2017-11-20 19:49:47 +00:00

1202 lines
28 KiB
C

/*-
* SPDX-License-Identifier: BSD-3-Clause
*
* 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 <fcntl.h>
#include <libutil.h>
#include <paths.h>
#ifdef DEBUG
#include <stdint.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "mptutil.h"
#ifdef DEBUG
static void dump_config(CONFIG_PAGE_RAID_VOL_0 *vol);
#endif
static long
dehumanize(const char *value)
{
char *vtp;
long iv;
if (value == NULL)
return (0);
iv = strtoq(value, &vtp, 0);
if (vtp == value || (vtp[0] != '\0' && vtp[1] != '\0')) {
return (0);
}
switch (vtp[0]) {
case 't': case 'T':
iv *= 1024;
case 'g': case 'G':
iv *= 1024;
case 'm': case 'M':
iv *= 1024;
case 'k': case 'K':
iv *= 1024;
case '\0':
break;
default:
return (0);
}
return (iv);
}
/*
* Lock the volume by opening its /dev device read/write. This will
* only work if nothing else has it opened (including mounts). We
* leak the fd on purpose since this application is not long-running.
*/
int
mpt_lock_volume(U8 VolumeBus, U8 VolumeID)
{
char path[MAXPATHLEN];
struct mpt_query_disk qd;
int error, vfd;
error = mpt_query_disk(VolumeBus, VolumeID, &qd);
if (error == ENOENT)
/*
* This means there isn't a CAM device associated with
* the volume, and thus it is already implicitly
* locked, so just return.
*/
return (0);
if (error) {
warnc(error, "Unable to lookup volume device name");
return (error);
}
snprintf(path, sizeof(path), "%s%s", _PATH_DEV, qd.devname);
vfd = open(path, O_RDWR);
if (vfd < 0) {
error = errno;
warn("Unable to lock volume %s", qd.devname);
return (error);
}
return (0);
}
static int
mpt_lock_physdisk(struct mpt_standalone_disk *disk)
{
char path[MAXPATHLEN];
int dfd, error;
snprintf(path, sizeof(path), "%s%s", _PATH_DEV, disk->devname);
dfd = open(path, O_RDWR);
if (dfd < 0) {
error = errno;
warn("Unable to lock disk %s", disk->devname);
return (error);
}
return (0);
}
static int
mpt_lookup_standalone_disk(const char *name, struct mpt_standalone_disk *disks,
int ndisks, int *index)
{
char *cp;
long bus, id;
int i;
/* Check for a raw <bus>:<id> string. */
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) {
return (EINVAL);
}
for (i = 0; i < ndisks; i++) {
if (disks[i].bus == (U8)bus &&
disks[i].target == (U8)id) {
*index = i;
return (0);
}
}
return (ENOENT);
}
}
if (name[0] == 'd' && name[1] == 'a') {
for (i = 0; i < ndisks; i++) {
if (strcmp(name, disks[i].devname) == 0) {
*index = i;
return (0);
}
}
return (ENOENT);
}
return (EINVAL);
}
/*
* Mark a standalone disk as being a physical disk.
*/
static int
mpt_create_physdisk(int fd, struct mpt_standalone_disk *disk, U8 *PhysDiskNum)
{
CONFIG_PAGE_HEADER header;
CONFIG_PAGE_RAID_PHYS_DISK_0 *config_page;
int error;
U32 ActionData;
error = mpt_read_config_page_header(fd, MPI_CONFIG_PAGETYPE_RAID_PHYSDISK,
0, 0, &header, NULL);
if (error)
return (error);
if (header.PageVersion > MPI_RAIDPHYSDISKPAGE0_PAGEVERSION) {
warnx("Unsupported RAID physdisk page 0 version %d",
header.PageVersion);
return (EOPNOTSUPP);
}
config_page = calloc(1, sizeof(CONFIG_PAGE_RAID_PHYS_DISK_0));
config_page->Header.PageType = MPI_CONFIG_PAGETYPE_RAID_PHYSDISK;
config_page->Header.PageNumber = 0;
config_page->Header.PageLength = sizeof(CONFIG_PAGE_RAID_PHYS_DISK_0) /
4;
config_page->PhysDiskIOC = 0; /* XXX */
config_page->PhysDiskBus = disk->bus;
config_page->PhysDiskID = disk->target;
/* XXX: Enclosure info for PhysDiskSettings? */
error = mpt_raid_action(fd, MPI_RAID_ACTION_CREATE_PHYSDISK, 0, 0, 0, 0,
config_page, sizeof(CONFIG_PAGE_RAID_PHYS_DISK_0), NULL,
&ActionData, sizeof(ActionData), NULL, NULL, 1);
if (error)
return (error);
*PhysDiskNum = ActionData & 0xff;
return (0);
}
static int
mpt_delete_physdisk(int fd, U8 PhysDiskNum)
{
return (mpt_raid_action(fd, MPI_RAID_ACTION_DELETE_PHYSDISK, 0, 0,
PhysDiskNum, 0, NULL, 0, NULL, NULL, 0, NULL, NULL, 0));
}
/*
* MPT's firmware does not have a clear command. Instead, we
* implement it by deleting each array and disk by hand.
*/
static int
clear_config(int ac, char **av)
{
CONFIG_PAGE_IOC_2 *ioc2;
CONFIG_PAGE_IOC_2_RAID_VOL *vol;
CONFIG_PAGE_IOC_3 *ioc3;
IOC_3_PHYS_DISK *disk;
CONFIG_PAGE_IOC_5 *ioc5;
IOC_5_HOT_SPARE *spare;
int ch, error, fd, i;
fd = mpt_open(mpt_unit);
if (fd < 0) {
error = errno;
warn("mpt_open");
return (error);
}
ioc2 = mpt_read_ioc_page(fd, 2, NULL);
if (ioc2 == NULL) {
error = errno;
warn("Failed to fetch volume list");
return (error);
}
/* Lock all the volumes first. */
vol = ioc2->RaidVolume;
for (i = 0; i < ioc2->NumActiveVolumes; vol++, i++) {
if (mpt_lock_volume(vol->VolumeBus, vol->VolumeID) < 0) {
warnx("Volume %s is busy and cannot be deleted",
mpt_volume_name(vol->VolumeBus, vol->VolumeID));
return (EBUSY);
}
}
printf(
"Are you sure you wish to clear the configuration on mpt%u? [y/N] ",
mpt_unit);
ch = getchar();
if (ch != 'y' && ch != 'Y') {
printf("\nAborting\n");
return (0);
}
/* Delete all the volumes. */
vol = ioc2->RaidVolume;
for (i = 0; i < ioc2->NumActiveVolumes; vol++, i++) {
error = mpt_raid_action(fd, MPI_RAID_ACTION_DELETE_VOLUME,
vol->VolumeBus, vol->VolumeID, 0,
MPI_RAID_ACTION_ADATA_DEL_PHYS_DISKS |
MPI_RAID_ACTION_ADATA_ZERO_LBA0, NULL, 0, NULL, NULL, 0,
NULL, NULL, 0);
if (error)
warnc(error, "Failed to delete volume %s",
mpt_volume_name(vol->VolumeBus, vol->VolumeID));
}
free(ioc2);
/* Delete all the spares. */
ioc5 = mpt_read_ioc_page(fd, 5, NULL);
if (ioc5 == NULL)
warn("Failed to fetch spare list");
else {
spare = ioc5->HotSpare;
for (i = 0; i < ioc5->NumHotSpares; spare++, i++)
if (mpt_delete_physdisk(fd, spare->PhysDiskNum) < 0)
warn("Failed to delete physical disk %d",
spare->PhysDiskNum);
free(ioc5);
}
/* Delete any RAID physdisks that may be left. */
ioc3 = mpt_read_ioc_page(fd, 3, NULL);
if (ioc3 == NULL)
warn("Failed to fetch drive list");
else {
disk = ioc3->PhysDisk;
for (i = 0; i < ioc3->NumPhysDisks; disk++, i++)
if (mpt_delete_physdisk(fd, disk->PhysDiskNum) < 0)
warn("Failed to delete physical disk %d",
disk->PhysDiskNum);
free(ioc3);
}
printf("mpt%d: Configuration cleared\n", mpt_unit);
mpt_rescan_bus(-1, -1);
close(fd);
return (0);
}
MPT_COMMAND(top, clear, clear_config);
#define RT_RAID0 0
#define RT_RAID1 1
#define RT_RAID1E 2
static struct raid_type_entry {
const char *name;
int raid_type;
} raid_type_table[] = {
{ "raid0", RT_RAID0 },
{ "raid-0", RT_RAID0 },
{ "raid1", RT_RAID1 },
{ "raid-1", RT_RAID1 },
{ "mirror", RT_RAID1 },
{ "raid1e", RT_RAID1E },
{ "raid-1e", RT_RAID1E },
{ NULL, 0 },
};
struct config_id_state {
struct mpt_standalone_disk *sdisks;
struct mpt_drive_list *list;
CONFIG_PAGE_IOC_2 *ioc2;
U8 target_id;
int nsdisks;
};
struct drive_info {
CONFIG_PAGE_RAID_PHYS_DISK_0 *info;
struct mpt_standalone_disk *sdisk;
};
struct volume_info {
int drive_count;
struct drive_info *drives;
};
/* Parse a comma-separated list of drives for a volume. */
static int
parse_volume(int fd, int raid_type, struct config_id_state *state,
char *volume_str, struct volume_info *info)
{
struct drive_info *dinfo;
U8 PhysDiskNum;
char *cp;
int count, error, i;
cp = volume_str;
for (count = 0; cp != NULL; count++) {
cp = strchr(cp, ',');
if (cp != NULL) {
cp++;
if (*cp == ',') {
warnx("Invalid drive list '%s'", volume_str);
return (EINVAL);
}
}
}
/* Validate the number of drives for this volume. */
switch (raid_type) {
case RT_RAID0:
if (count < 2) {
warnx("RAID0 requires at least 2 drives in each "
"array");
return (EINVAL);
}
break;
case RT_RAID1:
if (count != 2) {
warnx("RAID1 requires exactly 2 drives in each "
"array");
return (EINVAL);
}
break;
case RT_RAID1E:
if (count < 3) {
warnx("RAID1E requires at least 3 drives in each "
"array");
return (EINVAL);
}
break;
}
/* Validate each drive. */
info->drives = calloc(count, sizeof(struct drive_info));
info->drive_count = count;
for (dinfo = info->drives; (cp = strsep(&volume_str, ",")) != NULL;
dinfo++) {
/* If this drive is already a RAID phys just fetch the info. */
error = mpt_lookup_drive(state->list, cp, &PhysDiskNum);
if (error == 0) {
dinfo->info = mpt_pd_info(fd, PhysDiskNum, NULL);
if (dinfo->info == NULL)
return (errno);
continue;
}
/* See if it is a standalone disk. */
if (mpt_lookup_standalone_disk(cp, state->sdisks,
state->nsdisks, &i) < 0) {
error = errno;
warn("Unable to lookup drive %s", cp);
return (error);
}
dinfo->sdisk = &state->sdisks[i];
/* Lock the disk, we will create phys disk pages later. */
if (mpt_lock_physdisk(dinfo->sdisk) < 0)
return (errno);
}
return (0);
}
/*
* Add RAID physdisk pages for any standalone disks that a volume is
* going to use.
*/
static int
add_drives(int fd, struct volume_info *info, int verbose)
{
struct drive_info *dinfo;
U8 PhysDiskNum;
int error, i;
for (i = 0, dinfo = info->drives; i < info->drive_count;
i++, dinfo++) {
if (dinfo->info == NULL) {
if (mpt_create_physdisk(fd, dinfo->sdisk,
&PhysDiskNum) < 0) {
error = errno;
warn(
"Failed to create physical disk page for %s",
dinfo->sdisk->devname);
return (error);
}
if (verbose)
printf("Added drive %s with PhysDiskNum %u\n",
dinfo->sdisk->devname, PhysDiskNum);
dinfo->info = mpt_pd_info(fd, PhysDiskNum, NULL);
if (dinfo->info == NULL)
return (errno);
}
}
return (0);
}
/*
* Find the next free target ID assuming that 'target_id' is the last
* one used. 'target_id' should be 0xff for the initial test.
*/
static U8
find_next_volume(struct config_id_state *state)
{
CONFIG_PAGE_IOC_2_RAID_VOL *vol;
int i;
restart:
/* Assume the current one is used. */
state->target_id++;
/* Search drives first. */
for (i = 0; i < state->nsdisks; i++)
if (state->sdisks[i].target == state->target_id)
goto restart;
for (i = 0; i < state->list->ndrives; i++)
if (state->list->drives[i]->PhysDiskID == state->target_id)
goto restart;
/* Search volumes second. */
vol = state->ioc2->RaidVolume;
for (i = 0; i < state->ioc2->NumActiveVolumes; vol++, i++)
if (vol->VolumeID == state->target_id)
goto restart;
return (state->target_id);
}
/* Create a volume and populate it with drives. */
static CONFIG_PAGE_RAID_VOL_0 *
build_volume(int fd, struct volume_info *info, int raid_type, long stripe_size,
struct config_id_state *state, int verbose)
{
CONFIG_PAGE_HEADER header;
CONFIG_PAGE_RAID_VOL_0 *vol;
RAID_VOL0_PHYS_DISK *rdisk;
struct drive_info *dinfo;
U32 MinLBA;
uint64_t MaxLBA;
size_t page_size;
int error, i;
error = mpt_read_config_page_header(fd, MPI_CONFIG_PAGETYPE_RAID_VOLUME,
0, 0, &header, NULL);
if (error) {
errno = error;
return (NULL);
}
if (header.PageVersion > MPI_RAIDVOLPAGE0_PAGEVERSION) {
warnx("Unsupported RAID volume page 0 version %d",
header.PageVersion);
errno = EOPNOTSUPP;
return (NULL);
}
page_size = sizeof(CONFIG_PAGE_RAID_VOL_0) +
sizeof(RAID_VOL0_PHYS_DISK) * (info->drive_count - 1);
vol = calloc(1, page_size);
if (vol == NULL)
return (NULL);
/* Header */
vol->Header.PageType = MPI_CONFIG_PAGETYPE_RAID_VOLUME;
vol->Header.PageNumber = 0;
vol->Header.PageLength = page_size / 4;
/* Properties */
vol->VolumeID = find_next_volume(state);
vol->VolumeBus = 0;
vol->VolumeIOC = 0; /* XXX */
vol->VolumeStatus.Flags = MPI_RAIDVOL0_STATUS_FLAG_ENABLED;
vol->VolumeStatus.State = MPI_RAIDVOL0_STATUS_STATE_OPTIMAL;
vol->VolumeSettings.Settings = MPI_RAIDVOL0_SETTING_USE_DEFAULTS;
vol->VolumeSettings.HotSparePool = MPI_RAID_HOT_SPARE_POOL_0;
vol->NumPhysDisks = info->drive_count;
/* Find the smallest drive. */
MinLBA = info->drives[0].info->MaxLBA;
for (i = 1; i < info->drive_count; i++)
if (info->drives[i].info->MaxLBA < MinLBA)
MinLBA = info->drives[i].info->MaxLBA;
/*
* Now chop off 512MB at the end to leave room for the
* metadata. The controller might only use 64MB, but we just
* chop off the max to be simple.
*/
MinLBA -= (512 * 1024 * 1024) / 512;
switch (raid_type) {
case RT_RAID0:
vol->VolumeType = MPI_RAID_VOL_TYPE_IS;
vol->StripeSize = stripe_size / 512;
MaxLBA = MinLBA * info->drive_count;
break;
case RT_RAID1:
vol->VolumeType = MPI_RAID_VOL_TYPE_IM;
MaxLBA = MinLBA * (info->drive_count / 2);
break;
case RT_RAID1E:
vol->VolumeType = MPI_RAID_VOL_TYPE_IME;
vol->StripeSize = stripe_size / 512;
MaxLBA = MinLBA * info->drive_count / 2;
break;
default:
/* Pacify gcc. */
abort();
}
/*
* If the controller doesn't support 64-bit addressing and the
* new volume is larger than 2^32 blocks, warn the user and
* truncate the volume.
*/
if (MaxLBA >> 32 != 0 &&
!(state->ioc2->CapabilitiesFlags &
MPI_IOCPAGE2_CAP_FLAGS_RAID_64_BIT_ADDRESSING)) {
warnx(
"Controller does not support volumes > 2TB, truncating volume.");
MaxLBA = 0xffffffff;
}
vol->MaxLBA = MaxLBA;
vol->MaxLBAHigh = MaxLBA >> 32;
/* Populate drives. */
for (i = 0, dinfo = info->drives, rdisk = vol->PhysDisk;
i < info->drive_count; i++, dinfo++, rdisk++) {
if (verbose)
printf("Adding drive %u (%u:%u) to volume %u:%u\n",
dinfo->info->PhysDiskNum, dinfo->info->PhysDiskBus,
dinfo->info->PhysDiskID, vol->VolumeBus,
vol->VolumeID);
if (raid_type == RT_RAID1) {
if (i == 0)
rdisk->PhysDiskMap =
MPI_RAIDVOL0_PHYSDISK_PRIMARY;
else
rdisk->PhysDiskMap =
MPI_RAIDVOL0_PHYSDISK_SECONDARY;
} else
rdisk->PhysDiskMap = i;
rdisk->PhysDiskNum = dinfo->info->PhysDiskNum;
}
return (vol);
}
static int
create_volume(int ac, char **av)
{
CONFIG_PAGE_RAID_VOL_0 *vol;
struct config_id_state state;
struct volume_info *info;
long stripe_size;
int ch, error, fd, i, quick, raid_type, verbose;
#ifdef DEBUG
int dump;
#endif
if (ac < 2) {
warnx("create: volume type required");
return (EINVAL);
}
fd = mpt_open(mpt_unit);
if (fd < 0) {
error = errno;
warn("mpt_open");
return (error);
}
/* Lookup the RAID type first. */
raid_type = -1;
for (i = 0; raid_type_table[i].name != NULL; i++)
if (strcasecmp(raid_type_table[i].name, av[1]) == 0) {
raid_type = raid_type_table[i].raid_type;
break;
}
if (raid_type == -1) {
warnx("Unknown or unsupported volume type %s", av[1]);
return (EINVAL);
}
/* Parse any options. */
optind = 2;
#ifdef DEBUG
dump = 0;
#endif
quick = 0;
verbose = 0;
stripe_size = 64 * 1024;
while ((ch = getopt(ac, av, "dqs:v")) != -1) {
switch (ch) {
#ifdef DEBUG
case 'd':
dump = 1;
break;
#endif
case 'q':
quick = 1;
break;
case 's':
stripe_size = dehumanize(optarg);
if ((stripe_size < 512) || (!powerof2(stripe_size))) {
warnx("Invalid stripe size %s", optarg);
return (EINVAL);
}
break;
case 'v':
verbose = 1;
break;
case '?':
default:
return (EINVAL);
}
}
ac -= optind;
av += optind;
/* Fetch existing config data. */
state.ioc2 = mpt_read_ioc_page(fd, 2, NULL);
if (state.ioc2 == NULL) {
error = errno;
warn("Failed to read volume list");
return (error);
}
state.list = mpt_pd_list(fd);
if (state.list == NULL)
return (errno);
error = mpt_fetch_disks(fd, &state.nsdisks, &state.sdisks);
if (error) {
warn("Failed to fetch standalone disk list");
return (error);
}
state.target_id = 0xff;
/* Parse the drive list. */
if (ac != 1) {
warnx("Exactly one drive list is required");
return (EINVAL);
}
info = calloc(1, sizeof(*info));
if (info == NULL)
return (ENOMEM);
error = parse_volume(fd, raid_type, &state, av[0], info);
if (error)
return (error);
/* Create RAID physdisk pages for standalone disks. */
error = add_drives(fd, info, verbose);
if (error)
return (error);
/* Build the volume. */
vol = build_volume(fd, info, raid_type, stripe_size, &state, verbose);
if (vol == NULL)
return (errno);
#ifdef DEBUG
if (dump) {
dump_config(vol);
goto skip;
}
#endif
/* Send the new volume to the controller. */
error = mpt_raid_action(fd, MPI_RAID_ACTION_CREATE_VOLUME, vol->VolumeBus,
vol->VolumeID, 0, quick ? MPI_RAID_ACTION_ADATA_DO_NOT_SYNC : 0,
vol, vol->Header.PageLength * 4, NULL, NULL, 0, NULL, NULL, 1);
if (error) {
errno = error;
warn("Failed to add volume");
return (error);
}
#ifdef DEBUG
skip:
#endif
mpt_rescan_bus(vol->VolumeBus, vol->VolumeID);
/* Clean up. */
free(vol);
free(info);
free(state.sdisks);
mpt_free_pd_list(state.list);
free(state.ioc2);
close(fd);
return (0);
}
MPT_COMMAND(top, create, create_volume);
static int
delete_volume(int ac, char **av)
{
U8 VolumeBus, VolumeID;
int error, fd;
if (ac != 2) {
warnx("delete: volume required");
return (EINVAL);
}
fd = mpt_open(mpt_unit);
if (fd < 0) {
error = errno;
warn("mpt_open");
return (error);
}
error = mpt_lookup_volume(fd, av[1], &VolumeBus, &VolumeID);
if (error) {
warnc(error, "Invalid volume %s", av[1]);
return (error);
}
if (mpt_lock_volume(VolumeBus, VolumeID) < 0)
return (errno);
error = mpt_raid_action(fd, MPI_RAID_ACTION_DELETE_VOLUME, VolumeBus,
VolumeID, 0, MPI_RAID_ACTION_ADATA_DEL_PHYS_DISKS |
MPI_RAID_ACTION_ADATA_ZERO_LBA0, NULL, 0, NULL, NULL, 0, NULL,
NULL, 0);
if (error) {
warnc(error, "Failed to delete volume");
return (error);
}
mpt_rescan_bus(-1, -1);
close(fd);
return (0);
}
MPT_COMMAND(top, delete, delete_volume);
static int
find_volume_spare_pool(int fd, const char *name, int *pool)
{
CONFIG_PAGE_RAID_VOL_0 *info;
CONFIG_PAGE_IOC_2 *ioc2;
CONFIG_PAGE_IOC_2_RAID_VOL *vol;
U8 VolumeBus, VolumeID;
int error, i, j, new_pool, pool_count[7];
error = mpt_lookup_volume(fd, name, &VolumeBus, &VolumeID);
if (error) {
warnc(error, "Invalid volume %s", name);
return (error);
}
info = mpt_vol_info(fd, VolumeBus, VolumeID, NULL);
if (info == NULL)
return (errno);
/*
* Check for an existing pool other than pool 0 (used for
* global spares).
*/
if ((info->VolumeSettings.HotSparePool & ~MPI_RAID_HOT_SPARE_POOL_0) !=
0) {
*pool = 1 << (ffs(info->VolumeSettings.HotSparePool &
~MPI_RAID_HOT_SPARE_POOL_0) - 1);
return (0);
}
free(info);
/*
* Try to find a free pool. First, figure out which pools are
* in use.
*/
ioc2 = mpt_read_ioc_page(fd, 2, NULL);
if (ioc2 == NULL) {
error = errno;
warn("Failed to fetch volume list");
return (error);
}
bzero(pool_count, sizeof(pool_count));
vol = ioc2->RaidVolume;
for (i = 0; i < ioc2->NumActiveVolumes; vol++, i++) {
info = mpt_vol_info(fd, vol->VolumeBus, vol->VolumeID, NULL);
if (info == NULL)
return (errno);
for (j = 0; j < 7; j++)
if (info->VolumeSettings.HotSparePool & (1 << (j + 1)))
pool_count[j]++;
free(info);
}
free(ioc2);
/* Find the pool with the lowest use count. */
new_pool = 0;
for (i = 1; i < 7; i++)
if (pool_count[i] < pool_count[new_pool])
new_pool = i;
new_pool++;
/* Add this pool to the volume. */
info = mpt_vol_info(fd, VolumeBus, VolumeID, NULL);
if (info == NULL)
return (error);
info->VolumeSettings.HotSparePool |= (1 << new_pool);
error = mpt_raid_action(fd, MPI_RAID_ACTION_CHANGE_VOLUME_SETTINGS,
VolumeBus, VolumeID, 0, *(U32 *)&info->VolumeSettings, NULL, 0,
NULL, NULL, 0, NULL, NULL, 0);
if (error) {
warnx("Failed to add spare pool %d to %s", new_pool,
mpt_volume_name(VolumeBus, VolumeID));
return (error);
}
free(info);
*pool = (1 << new_pool);
return (0);
}
static int
add_spare(int ac, char **av)
{
CONFIG_PAGE_RAID_PHYS_DISK_0 *info;
struct mpt_standalone_disk *sdisks;
struct mpt_drive_list *list;
U8 PhysDiskNum;
int error, fd, i, nsdisks, pool;
if (ac < 2) {
warnx("add spare: drive required");
return (EINVAL);
}
if (ac > 3) {
warnx("add spare: extra arguments");
return (EINVAL);
}
fd = mpt_open(mpt_unit);
if (fd < 0) {
error = errno;
warn("mpt_open");
return (error);
}
if (ac == 3) {
error = find_volume_spare_pool(fd, av[2], &pool);
if (error)
return (error);
} else
pool = MPI_RAID_HOT_SPARE_POOL_0;
list = mpt_pd_list(fd);
if (list == NULL)
return (errno);
error = mpt_lookup_drive(list, av[1], &PhysDiskNum);
if (error) {
error = mpt_fetch_disks(fd, &nsdisks, &sdisks);
if (error != 0) {
warn("Failed to fetch standalone disk list");
return (error);
}
if (mpt_lookup_standalone_disk(av[1], sdisks, nsdisks, &i) <
0) {
error = errno;
warn("Unable to lookup drive %s", av[1]);
return (error);
}
if (mpt_lock_physdisk(&sdisks[i]) < 0)
return (errno);
if (mpt_create_physdisk(fd, &sdisks[i], &PhysDiskNum) < 0) {
error = errno;
warn("Failed to create physical disk page");
return (error);
}
free(sdisks);
}
mpt_free_pd_list(list);
info = mpt_pd_info(fd, PhysDiskNum, NULL);
if (info == NULL) {
error = errno;
warn("Failed to fetch drive info");
return (error);
}
info->PhysDiskSettings.HotSparePool = pool;
error = mpt_raid_action(fd, MPI_RAID_ACTION_CHANGE_PHYSDISK_SETTINGS, 0,
0, PhysDiskNum, *(U32 *)&info->PhysDiskSettings, NULL, 0, NULL,
NULL, 0, NULL, NULL, 0);
if (error) {
warnc(error, "Failed to assign spare");
return (error);
}
free(info);
close(fd);
return (0);
}
MPT_COMMAND(top, add, add_spare);
static int
remove_spare(int ac, char **av)
{
CONFIG_PAGE_RAID_PHYS_DISK_0 *info;
struct mpt_drive_list *list;
U8 PhysDiskNum;
int error, fd;
if (ac != 2) {
warnx("remove spare: drive required");
return (EINVAL);
}
fd = mpt_open(mpt_unit);
if (fd < 0) {
error = errno;
warn("mpt_open");
return (error);
}
list = mpt_pd_list(fd);
if (list == NULL)
return (errno);
error = mpt_lookup_drive(list, av[1], &PhysDiskNum);
if (error) {
warn("Failed to find drive %s", av[1]);
return (error);
}
mpt_free_pd_list(list);
info = mpt_pd_info(fd, PhysDiskNum, NULL);
if (info == NULL) {
error = errno;
warn("Failed to fetch drive info");
return (error);
}
if (info->PhysDiskSettings.HotSparePool == 0) {
warnx("Drive %u is not a hot spare", PhysDiskNum);
return (EINVAL);
}
if (mpt_delete_physdisk(fd, PhysDiskNum) < 0) {
error = errno;
warn("Failed to delete physical disk page");
return (error);
}
mpt_rescan_bus(info->PhysDiskBus, info->PhysDiskID);
free(info);
close(fd);
return (0);
}
MPT_COMMAND(top, remove, remove_spare);
#ifdef DEBUG
MPT_TABLE(top, pd);
static int
pd_create(int ac, char **av)
{
struct mpt_standalone_disk *disks;
int error, fd, i, ndisks;
U8 PhysDiskNum;
if (ac != 2) {
warnx("pd create: drive required");
return (EINVAL);
}
fd = mpt_open(mpt_unit);
if (fd < 0) {
error = errno;
warn("mpt_open");
return (error);
}
error = mpt_fetch_disks(fd, &ndisks, &disks);
if (error != 0) {
warn("Failed to fetch standalone disk list");
return (error);
}
if (mpt_lookup_standalone_disk(av[1], disks, ndisks, &i) < 0) {
error = errno;
warn("Unable to lookup drive");
return (error);
}
if (mpt_lock_physdisk(&disks[i]) < 0)
return (errno);
if (mpt_create_physdisk(fd, &disks[i], &PhysDiskNum) < 0) {
error = errno;
warn("Failed to create physical disk page");
return (error);
}
free(disks);
printf("Added drive %s with PhysDiskNum %u\n", av[1], PhysDiskNum);
close(fd);
return (0);
}
MPT_COMMAND(pd, create, pd_create);
static int
pd_delete(int ac, char **av)
{
CONFIG_PAGE_RAID_PHYS_DISK_0 *info;
struct mpt_drive_list *list;
int error, fd;
U8 PhysDiskNum;
if (ac != 2) {
warnx("pd delete: drive required");
return (EINVAL);
}
fd = mpt_open(mpt_unit);
if (fd < 0) {
error = errno;
warn("mpt_open");
return (error);
}
list = mpt_pd_list(fd);
if (list == NULL)
return (errno);
if (mpt_lookup_drive(list, av[1], &PhysDiskNum) < 0) {
error = errno;
warn("Failed to find drive %s", av[1]);
return (error);
}
mpt_free_pd_list(list);
info = mpt_pd_info(fd, PhysDiskNum, NULL);
if (info == NULL) {
error = errno;
warn("Failed to fetch drive info");
return (error);
}
if (mpt_delete_physdisk(fd, PhysDiskNum) < 0) {
error = errno;
warn("Failed to delete physical disk page");
return (error);
}
mpt_rescan_bus(info->PhysDiskBus, info->PhysDiskID);
free(info);
close(fd);
return (0);
}
MPT_COMMAND(pd, delete, pd_delete);
/* Display raw data about a volume config. */
static void
dump_config(CONFIG_PAGE_RAID_VOL_0 *vol)
{
int i;
printf("Volume Configuration (Debug):\n");
printf(
" Page Header: Type 0x%02x Number 0x%02x Length 0x%02x(%u) Version 0x%02x\n",
vol->Header.PageType, vol->Header.PageNumber,
vol->Header.PageLength, vol->Header.PageLength * 4,
vol->Header.PageVersion);
printf(" Address: %d:%d IOC %d\n", vol->VolumeBus, vol->VolumeID,
vol->VolumeIOC);
printf(" Type: %d (%s)\n", vol->VolumeType,
mpt_raid_level(vol->VolumeType));
printf(" Status: %s (Flags 0x%02x)\n",
mpt_volstate(vol->VolumeStatus.State), vol->VolumeStatus.Flags);
printf(" Settings: 0x%04x (Spare Pools 0x%02x)\n",
vol->VolumeSettings.Settings, vol->VolumeSettings.HotSparePool);
printf(" MaxLBA: %ju\n", (uintmax_t)vol->MaxLBAHigh << 32 |
vol->MaxLBA);
printf(" Stripe Size: %ld\n", (long)vol->StripeSize * 512);
printf(" %d Disks:\n", vol->NumPhysDisks);
for (i = 0; i < vol->NumPhysDisks; i++)
printf(" Disk %d: Num 0x%02x Map 0x%02x\n", i,
vol->PhysDisk[i].PhysDiskNum, vol->PhysDisk[i].PhysDiskMap);
}
static int
debug_config(int ac, char **av)
{
CONFIG_PAGE_RAID_VOL_0 *vol;
U8 VolumeBus, VolumeID;
int error, fd;
if (ac != 2) {
warnx("debug: volume required");
return (EINVAL);
}
fd = mpt_open(mpt_unit);
if (fd < 0) {
error = errno;
warn("mpt_open");
return (error);
}
error = mpt_lookup_volume(fd, av[1], &VolumeBus, &VolumeID);
if (error) {
warnc(error, "Invalid volume: %s", av[1]);
return (error);
}
vol = mpt_vol_info(fd, VolumeBus, VolumeID, NULL);
if (vol == NULL) {
error = errno;
warn("Failed to get volume info");
return (error);
}
dump_config(vol);
free(vol);
close(fd);
return (0);
}
MPT_COMMAND(top, debug, debug_config);
#endif