/*
 * Copyright(c) 2010 Intel Corporation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope 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; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Maintained at www.Open-FCoE.org
 */

/*
 * FCPing - FC fabric diagnostic.
 */
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdarg.h>
#include <unistd.h>
#include <inttypes.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <fcntl.h>
#include <malloc.h>
#include <limits.h>
#include <signal.h>
#include <libgen.h>
#include <assert.h>
#include <syslog.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <net/ethernet.h>
#include <netinet/ether.h>
#include <linux/types.h>
#include <linux/bsg.h>
#include "net_types.h"
#include "fc_types.h"
#include "fcoe_utils.h"
typedef uint8_t u8;
#include <scsi/sg.h>
#include "fc_ns.h"
#include "fc_gs.h"
#include "fc_els.h"
#include "scsi_bsg_fc.h"

#include "sysfs_hba.h"

static const char *cmdname;

#define FC_MAX_PAYLOAD  2112UL	/* Max FC payload */
#define MAX_SENSE_LEN	96	/* SCSI_SENSE_BUFFERSIZE */
#define MAX_HBA_COUNT	128
/* FC ELS ECHO Command takes 4 bytes */
#define FP_LEN_ECHO	sizeof(net32_t)
/* default max ping data length, excluding 4 bytes of ELS ECHO command */
#define FP_LEN_MAX	(FC_MAX_PAYLOAD - FP_LEN_ECHO)
#define FP_LEN_MIN	4	/* fcping needs 4 bytes as sequence number */
#define FP_LEN_DEF	32	/* default ping payload length */
#define FP_LEN_PAD	32	/* extra length for response */
#define FP_MIN_INTVL	0.001	/* minimum interval in seconds */
#define FP_DEF_INTVL	1.000	/* default sending interval in seconds */
#define SYSFS_HBA_DIR   "/sys/class/net"

/* Check if it is WKA accoriding to FC-FS-3 Rev 1.00 Clause 11 Table 30 */
#define FCID_IS_WKA(i) ((((i) >= 0xfffc01) && ((i) <= 0xfffcfe)) || \
			(((i) >= 0xfffff0) && ((i) <= 0xffffff)))

#define FC_WKA_FABRIC_CONTROLLER ((fc_fid_t)0xfffffd)
#define FC_WKA_DIRECTORY_SERVICE ((fc_fid_t)0xfffffc)

static void fp_usage(void)
{
	fprintf(stderr,
		"Usage: %s [ -fqx ] [ -i <interval> ] [ -c <count> ] -h <hba> "
		"[ -s <size> ] { -F <FC-ID> | -P <WWPN> | -N <WWNN> }\n"
		"  flags:\n"
		"     -f:            Flood ping\n"
		"     -q:            Quiet! just print summary\n"
		"     -x:            Hex dump of responses\n"
		"     -i <interval>: Wait <interval> seconds between each ping\n"
		"     -c <count>:    Stop after sending <count> pings\n"
		"     -h <hba>:      eth<n>, MAC address, WWPN, or FC-ID of the HBA\n"
		"     -s <size>:     Byte-length of ping request payload (max %lu)\n"
		"     -F <FC-ID>:    Destination port ID\n"
		"     -P <WWPN>:     Destination world-wide port name\n"
		"     -N <WWNN>:     Destination world-wide node name\n",
		cmdname, FP_LEN_MAX);

	fprintf(stderr, "\nNote that the default maximum FC payload allowed "
		"is %lu bytes and the default maximum fcping payload, "
		"i.e., the FC ELS ECHO data, allowed is %lu "
		"bytes.\n",
		FC_MAX_PAYLOAD, FP_LEN_MAX);

	exit(1);
}

static fc_fid_t fp_did;
static fc_wwn_t fp_port_wwn;
static fc_wwn_t fp_node_wwn;
static int fp_count = -1;	/* send indefinitely by default */
static uint32_t fp_len = FP_LEN_DEF + FP_LEN_ECHO;
static int fp_flood;			/* send as fast as possible */
static uint32_t fp_interval = FP_DEF_INTVL * 1000; /* in milliseconds */
static int fp_quiet;
static int fp_hex;
static char *fp_hba;	/* name of interface to be used */
static char fp_dev[64];
static int fp_fd;	/* file descriptor for openfc ioctls */
static void *fp_buf;	/* sending buffer */
static int fp_debug;
static char *host;
static struct port_attributes *port_attrs;

