Linux SCSI subsystem development
 help / color / mirror / Atom feed
* [PATCH v2 0/2] scsi: ufs: batch device-init reads with UFS 5.0 aggregated read
       [not found] <CGME20260930050201epcms2p13ab67182445b0555acc8d27b927cda86@epcms2p1>
@ 2026-09-30  5:02 ` Hyeoncheol Jeong
  2026-10-02  9:24   ` Bean Huo
  0 siblings, 1 reply; 12+ messages in thread
From: Hyeoncheol Jeong @ 2026-09-30  5:02 UTC (permalink / raw)
  To: James.Bottomley@HansenPartnership.com, mkp@kernel.org,
	avri.altman@sandisk.com, bvanassche@acm.org,
	peter.wang@mediatek.com, linux-scsi@vger.kernel.org
  Cc: Jinyoung Choi, Alim Akhtar, beanhuo@micron.com,
	can.guo@oss.qualcomm.com, Hyeoncheol Jeong,
	linux-kernel@vger.kernel.org

UFS 5.0 adds the AGGREGATED READ query (opcode 0x9): one QUERY RESPONSE
UPIU returns many descriptors, attributes and flags as a chain of typed
groups.

Patch 1 adds the opcode, the request mask, the group header and the reply
plumbing. Patch 2 uses it during device init: a single aggregated read
replaces the per-item queries for the attributes, flags and ID strings,
falling back to individual queries on older devices or a rejected opcode.

No functional change for pre-5.0 devices.

Motivation: on a Qualcomm SM8850 platform with a Samsung UFS 5.0 device,
timing raw UPIU queries over BSG (100 iterations) shows that replacing the
device-init read burst with a single aggregated read reduces the query
time by 75-78% (min 75.3%, avg 76.4%, max 78.5%).

(That setup batched 8 init reads; on current mainline this series batches
the 6 device-init reads that remain individual queries.)

Changes since v1:
- Rename UFS_AGG_TYPE_UNIT_DESC to UFS_AGG_TYPE_UNIT_RPMB_DESC (Peter Wang)
- Add UFS_AGG_GROUP_RESERVED = 0x00 (Peter Wang)
- Rename hdr_off/next_off to group_off/next_group_off (Peter Wang)
- Rebased onto v7.3-rc5

Hyeoncheol Jeong (2):
  scsi: ufs: Add the aggregated read query opcode and its reply format
  scsi: ufs: Serve the device init reads from one aggregated read

 drivers/ufs/core/ufshcd.c | 364 +++++++++++++++++++++++++++++++++++---
 include/ufs/ufs.h         |  47 +++++
 include/ufs/ufshcd.h      |  11 ++
 3 files changed, 402 insertions(+), 20 deletions(-)

--
2.25.1


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v2 1/2] scsi: ufs: Add the aggregated read query opcode and its reply format
       [not found] ` <20260930050201epcms2p13ab67182445b0555acc8d27b927cda86@epcms2p1>
@ 2026-09-30  5:15   ` Hyeoncheol Jeong
  2026-09-30  7:54     ` Peter Wang
  2026-09-30  5:18   ` [PATCH v2 2/2] scsi: ufs: Serve the device init reads from one aggregated read Hyeoncheol Jeong
  1 sibling, 1 reply; 12+ messages in thread
From: Hyeoncheol Jeong @ 2026-09-30  5:15 UTC (permalink / raw)
  To: James.Bottomley@HansenPartnership.com, mkp@kernel.org,
	avri.altman@sandisk.com, bvanassche@acm.org,
	peter.wang@mediatek.com, linux-scsi@vger.kernel.org
  Cc: Jinyoung Choi, Alim Akhtar, beanhuo@micron.com,
	can.guo@oss.qualcomm.com, linux-kernel@vger.kernel.org

UFS 5.0 adds the AGGREGATED READ query opcode (0x9): a single QUERY
RESPONSE UPIU returns many descriptors, attributes and flags as a chain
of typed groups (JESD220H 10.7.9.14).

Add the AGGREGATION TYPE mask, the group types and the group header, and
let ufshcd_copy_query_response() copy the reply's data segment like a
descriptor read, recording its length in struct ufs_query_res.

Signed-off-by: Hyeoncheol Jeong <hyenc.jeong@samsung.com>
---
 drivers/ufs/core/ufshcd.c |  7 +++++--
 include/ufs/ufs.h         | 44 +++++++++++++++++++++++++++++++++++++++
 include/ufs/ufshcd.h      |  2 ++
 3 files changed, 51 insertions(+), 2 deletions(-)

diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c
index 54f4e7d7de02..d07834fc4646 100644
--- a/drivers/ufs/core/ufshcd.c
+++ b/drivers/ufs/core/ufshcd.c
@@ -2473,10 +2473,13 @@ int ufshcd_copy_query_response(struct ufs_hba *hba, struct ufshcd_lrb *lrbp)
 	struct ufs_query_res *query_res = &hba->dev_cmd.query.response;
 
 	memcpy(&query_res->upiu_res, &lrbp->ucd_rsp_ptr->qr, QUERY_OSF_SIZE);
+	query_res->data_segment_length =
+		be16_to_cpu(lrbp->ucd_rsp_ptr->header.data_segment_length);
 
