/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <ctype.h>
#include <printAttrs.h>

static SAS_STATE hbastatus_string[] = {
	HBA_STATUS_OK,				"Okay",
	HBA_STATUS_ERROR,			"Error",
	HBA_STATUS_ERROR_NOT_SUPPORTED,		"Not Supported",
	HBA_STATUS_ERROR_INVALID_HANDLE,	"Invalid Handle",
	HBA_STATUS_ERROR_ARG,			"Argument Error",
	HBA_STATUS_ERROR_ILLEGAL_WWN,		"Illegal WWN",
	HBA_STATUS_ERROR_ILLEGAL_INDEX,		"Illegal Index",
	HBA_STATUS_ERROR_MORE_DATA,		"Not Enough Buffer for Data",
	HBA_STATUS_ERROR_STALE_DATA,		"Stale Data",
	HBA_STATUS_SCSI_CHECK_CONDITION,	"SCSI Check Condition",
	HBA_STATUS_ERROR_BUSY,			"Busy",
	HBA_STATUS_ERROR_TRY_AGAIN,		"Try Again",
	HBA_STATUS_ERROR_UNAVAILABLE,		"Unavailable",
	HBA_STATUS_ERROR_ELS_REJECT,		"ELS Reject",
	HBA_STATUS_ERROR_INVALID_LUN,		"Invalid LUN",
	HBA_STATUS_ERROR_INCOMPATIBLE,		"Request Incompatible",
	HBA_STATUS_ERROR_AMBIGUOUS_WWN,		"Ambiguous WWN",
	HBA_STATUS_ERROR_LOCAL_BUS,		"Local Bus Error",
	HBA_STATUS_ERROR_LOCAL_TARGET,		"Local Target Error",
	HBA_STATUS_ERROR_LOCAL_LUN,		"Local LUN Error",
	HBA_STATUS_ERROR_LOCAL_SCSIID_BOUND,	"Local SCSIID Bound",
	HBA_STATUS_ERROR_TARGET_FCID,		"Target FCID Error",
	HBA_STATUS_ERROR_TARGET_NODE_WWN,	"Target Node WWN Error",
	HBA_STATUS_ERROR_TARGET_PORT_WWN,	"Target Port WWN Error",
	HBA_STATUS_ERROR_TARGET_LUN,		"Target LUN Error",
	HBA_STATUS_ERROR_TARGET_LUID,		"Target LUID Error",
	HBA_STATUS_ERROR_NO_SUCH_BINDING,	"No Such Binding",
	HBA_STATUS_ERROR_NOT_A_TARGET,		"Not a Target",
	HBA_STATUS_ERROR_UNSUPPORTED_FC4,	"Unsupported FC4",
	HBA_STATUS_ERROR_INCAPABLE,		"Incapable",
	HBA_STATUS_ERROR_TARGET_BUSY,		"Target Busy",
	HBA_STATUS_ERROR_NOT_LOADED,		"Not Loaded",
	HBA_STATUS_ERROR_ALREADY_LOADED,	"Alreday Loaded",
	HBA_STATUS_ERROR_ILLEGAL_FCID,		"Illegal FCID",
	HBA_STATUS_ERROR_NOT_ASCSIDEVICE,	"Not a SCSI Device",
	HBA_STATUS_ERROR_INVALID_PROTOCOL_TYPE,	"Invalid Protocol Type",
	HBA_STATUS_ERROR_BAD_EVENT_TYPE,	"Bad Event Type",
	-1,					NULL
};

SAS_STATE porttype_string[] = {
	HBA_PORTTYPE_UNKNOWN,		"UNKNOWN",
	HBA_PORTTYPE_OTHER,		"OTHER",
	HBA_PORTTYPE_NOTPRESENT,	"NOT Present",
	HBA_PORTTYPE_SASDEVICE,		"SAS Device",
	HBA_PORTTYPE_SATADEVICE,	"SATA Device",
	HBA_PORTTYPE_SASEXPANDER, 	"SAS Expander",
	-1,				NULL,
};

SAS_STATE portstate_string[] = {
	HBA_PORTSTATE_UNKNOWN,		"unknown",
	HBA_PORTSTATE_ONLINE,		"online",
	HBA_PORTSTATE_OFFLINE,		"offline",
	HBA_PORTSTATE_BYPASSED,		"bypassed",
	HBA_PORTSTATE_DIAGNOSTICS,	"diagnostics",
	HBA_PORTSTATE_LINKDOWN,		"link Down",
	HBA_PORTSTATE_ERROR,		"port Error",
	HBA_PORTSTATE_LOOPBACK,		"loopback",
	HBA_PORTSTATE_DEGRADED,		"degraded",
	-1,				NULL,
};