struct fp_stats {
	uint32_t fp_tx_frames;
	uint32_t fp_rx_frames;
	uint32_t fp_rx_errors;
	uint64_t fp_transit_time_us; /* total transit time in microseconds */
	uint32_t fp_rx_times;        /* valid times on receive */
};
static struct fp_stats fp_stats;

#define hton24(p, v)				\
	do {					\
		p[0] = (((v) >> 16) & 0xFF);	\
		p[1] = (((v) >> 8) & 0xFF);	\
		p[2] = ((v) & 0xFF);		\
	} while (0)

#define hton64(p, v)					\
	do {						\
		p[0] = (u_char) ((v) >> 56) & 0xFF;	\
		p[1] = (u_char) ((v) >> 48) & 0xFF;	\
		p[2] = (u_char) ((v) >> 40) & 0xFF;	\
		p[3] = (u_char) ((v) >> 32) & 0xFF;	\
		p[4] = (u_char) ((v) >> 24) & 0xFF;	\
		p[5] = (u_char) ((v) >> 16) & 0xFF;	\
		p[6] = (u_char) ((v) >> 8) & 0xFF;	\
		p[7] = (u_char) (v) & 0xFF;		\
	} while (0)

__attribute__((__format__(__printf__, 2, 3)))
static void sa_log_func(const char *func, const char *format, ...);
__attribute__((__format__(__printf__, 3, 4)))
static void sa_log_err(int, const char *func, const char *format, ...);
static void sa_log_output(const char *buf);

/*
 * Log message.
 */
#define SA_LOG(...)						\
	do { sa_log_func(__func__, __VA_ARGS__); } while (0)

#define SA_LOG_ERR(error, ...)					\
	do { sa_log_err(error, NULL, __VA_ARGS__); } while (0)

/*
 * Logging exits.
 */
#define SA_LOG_EXIT(...)						\
	do {	sa_log_func(__func__, __VA_ARGS__);			\
		if (fp_debug)						\
			sa_log_func(__func__,				\
				    "Exiting at %s:%d", __FILE__, __LINE__); \
		exit(1);						\
	} while (0)

#define SA_LOG_ERR_EXIT(error, ...)					\
	do {	sa_log_func(__func__, __VA_ARGS__);			\
		if (fp_debug)						\
			sa_log_err(error, __func__,			\
				   "Exiting at %s:%d", __FILE__, __LINE__); \
		else							\
			sa_log_err(error, NULL, NULL);			\
		exit(1);						\
	} while (0)

#define SA_LOG_BUF_LEN  200     /* on-stack line buffer size */

/*
 * log with a variable argument list.
 */
static void
sa_log_va(const char *func, const char *format, va_list arg)
{
	size_t len;
	size_t flen;
	int add_newline;
	char sa_buf[SA_LOG_BUF_LEN];
	char *bp;

	/*
	 * If the caller didn't provide a newline at the end, we will.
	 */
	len = strlen(format);
	add_newline = 0;
	if (!len || format[len - 1] != '\n')
		add_newline = 1;
	bp = sa_buf;
	len = sizeof(sa_buf);
	if (func) {
		flen = snprintf(bp, len, "%s: ", func);
		len -= flen;
		bp += flen;
	}
	flen = vsnprintf(bp, len, format, arg);
	if (add_newline && flen < len) {
		bp += flen;
		*bp++ = '\n';
		*bp = '\0';
	}
	sa_log_output(sa_buf);
}

/*
 * log with function name.
 */
static void
sa_log_func(const char *func, const char *format, ...)
{
	va_list arg;

	va_start(arg, format);
	if (fp_debug)
		sa_log_va(func, format, arg);
	else
		sa_log_va(NULL, format, arg);
	va_end(arg);
}

/*
 * log with error number.
 */
static void
sa_log_err(int error, const char *func, const char *format, ...)
{
	va_list arg;
	char buf[SA_LOG_BUF_LEN];

	strerror_r(error, buf, sizeof(buf));
	sa_log_func(func, "errno=%d %s", error, buf);
	if (format) {
		va_start(arg, format);
		sa_log_va(func, format, arg);
		va_end(arg);
	}
}

