]> CyberLeo.Net >> Repos - FreeBSD/releng/8.1.git/blob - usr.sbin/mfiutil/mfi_drive.c
Copy stable/8 to releng/8.1 in preparation for 8.1-RC1.
[FreeBSD/releng/8.1.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         default:
69                 sprintf(buf, "PSTATE 0x%04x", state);
70                 return (buf);
71         }
72 }
73
74 int
75 mfi_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
139 bad:
140         warnx("Invalid drive number %s", drive);
141         return (EINVAL);
142 }
143
144 static void
145 mbox_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
152 void
153 mbox_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
162 int
163 mfi_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;
174 fetch:
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
193 int
194 mfi_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
204 static void
205 cam_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(). */
242 const char *
243 mfi_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. */
291 static int
292 drive_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
336 static int
337 fail_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 }
348 MFI_COMMAND(top, fail, fail_drive);
349
350 static int
351 good_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 }
362 MFI_COMMAND(top, good, good_drive);
363
364 static int
365 rebuild_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 }
376 MFI_COMMAND(top, rebuild, rebuild_drive);
377
378 static int
379 start_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 }
425 MFI_COMMAND(start, rebuild, start_rebuild);
426
427 static int
428 abort_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 }
474 MFI_COMMAND(abort, rebuild, abort_rebuild);
475
476 static int
477 drive_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 }
519 MFI_COMMAND(drive, progress, drive_progress);
520
521 static int
522 drive_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 }
575 MFI_COMMAND(drive, clear, drive_clear);
576
577 static int
578 drive_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 }
625 MFI_COMMAND(top, locate, drive_locate);