static SAS_STATE phystate_string[] = {
	HBA_SASSTATE_UNKNOWN,		"unknown",
	HBA_SASSTATE_DISABLED,		"disabled",
	HBA_SASSTATE_FAILED,		"failed",
	HBA_SASSTATE_SATASPINUP,	"sata-spinup",
	HBA_SASSTATE_SATAPORTSEL,	"sata-portselector",
	HBA_SASSPEED_1_5GBIT,		"1.5Gbit",
	HBA_SASSPEED_3GBIT,		"3Gbit",
	HBA_SASSPEED_6GBIT,		"6Gbit",
	-1,				NULL,
};

static SAS_STATE dtype_string[] = {
	DTYPE_DIRECT,			"Disk Device",
	DTYPE_SEQUENTIAL,		"Tape Device",
	DTYPE_PRINTER,			"Printer Device",
	DTYPE_PROCESSOR,		"Processor Device",
	DTYPE_WORM,			"WORM Device",
	DTYPE_RODIRECT,			"CD/DVD Device",
	DTYPE_SCANNER,			"Scanner Device",
	DTYPE_OPTICAL,			"Optical Memory Device",
	DTYPE_CHANGER,			"Medium Changer Device",
	DTYPE_COMM,			"Communications Device",
	DTYPE_ARRAY_CTRL,		"Storage Array Controller Device",
	DTYPE_ESI,			"Enclosure Services Device",
	DTYPE_RBC,			"Simplified Direct-access Device",
	DTYPE_OCRW,			"Optical Card Reader/Writer Device",
	DTYPE_BCC,			"Bridge Controller Commands",
	DTYPE_OSD,			"Object-based Storage Device",
	DTYPE_ADC,			"Automation/Drive Interface",
	DTYPE_WELLKNOWN,		"Well Known Logical Unit",
	DTYPE_UNKNOWN,			"Unknown Device",
	-1,				NULL
};

static char *getPhyStateString(HBA_UINT32 key, phystat_type phyt);

char *
getIndentSpaces(int number)
{
	int 		i = 0;
	/* the maximum indent with terminator '\0' */
	static char	ret[MAXINDENT+1];

	if (number > MAXINDENT)
		number = MAXINDENT;

	for (i = 0; i < number; i++) {
		ret[i] = ' ';
	}
	ret[i] = '\0';
	return (ret);
}

char *
getStateString(HBA_UINT32 key, SAS_STATE *stat_string)
{
	static char ret[64];
	while (stat_string->key != -1) {
		if (stat_string->key == key) {
			return ((char *)stat_string->value);
		}
		stat_string++;
	}
	(void *) sprintf(ret, "Undefined value (%d)", key);
	return (ret);
}

static char *
getPhyStateString(HBA_UINT32 key, phystat_type phyt)
{
	int i = 0, len = 0, match = 0;
	HBA_UINT32 physpeed[] = {
		HBA_SASSPEED_1_5GBIT,
		HBA_SASSPEED_3GBIT,
		HBA_SASSPEED_6GBIT
	};

	len = sizeof (physpeed) / sizeof (HBA_UINT32);
	for (i = 0; i < len; i++) {
		if (key == physpeed[i]) {
			match = 1;
			break;
		}
	}

	if (match == 1) {
		if (phyt == PHY_STATE)
			return ("enabled");
		else
			return (getStateString(key, phystate_string));
	} else {
		if (phyt == PHY_STATE)
			return (getStateString(key, phystate_string));
		else
			return ("not available");
	}
}

char *
getHBAStatus(HBA_STATUS key)
{
	return (getStateString(key, hbastatus_string));
}

/*
 * return device type description
 *
 * Arguments:
 *	dType - Device type returned from Standard INQUIRY
 * Returns:
 *	char string description for device type
 */
char *
getDTypeString(uchar_t dType)
{
	return (getStateString((dType & DTYPE_MASK), dtype_string));
}

uint64_t
wwnConversion(uchar_t *wwn)
{
	uint64_t tmp;
	(void *) memcpy(&tmp, wwn, sizeof (uint64_t));
	return (ntohll(tmp));
}

/*
 * prints out HBA information
 */