-	/* Get the descriptor */
+	/* Get the descriptor or aggregated data packet */
 	if (hba->dev_cmd.query.descriptor &&
-	    lrbp->ucd_rsp_ptr->qr.opcode == UPIU_QUERY_OPCODE_READ_DESC) {
+	    (lrbp->ucd_rsp_ptr->qr.opcode == UPIU_QUERY_OPCODE_READ_DESC ||
+	     lrbp->ucd_rsp_ptr->qr.opcode == UPIU_QUERY_OPCODE_AGGREGATED_READ)) {
 		u8 *descp = (u8 *)lrbp->ucd_rsp_ptr +
 				GENERAL_UPIU_REQUEST_SIZE;
 		u16 resp_len;
diff --git a/include/ufs/ufs.h b/include/ufs/ufs.h
index afbb32654fab..1e6ea87a5dc7 100644
--- a/include/ufs/ufs.h
+++ b/include/ufs/ufs.h
@@ -30,6 +30,7 @@ static_assert(sizeof(struct utp_upiu_query) == 20);
  * (ALIGNED_DEVMAN_RSP_SIZE) minus the fixed UPIU header it follows.
  */
 #define QUERY_AGGREGATED_MAX_SIZE (4096 - GENERAL_UPIU_REQUEST_SIZE)
+#define QUERY_AGG_GROUP_HDR_SIZE  4
 #define QUERY_DESC_MIN_SIZE       2
 #define QUERY_DESC_HDR_SIZE       2
 #define QUERY_OSF_SIZE            (GENERAL_UPIU_REQUEST_SIZE - \
@@ -569,6 +570,49 @@ enum ufs_dev_pwr_mode {
 
 #define UFS_WB_BUF_REMAIN_PERCENT(val) ((val) / 10)
 
+/* AGGREGATION TYPE field of an AGGREGATED READ query request */
+enum ufs_agg_type {
+	UFS_AGG_TYPE_ALL_FLAGS			= BIT(0),
+	UFS_AGG_TYPE_ALL_ATTRS			= BIT(1),
+	UFS_AGG_TYPE_DEVICE_DESC		= BIT(2),
+	/* Unit descriptors and the RPMB unit descriptor */
+	UFS_AGG_TYPE_UNIT_RPMB_DESC		= BIT(3),
+	UFS_AGG_TYPE_INTERCONNECT_DESC	= BIT(4),
+	UFS_AGG_TYPE_STRING_DESC		= BIT(5),
+	/* Geometry descriptor and power descriptor */
+	UFS_AGG_TYPE_GEOMETRY_POWER_DESC	= BIT(6),
+	UFS_AGG_TYPE_HEALTH_DESC		= BIT(7),
+};
+
+/* Group Type field of an aggregated data packet group header. */
+enum ufs_agg_group_type {
+	UFS_AGG_GROUP_RESERVED		= 0x00,
+	UFS_AGG_GROUP_FLAGS		= 0x01,
+	UFS_AGG_GROUP_ATTRS		= 0x02,
+	/* Any descriptor with a unique IDN, i.e. all but the string ones */
+	UFS_AGG_GROUP_DESCS		= 0x03,
+	UFS_AGG_GROUP_MANUFACTURER_STR	= 0x04,
+	UFS_AGG_GROUP_PRODUCT_NAME_STR	= 0x05,
+	UFS_AGG_GROUP_OEM_ID_STR	= 0x06,
+	UFS_AGG_GROUP_SERIAL_NUMBER_STR	= 0x07,
+	UFS_AGG_GROUP_PRODUCT_REV_STR	= 0x08,
+};
+
+/**
+ * struct utp_agg_group_header - aggregated data packet group header
+ * @group_type: type of the data carried by this group, see ufs_agg_group_type
+ * @reserved: reserved
+ * @next_group_offset: byte offset from the start of the packet to the next
+ *	group; zero ends the chain
+ */
+struct utp_agg_group_header {
+	u8 group_type;
+	u8 reserved;
+	__be16 next_group_offset;
+};
+
+static_assert(sizeof(struct utp_agg_group_header) == QUERY_AGG_GROUP_HDR_SIZE);
+
 /**
  * struct utp_cmd_rsp - RESPONSE UPIU structure
  * @residual_transfer_count: Residual transfer count DW-3
diff --git a/include/ufs/ufshcd.h b/include/ufs/ufshcd.h
index dfd302f2dc7c..1a5e181ff9e3 100644
--- a/include/ufs/ufshcd.h
+++ b/include/ufs/ufshcd.h
@@ -213,9 +213,11 @@ struct ufs_query_req {
 /**
  * struct ufs_query_res - UPIU QUERY
  * @upiu_res: query response data
+ * @data_segment_length: response data segment length
  */
 struct ufs_query_res {
 	struct utp_upiu_query upiu_res;
+	u16 data_segment_length;
 };
 
 /**
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v2 2/2] scsi: ufs: Serve the device init reads from one aggregated read
       [not found] ` <20260930050201epcms2p13ab67182445b0555acc8d27b927cda86@epcms2p1>
  2026-09-30  5:15   ` [PATCH v2 1/2] scsi: ufs: Add the aggregated read query opcode and its reply format Hyeoncheol Jeong
@ 2026-09-30  5:18   ` Hyeoncheol Jeong
  2026-09-30  7:28     ` sashiko-bot
                       ` (3 more replies)
  1 sibling, 4 replies; 12+ messages in thread
From: Hyeoncheol Jeong @ 2026-09-30  5:18 UTC (permalink / raw)
  To: James.Bottomley@HansenPartnership.com, mkp@kernel.org,
	avri.altman@sandisk.com, bvanassche@acm.org,
	peter.wang@mediatek.com, linux-scsi@vger.kernel.org
  Cc: Jinyoung Choi, Alim Akhtar, beanhuo@micron.com,
	can.guo@oss.qualcomm.com, linux-kernel@vger.kernel.org

Device init reads several attributes and flags one query at a time. On
UFS 5.0, issue one AGGREGATED READ once the device descriptor has
established wSpecVersion, cache the reply, and serve the following
attribute, flag and ID-string reads from it. Anything not present, and
older devices or a rejected opcode, fall back to individual queries.

The following per-item queries are folded into the one aggregated read:

Attributes (ALL_ATTRS group)
	0x0c bMaxNumOfRTT                  - ufshcd_set_rtt()
	0x17 bRefClkGatingWaitTime         - ufshcd_get_ref_clk_gating_wait()
	0x1e bWriteBoosterBufferLifeTimeEst - ufshcd_wb_probe()

Flags (ALL_FLAGS group)
	0x03 fPowerOnWPEn                  - ufshcd_device_params_init()

Strings (STRING_DESC groups)
	Product Name string                - ufs_get_device_desc()
	Serial Number string               - ufshcd_create_device_id()

(bWriteBoosterBufferLifeTimeEst is served from the packet only in the
shared-buffer mode)

Signed-off-by: Hyeoncheol Jeong <hyenc.jeong@samsung.com>
---
 drivers/ufs/core/ufshcd.c | 357 ++++++++++++++++++++++++++++++++++++--
 include/ufs/ufs.h         |   3 +
 include/ufs/ufshcd.h      |   9 +
 3 files changed, 351 insertions(+), 18 deletions(-)

diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c
index d07834fc4646..e4135d4a5812 100644
--- a/drivers/ufs/core/ufshcd.c
+++ b/drivers/ufs/core/ufshcd.c
@@ -3792,6 +3792,314 @@ int ufshcd_query_descriptor_retry(struct ufs_hba *hba,
 	return err;
 }
 
+/* Attribute data width by IDN for aggregated read (JESD220H Table 14.28). */
+static const u8 ufs_agg_attr_width[] = {
+	[QUERY_ATTR_IDN_BOOT_LU_EN] = 1, [QUERY_ATTR_IDN_POWER_MODE] = 1,
+	[QUERY_ATTR_IDN_ACTIVE_ICC_LVL] = 1, [QUERY_ATTR_IDN_OOO_DATA_EN] = 1,
+	[QUERY_ATTR_IDN_BKOPS_STATUS] = 1, [QUERY_ATTR_IDN_PURGE_STATUS] = 1,
+	[QUERY_ATTR_IDN_MAX_DATA_IN] = 1, [QUERY_ATTR_IDN_MAX_DATA_OUT] = 1,
+	[QUERY_ATTR_IDN_DYN_CAP_NEEDED] = 4, [QUERY_ATTR_IDN_REF_CLK_FREQ] = 1,
+	[QUERY_ATTR_IDN_CONF_DESC_LOCK] = 1, [QUERY_ATTR_IDN_MAX_NUM_OF_RTT] = 1,
+	[QUERY_ATTR_IDN_EE_CONTROL] = 2, [QUERY_ATTR_IDN_EE_STATUS] = 2,
+	[QUERY_ATTR_IDN_SECONDS_PASSED] = 4, [QUERY_ATTR_IDN_CNTX_CONF] = 2,
+	[QUERY_ATTR_IDN_FFU_STATUS] = 1, [QUERY_ATTR_IDN_PSA_STATE] = 1,
+	[QUERY_ATTR_IDN_PSA_DATA_SIZE] = 4,
+	[QUERY_ATTR_IDN_REF_CLK_GATING_WAIT_TIME] = 1,
+	[QUERY_ATTR_IDN_CASE_ROUGH_TEMP] = 1, [QUERY_ATTR_IDN_HIGH_TEMP_BOUND] = 1,
+	[QUERY_ATTR_IDN_LOW_TEMP_BOUND] = 1, [0x1b] = 1,
+	[QUERY_ATTR_IDN_WB_FLUSH_STATUS] = 1,
+	[QUERY_ATTR_IDN_AVAIL_WB_BUFF_SIZE] = 1,
+	[QUERY_ATTR_IDN_WB_BUFF_LIFE_TIME_EST] = 1,
+	[QUERY_ATTR_IDN_CURR_WB_BUFF_SIZE] = 4,
+};
+
+/**
+ * ufshcd_agg_group - find a group in the cached aggregated data packet
+ * @hba: per-adapter instance
+ * @type: group type to find
+ * @group_len: set to the group payload length on a hit
+ *
+ * Return: the group payload, or NULL if @type is not present.
+ */
+static const u8 *ufshcd_agg_group(const struct ufs_hba *hba, u8 type,
+				  u16 *group_len)
+{
+	const u8 *packet = hba->agg_packet;
+	u16 packet_len = hba->agg_packet_len;
+	u16 group_off = 0;
+
+	while (packet && group_off + QUERY_AGG_GROUP_HDR_SIZE <= packet_len) {
+		const struct utp_agg_group_header *hdr =
+			(const void *)(packet + group_off);
+		u16 next_group_off = be16_to_cpu(hdr->next_group_offset);
+		u16 payload_off = group_off + QUERY_AGG_GROUP_HDR_SIZE;
+
+		if (next_group_off && (next_group_off < payload_off ||
+				       next_group_off + QUERY_AGG_GROUP_HDR_SIZE > packet_len))
+			return NULL;
+
+		if (hdr->group_type == type) {
+			*group_len = (next_group_off ? next_group_off : packet_len) -
+				     payload_off;
+			return packet + payload_off;
+		}
+
+		if (!next_group_off)
+			break;
+
+		group_off = next_group_off;
+	}
+
+	return NULL;
+}
+
+/**
+ * ufshcd_agg_string - find a string descriptor in the cached aggregated packet
+ * @hba: per-adapter instance
+ * @index: string descriptor index
+ * @len: set to the descriptor length on a hit
+ *
+ * Return: the string descriptor, or NULL if not present.
+ */
+static const u8 *ufshcd_agg_string(struct ufs_hba *hba, u8 index, u8 *len)
+{
+	u16 group_len;
+	const u8 *group_buf;
+	int i;
+
+	if (!index)
+		return NULL;
+
+	for (i = 0; i < ARRAY_SIZE(hba->agg_str_idx); i++)
+		if (hba->agg_str_idx[i] == index)
+			break;
+
+	if (i == ARRAY_SIZE(hba->agg_str_idx))
+		return NULL;
+
+	group_buf = ufshcd_agg_group(hba, UFS_AGG_GROUP_MANUFACTURER_STR + i,
+				     &group_len);
+	if (!group_buf || group_len < QUERY_DESC_HDR_SIZE)
+		return NULL;
+
+	*len = group_buf[QUERY_DESC_LENGTH_OFFSET];
+	if (*len < QUERY_DESC_HDR_SIZE || *len > group_len)
+		return NULL;
+
+	return group_buf;
+}
+
+/**
+ * ufshcd_agg_desc - find a descriptor in the cached aggregated data packet
+ * @hba: per-adapter instance
+ * @idn: descriptor IDN
+ * @index: unit index, for unit descriptors; string index for string ones
+ * @len: set to the descriptor length on a hit
+ *
+ * Return: the descriptor, or NULL if not present.
+ */
+static const u8 *ufshcd_agg_desc(struct ufs_hba *hba, enum desc_idn idn,
+				 u8 index, u8 *len)
+{
+	u16 group_len, off = 0;
+	const u8 *group_buf;
+
+	if (idn == QUERY_DESC_IDN_STRING)
+		return ufshcd_agg_string(hba, index, len);
+
+	group_buf = ufshcd_agg_group(hba, UFS_AGG_GROUP_DESCS, &group_len);
+	while (group_buf && off + QUERY_DESC_HDR_SIZE <= group_len) {
+		const u8 *desc_buf = group_buf + off;
+		u8 desc_len = desc_buf[QUERY_DESC_LENGTH_OFFSET];
+
+		if (desc_len < QUERY_DESC_HDR_SIZE || off + desc_len > group_len)
+			break;
+
+		if (desc_buf[QUERY_DESC_DESC_TYPE_OFFSET] == idn &&
+		    (idn != QUERY_DESC_IDN_UNIT ||
+		     (desc_len > UNIT_DESC_PARAM_UNIT_INDEX &&
+		      desc_buf[UNIT_DESC_PARAM_UNIT_INDEX] == index))) {
+			*len = desc_len;
+			return desc_buf;
+		}
+
+		off += desc_len;
+	}
+
+	return NULL;
+}
+
+/**
+ * ufshcd_agg_attr - read an attribute from the cached aggregated data packet
+ * @hba: per-adapter instance
+ * @idn: attribute IDN (index 0 only)
+ * @out: set to the value on a hit
+ *
+ * Return: 0 on a hit, -ENOENT otherwise.
+ */
+static int ufshcd_agg_attr(struct ufs_hba *hba, u8 idn, u32 *out)
+{
+	u16 group_len, off = 0;
+	const u8 *group_buf;
+	int i;
+
+	if (idn >= ARRAY_SIZE(ufs_agg_attr_width) || !ufs_agg_attr_width[idn])
+		return -ENOENT;
+
+	group_buf = ufshcd_agg_group(hba, UFS_AGG_GROUP_ATTRS, &group_len);
+	if (!group_buf)
+		return -ENOENT;
+
+	for (i = 0; i < idn; i++)
+		off += ufs_agg_attr_width[i];
+
+	if (off + ufs_agg_attr_width[idn] > group_len)
+		return -ENOENT;
+
+	*out = 0;
+	for (i = 0; i < ufs_agg_attr_width[idn]; i++)
+		*out = (*out << 8) | group_buf[off + i];
+
+	return 0;
+}
+
+/**
+ * ufshcd_agg_flag - read a flag from the cached aggregated data packet
+ * @hba: per-adapter instance
+ * @idn: flag IDN
+ * @out: set to the value on a hit
+ *
+ * Return: 0 on a hit, -ENOENT otherwise.
+ */
+static int ufshcd_agg_flag(struct ufs_hba *hba, u8 idn, bool *out)
+{
+	u16 group_len;
+	const u8 *group_buf = ufshcd_agg_group(hba, UFS_AGG_GROUP_FLAGS,
+					       &group_len);
+
+	if (!group_buf || idn >= group_len)
+		return -ENOENT;
+
+	*out = group_buf[idn];
+	return 0;
+}
+
+/**
+ * ufshcd_read_attr - read an attribute, from the cached packet if present
+ * @hba: per-adapter instance
+ * @idn: attribute IDN
+ * @index: attribute index
+ * @out: result
+ *
+ * Return: 0 on success, < 0 on error.
+ */
+static int ufshcd_read_attr(struct ufs_hba *hba, u8 idn, u8 index, u32 *out)
+{
+	if (index == 0 && !ufshcd_agg_attr(hba, idn, out))
+		return 0;
+
+	return ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_READ_ATTR, idn,
+				       index, 0, out);
+}
+
+/**
+ * ufshcd_read_flag - read a flag, from the cached packet if present
+ * @hba: per-adapter instance
+ * @idn: flag IDN
+ * @out: result
+ *
+ * Return: 0 on success, < 0 on error.
+ */
+static int ufshcd_read_flag(struct ufs_hba *hba, u8 idn, bool *out)
+{
+	if (!ufshcd_agg_flag(hba, idn, out))
+		return 0;
+
+	return ufshcd_query_flag_retry(hba, UPIU_QUERY_OPCODE_READ_FLAG, idn, 0,
+				       out);
+}
+
+/**
+ * ufshcd_query_aggregated_read - issue an AGGREGATED READ query
+ * @hba: per-adapter instance
+ * @agg_type: AGGREGATION TYPE mask (OSF) selecting the items to fetch
+ * @buf: buffer for the reply data segment
+ * @buf_len: requested length in, received length out
+ *
+ * Return: 0 on success; > 0 on an OCS error; < 0 otherwise.
+ */
+static int ufshcd_query_aggregated_read(struct ufs_hba *hba, u8 agg_type,
+					u8 *buf, int *buf_len)
+{
+	struct ufs_query_req *request = NULL;
+	struct ufs_query_res *response = NULL;
+	int err;
+
+	if (*buf_len <= 0 || *buf_len > QUERY_AGGREGATED_MAX_SIZE)
+		return -EINVAL;
+
+	ufshcd_dev_man_lock(hba);
+	ufshcd_init_query(hba, &request, &response,
+			  UPIU_QUERY_OPCODE_AGGREGATED_READ, agg_type, 0, 0);
+	request->query_func = UPIU_QUERY_FUNC_STANDARD_READ_REQUEST;
+	request->upiu_req.length = cpu_to_be16(*buf_len);
+	hba->dev_cmd.query.descriptor = buf;
+
+	err = ufshcd_exec_dev_cmd(hba, DEV_CMD_TYPE_QUERY, dev_cmd_timeout);
+	if (!err)
+		*buf_len = response->data_segment_length;
+
+	hba->dev_cmd.query.descriptor = NULL;
+	ufshcd_dev_man_unlock(hba);
+	return err;
+}
+
+/**
+ * ufshcd_agg_read_begin - cache one AGGREGATED READ for the reads that follow
+ * @hba: per-adapter instance
+ * @agg_type: AGGREGATION TYPE mask to fetch
+ *
+ * Paired with ufshcd_agg_read_end(). Requires a UFS 5.0 device.
+ */
+static void ufshcd_agg_read_begin(struct ufs_hba *hba, u8 agg_type)
+{
+	int len = QUERY_AGGREGATED_MAX_SIZE;
+	int retries;
+	u8 *packet;
+
+	if (hba->dev_info.wspecversion < 0x500 ||
+	    hba->dev_info.agg_read_unsupported)
+		return;
+
+	/* Zeroed so a device over-reporting LENGTH terminates the walk safely. */
+	packet = kzalloc(QUERY_AGGREGATED_MAX_SIZE, GFP_KERNEL);
+	if (!packet)
+		return;
+
+	for (retries = QUERY_REQ_RETRIES; retries > 0; retries--) {
+		if (!ufshcd_query_aggregated_read(hba, agg_type, packet, &len))
+			break;
+	}
+
+	if (!retries) {
+		dev_dbg(hba->dev, "aggregated read unsupported, using individual queries\n");
+		hba->dev_info.agg_read_unsupported = true;
+		kfree(packet);
+		return;
+	}
+
+	hba->agg_packet = packet;
+	hba->agg_packet_len = len;
+}
+
+/* ufshcd_agg_read_end - drop the packet cached by ufshcd_agg_read_begin() */
+static void ufshcd_agg_read_end(struct ufs_hba *hba)
+{
+	kfree(hba->agg_packet);
+	hba->agg_packet = NULL;
+	hba->agg_packet_len = 0;
+}
+
 /**
  * ufshcd_read_desc_param - read the specified descriptor parameter
  * @hba: Pointer to adapter instance
@@ -3813,6 +4121,8 @@ int ufshcd_read_desc_param(struct ufs_hba *hba,
 {
 	int ret;
 	u8 *desc_buf;
+	const u8 *descp;
+	u8 desc_len;
 	int buff_len = QUERY_DESC_MAX_SIZE;
 	bool is_kmalloc = true;
 
@@ -3830,14 +4140,20 @@ int ufshcd_read_desc_param(struct ufs_hba *hba,
 		is_kmalloc = false;
 	}
 
-	/* Request for full descriptor */
-	ret = ufshcd_query_descriptor_retry(hba, UPIU_QUERY_OPCODE_READ_DESC,
-					    desc_id, desc_index, 0,
-					    desc_buf, &buff_len);
-	if (ret) {
-		dev_err(hba->dev, "%s: Failed reading descriptor. desc_id %d, desc_index %d, param_offset %d, ret %d\n",
-			__func__, desc_id, desc_index, param_offset, ret);
-		goto out;
+	/* Serve from the cached aggregated packet, else request the full one. */
+	descp = ufshcd_agg_desc(hba, desc_id, desc_index, &desc_len);
+	if (descp) {
+		memcpy(desc_buf, descp, desc_len);
+		ret = 0;
+	} else {
+		ret = ufshcd_query_descriptor_retry(hba, UPIU_QUERY_OPCODE_READ_DESC,
+						    desc_id, desc_index, 0,
+						    desc_buf, &buff_len);
+		if (ret) {
+			dev_err(hba->dev, "%s: Failed reading descriptor. desc_id %d, desc_index %d, param_offset %d, ret %d\n",
+				__func__, desc_id, desc_index, param_offset, ret);
+			goto out;
+		}
 	}
 
 	/* Update descriptor length */
@@ -4019,9 +4335,8 @@ static int ufshcd_get_ref_clk_gating_wait(struct ufs_hba *hba)
 	u32 gating_wait = UFSHCD_REF_CLK_GATING_WAIT_US;
 
 	if (hba->dev_info.wspecversion >= 0x300) {
-		err = ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_READ_ATTR,
-				QUERY_ATTR_IDN_REF_CLK_GATING_WAIT_TIME, 0, 0,
-				&gating_wait);
+		err = ufshcd_read_attr(hba, QUERY_ATTR_IDN_REF_CLK_GATING_WAIT_TIME,
+				       0, &gating_wait);
 		if (err)
 			dev_err(hba->dev, "Failed reading bRefClkGatingWait. err = %d, use default %uus\n",
 					 err, gating_wait);
@@ -6531,9 +6846,8 @@ static bool ufshcd_is_wb_buf_lifetime_available(struct ufs_hba *hba)
 	u8 index;
 
 	index = ufshcd_wb_get_query_index(hba);
-	ret = ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_READ_ATTR,
-				      QUERY_ATTR_IDN_WB_BUFF_LIFE_TIME_EST,
-				      index, 0, &lifetime);
+	ret = ufshcd_read_attr(hba, QUERY_ATTR_IDN_WB_BUFF_LIFE_TIME_EST,
+			       index, &lifetime);
 	if (ret) {
 		dev_err(hba->dev,
 			"%s: bWriteBoosterBufferLifeTimeEst read failed %d\n",
@@ -8677,8 +8991,7 @@ static void ufshcd_set_rtt(struct ufs_hba *hba)
 	if (dev_info->wspecversion < 0x400)
 		return;
 
-	if (ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_READ_ATTR,
-				    QUERY_ATTR_IDN_MAX_NUM_OF_RTT, 0, 0, &dev_rtt)) {
+	if (ufshcd_read_attr(hba, QUERY_ATTR_IDN_MAX_NUM_OF_RTT, 0, &dev_rtt)) {
 		dev_err(hba->dev, "failed reading bMaxNumOfRTT\n");
 		return;
 	}
@@ -8899,6 +9212,14 @@ static int ufs_get_device_desc(struct ufs_hba *hba)
 				      desc_buf[DEVICE_DESC_PARAM_SPEC_VER + 1];
 	dev_info->bqueuedepth = desc_buf[DEVICE_DESC_PARAM_Q_DPTH];
 
+	hba->agg_str_idx[0] = desc_buf[DEVICE_DESC_PARAM_MANF_NAME];
+	hba->agg_str_idx[1] = desc_buf[DEVICE_DESC_PARAM_PRDCT_NAME];
+	hba->agg_str_idx[2] = desc_buf[DEVICE_DESC_PARAM_OEM_ID];
+	hba->agg_str_idx[3] = desc_buf[DEVICE_DESC_PARAM_SN];
+	hba->agg_str_idx[4] = desc_buf[DEVICE_DESC_PARAM_PRDCT_REV];
+	ufshcd_agg_read_begin(hba, UFS_AGG_TYPE_ALL_ATTRS | UFS_AGG_TYPE_ALL_FLAGS |
+			      UFS_AGG_TYPE_STRING_DESC);
+
 	/*
 	 * According to the UFS standard, the UFS device queue depth
 	 * (bQueueDepth) must be in the range 1..255 if the shared queueing
@@ -9214,8 +9535,7 @@ static int ufshcd_device_params_init(struct ufs_hba *hba)
 
 	ufshcd_get_ref_clk_gating_wait(hba);
 
-	if (!ufshcd_query_flag_retry(hba, UPIU_QUERY_OPCODE_READ_FLAG,
-			QUERY_FLAG_IDN_PWR_ON_WPE, 0, &flag))
+	if (!ufshcd_read_flag(hba, QUERY_FLAG_IDN_PWR_ON_WPE, &flag))
 		hba->dev_info.f_power_on_wp_en = flag;
 
 	/* Probe maximum power mode co-supported by both UFS host and device */
@@ -9226,6 +9546,7 @@ static int ufshcd_device_params_init(struct ufs_hba *hba)
 
 	ufshcd_retrieve_tx_eq_settings(hba);
 out:
+	ufshcd_agg_read_end(hba);
 	return ret;
 }
 
diff --git a/include/ufs/ufs.h b/include/ufs/ufs.h
index 1e6ea87a5dc7..e29552d6acc2 100644
--- a/include/ufs/ufs.h
+++ b/include/ufs/ufs.h
@@ -707,6 +707,9 @@ struct ufs_dev_info {
 
 	bool hid_sup;
 
+	/* Set once the device has rejected an AGGREGATED READ query. */
+	bool agg_read_unsupported;
+
 	/* Unique device ID string (manufacturer+model+serial+version+date) */
 	char *device_id;
 	u8 rpmb_io_size;
diff --git a/include/ufs/ufshcd.h b/include/ufs/ufshcd.h
index 1a5e181ff9e3..53a7573890cd 100644
--- a/include/ufs/ufshcd.h
+++ b/include/ufs/ufshcd.h
@@ -1098,6 +1098,10 @@ enum ufshcd_mcq_opr {
  * @device_deemphasis_cap: a bitfield to indicate supported DeEmphasis dBs of device's TX lanes,
  *	cache of device M-PHY TX_HS_DeEmphasis_Setting_Capability Attribute (ID 0x12)
  * @tx_eq_params: TX Equalization settings
+ * @agg_packet: aggregated data packet cached during a read batch, or NULL
+ * @agg_packet_len: length of @agg_packet
+ * @agg_str_idx: string descriptor index per string group, indexed by
+ *	group type - UFS_AGG_GROUP_MANUFACTURER_STR
  */
 struct ufs_hba {
 	void __iomem *mmio_base;
@@ -1282,6 +1286,11 @@ struct ufs_hba {
 	u8 device_preshoot_cap;
 	u8 device_deemphasis_cap;
 	struct ufshcd_tx_eq_params tx_eq_params[UFS_HS_GEAR_MAX];
+
+	u8 *agg_packet;
+	u16 agg_packet_len;
+	u8 agg_str_idx[UFS_AGG_GROUP_PRODUCT_REV_STR -
+		       UFS_AGG_GROUP_MANUFACTURER_STR + 1];
 };
 
 /**
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* Re: [PATCH v2 2/2] scsi: ufs: Serve the device init reads from one aggregated read
  2026-09-30  5:18   ` [PATCH v2 2/2] scsi: ufs: Serve the device init reads from one aggregated read Hyeoncheol Jeong
@ 2026-09-30  7:28     ` sashiko-bot
  2026-09-30  7:55     ` Peter Wang
                       ` (2 subsequent siblings)
  3 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-09-30  7:28 UTC (permalink / raw)
  To: Hyeoncheol Jeong; +Cc: linux-scsi

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] ufs: unaligned memory access in ufshcd_agg_group() crashes on bad device input
--

commit 8bc3a3d567bfb8009dd9c20e3fdb96ed162e1ae5
Author: Hyeoncheol Jeong <hyenc.jeong@samsung.com>

scsi: ufs: Serve the device init reads from one aggregated read

This commit introduces an aggregated read mechanism for UFS 5.0 devices to
fetch attributes, flags, and ID-strings in a single query during device
initialization.

> diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c
> index c18b4784a3595..028f0179ee8be 100644
> --- a/drivers/ufs/core/ufshcd.c
> +++ b/drivers/ufs/core/ufshcd.c

[ ... ]

> +static const u8 *ufshcd_agg_group(const struct ufs_hba *hba, u8 type,
> +				  u16 *group_len)
> +{
> +	const u8 *packet = hba->agg_packet;
> +	u16 packet_len = hba->agg_packet_len;
> +	u16 group_off = 0;
> +
> +	while (packet && group_off + QUERY_AGG_GROUP_HDR_SIZE <= packet_len) {
> +		const struct utp_agg_group_header *hdr =
> +			(const void *)(packet + group_off);
> +		u16 next_group_off = be16_to_cpu(hdr->next_group_offset);

[Severity: High]
Does this code safely handle unaligned memory accesses?

If a malformed or untrusted UFS device returns an odd next_group_offset, the
loop adds this odd offset to the base pointer, and the next iteration will
attempt to read the 16-bit hdr->next_group_offset from an unaligned address.

This will cause a kernel panic on architectures without unaligned access
support. Should get_unaligned_be16() be used here, or should the
utp_agg_group_header struct be marked as __packed?

> +		u16 payload_off = group_off + QUERY_AGG_GROUP_HDR_SIZE;
> +
> +		if (next_group_off && (next_group_off < payload_off ||
> +				       next_group_off + QUERY_AGG_GROUP_HDR_SIZE > packet_len))
> +			return NULL;

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/1860250540.01790752381858.JavaMail.epsvc@epcpadp2new?part=2

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v2 1/2] scsi: ufs: Add the aggregated read query opcode and its reply format
  2026-09-30  5:15   ` [PATCH v2 1/2] scsi: ufs: Add the aggregated read query opcode and its reply format Hyeoncheol Jeong
@ 2026-09-30  7:54     ` Peter Wang
  0 siblings, 0 replies; 12+ messages in thread
From: Peter Wang @ 2026-09-30  7:54 UTC (permalink / raw)
  To: hyenc.jeong, James.Bottomley@HansenPartnership.com,
	mkp@kernel.org, avri.altman@sandisk.com, bvanassche@acm.org,
	linux-scsi@vger.kernel.org
  Cc: Jinyoung Choi, Alim Akhtar, beanhuo@micron.com,
	can.guo@oss.qualcomm.com, linux-kernel@vger.kernel.org

On Wed, 2026-09-30 at 14:15 +0900, Hyeoncheol Jeong wrote:
> UFS 5.0 adds the AGGREGATED READ query opcode (0x9): a single QUERY
> RESPONSE UPIU returns many descriptors, attributes and flags as a
> chain
> of typed groups (JESD220H 10.7.9.14).
> 
> Add the AGGREGATION TYPE mask, the group types and the group header,
> and
> let ufshcd_copy_query_response() copy the reply's data segment like a
> descriptor read, recording its length in struct ufs_query_res.
> 
> Signed-off-by: Hyeoncheol Jeong <hyenc.jeong@samsung.com>

Reviewed-by: Peter Wang <peter.wang@mediatek.com>


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v2 2/2] scsi: ufs: Serve the device init reads from one aggregated read
  2026-09-30  5:18   ` [PATCH v2 2/2] scsi: ufs: Serve the device init reads from one aggregated read Hyeoncheol Jeong
  2026-09-30  7:28     ` sashiko-bot
@ 2026-09-30  7:55     ` Peter Wang
  2026-10-01  5:53     ` Stanley Jhu
  2026-10-02 15:50     ` Bean Huo
  3 siblings, 0 replies; 12+ messages in thread
From: Peter Wang @ 2026-09-30  7:55 UTC (permalink / raw)
  To: hyenc.jeong, James.Bottomley@HansenPartnership.com,
	mkp@kernel.org, avri.altman@sandisk.com, bvanassche@acm.org,
	linux-scsi@vger.kernel.org
  Cc: Jinyoung Choi, Alim Akhtar, beanhuo@micron.com,
	can.guo@oss.qualcomm.com, linux-kernel@vger.kernel.org

On Wed, 2026-09-30 at 14:18 +0900, Hyeoncheol Jeong wrote:
> Device init reads several attributes and flags one query at a time.
> On
> UFS 5.0, issue one AGGREGATED READ once the device descriptor has
> established wSpecVersion, cache the reply, and serve the following
> attribute, flag and ID-string reads from it. Anything not present,
> and
> older devices or a rejected opcode, fall back to individual queries.
> 
> The following per-item queries are folded into the one aggregated
> read:
> 
> Attributes (ALL_ATTRS group)
> 	0x0c bMaxNumOfRTT                  - ufshcd_set_rtt()
> 	0x17 bRefClkGatingWaitTime         -
> ufshcd_get_ref_clk_gating_wait()
> 	0x1e bWriteBoosterBufferLifeTimeEst - ufshcd_wb_probe()
> 
> Flags (ALL_FLAGS group)
> 	0x03 fPowerOnWPEn                  -
> ufshcd_device_params_init()
> 
> Strings (STRING_DESC groups)
> 	Product Name string                - ufs_get_device_desc()
> 	Serial Number string               -
> ufshcd_create_device_id()
> 
> (bWriteBoosterBufferLifeTimeEst is served from the packet only in the
> shared-buffer mode)
> 
> Signed-off-by: Hyeoncheol Jeong <hyenc.jeong@samsung.com>
> ---

Reviewed-by: Peter Wang <peter.wang@mediatek.com>


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v2 2/2] scsi: ufs: Serve the device init reads from one aggregated read
  2026-09-30  5:18   ` [PATCH v2 2/2] scsi: ufs: Serve the device init reads from one aggregated read Hyeoncheol Jeong
  2026-09-30  7:28     ` sashiko-bot
  2026-09-30  7:55     ` Peter Wang
@ 2026-10-01  5:53     ` Stanley Jhu
  2026-10-02  5:11       ` Hyeoncheol Jeong
  2026-10-02 15:50     ` Bean Huo
  3 siblings, 1 reply; 12+ messages in thread
From: Stanley Jhu @ 2026-10-01  5:53 UTC (permalink / raw)
  To: Hyeoncheol Jeong
  Cc: James.Bottomley, mkp, avri.altman, bvanassche, peter.wang,
	linux-scsi, alim.akhtar, Stanley Jhu

On Wed, 30 Sep 2026 14:18:25 +0900, Hyeoncheol Jeong wrote:
> +	for (i = 0; i < idn; i++)
> +		off += ufs_agg_attr_width[i];

This assumes the Attributes group (Group Type 02h) is serialized by
ascending IDN at fixed offsets. JESD220H Section 10.7.9.14 and
Table 10.56 define only the group header and Next Group Offset chain;
the intra-group layout and ordering are not specified.

How does this handle:

- Array attributes: wContextConf (10h) requires INDEX=LUN and
  SELECTOR=ContextID, where valid SELECTOR values are 01h..0Fh so 00h
  is invalid (Table 14.28), yet AGGREGATED READ leaves bytes 14-17
  Reserved (Table 10.42). Assuming 2 bytes in ufs_agg_attr_width[]
  breaks if a device omits it or returns all elements.

- Optional attributes: if a device omits unimplemented optional
  attributes such as PSA (15h, 16h) instead of zero-padding them,
  subsequent attributes shift (e.g. bWriteBoosterBufferLifeTimeEst at
  1Eh shifts by 5 bytes) with no per-entry framing to detect it.

- Write-only attributes: ufs_agg_attr_width[] reserves 4 bytes for
  dSecondsPassed (0Fh), which Table 14.28 marks Write only. Whether a
  device serializes write-only attributes in a read response is
  unspecified.

> +	if (index == 0 && !ufshcd_agg_attr(hba, idn, out))
> +		return 0;

The commit message states that bWriteBoosterBufferLifeTimeEst is
served from the packet only in shared-buffer mode. In LU-dedicated
mode (bWriteBoosterBufferType != 01h, Table 14.28 NOTE 15) with
wb_dedicated_lu == 0, ufshcd_wb_get_query_index() also returns 0, so
this check serves LU 0's lifetime from a packet with no LUN qualifier.

> +	if (!group_buf || idn >= group_len)
> +		return -ENOENT;
> +
> +	*out = group_buf[idn];

The Flags group (Group Type 01h) has the same issue: Table 14.26
NOTE 1 distinguishes device-level flags from array flags addressed by
INDEX and SELECTOR, and the specification defines no 1-byte-per-IDN
packed layout.

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v2 2/2] scsi: ufs: Serve the device init reads from one aggregated read
  2026-10-01  5:53     ` Stanley Jhu
@ 2026-10-02  5:11       ` Hyeoncheol Jeong
  0 siblings, 0 replies; 12+ messages in thread
From: Hyeoncheol Jeong @ 2026-10-02  5:11 UTC (permalink / raw)
  To: Stanley Jhu
  Cc: peter.wang@mediatek.com, linux-scsi@vger.kernel.org,
	Jinyoung Choi

Hi Stanley,

Thank you for taking the time to review this so carefully.

On Thu, 1 Oct 2026 13:53:07 +0800, Stanley Jhu wrote:
> On Wed, 30 Sep 2026 14:18:25 +0900, Hyeoncheol Jeong wrote:
> > +    for (i = 0; i < idn; i++)
> > +        off += ufs_agg_attr_width[i];
>
> This assumes the Attributes group (Group Type 02h) is serialized by
> ascending IDN at fixed offsets. JESD220H Section 10.7.9.14 and
> Table 10.56 define only the group header and Next Group Offset chain;
> the intra-group layout and ordering are not specified.

You are right. The layout of entries within the Attributes and Flags
groups is not defined in JESD220H.

> > +    if (index == 0 && !ufshcd_agg_attr(hba, idn, out))
> > +        return 0;
>
> The commit message states that bWriteBoosterBufferLifeTimeEst is
> served from the packet only in shared-buffer mode. In LU-dedicated
> mode (bWriteBoosterBufferType != 01h, Table 14.28 NOTE 15) with
> wb_dedicated_lu == 0, ufshcd_wb_get_query_index() also returns 0, so
> this check serves LU 0's lifetime from a packet with no LUN qualifier.

Thank you for catching this. Checking index == 0 does not limit this
to the shared-buffer mode. This code will be dropped with patch 2.

> > +    if (!group_buf || idn >= group_len)
> > +        return -ENOENT;
> > +
> > +    *out = group_buf[idn];
>
> The Flags group (Group Type 01h) has the same issue: Table 14.26
> NOTE 1 distinguishes device-level flags from array flags addressed by
> INDEX and SELECTOR, and the specification defines no 1-byte-per-IDN
> packed layout.

I agree that the same concern applies to the Flags group.

I think using these groups needs to wait until the JEDEC specification
clarifies the format. In the next version, I will keep only the
interface introduced by patch 1 and drop the users in patch 2.
The parts the specification leaves undefined will follow once
it is clarified.

Thanks,
Hyeoncheol

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v2 0/2] scsi: ufs: batch device-init reads with UFS 5.0 aggregated read
  2026-09-30  5:02 ` [PATCH v2 0/2] scsi: ufs: batch device-init reads with UFS 5.0 " Hyeoncheol Jeong
@ 2026-10-02  9:24   ` Bean Huo
  2026-10-06  7:17     ` Hyeoncheol Jeong
  0 siblings, 1 reply; 12+ messages in thread
From: Bean Huo @ 2026-10-02  9:24 UTC (permalink / raw)
  To: hyenc.jeong, James.Bottomley@HansenPartnership.com,
	mkp@kernel.org, avri.altman@sandisk.com, bvanassche@acm.org,
	peter.wang@mediatek.com, linux-scsi@vger.kernel.org
  Cc: Jinyoung Choi, Alim Akhtar, beanhuo@micron.com,
	can.guo@oss.qualcomm.com, linux-kernel@vger.kernel.org

On Wed, 2026-09-30 at 14:02 +0900, Hyeoncheol Jeong wrote:
> No functional change for pre-5.0 devices.
> 
> Motivation: on a Qualcomm SM8850 platform with a Samsung UFS 5.0 device,
> timing raw UPIU queries over BSG (100 iterations) shows that replacing the
> device-init read burst with a single aggregated read reduces the query
> time by 75-78% (min 75.3%, avg 76.4%, max 78.5%).


Hi Jeong,

I don't know if you have plan to enable this feature in UEFI or any bootloaders
to eliminate the boot overhead becaused of query iterations.

Kind regards, 
Bean



^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v2 2/2] scsi: ufs: Serve the device init reads from one aggregated read
  2026-09-30  5:18   ` [PATCH v2 2/2] scsi: ufs: Serve the device init reads from one aggregated read Hyeoncheol Jeong
                       ` (2 preceding siblings ...)
  2026-10-01  5:53     ` Stanley Jhu
@ 2026-10-02 15:50     ` Bean Huo
  2026-10-06  8:01       ` Hyeoncheol Jeong
  3 siblings, 1 reply; 12+ messages in thread
From: Bean Huo @ 2026-10-02 15:50 UTC (permalink / raw)
  To: hyenc.jeong, James.Bottomley@HansenPartnership.com,
	mkp@kernel.org, avri.altman@sandisk.com, bvanassche@acm.org,
	peter.wang@mediatek.com, linux-scsi@vger.kernel.org
  Cc: Jinyoung Choi, Alim Akhtar, beanhuo@micron.com,
	can.guo@oss.qualcomm.com, linux-kernel@vger.kernel.org

On Wed, 2026-09-30 at 14:18 +0900, Hyeoncheol Jeong wrote:
> Device init reads several attributes and flags one query at a time. On
> UFS 5.0, issue one AGGREGATED READ once the device descriptor has
> established wSpecVersion, cache the reply, and serve the following
> attribute, flag and ID-string reads from it. Anything not present, and
> older devices or a rejected opcode, fall back to individual queries.
> 
> The following per-item queries are folded into the one aggregated read:
> 
> Attributes (ALL_ATTRS group)
>         0x0c bMaxNumOfRTT                  - ufshcd_set_rtt()
>         0x17 bRefClkGatingWaitTime         - ufshcd_get_ref_clk_gating_wait()
>         0x1e bWriteBoosterBufferLifeTimeEst - ufshcd_wb_probe()
> 
> Flags (ALL_FLAGS group)
>         0x03 fPowerOnWPEn                  - ufshcd_device_params_init()
> 
> Strings (STRING_DESC groups)
>         Product Name string                - ufs_get_device_desc()
>         Serial Number string               - ufshcd_create_device_id()
> 
> (bWriteBoosterBufferLifeTimeEst is served from the packet only in the
> shared-buffer mode)


I suggest we should aggregate-read as many of them as possible. If we lose even
one descriptor, this feature becomes useless.

> 
> Signed-off-by: Hyeoncheol Jeong <hyenc.jeong@samsung.com>
> ---
>  drivers/ufs/core/ufshcd.c | 357 ++++++++++++++++++++++++++++++++++++--
>  include/ufs/ufs.h         |   3 +
>  include/ufs/ufshcd.h      |   9 +
>  3 files changed, 351 insertions(+), 18 deletions(-)
> 
> diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c
> index d07834fc4646..e4135d4a5812 100644
> --- a/drivers/ufs/core/ufshcd.c
> +++ b/drivers/ufs/core/ufshcd.c
> @@ -3792,6 +3792,314 @@ int ufshcd_query_descriptor_retry(struct ufs_hba *hba,
>         return err;
>  }
>  
> +/* Attribute data width by IDN for aggregated read (JESD220H Table 14.28). */
> +static const u8 ufs_agg_attr_width[] = {
> +       [QUERY_ATTR_IDN_BOOT_LU_EN] = 1, [QUERY_ATTR_IDN_POWER_MODE] = 1,
> +       [QUERY_ATTR_IDN_ACTIVE_ICC_LVL] = 1, [QUERY_ATTR_IDN_OOO_DATA_EN] = 1,
> +       [QUERY_ATTR_IDN_BKOPS_STATUS] = 1, [QUERY_ATTR_IDN_PURGE_STATUS] = 1,
> +       [QUERY_ATTR_IDN_MAX_DATA_IN] = 1, [QUERY_ATTR_IDN_MAX_DATA_OUT] = 1,
> +       [QUERY_ATTR_IDN_DYN_CAP_NEEDED] = 4, [QUERY_ATTR_IDN_REF_CLK_FREQ] =
> 1,
> +       [QUERY_ATTR_IDN_CONF_DESC_LOCK] = 1, [QUERY_ATTR_IDN_MAX_NUM_OF_RTT] =
> 1,
> +       [QUERY_ATTR_IDN_EE_CONTROL] = 2, [QUERY_ATTR_IDN_EE_STATUS] = 2,
> +       [QUERY_ATTR_IDN_SECONDS_PASSED] = 4, [QUERY_ATTR_IDN_CNTX_CONF] = 2,
> +       [QUERY_ATTR_IDN_FFU_STATUS] = 1, [QUERY_ATTR_IDN_PSA_STATE] = 1,
> +       [QUERY_ATTR_IDN_PSA_DATA_SIZE] = 4,
> +       [QUERY_ATTR_IDN_REF_CLK_GATING_WAIT_TIME] = 1,
> +       [QUERY_ATTR_IDN_CASE_ROUGH_TEMP] = 1, [QUERY_ATTR_IDN_HIGH_TEMP_BOUND]
> = 1,
> +       [QUERY_ATTR_IDN_LOW_TEMP_BOUND] = 1, [0x1b] = 1,
> +       [QUERY_ATTR_IDN_WB_FLUSH_STATUS] = 1,
> +       [QUERY_ATTR_IDN_AVAIL_WB_BUFF_SIZE] = 1,
> +       [QUERY_ATTR_IDN_WB_BUFF_LIFE_TIME_EST] = 1,
> +       [QUERY_ATTR_IDN_CURR_WB_BUFF_SIZE] = 4,
> +};
> +


table 14.28 gives the size of each attribute, but I could not find where Spec
defines the layout of the attributes group in the aggregated data packet,
section 10.7.9.14 only defines the group header.

dDynCapNeeded (09h) is an array attribute; its number of indexes is MaxNumberLU.
1Ch-1Fh can also be per-LU in dedicated WB mode. If a device sends one entry per
index, or keeps a slot for 01h or 11h-13h, every offset after it moves. Then
bMaxNumOfRTT, bRefClkGatingWaitTime and bWriteBoosterBufferLifeTimeEst are read
from the wrong bytes, with no error and no fallback to a single query.
ufshcd_set_rtt() then writes a value based on that wrong data.

Has this been tested on more than one vendor's UFS 5.0 device?

> +/**
> + * ufshcd_agg_group - find a group in the cached aggregated data packet
> + * @hba: per-adapter instance
> + * @type: group type to find
> + * @group_len: set to the group payload length on a hit
> + *
> + * Return: the group payload, or NULL if @type is not present.
> + */
> +static const u8 *ufshcd_agg_group(const struct ufs_hba *hba, u8 type,
> +                                 u16 *group_len)
> +{
> +       const u8 *packet = hba->agg_packet;
> +       u16 packet_len = hba->agg_packet_len;
> +       u16 group_off = 0;
> +
> +       while (packet && group_off + QUERY_AGG_GROUP_HDR_SIZE <= packet_len) {
> +               const struct utp_agg_group_header *hdr =
> +                       (const void *)(packet + group_off);
> +               u16 next_group_off = be16_to_cpu(hdr->next_group_offset);
> +               u16 payload_off = group_off + QUERY_AGG_GROUP_HDR_SIZE;
> +
> +               if (next_group_off && (next_group_off < payload_off ||
> +                                      next_group_off +
> QUERY_AGG_GROUP_HDR_SIZE > packet_len))
> +                       return NULL;
> +
> +               if (hdr->group_type == type) {

if the reply is cut short (the device may return less than requested), a group
that is complete but its next offset points past the end is rejected before its
type is checked.


> +                       *group_len = (next_group_off ? next_group_off :
> packet_len) -
> +                                    payload_off;
> +                       return packet + payload_off;
> +               }
> +
> +               if (!next_group_off)
> +                       break;
> +
> +               group_off = next_group_off;
> +       }
> +
> +       return NULL;
> +}
> +
> +/**
> + * ufshcd_agg_string - find a string descriptor in the cached aggregated
> packet
> + * @hba: per-adapter instance
> + * @index: string descriptor index
> + * @len: set to the descriptor length on a hit
> + *
> + * Return: the string descriptor, or NULL if not present.
> + */
> +static const u8 *ufshcd_agg_string(struct ufs_hba *hba, u8 index, u8 *len)
> +{
> +       u16 group_len;
> +       const u8 *group_buf;
> +       int i;
> +
> +       if (!index)
> +               return NULL;
> +
> +       for (i = 0; i < ARRAY_SIZE(hba->agg_str_idx); i++)
> +               if (hba->agg_str_idx[i] == index)
> +                       break;
> +
> +       if (i == ARRAY_SIZE(hba->agg_str_idx))
> +               return NULL;
> +
> +       group_buf = ufshcd_agg_group(hba, UFS_AGG_GROUP_MANUFACTURER_STR + i,
> +                                    &group_len);
> +       if (!group_buf || group_len < QUERY_DESC_HDR_SIZE)
> +               return NULL;
> +
> +       *len = group_buf[QUERY_DESC_LENGTH_OFFSET];
> +       if (*len < QUERY_DESC_HDR_SIZE || *len > group_len)
> +               return NULL;
> +
> +       return group_buf;
> +}
> +
> +/**
> + * ufshcd_agg_desc - find a descriptor in the cached aggregated data packet
> + * @hba: per-adapter instance
> + * @idn: descriptor IDN
> + * @index: unit index, for unit descriptors; string index for string ones
> + * @len: set to the descriptor length on a hit
> + *
> + * Return: the descriptor, or NULL if not present.
> + */
> +static const u8 *ufshcd_agg_desc(struct ufs_hba *hba, enum desc_idn idn,
> +                                u8 index, u8 *len)
> +{
> +       u16 group_len, off = 0;
> +       const u8 *group_buf;
> +
> +       if (idn == QUERY_DESC_IDN_STRING)
> +               return ufshcd_agg_string(hba, index, len);
> +
> +       group_buf = ufshcd_agg_group(hba, UFS_AGG_GROUP_DESCS, &group_len);
> +       while (group_buf && off + QUERY_DESC_HDR_SIZE <= group_len) {
> +               const u8 *desc_buf = group_buf + off;
> +               u8 desc_len = desc_buf[QUERY_DESC_LENGTH_OFFSET];
> +
> +               if (desc_len < QUERY_DESC_HDR_SIZE || off + desc_len >
> group_len)
> +                       break;
> +
> +               if (desc_buf[QUERY_DESC_DESC_TYPE_OFFSET] == idn &&
> +                   (idn != QUERY_DESC_IDN_UNIT ||
> +                    (desc_len > UNIT_DESC_PARAM_UNIT_INDEX &&
> +                     desc_buf[UNIT_DESC_PARAM_UNIT_INDEX] == index))) {
> +                       *len = desc_len;
> +                       return desc_buf;
> +               }
> +
> +               off += desc_len;
> +       }
> +
> +       return NULL;
> +}
> +
> +/**
> + * ufshcd_agg_attr - read an attribute from the cached aggregated data packet
> + * @hba: per-adapter instance
> + * @idn: attribute IDN (index 0 only)
> + * @out: set to the value on a hit
> + *
> + * Return: 0 on a hit, -ENOENT otherwise.
> + */
> +static int ufshcd_agg_attr(struct ufs_hba *hba, u8 idn, u32 *out)
> +{
> +       u16 group_len, off = 0;
> +       const u8 *group_buf;
> +       int i;
> +
> +       if (idn >= ARRAY_SIZE(ufs_agg_attr_width) || !ufs_agg_attr_width[idn])
> +               return -ENOENT;
> +
> +       group_buf = ufshcd_agg_group(hba, UFS_AGG_GROUP_ATTRS, &group_len);
> +       if (!group_buf)
> +               return -ENOENT;
> +
> +       for (i = 0; i < idn; i++)
> +               off += ufs_agg_attr_width[i];
> +
> +       if (off + ufs_agg_attr_width[idn] > group_len)
> +               return -ENOENT;
> +
> +       *out = 0;
> +       for (i = 0; i < ufs_agg_attr_width[idn]; i++)
> +               *out = (*out << 8) | group_buf[off + i];
> +
> +       return 0;
> +}
> +
> +/**
> + * ufshcd_agg_flag - read a flag from the cached aggregated data packet
> + * @hba: per-adapter instance
> + * @idn: flag IDN
> + * @out: set to the value on a hit
> + *
> + * Return: 0 on a hit, -ENOENT otherwise.
> + */
> +static int ufshcd_agg_flag(struct ufs_hba *hba, u8 idn, bool *out)
> +{
> +       u16 group_len;
> +       const u8 *group_buf = ufshcd_agg_group(hba, UFS_AGG_GROUP_FLAGS,
> +                                              &group_len);
> +
> +       if (!group_buf || idn >= group_len)
> +               return -ENOENT;
> +
> +       *out = group_buf[idn];
> +       return 0;
> +}
> +
> +/**
> + * ufshcd_read_attr - read an attribute, from the cached packet if present
> + * @hba: per-adapter instance
> + * @idn: attribute IDN
> + * @index: attribute index
> + * @out: result
> + *
> + * Return: 0 on success, < 0 on error.
> + */
> +static int ufshcd_read_attr(struct ufs_hba *hba, u8 idn, u8 index, u32 *out)
> +{
> +       if (index == 0 && !ufshcd_agg_attr(hba, idn, out))
> +               return 0;
> +
> +       return ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_READ_ATTR, idn,
> +                                      index, 0, out);
> +}
> +
> +/**
> + * ufshcd_read_flag - read a flag, from the cached packet if present
> + * @hba: per-adapter instance
> + * @idn: flag IDN
> + * @out: result
> + *
> + * Return: 0 on success, < 0 on error.
> + */
> +static int ufshcd_read_flag(struct ufs_hba *hba, u8 idn, bool *out)
> +{
> +       if (!ufshcd_agg_flag(hba, idn, out))
> +               return 0;
> +
> +       return ufshcd_query_flag_retry(hba, UPIU_QUERY_OPCODE_READ_FLAG, idn,
> 0,
> +                                      out);
> +}
> +
> +/**
> + * ufshcd_query_aggregated_read - issue an AGGREGATED READ query
> + * @hba: per-adapter instance
> + * @agg_type: AGGREGATION TYPE mask (OSF) selecting the items to fetch
> + * @buf: buffer for the reply data segment
> + * @buf_len: requested length in, received length out
> + *
> + * Return: 0 on success; > 0 on an OCS error; < 0 otherwise.
> + */
> +static int ufshcd_query_aggregated_read(struct ufs_hba *hba, u8 agg_type,
> +                                       u8 *buf, int *buf_len)
> +{
> +       struct ufs_query_req *request = NULL;
> +       struct ufs_query_res *response = NULL;
> +       int err;
> +
> +       if (*buf_len <= 0 || *buf_len > QUERY_AGGREGATED_MAX_SIZE)
> +               return -EINVAL;
> +
> +       ufshcd_dev_man_lock(hba);
> +       ufshcd_init_query(hba, &request, &response,
> +                         UPIU_QUERY_OPCODE_AGGREGATED_READ, agg_type, 0, 0);
> +       request->query_func = UPIU_QUERY_FUNC_STANDARD_READ_REQUEST;
> +       request->upiu_req.length = cpu_to_be16(*buf_len);
> +       hba->dev_cmd.query.descriptor = buf;
> +
> +       err = ufshcd_exec_dev_cmd(hba, DEV_CMD_TYPE_QUERY, dev_cmd_timeout);
> +       if (!err)
> +               *buf_len = response->data_segment_length;
> +
> +       hba->dev_cmd.query.descriptor = NULL;
> +       ufshcd_dev_man_unlock(hba);
> +       return err;
> +}
> +
> +/**
> + * ufshcd_agg_read_begin - cache one AGGREGATED READ for the reads that
> follow
> + * @hba: per-adapter instance
> + * @agg_type: AGGREGATION TYPE mask to fetch
> + *
> + * Paired with ufshcd_agg_read_end(). Requires a UFS 5.0 device.
> + */
> +static void ufshcd_agg_read_begin(struct ufs_hba *hba, u8 agg_type)
> +{
> +       int len = QUERY_AGGREGATED_MAX_SIZE;
> +       int retries;
> +       u8 *packet;
> +
> +       if (hba->dev_info.wspecversion < 0x500 ||
> +           hba->dev_info.agg_read_unsupported)
> +               return;

is this feature mandatory? or you assume this feature should be supported by
defualt?

  
> +
> +       /* Zeroed so a device over-reporting LENGTH terminates the walk
> safely. */
> +       packet = kzalloc(QUERY_AGGREGATED_MAX_SIZE, GFP_KERNEL);
> +       if (!packet)
> +               return;
> +
> +       for (retries = QUERY_REQ_RETRIES; retries > 0; retries--) {
> +               if (!ufshcd_query_aggregated_read(hba, agg_type, packet,
> &len))
> +                       break;
> +       }
> +
If the device rejects the opcode with a query response error, is there a reason
to retry? Retrying makes sense for a transient error, but not for "invalid
opcode"


Kind regards,
Bean



^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v2 0/2] scsi: ufs: batch device-init reads with UFS 5.0 aggregated read
  2026-10-02  9:24   ` Bean Huo
@ 2026-10-06  7:17     ` Hyeoncheol Jeong
  0 siblings, 0 replies; 12+ messages in thread
From: Hyeoncheol Jeong @ 2026-10-06  7:17 UTC (permalink / raw)
  To: Bean Huo, James.Bottomley@HansenPartnership.com, mkp@kernel.org,
	avri.altman@sandisk.com, bvanassche@acm.org,
	peter.wang@mediatek.com, linux-scsi@vger.kernel.org
  Cc: Jinyoung Choi, Alim Akhtar, beanhuo@micron.com,
	can.guo@oss.qualcomm.com, linux-kernel@vger.kernel.org

Hi Bean,

On Fri, 2 Oct 2026 11:24:15 +0200, Bean Huo wrote:
> On Wed, 2026-09-30 at 14:02 +0900, Hyeoncheol Jeong wrote:
> > Motivation: on a Qualcomm SM8850 platform with a Samsung UFS 5.0 device,
> > timing raw UPIU queries over BSG (100 iterations) shows that replacing the
> > device-init read burst with a single aggregated read reduces the query
> > time by 75-78% (min 75.3%, avg 76.4%, max 78.5%).
>
> I don't know if you have plan to enable this feature in UEFI or any bootloaders
> to eliminate the boot overhead becaused of query iterations.

For now, my focus is on the kernel side. I do plan to bring the same
approach to bootloaders such as EDK II as a follow-up.

Thanks,
Hyeoncheol


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v2 2/2] scsi: ufs: Serve the device init reads from one aggregated read
  2026-10-02 15:50     ` Bean Huo
@ 2026-10-06  8:01       ` Hyeoncheol Jeong
  0 siblings, 0 replies; 12+ messages in thread
From: Hyeoncheol Jeong @ 2026-10-06  8:01 UTC (permalink / raw)
  To: Bean Huo, James.Bottomley@HansenPartnership.com, mkp@kernel.org,
	avri.altman@sandisk.com, bvanassche@acm.org,
	peter.wang@mediatek.com, linux-scsi@vger.kernel.org
  Cc: Jinyoung Choi, Alim Akhtar, beanhuo@micron.com,
	can.guo@oss.qualcomm.com, linux-kernel@vger.kernel.org

Hi Bean,

Thank you for the detailed review.

On Fri, 02 Oct 2026 17:50:19 +0200, Bean Huo wrote:
> On Wed, 2026-09-30 at 14:18 +0900, Hyeoncheol Jeong wrote:
> > (bWriteBoosterBufferLifeTimeEst is served from the packet only in the
> > shared-buffer mode)
>
> I suggest we should aggregate-read as many of them as possible. If we lose even
> one descriptor, this feature becomes useless.

You are right. As this is the first step, I took a conservative approach
and left out the reads that are issued asynchronously, such as the power
and unit descriptors. I agree that covering as many of the init reads as
possible is what makes this worthwhile.

> table 14.28 gives the size of each attribute, but I could not find where Spec
> defines the layout of the attributes group in the aggregated data packet,
> section 10.7.9.14 only defines the group header.
>
> dDynCapNeeded (09h) is an array attribute; its number of indexes is MaxNumberLU.
> 1Ch-1Fh can also be per-LU in dedicated WB mode. If a device sends one entry per
> index, or keeps a slot for 01h or 11h-13h, every offset after it moves. Then
> bMaxNumOfRTT, bRefClkGatingWaitTime and bWriteBoosterBufferLifeTimeEst are read
> from the wrong bytes, with no error and no fallback to a single query.
> ufshcd_set_rtt() then writes a value based on that wrong data.
>
> Has this been tested on more than one vendor's UFS 5.0 device?

You are right, and thank you for the examples. It has been tested
only on a Samsung UFS 5.0 device.

I think this layout needs to be specified in more detail in the
specification. Once it is, I plan to rework the patch and submit it
again.

> if the reply is cut short (the device may return less than requested), a group
> that is complete but its next offset points past the end is rejected before its
> type is checked.

I will fix this when I rework the patch.

> > +       if (hba->dev_info.wspecversion < 0x500 ||
> > +           hba->dev_info.agg_read_unsupported)
> > +               return;
>
> is this feature mandatory? or you assume this feature should be supported by
> defualt?

JESD220H lists AGGREGATED READ in Table 10.32 without marking it
optional, and I could not find a capability bit for it. The patch
therefore assumes a UFS 5.0 device supports it, but falls back to
individual queries if the device rejects it.

> If the device rejects the opcode with a query response error, is there a reason
> to retry? Retrying makes sense for a transient error, but not for "invalid
> opcode"

Agreed. A query response error should not be retried.

As I replied to Stanley, I think consuming these groups should wait
until the JEDEC specification clarifies the intra-group format. In the
next version I will keep only the interface from patch 1 and drop the
users in patch 2, and I will address the points above when they come
back.

Thanks,
Hyeoncheol


^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2026-10-06  8:15 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
     [not found] <CGME20260930050201epcms2p13ab67182445b0555acc8d27b927cda86@epcms2p7>
     [not found] ` <20260930050201epcms2p13ab67182445b0555acc8d27b927cda86@epcms2p1>
2026-09-30  5:15   ` [PATCH v2 1/2] scsi: ufs: Add the aggregated read query opcode and its reply format Hyeoncheol Jeong
2026-09-30  7:54     ` Peter Wang
2026-09-30  5:18   ` [PATCH v2 2/2] scsi: ufs: Serve the device init reads from one aggregated read Hyeoncheol Jeong
2026-09-30  7:28     ` sashiko-bot
2026-09-30  7:55     ` Peter Wang
2026-10-01  5:53     ` Stanley Jhu
2026-10-02  5:11       ` Hyeoncheol Jeong
2026-10-02 15:50     ` Bean Huo
2026-10-06  8:01       ` Hyeoncheol Jeong
     [not found] <CGME20260930050201epcms2p13ab67182445b0555acc8d27b927cda86@epcms2p1>
2026-09-30  5:02 ` [PATCH v2 0/2] scsi: ufs: batch device-init reads with UFS 5.0 " Hyeoncheol Jeong
2026-10-02  9:24   ` Bean Huo
2026-10-06  7:17     ` Hyeoncheol Jeong

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox