mfi_drive.c revision 196211
1/*-
2 * Copyright (c) 2008, 2009 Yahoo!, Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 3. The names of the authors may not be used to endorse or promote
14 *    products derived from this software without specific prior written
15 *    permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 *
29 * $FreeBSD: head/usr.sbin/mfiutil/mfi_drive.c 196211 2009-08-14 12:30:10Z scottl $
30 */
31
32#include <sys/types.h>
33#include <sys/errno.h>
34#include <ctype.h>
35#include <err.h>
36#include <libutil.h>
37#include <limits.h>
38#include <stdio.h>
39#include <stdlib.h>
40#include <string.h>
41#include <strings.h>
42#include <unistd.h>
43#include <cam/scsi/scsi_all.h>
44#include "mfiutil.h"
45
46MFI_TABLE(top, drive);
47
48const char *
49mfi_pdstate(enum mfi_pd_state state)
50{
51	static char buf[16];
52
53	switch (state) {
54	case MFI_PD_STATE_UNCONFIGURED_GOOD:
55		return ("UNCONFIGURED GOOD");
56	case MFI_PD_STATE_UNCONFIGURED_BAD:
57		return ("UNCONFIGURED BAD");
58	case MFI_PD_STATE_HOT_SPARE:
59		return ("HOT SPARE");
60	case MFI_PD_STATE_OFFLINE:
61		return ("OFFLINE");
62	case MFI_PD_STATE_FAILED:
63		return ("FAILED");
64	case MFI_PD_STATE_REBUILD:
65		return ("REBUILD");
66	case MFI_PD_STATE_ONLINE:
67		return ("ONLINE");
68	default:
69		sprintf(buf, "PSTATE 0x%04x", state);
70		return (buf);
71	}
72}
73
74int
75mfi_lookup_drive(int fd, char *drive, uint16_t *device_id)
76{
77	struct mfi_pd_list *list;
78	uint8_t encl, slot;
79	long val;
80	u_int i;
81	char *cp;
82
83	/* Look for a raw device id first. */
84	val = strtol(drive, &cp, 0);
85	if (*cp == '\0') {
86		if (val < 0 || val >= 0xffff)
87			goto bad;
88		*device_id = val;
89		return (0);
90	}
91
92	/* Support for MegaCli style [Exx]:Syy notation. */
93	if (toupper(drive[0]) == 'E' || toupper(drive[0]) == 'S') {
94		if (drive[1] == '\0')
95			goto bad;
96		cp = drive;
97		if (toupper(drive[0]) == 'E') {
98			cp++;			/* Eat 'E' */
99			val = strtol(cp, &cp, 0);
100			if (val < 0 || val > 0xff || *cp != ':')
101				goto bad;
102			encl = val;
103			cp++;			/* Eat ':' */
104			if (toupper(*cp) != 'S')
105				goto bad;
106		} else
107			encl = 0xff;
108		cp++;				/* Eat 'S' */
109		if (*cp == '\0')
110			goto bad;
111		val = strtol(cp, &cp, 0);
112		if (val < 0 || val > 0xff || *cp != '\0')
113			goto bad;
114		slot = val;
115
116		if (mfi_pd_get_list(fd, &list, NULL) < 0) {
117			warn("Failed to fetch drive list");
118			return (errno);
119		}
120
121		for (i = 0; i < list->count; i++) {
122			if (list->addr[i].scsi_dev_type != 0)
123				continue;
124
125			if (((encl == 0xff &&
126			    list->addr[i].encl_device_id == 0xffff) ||
127			    list->addr[i].encl_index == encl) &&
128			    list->addr[i].slot_number == slot) {
129				*device_id = list->addr[i].device_id;
130				free(list);
131				return (0);
132			}
133		}
134		free(list);
135		warnx("Unknown drive %s", drive);
136		return (EINVAL);
137	}
138
139bad:
140	warnx("Invalid drive number %s", drive);
141	return (EINVAL);
142}
143
144static void
145mbox_store_device_id(uint8_t *mbox, uint16_t device_id)
146{
147
148	mbox[0] = device_id & 0xff;
149	mbox[1] = device_id >> 8;
150}
151
152void
153mbox_store_pdref(uint8_t *mbox, union mfi_pd_ref *ref)
154{
155
156	mbox[0] = ref->v.device_id & 0xff;
157	mbox[1] = ref->v.device_id >> 8;
158	mbox[2] = ref->v.seq_num & 0xff;
159	mbox[3] = ref->v.seq_num >> 8;
160}
161
162int
163mfi_pd_get_list(int fd, struct mfi_pd_list **listp, uint8_t *statusp)
164{
165	struct mfi_pd_list *list;
166	uint32_t list_size;
167
168	/*
169	 * Keep fetching the list in a loop until we have a large enough
170	 * buffer to hold the entire list.
171	 */
172	list = NULL;
173	list_size = 1024;
174fetch:
175	list = reallocf(list, list_size);
176	if (list == NULL)
177		return (-1);
178	if (mfi_dcmd_command(fd, MFI_DCMD_PD_GET_LIST, list, list_size, NULL,
179	    0, statusp) < 0) {
180		free(list);
181		return (-1);
182	}
183
184	if (list->size > list_size) {
185		list_size = list->size;
186		goto fetch;
187	}
188
189	*listp = list;
190	return (0);
191}
192
193int
194mfi_pd_get_info(int fd, uint16_t device_id, struct mfi_pd_info *info,
195    uint8_t *statusp)
196{
197	uint8_t mbox[2];
198
199	mbox_store_device_id(&mbox[0], device_id);
200	return (mfi_dcmd_command(fd, MFI_DCMD_PD_GET_INFO, info,
201	    sizeof(struct mfi_pd_info), mbox, 2, statusp));
202}
203
204static void
205cam_strvis(char *dst, const char *src, int srclen, int dstlen)
206{
207
208	/* Trim leading/trailing spaces, nulls. */
209	while (srclen > 0 && src[0] == ' ')
210		src++, srclen--;
211	while (srclen > 0
212	    && (src[srclen-1] == ' ' || src[srclen-1] == '\0'))
213		srclen--;
214
215	while (srclen > 0 && dstlen > 1) {
216		char *cur_pos = dst;
217
218		if (*src < 0x20) {
219			/* SCSI-II Specifies that these should never occur. */
220			/* non-printable character */
221			if (dstlen > 4) {
222				*cur_pos++ = '\\';
223				*cur_pos++ = ((*src & 0300) >> 6) + '0';
224				*cur_pos++ = ((*src & 0070) >> 3) + '0';
225				*cur_pos++ = ((*src & 0007) >> 0) + '0';
226			} else {
227				*cur_pos++ = '?';
228			}
229		} else {
230			/* normal character */
231			*cur_pos++ = *src;
232		}
233		src++;
234		srclen--;
235		dstlen -= cur_pos - dst;
236		dst = cur_pos;
237	}
238	*dst = '\0';
239}
240
241/* Borrowed heavily from scsi_all.c:scsi_print_inquiry(). */
242const char *
243mfi_pd_inq_string(struct mfi_pd_info *info)
244{
245	struct scsi_inquiry_data *inq_data;
246	char vendor[16], product[48], revision[16], rstr[12], serial[SID_VENDOR_SPECIFIC_0_SIZE];
247	static char inq_string[64];
248
249	inq_data = (struct scsi_inquiry_data *)info->inquiry_data;
250	if (SID_QUAL_IS_VENDOR_UNIQUE(inq_data))
251		return (NULL);
252	if (SID_TYPE(inq_data) != T_DIRECT)
253		return (NULL);
254	if (SID_QUAL(inq_data) != SID_QUAL_LU_CONNECTED)
255		return (NULL);
256
257	cam_strvis(vendor, inq_data->vendor, sizeof(inq_data->vendor),
258	    sizeof(vendor));
259	cam_strvis(product, inq_data->product, sizeof(inq_data->product),
260	    sizeof(product));
261	cam_strvis(revision, inq_data->revision, sizeof(inq_data->revision),
262	    sizeof(revision));
263	cam_strvis(serial, (char *)inq_data->vendor_specific0, sizeof(inq_data->vendor_specific0),
264	    sizeof(serial));
265
266	/* Hack for SATA disks, no idea how to tell speed. */
267	if (strcmp(vendor, "ATA") == 0) {
268		snprintf(inq_string, sizeof(inq_string), "<%s %s serial=%s> SATA",
269		    product, revision, serial);
270		return (inq_string);
271	}
272
273	switch (SID_ANSI_REV(inq_data)) {
274	case SCSI_REV_CCS:
275		strcpy(rstr, "SCSI-CCS");
276		break;
277	case 5:
278		strcpy(rstr, "SAS");
279		break;
280	default:
281		snprintf(rstr, sizeof (rstr), "SCSI-%d",
282		    SID_ANSI_REV(inq_data));
283		break;
284	}
285	snprintf(inq_string, sizeof(inq_string), "<%s %s %s serial=%s> %s", vendor,
286	    product, revision, serial, rstr);
287	return (inq_string);
288}
289
290/* Helper function to set a drive to a given state. */
291static int
292drive_set_state(char *drive, uint16_t new_state)
293{
294	struct mfi_pd_info info;
295	uint16_t device_id;
296	uint8_t mbox[6];
297	int error, fd;
298
299	fd = mfi_open(mfi_unit);
300	if (fd < 0) {
301		warn("mfi_open");
302		return (errno);
303	}
304
305	error = mfi_lookup_drive(fd, drive, &device_id);
306	if (error)
307		return (error);
308
309	/* Get the info for this drive. */
310	if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) {
311		warn("Failed to fetch info for drive %u", device_id);
312		return (errno);
313	}
314
315	/* Try to change the state. */
316	if (info.fw_state == new_state) {
317		warnx("Drive %u is already in the desired state", device_id);
318		return (EINVAL);
319	}
320
321	mbox_store_pdref(&mbox[0], &info.ref);
322	mbox[4] = new_state & 0xff;
323	mbox[5] = new_state >> 8;
324	if (mfi_dcmd_command(fd, MFI_DCMD_PD_STATE_SET, NULL, 0, mbox, 6,
325	    NULL) < 0) {
326		warn("Failed to set drive %u to %s", device_id,
327		    mfi_pdstate(new_state));
328		return (errno);
329	}
330
331	close(fd);
332
333	return (0);
334}
335
336static int
337fail_drive(int ac, char **av)
338{
339
340	if (ac != 2) {
341		warnx("fail: %s", ac > 2 ? "extra arguments" :
342		    "drive required");
343		return (EINVAL);
344	}
345
346	return (drive_set_state(av[1], MFI_PD_STATE_FAILED));
347}
348MFI_COMMAND(top, fail, fail_drive);
349
350static int
351good_drive(int ac, char **av)
352{
353
354	if (ac != 2) {
355		warnx("good: %s", ac > 2 ? "extra arguments" :
356		    "drive required");
357		return (EINVAL);
358	}
359
360	return (drive_set_state(av[1], MFI_PD_STATE_UNCONFIGURED_GOOD));
361}
362MFI_COMMAND(top, good, good_drive);
363
364static int
365rebuild_drive(int ac, char **av)
366{
367
368	if (ac != 2) {
369		warnx("rebuild: %s", ac > 2 ? "extra arguments" :
370		    "drive required");
371		return (EINVAL);
372	}
373
374	return (drive_set_state(av[1], MFI_PD_STATE_REBUILD));
375}
376MFI_COMMAND(top, rebuild, rebuild_drive);
377
378static int
379start_rebuild(int ac, char **av)
380{
381	struct mfi_pd_info info;
382	uint16_t device_id;
383	uint8_t mbox[4];
384	int error, fd;
385
386	if (ac != 2) {
387		warnx("start rebuild: %s", ac > 2 ? "extra arguments" :
388		    "drive required");
389		return (EINVAL);
390	}
391
392	fd = mfi_open(mfi_unit);
393	if (fd < 0) {
394		warn("mfi_open");
395		return (errno);
396	}
397
398	error = mfi_lookup_drive(fd, av[1], &device_id);
399	if (error)
400		return (error);
401
402	/* Get the info for this drive. */
403	if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) {
404		warn("Failed to fetch info for drive %u", device_id);
405		return (errno);
406	}
407
408	/* Check the state, must be REBUILD. */
409	if (info.fw_state != MFI_PD_STATE_REBUILD) {
410		warn("Drive %d is not in the REBUILD state", device_id);
411		return (EINVAL);
412	}
413
414	/* Start the rebuild. */
415	mbox_store_pdref(&mbox[0], &info.ref);
416	if (mfi_dcmd_command(fd, MFI_DCMD_PD_REBUILD_START, NULL, 0, mbox, 4,
417	    NULL) < 0) {
418		warn("Failed to start rebuild on drive %u", device_id);
419		return (errno);
420	}
421	close(fd);
422
423	return (0);
424}
425MFI_COMMAND(start, rebuild, start_rebuild);
426
427static int
428abort_rebuild(int ac, char **av)
429{
430	struct mfi_pd_info info;
431	uint16_t device_id;
432	uint8_t mbox[4];
433	int error, fd;
434
435	if (ac != 2) {
436		warnx("abort rebuild: %s", ac > 2 ? "extra arguments" :
437		    "drive required");
438		return (EINVAL);
439	}
440
441	fd = mfi_open(mfi_unit);
442	if (fd < 0) {
443		warn("mfi_open");
444		return (errno);
445	}
446
447	error = mfi_lookup_drive(fd, av[1], &device_id);
448	if (error)
449		return (error);
450
451	/* Get the info for this drive. */
452	if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) {
453		warn("Failed to fetch info for drive %u", device_id);
454		return (errno);
455	}
456
457	/* Check the state, must be REBUILD. */
458	if (info.fw_state != MFI_PD_STATE_REBUILD) {
459		warn("Drive %d is not in the REBUILD state", device_id);
460		return (EINVAL);
461	}
462
463	/* Abort the rebuild. */
464	mbox_store_pdref(&mbox[0], &info.ref);
465	if (mfi_dcmd_command(fd, MFI_DCMD_PD_REBUILD_ABORT, NULL, 0, mbox, 4,
466	    NULL) < 0) {
467		warn("Failed to abort rebuild on drive %u", device_id);
468		return (errno);
469	}
470	close(fd);
471
472	return (0);
473}
474MFI_COMMAND(abort, rebuild, abort_rebuild);
475
476static int
477drive_progress(int ac, char **av)
478{
479	struct mfi_pd_info info;
480	uint16_t device_id;
481	int error, fd;
482
483	if (ac != 2) {
484		warnx("drive progress: %s", ac > 2 ? "extra arguments" :
485		    "drive required");
486		return (EINVAL);
487	}
488
489	fd = mfi_open(mfi_unit);
490	if (fd < 0) {
491		warn("mfi_open");
492		return (errno);
493	}
494
495	error = mfi_lookup_drive(fd, av[1], &device_id);
496	if (error)
497		return (error);
498
499	/* Get the info for this drive. */
500	if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) {
501		warn("Failed to fetch info for drive %u", device_id);
502		return (errno);
503	}
504	close(fd);
505
506	/* Display any of the active events. */
507	if (info.prog_info.active & MFI_PD_PROGRESS_REBUILD)
508		mfi_display_progress("Rebuild", &info.prog_info.rbld);
509	if (info.prog_info.active & MFI_PD_PROGRESS_PATROL)
510		mfi_display_progress("Patrol Read", &info.prog_info.patrol);
511	if (info.prog_info.active & MFI_PD_PROGRESS_CLEAR)
512		mfi_display_progress("Clear", &info.prog_info.clear);
513	if ((info.prog_info.active & (MFI_PD_PROGRESS_REBUILD |
514	    MFI_PD_PROGRESS_PATROL | MFI_PD_PROGRESS_CLEAR)) == 0)
515		printf("No activity in progress for drive %u.\n", device_id);
516
517	return (0);
518}
519MFI_COMMAND(drive, progress, drive_progress);
520
521static int
522drive_clear(int ac, char **av)
523{
524	struct mfi_pd_info info;
525	uint32_t opcode;
526	uint16_t device_id;
527	uint8_t mbox[4];
528	char *s1;
529	int error, fd;
530
531	if (ac != 3) {
532		warnx("drive clear: %s", ac > 3 ? "extra arguments" :
533		    "drive and action requires");
534		return (EINVAL);
535	}
536
537	for (s1 = av[2]; *s1 != '\0'; s1++)
538		*s1 = tolower(*s1);
539	if (strcmp(av[2], "start") == 0)
540		opcode = MFI_DCMD_PD_CLEAR_START;
541	else if ((strcmp(av[2], "stop") == 0) || (strcmp(av[2], "abort") == 0))
542		opcode = MFI_DCMD_PD_CLEAR_ABORT;
543	else {
544		warnx("drive clear: invalid action, must be 'start' or 'stop'\n");
545		return (EINVAL);
546	}
547
548	fd = mfi_open(mfi_unit);
549	if (fd < 0) {
550		warn("mfi_open");
551		return (errno);
552	}
553
554	error = mfi_lookup_drive(fd, av[1], &device_id);
555	if (error)
556		return (error);
557
558	/* Get the info for this drive. */
559	if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) {
560		warn("Failed to fetch info for drive %u", device_id);
561		return (errno);
562	}
563
564	mbox_store_pdref(&mbox[0], &info.ref);
565	if (mfi_dcmd_command(fd, opcode, NULL, 0, mbox, 4, NULL) < 0) {
566		warn("Failed to %s clear on drive %u",
567		    opcode == MFI_DCMD_PD_CLEAR_START ? "start" : "stop",
568		    device_id);
569		return (errno);
570	}
571
572	close(fd);
573	return (0);
574}
575MFI_COMMAND(drive, clear, drive_clear);
576
577static int
578drive_locate(int ac, char **av)
579{
580	uint16_t device_id;
581	uint32_t opcode;
582	int error, fd;
583	uint8_t mbox[4];
584
585	if (ac != 3) {
586		warnx("locate: %s", ac > 3 ? "extra arguments" :
587		    "drive and state required");
588		return (EINVAL);
589	}
590
591	if (strcasecmp(av[2], "on") == 0 || strcasecmp(av[2], "start") == 0)
592		opcode = MFI_DCMD_PD_LOCATE_START;
593	else if (strcasecmp(av[2], "off") == 0 ||
594	    strcasecmp(av[2], "stop") == 0)
595		opcode = MFI_DCMD_PD_LOCATE_STOP;
596	else {
597		warnx("locate: invalid state %s", av[2]);
598		return (EINVAL);
599	}
600
601	fd = mfi_open(mfi_unit);
602	if (fd < 0) {
603		warn("mfi_open");
604		return (errno);
605	}
606
607	error = mfi_lookup_drive(fd, av[1], &device_id);
608	if (error)
609		return (error);
610
611
612	mbox_store_device_id(&mbox[0], device_id);
613	mbox[2] = 0;
614	mbox[3] = 0;
615	if (mfi_dcmd_command(fd, opcode, NULL, 0, mbox, 4, NULL) < 0) {
616		warn("Failed to %s locate on drive %u",
617		    opcode == MFI_DCMD_PD_LOCATE_START ? "start" : "stop",
618		    device_id);
619		return (errno);
620	}
621	close(fd);
622
623	return (0);
624}
625MFI_COMMAND(top, locate, drive_locate);
626