Commit 1d8613a2 authored by Can Guo's avatar Can Guo Committed by Martin K. Petersen
Browse files

scsi: ufs: core: Introduce HBA performance monitor sysfs nodes

Add a new sysfs group which has nodes to monitor data/request transfer
performance. This sysfs group has nodes showing total sectors/requests
transferred, total busy time spent and max/min/avg/sum latencies. This
group can be enhanced later to show more UFS driver layer performance
statistics data during runtime.

Link: https://lore.kernel.org/r/1619058521-35307-2-git-send-email-cang@codeaurora.org


Reviewed-by: default avatarDaejun Park <daejun7.park@samsung.com>
Acked-by: default avatarBean Huo <beanhuo@micron.com>
Signed-off-by: default avatarCan Guo <cang@codeaurora.org>
Signed-off-by: default avatarMartin K. Petersen <martin.petersen@oracle.com>
parent 39107e85
Loading
Loading
Loading
Loading
+126 −0
Original line number Diff line number Diff line
@@ -995,6 +995,132 @@ Description: This entry shows the target state of an UFS UIC link

		The file is read only.

What:		/sys/bus/platform/drivers/ufshcd/*/monitor/monitor_enable
Date:		January 2021
Contact:	Can Guo <cang@codeaurora.org>
Description:	This file shows the status of performance monitor enablement
		and it can be used to start/stop the monitor. When the monitor
		is stopped, the performance data collected is also cleared.

What:		/sys/bus/platform/drivers/ufshcd/*/monitor/monitor_chunk_size
Date:		January 2021
Contact:	Can Guo <cang@codeaurora.org>
Description:	This file tells the monitor to focus on requests transferring
		data of specific chunk size (in Bytes). 0 means any chunk size.
		It can only be changed when monitor is disabled.

What:		/sys/bus/platform/drivers/ufshcd/*/monitor/read_total_sectors
Date:		January 2021
Contact:	Can Guo <cang@codeaurora.org>
Description:	This file shows how many sectors (in 512 Bytes) have been
		sent from device to host after monitor gets started.

		The file is read only.

What:		/sys/bus/platform/drivers/ufshcd/*/monitor/read_total_busy
Date:		January 2021
Contact:	Can Guo <cang@codeaurora.org>
Description:	This file shows how long (in micro seconds) has been spent
		sending data from device to host after monitor gets started.

		The file is read only.

What:		/sys/bus/platform/drivers/ufshcd/*/monitor/read_nr_requests
Date:		January 2021
Contact:	Can Guo <cang@codeaurora.org>
Description:	This file shows how many read requests have been sent after
		monitor gets started.

		The file is read only.

What:		/sys/bus/platform/drivers/ufshcd/*/monitor/read_req_latency_max
Date:		January 2021
Contact:	Can Guo <cang@codeaurora.org>
Description:	This file shows the maximum latency (in micro seconds) of
		read requests after monitor gets started.

		The file is read only.

What:		/sys/bus/platform/drivers/ufshcd/*/monitor/read_req_latency_min
Date:		January 2021
Contact:	Can Guo <cang@codeaurora.org>
Description:	This file shows the minimum latency (in micro seconds) of
		read requests after monitor gets started.

		The file is read only.

What:		/sys/bus/platform/drivers/ufshcd/*/monitor/read_req_latency_avg
Date:		January 2021
Contact:	Can Guo <cang@codeaurora.org>
Description:	This file shows the average latency (in micro seconds) of
		read requests after monitor gets started.

		The file is read only.

What:		/sys/bus/platform/drivers/ufshcd/*/monitor/read_req_latency_sum
Date:		January 2021
Contact:	Can Guo <cang@codeaurora.org>
Description:	This file shows the total latency (in micro seconds) of
		read requests sent after monitor gets started.

		The file is read only.

What:		/sys/bus/platform/drivers/ufshcd/*/monitor/write_total_sectors
Date:		January 2021
Contact:	Can Guo <cang@codeaurora.org>
Description:	This file shows how many sectors (in 512 Bytes) have been sent
		from host to device after monitor gets started.

		The file is read only.

