freebsd-nq/usr.sbin/mfiutil/mfi_evt.c
Scott Long 763fae7918 ntroduce mfiutil, a basic utility for managing LSI SAS-RAID & Dell PERC5/6
controllers.  Controller, array, and drive status can be checked, basic
attributes can be changed, and arrays and spares can be created and deleted.
Controller firmware can also be flashed.

This does not replace MegaCLI, found in ports, as that is officially sanctioned
and supported by LSI and includes vastly more functionality.  However, mfiutil
is open source and guaranteed to provide basic functionality, which can be
especially useful if you have a problem and can't get MegaCLI to work.

Approved by:    re
Obtained from:  Yahoo! Inc.
2009-08-13 23:18:45 +00:00

668 lines
16 KiB
C

/*-
* Copyright (c) 2008, 2009 Yahoo!, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The names of the authors may not be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#include <sys/types.h>
#include <sys/errno.h>
#include <err.h>
//#include <libutil.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <time.h>
#include <unistd.h>
#include "mfiutil.h"
static int
mfi_event_get_info(int fd, struct mfi_evt_log_state *info, uint8_t *statusp)
{
return (mfi_dcmd_command(fd, MFI_DCMD_CTRL_EVENT_GETINFO, info,
sizeof(struct mfi_evt_log_state), NULL, 0, statusp));
}
static int
mfi_get_events(int fd, struct mfi_evt_list *list, int num_events,
union mfi_evt filter, uint32_t start_seq, uint8_t *statusp)
{
uint32_t mbox[2];
size_t size;
mbox[0] = start_seq;
mbox[1] = filter.word;
size = sizeof(struct mfi_evt_list) + sizeof(struct mfi_evt_detail) *
(num_events - 1);
return (mfi_dcmd_command(fd, MFI_DCMD_CTRL_EVENT_GET, list, size,
(uint8_t *)&mbox, sizeof(mbox), statusp));
}
static int
show_logstate(int ac, char **av)
{
struct mfi_evt_log_state info;
int fd;
if (ac != 1) {
warnx("show logstate: extra arguments");
return (EINVAL);
}
fd = mfi_open(mfi_unit);
if (fd < 0) {
warn("mfi_open");
return (errno);
}
if (mfi_event_get_info(fd, &info, NULL) < 0) {
warn("Failed to get event log info");
return (errno);
}
printf("mfi%d Event Log Sequence Numbers:\n", mfi_unit);
printf(" Newest Seq #: %u\n", info.newest_seq_num);
printf(" Oldest Seq #: %u\n", info.oldest_seq_num);
printf(" Clear Seq #: %u\n", info.clear_seq_num);
printf("Shutdown Seq #: %u\n", info.shutdown_seq_num);
printf(" Boot Seq #: %u\n", info.boot_seq_num);
close(fd);
return (0);
}
MFI_COMMAND(show, logstate, show_logstate);
static int
parse_seq(struct mfi_evt_log_state *info, char *arg, uint32_t *seq)
{
char *cp;
long val;
if (strcasecmp(arg, "newest") == 0) {
*seq = info->newest_seq_num;
return (0);
}
if (strcasecmp(arg, "oldest") == 0) {
*seq = info->oldest_seq_num;
return (0);
}
if (strcasecmp(arg, "clear") == 0) {
*seq = info->clear_seq_num;
return (0);
}
if (strcasecmp(arg, "shutdown") == 0) {
*seq = info->shutdown_seq_num;
return (0);
}
if (strcasecmp(arg, "boot") == 0) {
*seq = info->boot_seq_num;
return (0);
}
val = strtol(arg, &cp, 0);
if (*cp != '\0' || val < 0) {
errno = EINVAL;
return (-1);
}
*seq = val;
return (0);
}
static int
parse_locale(char *arg, uint16_t *locale)
{
char *cp;
long val;
if (strncasecmp(arg, "vol", 3) == 0 || strcasecmp(arg, "ld") == 0) {
*locale = MFI_EVT_LOCALE_LD;
return (0);
}
if (strncasecmp(arg, "drive", 5) == 0 || strcasecmp(arg, "pd") == 0) {
*locale = MFI_EVT_LOCALE_PD;
return (0);
}
if (strncasecmp(arg, "encl", 4) == 0) {
*locale = MFI_EVT_LOCALE_ENCL;
return (0);
}
if (strncasecmp(arg, "batt", 4) == 0 ||
strncasecmp(arg, "bbu", 3) == 0) {
*locale = MFI_EVT_LOCALE_BBU;
return (0);
}
if (strcasecmp(arg, "sas") == 0) {
*locale = MFI_EVT_LOCALE_SAS;
return (0);
}
if (strcasecmp(arg, "ctrl") == 0 || strncasecmp(arg, "cont", 4) == 0) {
*locale = MFI_EVT_LOCALE_CTRL;
return (0);
}
if (strcasecmp(arg, "config") == 0) {
*locale = MFI_EVT_LOCALE_CONFIG;
return (0);
}
if (strcasecmp(arg, "cluster") == 0) {
*locale = MFI_EVT_LOCALE_CLUSTER;
return (0);
}
if (strcasecmp(arg, "all") == 0) {
*locale = MFI_EVT_LOCALE_ALL;
return (0);
}
val = strtol(arg, &cp, 0);
if (*cp != '\0' || val < 0 || val > 0xffff) {
errno = EINVAL;
return (-1);
}
*locale = val;
return (0);
}
static int
parse_class(char *arg, int8_t *class)
{
char *cp;
long val;
if (strcasecmp(arg, "debug") == 0) {
*class = MFI_EVT_CLASS_DEBUG;
return (0);
}
if (strncasecmp(arg, "prog", 4) == 0) {
*class = MFI_EVT_CLASS_PROGRESS;
return (0);
}
if (strncasecmp(arg, "info", 4) == 0) {
*class = MFI_EVT_CLASS_INFO;
return (0);
}
if (strncasecmp(arg, "warn", 4) == 0) {
*class = MFI_EVT_CLASS_WARNING;
return (0);
}
if (strncasecmp(arg, "crit", 4) == 0) {
*class = MFI_EVT_CLASS_CRITICAL;
return (0);
}
if (strcasecmp(arg, "fatal") == 0) {
*class = MFI_EVT_CLASS_FATAL;
return (0);
}
if (strcasecmp(arg, "dead") == 0) {
*class = MFI_EVT_CLASS_DEAD;
return (0);
}
val = strtol(arg, &cp, 0);
if (*cp != '\0' || val < -128 || val > 127) {
errno = EINVAL;
return (-1);
}
*class = val;
return (0);
}
/*
* The timestamp is the number of seconds since 00:00 Jan 1, 2000. If
* the bits in 24-31 are all set, then it is the number of seconds since
* boot.
*/
static const char *
format_timestamp(uint32_t timestamp)
{
static char buffer[32];
static time_t base;
time_t t;
struct tm tm;
if ((timestamp & 0xff000000) == 0xff000000) {
snprintf(buffer, sizeof(buffer), "boot + %us", timestamp &
0x00ffffff);
return (buffer);
}
if (base == 0) {
/* Compute 00:00 Jan 1, 2000 offset. */
bzero(&tm, sizeof(tm));
tm.tm_mday = 1;
tm.tm_year = (2000 - 1900);
base = mktime(&tm);
}
if (base == -1) {
snprintf(buffer, sizeof(buffer), "%us", timestamp);
return (buffer);
}
t = base + timestamp;
strftime(buffer, sizeof(buffer), "%+", localtime(&t));
return (buffer);
}
static const char *
format_locale(uint16_t locale)
{
static char buffer[8];
switch (locale) {
case MFI_EVT_LOCALE_LD:
return ("VOLUME");
case MFI_EVT_LOCALE_PD:
return ("DRIVE");
case MFI_EVT_LOCALE_ENCL:
return ("ENCL");
case MFI_EVT_LOCALE_BBU:
return ("BATTERY");
case MFI_EVT_LOCALE_SAS:
return ("SAS");
case MFI_EVT_LOCALE_CTRL:
return ("CTRL");
case MFI_EVT_LOCALE_CONFIG:
return ("CONFIG");
case MFI_EVT_LOCALE_CLUSTER:
return ("CLUSTER");
case MFI_EVT_LOCALE_ALL:
return ("ALL");
default:
snprintf(buffer, sizeof(buffer), "0x%04x", locale);
return (buffer);
}
}
static const char *
format_class(int8_t class)
{
static char buffer[6];
switch (class) {
case MFI_EVT_CLASS_DEBUG:
return ("debug");
case MFI_EVT_CLASS_PROGRESS:
return ("progress");
case MFI_EVT_CLASS_INFO:
return ("info");
case MFI_EVT_CLASS_WARNING:
return ("WARN");
case MFI_EVT_CLASS_CRITICAL:
return ("CRIT");
case MFI_EVT_CLASS_FATAL:
return ("FATAL");
case MFI_EVT_CLASS_DEAD:
return ("DEAD");
default:
snprintf(buffer, sizeof(buffer), "%d", class);
return (buffer);
}
}
/* Simulates %D from kernel printf(9). */
static void
simple_hex(void *ptr, size_t length, const char *separator)
{
unsigned char *cp;
u_int i;
if (length == 0)
return;
cp = ptr;
printf("%02x", cp[0]);
for (i = 1; i < length; i++)
printf("%s%02x", separator, cp[i]);
}
static const char *
pdrive_location(struct mfi_evt_pd *pd)
{
static char buffer[16];
if (pd->enclosure_index == 0)
snprintf(buffer, sizeof(buffer), "%02d(s%d)", pd->device_id,
pd->slot_number);
else
snprintf(buffer, sizeof(buffer), "%02d(e%d/s%d)", pd->device_id,
pd->enclosure_index, pd->slot_number);
return (buffer);
}
static const char *
volume_name(int fd, struct mfi_evt_ld *ld)
{
return (mfi_volume_name(fd, ld->target_id));
}
/* Ripped from sys/dev/mfi/mfi.c. */
static void
mfi_decode_evt(int fd, struct mfi_evt_detail *detail, int verbose)
{
printf("%5d (%s/%s/%s) - ", detail->seq, format_timestamp(detail->time),
format_locale(detail->class.members.locale),
format_class(detail->class.members.class));
switch (detail->arg_type) {
case MR_EVT_ARGS_NONE:
break;
case MR_EVT_ARGS_CDB_SENSE:
if (verbose) {
printf("PD %s CDB ",
pdrive_location(&detail->args.cdb_sense.pd)
);
simple_hex(detail->args.cdb_sense.cdb,
detail->args.cdb_sense.cdb_len, ":");
printf(" Sense ");
simple_hex(detail->args.cdb_sense.sense,
detail->args.cdb_sense.sense_len, ":");
printf(":\n ");
}
break;
case MR_EVT_ARGS_LD:
printf("VOL %s event: ", volume_name(fd, &detail->args.ld));
break;
case MR_EVT_ARGS_LD_COUNT:
printf("VOL %s", volume_name(fd, &detail->args.ld_count.ld));
if (verbose) {
printf(" count %lld: ",
(long long)detail->args.ld_count.count);
}
printf(": ");
break;
case MR_EVT_ARGS_LD_LBA:
printf("VOL %s", volume_name(fd, &detail->args.ld_count.ld));
if (verbose) {
printf(" lba %lld",
(long long)detail->args.ld_lba.lba);
}
printf(": ");
break;
case MR_EVT_ARGS_LD_OWNER:
printf("VOL %s", volume_name(fd, &detail->args.ld_count.ld));
if (verbose) {
printf(" owner changed: prior %d, new %d",
detail->args.ld_owner.pre_owner,
detail->args.ld_owner.new_owner);
}
printf(": ");
break;
case MR_EVT_ARGS_LD_LBA_PD_LBA:
printf("VOL %s", volume_name(fd, &detail->args.ld_count.ld));
if (verbose) {
printf(" lba %lld, physical drive PD %s lba %lld",
(long long)detail->args.ld_lba_pd_lba.ld_lba,
pdrive_location(&detail->args.ld_lba_pd_lba.pd),
(long long)detail->args.ld_lba_pd_lba.pd_lba);
}
printf(": ");
break;
case MR_EVT_ARGS_LD_PROG:
printf("VOL %s", volume_name(fd, &detail->args.ld_prog.ld));
if (verbose) {
printf(" progress %d%% in %ds",
detail->args.ld_prog.prog.progress/655,
detail->args.ld_prog.prog.elapsed_seconds);
}
printf(": ");
break;
case MR_EVT_ARGS_LD_STATE:
printf("VOL %s", volume_name(fd, &detail->args.ld_prog.ld));
if (verbose) {
printf(" state prior %s new %s",
mfi_ldstate(detail->args.ld_state.prev_state),
mfi_ldstate(detail->args.ld_state.new_state));
}
printf(": ");
break;
case MR_EVT_ARGS_LD_STRIP:
printf("VOL %s", volume_name(fd, &detail->args.ld_prog.ld));
if (verbose) {
printf(" strip %lld",
(long long)detail->args.ld_strip.strip);
}
printf(": ");
break;
case MR_EVT_ARGS_PD:
if (verbose) {
printf("PD %s event: ",
pdrive_location(&detail->args.pd));
}
break;
case MR_EVT_ARGS_PD_ERR:
if (verbose) {
printf("PD %s err %d: ",
pdrive_location(&detail->args.pd_err.pd),
detail->args.pd_err.err);
}
break;
case MR_EVT_ARGS_PD_LBA:
if (verbose) {
printf("PD %s lba %lld: ",
pdrive_location(&detail->args.pd_lba.pd),
(long long)detail->args.pd_lba.lba);
}
break;
case MR_EVT_ARGS_PD_LBA_LD:
if (verbose) {
printf("PD %s lba %lld VOL %s: ",
pdrive_location(&detail->args.pd_lba_ld.pd),
(long long)detail->args.pd_lba.lba,
volume_name(fd, &detail->args.pd_lba_ld.ld));
}
break;
case MR_EVT_ARGS_PD_PROG:
if (verbose) {
printf("PD %s progress %d%% seconds %ds: ",
pdrive_location(&detail->args.pd_prog.pd),
detail->args.pd_prog.prog.progress/655,
detail->args.pd_prog.prog.elapsed_seconds);
}
break;
case MR_EVT_ARGS_PD_STATE:
if (verbose) {
printf("PD %s state prior %s new %s: ",
pdrive_location(&detail->args.pd_prog.pd),
mfi_pdstate(detail->args.pd_state.prev_state),
mfi_pdstate(detail->args.pd_state.new_state));
}
break;
case MR_EVT_ARGS_PCI:
if (verbose) {
printf("PCI 0x%04x 0x%04x 0x%04x 0x%04x: ",
detail->args.pci.venderId,
detail->args.pci.deviceId,
detail->args.pci.subVenderId,
detail->args.pci.subDeviceId);
}
break;
case MR_EVT_ARGS_RATE:
if (verbose) {
printf("Rebuild rate %d: ", detail->args.rate);
}
break;
case MR_EVT_ARGS_TIME:
if (verbose) {
printf("Adapter time %s; %d seconds since power on: ",
format_timestamp(detail->args.time.rtc),
detail->args.time.elapsedSeconds);
}
break;
case MR_EVT_ARGS_ECC:
if (verbose) {
printf("Adapter ECC %x,%x: %s: ",
detail->args.ecc.ecar,
detail->args.ecc.elog,
detail->args.ecc.str);
}
break;
default:
if (verbose) {
printf("Type %d: ", detail->arg_type);
}
break;
}
printf("%s\n", detail->description);
}
static int
show_events(int ac, char **av)
{
struct mfi_evt_log_state info;
struct mfi_evt_list *list;
union mfi_evt filter;
long val;
char *cp;
ssize_t size;
uint32_t seq, start, stop;
uint8_t status;
int ch, fd, num_events, verbose;
u_int i;
fd = mfi_open(mfi_unit);
if (fd < 0) {
warn("mfi_open");
return (errno);
}
if (mfi_event_get_info(fd, &info, NULL) < 0) {
warn("Failed to get event log info");
return (errno);
}
/* Default settings. */
num_events = 15;
filter.members.reserved = 0;
filter.members.locale = MFI_EVT_LOCALE_ALL;
filter.members.class = MFI_EVT_CLASS_WARNING;
start = info.boot_seq_num;
stop = info.newest_seq_num;
verbose = 0;
/* Parse any options. */
optind = 1;
while ((ch = getopt(ac, av, "c:l:n:v")) != -1) {
switch (ch) {
case 'c':
if (parse_class(optarg, &filter.members.class) < 0) {
warn("Error parsing event class");
return (errno);
}
break;
case 'l':
if (parse_locale(optarg, &filter.members.locale) < 0) {
warn("Error parsing event locale");
return (errno);
}
break;
case 'n':
val = strtol(optarg, &cp, 0);
if (*cp != '\0' || val <= 0) {
warnx("Invalid event count");
return (EINVAL);
}
num_events = val;
break;
case 'v':
verbose = 1;
break;
case '?':
default:
return (EINVAL);
}
}
ac -= optind;
av += optind;
/* Determine buffer size and validate it. */
size = sizeof(struct mfi_evt_list) + sizeof(struct mfi_evt_detail) *
(num_events - 1);
if (size > getpagesize()) {
warnx("Event count is too high");
return (EINVAL);
}
/* Handle optional start and stop sequence numbers. */
if (ac > 2) {
warnx("show events: extra arguments");
return (EINVAL);
}
if (ac > 0 && parse_seq(&info, av[0], &start) < 0) {
warn("Error parsing starting sequence number");
return (errno);
}
if (ac > 1 && parse_seq(&info, av[1], &stop) < 0) {
warn("Error parsing ending sequence number");
return (errno);
}
list = malloc(size);
for (seq = start;;) {
if (mfi_get_events(fd, list, num_events, filter, seq,
&status) < 0) {
warn("Failed to fetch events");
return (errno);
}
if (status == MFI_STAT_NOT_FOUND) {
if (seq == start)
warnx("No matching events found");
break;
}
if (status != MFI_STAT_OK) {
warnx("Error fetching events: %s", mfi_status(status));
return (EIO);
}
for (i = 0; i < list->count; i++) {
/*
* If this event is newer than 'stop_seq' then
* break out of the loop. Note that the log
* is a circular buffer so we have to handle
* the case that our stop point is earlier in
* the buffer than our start point.
*/
if (list->event[i].seq >= stop) {
if (start <= stop)
break;
else if (list->event[i].seq < start)
break;
}
mfi_decode_evt(fd, &list->event[i], verbose);
}
/*
* XXX: If the event's seq # is the end of the buffer
* then this probably won't do the right thing. We
* need to know the size of the buffer somehow.
*/
seq = list->event[list->count - 1].seq + 1;
}
free(list);
close(fd);
return (0);
}
MFI_COMMAND(show, events, show_events);