[SCSI] scsi_transport_iscsi: Add host statistics support

Add transport_iscsi hooks to get aggregate host statistics.
The statistics include MAC, TCP/IP & iSCSI statistics.

Signed-off-by: Lalit Chandivade <lalit.chandivade@qlogic.com>
Signed-off-by: Vikas Chaudhary <vikas.chaudhary@qlogic.com>
Reviewed-by: Mike Christie <michaelc@cs.wisc.edu>
Signed-off-by: James Bottomley <JBottomley@Parallels.com>
diff --git a/drivers/scsi/scsi_transport_iscsi.c b/drivers/scsi/scsi_transport_iscsi.c
index 9477f84..4cf918a 100644
--- a/drivers/scsi/scsi_transport_iscsi.c
+++ b/drivers/scsi/scsi_transport_iscsi.c
@@ -3416,6 +3416,73 @@
 }
 
 static int
+iscsi_get_host_stats(struct iscsi_transport *transport, struct nlmsghdr *nlh)
+{
+	struct iscsi_uevent *ev = nlmsg_data(nlh);
+	struct Scsi_Host *shost = NULL;
+	struct iscsi_internal *priv;
+	struct sk_buff *skbhost_stats;
+	struct nlmsghdr *nlhhost_stats;
+	struct iscsi_uevent *evhost_stats;
+	int host_stats_size = 0;
+	int len, err = 0;
+	char *buf;
+
+	if (!transport->get_host_stats)
+		return -EINVAL;
+
+	priv = iscsi_if_transport_lookup(transport);
+	if (!priv)
+		return -EINVAL;
+
+	host_stats_size = sizeof(struct iscsi_offload_host_stats);
+	len = nlmsg_total_size(sizeof(*ev) + host_stats_size);
+
+	shost = scsi_host_lookup(ev->u.get_host_stats.host_no);
+	if (!shost) {
+		pr_err("%s: failed. Cound not find host no %u\n",
+		       __func__, ev->u.get_host_stats.host_no);
+		return -ENODEV;
+	}
+
+	do {
+		int actual_size;
+
+		skbhost_stats = alloc_skb(len, GFP_KERNEL);
+		if (!skbhost_stats) {
+			pr_err("cannot deliver host stats: OOM\n");
+			err = -ENOMEM;
+			goto exit_host_stats;
+		}
+
+		nlhhost_stats = __nlmsg_put(skbhost_stats, 0, 0, 0,
+				      (len - sizeof(*nlhhost_stats)), 0);
+		evhost_stats = nlmsg_data(nlhhost_stats);
+		memset(evhost_stats, 0, sizeof(*evhost_stats));
+		evhost_stats->transport_handle = iscsi_handle(transport);
+		evhost_stats->type = nlh->nlmsg_type;
+		evhost_stats->u.get_host_stats.host_no =
+					ev->u.get_host_stats.host_no;
+		buf = (char *)((char *)evhost_stats + sizeof(*evhost_stats));
+		memset(buf, 0, host_stats_size);
+
+		err = transport->get_host_stats(shost, buf, host_stats_size);
+
+		actual_size = nlmsg_total_size(sizeof(*ev) + host_stats_size);
+		skb_trim(skbhost_stats, NLMSG_ALIGN(actual_size));
+		nlhhost_stats->nlmsg_len = actual_size;
+
+		err = iscsi_multicast_skb(skbhost_stats, ISCSI_NL_GRP_ISCSID,
+					  GFP_KERNEL);
+	} while (err < 0 && err != -ECONNREFUSED);
+
+exit_host_stats:
+	scsi_host_put(shost);
+	return err;
+}
+
+
+static int
 iscsi_if_recv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, uint32_t *group)
 {
 	int err = 0;
@@ -3594,6 +3661,9 @@
 		err = iscsi_set_chap(transport, ev,
 				     nlmsg_attrlen(nlh, sizeof(*ev)));
 		break;
+	case ISCSI_UEVENT_GET_HOST_STATS:
+		err = iscsi_get_host_stats(transport, nlh);
+		break;
 	default:
 		err = -ENOSYS;
 		break;
diff --git a/include/scsi/iscsi_if.h b/include/scsi/iscsi_if.h
index a572083..3851a73 100644
--- a/include/scsi/iscsi_if.h
+++ b/include/scsi/iscsi_if.h
@@ -70,6 +70,7 @@
 	ISCSI_UEVENT_LOGOUT_FLASHNODE	= UEVENT_BASE + 29,
 	ISCSI_UEVENT_LOGOUT_FLASHNODE_SID	= UEVENT_BASE + 30,
 	ISCSI_UEVENT_SET_CHAP		= UEVENT_BASE + 31,
+	ISCSI_UEVENT_GET_HOST_STATS	= UEVENT_BASE + 32,
 
 	/* up events */
 	ISCSI_KEVENT_RECV_PDU		= KEVENT_BASE + 1,
@@ -242,6 +243,9 @@
 			uint32_t	host_no;
 			uint32_t	sid;
 		} logout_flashnode_sid;
+		struct msg_get_host_stats {
+			uint32_t host_no;
+		} get_host_stats;
 	} u;
 	union {
 		/* messages k -> u */
@@ -845,4 +849,112 @@
 	uint8_t password_length;
 };
 