What:		/sys/bus/platform/drivers/ufshcd/*/monitor/write_total_busy
Date:		January 2021
Contact:	Can Guo <cang@codeaurora.org>
Description:	This file shows how long (in micro seconds) has been spent
		sending data from host to device after monitor gets started.

		The file is read only.

What:		/sys/bus/platform/drivers/ufshcd/*/monitor/write_nr_requests
Date:		January 2021
Contact:	Can Guo <cang@codeaurora.org>
Description:	This file shows how many write requests have been sent after
		monitor gets started.

		The file is read only.

What:		/sys/bus/platform/drivers/ufshcd/*/monitor/write_req_latency_max
Date:		January 2021
Contact:	Can Guo <cang@codeaurora.org>
Description:	This file shows the maximum latency (in micro seconds) of write
		requests after monitor gets started.

		The file is read only.

What:		/sys/bus/platform/drivers/ufshcd/*/monitor/write_req_latency_min
Date:		January 2021
Contact:	Can Guo <cang@codeaurora.org>
Description:	This file shows the minimum latency (in micro seconds) of write
		requests after monitor gets started.

		The file is read only.

What:		/sys/bus/platform/drivers/ufshcd/*/monitor/write_req_latency_avg
Date:		January 2021
Contact:	Can Guo <cang@codeaurora.org>
Description:	This file shows the average latency (in micro seconds) of write
		requests after monitor gets started.

		The file is read only.

What:		/sys/bus/platform/drivers/ufshcd/*/monitor/write_req_latency_sum
Date:		January 2021
Contact:	Can Guo <cang@codeaurora.org>
Description:	This file shows the total latency (in micro seconds) of write
		requests after monitor gets started.

		The file is read only.

What:		/sys/bus/platform/drivers/ufshcd/*/device_descriptor/wb_presv_us_en
Date:		June 2020
Contact:	Asutosh Das <asutoshd@codeaurora.org>
+237 −0
Original line number Diff line number Diff line
@@ -278,6 +278,242 @@ static const struct attribute_group ufs_sysfs_default_group = {
	.attrs = ufs_sysfs_ufshcd_attrs,
};

static ssize_t monitor_enable_show(struct device *dev,
				   struct device_attribute *attr, char *buf)
{
	struct ufs_hba *hba = dev_get_drvdata(dev);

	return sysfs_emit(buf, "%d\n", hba->monitor.enabled);
}

static ssize_t monitor_enable_store(struct device *dev,
				    struct device_attribute *attr,
				    const char *buf, size_t count)
{
	struct ufs_hba *hba = dev_get_drvdata(dev);
	unsigned long value, flags;

	if (kstrtoul(buf, 0, &value))
		return -EINVAL;

	value = !!value;
	spin_lock_irqsave(hba->host->host_lock, flags);
	if (value == hba->monitor.enabled)
		goto out_unlock;

	if (!value) {
		memset(&hba->monitor, 0, sizeof(hba->monitor));
	} else {
		hba->monitor.enabled = true;
		hba->monitor.enabled_ts = ktime_get();
	}

out_unlock:
	spin_unlock_irqrestore(hba->host->host_lock, flags);
	return count;
}

static ssize_t monitor_chunk_size_show(struct device *dev,
				   struct device_attribute *attr, char *buf)
{
	struct ufs_hba *hba = dev_get_drvdata(dev);

	return sysfs_emit(buf, "%lu\n", hba->monitor.chunk_size);
}

static ssize_t monitor_chunk_size_store(struct device *dev,
				    struct device_attribute *attr,
				    const char *buf, size_t count)
{
	struct ufs_hba *hba = dev_get_drvdata(dev);
	unsigned long value, flags;

	if (kstrtoul(buf, 0, &value))
		return -EINVAL;

	spin_lock_irqsave(hba->host->host_lock, flags);
	/* Only allow chunk size change when monitor is disabled */
	if (!hba->monitor.enabled)
		hba->monitor.chunk_size = value;
	spin_unlock_irqrestore(hba->host->host_lock, flags);
	return count;
}