static void
sa_log_output(const char *buf)
{
	fprintf(stderr, "%s", buf);
	fflush(stderr);
}

static char *
sa_hex_format(char *buf, size_t buflen,
	      const unsigned char *data, size_t data_len,
	      unsigned int group_len, char *inter_group_sep)
{
	size_t rlen, tlen;
	char *bp, *sep;
	unsigned int i;

	rlen = buflen;
	bp = buf;
	sep = "";
	for (i = 0; rlen > 0 && i < data_len; ) {
		tlen = snprintf(bp, rlen, "%s%2.2x", sep, data[i]);
		rlen -= tlen;
		bp += tlen;
		i++;
		sep = (i % group_len) ? "" : inter_group_sep;
	}
	return buf;
}

/*
 * Hex dump buffer to file.
 */
static void sa_hex_dump(unsigned char *bp, size_t len, FILE *fp)
{
	char lbuf[120];
	size_t tlen;
	uint32_t offset = 0;

	while (len > 0) {
		tlen = 16;  /* bytes per line */
		if (tlen > len)
			tlen = len;
		sa_hex_format(lbuf, sizeof(lbuf), bp, tlen, 4, " ");
		fprintf(fp, "%6x %s\n", offset, lbuf);
		offset += tlen;
		len -= tlen;
		bp += tlen;
	}
}

/*
 * Convert 48-bit IEEE MAC address to 64-bit FC WWN.
 */
fc_wwn_t
fc_wwn_from_mac(uint64_t mac, uint32_t scheme, uint32_t port)
{
	fc_wwn_t wwn;

	assert(mac < (1ULL << 48));
	wwn = mac | ((fc_wwn_t) scheme << 60);
	switch (scheme) {
	case 1:
		assert(port == 0);
		break;
	case 2:
		assert(port < 0xfff);
		wwn |= (fc_wwn_t) port << 48;
		break;
	default:
		assert(1);
		break;
	}
	return wwn;
}

/*
 * Handle WWN/MAC arguments
 */
static fc_wwn_t
fp_parse_wwn(const char *arg, char *msg, uint32_t scheme, uint32_t port)
{
	char *endptr;
	fc_wwn_t wwn;
	fc_wwn_t oui;
	struct ether_addr mac;

	wwn = strtoull(arg, &endptr, 16);
	if (*endptr != '\0') {
		if (ether_aton_r(arg, &mac) == NULL &&
		    ether_hostton(arg, &mac) != 0) {
			SA_LOG_EXIT("invalid %s WWN or MAC addr %s", msg, arg);
		}
		oui = net48_get((net48_t *)mac.ether_addr_octet);
		wwn = fc_wwn_from_mac(oui, scheme, port);
	}
	return wwn;
}

/*
 * Handle options.
 */
static void
fp_options(int argc, char *argv[])
{
	int opt;
	char *endptr;
	float sec;
	int targ_spec = 0;

	cmdname = basename(argv[0]);
	if (argc <= 1)
		fp_usage();

	while ((opt = getopt(argc, argv, "?c:fi:h:qs:xF:P:N:")) != -1) {
		switch (opt) {
		case 'c':
			fp_count = (int) strtoul(optarg, &endptr, 10);
			if (*endptr != '\0')
				SA_LOG_EXIT("bad count %s\n", optarg);
			break;
		case 'f':
			fp_flood = 1;
			break;
		case 'i':
			if (sscanf(optarg, "%f", &sec) != 1 ||
			    sec < FP_MIN_INTVL)
				SA_LOG_EXIT("bad interval %s\n", optarg);
			fp_interval = sec * 1000;
			break;
		case 'h':
			fp_hba = optarg;
			break;
		case 'q':
			fp_quiet = 1;
			break;
		case 's':
			/* maximum ECHO data allowed is 2108 */
			fp_len = strtoul(optarg, &endptr, 0);
			if (*endptr != '\0' || fp_len > FP_LEN_MAX)
				SA_LOG_EXIT("Bad size %s for FC ELS ECHO "
					    "data, max %lu bytes allowed.\n",
					    optarg, FP_LEN_MAX);
			if (fp_len < FP_LEN_MIN)
				SA_LOG_EXIT("Bad size %s for FC ELS ECHO "
					    "data, min %d bytes allowed.\n",
					    optarg, FP_LEN_MIN);
			/* add 4 bytes for the ECHO command */
			fp_len += FP_LEN_ECHO;
			break;
		case 'x':
			fp_hex = 1;
			break;

			/*
			 * -F specifies the target FC_ID.
			 */
		case 'F':
			fp_did = strtoull(optarg, &endptr, 16);
			if (*endptr != '\0')
				SA_LOG_EXIT("bad target FC_ID %s\n", optarg);
			targ_spec++;
			break;

			/*
			 * The -P and -N flags take a world-wide name
			 * in hex, or an ethernet addr, or an etherhost
			 * entry from /etc/ethers.
			 */
		case 'N':
			fp_node_wwn = fp_parse_wwn(optarg, "Node", 1, 0);
			targ_spec++;
			break;

		case 'P':
			fp_port_wwn = fp_parse_wwn(optarg, "Port", 2, 0);
			targ_spec++;
			break;

		case '?':
		default:
			fp_usage();	/* exits */
			break;
		}
	}
	argc -= optind;
	argv += optind;

	if (fp_hba == NULL)
		SA_LOG_EXIT("FCoE interface not specified");

	if (targ_spec == 0)
		SA_LOG_EXIT("no target specified");

	if (targ_spec > 1)
		SA_LOG_EXIT("too many targets specified;"
			    " only one is allowed.");

	return;
}