void
printHBAInfo(SMHBA_ADAPTERATTRIBUTES *attrs, int pflag, int numberOfPorts,
    const char *adapterName)
{

	(void *) fprintf(stdout, "%s %s\n", "HBA Name:", adapterName);

	if (pflag & PRINT_VERBOSE) {
		(void *) fprintf(stdout, "%s%s %s\n",
		    getIndentSpaces(4), "Manufacturer:",
		    attrs->Manufacturer[0] == 0?
		    "not available":attrs->Manufacturer);
		(void *) fprintf(stdout, "%s%s %s\n",
		    getIndentSpaces(4), "Model: ",
		    attrs->Model[0] == 0? "not available":attrs->Model);
		(void *) fprintf(stdout, "%s%s %s\n",
		    getIndentSpaces(4),
		    "Firmware Version:",
		    attrs->FirmwareVersion[0] == 0? "not available":
		    attrs->FirmwareVersion);
		(void *) fprintf(stdout, "%s%s %s\n",
		    getIndentSpaces(4),
		    "FCode/BIOS Version:",
		    attrs->OptionROMVersion[0] == 0? "not available":
		    attrs->OptionROMVersion);
		(void *) fprintf(stdout, "%s%s %s\n",
		    getIndentSpaces(4),
		    "Serial Number:",
		    attrs->SerialNumber[0] == 0? "not available":
		    attrs->SerialNumber);
		(void *) fprintf(stdout, "%s%s %s\n",
		    getIndentSpaces(4),
		    "Driver Name:",
		    attrs->DriverName[0] == 0? "not available":
		    attrs->DriverName);
		(void *) fprintf(stdout, "%s%s %s\n",
		    getIndentSpaces(4),
		    "Driver Version:",
		    attrs->DriverVersion[0] == 0? "not available":
		    attrs->DriverVersion);
		(void *) fprintf(stdout, "%s%s %d\n",
		    getIndentSpaces(4),
		    "Number of HBA Ports:",
		    numberOfPorts);
	}
}

/*
 * prints out all the HBA port information
 */
void
printHBAPortInfo(SMHBA_PORTATTRIBUTES *port,
    SMHBA_ADAPTERATTRIBUTES *attrs, int pflag) {

	if ((port == NULL) || (attrs == NULL)) {
		return;
	}

	(void *) fprintf(stdout, "%s%s %s\n",
	    getIndentSpaces(2),
	    "HBA Port Name:",
	    port->OSDeviceName);

	if (!(pflag & PRINT_VERBOSE)) {
		return;
	}

	if (port->PortType != HBA_PORTTYPE_SASDEVICE)
		return;

	(void *) fprintf(stdout, "%s%s %s\n",
	    getIndentSpaces(4),
	    "Type:",
	    getStateString(port->PortType, porttype_string));
	(void *) fprintf(stdout, "%s%s %s\n",
	    getIndentSpaces(4),
	    "State:",
	    getStateString(port->PortState, portstate_string));

	(void *) fprintf(stdout, "%s%s %016llx\n",
	    getIndentSpaces(4),
	    "Local SAS Address:",
	    wwnConversion(port->PortSpecificAttribute.SASPort->\
	    LocalSASAddress.wwn));

	(void *) fprintf(stdout, "%s%s %016llx\n",
	    getIndentSpaces(4),
	    "Attached SAS Address:",
	    wwnConversion(port->PortSpecificAttribute.SASPort->\
	    AttachedSASAddress.wwn));

	(void *) fprintf(stdout, "%s%s %d\n",
	    getIndentSpaces(4),
	    "Number of Phys:",
	    port->PortSpecificAttribute.SASPort->NumberofPhys);
}

void
printHBAPortPhyInfo(SMHBA_SAS_PHY *phyinfo)
{
	if (phyinfo == NULL)
		return;

	(void *) fprintf(stdout, "%s%s %u\n",
	    getIndentSpaces(6),
	    "Identifier:",
	    phyinfo->PhyIdentifier);

	(void *) fprintf(stdout, "%s%s %s\n",
	    getIndentSpaces(8),
	    "State: ",
	    getPhyStateString(phyinfo->NegotiatedLinkRate, PHY_STATE));
	(void *) fprintf(stdout, "%s%s %s/%s\n",
	    getIndentSpaces(8),
	    "HardwareLinkRate(Min/Max):",
	    getPhyStateString(phyinfo->HardwareMinLinkRate, PHY_SPEED),
	    getPhyStateString(phyinfo->HardwareMaxLinkRate, PHY_SPEED));
	(void *) fprintf(stdout, "%s%s %s/%s\n",
	    getIndentSpaces(8),
	    "ProgrammedLinkRate(Min/Max):",
	    getPhyStateString(phyinfo->ProgrammedMinLinkRate, PHY_SPEED),
	    getPhyStateString(phyinfo->ProgrammedMaxLinkRate, PHY_SPEED));
	(void *) fprintf(stdout, "%s%s %s\n",
	    getIndentSpaces(8),
	    "NegotiatedLinkRate:",
	    getPhyStateString(phyinfo->NegotiatedLinkRate, PHY_SPEED));
}

void
printHBAPortPhyStatistics(SMHBA_SASPHYSTATISTICS *phystat)
{
	if (phystat == NULL)
		return;

	(void *) fprintf(stdout, "%s%s\n",
	    getIndentSpaces(8),
	    "Link Error Statistics:");
	(void *) fprintf(stdout, "%s%s %llu\n",
	    getIndentSpaces(12),
	    "Invalid Dword:",
	    phystat->InvalidDwordCount);
	(void *) fprintf(stdout, "%s%s %llu\n",
	    getIndentSpaces(12),
	    "Running Disparity Error:",
	    phystat->RunningDisparityErrorCount);
	(void *) fprintf(stdout, "%s%s %llu\n",
	    getIndentSpaces(12),
	    "Loss of Dword Sync:",
	    phystat->LossofDwordSyncCount);
	(void *) fprintf(stdout, "%s%s %llu\n",
	    getIndentSpaces(12),
	    "Reset Problem:",
	    phystat->PhyResetProblemCount);
}

/*
 * print the OS device name for the logical-unit object
 *
 * Arguments:
 *	devListWalk - OS device path info
 *	verbose - boolean indicating whether to display additional info
 *
 * returns:
 * 	0 - we're good.
 * 	>0 - we met issues.
 */
int
printTargetPortInfo(targetPortList_t *TPListWalk, int pflag)
{
	targetPortConfig_t	*configList;
	targetPortMappingData_t	*mapList;
	int			count, i;
	int			ret = 0;

	(void *) fprintf(stdout, "Target Port SAS Address: %016llx\n",
	    wwnConversion(TPListWalk->sasattr.LocalSASAddress.wwn));
	if ((pflag & PRINT_VERBOSE) || (pflag & PRINT_TARGET_SCSI)) {
		(void *) fprintf(stdout, "%sType: %s\n", getIndentSpaces(4),
		    getStateString(TPListWalk->targetattr.PortType,
		    porttype_string));
		for (configList = TPListWalk->configEntry;
		    configList != NULL; configList = configList->next) {
			(void *) fprintf(stdout, "%sHBA Port Name: %s\n",
			    getIndentSpaces(4), configList->hbaPortName);
			if (wwnConversion(configList->expanderSASAddr.wwn) !=
			    0) {
				if (configList->expanderValid) {
					(void *) fprintf(stdout,
					    "%sExpander Device SAS Address:"
					    " %016llx",
					    getIndentSpaces(8),
					    wwnConversion(configList->
					    expanderSASAddr.wwn));
				} else {
					(void *) fprintf(stdout,
					    "%sExpander Device SAS Address:"
					    " %016llx (Failed to Validate"
					    " Attached Port.)",
					    getIndentSpaces(8),
					    wwnConversion(configList->
					    expanderSASAddr.wwn));
					ret++;
				}
			} else {
				if (configList->expanderValid) {
					(void *) fprintf(stdout,
					    "%sExpander Device SAS Address: %s",
					    getIndentSpaces(8),
					    "None (direct attached)");
				} else {
					(void *) fprintf(stdout,
					    "%sExpander Device SAS Address: %s",
					    getIndentSpaces(8),
					    "None (Failed to Get"
					    " Attached Port)");
				}
			}
			(void *) fprintf(stdout, "\n");
			if (pflag & PRINT_TARGET_SCSI) {

				if (configList->reportLUNsFailed) {
					(void *) fprintf(stdout,
					    "%s %016llx\n",
					    gettext("Error: Failed to get "
					    "ReportLun Data on"),
					    wwnConversion(TPListWalk->
					    sasattr.LocalSASAddress.wwn));
					ret++;
					continue;
				}

				for (mapList = configList->map;
				    mapList != NULL; mapList = mapList->next) {
					(void *) fprintf(stdout, "%sLUN : %d\n",
					    getIndentSpaces(12),
					    mapList->osLUN);
					if (mapList->mappingExist) {
						(void *) fprintf(stdout,
						    "%sOS Device Name : %s\n",
						    getIndentSpaces(14),
						    (mapList->osDeviceName[0] ==
						    '\0') ?  "Not avaialble" :
						    mapList->osDeviceName);
					} else {
						(void *) fprintf(stdout,
						    "%sOS Device Name : %s\n",
						    getIndentSpaces(14), "No "
						    "matching OS Device "
						    "found.");
						ret++;
					}
		/* indentation changed here */
		if (mapList->inquiryFailed) {
			(void *) fprintf(stdout, "%s %s LUN %d\n",
			    gettext("Error: Failed to get Inquiry Data on"),
			    mapList->osDeviceName, mapList->osLUN);
			ret++;
		} else {
			(void *) fprintf(stdout, "%sVendor: ",
			    getIndentSpaces(14));
			for (count = sizeof (mapList->inq_vid), i = 0;
			    i < count; i++) {
				if (isprint(mapList->inq_vid[i]))
					(void *) fprintf(stdout, "%c",
					    mapList->inq_vid[i]);
			}

			(void *) fprintf(stdout, "\n%sProduct: ",
			    getIndentSpaces(14));
			for (count = sizeof (mapList->inq_pid), i = 0;
			    i < count; i++) {
				if (isprint(mapList->inq_pid[i]))
					(void *) fprintf(stdout, "%c",
					    mapList->inq_pid[i]);
			}

			(void *) fprintf(stdout, "\n%sDevice Type: %s\n",
			    getIndentSpaces(14),
			    getDTypeString(mapList->inq_dtype));
		}
		/* indentation changed back */
				}
			}
		}
	}
	return (ret);
}