static ssize_t read_total_sectors_show(struct device *dev,
				       struct device_attribute *attr, char *buf)
{
	struct ufs_hba *hba = dev_get_drvdata(dev);

	return sysfs_emit(buf, "%lu\n", hba->monitor.nr_sec_rw[READ]);
}

static ssize_t read_total_busy_show(struct device *dev,
				    struct device_attribute *attr, char *buf)
{
	struct ufs_hba *hba = dev_get_drvdata(dev);

	return sysfs_emit(buf, "%llu\n",
			  ktime_to_us(hba->monitor.total_busy[READ]));
}

static ssize_t read_nr_requests_show(struct device *dev,
				     struct device_attribute *attr, char *buf)
{
	struct ufs_hba *hba = dev_get_drvdata(dev);

	return sysfs_emit(buf, "%lu\n", hba->monitor.nr_req[READ]);
}

static ssize_t read_req_latency_avg_show(struct device *dev,
					 struct device_attribute *attr,
					 char *buf)
{
	struct ufs_hba *hba = dev_get_drvdata(dev);
	struct ufs_hba_monitor *m = &hba->monitor;

	return sysfs_emit(buf, "%llu\n", div_u64(ktime_to_us(m->lat_sum[READ]),
						 m->nr_req[READ]));
}

static ssize_t read_req_latency_max_show(struct device *dev,
					 struct device_attribute *attr,
					 char *buf)
{
	struct ufs_hba *hba = dev_get_drvdata(dev);

	return sysfs_emit(buf, "%llu\n",
			  ktime_to_us(hba->monitor.lat_max[READ]));
}

static ssize_t read_req_latency_min_show(struct device *dev,
					 struct device_attribute *attr,
					 char *buf)
{
	struct ufs_hba *hba = dev_get_drvdata(dev);

	return sysfs_emit(buf, "%llu\n",
			  ktime_to_us(hba->monitor.lat_min[READ]));
}

static ssize_t read_req_latency_sum_show(struct device *dev,
					 struct device_attribute *attr,
					 char *buf)
{
	struct ufs_hba *hba = dev_get_drvdata(dev);

	return sysfs_emit(buf, "%llu\n",
			  ktime_to_us(hba->monitor.lat_sum[READ]));
}

static ssize_t write_total_sectors_show(struct device *dev,
					struct device_attribute *attr,
					char *buf)
{
	struct ufs_hba *hba = dev_get_drvdata(dev);

	return sysfs_emit(buf, "%lu\n", hba->monitor.nr_sec_rw[WRITE]);
}

static ssize_t write_total_busy_show(struct device *dev,
				     struct device_attribute *attr, char *buf)
{
	struct ufs_hba *hba = dev_get_drvdata(dev);

	return sysfs_emit(buf, "%llu\n",
			  ktime_to_us(hba->monitor.total_busy[WRITE]));
}

static ssize_t write_nr_requests_show(struct device *dev,
				      struct device_attribute *attr, char *buf)
{
	struct ufs_hba *hba = dev_get_drvdata(dev);

	return sysfs_emit(buf, "%lu\n", hba->monitor.nr_req[WRITE]);
}

static ssize_t write_req_latency_avg_show(struct device *dev,
					  struct device_attribute *attr,
					  char *buf)
{
	struct ufs_hba *hba = dev_get_drvdata(dev);
	struct ufs_hba_monitor *m = &hba->monitor;

	return sysfs_emit(buf, "%llu\n", div_u64(ktime_to_us(m->lat_sum[WRITE]),
						 m->nr_req[WRITE]));
}

static ssize_t write_req_latency_max_show(struct device *dev,
					  struct device_attribute *attr,
					  char *buf)
{
	struct ufs_hba *hba = dev_get_drvdata(dev);

	return sysfs_emit(buf, "%llu\n",
			  ktime_to_us(hba->monitor.lat_max[WRITE]));
}

static ssize_t write_req_latency_min_show(struct device *dev,
					  struct device_attribute *attr,
					  char *buf)
{
	struct ufs_hba *hba = dev_get_drvdata(dev);

	return sysfs_emit(buf, "%llu\n",
			  ktime_to_us(hba->monitor.lat_min[WRITE]));
}

