scsi-hd moves as many bytes as the SG list of a request holds, and
ufs-lu builds that list from the whole PRDT. When the PRDT is larger
than the transfer length in the CDB, a WRITE writes more blocks than
the CDB names and a READ reads more. A one-block WRITE(10) at LBA 0
with an 8 KiB PRDT also wrote LBA 1, and its response reported no
residual. A UFS device transfers what the CDB asks for and reports the
rest of the Expected Data Transfer Length as a residual.
The commands that scsi-hd emulates had their residual computed against
the PRDT too: an INQUIRY with an allocation length of 36, a 4 KiB PRDT
and an Expected Data Transfer Length of 4096 reported an overflow of
4294959176 bytes. It now reports an underflow of 4060.
Cut the SG list to the transfer length of the parsed command before
the request reaches scsi-hd. The new long-prdt qtest failed before this
change.
Fixes: 2a8b36a4967d ("hw/ufs: Support for UFS logical unit")
Signed-off-by: Daejun Park <[email protected]>
---
hw/ufs/lu.c | 28 ++++++++++++++++++++
tests/qtest/ufs-test.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 99 insertions(+)
diff --git a/hw/ufs/lu.c b/hw/ufs/lu.c
index b812e1bef8..bebe4eaac2 100644
--- a/hw/ufs/lu.c
+++ b/hw/ufs/lu.c
@@ -379,6 +379,24 @@ static UfsReqResult ufs_emulate_scsi_cmd(UfsLu *lu,
UfsRequest *req)
return UFS_REQUEST_SUCCESS;
}
+/* Keep only the first len bytes of the SG list built from the PRDT */
+static void ufs_limit_sg_list(UfsRequest *req, uint64_t len)
+{
+ QEMUSGList *prdt_sg = req->sg;
+ QEMUSGList *sg = g_new0(QEMUSGList, 1);
+ int i;
+
+ qemu_sglist_init(sg, prdt_sg->dev, prdt_sg->nsg, prdt_sg->as);
+ for (i = 0; i < prdt_sg->nsg && sg->size < len; i++) {
+ qemu_sglist_add(sg, prdt_sg->sg[i].base,
+ MIN(prdt_sg->sg[i].len, len - sg->size));
+ }
+ qemu_sglist_destroy(prdt_sg);
+ g_free(prdt_sg);
+ req->sg = sg;
+ req->data_len = sg->size;
+}
+
static UfsReqResult ufs_process_scsi_cmd(UfsLu *lu, UfsRequest *req)
{
uint8_t task_tag = req->req_upiu.header.task_tag;
@@ -425,6 +443,16 @@ static UfsReqResult ufs_process_scsi_cmd(UfsLu *lu,
UfsRequest *req)
return UFS_REQUEST_FAIL;
}
+ /*
+ * scsi-hd moves as many bytes as the SG list holds, while a UFS device
+ * transfers what the CDB asks for. Cut a longer SG list to the transfer
+ * length; the response reports the rest of the Expected Data Transfer
+ * Length as a residual.
+ */
+ if (req->sg && req->sg->size > scsi_req->cmd.xfer) {
+ ufs_limit_sg_list(req, scsi_req->cmd.xfer);
+ }
+
req->sreq = scsi_req;
len = scsi_req_enqueue(scsi_req);
diff --git a/tests/qtest/ufs-test.c b/tests/qtest/ufs-test.c
index 594b91706b..4e0c2aee5f 100644
--- a/tests/qtest/ufs-test.c
+++ b/tests/qtest/ufs-test.c
@@ -859,6 +859,76 @@ static void ufstest_sync_cache(void *obj, void *data,
QGuestAllocator *alloc)
ufs_exit(ufs, alloc);
}
+static void ufstest_long_prdt(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QUfs *ufs = obj;
+ const uint8_t request_sense_cdb[UFS_CDB_SIZE] = {
+ REQUEST_SENSE,
+ };
+ /* One block at LBA 0 and at LBA 1 */
+ const uint8_t write_lba0_cdb[UFS_CDB_SIZE] = {
+ WRITE_10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
+ };
+ const uint8_t read_lba0_cdb[UFS_CDB_SIZE] = {
+ READ_10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
+ };
+ const uint8_t read_lba1_cdb[UFS_CDB_SIZE] = {
+ READ_10, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00,
+ };
+ /* Allocation length 36 */
+ const uint8_t inquiry_cdb[UFS_CDB_SIZE] = {
+ INQUIRY, 0x00, 0x00, 0x00, 0x24, 0x00,
+ };
+ uint8_t buf[2 * 4096];
+ uint8_t zero_buf[4096] = { 0 };
+ uint8_t write_buf[2 * 4096];
+ UtpUpiuRsp rsp_upiu, write_rsp_upiu;
+ enum UtpOcsCodes ocs;
+
+ ufs_init(ufs, alloc);
+ ocs = ufs_send_scsi_command(ufs, 1, request_sense_cdb, NULL, 0, buf, 4096,
+ &rsp_upiu);
+ g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS);
+
+ /* A one-block WRITE with a two-block PRDT writes one block */
+ memset(write_buf, 0xa5, sizeof(write_buf));
+ ocs = ufs_send_scsi_command(ufs, 1, write_lba0_cdb, write_buf,
+ sizeof(write_buf), NULL, 0, &write_rsp_upiu);
+ g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS);
+ g_assert_cmpuint(write_rsp_upiu.header.scsi_status, ==, GOOD);
+
+ ocs = ufs_send_scsi_command(ufs, 1, read_lba1_cdb, NULL, 0, buf, 4096,
+ &rsp_upiu);
+ g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS);
+ g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+ g_assert_cmpmem(buf, 4096, zero_buf, sizeof(zero_buf));
+
+ g_assert_cmpuint(write_rsp_upiu.header.flags, ==, UFS_UPIU_FLAG_UNDERFLOW);
+ g_assert_cmpuint(be32_to_cpu(write_rsp_upiu.sr.residual_transfer_count),
+ ==, 4096);
+
+ /* A one-block READ with a two-block PRDT reads one block */
+ ocs = ufs_send_scsi_command(ufs, 1, read_lba0_cdb, NULL, 0, buf,
+ sizeof(buf), &rsp_upiu);
+ g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS);
+ g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+ g_assert_cmpuint(rsp_upiu.header.flags, ==, UFS_UPIU_FLAG_UNDERFLOW);
+ g_assert_cmpuint(be32_to_cpu(rsp_upiu.sr.residual_transfer_count), ==,
+ 4096);
+ g_assert_cmpmem(buf, 4096, write_buf, 4096);
+
+ /* A 36-byte INQUIRY with a 4 KiB PRDT reports the rest as a residual */
+ ocs = ufs_send_scsi_command(ufs, 1, inquiry_cdb, NULL, 0, buf, 4096,
+ &rsp_upiu);
+ g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS);
+ g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+ g_assert_cmpuint(rsp_upiu.header.flags, ==, UFS_UPIU_FLAG_UNDERFLOW);
+ g_assert_cmpuint(be32_to_cpu(rsp_upiu.sr.residual_transfer_count), ==,
+ 4096 - 36);
+
+ ufs_exit(ufs, alloc);
+}
+
static void ufstest_mcq_cq_wraparound(void *obj, void *data,
QGuestAllocator *alloc)
{
@@ -1812,6 +1882,7 @@ static void ufs_register_nodes(void)
qos_add_test("mcq-read-write", "ufs", ufstest_read_write, &mcq_test_opts);
qos_add_test("no-prdt", "ufs", ufstest_no_prdt, &io_test_opts);
qos_add_test("sync-cache", "ufs", ufstest_sync_cache, &io_test_opts);
+ qos_add_test("long-prdt", "ufs", ufstest_long_prdt, &io_test_opts);
qos_add_test("mcq-cq-wraparound", "ufs", ufstest_mcq_cq_wraparound,
&mcq_test_opts);
qos_add_test("query-flag", "ufs", ufstest_query_flag_request,
--
2.43.0