mpt_drive.c revision 215046
11590Srgrimes/*-
21590Srgrimes * Copyright (c) 2008 Yahoo!, Inc.
31590Srgrimes * All rights reserved.
41590Srgrimes * Written by: John Baldwin <jhb@FreeBSD.org>
51590Srgrimes *
61590Srgrimes * Redistribution and use in source and binary forms, with or without
71590Srgrimes * modification, are permitted provided that the following conditions
81590Srgrimes * are met:
91590Srgrimes * 1. Redistributions of source code must retain the above copyright
101590Srgrimes *    notice, this list of conditions and the following disclaimer.
111590Srgrimes * 2. Redistributions in binary form must reproduce the above copyright
121590Srgrimes *    notice, this list of conditions and the following disclaimer in the
131590Srgrimes *    documentation and/or other materials provided with the distribution.
141590Srgrimes * 3. Neither the name of the author nor the names of any co-contributors
151590Srgrimes *    may be used to endorse or promote products derived from this software
161590Srgrimes *    without specific prior written permission.
171590Srgrimes *
181590Srgrimes * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
191590Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
201590Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
211590Srgrimes * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
221590Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
231590Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
241590Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
251590Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
261590Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
271590Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
281590Srgrimes * SUCH DAMAGE.
291590Srgrimes */
301590Srgrimes
3127443Scharnier#include <sys/cdefs.h>
321590Srgrimes__RCSID("$FreeBSD: head/usr.sbin/mptutil/mpt_drive.c 215046 2010-11-09 19:28:06Z jhb $");
331590Srgrimes
341590Srgrimes#include <sys/param.h>
351590Srgrimes#include <sys/errno.h>
361590Srgrimes#include <ctype.h>
3727443Scharnier#include <err.h>
381590Srgrimes#include <libutil.h>
3927443Scharnier#include <limits.h>
401590Srgrimes#include <stdio.h>
4199112Sobrien#include <stdlib.h>
4299112Sobrien#include <string.h>
431590Srgrimes#include <strings.h>
4455206Speter#include <unistd.h>
452215Scsgr
462215Scsgr#include <camlib.h>
4755206Speter#include <cam/scsi/scsi_all.h>
481590Srgrimes
491590Srgrimes#include "mptutil.h"
50100824Sdwmalone
511590Srgrimesconst char *
52100824Sdwmalonempt_pdstate(CONFIG_PAGE_RAID_PHYS_DISK_0 *info)
531590Srgrimes{
541590Srgrimes	static char buf[16];
551590Srgrimes
56165758Srodrigc	switch (info->PhysDiskStatus.State) {
57176471Sdes	case MPI_PHYSDISK0_STATUS_ONLINE:
58219043Sdchagin		if ((info->PhysDiskStatus.Flags &
59176471Sdes		    MPI_PHYSDISK0_STATUS_FLAG_OUT_OF_SYNC) &&
60219043Sdchagin		    info->PhysDiskSettings.HotSparePool == 0)
61176471Sdes			return ("REBUILD");
62176471Sdes		else
63176471Sdes			return ("ONLINE");
64176471Sdes	case MPI_PHYSDISK0_STATUS_MISSING:
65176471Sdes		return ("MISSING");
66176471Sdes	case MPI_PHYSDISK0_STATUS_NOT_COMPATIBLE:
67176471Sdes		return ("NOT COMPATIBLE");
68190168Sdelphij	case MPI_PHYSDISK0_STATUS_FAILED:
69176471Sdes		return ("FAILED");
70190168Sdelphij	case MPI_PHYSDISK0_STATUS_INITIALIZING:
71165916Sjhb		return ("INITIALIZING");
7227443Scharnier	case MPI_PHYSDISK0_STATUS_OFFLINE_REQUESTED:
73176471Sdes		return ("OFFLINE REQUESTED");
74176471Sdes	case MPI_PHYSDISK0_STATUS_FAILED_REQUESTED:
7527443Scharnier		return ("FAILED REQUESTED");
76176471Sdes	case MPI_PHYSDISK0_STATUS_OTHER_OFFLINE:
771590Srgrimes		return ("OTHER OFFLINE");
781590Srgrimes	default:
791590Srgrimes		sprintf(buf, "PSTATE 0x%02x", info->PhysDiskStatus.State);
80176471Sdes		return (buf);
8127443Scharnier	}
8227443Scharnier}
831590Srgrimes
84158766Snetchild/*
851590Srgrimes * There are several ways to enumerate physical disks.  Unfortunately,
86219043Sdchagin * none of them are truly complete, so we have to build a union of all of
87219043Sdchagin * them.  Specifically:
88100824Sdwmalone *
89100824Sdwmalone * - IOC2 : This gives us a list of volumes, and by walking the volumes we
90219043Sdchagin *          can enumerate all of the drives attached to volumes including
91219043Sdchagin *          online drives and failed drives.
92100824Sdwmalone * - IOC3 : This gives us a list of all online physical drives including
93115759Speter *          drives that are not part of a volume nor a spare drive.  It
94115759Speter *          does not include any failed drives.
95100824Sdwmalone * - IOC5 : This gives us a list of all spare drives including failed
96219138Sdchagin *          spares.
97100824Sdwmalone *
98234494Sjhb * The specific edge cases are that 1) a failed volume member can only be
99226329Sdes * found via IOC2, 2) a drive that is neither a volume member nor a spare
100226329Sdes * can only be found via IOC3, and 3) a failed spare can only be found via
101100824Sdwmalone * IOC5.
102176471Sdes *
103176471Sdes * To handle this, walk all of the three lists and use the following
104176471Sdes * routine to add each drive encountered.  It quietly succeeds if the
105226269Sdes * drive is already present in the list.  It also sorts the list as it
106233925Sjhb * inserts new drives.
107233925Sjhb */
108100824Sdwmalonestatic int
109226157Sdesmpt_pd_insert(int fd, struct mpt_drive_list *list, U8 PhysDiskNum)
110100824Sdwmalone{
111176471Sdes	int i, j;
112219043Sdchagin
113100824Sdwmalone	/*
1141590Srgrimes	 * First, do a simple linear search to see if we have already
1151590Srgrimes	 * seen this drive.
116176471Sdes	 */
1171590Srgrimes	for (i = 0; i < list->ndrives; i++) {
1181590Srgrimes		if (list->drives[i]->PhysDiskNum == PhysDiskNum)
119226262Sdes			return (0);
120226262Sdes		if (list->drives[i]->PhysDiskNum > PhysDiskNum)
121226262Sdes			break;
122226262Sdes	}
123226262Sdes
124226262Sdes	/*
125226262Sdes	 * 'i' is our slot for the 'new' drive.  Make room and then
126226262Sdes	 * read the drive info.
127226164Sdes	 */
128219138Sdchagin	for (j = list->ndrives - 1; j >= i; j--)
129219138Sdchagin		list->drives[j + 1] = list->drives[j];
130219138Sdchagin	list->drives[i] = mpt_pd_info(fd, PhysDiskNum, NULL);
131219138Sdchagin	if (list->drives[i] == NULL)
132219138Sdchagin		return (errno);
133219138Sdchagin	list->ndrives++;
134219138Sdchagin	return (0);
135219138Sdchagin}
136219138Sdchagin
137219138Sdchaginstruct mpt_drive_list *
138219138Sdchaginmpt_pd_list(int fd)
139219138Sdchagin{
140219138Sdchagin	CONFIG_PAGE_IOC_2 *ioc2;
141219138Sdchagin	CONFIG_PAGE_IOC_2_RAID_VOL *vol;
142219138Sdchagin	CONFIG_PAGE_RAID_VOL_0 **volumes;
143219138Sdchagin	RAID_VOL0_PHYS_DISK *rdisk;
144219138Sdchagin	CONFIG_PAGE_IOC_3 *ioc3;
145219138Sdchagin	IOC_3_PHYS_DISK *disk;
146219138Sdchagin	CONFIG_PAGE_IOC_5 *ioc5;
147219138Sdchagin	IOC_5_HOT_SPARE *spare;
148219138Sdchagin	struct mpt_drive_list *list;
149219138Sdchagin	int count, error, i, j;
150219138Sdchagin
151219138Sdchagin	ioc2 = mpt_read_ioc_page(fd, 2, NULL);
152219138Sdchagin	if (ioc2 == NULL) {
153219138Sdchagin		error = errno;
154219043Sdchagin		warn("Failed to fetch volume list");
155219043Sdchagin		errno = error;
156219043Sdchagin		return (NULL);
157219043Sdchagin	}
158219043Sdchagin
159219043Sdchagin	ioc3 = mpt_read_ioc_page(fd, 3, NULL);
160219043Sdchagin	if (ioc3 == NULL) {
161219043Sdchagin		error = errno;
162219043Sdchagin		warn("Failed to fetch drive list");
163100824Sdwmalone		free(ioc2);
164100824Sdwmalone		errno = error;
1651590Srgrimes		return (NULL);
1661590Srgrimes	}
167100824Sdwmalone
1681590Srgrimes	ioc5 = mpt_read_ioc_page(fd, 5, NULL);
169112201Sjhb	if (ioc5 == NULL) {
170115759Speter		error = errno;
171219043Sdchagin		warn("Failed to fetch spare list");
1721590Srgrimes		free(ioc3);
173226153Sdes		free(ioc2);
17411823Sache		errno = error;
175219043Sdchagin		return (NULL);
176226153Sdes	}
177219043Sdchagin
178219043Sdchagin	/*
179219043Sdchagin	 * Go ahead and read the info for all the volumes.  For this
1801590Srgrimes	 * pass we figure out how many physical drives there are.
1811590Srgrimes	 */
1821590Srgrimes	volumes = malloc(sizeof(*volumes) * ioc2->NumActiveVolumes);
1831590Srgrimes	count = 0;
1841590Srgrimes	vol = ioc2->RaidVolume;
1851590Srgrimes	for (i = 0; i < ioc2->NumActiveVolumes; vol++, i++) {
1861590Srgrimes		volumes[i] = mpt_vol_info(fd, vol->VolumeBus, vol->VolumeID,
1871590Srgrimes		    NULL);
1881590Srgrimes		if (volumes[i] == NULL) {
1891590Srgrimes			error = errno;
1901590Srgrimes			warn("Failed to read volume info");
1911590Srgrimes			errno = error;
1921590Srgrimes			return (NULL);
1931590Srgrimes		}
1941590Srgrimes		count += volumes[i]->NumPhysDisks;
195115759Speter	}
196115759Speter	count += ioc3->NumPhysDisks;
197115759Speter	count += ioc5->NumHotSpares;
198176471Sdes
199176471Sdes	/* Walk the various lists enumerating drives. */
200176471Sdes	list = malloc(sizeof(*list) + sizeof(CONFIG_PAGE_RAID_PHYS_DISK_0) *
201152331Srwatson	    count);
202152331Srwatson	list->ndrives = 0;
203152331Srwatson
204123187Speter	for (i = 0; i < ioc2->NumActiveVolumes; i++) {
205123187Speter		rdisk = volumes[i]->PhysDisk;
206123187Speter		for (j = 0; j < volumes[i]->NumPhysDisks; rdisk++, j++)
207151930Srwatson			if (mpt_pd_insert(fd, list, rdisk->PhysDiskNum) < 0)
208151930Srwatson				return (NULL);
209151930Srwatson		free(volumes[i]);
2101590Srgrimes	}
2111590Srgrimes	free(ioc2);
2121590Srgrimes	free(volumes);
2131590Srgrimes
2141590Srgrimes	spare = ioc5->HotSpare;
2151590Srgrimes	for (i = 0; i < ioc5->NumHotSpares; spare++, i++)
2161590Srgrimes		if (mpt_pd_insert(fd, list, spare->PhysDiskNum) < 0)
2171590Srgrimes			return (NULL);
21827443Scharnier	free(ioc5);
21927443Scharnier
2201590Srgrimes	disk = ioc3->PhysDisk;
2211590Srgrimes	for (i = 0; i < ioc3->NumPhysDisks; disk++, i++)
2221590Srgrimes		if (mpt_pd_insert(fd, list, disk->PhysDiskNum) < 0)
2231590Srgrimes			return (NULL);
2241590Srgrimes	free(ioc3);
22519853Sfenner
2261590Srgrimes	return (list);
2271590Srgrimes}
228226153Sdes
22927443Scharniervoid
23027443Scharniermpt_free_pd_list(struct mpt_drive_list *list)
23127443Scharnier{
23227443Scharnier	int i;
233219043Sdchagin
234112201Sjhb	for (i = 0; i < list->ndrives; i++)
2351590Srgrimes		free(list->drives[i]);
236112201Sjhb	free(list);
237112201Sjhb}
238151930Srwatson
239226153Sdesint
240203551Sjhmpt_lookup_drive(struct mpt_drive_list *list, const char *drive,
241203551Sjh    U8 *PhysDiskNum)
242203551Sjh{
243203551Sjh	long val;
244203551Sjh	uint8_t bus, id;
245151930Srwatson	char *cp;
246151930Srwatson
247226153Sdes	/* Look for a raw device id first. */
248203551Sjh	val = strtol(drive, &cp, 0);
249112201Sjhb	if (*cp == '\0') {
250112201Sjhb		if (val < 0 || val > 0xff)
251112201Sjhb			goto bad;
252112201Sjhb		*PhysDiskNum = val;
2531590Srgrimes		return (0);
254236577Sjhb	}
255236577Sjhb
256115759Speter	/* Look for a <bus>:<id> string. */
25727443Scharnier	if (*cp == ':') {
25827443Scharnier		if (val < 0 || val > 0xff)
2591590Srgrimes			goto bad;
260226153Sdes		bus = val;
26127443Scharnier		val = strtol(cp + 1, &cp, 0);
26227443Scharnier		if (*cp != '\0')
2631590Srgrimes			goto bad;
2641590Srgrimes		if (val < 0 || val > 0xff)
26527443Scharnier			goto bad;
26627443Scharnier		id = val;
267219043Sdchagin
268219043Sdchagin		for (val = 0; val < list->ndrives; val++) {
269219043Sdchagin			if (list->drives[val]->PhysDiskBus == bus &&
270236577Sjhb			    list->drives[val]->PhysDiskID == id) {
271236577Sjhb				*PhysDiskNum = list->drives[val]->PhysDiskNum;
272115759Speter				return (0);
2731590Srgrimes			}
2741590Srgrimes		}
275112201Sjhb		return (ENOENT);
2761590Srgrimes	}
2771590Srgrimes
278219138Sdchaginbad:
279219138Sdchagin	return (EINVAL);
280219138Sdchagin}
281219138Sdchagin
282219138Sdchagin/* Borrowed heavily from scsi_all.c:scsi_print_inquiry(). */
283219138Sdchaginconst char *
2841590Srgrimesmpt_pd_inq_string(CONFIG_PAGE_RAID_PHYS_DISK_0 *pd_info)
2851590Srgrimes{
286219138Sdchagin	RAID_PHYS_DISK0_INQUIRY_DATA *inq_data;
287219138Sdchagin	u_char vendor[9], product[17], revision[5];
288219138Sdchagin	static char inq_string[64];
289219138Sdchagin
290219138Sdchagin	inq_data = &pd_info->InquiryData;
291219138Sdchagin	cam_strvis(vendor, inq_data->VendorID, sizeof(inq_data->VendorID),
2921590Srgrimes	    sizeof(vendor));
2931590Srgrimes	cam_strvis(product, inq_data->ProductID, sizeof(inq_data->ProductID),
294189707Sjhb	    sizeof(product));
2951590Srgrimes	cam_strvis(revision, inq_data->ProductRevLevel,
2961590Srgrimes	    sizeof(inq_data->ProductRevLevel), sizeof(revision));
2971590Srgrimes
2981590Srgrimes	/* Total hack. */
2991590Srgrimes	if (strcmp(vendor, "ATA") == 0)
3001590Srgrimes		snprintf(inq_string, sizeof(inq_string), "<%s %s> SATA",
301219138Sdchagin		    product, revision);
3021590Srgrimes	else
3031590Srgrimes		snprintf(inq_string, sizeof(inq_string), "<%s %s %s> SAS",
304234494Sjhb		    vendor, product, revision);
305234494Sjhb	return (inq_string);
306234494Sjhb}
307234494Sjhb
3081590Srgrimes/* Helper function to set a drive to a given state. */
30918400Sphkstatic int
31018470Sphkdrive_set_state(char *drive, U8 Action, U8 State, const char *name)
31118400Sphk{
312176471Sdes	CONFIG_PAGE_RAID_PHYS_DISK_0 *info;
313176471Sdes	struct mpt_drive_list *list;
314176471Sdes	U8 PhysDiskNum;
315226269Sdes	int error, fd;
316226269Sdes
317233925Sjhb	fd = mpt_open(mpt_unit);
318233925Sjhb	if (fd < 0) {
319233925Sjhb		error = errno;
320233925Sjhb		warn("mpt_open");
321233925Sjhb		return (error);
322233925Sjhb	}
323233925Sjhb
324112203Sjhb	list = mpt_pd_list(fd);
325112203Sjhb	if (list == NULL)
326112203Sjhb		return (errno);
3271590Srgrimes
3281590Srgrimes	if (mpt_lookup_drive(list, drive, &PhysDiskNum) < 0) {
329226153Sdes		error = errno;
3301590Srgrimes		warn("Failed to find drive %s", drive);
331100824Sdwmalone		return (error);
3321590Srgrimes	}
3331590Srgrimes	mpt_free_pd_list(list);
334100824Sdwmalone
335100824Sdwmalone	/* Get the info for this drive. */
3361590Srgrimes	info = mpt_pd_info(fd, PhysDiskNum, NULL);
3371590Srgrimes	if (info == NULL) {
3381590Srgrimes		error = errno;
3391590Srgrimes		warn("Failed to fetch info for drive %u", PhysDiskNum);
340226153Sdes		return (error);
3411590Srgrimes	}
3421590Srgrimes
3431590Srgrimes	/* Try to change the state. */
3441590Srgrimes	if (info->PhysDiskStatus.State == State) {
3451590Srgrimes		warnx("Drive %u is already in the desired state", PhysDiskNum);
346219043Sdchagin		return (EINVAL);
347219043Sdchagin	}
348219043Sdchagin
349219043Sdchagin	error = mpt_raid_action(fd, Action, 0, 0, PhysDiskNum, 0, NULL, 0, NULL,
350219043Sdchagin	    NULL, 0, NULL, NULL, 0);
351219043Sdchagin	if (error) {
352219043Sdchagin		warnc(error, "Failed to set drive %u to %s", PhysDiskNum, name);
353219043Sdchagin		return (error);
354219043Sdchagin	}
355219043Sdchagin
356219043Sdchagin	free(info);
357219043Sdchagin	close(fd);
358219043Sdchagin
359219043Sdchagin	return (0);
360219043Sdchagin}
361219043Sdchagin
362219043Sdchaginstatic int
363219043Sdchaginfail_drive(int ac, char **av)
364219043Sdchagin{
365219043Sdchagin
366219043Sdchagin	if (ac != 2) {
367219043Sdchagin		warnx("fail: %s", ac > 2 ? "extra arguments" :
368219043Sdchagin		    "drive required");
369219043Sdchagin		return (EINVAL);
370219043Sdchagin	}
371219043Sdchagin
372219043Sdchagin	return (drive_set_state(av[1], MPI_RAID_ACTION_FAIL_PHYSDISK,
373219043Sdchagin	    MPI_PHYSDISK0_STATUS_FAILED_REQUESTED, "FAILED"));
374219043Sdchagin}
375219043SdchaginMPT_COMMAND(top, fail, fail_drive);
376219043Sdchagin
377219043Sdchaginstatic int
378219043Sdchaginonline_drive(int ac, char **av)
379219043Sdchagin{
380219043Sdchagin
381219043Sdchagin	if (ac != 2) {
382219043Sdchagin		warnx("online: %s", ac > 2 ? "extra arguments" :
383219043Sdchagin		    "drive required");
384219043Sdchagin		return (EINVAL);
385219043Sdchagin	}
386219043Sdchagin
387219043Sdchagin	return (drive_set_state(av[1], MPI_RAID_ACTION_PHYSDISK_ONLINE,
388219043Sdchagin	    MPI_PHYSDISK0_STATUS_ONLINE, "ONLINE"));
389219043Sdchagin}
390219043SdchaginMPT_COMMAND(top, online, online_drive);
391219043Sdchagin
392219043Sdchaginstatic int
393219043Sdchaginoffline_drive(int ac, char **av)
394219043Sdchagin{
395219043Sdchagin
396219043Sdchagin	if (ac != 2) {
397219043Sdchagin		warnx("offline: %s", ac > 2 ? "extra arguments" :
398219043Sdchagin		    "drive required");
399219043Sdchagin		return (EINVAL);
400219043Sdchagin	}
401219043Sdchagin
402219043Sdchagin	return (drive_set_state(av[1], MPI_RAID_ACTION_PHYSDISK_OFFLINE,
403219043Sdchagin	    MPI_PHYSDISK0_STATUS_OFFLINE_REQUESTED, "OFFLINE"));
404219043Sdchagin}
405219043SdchaginMPT_COMMAND(top, offline, offline_drive);
406219043Sdchagin