/*
 * Lookup specified adapter using HBAAPI.
 */
static int
fp_find_hba(void)
{
	struct stat statbuf;
	char hba_dir[256];
	fc_wwn_t wwn = 0;
	struct hba_wwn wwpn;
	char *endptr;
	uint32_t fcid;
	int hba_cnt;

	hba_cnt = get_number_of_adapters();
	if (!hba_cnt)
		SA_LOG_EXIT("No FCoE interfaces created");

	/*
	 * Parse HBA spec. if there is one.
	 * These formats are tried:
	 *    If pass in an interface name, it does not need
	 *    to be validated here. The interface name can be
	 *    anything. It will have to be found via HBAAPI
	 *    library. It fails if not found.
	 *    host<n> = match the index <n>.
	 *    mac address xx:xx:xx:xx:xx:xx
	 *    otherwise, try parsing as a wwn and match that.
	 */
	snprintf(hba_dir, sizeof(hba_dir), SYSFS_HBA_DIR "/%s", fp_hba);
	if (!stat(hba_dir, &statbuf)) {
		host = get_host_from_netdev(fp_hba);
	} else if (strstr(fp_hba, "host") == fp_hba) {
		(void) strtoul(fp_hba + strlen("host"), &endptr, 10);
		if (*endptr != '\0')
			SA_LOG_EXIT("invalid hba name %s", fp_hba);
		host = strdup(fp_hba);
	} else if (strstr(fp_hba, ":")) {
		if (strlen(fp_hba) == strlen("xx:yy:aa:bb:cc:dd:ee:ff")) {
			fc_wwn_t wwn1;

			wwn1 = fp_parse_wwn(fp_hba, "HBA", 2, 0);
			wwn1 &= 0xffff000000000000;
			wwn = fp_parse_wwn(&fp_hba[6], "HBA", 2, 0);
			wwn &= 0x0000ffffffffffff;
			wwn |= wwn1;
		} else if (strlen(fp_hba) == strlen("aa:bb:cc:dd:ee:ff")) {
			wwn = fp_parse_wwn(fp_hba, "HBA", 2, 0);
		} else {
			SA_LOG_EXIT("invalid WWPN or MAC address %s", fp_hba);
		}
		hton64(wwpn.wwn, wwn);
		host = get_host_by_wwpn(wwpn);
	} else {
		wwn = strtoull(fp_hba, &endptr, 16);
		if (wwn < 0x1000000) {
			fcid = wwn;
			host = get_host_by_fcid(fcid);
		} else {
			if (*endptr != '\0')
				SA_LOG_EXIT("unsupported hba name");
			wwn = fp_parse_wwn(fp_hba, "HBA", 2, 0);
			hton64(wwpn.wwn, wwn);
			host = get_host_by_wwpn(wwpn);
		}
	}

	if (!host) {
		SA_LOG("FCoE interface %s not found", fp_hba);
		return 0;
	}

	snprintf(fp_dev, sizeof(fp_dev), "fc_%s", host);

	port_attrs = get_port_attribs(host);
	if (!port_attrs) {
		free(host);
		return 0;
	}

	return 1;
}

