diff --git a/Documentation/ABI/testing/sysfs-class-scsi_host b/Documentation/ABI/testing/sysfs-class-scsi_host
index 7c98d8f43c45..2ce266df812f 100644
--- a/Documentation/ABI/testing/sysfs-class-scsi_host
+++ b/Documentation/ABI/testing/sysfs-class-scsi_host
@@ -117,3 +117,75 @@ KernelVersion:	v2.6.39
 Contact:	linux-ide@vger.kernel.org
 Description:
 		(RO) Displays supported enclosure management message types.
+
+What:		/sys/class/scsi_host/hostX/stats/state_first_change_time
+Date:		March 2023
+Kernel Version:	6.4
+Contact:	shane.seymour@hpe.com
+Description:
+		(RO) This is the time since boot where this SCSI host saw it's
+		initial state change. For most SCSI hosts this should be
+		relatively close to the time the system booted up (if the
+		Low Level Driver (LLD) was loaded at boot).
+
+		Looking at this file will help you determine when the host
+		was created.  The time value read from this file is the
+		number of nanoseconds since boot.
+
+What:		/sys/class/scsi_host/hostX/stats/state_last_change_time
+Date:		March 2023
+Kernel Version:	6.4
+Contact:	shane.seymour@hpe.com
+Description:
+		(RO) This is the last time that the state of the SCSI host
+		changed. This can be used in two ways. You can read it
+		twice - once before reading any statistics and once after.
+
+		If the value has changed between reads you might consider
+		re-reading the statistics files again as the data will
+		have changed.
+
+What:		/sys/class/scsi_host/hostX/stats/state_other_count
+Date:		March 2023
+Kernel Version:	6.4
+Contact:	shane.seymour@hpe.com
+Description:
+		(RO) This is a simple count of the number of times this SCSI
+		host has transitioned out of this state.
+
+What:		/sys/class/scsi_host/hostX/stats/state_other_ns
+Date:		March 2023
+Kernel Version:	6.4
+Contact:	shane.seymour@hpe.com
+Description:
+		(RO) This is the amount of time spent in a state of other.
+
+What:		/sys/class/scsi_host/hostX/stats/state_recovery_count
+Date:		March 2023
+Kernel Version:	6.4
+Contact:	shane.seymour@hpe.com
+Description:
+		(RO) This is a simple count of the number of times this SCSI
+		host has transitioned out of this state.
+
+What:		/sys/class/scsi_host/hostX/stats/state_recovery_ns
+Date:		March 2023
+Kernel Version:	6.4
+Contact:	shane.seymour@hpe.com
+Description:
+		(RO) This is the amount of time spent in a state of recovery.
+
+What:		/sys/class/scsi_host/hostX/stats/state_running_count
+Date:		March 2023
+Kernel Version:	6.4
+Contact:	shane.seymour@hpe.com
+Description:
+		(RO) This is a simple count of the number of times this SCSI
+		host has transitioned out of this state.
+
+What:		/sys/class/scsi_host/hostX/stats/state_running_ns
+Date:		March 2023
+Kernel Version:	6.4
+Contact:	shane.seymour@hpe.com
+Description:
+		(RO) This is the amount of time spent in a state of running.
diff --git a/drivers/scsi/hosts.c b/drivers/scsi/hosts.c
index f7f62e56afca..1a010488b52b 100644
--- a/drivers/scsi/hosts.c
+++ b/drivers/scsi/hosts.c
@@ -64,6 +64,49 @@ static struct class shost_class = {
 	.dev_groups	= scsi_shost_groups,
 };
 