+#define ISCSI_HOST_STATS_CUSTOM_MAX             32
+#define ISCSI_HOST_STATS_CUSTOM_DESC_MAX        64
+struct iscsi_host_stats_custom {
+	char desc[ISCSI_HOST_STATS_CUSTOM_DESC_MAX];
+	uint64_t value;
+};
+
+/* struct iscsi_offload_host_stats: Host statistics,
+ * Include statistics for MAC, IP, TCP & iSCSI.
+ */
+struct iscsi_offload_host_stats {
+	/* MAC */
+	uint64_t mactx_frames;
+	uint64_t mactx_bytes;
+	uint64_t mactx_multicast_frames;
+	uint64_t mactx_broadcast_frames;
+	uint64_t mactx_pause_frames;
+	uint64_t mactx_control_frames;
+	uint64_t mactx_deferral;
+	uint64_t mactx_excess_deferral;
+	uint64_t mactx_late_collision;
+	uint64_t mactx_abort;
+	uint64_t mactx_single_collision;
+	uint64_t mactx_multiple_collision;
+	uint64_t mactx_collision;
+	uint64_t mactx_frames_dropped;
+	uint64_t mactx_jumbo_frames;
+	uint64_t macrx_frames;
+	uint64_t macrx_bytes;
+	uint64_t macrx_unknown_control_frames;
+	uint64_t macrx_pause_frames;
+	uint64_t macrx_control_frames;
+	uint64_t macrx_dribble;
+	uint64_t macrx_frame_length_error;
+	uint64_t macrx_jabber;
+	uint64_t macrx_carrier_sense_error;
+	uint64_t macrx_frame_discarded;
+	uint64_t macrx_frames_dropped;
+	uint64_t mac_crc_error;
+	uint64_t mac_encoding_error;
+	uint64_t macrx_length_error_large;
+	uint64_t macrx_length_error_small;
+	uint64_t macrx_multicast_frames;
+	uint64_t macrx_broadcast_frames;
+	/* IP */
+	uint64_t iptx_packets;
+	uint64_t iptx_bytes;
+	uint64_t iptx_fragments;
+	uint64_t iprx_packets;
+	uint64_t iprx_bytes;
+	uint64_t iprx_fragments;
+	uint64_t ip_datagram_reassembly;
+	uint64_t ip_invalid_address_error;
+	uint64_t ip_error_packets;
+	uint64_t ip_fragrx_overlap;
+	uint64_t ip_fragrx_outoforder;
+	uint64_t ip_datagram_reassembly_timeout;
+	uint64_t ipv6tx_packets;
+	uint64_t ipv6tx_bytes;
+	uint64_t ipv6tx_fragments;
+	uint64_t ipv6rx_packets;
+	uint64_t ipv6rx_bytes;
+	uint64_t ipv6rx_fragments;
+	uint64_t ipv6_datagram_reassembly;
+	uint64_t ipv6_invalid_address_error;
+	uint64_t ipv6_error_packets;
+	uint64_t ipv6_fragrx_overlap;
+	uint64_t ipv6_fragrx_outoforder;
+	uint64_t ipv6_datagram_reassembly_timeout;
+	/* TCP */
+	uint64_t tcptx_segments;
+	uint64_t tcptx_bytes;
+	uint64_t tcprx_segments;
+	uint64_t tcprx_byte;
+	uint64_t tcp_duplicate_ack_retx;
+	uint64_t tcp_retx_timer_expired;
+	uint64_t tcprx_duplicate_ack;
+	uint64_t tcprx_pure_ackr;
+	uint64_t tcptx_delayed_ack;
+	uint64_t tcptx_pure_ack;
+	uint64_t tcprx_segment_error;
+	uint64_t tcprx_segment_outoforder;
+	uint64_t tcprx_window_probe;
+	uint64_t tcprx_window_update;
+	uint64_t tcptx_window_probe_persist;
+	/* ECC */
+	uint64_t ecc_error_correction;
+	/* iSCSI */
+	uint64_t iscsi_pdu_tx;
+	uint64_t iscsi_data_bytes_tx;
+	uint64_t iscsi_pdu_rx;
+	uint64_t iscsi_data_bytes_rx;
+	uint64_t iscsi_io_completed;
+	uint64_t iscsi_unexpected_io_rx;
+	uint64_t iscsi_format_error;
+	uint64_t iscsi_hdr_digest_error;
+	uint64_t iscsi_data_digest_error;
+	uint64_t iscsi_sequence_error;
+	/*
+	 * iSCSI Custom Host Statistics support, i.e. Transport could
+	 * extend existing host statistics with its own specific statistics
+	 * up to ISCSI_HOST_STATS_CUSTOM_MAX
+	 */
+	uint32_t custom_length;
+	struct iscsi_host_stats_custom custom[0]
+		__aligned(sizeof(uint64_t));
+};
+
 #endif
diff --git a/include/scsi/scsi_transport_iscsi.h b/include/scsi/scsi_transport_iscsi.h
index 2ac11fe..88640a4 100644
--- a/include/scsi/scsi_transport_iscsi.h
+++ b/include/scsi/scsi_transport_iscsi.h
@@ -166,6 +166,7 @@
 	int (*logout_flashnode) (struct iscsi_bus_flash_session *fnode_sess,
 				 struct iscsi_bus_flash_conn *fnode_conn);
 	int (*logout_flashnode_sid) (struct iscsi_cls_session *cls_sess);
+	int (*get_host_stats) (struct Scsi_Host *shost, char *buf, int len);
 };
 
 /*