static ssize_t write_req_latency_sum_show(struct device *dev,
					  struct device_attribute *attr,
					  char *buf)
{
	struct ufs_hba *hba = dev_get_drvdata(dev);

	return sysfs_emit(buf, "%llu\n",
			  ktime_to_us(hba->monitor.lat_sum[WRITE]));
}

static DEVICE_ATTR_RW(monitor_enable);
static DEVICE_ATTR_RW(monitor_chunk_size);
static DEVICE_ATTR_RO(read_total_sectors);
static DEVICE_ATTR_RO(read_total_busy);
static DEVICE_ATTR_RO(read_nr_requests);
static DEVICE_ATTR_RO(read_req_latency_avg);
static DEVICE_ATTR_RO(read_req_latency_max);
static DEVICE_ATTR_RO(read_req_latency_min);
static DEVICE_ATTR_RO(read_req_latency_sum);
static DEVICE_ATTR_RO(write_total_sectors);
static DEVICE_ATTR_RO(write_total_busy);
static DEVICE_ATTR_RO(write_nr_requests);
static DEVICE_ATTR_RO(write_req_latency_avg);
static DEVICE_ATTR_RO(write_req_latency_max);
static DEVICE_ATTR_RO(write_req_latency_min);
static DEVICE_ATTR_RO(write_req_latency_sum);

static struct attribute *ufs_sysfs_monitor_attrs[] = {
	&dev_attr_monitor_enable.attr,
	&dev_attr_monitor_chunk_size.attr,
	&dev_attr_read_total_sectors.attr,
	&dev_attr_read_total_busy.attr,
	&dev_attr_read_nr_requests.attr,
	&dev_attr_read_req_latency_avg.attr,
	&dev_attr_read_req_latency_max.attr,
	&dev_attr_read_req_latency_min.attr,
	&dev_attr_read_req_latency_sum.attr,
	&dev_attr_write_total_sectors.attr,
	&dev_attr_write_total_busy.attr,
	&dev_attr_write_nr_requests.attr,
	&dev_attr_write_req_latency_avg.attr,
	&dev_attr_write_req_latency_max.attr,
	&dev_attr_write_req_latency_min.attr,
	&dev_attr_write_req_latency_sum.attr,
	NULL
};

static const struct attribute_group ufs_sysfs_monitor_group = {
	.name = "monitor",
	.attrs = ufs_sysfs_monitor_attrs,
};

