1fc58801cSScott Long /*- 2fc58801cSScott Long * Copyright (c) 2008 Yahoo!, Inc. 3fc58801cSScott Long * All rights reserved. 4fc58801cSScott Long * Written by: John Baldwin <jhb@FreeBSD.org> 5fc58801cSScott Long * 6fc58801cSScott Long * Redistribution and use in source and binary forms, with or without 7fc58801cSScott Long * modification, are permitted provided that the following conditions 8fc58801cSScott Long * are met: 9fc58801cSScott Long * 1. Redistributions of source code must retain the above copyright 10fc58801cSScott Long * notice, this list of conditions and the following disclaimer. 11fc58801cSScott Long * 2. Redistributions in binary form must reproduce the above copyright 12fc58801cSScott Long * notice, this list of conditions and the following disclaimer in the 13fc58801cSScott Long * documentation and/or other materials provided with the distribution. 14fc58801cSScott Long * 3. Neither the name of the author nor the names of any co-contributors 15fc58801cSScott Long * may be used to endorse or promote products derived from this software 16fc58801cSScott Long * without specific prior written permission. 17fc58801cSScott Long * 18fc58801cSScott Long * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19fc58801cSScott Long * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20fc58801cSScott Long * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21fc58801cSScott Long * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22fc58801cSScott Long * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23fc58801cSScott Long * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24fc58801cSScott Long * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25fc58801cSScott Long * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26fc58801cSScott Long * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27fc58801cSScott Long * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28fc58801cSScott Long * SUCH DAMAGE. 29fc58801cSScott Long */ 30fc58801cSScott Long 31fc58801cSScott Long #include <sys/cdefs.h> 32fc58801cSScott Long __RCSID("$FreeBSD$"); 33fc58801cSScott Long 34fc58801cSScott Long #include <sys/param.h> 35fc58801cSScott Long #include <sys/errno.h> 36fc58801cSScott Long #include <ctype.h> 37fc58801cSScott Long #include <err.h> 38fc58801cSScott Long #include <libutil.h> 39fc58801cSScott Long #include <limits.h> 40fc58801cSScott Long #include <stdio.h> 41fc58801cSScott Long #include <stdlib.h> 42fc58801cSScott Long #include <string.h> 43fc58801cSScott Long #include <strings.h> 44fc58801cSScott Long #include <unistd.h> 45fc58801cSScott Long 46fc58801cSScott Long #include <camlib.h> 47fc58801cSScott Long #include <cam/scsi/scsi_all.h> 48fc58801cSScott Long 49fc58801cSScott Long #include "mptutil.h" 50fc58801cSScott Long 51fc58801cSScott Long const char * 52fc58801cSScott Long mpt_pdstate(CONFIG_PAGE_RAID_PHYS_DISK_0 *info) 53fc58801cSScott Long { 54fc58801cSScott Long static char buf[16]; 55fc58801cSScott Long 56fc58801cSScott Long switch (info->PhysDiskStatus.State) { 57fc58801cSScott Long case MPI_PHYSDISK0_STATUS_ONLINE: 58fc58801cSScott Long if ((info->PhysDiskStatus.Flags & 59fc58801cSScott Long MPI_PHYSDISK0_STATUS_FLAG_OUT_OF_SYNC) && 60fc58801cSScott Long info->PhysDiskSettings.HotSparePool == 0) 61fc58801cSScott Long return ("REBUILD"); 62fc58801cSScott Long else 63fc58801cSScott Long return ("ONLINE"); 64fc58801cSScott Long case MPI_PHYSDISK0_STATUS_MISSING: 65fc58801cSScott Long return ("MISSING"); 66fc58801cSScott Long case MPI_PHYSDISK0_STATUS_NOT_COMPATIBLE: 67fc58801cSScott Long return ("NOT COMPATIBLE"); 68fc58801cSScott Long case MPI_PHYSDISK0_STATUS_FAILED: 69fc58801cSScott Long return ("FAILED"); 70fc58801cSScott Long case MPI_PHYSDISK0_STATUS_INITIALIZING: 71fc58801cSScott Long return ("INITIALIZING"); 72fc58801cSScott Long case MPI_PHYSDISK0_STATUS_OFFLINE_REQUESTED: 73fc58801cSScott Long return ("OFFLINE REQUESTED"); 74fc58801cSScott Long case MPI_PHYSDISK0_STATUS_FAILED_REQUESTED: 75fc58801cSScott Long return ("FAILED REQUESTED"); 76fc58801cSScott Long case MPI_PHYSDISK0_STATUS_OTHER_OFFLINE: 77fc58801cSScott Long return ("OTHER OFFLINE"); 78fc58801cSScott Long default: 79fc58801cSScott Long sprintf(buf, "PSTATE 0x%02x", info->PhysDiskStatus.State); 80fc58801cSScott Long return (buf); 81fc58801cSScott Long } 82fc58801cSScott Long } 83fc58801cSScott Long 84fc58801cSScott Long /* 85fc58801cSScott Long * There are several ways to enumerate physical disks. Unfortunately, 86fc58801cSScott Long * none of them are truly complete, so we have to build a union of all of 87fc58801cSScott Long * them. Specifically: 88fc58801cSScott Long * 89fc58801cSScott Long * - IOC2 : This gives us a list of volumes, and by walking the volumes we 90fc58801cSScott Long * can enumerate all of the drives attached to volumes including 91fc58801cSScott Long * online drives and failed drives. 92fc58801cSScott Long * - IOC3 : This gives us a list of all online physical drives including 93fc58801cSScott Long * drives that are not part of a volume nor a spare drive. It 94fc58801cSScott Long * does not include any failed drives. 95fc58801cSScott Long * - IOC5 : This gives us a list of all spare drives including failed 96fc58801cSScott Long * spares. 97fc58801cSScott Long * 98fc58801cSScott Long * The specific edge cases are that 1) a failed volume member can only be 99fc58801cSScott Long * found via IOC2, 2) a drive that is neither a volume member nor a spare 100fc58801cSScott Long * can only be found via IOC3, and 3) a failed spare can only be found via 101fc58801cSScott Long * IOC5. 102fc58801cSScott Long * 103fc58801cSScott Long * To handle this, walk all of the three lists and use the following 104fc58801cSScott Long * routine to add each drive encountered. It quietly succeeds if the 105fc58801cSScott Long * drive is already present in the list. It also sorts the list as it 106fc58801cSScott Long * inserts new drives. 107fc58801cSScott Long */ 108fc58801cSScott Long static int 109fc58801cSScott Long mpt_pd_insert(int fd, struct mpt_drive_list *list, U8 PhysDiskNum) 110fc58801cSScott Long { 111fc58801cSScott Long int i, j; 112fc58801cSScott Long 113fc58801cSScott Long /* 114fc58801cSScott Long * First, do a simple linear search to see if we have already 115fc58801cSScott Long * seen this drive. 116fc58801cSScott Long */ 117fc58801cSScott Long for (i = 0; i < list->ndrives; i++) { 118fc58801cSScott Long if (list->drives[i]->PhysDiskNum == PhysDiskNum) 119fc58801cSScott Long return (0); 120fc58801cSScott Long if (list->drives[i]->PhysDiskNum > PhysDiskNum) 121fc58801cSScott Long break; 122fc58801cSScott Long } 123fc58801cSScott Long 124fc58801cSScott Long /* 125fc58801cSScott Long * 'i' is our slot for the 'new' drive. Make room and then 126fc58801cSScott Long * read the drive info. 127fc58801cSScott Long */ 128fc58801cSScott Long for (j = list->ndrives - 1; j >= i; j--) 129fc58801cSScott Long list->drives[j + 1] = list->drives[j]; 130fc58801cSScott Long list->drives[i] = mpt_pd_info(fd, PhysDiskNum, NULL); 131fc58801cSScott Long if (list->drives[i] == NULL) 132*c5f2b79dSJohn Baldwin return (errno); 133fc58801cSScott Long list->ndrives++; 134fc58801cSScott Long return (0); 135fc58801cSScott Long } 136fc58801cSScott Long 137fc58801cSScott Long struct mpt_drive_list * 138fc58801cSScott Long mpt_pd_list(int fd) 139fc58801cSScott Long { 140fc58801cSScott Long CONFIG_PAGE_IOC_2 *ioc2; 141fc58801cSScott Long CONFIG_PAGE_IOC_2_RAID_VOL *vol; 142fc58801cSScott Long CONFIG_PAGE_RAID_VOL_0 **volumes; 143fc58801cSScott Long RAID_VOL0_PHYS_DISK *rdisk; 144fc58801cSScott Long CONFIG_PAGE_IOC_3 *ioc3; 145fc58801cSScott Long IOC_3_PHYS_DISK *disk; 146fc58801cSScott Long CONFIG_PAGE_IOC_5 *ioc5; 147fc58801cSScott Long IOC_5_HOT_SPARE *spare; 148fc58801cSScott Long struct mpt_drive_list *list; 149*c5f2b79dSJohn Baldwin int count, error, i, j; 150fc58801cSScott Long 151fc58801cSScott Long ioc2 = mpt_read_ioc_page(fd, 2, NULL); 152fc58801cSScott Long if (ioc2 == NULL) { 153*c5f2b79dSJohn Baldwin error = errno; 154fc58801cSScott Long warn("Failed to fetch volume list"); 155*c5f2b79dSJohn Baldwin errno = error; 156fc58801cSScott Long return (NULL); 157fc58801cSScott Long } 158fc58801cSScott Long 159fc58801cSScott Long ioc3 = mpt_read_ioc_page(fd, 3, NULL); 160fc58801cSScott Long if (ioc3 == NULL) { 161*c5f2b79dSJohn Baldwin error = errno; 162fc58801cSScott Long warn("Failed to fetch drive list"); 163fc58801cSScott Long free(ioc2); 164*c5f2b79dSJohn Baldwin errno = error; 165fc58801cSScott Long return (NULL); 166fc58801cSScott Long } 167fc58801cSScott Long 168fc58801cSScott Long ioc5 = mpt_read_ioc_page(fd, 5, NULL); 169fc58801cSScott Long if (ioc5 == NULL) { 170*c5f2b79dSJohn Baldwin error = errno; 171fc58801cSScott Long warn("Failed to fetch spare list"); 172fc58801cSScott Long free(ioc3); 173fc58801cSScott Long free(ioc2); 174*c5f2b79dSJohn Baldwin errno = error; 175fc58801cSScott Long return (NULL); 176fc58801cSScott Long } 177fc58801cSScott Long 178fc58801cSScott Long /* 179fc58801cSScott Long * Go ahead and read the info for all the volumes. For this 180fc58801cSScott Long * pass we figure out how many physical drives there are. 181fc58801cSScott Long */ 182fc58801cSScott Long volumes = malloc(sizeof(*volumes) * ioc2->NumActiveVolumes); 183fc58801cSScott Long count = 0; 184fc58801cSScott Long vol = ioc2->RaidVolume; 185fc58801cSScott Long for (i = 0; i < ioc2->NumActiveVolumes; vol++, i++) { 186fc58801cSScott Long volumes[i] = mpt_vol_info(fd, vol->VolumeBus, vol->VolumeID, 187fc58801cSScott Long NULL); 188fc58801cSScott Long if (volumes[i] == NULL) { 189*c5f2b79dSJohn Baldwin error = errno; 190fc58801cSScott Long warn("Failed to read volume info"); 191*c5f2b79dSJohn Baldwin errno = error; 192fc58801cSScott Long return (NULL); 193fc58801cSScott Long } 194fc58801cSScott Long count += volumes[i]->NumPhysDisks; 195fc58801cSScott Long } 196fc58801cSScott Long count += ioc3->NumPhysDisks; 197fc58801cSScott Long count += ioc5->NumHotSpares; 198fc58801cSScott Long 199fc58801cSScott Long /* Walk the various lists enumerating drives. */ 200fc58801cSScott Long list = malloc(sizeof(*list) + sizeof(CONFIG_PAGE_RAID_PHYS_DISK_0) * 201fc58801cSScott Long count); 202fc58801cSScott Long list->ndrives = 0; 203fc58801cSScott Long 204fc58801cSScott Long for (i = 0; i < ioc2->NumActiveVolumes; i++) { 205fc58801cSScott Long rdisk = volumes[i]->PhysDisk; 206fc58801cSScott Long for (j = 0; j < volumes[i]->NumPhysDisks; rdisk++, j++) 207fc58801cSScott Long if (mpt_pd_insert(fd, list, rdisk->PhysDiskNum) < 0) 208fc58801cSScott Long return (NULL); 209fc58801cSScott Long free(volumes[i]); 210fc58801cSScott Long } 211fc58801cSScott Long free(ioc2); 212fc58801cSScott Long free(volumes); 213fc58801cSScott Long 214fc58801cSScott Long spare = ioc5->HotSpare; 215fc58801cSScott Long for (i = 0; i < ioc5->NumHotSpares; spare++, i++) 216fc58801cSScott Long if (mpt_pd_insert(fd, list, spare->PhysDiskNum) < 0) 217fc58801cSScott Long return (NULL); 218fc58801cSScott Long free(ioc5); 219fc58801cSScott Long 220fc58801cSScott Long disk = ioc3->PhysDisk; 221fc58801cSScott Long for (i = 0; i < ioc3->NumPhysDisks; disk++, i++) 222fc58801cSScott Long if (mpt_pd_insert(fd, list, disk->PhysDiskNum) < 0) 223fc58801cSScott Long return (NULL); 224fc58801cSScott Long free(ioc3); 225fc58801cSScott Long 226fc58801cSScott Long return (list); 227fc58801cSScott Long } 228fc58801cSScott Long 229fc58801cSScott Long void 230fc58801cSScott Long mpt_free_pd_list(struct mpt_drive_list *list) 231fc58801cSScott Long { 232fc58801cSScott Long int i; 233fc58801cSScott Long 234fc58801cSScott Long for (i = 0; i < list->ndrives; i++) 235fc58801cSScott Long free(list->drives[i]); 236fc58801cSScott Long free(list); 237fc58801cSScott Long } 238fc58801cSScott Long 239fc58801cSScott Long int 240fc58801cSScott Long mpt_lookup_drive(struct mpt_drive_list *list, const char *drive, 241fc58801cSScott Long U8 *PhysDiskNum) 242fc58801cSScott Long { 243fc58801cSScott Long long val; 244fc58801cSScott Long uint8_t bus, id; 245fc58801cSScott Long char *cp; 246fc58801cSScott Long 247fc58801cSScott Long /* Look for a raw device id first. */ 248fc58801cSScott Long val = strtol(drive, &cp, 0); 249fc58801cSScott Long if (*cp == '\0') { 250fc58801cSScott Long if (val < 0 || val > 0xff) 251fc58801cSScott Long goto bad; 252fc58801cSScott Long *PhysDiskNum = val; 253fc58801cSScott Long return (0); 254fc58801cSScott Long } 255fc58801cSScott Long 256fc58801cSScott Long /* Look for a <bus>:<id> string. */ 257fc58801cSScott Long if (*cp == ':') { 258fc58801cSScott Long if (val < 0 || val > 0xff) 259fc58801cSScott Long goto bad; 260fc58801cSScott Long bus = val; 261fc58801cSScott Long val = strtol(cp + 1, &cp, 0); 262fc58801cSScott Long if (*cp != '\0') 263fc58801cSScott Long goto bad; 264fc58801cSScott Long if (val < 0 || val > 0xff) 265fc58801cSScott Long goto bad; 266fc58801cSScott Long id = val; 267fc58801cSScott Long 268fc58801cSScott Long for (val = 0; val < list->ndrives; val++) { 269fc58801cSScott Long if (list->drives[val]->PhysDiskBus == bus && 270fc58801cSScott Long list->drives[val]->PhysDiskID == id) { 271fc58801cSScott Long *PhysDiskNum = list->drives[val]->PhysDiskNum; 272fc58801cSScott Long return (0); 273fc58801cSScott Long } 274fc58801cSScott Long } 275*c5f2b79dSJohn Baldwin return (ENOENT); 276fc58801cSScott Long } 277fc58801cSScott Long 278fc58801cSScott Long bad: 279*c5f2b79dSJohn Baldwin return (EINVAL); 280fc58801cSScott Long } 281fc58801cSScott Long 282fc58801cSScott Long /* Borrowed heavily from scsi_all.c:scsi_print_inquiry(). */ 283fc58801cSScott Long const char * 284fc58801cSScott Long mpt_pd_inq_string(CONFIG_PAGE_RAID_PHYS_DISK_0 *pd_info) 285fc58801cSScott Long { 286fc58801cSScott Long RAID_PHYS_DISK0_INQUIRY_DATA *inq_data; 287fc58801cSScott Long u_char vendor[9], product[17], revision[5]; 288fc58801cSScott Long static char inq_string[64]; 289fc58801cSScott Long 290fc58801cSScott Long inq_data = &pd_info->InquiryData; 291fc58801cSScott Long cam_strvis(vendor, inq_data->VendorID, sizeof(inq_data->VendorID), 292fc58801cSScott Long sizeof(vendor)); 293fc58801cSScott Long cam_strvis(product, inq_data->ProductID, sizeof(inq_data->ProductID), 294fc58801cSScott Long sizeof(product)); 295fc58801cSScott Long cam_strvis(revision, inq_data->ProductRevLevel, 296fc58801cSScott Long sizeof(inq_data->ProductRevLevel), sizeof(revision)); 297fc58801cSScott Long 298fc58801cSScott Long /* Total hack. */ 299fc58801cSScott Long if (strcmp(vendor, "ATA") == 0) 300fc58801cSScott Long snprintf(inq_string, sizeof(inq_string), "<%s %s> SATA", 301fc58801cSScott Long product, revision); 302fc58801cSScott Long else 303fc58801cSScott Long snprintf(inq_string, sizeof(inq_string), "<%s %s %s> SAS", 304fc58801cSScott Long vendor, product, revision); 305fc58801cSScott Long return (inq_string); 306fc58801cSScott Long } 307fc58801cSScott Long 308fc58801cSScott Long /* Helper function to set a drive to a given state. */ 309fc58801cSScott Long static int 310fc58801cSScott Long drive_set_state(char *drive, U8 Action, U8 State, const char *name) 311fc58801cSScott Long { 312fc58801cSScott Long CONFIG_PAGE_RAID_PHYS_DISK_0 *info; 313fc58801cSScott Long struct mpt_drive_list *list; 314fc58801cSScott Long U8 PhysDiskNum; 315*c5f2b79dSJohn Baldwin int error, fd; 316fc58801cSScott Long 317fc58801cSScott Long fd = mpt_open(mpt_unit); 318fc58801cSScott Long if (fd < 0) { 319*c5f2b79dSJohn Baldwin error = errno; 320fc58801cSScott Long warn("mpt_open"); 321*c5f2b79dSJohn Baldwin return (error); 322fc58801cSScott Long } 323fc58801cSScott Long 324fc58801cSScott Long list = mpt_pd_list(fd); 325fc58801cSScott Long if (list == NULL) 326fc58801cSScott Long return (errno); 327fc58801cSScott Long 328fc58801cSScott Long if (mpt_lookup_drive(list, drive, &PhysDiskNum) < 0) { 329*c5f2b79dSJohn Baldwin error = errno; 330fc58801cSScott Long warn("Failed to find drive %s", drive); 331*c5f2b79dSJohn Baldwin return (error); 332fc58801cSScott Long } 333fc58801cSScott Long mpt_free_pd_list(list); 334fc58801cSScott Long 335fc58801cSScott Long /* Get the info for this drive. */ 336fc58801cSScott Long info = mpt_pd_info(fd, PhysDiskNum, NULL); 337fc58801cSScott Long if (info == NULL) { 338*c5f2b79dSJohn Baldwin error = errno; 339fc58801cSScott Long warn("Failed to fetch info for drive %u", PhysDiskNum); 340*c5f2b79dSJohn Baldwin return (error); 341fc58801cSScott Long } 342fc58801cSScott Long 343fc58801cSScott Long /* Try to change the state. */ 344fc58801cSScott Long if (info->PhysDiskStatus.State == State) { 345fc58801cSScott Long warnx("Drive %u is already in the desired state", PhysDiskNum); 346fc58801cSScott Long return (EINVAL); 347fc58801cSScott Long } 348fc58801cSScott Long 349*c5f2b79dSJohn Baldwin error = mpt_raid_action(fd, Action, 0, 0, PhysDiskNum, 0, NULL, 0, NULL, 350*c5f2b79dSJohn Baldwin NULL, 0, NULL, NULL, 0); 351*c5f2b79dSJohn Baldwin if (error) { 352*c5f2b79dSJohn Baldwin warnc(error, "Failed to set drive %u to %s", PhysDiskNum, name); 353*c5f2b79dSJohn Baldwin return (error); 354fc58801cSScott Long } 355fc58801cSScott Long 356fc58801cSScott Long free(info); 357fc58801cSScott Long close(fd); 358fc58801cSScott Long 359fc58801cSScott Long return (0); 360fc58801cSScott Long } 361fc58801cSScott Long 362fc58801cSScott Long static int 363fc58801cSScott Long fail_drive(int ac, char **av) 364fc58801cSScott Long { 365fc58801cSScott Long 366fc58801cSScott Long if (ac != 2) { 367fc58801cSScott Long warnx("fail: %s", ac > 2 ? "extra arguments" : 368fc58801cSScott Long "drive required"); 369fc58801cSScott Long return (EINVAL); 370fc58801cSScott Long } 371fc58801cSScott Long 372fc58801cSScott Long return (drive_set_state(av[1], MPI_RAID_ACTION_FAIL_PHYSDISK, 373fc58801cSScott Long MPI_PHYSDISK0_STATUS_FAILED_REQUESTED, "FAILED")); 374fc58801cSScott Long } 375fc58801cSScott Long MPT_COMMAND(top, fail, fail_drive); 376fc58801cSScott Long 377fc58801cSScott Long static int 378fc58801cSScott Long online_drive(int ac, char **av) 379fc58801cSScott Long { 380fc58801cSScott Long 381fc58801cSScott Long if (ac != 2) { 382fc58801cSScott Long warnx("online: %s", ac > 2 ? "extra arguments" : 383fc58801cSScott Long "drive required"); 384fc58801cSScott Long return (EINVAL); 385fc58801cSScott Long } 386fc58801cSScott Long 387fc58801cSScott Long return (drive_set_state(av[1], MPI_RAID_ACTION_PHYSDISK_ONLINE, 388fc58801cSScott Long MPI_PHYSDISK0_STATUS_ONLINE, "ONLINE")); 389fc58801cSScott Long } 390fc58801cSScott Long MPT_COMMAND(top, online, online_drive); 391fc58801cSScott Long 392fc58801cSScott Long static int 393fc58801cSScott Long offline_drive(int ac, char **av) 394fc58801cSScott Long { 395fc58801cSScott Long 396fc58801cSScott Long if (ac != 2) { 397fc58801cSScott Long warnx("offline: %s", ac > 2 ? "extra arguments" : 398fc58801cSScott Long "drive required"); 399fc58801cSScott Long return (EINVAL); 400fc58801cSScott Long } 401fc58801cSScott Long 402fc58801cSScott Long return (drive_set_state(av[1], MPI_RAID_ACTION_PHYSDISK_OFFLINE, 403fc58801cSScott Long MPI_PHYSDISK0_STATUS_OFFLINE_REQUESTED, "OFFLINE")); 404fc58801cSScott Long } 405fc58801cSScott Long MPT_COMMAND(top, offline, offline_drive); 406