+/**
+ *
+ *	scsi_host_do_state_stats - Maintains statistics for presentation
+ *	via sysfs of host state changes per host.
+ *	@shost - scsi host to track state change stats for.
+ *	@oldstate - the previous state that the host was in.
+ *
+ **/
+static void scsi_host_do_state_stats(struct Scsi_Host *shost,
+	enum scsi_host_state oldstate)
+{
+	ktime_t now, last;
+
+	now = ktime_get();
+	last = shost->stats->state_last_change_time;
+
+	if (last == 0) {
+		shost->stats->state_first_change_time = now;
+		shost->stats->state_last_change_time = now;
+		last = now;
+	}
+
+	switch (oldstate) {
+
+	case SHOST_CREATED:
+	case SHOST_RUNNING:
+		shost->stats->state_running_ns += ktime_sub(now, last);
+		shost->stats->state_running_count++;
+		break;
+	case SHOST_RECOVERY:
+	case SHOST_CANCEL_RECOVERY:
+	case SHOST_DEL_RECOVERY:
+		shost->stats->state_recovery_ns += ktime_sub(now, last);
+		shost->stats->state_recovery_count++;
+		break;
+	default:
+		shost->stats->state_other_ns += ktime_sub(now, last);
+		shost->stats->state_other_count++;
+		break;
+	}
+	shost->stats->state_last_change_time = now;
+}
+
 /**
  *	scsi_host_set_state - Take the given host through the host state model.
  *	@shost:	scsi host to change the state of.
@@ -146,6 +189,7 @@ int scsi_host_set_state(struct Scsi_Host *shost, enum scsi_host_state state)
 		break;
 	}
 	shost->shost_state = state;
+	scsi_host_do_state_stats(shost, oldstate);
 	return 0;
 
  illegal:
@@ -369,6 +413,8 @@ static void scsi_host_dev_release(struct device *dev)
 
 	ida_free(&host_index_ida, shost->host_no);
 
+	kfree(shost->stats);
+
 	if (shost->shost_state != SHOST_CREATED)
 		put_device(parent);
 	kfree(shost);
@@ -401,6 +447,12 @@ struct Scsi_Host *scsi_host_alloc(struct scsi_host_template *sht, int privsize)
 	if (!shost)
 		return NULL;
 
+	shost->stats = kzalloc(sizeof(struct scsi_host_stats), GFP_KERNEL);
+	if (!shost->stats) {
+		kfree(shost);
+		return NULL;
+	}
+
 	shost->host_lock = &shost->default_lock;
 	spin_lock_init(shost->host_lock);
 	shost->shost_state = SHOST_CREATED;
diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c
index ee28f73af4d4..00d41c8820aa 100644
--- a/drivers/scsi/scsi_sysfs.c
+++ b/drivers/scsi/scsi_sysfs.c
@@ -428,8 +428,110 @@ static const struct attribute_group scsi_shost_attr_group = {
 	.attrs =	scsi_sysfs_shost_attrs,
 };
 
+/*
+ *	sysfs functions for shost state statistics.
+ */
+
+static ssize_t state_first_change_time_show(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct Scsi_Host *shost = class_to_shost(dev);
+
+	return scnprintf(buf, PAGE_SIZE, "%lld",
+		ktime_to_ns(shost->stats->state_first_change_time));
+}
+static DEVICE_ATTR_RO(state_first_change_time);
+
+static ssize_t state_last_change_time_show(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct Scsi_Host *shost = class_to_shost(dev);
+
+	return scnprintf(buf, PAGE_SIZE, "%lld",
+		ktime_to_ns(shost->stats->state_last_change_time));
+}
+static DEVICE_ATTR_RO(state_last_change_time);
+
+static ssize_t state_running_ns_show(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct Scsi_Host *shost = class_to_shost(dev);
+
+	return scnprintf(buf, PAGE_SIZE, "%lld",
+		ktime_to_ns(shost->stats->state_running_ns));
+}
+static DEVICE_ATTR_RO(state_running_ns);
+
+static ssize_t state_recovery_ns_show(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct Scsi_Host *shost = class_to_shost(dev);
+
+	return scnprintf(buf, PAGE_SIZE, "%lld",
+		ktime_to_ns(shost->stats->state_recovery_ns));
+}
+static DEVICE_ATTR_RO(state_recovery_ns);
+
+static ssize_t state_other_ns_show(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct Scsi_Host *shost = class_to_shost(dev);
+
+	return scnprintf(buf, PAGE_SIZE, "%lld",
+		ktime_to_ns(shost->stats->state_other_ns));
+}
+static DEVICE_ATTR_RO(state_other_ns);
+
+static ssize_t state_running_count_show(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct Scsi_Host *shost = class_to_shost(dev);
+
+	return scnprintf(buf, PAGE_SIZE, "%d",
+		shost->stats->state_running_count);
+}
+static DEVICE_ATTR_RO(state_running_count);
+
+static ssize_t state_recovery_count_show(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct Scsi_Host *shost = class_to_shost(dev);
+
+	return scnprintf(buf, PAGE_SIZE, "%d",
+		shost->stats->state_recovery_count);
+}
+static DEVICE_ATTR_RO(state_recovery_count);
+
+static ssize_t state_other_count_show(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct Scsi_Host *shost = class_to_shost(dev);
+
+	return scnprintf(buf, PAGE_SIZE, "%d",
+		shost->stats->state_other_count);
+}
+static DEVICE_ATTR_RO(state_other_count);
+
+static struct attribute *scsi_shost_stats[] = {
+	&dev_attr_state_last_change_time.attr,
+	&dev_attr_state_first_change_time.attr,
+	&dev_attr_state_running_ns.attr,
+	&dev_attr_state_recovery_ns.attr,
+	&dev_attr_state_other_ns.attr,
+	&dev_attr_state_running_count.attr,
+	&dev_attr_state_recovery_count.attr,
+	&dev_attr_state_other_count.attr,
+	NULL
+};
+
+static struct attribute_group scsi_shost_stats_group = {
+	.name = "stats",
+	.attrs = scsi_shost_stats,
+};
+
 const struct attribute_group *scsi_shost_groups[] = {
 	&scsi_shost_attr_group,
+	&scsi_shost_stats_group,
 	NULL
 };
 
diff --git a/include/scsi/scsi_host.h b/include/scsi/scsi_host.h
index 587cc767bb67..e6229567a295 100644
--- a/include/scsi/scsi_host.h
+++ b/include/scsi/scsi_host.h
@@ -513,11 +513,26 @@ struct scsi_host_template {
 		return rc;						\
 	}
 
+/*
+ * host statistics
+ */
+
+struct scsi_host_stats {
+	ktime_t state_running_ns;
+	ktime_t state_recovery_ns;
+	ktime_t state_other_ns;
+	ktime_t state_first_change_time;
+	ktime_t state_last_change_time;
+	uint32_t state_running_count;
+	uint32_t state_recovery_count;
+	uint32_t state_other_count;
+};
 
 /*
  * shost state: If you alter this, you also need to alter scsi_sysfs.c
  * (for the ascii descriptions) and the state model enforcer:
- * scsi_host_set_state()
+ * scsi_host_set_state() and if host state statistics are accounted
+ * for correctly in scsi_host_do_state_stats()
  */
 enum scsi_host_state {
 	SHOST_CREATED = 1,
@@ -704,6 +719,11 @@ struct Scsi_Host {
 	 */
 	struct device *dma_dev;
 
+	/*
+	 * Host statistics
+	 */
+	struct scsi_host_stats *stats;
+
 	/*
 	 * We should ensure that this is aligned, both for better performance
 	 * and also because some compilers (m68k) don't automatically force