static ssize_t ufs_sysfs_read_desc_param(struct ufs_hba *hba,
				  enum desc_idn desc_id,
				  u8 desc_index,
@@ -881,6 +1117,7 @@ static const struct attribute_group ufs_sysfs_attributes_group = {

static const struct attribute_group *ufs_sysfs_groups[] = {
	&ufs_sysfs_default_group,
	&ufs_sysfs_monitor_group,
	&ufs_sysfs_device_descriptor_group,
	&ufs_sysfs_interconnect_descriptor_group,
	&ufs_sysfs_geometry_descriptor_group,
+62 −0
Original line number Diff line number Diff line
@@ -2024,6 +2024,64 @@ static void ufshcd_clk_scaling_update_busy(struct ufs_hba *hba)
		scaling->is_busy_started = false;
	}
}

static inline int ufshcd_monitor_opcode2dir(u8 opcode)
{
	if (opcode == READ_6 || opcode == READ_10 || opcode == READ_16)
		return READ;
	else if (opcode == WRITE_6 || opcode == WRITE_10 || opcode == WRITE_16)
		return WRITE;
	else
		return -EINVAL;
}

static inline bool ufshcd_should_inform_monitor(struct ufs_hba *hba,
						struct ufshcd_lrb *lrbp)
{
	struct ufs_hba_monitor *m = &hba->monitor;

	return (m->enabled && lrbp && lrbp->cmd &&
		(!m->chunk_size || m->chunk_size == lrbp->cmd->sdb.length) &&
		ktime_before(hba->monitor.enabled_ts, lrbp->issue_time_stamp));
}

static void ufshcd_start_monitor(struct ufs_hba *hba, struct ufshcd_lrb *lrbp)
{
	int dir = ufshcd_monitor_opcode2dir(*lrbp->cmd->cmnd);

	if (dir >= 0 && hba->monitor.nr_queued[dir]++ == 0)
		hba->monitor.busy_start_ts[dir] = ktime_get();
}

static void ufshcd_update_monitor(struct ufs_hba *hba, struct ufshcd_lrb *lrbp)
{
	int dir = ufshcd_monitor_opcode2dir(*lrbp->cmd->cmnd);

	if (dir >= 0 && hba->monitor.nr_queued[dir] > 0) {
		struct request *req = lrbp->cmd->request;
		struct ufs_hba_monitor *m = &hba->monitor;
		ktime_t now, inc, lat;

		now = lrbp->compl_time_stamp;
		inc = ktime_sub(now, m->busy_start_ts[dir]);
		m->total_busy[dir] = ktime_add(m->total_busy[dir], inc);
		m->nr_sec_rw[dir] += blk_rq_sectors(req);

		/* Update latencies */
		m->nr_req[dir]++;
		lat = ktime_sub(now, lrbp->issue_time_stamp);
		m->lat_sum[dir] += lat;
		if (m->lat_max[dir] < lat || !m->lat_max[dir])
			m->lat_max[dir] = lat;
		if (m->lat_min[dir] > lat || !m->lat_min[dir])
			m->lat_min[dir] = lat;

		m->nr_queued[dir]--;
		/* Push forward the busy start of monitor */
		m->busy_start_ts[dir] = now;
	}
}

/**
 * ufshcd_send_command - Send SCSI or device management commands
 * @hba: per adapter instance
@@ -2040,6 +2098,8 @@ void ufshcd_send_command(struct ufs_hba *hba, unsigned int task_tag)
	ufshcd_add_command_trace(hba, task_tag, UFS_CMD_SEND);
	ufshcd_clk_scaling_start_busy(hba);
	__set_bit(task_tag, &hba->outstanding_reqs);
	if (unlikely(ufshcd_should_inform_monitor(hba, lrbp)))
		ufshcd_start_monitor(hba, lrbp);
	ufshcd_writel(hba, 1 << task_tag, REG_UTP_TRANSFER_REQ_DOOR_BELL);
	/* Make sure that doorbell is committed immediately */
	wmb();
@@ -5143,6 +5203,8 @@ static void __ufshcd_transfer_req_compl(struct ufs_hba *hba,
		lrbp->compl_time_stamp = ktime_get();
		cmd = lrbp->cmd;
		if (cmd) {
			if (unlikely(ufshcd_should_inform_monitor(hba, lrbp)))
				ufshcd_update_monitor(hba, lrbp);
			ufshcd_add_command_trace(hba, index, UFS_CMD_COMP);
			result = ufshcd_transfer_rsp_status(hba, lrbp);
			scsi_dma_unmap(cmd);
+21 −0
Original line number Diff line number Diff line
@@ -647,6 +647,25 @@ struct ufs_hba_variant_params {
	u32 wb_flush_threshold;
};

struct ufs_hba_monitor {
	unsigned long chunk_size;

	unsigned long nr_sec_rw[2];
	ktime_t total_busy[2];

	unsigned long nr_req[2];
	/* latencies*/
	ktime_t lat_sum[2];
	ktime_t lat_max[2];
	ktime_t lat_min[2];

	u32 nr_queued[2];
	ktime_t busy_start_ts[2];

	ktime_t enabled_ts;
	bool enabled;
};

/**
 * struct ufs_hba - per adapter private structure
 * @mmio_base: UFSHCI base register address
@@ -838,6 +857,8 @@ struct ufs_hba {
	struct request_queue	*bsg_queue;
	struct delayed_work rpm_dev_flush_recheck_work;

	struct ufs_hba_monitor	monitor;

#ifdef CONFIG_SCSI_UFS_CRYPTO
	union ufs_crypto_capabilities crypto_capabilities;
	union ufs_crypto_cap_entry *crypto_cap_array;