1196212Sscottl/*- 2196212Sscottl * Copyright (c) 2008 Yahoo!, Inc. 3196212Sscottl * All rights reserved. 4196212Sscottl * Written by: John Baldwin <jhb@FreeBSD.org> 5196212Sscottl * 6196212Sscottl * Redistribution and use in source and binary forms, with or without 7196212Sscottl * modification, are permitted provided that the following conditions 8196212Sscottl * are met: 9196212Sscottl * 1. Redistributions of source code must retain the above copyright 10196212Sscottl * notice, this list of conditions and the following disclaimer. 11196212Sscottl * 2. Redistributions in binary form must reproduce the above copyright 12196212Sscottl * notice, this list of conditions and the following disclaimer in the 13196212Sscottl * documentation and/or other materials provided with the distribution. 14196212Sscottl * 3. Neither the name of the author nor the names of any co-contributors 15196212Sscottl * may be used to endorse or promote products derived from this software 16196212Sscottl * without specific prior written permission. 17196212Sscottl * 18196212Sscottl * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19196212Sscottl * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20196212Sscottl * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21196212Sscottl * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22196212Sscottl * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23196212Sscottl * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24196212Sscottl * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25196212Sscottl * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26196212Sscottl * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27196212Sscottl * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28196212Sscottl * SUCH DAMAGE. 29196212Sscottl */ 30196212Sscottl 31196212Sscottl#include <sys/cdefs.h> 32196212Sscottl__RCSID("$FreeBSD$"); 33196212Sscottl 34196212Sscottl#include <sys/param.h> 35196212Sscottl#include <sys/errno.h> 36196212Sscottl#include <ctype.h> 37196212Sscottl#include <err.h> 38196212Sscottl#include <libutil.h> 39196212Sscottl#include <limits.h> 40196212Sscottl#include <stdio.h> 41196212Sscottl#include <stdlib.h> 42196212Sscottl#include <string.h> 43196212Sscottl#include <strings.h> 44196212Sscottl#include <unistd.h> 45196212Sscottl 46196212Sscottl#include <camlib.h> 47196212Sscottl#include <cam/scsi/scsi_all.h> 48196212Sscottl 49196212Sscottl#include "mptutil.h" 50196212Sscottl 51196212Sscottlconst char * 52196212Sscottlmpt_pdstate(CONFIG_PAGE_RAID_PHYS_DISK_0 *info) 53196212Sscottl{ 54196212Sscottl static char buf[16]; 55196212Sscottl 56196212Sscottl switch (info->PhysDiskStatus.State) { 57196212Sscottl case MPI_PHYSDISK0_STATUS_ONLINE: 58196212Sscottl if ((info->PhysDiskStatus.Flags & 59196212Sscottl MPI_PHYSDISK0_STATUS_FLAG_OUT_OF_SYNC) && 60196212Sscottl info->PhysDiskSettings.HotSparePool == 0) 61196212Sscottl return ("REBUILD"); 62196212Sscottl else 63196212Sscottl return ("ONLINE"); 64196212Sscottl case MPI_PHYSDISK0_STATUS_MISSING: 65196212Sscottl return ("MISSING"); 66196212Sscottl case MPI_PHYSDISK0_STATUS_NOT_COMPATIBLE: 67196212Sscottl return ("NOT COMPATIBLE"); 68196212Sscottl case MPI_PHYSDISK0_STATUS_FAILED: 69196212Sscottl return ("FAILED"); 70196212Sscottl case MPI_PHYSDISK0_STATUS_INITIALIZING: 71196212Sscottl return ("INITIALIZING"); 72196212Sscottl case MPI_PHYSDISK0_STATUS_OFFLINE_REQUESTED: 73196212Sscottl return ("OFFLINE REQUESTED"); 74196212Sscottl case MPI_PHYSDISK0_STATUS_FAILED_REQUESTED: 75196212Sscottl return ("FAILED REQUESTED"); 76196212Sscottl case MPI_PHYSDISK0_STATUS_OTHER_OFFLINE: 77196212Sscottl return ("OTHER OFFLINE"); 78196212Sscottl default: 79196212Sscottl sprintf(buf, "PSTATE 0x%02x", info->PhysDiskStatus.State); 80196212Sscottl return (buf); 81196212Sscottl } 82196212Sscottl} 83196212Sscottl 84196212Sscottl/* 85196212Sscottl * There are several ways to enumerate physical disks. Unfortunately, 86196212Sscottl * none of them are truly complete, so we have to build a union of all of 87196212Sscottl * them. Specifically: 88196212Sscottl * 89196212Sscottl * - IOC2 : This gives us a list of volumes, and by walking the volumes we 90196212Sscottl * can enumerate all of the drives attached to volumes including 91196212Sscottl * online drives and failed drives. 92196212Sscottl * - IOC3 : This gives us a list of all online physical drives including 93196212Sscottl * drives that are not part of a volume nor a spare drive. It 94196212Sscottl * does not include any failed drives. 95196212Sscottl * - IOC5 : This gives us a list of all spare drives including failed 96196212Sscottl * spares. 97196212Sscottl * 98196212Sscottl * The specific edge cases are that 1) a failed volume member can only be 99196212Sscottl * found via IOC2, 2) a drive that is neither a volume member nor a spare 100196212Sscottl * can only be found via IOC3, and 3) a failed spare can only be found via 101196212Sscottl * IOC5. 102196212Sscottl * 103196212Sscottl * To handle this, walk all of the three lists and use the following 104196212Sscottl * routine to add each drive encountered. It quietly succeeds if the 105196212Sscottl * drive is already present in the list. It also sorts the list as it 106196212Sscottl * inserts new drives. 107196212Sscottl */ 108196212Sscottlstatic int 109196212Sscottlmpt_pd_insert(int fd, struct mpt_drive_list *list, U8 PhysDiskNum) 110196212Sscottl{ 111196212Sscottl int i, j; 112196212Sscottl 113196212Sscottl /* 114196212Sscottl * First, do a simple linear search to see if we have already 115196212Sscottl * seen this drive. 116196212Sscottl */ 117196212Sscottl for (i = 0; i < list->ndrives; i++) { 118196212Sscottl if (list->drives[i]->PhysDiskNum == PhysDiskNum) 119196212Sscottl return (0); 120196212Sscottl if (list->drives[i]->PhysDiskNum > PhysDiskNum) 121196212Sscottl break; 122196212Sscottl } 123196212Sscottl 124196212Sscottl /* 125196212Sscottl * 'i' is our slot for the 'new' drive. Make room and then 126196212Sscottl * read the drive info. 127196212Sscottl */ 128196212Sscottl for (j = list->ndrives - 1; j >= i; j--) 129196212Sscottl list->drives[j + 1] = list->drives[j]; 130196212Sscottl list->drives[i] = mpt_pd_info(fd, PhysDiskNum, NULL); 131196212Sscottl if (list->drives[i] == NULL) 132215046Sjhb return (errno); 133196212Sscottl list->ndrives++; 134196212Sscottl return (0); 135196212Sscottl} 136196212Sscottl 137196212Sscottlstruct mpt_drive_list * 138196212Sscottlmpt_pd_list(int fd) 139196212Sscottl{ 140196212Sscottl CONFIG_PAGE_IOC_2 *ioc2; 141196212Sscottl CONFIG_PAGE_IOC_2_RAID_VOL *vol; 142196212Sscottl CONFIG_PAGE_RAID_VOL_0 **volumes; 143196212Sscottl RAID_VOL0_PHYS_DISK *rdisk; 144196212Sscottl CONFIG_PAGE_IOC_3 *ioc3; 145196212Sscottl IOC_3_PHYS_DISK *disk; 146196212Sscottl CONFIG_PAGE_IOC_5 *ioc5; 147196212Sscottl IOC_5_HOT_SPARE *spare; 148196212Sscottl struct mpt_drive_list *list; 149215046Sjhb int count, error, i, j; 150196212Sscottl 151196212Sscottl ioc2 = mpt_read_ioc_page(fd, 2, NULL); 152196212Sscottl if (ioc2 == NULL) { 153215046Sjhb error = errno; 154196212Sscottl warn("Failed to fetch volume list"); 155215046Sjhb errno = error; 156196212Sscottl return (NULL); 157196212Sscottl } 158196212Sscottl 159196212Sscottl ioc3 = mpt_read_ioc_page(fd, 3, NULL); 160196212Sscottl if (ioc3 == NULL) { 161215046Sjhb error = errno; 162196212Sscottl warn("Failed to fetch drive list"); 163196212Sscottl free(ioc2); 164215046Sjhb errno = error; 165196212Sscottl return (NULL); 166196212Sscottl } 167196212Sscottl 168196212Sscottl ioc5 = mpt_read_ioc_page(fd, 5, NULL); 169196212Sscottl if (ioc5 == NULL) { 170215046Sjhb error = errno; 171196212Sscottl warn("Failed to fetch spare list"); 172196212Sscottl free(ioc3); 173196212Sscottl free(ioc2); 174215046Sjhb errno = error; 175196212Sscottl return (NULL); 176196212Sscottl } 177196212Sscottl 178196212Sscottl /* 179196212Sscottl * Go ahead and read the info for all the volumes. For this 180196212Sscottl * pass we figure out how many physical drives there are. 181196212Sscottl */ 182196212Sscottl volumes = malloc(sizeof(*volumes) * ioc2->NumActiveVolumes); 183196212Sscottl count = 0; 184196212Sscottl vol = ioc2->RaidVolume; 185196212Sscottl for (i = 0; i < ioc2->NumActiveVolumes; vol++, i++) { 186196212Sscottl volumes[i] = mpt_vol_info(fd, vol->VolumeBus, vol->VolumeID, 187196212Sscottl NULL); 188196212Sscottl if (volumes[i] == NULL) { 189215046Sjhb error = errno; 190196212Sscottl warn("Failed to read volume info"); 191215046Sjhb errno = error; 192196212Sscottl return (NULL); 193196212Sscottl } 194196212Sscottl count += volumes[i]->NumPhysDisks; 195196212Sscottl } 196196212Sscottl count += ioc3->NumPhysDisks; 197196212Sscottl count += ioc5->NumHotSpares; 198196212Sscottl 199196212Sscottl /* Walk the various lists enumerating drives. */ 200196212Sscottl list = malloc(sizeof(*list) + sizeof(CONFIG_PAGE_RAID_PHYS_DISK_0) * 201196212Sscottl count); 202196212Sscottl list->ndrives = 0; 203196212Sscottl 204196212Sscottl for (i = 0; i < ioc2->NumActiveVolumes; i++) { 205196212Sscottl rdisk = volumes[i]->PhysDisk; 206196212Sscottl for (j = 0; j < volumes[i]->NumPhysDisks; rdisk++, j++) 207196212Sscottl if (mpt_pd_insert(fd, list, rdisk->PhysDiskNum) < 0) 208196212Sscottl return (NULL); 209196212Sscottl free(volumes[i]); 210196212Sscottl } 211196212Sscottl free(ioc2); 212196212Sscottl free(volumes); 213196212Sscottl 214196212Sscottl spare = ioc5->HotSpare; 215196212Sscottl for (i = 0; i < ioc5->NumHotSpares; spare++, i++) 216196212Sscottl if (mpt_pd_insert(fd, list, spare->PhysDiskNum) < 0) 217196212Sscottl return (NULL); 218196212Sscottl free(ioc5); 219196212Sscottl 220196212Sscottl disk = ioc3->PhysDisk; 221196212Sscottl for (i = 0; i < ioc3->NumPhysDisks; disk++, i++) 222196212Sscottl if (mpt_pd_insert(fd, list, disk->PhysDiskNum) < 0) 223196212Sscottl return (NULL); 224196212Sscottl free(ioc3); 225196212Sscottl 226196212Sscottl return (list); 227196212Sscottl} 228196212Sscottl 229196212Sscottlvoid 230196212Sscottlmpt_free_pd_list(struct mpt_drive_list *list) 231196212Sscottl{ 232196212Sscottl int i; 233196212Sscottl 234196212Sscottl for (i = 0; i < list->ndrives; i++) 235196212Sscottl free(list->drives[i]); 236196212Sscottl free(list); 237196212Sscottl} 238196212Sscottl 239196212Sscottlint 240196212Sscottlmpt_lookup_drive(struct mpt_drive_list *list, const char *drive, 241196212Sscottl U8 *PhysDiskNum) 242196212Sscottl{ 243196212Sscottl long val; 244196212Sscottl uint8_t bus, id; 245196212Sscottl char *cp; 246196212Sscottl 247196212Sscottl /* Look for a raw device id first. */ 248196212Sscottl val = strtol(drive, &cp, 0); 249196212Sscottl if (*cp == '\0') { 250196212Sscottl if (val < 0 || val > 0xff) 251196212Sscottl goto bad; 252196212Sscottl *PhysDiskNum = val; 253196212Sscottl return (0); 254196212Sscottl } 255196212Sscottl 256196212Sscottl /* Look for a <bus>:<id> string. */ 257196212Sscottl if (*cp == ':') { 258196212Sscottl if (val < 0 || val > 0xff) 259196212Sscottl goto bad; 260196212Sscottl bus = val; 261196212Sscottl val = strtol(cp + 1, &cp, 0); 262196212Sscottl if (*cp != '\0') 263196212Sscottl goto bad; 264196212Sscottl if (val < 0 || val > 0xff) 265196212Sscottl goto bad; 266196212Sscottl id = val; 267196212Sscottl 268196212Sscottl for (val = 0; val < list->ndrives; val++) { 269196212Sscottl if (list->drives[val]->PhysDiskBus == bus && 270196212Sscottl list->drives[val]->PhysDiskID == id) { 271196212Sscottl *PhysDiskNum = list->drives[val]->PhysDiskNum; 272196212Sscottl return (0); 273196212Sscottl } 274196212Sscottl } 275215046Sjhb return (ENOENT); 276196212Sscottl } 277196212Sscottl 278196212Sscottlbad: 279215046Sjhb return (EINVAL); 280196212Sscottl} 281196212Sscottl 282196212Sscottl/* Borrowed heavily from scsi_all.c:scsi_print_inquiry(). */ 283196212Sscottlconst char * 284196212Sscottlmpt_pd_inq_string(CONFIG_PAGE_RAID_PHYS_DISK_0 *pd_info) 285196212Sscottl{ 286196212Sscottl RAID_PHYS_DISK0_INQUIRY_DATA *inq_data; 287196212Sscottl u_char vendor[9], product[17], revision[5]; 288196212Sscottl static char inq_string[64]; 289196212Sscottl 290196212Sscottl inq_data = &pd_info->InquiryData; 291196212Sscottl cam_strvis(vendor, inq_data->VendorID, sizeof(inq_data->VendorID), 292196212Sscottl sizeof(vendor)); 293196212Sscottl cam_strvis(product, inq_data->ProductID, sizeof(inq_data->ProductID), 294196212Sscottl sizeof(product)); 295196212Sscottl cam_strvis(revision, inq_data->ProductRevLevel, 296196212Sscottl sizeof(inq_data->ProductRevLevel), sizeof(revision)); 297196212Sscottl 298196212Sscottl /* Total hack. */ 299196212Sscottl if (strcmp(vendor, "ATA") == 0) 300196212Sscottl snprintf(inq_string, sizeof(inq_string), "<%s %s> SATA", 301196212Sscottl product, revision); 302196212Sscottl else 303196212Sscottl snprintf(inq_string, sizeof(inq_string), "<%s %s %s> SAS", 304196212Sscottl vendor, product, revision); 305196212Sscottl return (inq_string); 306196212Sscottl} 307196212Sscottl 308196212Sscottl/* Helper function to set a drive to a given state. */ 309196212Sscottlstatic int 310196212Sscottldrive_set_state(char *drive, U8 Action, U8 State, const char *name) 311196212Sscottl{ 312196212Sscottl CONFIG_PAGE_RAID_PHYS_DISK_0 *info; 313196212Sscottl struct mpt_drive_list *list; 314196212Sscottl U8 PhysDiskNum; 315215046Sjhb int error, fd; 316196212Sscottl 317196212Sscottl fd = mpt_open(mpt_unit); 318196212Sscottl if (fd < 0) { 319215046Sjhb error = errno; 320196212Sscottl warn("mpt_open"); 321215046Sjhb return (error); 322196212Sscottl } 323196212Sscottl 324196212Sscottl list = mpt_pd_list(fd); 325196212Sscottl if (list == NULL) 326196212Sscottl return (errno); 327196212Sscottl 328196212Sscottl if (mpt_lookup_drive(list, drive, &PhysDiskNum) < 0) { 329215046Sjhb error = errno; 330196212Sscottl warn("Failed to find drive %s", drive); 331215046Sjhb return (error); 332196212Sscottl } 333196212Sscottl mpt_free_pd_list(list); 334196212Sscottl 335196212Sscottl /* Get the info for this drive. */ 336196212Sscottl info = mpt_pd_info(fd, PhysDiskNum, NULL); 337196212Sscottl if (info == NULL) { 338215046Sjhb error = errno; 339196212Sscottl warn("Failed to fetch info for drive %u", PhysDiskNum); 340215046Sjhb return (error); 341196212Sscottl } 342196212Sscottl 343196212Sscottl /* Try to change the state. */ 344196212Sscottl if (info->PhysDiskStatus.State == State) { 345196212Sscottl warnx("Drive %u is already in the desired state", PhysDiskNum); 346196212Sscottl return (EINVAL); 347196212Sscottl } 348196212Sscottl 349215046Sjhb error = mpt_raid_action(fd, Action, 0, 0, PhysDiskNum, 0, NULL, 0, NULL, 350215046Sjhb NULL, 0, NULL, NULL, 0); 351215046Sjhb if (error) { 352215046Sjhb warnc(error, "Failed to set drive %u to %s", PhysDiskNum, name); 353215046Sjhb return (error); 354196212Sscottl } 355196212Sscottl 356196212Sscottl free(info); 357196212Sscottl close(fd); 358196212Sscottl 359196212Sscottl return (0); 360196212Sscottl} 361196212Sscottl 362196212Sscottlstatic int 363196212Sscottlfail_drive(int ac, char **av) 364196212Sscottl{ 365196212Sscottl 366196212Sscottl if (ac != 2) { 367196212Sscottl warnx("fail: %s", ac > 2 ? "extra arguments" : 368196212Sscottl "drive required"); 369196212Sscottl return (EINVAL); 370196212Sscottl } 371196212Sscottl 372196212Sscottl return (drive_set_state(av[1], MPI_RAID_ACTION_FAIL_PHYSDISK, 373196212Sscottl MPI_PHYSDISK0_STATUS_FAILED_REQUESTED, "FAILED")); 374196212Sscottl} 375196212SscottlMPT_COMMAND(top, fail, fail_drive); 376196212Sscottl 377196212Sscottlstatic int 378196212Sscottlonline_drive(int ac, char **av) 379196212Sscottl{ 380196212Sscottl 381196212Sscottl if (ac != 2) { 382196212Sscottl warnx("online: %s", ac > 2 ? "extra arguments" : 383196212Sscottl "drive required"); 384196212Sscottl return (EINVAL); 385196212Sscottl } 386196212Sscottl 387196212Sscottl return (drive_set_state(av[1], MPI_RAID_ACTION_PHYSDISK_ONLINE, 388196212Sscottl MPI_PHYSDISK0_STATUS_ONLINE, "ONLINE")); 389196212Sscottl} 390196212SscottlMPT_COMMAND(top, online, online_drive); 391196212Sscottl 392196212Sscottlstatic int 393196212Sscottloffline_drive(int ac, char **av) 394196212Sscottl{ 395196212Sscottl 396196212Sscottl if (ac != 2) { 397196212Sscottl warnx("offline: %s", ac > 2 ? "extra arguments" : 398196212Sscottl "drive required"); 399196212Sscottl return (EINVAL); 400196212Sscottl } 401196212Sscottl 402196212Sscottl return (drive_set_state(av[1], MPI_RAID_ACTION_PHYSDISK_OFFLINE, 403196212Sscottl MPI_PHYSDISK0_STATUS_OFFLINE_REQUESTED, "OFFLINE")); 404196212Sscottl} 405196212SscottlMPT_COMMAND(top, offline, offline_drive); 406