/*
 *	Main program.
 *
 *	Copyright (c) 2007 by Jefferson Ogata
 */

/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; see the file COPYING.  If not, write to the Free Software
 * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/********************************************************************

megactl/megasasctl

Program to do the things you wish dellmgr or MegaCli could do, e.g.
report device log pages, run device self tests, report disk error
counts without having to pop in and out of countless dellmgr menus,
and actually document the adapter configuration concisely (dellmgr
gives you no way to do this).

Author: Jefferson Ogata (JO317) <ogata@antibozo.net>
Date: 2006/01/23

Version 0.4.0 major changes, including SAS support: 2007/08/20

TODO:

Other log page parsers.

Cleaner log page output.

Fixes for 64-bit systems. Currently builds only with -m32.

Fetch TTY logs.

********************************************************************/

#include	"mega.h"
#include	"megaioctl.h"
#include	"adapter.h"

#include	<stdio.h>
#include	<stdlib.h>
#include	<stdarg.h>
#include	<unistd.h>
#include	<errno.h>
#include	<string.h>
#include	<memory.h>
#include	<malloc.h>
#include	<fcntl.h>
#include	<signal.h>
#include	<ctype.h>
#include	<sys/types.h>
#include	<sys/stat.h>
#include	<sys/sysmacros.h>

#include	<scg/scsireg.h>

#include	"dumpbytes.h"

#include	"logpage.h"


#ifdef	MEGA_SAS_CTL

#define		MEGA_DEVICE	"/dev/megaraid_sas_ioctl_node"
#define		MEGA_NAME	"megaraid_sas_ioctl"

#else

#define		MEGA_DEVICE	"/dev/megadev0"
#define		MEGA_NAME	"megadev"

#define		MEGA_MIN_VERSION	0x118c

#endif	/* defined(MEGA_SAS_CTL) */


static char		*version = "0.4.3-hmage";


static int		verbosity = 0;


static char		*me;
static char		*usages[] = {
"usage: %p [-vest] [-H] [-l log-page-nr] [-T long|short] [target ...]",
"",
"Reports diagnostics on megaraid adapters and attached disks. Permits",
"dumping of controller log pages for inspection of error, temperature,",
"and self-test conditions, initiates self-test diagnostics, and documents",
"adapter and logical drive configuration. Target devices may be adapters,",
#ifdef	MEGA_SAS_CTL
"(e.g. a0), enclosures (e.g. a0e0), or individual disks (e.g. a0e0s0). If",
#else
"(e.g. a0), channels (e.g. a0c0), or individual disks (e.g. a0c0t0). If",
#endif
"no target is specified, reports configuration and drive state on all",
"adapters. If a target matches a collection of disks, operations are",
"applied to all matching devices. Options are:",
"-v       Increase program verbosity.",
"-e       Dump read (0x03), write (0x02), and verify (0x05) error log",
"         pages.",
"-s       Dump self-test (0x10) log page.",
"-t       Dump temperature (0x0d) log page.",
"-l page  Dump the specified log page. Log page 0 documents the log pages",
"         the device supports.",
"-p       Do not report physical disks. Reports only adapters and logical",
"         drives. Useful for concisely documenting adapter configuration.",
"-T test  Initiate the background short or long self-test procedure. The",
"         test may take up to an hour to complete, but does not inhibit",
"         access to the device. The test may be monitored using the -s",
"         option.",
"-H       Perform an adapter health check. Inspects state of all logical",
"         and physical drives and battery backup unit and reports problem",
"         conditions. If all is well, generates no output. Useful in a",
"         cron job.",
"-B       When performing health check, do not treat battery problems as",
"         failures.",
"-V       Show version.",
"",
"N.B. The background long self test is a useful tool for diagnosing",
"problems with individual disks. But be cautious with program usage.",
"\"%p -T long\" with no targets will initiate a background long self",
"test on every drive on every adapter. This may not be what you want.",
"",
"By default, the health check option inspects log pages 0x02, 0x03, and",
"0x05 for uncorrected read, write, and verify errors, 0x0d for excess",
"temperature conditions, and 0x10 for failed self tests. If, however, any",
"of the log page options is specified, only the designated log pages are",
"inspected.",
"",
#ifdef	MEGA_SAS_CTL
"This program requires the device file " MEGA_DEVICE " to be",
"present on the system. If your system does not have this device file,",
"you may create it either by executing LSI\'s \"MegaCli\" program once,",
"or by locating the megadev_sas_ioctl entry in /proc/devices and creating",
MEGA_DEVICE " as a character device with suitable",
"permissions with a matching major device number and a minor number of 0.",
#else
"This program requires the device file " MEGA_DEVICE " to be present on",
"the system. If your system does not have this device file, you may",
"create it either by executing Dell\'s \"dellmgr\" program once, or by",
"locating the megadev entry in /proc/devices and creating " MEGA_DEVICE,
"as a character device with suitable permissions with a matching major",
"device number and a minor number of 0.",
#endif
    0,
};