static void
fp_report(void)
{
	double loss;
	struct fp_stats *sp = &fp_stats;

	loss = 100.0 * (sp->fp_tx_frames - sp->fp_rx_frames) / sp->fp_tx_frames;
	printf("%d frames sent, %d received %d errors, %.3f%% loss, "
	       "avg. rt time %.3f ms\n",
	       sp->fp_tx_frames, sp->fp_rx_frames, sp->fp_rx_errors, loss,
	       sp->fp_rx_times ?  sp->fp_transit_time_us * 1.0 /
	       (1000.0 * sp->fp_rx_times) : 0.0);
}

/*
 * Lookup ID from port name or node name.
 */
static int
fp_ns_get_id(uint32_t op, fc_wwn_t wwn, char *response, size_t *resp_len)
{
	struct ct_get_id {
		struct fc_ct_hdr hdr;
		net64_t	 wwn;
	} ct;
	struct fc_bsg_request cdb;
	struct fc_bsg_reply reply;
	struct sg_io_v4 sg_io;
	size_t actual_len;
	int cmd, rc = 0;
	uint32_t *uct = (uint32_t *)&ct.hdr;

	memset((char *)&cdb, 0, sizeof(cdb));
	memset(&ct, 0, sizeof(ct));
	ct.hdr.ct_rev = FC_CT_REV;
	hton24(ct.hdr.ct_in_id, 0xfffffc);
	ct.hdr.ct_fs_type = FC_FST_DIR;
	ct.hdr.ct_fs_subtype = FC_NS_SUBTYPE;
	ct.hdr.ct_options = 0;
	ct.hdr.ct_cmd = htons(op);
	ct.hdr.ct_mr_size = *resp_len;
	net64_put(&ct.wwn, wwn);

	cdb.msgcode = FC_BSG_HST_CT;
	hton24(cdb.rqst_data.h_ct.port_id, 0xfffffc);

	/* copy preamble words one at a time, to make compiler happy */
	cdb.rqst_data.h_ct.preamble_word0 = uct[0];
	cdb.rqst_data.h_ct.preamble_word1 = uct[1];
	cdb.rqst_data.h_ct.preamble_word2 = uct[2];

	sg_io.guard = 'Q';
	sg_io.protocol = BSG_PROTOCOL_SCSI;
	sg_io.subprotocol = BSG_SUB_PROTOCOL_SCSI_TRANSPORT;
	sg_io.request_len = sizeof(cdb);
	sg_io.request = (uintptr_t)&cdb;
	sg_io.dout_xfer_len = sizeof(ct);
	sg_io.dout_xferp = (uintptr_t)&ct;
	sg_io.din_xfer_len = *resp_len;
	sg_io.din_xferp = (uintptr_t)response;
	sg_io.max_response_len = sizeof(reply);
	sg_io.response = (uintptr_t)&reply;
	sg_io.timeout = 1000;	/* millisecond */
	memset(&reply, 0, sizeof(reply));
	memset(response, 0, *resp_len);

	rc = ioctl(fp_fd, SG_IO, &sg_io);
	if (rc < 0) {
		if (op == FC_NS_GID_PN)
			printf("GID_PN error: %s\n", strerror(errno));
		if (op == FC_NS_GID_NN)
			printf("GID_NN error: %s\n", strerror(errno));
		return rc;
	}

	cmd = ((response[8]<<8) | response[9]) & 0xffff;
	if (cmd != FC_FS_ACC)
		return -1;

	actual_len = reply.reply_payload_rcv_len;
	if (actual_len < *resp_len)
		*resp_len = actual_len;

	return 0;
}