/*
 * print the OS device name for the logical-unit object
 *
 * Arguments:
 *	devListWalk - OS device path info
 *	verbose - boolean indicating whether to display additional info
 *
 * returns:
 * 	0 - we're good.
 * 	>0 - we met issues.
 */
int
printOSDeviceNameInfo(discoveredDevice *devListWalk, boolean_t verbose)
{
	portList		*portElem;
	tgtPortWWNList		*tgtWWNList;
	int			i, count;
	int			ret = 0;

	(void *) fprintf(stdout, "OS Device Name: %s\n",
	    devListWalk->OSDeviceName);
	if (verbose == B_TRUE) {
		for (portElem = devListWalk->HBAPortList;
		    portElem != NULL; portElem = portElem->next) {
			(void *) fprintf(stdout, "%sHBA Port Name: ",
			    getIndentSpaces(4));
			(void *) fprintf(stdout, "%s", portElem->portName);
			for (tgtWWNList = portElem->tgtPortWWN;
			    tgtWWNList != NULL; tgtWWNList = tgtWWNList->next) {
				(void *) fprintf(stdout,
				    "\n%sTarget Port SAS Address: ",
				    getIndentSpaces(8));
				(void *) fprintf(stdout, "%016llx",
				    wwnConversion(tgtWWNList->portWWN.wwn));
				(void *) fprintf(stdout, "\n%sLUN: %u",
				    getIndentSpaces(12),
				    tgtWWNList->scsiOSLun);
			}
			(void *) fprintf(stdout, "\n");
		}

		if (devListWalk->inquiryFailed) {
			(void *) fprintf(stdout, "%s %s\n",
			    gettext("Error: Failed to get Inquiry data "
			    "on device"), devListWalk->OSDeviceName);
			ret++;
		} else {
			(void *) fprintf(stdout, "%sVendor: ",
			    getIndentSpaces(4));
			for (count = sizeof (devListWalk->VID), i = 0;
			    i < count; i++) {
				if (isprint(devListWalk->VID[i]))
					(void *) fprintf(stdout, "%c",
					    devListWalk->VID[i]);
			}

			(void *) fprintf(stdout, "\n%sProduct: ",
			    getIndentSpaces(4));
			for (count = sizeof (devListWalk->PID), i = 0;
			    i < count; i++) {
				if (isprint(devListWalk->PID[i]))
					(void *) fprintf(stdout, "%c",
					    devListWalk->PID[i]);
			}

			(void *) fprintf(stdout, "\n%sDevice Type: %s\n",
			    getIndentSpaces(4),
			    getDTypeString(devListWalk->dType));
		}
	}
	return (ret);
}