]> CyberLeo.Net >> Repos - FreeBSD/releng/8.2.git/blob - usr.sbin/mfiutil/mfi_drive.c
MFC r217226:
[FreeBSD/releng/8.2.git] / usr.sbin / mfiutil / mfi_drive.c
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$
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
46 MFI_TABLE(top, drive);
47
48 const char *
49 mfi_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         case MFI_PD_STATE_COPYBACK:
69                 return ("COPYBACK");
70         case MFI_PD_STATE_SYSTEM:
71                 return ("SYSTEM");
72         default:
73                 sprintf(buf, "PSTATE 0x%04x", state);
74                 return (buf);
75         }
76 }
77
78 int
79 mfi_lookup_drive(int fd, char *drive, uint16_t *device_id)
80 {
81         struct mfi_pd_list *list;
82         long val;
83         int error;
84         u_int i;
85         char *cp;
86         uint8_t encl, slot;
87
88         /* Look for a raw device id first. */
89         val = strtol(drive, &cp, 0);
90         if (*cp == '\0') {
91                 if (val < 0 || val >= 0xffff)
92                         goto bad;
93                 *device_id = val;
94                 return (0);
95         }
96
97         /* Support for MegaCli style [Exx]:Syy notation. */
98         if (toupper(drive[0]) == 'E' || toupper(drive[0]) == 'S') {
99                 if (drive[1] == '\0')
100                         goto bad;
101                 cp = drive;
102                 if (toupper(drive[0]) == 'E') {
103                         cp++;                   /* Eat 'E' */
104                         val = strtol(cp, &cp, 0);
105                         if (val < 0 || val > 0xff || *cp != ':')
106                                 goto bad;
107                         encl = val;
108                         cp++;                   /* Eat ':' */
109                         if (toupper(*cp) != 'S')
110                                 goto bad;
111                 } else
112                         encl = 0xff;
113                 cp++;                           /* Eat 'S' */
114                 if (*cp == '\0')
115                         goto bad;
116                 val = strtol(cp, &cp, 0);
117                 if (val < 0 || val > 0xff || *cp != '\0')
118                         goto bad;
119                 slot = val;
120
121                 if (mfi_pd_get_list(fd, &list, NULL) < 0) {
122                         error = errno;
123                         warn("Failed to fetch drive list");
124                         return (error);
125                 }
126
127                 for (i = 0; i < list->count; i++) {
128                         if (list->addr[i].scsi_dev_type != 0)
129                                 continue;
130
131                         if (((encl == 0xff &&
132                             list->addr[i].encl_device_id == 0xffff) ||
133                             list->addr[i].encl_index == encl) &&
134                             list->addr[i].slot_number == slot) {
135                                 *device_id = list->addr[i].device_id;
136                                 free(list);
137                                 return (0);
138                         }
139                 }
140                 free(list);
141                 warnx("Unknown drive %s", drive);
142                 return (EINVAL);
143         }
144
145 bad:
146         warnx("Invalid drive number %s", drive);
147         return (EINVAL);
148 }
149
150 static void
151 mbox_store_device_id(uint8_t *mbox, uint16_t device_id)
152 {
153
154         mbox[0] = device_id & 0xff;
155         mbox[1] = device_id >> 8;
156 }
157
158 void
159 mbox_store_pdref(uint8_t *mbox, union mfi_pd_ref *ref)
160 {
161
162         mbox[0] = ref->v.device_id & 0xff;
163         mbox[1] = ref->v.device_id >> 8;
164         mbox[2] = ref->v.seq_num & 0xff;
165         mbox[3] = ref->v.seq_num >> 8;
166 }
167
168 int
169 mfi_pd_get_list(int fd, struct mfi_pd_list **listp, uint8_t *statusp)
170 {
171         struct mfi_pd_list *list;
172         uint32_t list_size;
173
174         /*
175          * Keep fetching the list in a loop until we have a large enough
176          * buffer to hold the entire list.
177          */
178         list = NULL;
179         list_size = 1024;
180 fetch:
181         list = reallocf(list, list_size);
182         if (list == NULL)
183                 return (-1);
184         if (mfi_dcmd_command(fd, MFI_DCMD_PD_GET_LIST, list, list_size, NULL,
185             0, statusp) < 0) {
186                 free(list);
187                 return (-1);
188         }
189
190         if (list->size > list_size) {
191                 list_size = list->size;
192                 goto fetch;
193         }
194
195         *listp = list;
196         return (0);
197 }
198
199 int
200 mfi_pd_get_info(int fd, uint16_t device_id, struct mfi_pd_info *info,
201     uint8_t *statusp)
202 {
203         uint8_t mbox[2];
204
205         mbox_store_device_id(&mbox[0], device_id);
206         return (mfi_dcmd_command(fd, MFI_DCMD_PD_GET_INFO, info,
207             sizeof(struct mfi_pd_info), mbox, 2, statusp));
208 }
209
210 static void
211 cam_strvis(char *dst, const char *src, int srclen, int dstlen)
212 {
213
214         /* Trim leading/trailing spaces, nulls. */
215         while (srclen > 0 && src[0] == ' ')
216                 src++, srclen--;
217         while (srclen > 0
218             && (src[srclen-1] == ' ' || src[srclen-1] == '\0'))
219                 srclen--;
220
221         while (srclen > 0 && dstlen > 1) {
222                 char *cur_pos = dst;
223
224                 if (*src < 0x20) {
225                         /* SCSI-II Specifies that these should never occur. */
226                         /* non-printable character */
227                         if (dstlen > 4) {
228                                 *cur_pos++ = '\\';
229                                 *cur_pos++ = ((*src & 0300) >> 6) + '0';
230                                 *cur_pos++ = ((*src & 0070) >> 3) + '0';
231                                 *cur_pos++ = ((*src & 0007) >> 0) + '0';
232                         } else {
233                                 *cur_pos++ = '?';
234                         }
235                 } else {
236                         /* normal character */
237                         *cur_pos++ = *src;
238                 }
239                 src++;
240                 srclen--;
241                 dstlen -= cur_pos - dst;
242                 dst = cur_pos;
243         }
244         *dst = '\0';
245 }
246
247 /* Borrowed heavily from scsi_all.c:scsi_print_inquiry(). */
248 const char *
249 mfi_pd_inq_string(struct mfi_pd_info *info)
250 {
251         struct scsi_inquiry_data *inq_data;
252         char vendor[16], product[48], revision[16], rstr[12], serial[SID_VENDOR_SPECIFIC_0_SIZE];
253         static char inq_string[64];
254
255         inq_data = (struct scsi_inquiry_data *)info->inquiry_data;
256         if (SID_QUAL_IS_VENDOR_UNIQUE(inq_data))
257                 return (NULL);
258         if (SID_TYPE(inq_data) != T_DIRECT)
259                 return (NULL);
260         if (SID_QUAL(inq_data) != SID_QUAL_LU_CONNECTED)
261                 return (NULL);
262
263         cam_strvis(vendor, inq_data->vendor, sizeof(inq_data->vendor),
264             sizeof(vendor));
265         cam_strvis(product, inq_data->product, sizeof(inq_data->product),
266             sizeof(product));
267         cam_strvis(revision, inq_data->revision, sizeof(inq_data->revision),
268             sizeof(revision));
269         cam_strvis(serial, (char *)inq_data->vendor_specific0, sizeof(inq_data->vendor_specific0),
270             sizeof(serial));
271
272         /* Hack for SATA disks, no idea how to tell speed. */
273         if (strcmp(vendor, "ATA") == 0) {
274                 snprintf(inq_string, sizeof(inq_string), "<%s %s serial=%s> SATA",
275                     product, revision, serial);
276                 return (inq_string);
277         }
278
279         switch (SID_ANSI_REV(inq_data)) {
280         case SCSI_REV_CCS:
281                 strcpy(rstr, "SCSI-CCS");
282                 break;
283         case 5:
284                 strcpy(rstr, "SAS");
285                 break;
286         default:
287                 snprintf(rstr, sizeof (rstr), "SCSI-%d",
288                     SID_ANSI_REV(inq_data));
289                 break;
290         }
291         snprintf(inq_string, sizeof(inq_string), "<%s %s %s serial=%s> %s", vendor,
292             product, revision, serial, rstr);
293         return (inq_string);
294 }
295
296 /* Helper function to set a drive to a given state. */
297 static int
298 drive_set_state(char *drive, uint16_t new_state)
299 {
300         struct mfi_pd_info info;
301         uint16_t device_id;
302         uint8_t mbox[6];
303         int error, fd;
304
305         fd = mfi_open(mfi_unit);
306         if (fd < 0) {
307                 error = errno;
308                 warn("mfi_open");
309                 return (error);
310         }
311
312         error = mfi_lookup_drive(fd, drive, &device_id);
313         if (error)
314                 return (error);
315
316         /* Get the info for this drive. */
317         if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) {
318                 error = errno;
319                 warn("Failed to fetch info for drive %u", device_id);
320                 return (error);
321         }
322
323         /* Try to change the state. */
324         if (info.fw_state == new_state) {
325                 warnx("Drive %u is already in the desired state", device_id);
326                 return (EINVAL);
327         }
328
329         mbox_store_pdref(&mbox[0], &info.ref);
330         mbox[4] = new_state & 0xff;
331         mbox[5] = new_state >> 8;
332         if (mfi_dcmd_command(fd, MFI_DCMD_PD_STATE_SET, NULL, 0, mbox, 6,
333             NULL) < 0) {
334                 error = errno;
335                 warn("Failed to set drive %u to %s", device_id,
336                     mfi_pdstate(new_state));
337                 return (error);
338         }
339
340         close(fd);
341
342         return (0);
343 }
344
345 static int
346 fail_drive(int ac, char **av)
347 {
348
349         if (ac != 2) {
350                 warnx("fail: %s", ac > 2 ? "extra arguments" :
351                     "drive required");
352                 return (EINVAL);
353         }
354
355         return (drive_set_state(av[1], MFI_PD_STATE_FAILED));
356 }
357 MFI_COMMAND(top, fail, fail_drive);
358
359 static int
360 good_drive(int ac, char **av)
361 {
362
363         if (ac != 2) {
364                 warnx("good: %s", ac > 2 ? "extra arguments" :
365                     "drive required");
366                 return (EINVAL);
367         }
368
369         return (drive_set_state(av[1], MFI_PD_STATE_UNCONFIGURED_GOOD));
370 }
371 MFI_COMMAND(top, good, good_drive);
372
373 static int
374 rebuild_drive(int ac, char **av)
375 {
376
377         if (ac != 2) {
378                 warnx("rebuild: %s", ac > 2 ? "extra arguments" :
379                     "drive required");
380                 return (EINVAL);
381         }
382
383         return (drive_set_state(av[1], MFI_PD_STATE_REBUILD));
384 }
385 MFI_COMMAND(top, rebuild, rebuild_drive);
386
387 static int
388 start_rebuild(int ac, char **av)
389 {
390         struct mfi_pd_info info;
391         uint16_t device_id;
392         uint8_t mbox[4];
393         int error, fd;
394
395         if (ac != 2) {
396                 warnx("start rebuild: %s", ac > 2 ? "extra arguments" :
397                     "drive required");
398                 return (EINVAL);
399         }
400
401         fd = mfi_open(mfi_unit);
402         if (fd < 0) {
403                 error = errno;
404                 warn("mfi_open");
405                 return (error);
406         }
407
408         error = mfi_lookup_drive(fd, av[1], &device_id);
409         if (error)
410                 return (error);
411
412         /* Get the info for this drive. */
413         if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) {
414                 error = errno;
415                 warn("Failed to fetch info for drive %u", device_id);
416                 return (error);
417         }
418
419         /* Check the state, must be REBUILD. */
420         if (info.fw_state != MFI_PD_STATE_REBUILD) {
421                 warnx("Drive %d is not in the REBUILD state", device_id);
422                 return (EINVAL);
423         }
424
425         /* Start the rebuild. */
426         mbox_store_pdref(&mbox[0], &info.ref);
427         if (mfi_dcmd_command(fd, MFI_DCMD_PD_REBUILD_START, NULL, 0, mbox, 4,
428             NULL) < 0) {
429                 error = errno;
430                 warn("Failed to start rebuild on drive %u", device_id);
431                 return (error);
432         }
433         close(fd);
434
435         return (0);
436 }
437 MFI_COMMAND(start, rebuild, start_rebuild);
438
439 static int
440 abort_rebuild(int ac, char **av)
441 {
442         struct mfi_pd_info info;
443         uint16_t device_id;
444         uint8_t mbox[4];
445         int error, fd;
446
447         if (ac != 2) {
448                 warnx("abort rebuild: %s", ac > 2 ? "extra arguments" :
449                     "drive required");
450                 return (EINVAL);
451         }
452
453         fd = mfi_open(mfi_unit);
454         if (fd < 0) {
455                 error = errno;
456                 warn("mfi_open");
457                 return (error);
458         }
459
460         error = mfi_lookup_drive(fd, av[1], &device_id);
461         if (error)
462                 return (error);
463
464         /* Get the info for this drive. */
465         if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) {
466                 error = errno;
467                 warn("Failed to fetch info for drive %u", device_id);
468                 return (error);
469         }
470
471         /* Check the state, must be REBUILD. */
472         if (info.fw_state != MFI_PD_STATE_REBUILD) {
473                 warn("Drive %d is not in the REBUILD state", device_id);
474                 return (EINVAL);
475         }
476
477         /* Abort the rebuild. */
478         mbox_store_pdref(&mbox[0], &info.ref);
479         if (mfi_dcmd_command(fd, MFI_DCMD_PD_REBUILD_ABORT, NULL, 0, mbox, 4,
480             NULL) < 0) {
481                 error = errno;
482                 warn("Failed to abort rebuild on drive %u", device_id);
483                 return (error);
484         }
485         close(fd);
486
487         return (0);
488 }
489 MFI_COMMAND(abort, rebuild, abort_rebuild);
490
491 static int
492 drive_progress(int ac, char **av)
493 {
494         struct mfi_pd_info info;
495         uint16_t device_id;
496         int error, fd;
497
498         if (ac != 2) {
499                 warnx("drive progress: %s", ac > 2 ? "extra arguments" :
500                     "drive required");
501                 return (EINVAL);
502         }
503
504         fd = mfi_open(mfi_unit);
505         if (fd < 0) {
506                 error = errno;
507                 warn("mfi_open");
508                 return (error);
509         }
510
511         error = mfi_lookup_drive(fd, av[1], &device_id);
512         if (error)
513                 return (error);
514
515         /* Get the info for this drive. */
516         if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) {
517                 error = errno;
518                 warn("Failed to fetch info for drive %u", device_id);
519                 return (error);
520         }
521         close(fd);
522
523         /* Display any of the active events. */
524         if (info.prog_info.active & MFI_PD_PROGRESS_REBUILD)
525                 mfi_display_progress("Rebuild", &info.prog_info.rbld);
526         if (info.prog_info.active & MFI_PD_PROGRESS_PATROL)
527                 mfi_display_progress("Patrol Read", &info.prog_info.patrol);
528         if (info.prog_info.active & MFI_PD_PROGRESS_CLEAR)
529                 mfi_display_progress("Clear", &info.prog_info.clear);
530         if ((info.prog_info.active & (MFI_PD_PROGRESS_REBUILD |
531             MFI_PD_PROGRESS_PATROL | MFI_PD_PROGRESS_CLEAR)) == 0)
532                 printf("No activity in progress for drive %u.\n", device_id);
533
534         return (0);
535 }
536 MFI_COMMAND(drive, progress, drive_progress);
537
538 static int
539 drive_clear(int ac, char **av)
540 {
541         struct mfi_pd_info info;
542         uint32_t opcode;
543         uint16_t device_id;
544         uint8_t mbox[4];
545         char *s1;
546         int error, fd;
547
548         if (ac != 3) {
549                 warnx("drive clear: %s", ac > 3 ? "extra arguments" :
550                     "drive and action requires");
551                 return (EINVAL);
552         }
553
554         for (s1 = av[2]; *s1 != '\0'; s1++)
555                 *s1 = tolower(*s1);
556         if (strcmp(av[2], "start") == 0)
557                 opcode = MFI_DCMD_PD_CLEAR_START;
558         else if ((strcmp(av[2], "stop") == 0) || (strcmp(av[2], "abort") == 0))
559                 opcode = MFI_DCMD_PD_CLEAR_ABORT;
560         else {
561                 warnx("drive clear: invalid action, must be 'start' or 'stop'\n");
562                 return (EINVAL);
563         }
564
565         fd = mfi_open(mfi_unit);
566         if (fd < 0) {
567                 error = errno;
568                 warn("mfi_open");
569                 return (error);
570         }
571
572         error = mfi_lookup_drive(fd, av[1], &device_id);
573         if (error)
574                 return (error);
575
576         /* Get the info for this drive. */
577         if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) {
578                 error = errno;
579                 warn("Failed to fetch info for drive %u", device_id);
580                 return (error);
581         }
582
583         mbox_store_pdref(&mbox[0], &info.ref);
584         if (mfi_dcmd_command(fd, opcode, NULL, 0, mbox, 4, NULL) < 0) {
585                 error = errno;
586                 warn("Failed to %s clear on drive %u",
587                     opcode == MFI_DCMD_PD_CLEAR_START ? "start" : "stop",
588                     device_id);
589                 return (error);
590         }
591
592         close(fd);
593         return (0);
594 }
595 MFI_COMMAND(drive, clear, drive_clear);
596
597 static int
598 drive_locate(int ac, char **av)
599 {
600         uint16_t device_id;
601         uint32_t opcode;
602         int error, fd;
603         uint8_t mbox[4];
604
605         if (ac != 3) {
606                 warnx("locate: %s", ac > 3 ? "extra arguments" :
607                     "drive and state required");
608                 return (EINVAL);
609         }
610
611         if (strcasecmp(av[2], "on") == 0 || strcasecmp(av[2], "start") == 0)
612                 opcode = MFI_DCMD_PD_LOCATE_START;
613         else if (strcasecmp(av[2], "off") == 0 ||
614             strcasecmp(av[2], "stop") == 0)
615                 opcode = MFI_DCMD_PD_LOCATE_STOP;
616         else {
617                 warnx("locate: invalid state %s", av[2]);
618                 return (EINVAL);
619         }
620
621         fd = mfi_open(mfi_unit);
622         if (fd < 0) {
623                 error = errno;
624                 warn("mfi_open");
625                 return (error);
626         }
627
628         error = mfi_lookup_drive(fd, av[1], &device_id);
629         if (error)
630                 return (error);
631
632
633         mbox_store_device_id(&mbox[0], device_id);
634         mbox[2] = 0;
635         mbox[3] = 0;
636         if (mfi_dcmd_command(fd, opcode, NULL, 0, mbox, 4, NULL) < 0) {
637                 error = errno;
638                 warn("Failed to %s locate on drive %u",
639                     opcode == MFI_DCMD_PD_LOCATE_START ? "start" : "stop",
640                     device_id);
641                 return (error);
642         }
643         close(fd);
644
645         return (0);
646 }
647 MFI_COMMAND(top, locate, drive_locate);