static int fp_lookup_target(void)
{
	char response[32];
	size_t resp_len;
	int rc;

	if (fp_did != 0)
		return 0;

	if (fp_port_wwn != 0) {
		resp_len = sizeof(response);
		memset(&response, 0, sizeof(response));
		rc = fp_ns_get_id(FC_NS_GID_PN, fp_port_wwn,
				  response, &resp_len);
		if (rc == 0) {
			fp_did = ((response[17] << 16) & 0xff0000) |
				((response[18] << 8) & 0x00ff00) |
				(response[19] & 0x0000ff);
			return 0;
		}
		SA_LOG("cannot find fcid of destination @ wwpn 0x%llX",
		       fp_port_wwn);
	}
	if (fp_node_wwn != 0) {
		resp_len = sizeof(response);
		memset(&response, 0, sizeof(response));
		rc = fp_ns_get_id(FC_NS_GID_NN, fp_node_wwn,
				  response, &resp_len);
		if (rc == 0) {
			fp_did = ((response[17] << 16) & 0xff0000) |
				((response[18] << 8) & 0x00ff00) |
				(response[19] & 0x0000ff);
			return 0;
		}
		SA_LOG("cannot find fcid of destination @ wwnn 0x%llX",
		       fp_node_wwn);
	}
	return 1;
}

/*
 * fp_get_max_data_len - get the maximum ECHO data size by FCID
 * @fcid: the fcid
 *
 * Returns the maximum allowed ECHO data size. The ECHO data plus the 4 bytes
 * ECHO ELS command is the maximum payload allowed.
 */
static uint32_t fp_get_max_data_len(fc_fid_t fcid)
{
	struct port_attributes *rport_attrs;
	uint32_t dlen = FP_LEN_DEF;
	uint32_t maxframe_size;
	char *rport;

	/* not found from disovered ports, if it's one of the
	 * WKA from FC-LS Table 30, use FC_MAX_PAYLOAD */
	if (FCID_IS_WKA(fcid)) {
		dlen = FP_LEN_MAX;
		goto out;
	}

	rport = get_rport_by_fcid(fcid);
	if (!rport)
		goto out;

	rport_attrs = get_rport_attribs(rport);
	if (!rport_attrs)
		goto free_rport;

	maxframe_size = strtoul(rport_attrs->maxframe_size, NULL, 16);
	dlen = maxframe_size - FP_LEN_ECHO;

	free(rport_attrs);
free_rport:
	free(rport);

out:
	/* returns maximum allowed ECHO data length, excluding the 4
	 * bytes ECHO command in the payload */
	return dlen;
}

/*
 * fp_check_data_len - figure out maximum allowed ECHO data size
 *
 * From FC-LS 4.2.4, for maximum allowed payload when Login exists
 *
 * "If a Login with the destination Nx_Port exists, the ECHO data field size
 * is limited by 4 less than the smallest Receive Data_Field Size supported by
 * the destination Nx_Port, the Fabric, and the source Nx_Port for the class
 * of service being use."
 *
 * So, here we figure out the minimum of the source PortMaxFrameSize, the target
 * PortMaxFraemSize, and the Domain Controller (Fabric) PortMaxFrameSize
 * (default to be FC_MAX_PAYLOAD). For any FCID that is in FC-LS Table 30 WKA,
 * use FP_LEN_MAX for ECHO data, i.e., FC_MAX_PAYLOAD - 4.
 */
static void fp_check_data_len(void)
{
	fc_fid_t sid;
	uint32_t slen = 0;
	uint32_t dlen = 0;
	uint32_t flen = 0;
	uint32_t plen = FP_LEN_DEF;
	uint32_t maxframe_size;

	/* find out maximum payload supported by the fabric */
	flen = fp_get_max_data_len(FC_WKA_FABRIC_CONTROLLER);
	if (!flen) {
		flen = fp_get_max_data_len(FC_WKA_DIRECTORY_SERVICE);
		if (!flen)
			flen = FP_LEN_MAX;
	}

	/* find out maximum payload supported by the target */
	dlen = fp_get_max_data_len(fp_did);
	if (!dlen)
		dlen = FP_LEN_DEF;


	maxframe_size = strtoul(port_attrs->maxframe_size, NULL, 16);
	sid = strtoul(port_attrs->port_id, NULL, 16);

	slen = maxframe_size - FP_LEN_ECHO;
	plen = MIN(flen, MIN(slen, dlen));

	printf("Maximum ECHO data allowed by the Fabric (0x%06x) : %d bytes.\n"
	       "Maximum ECHO data allowed by the Source (0x%06x) : %d bytes.\n"
	       "Maximum ECHO data allowed by the Target (0x%06x) : %d bytes.\n"
	       "Maximum ECHO data requested from user input (-s) : %" PRIu32 " "
	       "(default %d) bytes.\n",
	       FC_WKA_FABRIC_CONTROLLER, flen, sid, slen, fp_did, dlen,
	       (uint32_t)(fp_len - FP_LEN_ECHO), FP_LEN_DEF);

	/* fp_len is the total payload, including 4 bytes for ECHO command */
	fp_len = MIN(fp_len, plen + FP_LEN_ECHO);
	printf("Actual FC ELS ECHO data size used : %" PRIu32 " bytes.\n"
	       "Actual FC ELS ECHO payload size used : %d bytes "
	       "(including %zu bytes ECHO command).\n",
	       (uint32_t)(fp_len - FP_LEN_ECHO), fp_len, FP_LEN_ECHO);
}