static void usage (const int ec, const char *format, ...)
{
    char		**u;
    va_list		ap;

    va_start (ap, format);
    if (format)
    {
	fprintf (stderr, "%s: ", me);
	vfprintf (stderr, format, ap);
	fprintf (stderr, "\n\n");
    }
    for (u = usages; *u; ++u)
    {   
	char            *s;
	int             esc;
	for (s = *u, esc = 0; *s; ++s)
	{   
	    if (esc)
	    {   
		switch (*s)
		{
		 case 'p':      fputs (me, stderr); break;
		 case '%':      fputc ('%', stderr); break;
		 default:       fputc ('%', stderr); fputc (*s, stderr); break;
		}
		esc = 0;
	    }
	    else
	    {   
		switch (*s)
		{
		 case '%':      esc = 1; break;
		 default:       fputc (*s, stderr); break;
		}
	    }
	}
	fputc ('\n', stderr);
    }

    exit (ec);
}


static char *friendlySize (uint64_t b, char *unit)
{
    static char		*suffix[] = { "", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi", };
    int			k;
    static char		bytes[128];

    for (k = 0; (b >= 10000) && (k < sizeof (suffix) / sizeof (suffix[0]) - 1); ++k, b /= 1024)
	;
    snprintf (bytes, sizeof bytes, "%4"PRIu64"%s%s", b, suffix[k], unit);
    return bytes;
}


static void describePhysicalDrive (FILE *f, struct physical_drive_info *d, int verbosity)
{
    char			*state;

    if (d->present)
	switch (d->state)
	{
	 case PdStateUnconfiguredGood:	state = "ready"; break;
	 case PdStateUnconfiguredBad:	state = "BAD"; break;
	 case PdStateOnline:		state = "online"; break;
	 case PdStateFailed:		state = d->span ? "FAILED" : "rdy/fail"; break;
	 case PdStateRebuild:		state = "rebuild"; break;
	 case PdStateHotspare:		state = "hotspare"; break;
	 default:			state = "???"; break;
	}
    else
	state = "absent";

    fprintf (f, "%-8s", d->name);
    if (verbosity > 0)
	fprintf (f, " %8s %-16s", d->vendor, d->model);
    if (verbosity > 1)
	fprintf (f, " rev:%-4s s/n:%-20s", d->revision, d->serial);
    fprintf (f, "  %8s", friendlySize (d->blocks << 9, "B"));
    fprintf (f, " %5s%c", d->span && d->span->num_logical_drives ? d->span->logical_drive[0]->name : "", d->span && (d->span->num_logical_drives > 1) ? '+' : ' ');
    fprintf (f, " %-8s", state);
    if (d->media_errors || d->other_errors)
	fprintf (f, " errs: media:%-2u other:%u", d->media_errors, d->other_errors);
    if (d->predictive_failures)
	fprintf (f, " predictive-failure");
    fprintf (f, "\n");
    if (d->present && d->error_string)
	fprintf (f, "\t%s\n", d->error_string);
}


static void describeLogicalDrive (FILE *f, struct logical_drive_info *l, int verbosity)
{
    char			*state;
    uint64_t			blocks;
    int				k;
    struct span_reference	*r;

    switch (l->state)
    {
     case LdStateOffline:		state = "OFFLINE"; break;
     case LdStatePartiallyDegraded:
     case LdStateDegraded:		state = "DEGRADED"; break;
     case LdStateOptimal:		state = "optimal"; break;
     case LdStateDeleted:		state = "deleted"; break;
     default:				state = "???"; break;
    }

    for (k = 0, blocks = 0; k < l->num_spans; ++k)
    {
	r = &l->span[k];
	switch (l->raid_level)
	{
	 case 0:	blocks += r->blocks_per_disk * r->span->num_disks; break;
	 case 1:	blocks += r->blocks_per_disk * r->span->num_disks / 2; break;
	 case 5:	blocks += r->blocks_per_disk * (r->span->num_disks - 1); break;
	 case 6:	blocks += r->blocks_per_disk * (r->span->num_disks - 2); break;
	}
    }

    fprintf (f, "%-8s", l->name);
    fprintf (f, " %8s", friendlySize (blocks << 9, "B"));
    fprintf (f, " RAID %u%s", l->raid_level, l->num_spans > 1 ? "0" : " ");
    fprintf (f, " %2ux%-2u", l->num_spans, l->span_size);
    fprintf (f, " %s", state);
    fprintf (f, "\n");
    if (verbosity > 0)
    {
	for (k = 0; k < l->num_spans; ++k)
	{
	    struct physical_drive_info	**p;
	    int				j;

	    r = &l->span[k];
	    fprintf (f, "      row %2d:", k);
	    for (j = 0, p = r->span->disk; j < r->span->num_disks; ++j, ++p)
	    {
		char			*flag = (*p)->state != PdStateOnline ? "*" : " ";
		fprintf (f, " %s%-8s", flag, (*p)->name);
	    }
	    fprintf (f, "\n");
	}
    }
}


static void describeBattery (FILE *f, struct adapter_config *a, int verbosity)
{
    if (a->battery.healthy)
	fprintf (f, "good");
    else
    {
	fprintf (f, "FAULT");
	if (a->battery.module_missing)
	    fprintf (f, ", module missing");
	if (a->battery.pack_missing)
	    fprintf (f, ", pack missing");
	if (a->battery.low_voltage)
	    fprintf (f, ", low voltage");
	if (a->battery.high_temperature)
	    fprintf (f, ", high temperature");
	if (a->battery.cycles_exceeded)
	    fprintf (f, ", cycles exceeded");
	if (a->battery.over_charged)
	    fprintf (f, ", over charged");
	switch (a->battery.charger_state)
	{
	 case ChargerStateComplete:		break;
	 case ChargerStateFailed:		fprintf (f, ", charge failed"); break;
	 case ChargerStateInProgress:		fprintf (f, ", charging"); break;
	 default:				fprintf (f, ", unknown charge state"); break;
	}
    }
    if (verbosity)
    {
	if (a->battery.voltage >= 0)
	    fprintf (f, "/%dmV", a->battery.voltage);
	if (a->battery.temperature >= 0)
	    fprintf (f, "/%dC", a->battery.temperature);
    }
}


static void describeAdapter (FILE *f, struct adapter_config *a, int verbosity)
{
    fprintf (f, "%-8s %-24s", a->name, a->product);
    if (verbosity > 0)
	fprintf (f, " bios:%s fw:%s", a->bios, a->firmware);
    fprintf (f, " %s:%u ldrv:%-2u", a->is_sas ? "encl" : "chan", a->num_channels, a->num_logicals);
    if (verbosity > 0)
	fprintf (f, " rbld:%u%%", a->rebuild_rate);
    if (verbosity > 1)
	fprintf (f, " mem:%uMiB", a->dram_size);
    fprintf (f, " batt:");
    describeBattery (f, a, verbosity);
    fprintf (f, "\n");
}


int main (int argc, char **argv)
{
    int				k;
    int				fd;
    uint32_t			driverVersion = 0;
    uint32_t			numAdapters;
    int				startSelfTest = -1;
    int				healthCheck = 0;
    int				checkBattery = 1;
    char			*device = MEGA_DEVICE;
    struct query_object {
	int			    adapter;
	int			    channel;
	int			    id;
    }				*object = NULL;
    int				numObjects = 0;
    uint8_t			readLog[LOG_PAGE_MAX] = { 0, };
    int				reportPhysical = 1;
    int				showVersion = 0;
    FILE			*fp;
    char			*line = NULL;
    int				major;
    size_t			len = 0;
    char			lf;
#ifdef	MEGA_SAS_CTL
    int				sas = 1;
#else
    int				sas = 0;
#endif

    if ((me = strrchr (argv[0], '/')))
	++me;
    else
	me = argv[0];

    if (argc > 1)
    {
	if ((object = (struct query_object *) malloc ((argc - 1) * sizeof (*object))) == NULL)
	{
	    perror ("malloc");
	    return 1;
	}
    }

    for (k = 1; k < argc; ++k)
    {
	if (argv[k][0] == '-')
	{
	    char		*s;

	    for (s = argv[k] + 1; *s; ++s)
	    {
		if (*s == 'v')
		{
		    ++verbosity;
		    continue;
		}
		if (*s == 'e')
		{
		    /* read error log pages */
		    readLog[0x02] = 1;	/* write errors */
		    readLog[0x03] = 1;	/* read errors */
		    readLog[0x05] = 1;	/* read errors */
		    continue;
		}
		if (*s == 's')
		{
		    /* read self test log page */
		    readLog[0x10] = 1;
		    continue;
		}
		if (*s == 't')
		{
		    /* read temperature log page */
		    readLog[0x0d] = 1;
		    continue;
		}
		if (*s == 'l')
		{
		    /* read specific log page */
		    char		*t;
		    unsigned long	u;

		    if ((++k) >= argc)
			usage (2, "no log page specified");
		    u = strtoul (argv[k], &t, 0);
		    if (*t)
			usage (2, "invalid log page \"%s\"", argv[k]);
		    if (u >= sizeof (readLog) / sizeof (readLog[0]))
			usage (2, "log page out of range: \"%s\"", argv[k]);
		    readLog[u] = 1;
		    continue;
		}
		if (*s == 'D')
		{
		    /* specify device file */
		    if ((++k) >= argc)
			usage (2, "no device specified");
		    device = argv[k];
		    continue;
		}
		if (*s == 'p')
		{
		    reportPhysical = 0;;
		    continue;
		}
		if (*s == 'B')
		{
		    /* skip battery check */
		    checkBattery = 0;
		    continue;
		}
		if (*s == 'H')
		{
		    /* perform adapter health check */
		    ++healthCheck;
		    continue;
		}
		if (*s == 'T')
		{
		    /* start self test */
		    if ((++k) >= argc)
			usage (2, "must specify short or long self-test");
		    if (!strcmp (argv[k], "short"))
			startSelfTest = SCSI_SELFTEST_BACKGROUND_SHORT;
		    else if (!strcmp (argv[k], "long"))
			startSelfTest = SCSI_SELFTEST_BACKGROUND_LONG;
		    else
			usage (2, "invalid self test: \"%s\"; must specify short or long", argv[k]);
		    continue;
		}
		else if ((*s == '?') || (*s == 'h'))
		    usage (0, NULL);
		else if (*s == 'V')
		{
		    ++showVersion;
		    continue;
		}
		usage (2, "invalid flag \"%s\"", s);
	    }
	}
	else
	{
	    char		*s;
	    char		*t;
	    unsigned long	l;

	    s = argv[k];
	    object[numObjects].adapter = -1;
	    object[numObjects].channel = -1;
	    object[numObjects].id = -1;

	    if (*s)
	    {
		if (tolower (*s) != 'a')
		    usage (2, "invalid specifier \"%s\"", argv[k]);
		++s;
		l = strtoul (s, &t, 10);
		if (s == t)
		    usage (2, "invalid specifier \"%s\"", argv[k]);
#ifndef	MEGA_SAS_CTL
		if (l >= MAX_CONTROLLERS)
		    usage (2, "adapter out of range: \"%s\"", argv[k]);
#endif
		object[numObjects].adapter = l;
		s = t;
	    }

	    if (*s)
	    {
		if (tolower (*s) != (sas ? 'e' : 'c'))
		    usage (2, "invalid specifier \"%s\"", argv[k]);
		++s;
		l = strtoul (s, &t, 10);
		if (s == t)
		    usage (2, "invalid specifier \"%s\"", argv[k]);
#ifndef	MEGA_SAS_CTL
		if (l >= MAX_MBOX_CHANNELS)
		    usage (2, "channel out of range: \"%s\"", argv[k]);
#endif
		object[numObjects].channel = l;
		s = t;
	    }

	    if (*s)
	    {
		if (tolower (*s) != (sas ? 's' : 't'))
		    usage (2, "invalid specifier \"%s\"", argv[k]);
		++s;
		l = strtoul (s, &t, 10);
		if (s == t)
		    usage (2, "invalid specifier \"%s\"", argv[k]);
#ifndef	MEGA_SAS_CTL
		if (l > MAX_MBOX_TARGET)
		    usage (2, "target out of range: \"%s\"", argv[k]);
#endif
		object[numObjects].id = l;
		s = t;
	    }

	    ++numObjects;
	}
    }

    if (showVersion)
    {
	if (verbosity)
	    fprintf (stdout, "%s: version %s by Jefferson Ogata\n", me, version);
	else
	    fprintf (stdout, "%s\n", version);
	return 0;
    }

    if (healthCheck)
    {
	int			set = 0;

	for (k = 0; k < numObjects; ++k)
	    if ((object[k].channel >= 0) || (object[k].id >= 0))
		usage (2, "for health check, must specify adapter only");
	for (k = 0; k < sizeof readLog / sizeof (readLog[0]); ++k)
	    if (readLog[k])
	    {
		set = 1;
		break;
	    }
	if (set == 0)
	{
	    /* No specific log pages requested; check read/write/verify errors and temperature. */
	    readLog[0x02] = 1;
	    readLog[0x03] = 1;
	    readLog[0x05] = 1;
	    readLog[0x0d] = 1;
	}
    }

    if ((fd = open (device, O_RDONLY)) < 0)
    {
        if ((fp = fopen ("/proc/devices", "r")) == NULL)
	{
            fprintf (stderr, "file /proc/devices access error\n");
            return 1;
        } else {
            while (getline(&line, &len, fp) != -1)
	    {
                if ((sscanf(line, "%d "MEGA_NAME"%c", &major, &lf) == 2) && (lf = 10))
	        {
                    mknod(device, S_IFCHR /*| 0666*/, makedev(major, 0));
                    free(line);
                    break;
                }
                if (line)
	        {
                    free(line);
                    line = NULL;
                }
            }
            fclose(fp);
        }

        if ((fd = open (device, O_RDONLY)) < 0)
        {
            fprintf (stderr, "unable to open device %s: %s\n", device, strerror (errno));
            return 1;
        }
    }

#ifndef	MEGA_SAS_CTL
    if (megaGetDriverVersion (fd, &driverVersion) < 0)
    {
	fprintf (stderr, "unable to determine megaraid driver version: %s\n", megaErrorString ());
	return 1;
    }

    if (driverVersion < MEGA_MIN_VERSION)
    {
	fprintf (stderr, "megaraid driver version %x too old.\n", driverVersion);
	return 1;
    }
#endif

    if (megaGetNumAdapters (fd, &numAdapters, sas) < 0)
    {
	fprintf (stderr, "unable to find any adapters: %s\n", megaErrorString ());
	return 1;
    }

    if (verbosity > 2)
	fprintf (stderr, "%u adapters, driver version %08x\n\n", numAdapters, driverVersion);

    /* Default to enumerating all adapters. */
    if (numObjects == 0)
    {
	if (object)
	    free (object);
	if ((object = (struct query_object *) malloc (numAdapters * sizeof (*object))) == NULL)
	{
	    perror ("malloc");
	    return 1;
	}
	for (k = 0; k < numAdapters; ++k)
	{
	    object[k].adapter = k;
	    object[k].channel = -1;
	    object[k].id = -1;
	}
	numObjects = k;
    }

    for (k = 0; k < numObjects; ++k)
    {
	int				adapter = object[k].adapter;
	int				channel = object[k].channel;
	int				id = object[k].id;
	char				name[32];
	struct adapter_config		*a;
	uint32_t			c;
	uint32_t			i;
	int				j;

	if (id >= 0)
	    snprintf (name, sizeof name, "a%u%c%u%c%u", adapter, sas ? 'e' : 'c', channel, sas ? 's' : 't', id);
	else if (channel >= 0)
	    snprintf (name, sizeof name, "a%u%c%u", adapter, sas ? 'e' : 'c', channel);
	else
	    snprintf (name, sizeof name, "a%u", adapter);

	if (adapter >= numAdapters)
	{
	    fprintf (stderr, "%s: no such adapter\n", name);
	    continue;
	}

	if ((a = getAdapterConfig (fd, adapter, sas)) == NULL)
	{
	    fprintf (stderr, "%s: cannot read adapter configuration: %s\n", name, megaErrorString ());
	    break;
	}

	if (healthCheck)
	{
	    int				adapterReported = 0;
	    struct logical_drive_info	*l;

	    if (checkBattery && (!a->battery.healthy))
	    {
		if (!(adapterReported++))
		    describeAdapter (stdout, a, verbosity);
	    }

#ifndef	MEGA_SAS_CTL
	    /* Scan all physical devices. */
	    for (c = 0; c < a->num_channels; ++c)
	    {
		for (i = 0; i <= MAX_MBOX_TARGET; ++i)
		{
		    uint8_t			target = (a->channel[c] << 4) | i;

		    (void) getPhysicalDriveInfo (a, target, 1);
		}
	    }
#endif

	    for (i = 0, l = a->logical; i < a->num_logicals; ++i, ++l)
	    {
		int				reportDrive = 0;

		if ((l->state != LdStateOptimal) && (l->state != LdStateDeleted))
		    ++reportDrive;

		if (reportDrive)
		{
		    if (!(adapterReported++))
			describeAdapter (stdout, a, verbosity);
		    describeLogicalDrive (stdout, l, verbosity);
		}
	    }

	    for (i = 0; i < a->num_physicals; ++i)
	    {
		struct physical_drive_info	*d = a->physical_list[i];
		int				reportDrive = 0;
		struct log_page_list		*log;

		if (d == NULL)
		    break;
		if (!(d->present))
		    continue;

//describePhysicalDrive (stdout, d, verbosity);

		/* check for drive problems */
		if ((d->state == PdStateRebuild) || (d->span && (d->state == PdStateFailed)))
		    ++reportDrive;
		if (d->media_errors)
		    ++reportDrive;
		if (d->predictive_failures)
		    ++reportDrive;

		/* check interesting log pages */
		for (j = 0; j < sizeof (readLog) / sizeof (readLog[0]); ++j)
		{
		    if (readLog[j] == 0)
			continue;

		    if ((log = getDriveLogPage (d, j)) == NULL)
			continue;

		    if (log->log.problem)
			++reportDrive;
		}

		if (reportDrive)
		{
		    if (!(adapterReported++))
			describeAdapter (stdout, a, verbosity);
		    describePhysicalDrive (stdout, d, verbosity);
		    for (j = 0; j < sizeof (readLog) / sizeof (readLog[0]); ++j)
		    {
			if (readLog[j] == 0)
			    continue;

			if ((log = getDriveLogPage (d, j)) == NULL)
			    continue;

			dumpLogPage (stdout, &log->log, NULL, 0, verbosity);
		    }
		}
	    }
	    continue;
	}

	if (channel >= 0)
	{
	    for (c = 0; c < a->num_channels; ++c)
		if (channel == a->channel[c])
		    break;
	    if (c >= a->num_channels)
	    {
		fprintf (stderr, "%s: no such channel\n", name);
		continue;
	    }
	}

	if ((channel < 0) && (id < 0))
	{
	    struct logical_drive_info	*l;
	    int				x;

	    describeAdapter (stdout, a, verbosity);

#ifndef	MEGA_SAS_CTL
	    /* Scan all physical devices. */
	    for (c = 0; c < a->num_channels; ++c)
	    {
		for (i = 0; i <= MAX_MBOX_TARGET; ++i)
		{
		    uint8_t			target = (a->channel[c] << 4) | i;

		    (void) getPhysicalDriveInfo (a, target, 1);
		}
	    }
#endif

	    for (i = 0, l = a->logical; i < a->num_logicals; ++i, ++l)
		describeLogicalDrive (stdout, l, verbosity);

	    x = 0;

	    for (i = 0; i < a->num_physicals; ++i)
	    {
		struct physical_drive_info	*d = a->physical_list[i];

		if (d == NULL)
		    break;
		if (!(d->present))
		    continue;

		if (d->state == PdStateHotspare)
		{
		    if (x == 0)
			fprintf (stdout, "hot spares  :");
		    else if ((x % 8) == 0)
			fprintf (stdout, "            :");
		    fprintf (stdout, "  %-8s", d->name);
		    if (((++x) % 8) == 0)
			fprintf (stdout, "\n");
		}
	    }
	    if (x % 8)
		fprintf (stdout, "\n");

	    x = 0;
	    for (i = 0; i < a->num_physicals; ++i)
	    {
		struct physical_drive_info	*d = a->physical_list[i];

		if (d == NULL)
		    break;
		if (!(d->present))
		    continue;

		if ((!(d->span)) && (d->state != PdStateHotspare))
		{
		    if (x == 0)
			fprintf (stdout, "unconfigured:");
		    else if ((x % 8) == 0)
			fprintf (stdout, "            :");
		    fprintf (stdout, "  %-8s", d->name);
		    if (((++x) % 8) == 0)
			fprintf (stdout, "\n");
		}
	    }
	    if (x % 8)
		fprintf (stdout, "\n");
	}

	for (c = 0; c < a->num_channels; ++c)
	{
	    if ((channel >= 0) && (channel != a->channel[c]))
		continue;

#ifndef	MEGA_SAS_CTL
	    if (id >= 0)
	    {
		uint8_t			target = (a->channel[c] << 4) | id;
		(void) getPhysicalDriveInfo (a, target, 1);
	    }
	    else
	    {
		/* Scan all devices on this channel. */
		for (i = 0; i <= MAX_MBOX_TARGET; ++i)
		{
		    uint8_t			target = (a->channel[c] << 4) | i;

		    (void) getPhysicalDriveInfo (a, target, 1);
		}
	    }
#endif

	    for (i = 0; i < a->num_physicals; ++i)
	    {
		struct physical_drive_info	*d = a->physical_list[i];

		if (d == NULL)
		    break;

		if (d->channel != a->channel[c])
		    continue;

		if ((id >= 0) && (id != d->id))
		    continue;

		if (startSelfTest >= 0)
		{
		    uint8_t		diag[256];

		    memset (diag, 0, sizeof diag);
		    if (megaScsiSendDiagnostic (&a->target, d->target, diag, sizeof diag, startSelfTest, 0, 0) < 0)
			fprintf (stderr, "self test: %s\n", megaErrorString ());
		}

		if (reportPhysical)
		{
		    describePhysicalDrive (stdout, d, verbosity);
		    for (j = 0; j < sizeof (readLog) / sizeof (readLog[0]); ++j)
		    {
			struct log_page_list	*log;

			if (readLog[j] == 0)
			    continue;

			if ((log = getDriveLogPage (d, j)) == NULL)
			    continue;

			dumpLogPage (stdout, &log->log, &log->buf, sizeof (log->buf), verbosity);
		    }
		}
	    }
	}

	fprintf (stdout, "\n");
    }

    return 0;
}


#if	0
	if (0)
	{
	    uint8_t		modes[1024];

	    memset (modes, 0, sizeof modes);
	    megaScsiModeSense (fd, adapter, target, modes, sizeof modes, 0, 0x3f, 0xff);
	}
#endif


