Commit ad2f77a00b for qemu.org
commit ad2f77a00b4ab8ef98863048a842a459c031dce8
Author: Daejun Park <daejun7.park@samsung.com>
Date: Tue Oct 20 09:18:26 2026 +0900
hw/ufs: Pass SYNCHRONIZE CACHE(16) to scsi-hd as SYNCHRONIZE CACHE(10)
scsi-hd implements SYNCHRONIZE CACHE(10) only and fails the 16-byte
variant with INVALID COMMAND OPERATION CODE. Linux sends SYNCHRONIZE
CACHE(16) to zoned disks (sd_zbc sets use_16_for_sync), so once a
logical unit can be zoned, every flush from such a guest fails. With
the zoned logical units and without this change, a Linux 7.3-rc7
guest logged
sd 2:0:0:1: [sdb] tag#9 Add. Sense: Invalid command operation code
critical target error, dev sdb, sector 0 op 0x1:(WRITE) flags 0x800
and fsync() on the disk failed with EREMOTEIO.
scsi-hd flushes the whole medium for SYNCHRONIZE CACHE(10) and does not
look at its LBA range. Hand the 16-byte command to scsi-hd as the
10-byte one and keep the IMMED bit. This keeps the UFS change inside
hw/ufs; if scsi-disk learns SYNCHRONIZE CACHE(16), the translation can
go.
Signed-off-by: Daejun Park <daejun7.park@samsung.com>
Reviewed-by: Jeuk Kim <jeuk20.kim@samsung.com>
Signed-off-by: Jeuk Kim <jeuk20.kim@samsung.com>
diff --git a/hw/ufs/lu.c b/hw/ufs/lu.c
index 1101e36873..a7b6d778f9 100644
--- a/hw/ufs/lu.c
+++ b/hw/ufs/lu.c
@@ -406,6 +406,8 @@ static UfsReqResult ufs_emulate_scsi_cmd(UfsLu *lu, UfsRequest *req)
static UfsReqResult ufs_process_scsi_cmd(UfsLu *lu, UfsRequest *req)
{
uint8_t task_tag = req->req_upiu.header.task_tag;
+ uint8_t *cdb = req->req_upiu.sc.cdb;
+ uint8_t sync_cache_10[UFS_CDB_SIZE] = { SYNCHRONIZE_CACHE };
SCSIRequest *scsi_req;
uint32_t len;
@@ -419,8 +421,19 @@ static UfsReqResult ufs_process_scsi_cmd(UfsLu *lu, UfsRequest *req)
return ufs_emulate_scsi_cmd(lu, req);
}
- scsi_req = scsi_req_new(lu->scsi_dev, task_tag, lu->lun,
- req->req_upiu.sc.cdb, UFS_CDB_SIZE, req);
+ /*
+ * scsi-hd implements SYNCHRONIZE CACHE(10) only, while Linux sends the
+ * 16-byte variant to zoned disks. scsi-hd flushes the whole medium for
+ * SYNCHRONIZE CACHE(10) whatever its LBA range, so pass the 16-byte
+ * command on as the 10-byte one, keeping IMMED.
+ */
+ if (cdb[0] == SYNCHRONIZE_CACHE_16) {
+ sync_cache_10[1] = cdb[1] & 0x02;
+ cdb = sync_cache_10;
+ }
+
+ scsi_req = scsi_req_new(lu->scsi_dev, task_tag, lu->lun, cdb, UFS_CDB_SIZE,
+ req);
/*
* A command that moves data needs a PRDT. Without one there is no SG
diff --git a/tests/qtest/ufs-test.c b/tests/qtest/ufs-test.c
index ea7b9aa62a..3c9e9e4032 100644
--- a/tests/qtest/ufs-test.c
+++ b/tests/qtest/ufs-test.c
@@ -814,6 +814,51 @@ static void ufstest_no_prdt(void *obj, void *data, QGuestAllocator *alloc)
ufs_exit(ufs, alloc);
}
+static void ufstest_sync_cache(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QUfs *ufs = obj;
+ uint8_t buf[4096];
+ const uint8_t request_sense_cdb[UFS_CDB_SIZE] = {
+ REQUEST_SENSE,
+ };
+ const uint8_t sync_cache_10_cdb[UFS_CDB_SIZE] = {
+ SYNCHRONIZE_CACHE,
+ };
+ const uint8_t sync_cache_16_cdb[UFS_CDB_SIZE] = {
+ SYNCHRONIZE_CACHE_16,
+ };
+ const uint8_t sync_cache_16_immed_cdb[UFS_CDB_SIZE] = {
+ SYNCHRONIZE_CACHE_16, 0x02 /* IMMED */
+ };
+ enum UtpOcsCodes ocs;
+ UtpUpiuRsp rsp_upiu;
+ const int test_lun = 1;
+
+ ufs_init(ufs, alloc);
+
+ /* Clear Unit Attention */
+ ocs = ufs_send_scsi_command(ufs, test_lun, request_sense_cdb, NULL, 0,
+ buf, sizeof(buf), &rsp_upiu);
+ g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS);
+
+ ocs = ufs_send_scsi_command(ufs, test_lun, sync_cache_10_cdb, NULL, 0,
+ NULL, 0, &rsp_upiu);
+ g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS);
+ g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+
+ ocs = ufs_send_scsi_command(ufs, test_lun, sync_cache_16_cdb, NULL, 0,
+ NULL, 0, &rsp_upiu);
+ g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS);
+ g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+
+ ocs = ufs_send_scsi_command(ufs, test_lun, sync_cache_16_immed_cdb, NULL,
+ 0, NULL, 0, &rsp_upiu);
+ g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS);
+ g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+
+ ufs_exit(ufs, alloc);
+}
+
static void ufstest_mcq_cq_wraparound(void *obj, void *data,
QGuestAllocator *alloc)
{
@@ -1766,6 +1811,7 @@ static void ufs_register_nodes(void)
qos_add_test("legacy-read-write", "ufs", ufstest_read_write, &io_test_opts);
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("mcq-cq-wraparound", "ufs", ufstest_mcq_cq_wraparound,
&mcq_test_opts);
qos_add_test("query-flag", "ufs", ufstest_query_flag_request,