/*
 * ELS_ECHO request format being used.
 * Put a sequence number in the payload, followed by the pattern.
 */
struct fcping_echo {
	net8_t      fe_op;              /* opcode */
	net24_t     fe_resvd;           /* reserved, must be zero */
	net32_t     fe_seq;             /* sequence number */
};

/*
 * Setup buffer to be sent.
 */
static void
fp_buf_setup(void)
{
	struct fcping_echo *ep;
	net8_t *pp;
	int len;
	int i;

	/*
	 * Alloc extra in case of odd len or shorter than minimum.
	 */
	len = fp_len + sizeof(*ep) + sizeof(net32_t);
	ep = calloc(1, len);
	if (ep == NULL)
		SA_LOG_ERR_EXIT(errno, "calloc %d bytes failed", len);
	ep->fe_op = ELS_ECHO;
	net32_put(&ep->fe_seq, 1);      /* starting sequence number */
	i = 0;
	for (pp = (net8_t *) (ep + 1); pp < (net8_t *) ep + fp_len; pp++)
		*pp = i++;
	fp_buf = ep;
}

static unsigned long long
fp_get_time_usec(void)
{
#ifdef _POSIX_TIMERS
	struct timespec ts;
	int rc;

	rc = clock_gettime(CLOCK_MONOTONIC, &ts);
	if (rc)
		SA_LOG_ERR_EXIT(errno, "clock_gettime error");
	return ts.tv_sec * 1000000ULL + ts.tv_nsec / 1000;
#else
#warning no _POSIX_TIMERS
	struct timeval ts;

	gettimeofday(&ts, NULL);
	return ts.tv_sec * 1000000ULL + ts.tv_usec;
#endif /* _POSIX_TIMERS */
}

static int
send_els_echo(int fp_fd, void *fp_buf, uint32_t fp_len,
	      unsigned char *resp, uint32_t *resp_len, fc_fid_t fp_did)
{
	struct fc_bsg_request cdb;
	char sense[MAX_SENSE_LEN];
	struct sg_io_v4 sg_io;
	int rc;

	cdb.msgcode = FC_BSG_HST_ELS_NOLOGIN;
	cdb.rqst_data.h_els.command_code = ELS_ECHO;
	hton24(cdb.rqst_data.h_els.port_id, fp_did);

	sg_io.guard = 'Q';
	sg_io.protocol = BSG_PROTOCOL_SCSI;
	sg_io.subprotocol = BSG_SUB_PROTOCOL_SCSI_TRANSPORT;
	sg_io.request_len = sizeof(cdb);
	sg_io.request = (unsigned long)&cdb;
	sg_io.dout_xfer_len = fp_len;
	sg_io.dout_xferp = (unsigned long)fp_buf;
	sg_io.din_xfer_len = *resp_len;
	sg_io.din_xferp = (unsigned long)resp;
	sg_io.max_response_len = sizeof(sense);
	sg_io.response = (unsigned long)sense;
	sg_io.timeout = 20000;
	memset(sense, 0, sizeof(sense));

	rc = ioctl(fp_fd, SG_IO, &sg_io);
	if (rc < 0)
		return 1;

	*resp_len = sg_io.din_xfer_len - sg_io.din_resid;
	return 0;
}

/*
 * Send ELS ECHO.
 */
static int fp_send_ping(void)
{
	struct fp_stats *sp = &fp_stats;
	struct fcping_echo *ep;
	int rc;
	uint32_t resp_len;
	unsigned char *resp;
	unsigned long long tx_time;
	unsigned long long usec;
	char msg[80];
	char time_msg[80];

	resp_len = fp_len + FP_LEN_PAD; /* for odd-byte padding and then some */
	resp = calloc(1, resp_len);
	if (resp == NULL)
		SA_LOG_EXIT("calloc %d bytes failed", resp_len);

	sp->fp_tx_frames++;
	if (fp_len >= sizeof(*ep)) {
		ep = (struct fcping_echo *) fp_buf;
		net32_put(&ep->fe_seq, sp->fp_tx_frames);
	}
	tx_time = fp_get_time_usec();

	/* send ELS ECHO frame and receive */
	rc = send_els_echo(fp_fd, fp_buf, fp_len, resp, &resp_len, fp_did);
	if (rc) {
		sp->fp_rx_errors++;
		printf("echo %4d error: %s\n",
		       sp->fp_tx_frames, strerror(errno));
	} else {
		usec = fp_get_time_usec();
		sp->fp_rx_frames++;
		ep = (struct fcping_echo *) resp;
		if (usec < tx_time) {
			snprintf(time_msg, sizeof(time_msg),
				 "time unknown now %llx old %llx",
				 usec, tx_time);
			usec = 0;	/* as if time went backwards */
		} else {
			usec = usec - tx_time;
			snprintf(time_msg, sizeof(time_msg),
				 "%6.3f ms", usec / 1000.0);
			sp->fp_transit_time_us += usec;
			sp->fp_rx_times++;
		}
		if (ep->fe_op == ELS_LS_ACC) {
			if (memcmp((char *) ep + 1,
				   (char *) fp_buf + 1, fp_len - 1) == 0)
				snprintf(msg, sizeof(msg), "accepted");
			else {
				sp->fp_rx_errors++;
				snprintf(msg, sizeof(msg),
					 "accept data mismatches");
			}
		} else if (ep->fe_op == ELS_LS_RJT) {
			sp->fp_rx_errors++;
			snprintf(msg, sizeof(msg), "REJECT received");
		} else {
			sp->fp_rx_errors++;
			snprintf(msg, sizeof(msg),
				 "op %x received", ep->fe_op);
		}
		if (fp_quiet == 0)
			printf("echo %4d %-30s %s\n",
			       sp->fp_tx_frames, msg, time_msg);
	}
	if (fp_hex) {
		printf("response length %u\n", resp_len);
		sa_hex_dump(resp, resp_len, stdout);
		printf("\n");
	}
	free(resp);
	return rc;
}

static void fp_signal_handler(UNUSED int sig)
{
	/*
	 * Allow graceful termination of the
	 * for loop in fp_start.
	 */
	fp_count = 0;
}

/*
 * Main loop.
 */
static void fp_start(void)
{
	struct sigaction act;
	int i;
	int rc;

	memset(&act, 0, sizeof(act));
	act.sa_handler = fp_signal_handler;
	act.sa_flags = 0;

	sigaction(SIGTERM, &act, NULL);		/* Signal 15: kill <pid> */
	sigaction(SIGQUIT, &act, NULL);		/* Signal 3: Ctrl-\ */
	sigaction(SIGINT,  &act, NULL);		/* Signal 2: Ctrl-C */

	printf("Sending FC ELS ECHO from %s (%s) to 0x%X:\n",
	       port_attrs->port_id, fp_dev, fp_did);

	for (i = 0; fp_count == -1 || i < fp_count; i++) {
		rc = fp_send_ping();
		if (rc != 0 && errno == EMSGSIZE)
			break;
		if (rc != 0 && errno == ECONNABORTED)
			break;
		if (fp_flood == 0)
			usleep(fp_interval * 1000);
		if (!fp_count)
			break;
	}
}

/*
 * Main.
 */
int main(int argc, char *argv[])
{
	char bsg_dev[80];
	int rc = 1;

	fp_options(argc, argv);
	if (fp_find_hba()) {
		sprintf(bsg_dev, "/dev/bsg/%s", fp_dev);
		fp_fd = open(bsg_dev, O_RDWR);
		if (fp_fd < 0)
			SA_LOG_ERR_EXIT(errno,
					"open of %s failed", bsg_dev);

		if (!fp_lookup_target()) {
			fp_check_data_len();
			fp_buf_setup();
			fp_start();
			fp_report();
			rc = 0;
		}
		free(port_attrs);
		free(host);
		close(fp_fd);
	}

	return rc;
}
