From nobody Fri Oct 23 15:09:53 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=samsung.com ARC-Seal: i=1; a=rsa-sha256; t=1792455496; cv=none; d=zohomail.com; s=zohoarc; b=KqMftOJ8uJCYPjXUe9AY54BQ4DUCp6pWRKdQM4joGv6xLXdH5WL+S9KF9YuuxStDgZCCTxzNdeVJgz4fKvuPCRTxShtlDwVl808tO1CvWzRPx59Fm8jYdQ6EMGKGIj+uay9iufJZEGBsYEX8pmIhXn+4Ht9ycNMm+tYpQunfuXk= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1792455496; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:Reply-To:Reply-To:Sender:Subject:Subject:To:To:Message-Id; bh=LrFeW/yEBp2dJ1K63CtmLTPigywhfDKuHTqi7GMDtIA=; b=d0FHQNoMvrR+63xUlOn6JxM7ydYA9tDuBRYoFlk4dragdAbIgWNh1GMHlOnXVOzBwvwe5NuTYXVe/bNsIVlqbkGML3rdf1v9Ig0RSwFYNDWNukCMlThPYm2iawJcsiznCy7zLDoQHB5YUCqL0vgsBo8YUdZcIHxSVxUMaPbI6vE= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1792455496067752.2833348710273; Mon, 19 Oct 2026 17:18:16 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1xIxYC-0005K1-Hf; Mon, 19 Oct 2026 20:17:56 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1xIxY3-0005FZ-LD for qemu-devel@nongnu.org; Mon, 19 Oct 2026 20:17:52 -0400 Received: from mailout3.samsung.com ([203.254.224.33]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1xIxY0-0003Or-Mu for qemu-devel@nongnu.org; Mon, 19 Oct 2026 20:17:47 -0400 Received: from epcas2p2.samsung.com (unknown [182.195.41.54]) by mailout3.samsung.com (KnoxPortal) with ESMTP id 20261020001739epoutp033bc36f49c6ca4f83d4d41aa625551987~gFL5ohTsX0924909249epoutp03m for ; Tue, 20 Oct 2026 00:17:39 +0000 (GMT) Received: from epsnrtp01.localdomain (unknown [182.195.42.153]) by epcas2p4.samsung.com (KnoxPortal) with ESMTPS id 20261020001739epcas2p4a8d8658b91e95dc97e86a5d653353e0c~gFL5XTIXi1254212542epcas2p4o; Tue, 20 Oct 2026 00:17:39 +0000 (GMT) Received: from epcas2p1.samsung.com (unknown [182.195.38.208]) by epsnrtp01.localdomain (Postfix) with ESMTP id 4j7tKH1QgCz6B9mD; Tue, 20 Oct 2026 00:17:39 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout3.samsung.com 20261020001739epoutp033bc36f49c6ca4f83d4d41aa625551987~gFL5ohTsX0924909249epoutp03m DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1792455459; bh=LrFeW/yEBp2dJ1K63CtmLTPigywhfDKuHTqi7GMDtIA=; h=Subject:Reply-To:From:To:CC:In-Reply-To:Date:References:From; b=eWRjQofZQLJjJYTDMB9Ug+Ls+dkKRasuQd635sV6wc+Wl9GAGB5f36bjPo9czdTBR 3bsa0o3dW+5Vb86g9tFHxGZ3mH4W89TUiB75RXpbG2tbpyvZlmfGmC+hQF1K0HPrGB sk5ylFTlMc/LsuRVAPIuphQ5pLrAx0HMjWTxCZIE= Mime-Version: 1.0 Subject: [PATCH v3 1/6] hw/ufs: Fail SCSI commands that move data without a PRDT From: Daejun Park To: "qemu-devel@nongnu.org" CC: Jeuk Kim , "farosas@suse.de" , "lvivier@redhat.com" , "pbonzini@redhat.com" , "kwolf@redhat.com" , "hreitz@redhat.com" , "qemu-block@nongnu.org" , "pierrick.bouvier@oss.qualcomm.com" , "stanleyjhu@google.com" , Keoseong Park , "qemu-stable@nongnu.org" , Daejun Park X-Priority: 3 X-Content-Kind-Code: NORMAL In-Reply-To: <20261020001647epcms2p52cd58173164a43e252c33a3eb5935dc8@epcms2p5> X-CPGS-Detection: blocking_info_exchange X-Drm-Type: N,general X-Msg-Generator: Mail X-Msg-Type: PERSONAL X-Reply-Demand: N Message-ID: <20261020001738epcms2p44708febeb26b208e427c34883225650d@epcms2p4> Date: Tue, 20 Oct 2026 09:17:38 +0900 X-CMS-MailID: 20261020001738epcms2p44708febeb26b208e427c34883225650d Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" X-Sendblock-Type: AUTO_CONFIDENTIAL CMS-TYPE: 102P X-CPGSPASS: Y X-CPGSPASS: Y cpgsPolicy: CPGSC10-223,Y X-CFilter-Loop: Reflected X-CMS-RootMailID: 20261020001647epcms2p52cd58173164a43e252c33a3eb5935dc8 References: <20261020001647epcms2p52cd58173164a43e252c33a3eb5935dc8@epcms2p5> Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=203.254.224.33; envelope-from=daejun7.park@samsung.com; helo=mailout3.samsung.com X-Spam_score_int: -46 X-Spam_score: -4.7 X-Spam_bar: ---- X-Spam_report: (-4.7 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.329, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, RCVD_IN_MSPIKE_H3=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_PASS=-0.001, SPF_NONE=0.001 autolearn=unavailable autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: daejun7.park@samsung.com Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @samsung.com) X-ZM-MESSAGEID: 1792455498783158500 A command whose CDB transfers data, sent with a PRDT length of 0, reaches scsi-hd without an SG list. The SCSI layer then calls the transfer_data callback of the bus, which ufs_scsi_info does not set, and QEMU dies with SIGSEGV. A guest triggers this with, for example, an INQUIRY whose PRDT is empty; the new no-prdt qtest crashed QEMU before this change. Check the transfer direction that scsi_req_new() parsed, and fail such a command with OCS MISMATCH DATA BUFFER SIZE before it reaches scsi-hd: the PRDT describes less data than the command moves. virtio-scsi likewise fails a command that has no matching buffer (VIRTIO_SCSI_S_OVERRUN). A request that only reports a pending unit attention moves no data and goes through, so that the guest still sees the unit attention. Handing scsi-hd an empty SG list instead is not enough: READ and WRITE then complete synchronously in dma_blk_io() and trip the assertion in scsi_dma_complete(). Fixes: 2a8b36a4967d ("hw/ufs: Support for UFS logical unit") Cc: qemu-stable@nongnu.org Signed-off-by: Daejun Park Reviewed-by: Jeuk Kim --- hw/ufs/lu.c | 24 ++++++++++++++++++++---- hw/ufs/ufs.c | 10 ++++++++-- hw/ufs/ufs.h | 2 ++ tests/qtest/ufs-test.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 75 insertions(+), 6 deletions(-) diff --git a/hw/ufs/lu.c b/hw/ufs/lu.c index 5905c49fac..fb72416fe1 100644 --- a/hw/ufs/lu.c +++ b/hw/ufs/lu.c @@ -382,6 +382,8 @@ static UfsReqResult ufs_emulate_scsi_cmd(UfsLu *lu, Ufs= Request *req) static UfsReqResult ufs_process_scsi_cmd(UfsLu *lu, UfsRequest *req) { uint8_t task_tag =3D req->req_upiu.header.task_tag; + SCSIRequest *scsi_req; + uint32_t len; =20 /* * Each ufs-lu has its own independent virtual SCSI bus. Therefore, we= can't @@ -393,12 +395,26 @@ static UfsReqResult ufs_process_scsi_cmd(UfsLu *lu, U= fsRequest *req) return ufs_emulate_scsi_cmd(lu, req); } =20 - SCSIRequest *scsi_req =3D - scsi_req_new(lu->scsi_dev, task_tag, lu->lun, req->req_upiu.sc.cdb, - UFS_CDB_SIZE, req); + scsi_req =3D scsi_req_new(lu->scsi_dev, task_tag, lu->lun, + req->req_upiu.sc.cdb, UFS_CDB_SIZE, req); + + /* + * A command that moves data needs a PRDT. Without one there is no SG + * list, and the SCSI layer would call the transfer_data callback, whi= ch + * ufs-lu does not set. Fail the request with OCS MISMATCH DATA BUFFER + * SIZE, as virtio-scsi fails a buffer that does not match the command. + * A request that reports a pending unit attention instead moves no da= ta; + * its sense is already set, so let it through and the guest sees the = UA. + */ + if (scsi_req->cmd.mode !=3D SCSI_XFER_NONE && !req->sg && + !scsi_req->sense_len) { + scsi_req_unref(scsi_req); + return UFS_REQUEST_MISMATCH_DATA_BUF; + } + req->sreq =3D scsi_req; =20 - uint32_t len =3D scsi_req_enqueue(scsi_req); + len =3D scsi_req_enqueue(scsi_req); if (len) { scsi_req_continue(scsi_req); } diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c index f008af7467..dda615da7c 100644 --- a/hw/ufs/ufs.c +++ b/hw/ufs/ufs.c @@ -2451,10 +2451,16 @@ void ufs_complete_req(UfsRequest *req, UfsReqResult= req_result) UfsHc *u =3D req->hc; assert(req->state =3D=3D UFS_REQUEST_RUNNING); =20 - if (req_result =3D=3D UFS_REQUEST_SUCCESS) { + switch (req_result) { + case UFS_REQUEST_SUCCESS: req->utrd.header.dword_2 =3D cpu_to_le32(UFS_OCS_SUCCESS); - } else { + break; + case UFS_REQUEST_MISMATCH_DATA_BUF: + req->utrd.header.dword_2 =3D cpu_to_le32(UFS_OCS_MISMATCH_DATA_BUF= _SIZE); + break; + default: req->utrd.header.dword_2 =3D cpu_to_le32(UFS_OCS_INVALID_CMD_TABLE= _ATTR); + break; } =20 req->state =3D UFS_REQUEST_COMPLETE; diff --git a/hw/ufs/ufs.h b/hw/ufs/ufs.h index 265a43faaa..adf74b6833 100644 --- a/hw/ufs/ufs.h +++ b/hw/ufs/ufs.h @@ -47,6 +47,8 @@ typedef enum UfsReqResult { UFS_REQUEST_SUCCESS =3D 0, UFS_REQUEST_FAIL =3D 1, UFS_REQUEST_NO_COMPLETE =3D 2, + /* The PRDT describes less data than the command moves */ + UFS_REQUEST_MISMATCH_DATA_BUF =3D 3, } UfsReqResult; =20 #define UFS_INVALID_SLOT (-1) diff --git a/tests/qtest/ufs-test.c b/tests/qtest/ufs-test.c index 9a9c5ca834..ea7b9aa62a 100644 --- a/tests/qtest/ufs-test.c +++ b/tests/qtest/ufs-test.c @@ -770,6 +770,50 @@ static void ufstest_read_write(void *obj, void *data, = QGuestAllocator *alloc) ufs_exit(ufs, alloc); } =20 +static void ufstest_no_prdt(void *obj, void *data, QGuestAllocator *alloc) +{ + QUfs *ufs =3D obj; + uint8_t buf[4096]; + const uint8_t inquiry_cdb[UFS_CDB_SIZE] =3D { + /* INQUIRY, allocation length 36 */ + INQUIRY, 0x00, 0x00, 0x00, 0x24 + }; + const uint8_t write_cdb[UFS_CDB_SIZE] =3D { + /* WRITE(10) to LBA 0, transfer length 1 */ + WRITE_10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00 + }; + enum UtpOcsCodes ocs; + UtpUpiuRsp rsp_upiu; + const int test_lun =3D 1; + + ufs_init(ufs, alloc); + + /* A pending unit attention is reported even without a PRDT */ + ocs =3D ufs_send_scsi_command(ufs, test_lun, write_cdb, NULL, 0, NULL,= 0, + &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_SUCCESS); + g_assert_cmpuint(rsp_upiu.header.scsi_status, =3D=3D, CHECK_CONDITION); + g_assert_cmpuint(rsp_upiu.sr.sense_data[2] & 0xf, =3D=3D, UNIT_ATTENTI= ON); + + /* Commands that move data but come without a PRDT fail */ + ocs =3D ufs_send_scsi_command(ufs, test_lun, inquiry_cdb, NULL, 0, NUL= L, 0, + &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_MISMATCH_DATA_BUF_SIZE); + + ocs =3D ufs_send_scsi_command(ufs, test_lun, write_cdb, NULL, 0, NULL,= 0, + &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_MISMATCH_DATA_BUF_SIZE); + + /* The logical unit keeps working */ + ocs =3D ufs_send_scsi_command(ufs, test_lun, inquiry_cdb, NULL, 0, buf= , 0x24, + &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_SUCCESS); + g_assert_cmpuint(rsp_upiu.header.scsi_status, =3D=3D, GOOD); + g_assert_cmpuint(buf[0] & 0x1f, =3D=3D, TYPE_DISK); + + ufs_exit(ufs, alloc); +} + static void ufstest_mcq_cq_wraparound(void *obj, void *data, QGuestAllocator *alloc) { @@ -1721,6 +1765,7 @@ static void ufs_register_nodes(void) qos_add_test("init", "ufs", ufstest_init, NULL); 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_op= ts); + qos_add_test("no-prdt", "ufs", ufstest_no_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, --=20 2.43.0 From nobody Fri Oct 23 15:09:53 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=samsung.com ARC-Seal: i=1; a=rsa-sha256; t=1792455531; cv=none; d=zohomail.com; s=zohoarc; b=Tzp5kyczF6GqsVgtzallPwCYooXP7uoeUy7eAV7c5m8dtwUdfJR3I8CN9kP/7CZ8EI0CxZBDO8IqXPhyhh9uObqDedbNlsBLb4GcOrQ9M6r9FoIyAQN+1Nwz6UEOZb6xqFk+P+7POUap+cqBntmMlZ1BGOCD6+y0/lLM3xNyw4g= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1792455531; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:Reply-To:Reply-To:Sender:Subject:Subject:To:To:Message-Id; bh=RNtYU8yQWEF1wiBiBqOxCh7QmsEFTuRjTsno2UVWvhs=; b=c9FhfXNGHob5KZQKUWFAugxotehUbU0OK8QVOvvj5PbVUuTwhN/xFUngSZz15uGdKb6bJpw4ksaj4v6EVFRDgqiSi5hjCBXkARmpYR/zvWst4SXMahd37eGkEFJCzLqRUTE15sxwNQLDJX+GjHv8JbKz3vkLNBc7nXeO1aMQQRo= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1792455531254368.0965239819569; Mon, 19 Oct 2026 17:18:51 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1xIxYn-00063Q-PX; Mon, 19 Oct 2026 20:18:33 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1xIxYm-00061T-6D for qemu-devel@nongnu.org; Mon, 19 Oct 2026 20:18:32 -0400 Received: from mailout1.samsung.com ([203.254.224.24]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1xIxYj-0003Qq-VH for qemu-devel@nongnu.org; Mon, 19 Oct 2026 20:18:31 -0400 Received: from epcas2p1.samsung.com (unknown [182.195.41.53]) by mailout1.samsung.com (KnoxPortal) with ESMTP id 20261020001827epoutp017e57cf9ec673bf43989f0969af36dca1~gFMmaKwwN2582125821epoutp01R for ; Tue, 20 Oct 2026 00:18:27 +0000 (GMT) Received: from epsnrtp02.localdomain (unknown [182.195.42.154]) by epcas2p1.samsung.com (KnoxPortal) with ESMTPS id 20261020001827epcas2p12b2052893fab4c0a259ab42df92d8ab5~gFMmDBhT62743327433epcas2p1A; Tue, 20 Oct 2026 00:18:27 +0000 (GMT) Received: from epcas2p1.samsung.com (unknown [182.195.38.201]) by epsnrtp02.localdomain (Postfix) with ESMTP id 4j7tLC0Gwzz2SSKZ; Tue, 20 Oct 2026 00:18:27 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout1.samsung.com 20261020001827epoutp017e57cf9ec673bf43989f0969af36dca1~gFMmaKwwN2582125821epoutp01R DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1792455507; bh=RNtYU8yQWEF1wiBiBqOxCh7QmsEFTuRjTsno2UVWvhs=; h=Subject:Reply-To:From:To:CC:In-Reply-To:Date:References:From; b=GB1Cjq/cCbYf5Ff+1Ulc3/NPfbSsvP6mNXnr9sqXd0O9l2MQPS3tA9BtQ0C+RpAfO Slc5m4blj6gGkUUh9nNnx/nowLcOGEQ4mwONseQnwzoWxfS8A1oAj1yiJ8/cE3Kc55 nhAw7met4euLd3aqeg0tpalYV1HQLs9BwU8c2juI= Mime-Version: 1.0 Subject: [PATCH v3 2/6] hw/ufs: Pass SYNCHRONIZE CACHE(16) to scsi-hd as SYNCHRONIZE CACHE(10) From: Daejun Park To: "qemu-devel@nongnu.org" CC: Jeuk Kim , "farosas@suse.de" , "lvivier@redhat.com" , "pbonzini@redhat.com" , "kwolf@redhat.com" , "hreitz@redhat.com" , "qemu-block@nongnu.org" , "pierrick.bouvier@oss.qualcomm.com" , "stanleyjhu@google.com" , Keoseong Park , Daejun Park X-Priority: 3 X-Content-Kind-Code: NORMAL In-Reply-To: <20261020001647epcms2p52cd58173164a43e252c33a3eb5935dc8@epcms2p5> X-CPGS-Detection: blocking_info_exchange X-Drm-Type: N,general X-Msg-Generator: Mail X-Msg-Type: PERSONAL X-Reply-Demand: N Message-ID: <20261020001826epcms2p36cd8f15fb78c5a98d2338a3df35beea4@epcms2p3> Date: Tue, 20 Oct 2026 09:18:26 +0900 X-CMS-MailID: 20261020001826epcms2p36cd8f15fb78c5a98d2338a3df35beea4 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" X-Sendblock-Type: AUTO_CONFIDENTIAL CMS-TYPE: 102P X-CPGSPASS: Y X-CPGSPASS: Y cpgsPolicy: CPGSC10-223,Y X-CFilter-Loop: Reflected X-CMS-RootMailID: 20261020001647epcms2p52cd58173164a43e252c33a3eb5935dc8 References: <20261020001647epcms2p52cd58173164a43e252c33a3eb5935dc8@epcms2p5> Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=203.254.224.24; envelope-from=daejun7.park@samsung.com; helo=mailout1.samsung.com X-Spam_score_int: -46 X-Spam_score: -4.7 X-Spam_bar: ---- X-Spam_report: (-4.7 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.329, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, RCVD_IN_MSPIKE_H3=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_PASS=-0.001, SPF_NONE=0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: daejun7.park@samsung.com Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @samsung.com) X-ZM-MESSAGEID: 1792455533680158500 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 that a later commit adds 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 Reviewed-by: Jeuk Kim --- hw/ufs/lu.c | 17 +++++++++++++++-- tests/qtest/ufs-test.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/hw/ufs/lu.c b/hw/ufs/lu.c index fb72416fe1..1c4d34c1c0 100644 --- a/hw/ufs/lu.c +++ b/hw/ufs/lu.c @@ -382,6 +382,8 @@ static UfsReqResult ufs_emulate_scsi_cmd(UfsLu *lu, Ufs= Request *req) static UfsReqResult ufs_process_scsi_cmd(UfsLu *lu, UfsRequest *req) { uint8_t task_tag =3D req->req_upiu.header.task_tag; + uint8_t *cdb =3D req->req_upiu.sc.cdb; + uint8_t sync_cache_10[UFS_CDB_SIZE] =3D { SYNCHRONIZE_CACHE }; SCSIRequest *scsi_req; uint32_t len; =20 @@ -395,8 +397,19 @@ static UfsReqResult ufs_process_scsi_cmd(UfsLu *lu, Uf= sRequest *req) return ufs_emulate_scsi_cmd(lu, req); } =20 - scsi_req =3D 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] =3D=3D SYNCHRONIZE_CACHE_16) { + sync_cache_10[1] =3D cdb[1] & 0x02; + cdb =3D sync_cache_10; + } + + scsi_req =3D scsi_req_new(lu->scsi_dev, task_tag, lu->lun, cdb, UFS_CD= B_SIZE, + req); =20 /* * 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, QGu= estAllocator *alloc) ufs_exit(ufs, alloc); } =20 +static void ufstest_sync_cache(void *obj, void *data, QGuestAllocator *all= oc) +{ + QUfs *ufs =3D obj; + uint8_t buf[4096]; + const uint8_t request_sense_cdb[UFS_CDB_SIZE] =3D { + REQUEST_SENSE, + }; + const uint8_t sync_cache_10_cdb[UFS_CDB_SIZE] =3D { + SYNCHRONIZE_CACHE, + }; + const uint8_t sync_cache_16_cdb[UFS_CDB_SIZE] =3D { + SYNCHRONIZE_CACHE_16, + }; + const uint8_t sync_cache_16_immed_cdb[UFS_CDB_SIZE] =3D { + SYNCHRONIZE_CACHE_16, 0x02 /* IMMED */ + }; + enum UtpOcsCodes ocs; + UtpUpiuRsp rsp_upiu; + const int test_lun =3D 1; + + ufs_init(ufs, alloc); + + /* Clear Unit Attention */ + ocs =3D ufs_send_scsi_command(ufs, test_lun, request_sense_cdb, NULL, = 0, + buf, sizeof(buf), &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_SUCCESS); + + ocs =3D ufs_send_scsi_command(ufs, test_lun, sync_cache_10_cdb, NULL, = 0, + NULL, 0, &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_SUCCESS); + g_assert_cmpuint(rsp_upiu.header.scsi_status, =3D=3D, GOOD); + + ocs =3D ufs_send_scsi_command(ufs, test_lun, sync_cache_16_cdb, NULL, = 0, + NULL, 0, &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_SUCCESS); + g_assert_cmpuint(rsp_upiu.header.scsi_status, =3D=3D, GOOD); + + ocs =3D ufs_send_scsi_command(ufs, test_lun, sync_cache_16_immed_cdb, = NULL, + 0, NULL, 0, &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_SUCCESS); + g_assert_cmpuint(rsp_upiu.header.scsi_status, =3D=3D, 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_op= ts); 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, --=20 2.43.0 From nobody Fri Oct 23 15:09:53 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=samsung.com ARC-Seal: i=1; a=rsa-sha256; t=1792455579; cv=none; d=zohomail.com; s=zohoarc; b=DJHNInRaHTjEv9yVMRQULps+ZHpmp+ENNgdTwvBLtDVmpxbOLfZjo4LTDmlioIC58zORHNsNAcIvMv+CbGTpOCUDtT+zRZwvRhCVZzf3sIIKfSj0G+0OV26OXFs5FSAd8OmUvflniuOr/7JxfK2Emgx4cfp9FyC2zt7D2ELoIcM= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1792455579; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:Reply-To:Reply-To:Sender:Subject:Subject:To:To:Message-Id; bh=NiRK2C5NtVexQ5sqAwvpV1n1Y2GHtLzQr7iVLs4cYcA=; b=RL2E5+BXJvHExq7Mq2L0uIUPLN+IOwicspSpbB2ww+QGMUgrhLzKX/SPRLajIqTRIcB4LGsaAsDpJajPsvOUBZ2ofZH611aGNNrUJ+/oselx40ibDmrhCEeLp6gyZLYEhzISWxGq5q7r5ogaAlFhUWlII5rfK40KtjRwDHpmmog= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1792455579533363.1871596494967; Mon, 19 Oct 2026 17:19:39 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1xIxZh-0006VC-Ef; Mon, 19 Oct 2026 20:19:29 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1xIxZe-0006SF-HV for qemu-devel@nongnu.org; Mon, 19 Oct 2026 20:19:27 -0400 Received: from mailout3.samsung.com ([203.254.224.33]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1xIxZb-0003X1-Rk for qemu-devel@nongnu.org; Mon, 19 Oct 2026 20:19:26 -0400 Received: from epcas2p2.samsung.com (unknown [182.195.41.54]) by mailout3.samsung.com (KnoxPortal) with ESMTP id 20261020001921epoutp03f894842fbf5a7aaef9ebec8051b047e7~gFNYVpxFv0886508865epoutp03W for ; Tue, 20 Oct 2026 00:19:21 +0000 (GMT) Received: from epsnrtp02.localdomain (unknown [182.195.42.154]) by epcas2p4.samsung.com (KnoxPortal) with ESMTPS id 20261020001920epcas2p40f0265121d4def15ee042a3fc2927a2e~gFNX7bz382222922229epcas2p4_; Tue, 20 Oct 2026 00:19:20 +0000 (GMT) Received: from epcas2p2.samsung.com (unknown [182.195.38.209]) by epsnrtp02.localdomain (Postfix) with ESMTP id 4j7tMD4MRyz2SSKd; Tue, 20 Oct 2026 00:19:20 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout3.samsung.com 20261020001921epoutp03f894842fbf5a7aaef9ebec8051b047e7~gFNYVpxFv0886508865epoutp03W DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1792455561; bh=NiRK2C5NtVexQ5sqAwvpV1n1Y2GHtLzQr7iVLs4cYcA=; h=Subject:Reply-To:From:To:CC:In-Reply-To:Date:References:From; b=SmOpnNHlyCsk+0wupXojq5tlO9UQXeHVrTQ4k5gMlmF0meRLLxIviXjdmaje6hv31 A7p1QhcOwDVkvvN5eXfPYxZtPXFYb5P2LSi0+MOlz9sKL7aJEeyvKBh8wWEpBdgg2a 5237SzaFsKkujwjAC9ju3ew7Vw45xg5OR95eMPEs= Mime-Version: 1.0 Subject: [PATCH v3 3/6] hw/ufs: Check the PRDT size against the transfer length of the CDB From: Daejun Park To: "qemu-devel@nongnu.org" CC: Jeuk Kim , "farosas@suse.de" , "lvivier@redhat.com" , "pbonzini@redhat.com" , "kwolf@redhat.com" , "hreitz@redhat.com" , "qemu-block@nongnu.org" , "pierrick.bouvier@oss.qualcomm.com" , "stanleyjhu@google.com" , Keoseong Park , Daejun Park X-Priority: 3 X-Content-Kind-Code: NORMAL In-Reply-To: <20261020001647epcms2p52cd58173164a43e252c33a3eb5935dc8@epcms2p5> X-CPGS-Detection: blocking_info_exchange X-Drm-Type: N,general X-Msg-Generator: Mail X-Msg-Type: PERSONAL X-Reply-Demand: N Message-ID: <20261020001920epcms2p4004ef6eaae9fd389e5fc91c104caf2c2@epcms2p4> Date: Tue, 20 Oct 2026 09:19:20 +0900 X-CMS-MailID: 20261020001920epcms2p4004ef6eaae9fd389e5fc91c104caf2c2 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" X-Sendblock-Type: AUTO_CONFIDENTIAL CMS-TYPE: 102P X-CPGSPASS: Y X-CPGSPASS: Y cpgsPolicy: CPGSC10-223,Y X-CFilter-Loop: Reflected X-CMS-RootMailID: 20261020001647epcms2p52cd58173164a43e252c33a3eb5935dc8 References: <20261020001647epcms2p52cd58173164a43e252c33a3eb5935dc8@epcms2p5> Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=203.254.224.33; envelope-from=daejun7.park@samsung.com; helo=mailout3.samsung.com X-Spam_score_int: -46 X-Spam_score: -4.7 X-Spam_bar: ---- X-Spam_report: (-4.7 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.329, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, RCVD_IN_MSPIKE_H3=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_PASS=-0.001, SPF_NONE=0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: daejun7.park@samsung.com Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @samsung.com) X-ZM-MESSAGEID: 1792455580311158500 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. When the PRDT is smaller than the transfer length, scsi-hd moved what it holds and the request completed without an error, for example a two-block WRITE(10) with a 4 KiB PRDT. Such a request now fails with OCS MISMATCH DATA BUFFER SIZE, as one without a PRDT does. As in virtio-scsi, the PRDT is compared with the transfer length of the CDB, which for commands that return data is the allocation length, and before scsi-hd checks the CDB. The new ufs_fit_data_buffer() in ufs.c, where the SG list is built from the PRDT, cuts a longer list and reports a shorter one; ufs-lu passes it the transfer length that scsi_req_new() parsed. The HID qtest wrote eight blocks from a one-block buffer, so give it eight blocks. The new long-prdt and short-prdt qtests failed before this change. Fixes: 2a8b36a4967d ("hw/ufs: Support for UFS logical unit") Signed-off-by: Daejun Park Reviewed-by: Jeuk Kim Reviewed-by: Stanley Jhu --- hw/ufs/lu.c | 20 ++++--- hw/ufs/trace-events | 1 + hw/ufs/ufs.c | 35 ++++++++++++ hw/ufs/ufs.h | 1 + tests/qtest/ufs-test.c | 141 +++++++++++++++++++++++++++++++++++++++++++++= +++- 5 files changed, 189 insertions(+), 9 deletions(-) diff --git a/hw/ufs/lu.c b/hw/ufs/lu.c index 1c4d34c1c0..0c0537cfee 100644 --- a/hw/ufs/lu.c +++ b/hw/ufs/lu.c @@ -412,15 +412,19 @@ static UfsReqResult ufs_process_scsi_cmd(UfsLu *lu, U= fsRequest *req) req); =20 /* - * A command that moves data needs a PRDT. Without one there is no SG - * list, and the SCSI layer would call the transfer_data callback, whi= ch - * ufs-lu does not set. Fail the request with OCS MISMATCH DATA BUFFER - * SIZE, as virtio-scsi fails a buffer that does not match the command. - * A request that reports a pending unit attention instead moves no da= ta; - * its sense is already set, so let it through and the guest sees the = UA. + * scsi-hd moves as many bytes as the SG list holds, and without an SG + * list it would call the transfer_data callback, which ufs-lu does not + * set. A UFS device moves what the CDB asks for, so fit the PRDT to t= he + * transfer length, and fail a request whose PRDT describes less, a + * missing PRDT included, with OCS MISMATCH DATA BUFFER SIZE, as + * virtio-scsi fails a buffer that does not match the command. The + * response reports the rest of the Expected Data Transfer Length as a + * residual. A request that reports a pending unit attention moves no + * data; its sense is already set, so let it through and the guest sees + * the UA. */ - if (scsi_req->cmd.mode !=3D SCSI_XFER_NONE && !req->sg && - !scsi_req->sense_len) { + if (!scsi_req->sense_len && + !ufs_fit_data_buffer(req, scsi_req->cmd.xfer)) { scsi_req_unref(scsi_req); return UFS_REQUEST_MISMATCH_DATA_BUF; } diff --git a/hw/ufs/trace-events b/hw/ufs/trace-events index 5e5a54a3fb..db9d172713 100644 --- a/hw/ufs/trace-events +++ b/hw/ufs/trace-events @@ -22,6 +22,7 @@ ufs_hce_reset(void) "HCE 1 -> 0 reset: cancelling BHs, re= setting MCQ and request ufs_err_dma_read_utrd(uint32_t slot, uint64_t addr) "failed to read utrd. = UTRLDBR slot %"PRIu32", UTRD dma addr %"PRIu64"" ufs_err_dma_read_req_upiu(uint32_t slot, uint64_t addr) "failed to read re= q upiu. UTRLDBR slot %"PRIu32", request upiu addr %"PRIu64"" ufs_err_dma_read_prdt(uint32_t slot, uint64_t addr) "failed to read prdt. = UTRLDBR slot %"PRIu32", prdt addr %"PRIu64"" +ufs_err_mismatch_data_buf(uint32_t slot, uint8_t task_tag, uint64_t xfer, = uint64_t prdt_size) "slot %"PRIu32", task tag %"PRIu8", transfer length %"P= RIu64", PRDT %"PRIu64"" ufs_err_dma_read_sq(uint8_t sqid, uint64_t addr) "failed to read sq entry.= sqid %"PRIu8", hwaddr %"PRIu64"" ufs_err_dma_write_utrd(uint32_t slot, uint64_t addr) "failed to write utrd= . UTRLDBR slot %"PRIu32", UTRD dma addr %"PRIu64"" ufs_err_dma_write_rsp_upiu(uint32_t slot, uint64_t addr) "failed to write = rsp upiu. UTRLDBR slot %"PRIu32", response upiu addr %"PRIu64"" diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c index dda615da7c..cd28f97a12 100644 --- a/hw/ufs/ufs.c +++ b/hw/ufs/ufs.c @@ -227,6 +227,41 @@ static MemTxResult ufs_dma_read_prdt(UfsRequest *req) return MEMTX_OK; } =20 +/* + * Fit the SG list built from the PRDT to the len bytes that the command of + * req moves: keep only the first len bytes of a longer list. Return false= if + * the PRDT describes fewer than len bytes. + */ +bool ufs_fit_data_buffer(UfsRequest *req, uint64_t len) +{ + QEMUSGList *prdt_sg =3D req->sg; + uint64_t prdt_size =3D prdt_sg ? prdt_sg->size : 0; + QEMUSGList *sg; + int i; + + if (prdt_size < len) { + trace_ufs_err_mismatch_data_buf(req->slot, + req->req_upiu.header.task_tag, len, + prdt_size); + return false; + } + if (prdt_size =3D=3D len) { + return true; + } + + sg =3D g_new0(QEMUSGList, 1); + qemu_sglist_init(sg, prdt_sg->dev, prdt_sg->nsg, prdt_sg->as); + for (i =3D 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 =3D sg; + req->data_len =3D sg->size; + return true; +} + static MemTxResult ufs_dma_read_upiu(UfsRequest *req) { MemTxResult ret; diff --git a/hw/ufs/ufs.h b/hw/ufs/ufs.h index adf74b6833..15b3a352f3 100644 --- a/hw/ufs/ufs.h +++ b/hw/ufs/ufs.h @@ -306,6 +306,7 @@ void ufs_build_upiu_header(UfsRequest *req, uint8_t tra= ns_type, uint8_t flags, uint16_t data_segment_length); void ufs_build_query_response(UfsRequest *req); void ufs_complete_req(UfsRequest *req, UfsReqResult req_result); +bool ufs_fit_data_buffer(UfsRequest *req, uint64_t len); void ufs_wb_update_avail_buffer(UfsHc *u); void ufs_init_wlu(UfsLu *wlu, uint8_t wlun); bool ufs_realize(UfsHc *u, DeviceState *dev, AddressSpace *dma_as, diff --git a/tests/qtest/ufs-test.c b/tests/qtest/ufs-test.c index 3c9e9e4032..e8993e8c24 100644 --- a/tests/qtest/ufs-test.c +++ b/tests/qtest/ufs-test.c @@ -859,6 +859,142 @@ static void ufstest_sync_cache(void *obj, void *data,= QGuestAllocator *alloc) ufs_exit(ufs, alloc); } =20 +static void ufstest_long_prdt(void *obj, void *data, QGuestAllocator *allo= c) +{ + QUfs *ufs =3D obj; + const uint8_t request_sense_cdb[UFS_CDB_SIZE] =3D { + REQUEST_SENSE, + }; + /* One block at LBA 0 and at LBA 1 */ + const uint8_t write_lba0_cdb[UFS_CDB_SIZE] =3D { + WRITE_10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + }; + const uint8_t read_lba0_cdb[UFS_CDB_SIZE] =3D { + READ_10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + }; + const uint8_t read_lba1_cdb[UFS_CDB_SIZE] =3D { + READ_10, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, + }; + /* Allocation length 36 */ + const uint8_t inquiry_cdb[UFS_CDB_SIZE] =3D { + INQUIRY, 0x00, 0x00, 0x00, 0x24, 0x00, + }; + uint8_t buf[2 * 4096]; + uint8_t zero_buf[4096] =3D { 0 }; + uint8_t write_buf[2 * 4096]; + UtpUpiuRsp rsp_upiu, write_rsp_upiu; + enum UtpOcsCodes ocs; + + ufs_init(ufs, alloc); + ocs =3D ufs_send_scsi_command(ufs, 1, request_sense_cdb, NULL, 0, buf,= 4096, + &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_SUCCESS); + + /* A one-block WRITE with a two-block PRDT writes one block */ + memset(write_buf, 0xa5, sizeof(write_buf)); + ocs =3D ufs_send_scsi_command(ufs, 1, write_lba0_cdb, write_buf, + sizeof(write_buf), NULL, 0, &write_rsp_upi= u); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_SUCCESS); + g_assert_cmpuint(write_rsp_upiu.header.scsi_status, =3D=3D, GOOD); + + ocs =3D ufs_send_scsi_command(ufs, 1, read_lba1_cdb, NULL, 0, buf, 409= 6, + &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_SUCCESS); + g_assert_cmpuint(rsp_upiu.header.scsi_status, =3D=3D, GOOD); + g_assert_cmpmem(buf, 4096, zero_buf, sizeof(zero_buf)); + + g_assert_cmpuint(write_rsp_upiu.header.flags, =3D=3D, UFS_UPIU_FLAG_UN= DERFLOW); + g_assert_cmpuint(be32_to_cpu(write_rsp_upiu.sr.residual_transfer_count= ), + =3D=3D, 4096); + + /* A one-block READ with a two-block PRDT reads one block */ + ocs =3D ufs_send_scsi_command(ufs, 1, read_lba0_cdb, NULL, 0, buf, + sizeof(buf), &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_SUCCESS); + g_assert_cmpuint(rsp_upiu.header.scsi_status, =3D=3D, GOOD); + g_assert_cmpuint(rsp_upiu.header.flags, =3D=3D, UFS_UPIU_FLAG_UNDERFLO= W); + g_assert_cmpuint(be32_to_cpu(rsp_upiu.sr.residual_transfer_count), =3D= =3D, + 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 =3D ufs_send_scsi_command(ufs, 1, inquiry_cdb, NULL, 0, buf, 4096, + &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_SUCCESS); + g_assert_cmpuint(rsp_upiu.header.scsi_status, =3D=3D, GOOD); + g_assert_cmpuint(rsp_upiu.header.flags, =3D=3D, UFS_UPIU_FLAG_UNDERFLO= W); + g_assert_cmpuint(be32_to_cpu(rsp_upiu.sr.residual_transfer_count), =3D= =3D, + 4096 - 36); + + ufs_exit(ufs, alloc); +} + +static void ufstest_short_prdt(void *obj, void *data, QGuestAllocator *all= oc) +{ + QUfs *ufs =3D obj; + const uint8_t request_sense_cdb[UFS_CDB_SIZE] =3D { + REQUEST_SENSE, + }; + /* Two blocks at LBA 2, and one */ + const uint8_t write_2_cdb[UFS_CDB_SIZE] =3D { + WRITE_10, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, + }; + const uint8_t read_2_cdb[UFS_CDB_SIZE] =3D { + READ_10, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, + }; + const uint8_t write_1_cdb[UFS_CDB_SIZE] =3D { + WRITE_10, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, 0x00, + }; + const uint8_t read_1_cdb[UFS_CDB_SIZE] =3D { + READ_10, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, 0x00, + }; + /* Allocation length 36 */ + const uint8_t inquiry_cdb[UFS_CDB_SIZE] =3D { + INQUIRY, 0x00, 0x00, 0x00, 0x24, 0x00, + }; + uint8_t buf[4096]; + uint8_t zero_buf[4096] =3D { 0 }; + uint8_t write_buf[4096]; + UtpUpiuRsp rsp_upiu; + enum UtpOcsCodes ocs; + + ufs_init(ufs, alloc); + ocs =3D ufs_send_scsi_command(ufs, 1, request_sense_cdb, NULL, 0, buf,= 4096, + &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_SUCCESS); + memset(write_buf, 0x5a, sizeof(write_buf)); + + /* A PRDT that describes less than the command moves fails */ + ocs =3D ufs_send_scsi_command(ufs, 1, write_2_cdb, write_buf, + sizeof(write_buf), NULL, 0, &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_MISMATCH_DATA_BUF_SIZE); + ocs =3D ufs_send_scsi_command(ufs, 1, read_2_cdb, NULL, 0, buf, sizeof= (buf), + &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_MISMATCH_DATA_BUF_SIZE); + ocs =3D ufs_send_scsi_command(ufs, 1, inquiry_cdb, NULL, 0, buf, 16, + &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_MISMATCH_DATA_BUF_SIZE); + + /* The failed WRITE wrote nothing */ + ocs =3D ufs_send_scsi_command(ufs, 1, read_1_cdb, NULL, 0, buf, sizeof= (buf), + &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_SUCCESS); + g_assert_cmpuint(rsp_upiu.header.scsi_status, =3D=3D, GOOD); + g_assert_cmpmem(buf, sizeof(buf), zero_buf, sizeof(zero_buf)); + + /* A matching PRDT still works */ + ocs =3D ufs_send_scsi_command(ufs, 1, write_1_cdb, write_buf, + sizeof(write_buf), NULL, 0, &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_SUCCESS); + g_assert_cmpuint(rsp_upiu.header.scsi_status, =3D=3D, GOOD); + ocs =3D ufs_send_scsi_command(ufs, 1, read_1_cdb, NULL, 0, buf, sizeof= (buf), + &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_SUCCESS); + g_assert_cmpmem(buf, sizeof(buf), write_buf, sizeof(write_buf)); + + ufs_exit(ufs, alloc); +} + static void ufstest_mcq_cq_wraparound(void *obj, void *data, QGuestAllocator *alloc) { @@ -1577,7 +1713,8 @@ static void ufstest_hid(void *obj, void *data, QGuest= Allocator *alloc) /* WRITE(10) to LBA 0, transfer length 8 sectors =3D 1 fragment */ WRITE_10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00 }; - uint8_t write_buf[4096]; + /* The PRDT must hold the eight blocks that the WRITE(10) moves */ + uint8_t write_buf[8 * 4096]; uint8_t read_buf[4096]; int i; =20 @@ -1812,6 +1949,8 @@ static void ufs_register_nodes(void) qos_add_test("mcq-read-write", "ufs", ufstest_read_write, &mcq_test_op= ts); 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("short-prdt", "ufs", ufstest_short_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, --=20 2.43.0 From nobody Fri Oct 23 15:09:53 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=samsung.com ARC-Seal: i=1; a=rsa-sha256; t=1792455620; cv=none; d=zohomail.com; s=zohoarc; b=QnbHp0FZLYOGgdQZraxsy0tJ0poCBzNhavXJuH0hp1Yj8K4UvUgulk2TAgwDYiG2SksQq266Q+qAGFIkZJPXyGwvPKWCcSOE0GPQUJdiyUGC7TGbZtDJ/Etehz4dS2E4gfbmvU7av6QAYM6LnNkSe8RfsefC/pUz2oRUNRrgqOw= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1792455620; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:Reply-To:Reply-To:Sender:Subject:Subject:To:To:Message-Id; bh=nMiVJqA0HitjeW0T9xrvU/y8ho7zI+rBDiFH1CtMKko=; b=nfHg5qUDwiF+iT8mDFGhCWrpDX/sy/LCySrCEdSRny4jOvRpxaCPY3tLzas06UroJZ6V1gH6SbqHhFe/ZS8f5dqdgtE4X52zwZYgERDt7fJYUMsShhKI3bkz9Ghrv1aXv05jct6zJFOXk36oYtO5E0ZFBsoWNwRFnBYTSQnsqs8= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1792455620444254.22609429922977; Mon, 19 Oct 2026 17:20:20 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1xIxaJ-0006vx-RF; Mon, 19 Oct 2026 20:20:07 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1xIxaH-0006vV-QX for qemu-devel@nongnu.org; Mon, 19 Oct 2026 20:20:05 -0400 Received: from mailout2.samsung.com ([203.254.224.25]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1xIxaE-0003ZO-Pu for qemu-devel@nongnu.org; Mon, 19 Oct 2026 20:20:05 -0400 Received: from epcas2p4.samsung.com (unknown [182.195.41.56]) by mailout2.samsung.com (KnoxPortal) with ESMTP id 20261020001959epoutp02ead7fc9c7a14a8c4c5e7073d09c621d4~gFN77Q8wo3049430494epoutp02g for ; Tue, 20 Oct 2026 00:19:59 +0000 (GMT) Received: from epsnrtp03.localdomain (unknown [182.195.42.155]) by epcas2p3.samsung.com (KnoxPortal) with ESMTPS id 20261020001959epcas2p3761b54091037a8e28f9cfa4752454ff0~gFN7o_hBP3172931729epcas2p3l; Tue, 20 Oct 2026 00:19:59 +0000 (GMT) Received: from epcas2p4.samsung.com (unknown [182.195.38.203]) by epsnrtp03.localdomain (Postfix) with ESMTP id 4j7tMy715zz3hhT4; Tue, 20 Oct 2026 00:19:58 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout2.samsung.com 20261020001959epoutp02ead7fc9c7a14a8c4c5e7073d09c621d4~gFN77Q8wo3049430494epoutp02g DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1792455599; bh=nMiVJqA0HitjeW0T9xrvU/y8ho7zI+rBDiFH1CtMKko=; h=Subject:Reply-To:From:To:CC:In-Reply-To:Date:References:From; b=Zq0Z4bdHA2KSN+73sMRU0jpy6ZE8sk6yYFJ9//v0J3vjQ1a9/Dt+muXvcHL2r6q9n hSotWO99dAvsSVP0be1wm8qGXab2WwmEeqnNKFH1zFmuiQVCA7Xa4/m3vLqWJ+m6BP wrZD8TuUjUvURYReHV08BaDPs2gP0sOVvkoe0wxg= Mime-Version: 1.0 Subject: [PATCH v3 4/6] docs/system: Document zoned UFS logical units From: Daejun Park To: "qemu-devel@nongnu.org" CC: Jeuk Kim , "farosas@suse.de" , "lvivier@redhat.com" , "pbonzini@redhat.com" , "kwolf@redhat.com" , "hreitz@redhat.com" , "qemu-block@nongnu.org" , "pierrick.bouvier@oss.qualcomm.com" , "stanleyjhu@google.com" , Keoseong Park , Daejun Park X-Priority: 3 X-Content-Kind-Code: NORMAL In-Reply-To: <20261020001647epcms2p52cd58173164a43e252c33a3eb5935dc8@epcms2p5> X-CPGS-Detection: blocking_info_exchange X-Drm-Type: N,general X-Msg-Generator: Mail X-Msg-Type: PERSONAL X-Reply-Demand: N Message-ID: <20261020001958epcms2p71f9e9ddf12328d5789a672c1fe4879b0@epcms2p7> Date: Tue, 20 Oct 2026 09:19:58 +0900 X-CMS-MailID: 20261020001958epcms2p71f9e9ddf12328d5789a672c1fe4879b0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" X-Sendblock-Type: AUTO_CONFIDENTIAL CMS-TYPE: 102P X-CPGSPASS: Y X-CPGSPASS: Y cpgsPolicy: CPGSC10-223,Y X-CFilter-Loop: Reflected X-CMS-RootMailID: 20261020001647epcms2p52cd58173164a43e252c33a3eb5935dc8 References: <20261020001647epcms2p52cd58173164a43e252c33a3eb5935dc8@epcms2p5> Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=203.254.224.25; envelope-from=daejun7.park@samsung.com; helo=mailout2.samsung.com X-Spam_score_int: -46 X-Spam_score: -4.7 X-Spam_bar: ---- X-Spam_report: (-4.7 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.329, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, RCVD_IN_MSPIKE_H4=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_PASS=-0.001, SPF_NONE=0.001 autolearn=unavailable autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: daejun7.park@samsung.com Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @samsung.com) X-ZM-MESSAGEID: 1792455623016158500 Describe the zoned, zoned.zone_size and zoned.max_open properties of ufs-lu with an example, what a zoned logical unit emulates and what it rejects, that reads above the write pointer return what the drive holds, that zone state is not persistent and that the drive cannot be resized. Signed-off-by: Daejun Park Reviewed-by: Jeuk Kim --- docs/system/devices/ufs.rst | 55 +++++++++++++++++++++++++++++++++++++++++= ++++ 1 file changed, 55 insertions(+) diff --git a/docs/system/devices/ufs.rst b/docs/system/devices/ufs.rst index d3482e9299..a76fdbe76e 100644 --- a/docs/system/devices/ufs.rst +++ b/docs/system/devices/ufs.rst @@ -74,6 +74,61 @@ Logical Unit Properties Logical unit number, 0 to 31. A host controller accepts up to 32 logical units. =20 +``zoned`` (default off) + Present the logical unit as a host-managed zoned block device (ZBC). + All zones are sequential write required zones whose capacity is their + size. The drive must be writable. + +``zoned.zone_size`` (default 128M) + Zone size in bytes: a multiple of the 4 KiB logical block size, below + 16 TiB and not larger than the drive. A logical unit has at most + 67108862 zones. Linux also requires a power of two. The last zone is + smaller when the capacity is not a multiple of the zone size. + +``zoned.max_open`` (default 6) + Maximum number of open zones; 0 means no limit. When the limit is + reached, a write to another zone closes an implicitly open zone, and + fails with INSUFFICIENT ZONE RESOURCES if every open zone was opened + explicitly. f2fs needs at least as many as its active logs, 6 by + default. + +Zoned Logical Units +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +The following adds a regular logical unit and a zoned one with 64 MiB +zones: + +.. code-block:: console + + -drive file=3Dlu0.img,if=3Dnone,id=3Dlu0,format=3Draw \ + -drive file=3Dlu1.img,if=3Dnone,id=3Dlu1,format=3Draw \ + -device ufs,id=3Dufs0,serial=3D0001 \ + -device ufs-lu,drive=3Dlu0,bus=3Dufs0,lun=3D0 \ + -device ufs-lu,drive=3Dlu1,bus=3Dufs0,lun=3D1,zoned=3Don,zoned.zone_si= ze=3D64M + +A Linux guest needs ``CONFIG_BLK_DEV_ZONED``. f2fs keeps its metadata on +a regular logical unit, for example ``mkfs.f2fs -m -c /dev/sdb /dev/sda`` +with the zoned logical unit as ``/dev/sdb``. + +The zoned logical unit reports the ZBC device type in its INQUIRY data, +handles REPORT ZONES and the OPEN, CLOSE, FINISH and RESET WRITE POINTER +zone operations, and checks every WRITE and WRITE AND VERIFY command +against the write pointer of its zone. Zone operations act on one zone +or, with the ALL bit, on every zone; a ZONE COUNT above 1 is rejected. +The logical unit does not support WRITE SAME or UNMAP and reports no +logical block provisioning. All other commands, including reads, MODE +SENSE and SYNCHRONIZE CACHE, behave as on a regular logical unit. FORMAT +UNIT changes neither data nor zones. + +Reads are not restricted by the write pointer: reads above it, including +reads of a zone after RESET WRITE POINTER, return whatever the drive +holds. Zone conditions and write pointers are kept in memory only: after +QEMU restarts every zone is empty again while the drive keeps its data, +so the write pointers no longer match what a file system wrote. The zone +layout is set when the logical unit is created; resizing its drive is +not supported. Zone conditions and write pointers, open zones included, +are kept across a host controller reset and a system reset. + Limitations =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =20 --=20 2.43.0 From nobody Fri Oct 23 15:09:53 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=samsung.com ARC-Seal: i=1; a=rsa-sha256; t=1792455714; cv=none; d=zohomail.com; s=zohoarc; b=WGcIeqFMZc3XgxP/srEPqqO5RMTK8a7R00ULF34OXcdSCy0o1UHW2U2Zw5O4tMDrsdaVcKtXuV2xhJWL9iuPyHmwBe3lQvpgGSoKcHr0Z6y9DGRKRne0mQxYE6XE58Xp1nN4Nsg7sA5VEx9r/GfPQYPJv/8EqPr2hfdTKl9OCmI= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1792455714; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:Reply-To:Reply-To:Sender:Subject:Subject:To:To:Message-Id; bh=03Xtx5iqnfHwr7PGjGgn8EVMEefskJiOvnZH/nKxT1o=; b=FNIIQ1COGsV9qIcnqKn0B1v2DSS0bBCtKd4Op28qD9o7i55tx2NRMSZ/phEipihY+vYWG3kXSPJUj2i0klNRZ8MyD8DfDvXWe4fb7Hx0lZe2pEARn8Vv8grgnCmlOkJB+tzgcvYS1ptWTiwWiwDGecBv4svDB9l2Fu45yMxJdqU= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1792455714531532.0952279011491; Mon, 19 Oct 2026 17:21:54 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1xIxbj-0007Tf-61; Mon, 19 Oct 2026 20:21:35 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1xIxbV-0007SR-Tm for qemu-devel@nongnu.org; Mon, 19 Oct 2026 20:21:24 -0400 Received: from mailout3.samsung.com ([203.254.224.33]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1xIxbQ-0003tE-Fa for qemu-devel@nongnu.org; Mon, 19 Oct 2026 20:21:21 -0400 Received: from epcas2p1.samsung.com (unknown [182.195.41.53]) by mailout3.samsung.com (KnoxPortal) with ESMTP id 20261020002114epoutp03ba11c14b6c27c502327944262f41391b~gFPBSp3Hf1316913169epoutp03k for ; Tue, 20 Oct 2026 00:21:14 +0000 (GMT) Received: from epsnrtp04.localdomain (unknown [182.195.42.156]) by epcas2p2.samsung.com (KnoxPortal) with ESMTPS id 20261020002113epcas2p29ad20ebdeb29e983ea25f21ad1c0c583~gFPA8ubYl1156111561epcas2p2k; Tue, 20 Oct 2026 00:21:13 +0000 (GMT) Received: from epcas2p4.samsung.com (unknown [182.195.38.203]) by epsnrtp04.localdomain (Postfix) with ESMTP id 4j7tPP2fmtz6B9mV; Tue, 20 Oct 2026 00:21:13 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout3.samsung.com 20261020002114epoutp03ba11c14b6c27c502327944262f41391b~gFPBSp3Hf1316913169epoutp03k DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1792455674; bh=03Xtx5iqnfHwr7PGjGgn8EVMEefskJiOvnZH/nKxT1o=; h=Subject:Reply-To:From:To:CC:In-Reply-To:Date:References:From; b=LqUdLITuV27swceUvJfej00TcpWzX+6atjAyskj2Eg/23PLbvDFUMTcVN6+dhNpDd VGrpop126c7IVAoME1ljr0zRpPTjrrOn4Gi87C6kd9z651VNW64aoQeQWesXtoSuoa Cv964As2Cb0TLBr7E49XzTfl0MYYRSEn7jMeLHzE= Mime-Version: 1.0 Subject: [PATCH v3 5/6] hw/ufs: Support for Zoned UFS From: Daejun Park To: "qemu-devel@nongnu.org" CC: Jeuk Kim , "farosas@suse.de" , "lvivier@redhat.com" , "pbonzini@redhat.com" , "kwolf@redhat.com" , "hreitz@redhat.com" , "qemu-block@nongnu.org" , "pierrick.bouvier@oss.qualcomm.com" , "stanleyjhu@google.com" , Keoseong Park , Daejun Park X-Priority: 3 X-Content-Kind-Code: NORMAL In-Reply-To: <20261020001647epcms2p52cd58173164a43e252c33a3eb5935dc8@epcms2p5> X-CPGS-Detection: blocking_info_exchange X-Drm-Type: N,general X-Msg-Generator: Mail X-Msg-Type: PERSONAL X-Reply-Demand: N Message-ID: <20261020002112epcms2p30a1e8fd3b2c6a3de2ac533f98b7b6c1e@epcms2p3> Date: Tue, 20 Oct 2026 09:21:12 +0900 X-CMS-MailID: 20261020002112epcms2p30a1e8fd3b2c6a3de2ac533f98b7b6c1e Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" X-Sendblock-Type: AUTO_CONFIDENTIAL CMS-TYPE: 102P X-CPGSPASS: Y X-CPGSPASS: Y cpgsPolicy: CPGSC10-223,Y X-CFilter-Loop: Reflected X-CMS-RootMailID: 20261020001647epcms2p52cd58173164a43e252c33a3eb5935dc8 References: <20261020001647epcms2p52cd58173164a43e252c33a3eb5935dc8@epcms2p5> Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=203.254.224.33; envelope-from=daejun7.park@samsung.com; helo=mailout3.samsung.com X-Spam_score_int: -46 X-Spam_score: -4.7 X-Spam_bar: ---- X-Spam_report: (-4.7 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.329, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, RCVD_IN_MSPIKE_H3=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_PASS=-0.001, SPF_NONE=0.001 autolearn=unavailable autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: daejun7.park@samsung.com Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @samsung.com) X-ZM-MESSAGEID: 1792455716999158500 Add a "zoned" property to ufs-lu. A zoned logical unit is a host-managed zoned block device made of sequential write required zones, with the zone size and the maximum number of open zones set by zoned.zone_size and zoned.max_open. max_open defaults to 6 because f2fs refuses a zoned device with fewer open zones than its active logs, 6 by default. The logical unit keeps the condition and the write pointer of each zone in memory and emulates REPORT ZONES and the OPEN, CLOSE, FINISH and RESET WRITE POINTER zone operations, with or without the ALL bit. Every WRITE and WRITE AND VERIFY of at least one block that lies within the capacity is checked against its zone before it reaches scsi-hd: a write crossing the end of the zone fails with WRITE BOUNDARY VIOLATION, a write to a full zone with INVALID FIELD IN CDB, and a write that does not start at the write pointer with UNALIGNED WRITE COMMAND. When max_open zones are open, a write to another zone closes an implicitly open zone, and fails with INSUFFICIENT ZONE RESOURCES only if all open zones were opened explicitly. The zone checks, the open zone accounting and the sense codes follow the ZBC emulation of Linux scsi_debug; unlike it, a ZONE COUNT above 1 is rejected and RESET WRITE POINTER does not zero the zone. The zone of a write is opened and its write pointer moved once scsi-hd has accepted the write, before any data moves, so that queued writes to one zone pass the check in submission order: with MCQ, the Android common kernel queues several writes to a zone on one submission queue (BLK_FEAT_ORDERED_HWQ). The write pointer moves back if scsi-hd then fails or drops the write, unless the zone changed since, for example because a later write to it started. A write that passes the zone checks but is refused before it starts changes no zone: the host controller fails one whose PRDT is shorter than the write with OCS MISMATCH DATA BUFFER SIZE, and scsi-hd fails one with a WRPROTECT field with INVALID FIELD IN CDB. Neither keeps the write pointer of an earlier write to that zone from moving back. A pending unit attention is reported before any zone command or zone check, as scsi-hd reports it before most commands, INQUIRY and REPORT LUNS excepted. UNMAP fails with INVALID COMMAND OPERATION CODE, since deallocation would bypass the write pointers, and so does WRITE SAME, which Linux does not send to UFS devices (no_write_same) and which scsi-hd completes without writing when NDOB is set. READ CAPACITY(16) reports no logical block provisioning, the Block Limits VPD page reports no UNMAP limits, and the Logical Block Provisioning VPD page clears LBPU, LBPWS and LBPWS10. Reads and the remaining commands go to scsi-hd. Reads above the write pointer are not restricted and return what the drive holds, also after RESET WRITE POINTER. REPORT ZONES and the ZBC device characteristics page, which the logical unit answers itself, follow the PRDT rule of the commands that reach scsi-hd: a PRDT shorter than the allocation length fails with OCS MISMATCH DATA BUFFER SIZE. The wrapped scsi-hd stays a direct-access (TYPE_DISK) device. INQUIRY data of a zoned logical unit reaches the guest through a transfer_data callback, which sets the peripheral device type to TYPE_ZBC and adds the ZBC device characteristics page (B6h) to the list of supported VPD pages. The logical unit emulates that page, reporting unrestricted reads (URSWRZ), which Linux requires for host-managed devices, and the maximum number of open zones. Setting the scsi-hd device type to TYPE_ZBC instead takes the rest of the command set off the disk path. scsi-disk serves mode pages for TYPE_DISK and TYPE_ROM only, so MODE SENSE fails, the guest assumes a write-through cache and never sends SYNCHRONIZE CACHE or FUA writes. With a Linux 7.3-rc7 guest, a zoned logical unit built that way logged "Assuming drive cache: write through" and no flush reached QEMU on fsync. With this patch the guest logs "Write cache: enabled, read cache: enabled, supports DPO and FUA" and the flush arrives. Signed-off-by: Daejun Park Reviewed-by: Jeuk Kim Reviewed-by: Stanley Jhu --- hw/ufs/lu.c | 903 ++++++++++++++++++++++++++++++++++++++++++++++++= +++- hw/ufs/trace-events | 6 + hw/ufs/ufs.h | 43 +++ include/block/ufs.h | 27 ++ 4 files changed, 971 insertions(+), 8 deletions(-) diff --git a/hw/ufs/lu.c b/hw/ufs/lu.c index 0c0537cfee..4fe1247547 100644 --- a/hw/ufs/lu.c +++ b/hw/ufs/lu.c @@ -20,6 +20,9 @@ #include "ufs.h" =20 #define SCSI_COMMAND_FAIL (-1) +#define REPORT_ZONES_HEADER_SIZE 64 +#define REPORT_ZONES_DESC_SIZE 64 +#define ZBC_VPD_B6_LEN 64 =20 static void ufs_build_upiu_sense_data(UfsRequest *req, uint8_t *sense, uint32_t sense_len) @@ -58,6 +61,307 @@ static void ufs_build_scsi_response_upiu(UfsRequest *re= q, uint8_t *sense, status, data_segment_length); } =20 +static inline uint32_t ufs_zone_idx(UfsLu *lu, uint64_t lba) +{ + return lba / lu->zone_info.zone_size; +} + +static inline UfsZoneState *ufs_get_zone_by_lba(UfsLu *lu, uint64_t lba) +{ + if (lba >=3D be64_to_cpu(lu->unit_desc.logical_block_count)) { + return NULL; + } + + return &lu->zone_array[ufs_zone_idx(lu, lba)]; +} + +/* The last zone may be smaller than the zone size */ +static inline uint64_t ufs_zone_end(UfsZoneState *zone) +{ + return zone->start + zone->size; +} + +static void ufs_assign_zone_cond(UfsLu *lu, UfsZoneState *zone, + UfsZoneCond new_cond) +{ + switch (zone->cond) { + case UFS_ZONE_COND_IMPLICIT_OPEN: + case UFS_ZONE_COND_EXPLICIT_OPEN: + lu->zone_info.nr_open--; + break; + default: + break; + } + + zone->cond =3D new_cond; + zone->seq++; + + switch (new_cond) { + case UFS_ZONE_COND_EMPTY: + zone->wp =3D zone->start; + break; + case UFS_ZONE_COND_IMPLICIT_OPEN: + case UFS_ZONE_COND_EXPLICIT_OPEN: + lu->zone_info.nr_open++; + break; + case UFS_ZONE_COND_FULL: + zone->wp =3D ufs_zone_end(zone); + break; + default: + break; + } +} + +static UfsZoneState *ufs_find_implicit_open_zone(UfsLu *lu) +{ + uint32_t i; + + for (i =3D 0; i < lu->zone_info.nr_zones; i++) { + if (lu->zone_array[i].cond =3D=3D UFS_ZONE_COND_IMPLICIT_OPEN) { + return &lu->zone_array[i]; + } + } + + return NULL; +} + +/* Close one implicitly open zone; false if there is none */ +static bool ufs_close_implicit_open_zone(UfsLu *lu) +{ + UfsZoneState *zone =3D ufs_find_implicit_open_zone(lu); + uint64_t seq; + + if (!zone) { + return false; + } + + /* + * Closing moves no write pointer: keep seq, so that a write in flight= to + * this zone can still be undone. + */ + seq =3D zone->seq; + ufs_assign_zone_cond(lu, zone, UFS_ZONE_COND_CLOSED); + zone->seq =3D seq; + return true; +} + +/* + * Open an empty or closed zone. When all open zone resources are in use, = an + * implicitly open zone is closed to make room; if every open zone was ope= ned + * explicitly, the zone cannot be opened. A max_open of 0 means no limit. + */ +static bool ufs_open_zone(UfsLu *lu, UfsZoneState *zone, UfsZoneCond open_= cond) +{ + switch (zone->cond) { + case UFS_ZONE_COND_EMPTY: + case UFS_ZONE_COND_CLOSED: + if (lu->zone_info.max_open && + lu->zone_info.nr_open >=3D lu->zone_info.max_open && + !ufs_close_implicit_open_zone(lu)) { + return false; + } + ufs_assign_zone_cond(lu, zone, open_cond); + return true; + case UFS_ZONE_COND_IMPLICIT_OPEN: + if (open_cond =3D=3D UFS_ZONE_COND_EXPLICIT_OPEN) { + ufs_assign_zone_cond(lu, zone, UFS_ZONE_COND_EXPLICIT_OPEN); + } + return true; + default: + /* Explicitly open and full zones stay as they are */ + return true; + } +} + +/* Whether ufs_open_zone() would succeed, without changing anything */ +static bool ufs_can_open_zone(UfsLu *lu, UfsZoneState *zone) +{ + if (zone->cond !=3D UFS_ZONE_COND_EMPTY && + zone->cond !=3D UFS_ZONE_COND_CLOSED) { + return true; + } + + return !lu->zone_info.max_open || + lu->zone_info.nr_open < lu->zone_info.max_open || + ufs_find_implicit_open_zone(lu); +} + +static void ufs_close_zone(UfsLu *lu, UfsZoneState *zone) +{ + if (zone->cond =3D=3D UFS_ZONE_COND_IMPLICIT_OPEN || + zone->cond =3D=3D UFS_ZONE_COND_EXPLICIT_OPEN) { + ufs_assign_zone_cond(lu, zone, zone->wp =3D=3D zone->start ? + UFS_ZONE_COND_EMPTY : + UFS_ZONE_COND_CLOSED); + } +} + +static void ufs_finish_zone(UfsLu *lu, UfsZoneState *zone, bool all) +{ + switch (zone->cond) { + case UFS_ZONE_COND_EMPTY: + /* FINISH ZONE with the ALL bit leaves empty zones alone */ + if (all) { + break; + } + /* fallthrough */ + case UFS_ZONE_COND_IMPLICIT_OPEN: + case UFS_ZONE_COND_EXPLICIT_OPEN: + case UFS_ZONE_COND_CLOSED: + ufs_assign_zone_cond(lu, zone, UFS_ZONE_COND_FULL); + break; + case UFS_ZONE_COND_FULL: + /* An in-flight write that filled the zone must not undo this */ + zone->seq++; + break; + default: + break; + } +} + +static void ufs_reset_write_pointer_zone(UfsLu *lu, UfsZoneState *zone) +{ + switch (zone->cond) { + case UFS_ZONE_COND_IMPLICIT_OPEN: + case UFS_ZONE_COND_EXPLICIT_OPEN: + case UFS_ZONE_COND_CLOSED: + case UFS_ZONE_COND_FULL: + ufs_assign_zone_cond(lu, zone, UFS_ZONE_COND_EMPTY); + break; + default: + break; + } +} + +/* + * Open the zone of a write that passed ufs_zbc_check_write() and move its + * write pointer, once scsi-hd has accepted the write and before any data + * moves. A write that scsi-hd rejects outright, for example one with a + * WRPROTECT field, thus leaves the zones alone. The write pointer moves + * before the write completes, so that queued writes to one zone pass the + * check in submission order. + */ +static void ufs_zbc_start_write(UfsLu *lu, UfsRequest *req) +{ + UfsZoneState *zone =3D req->zone_write; + + if (!zone) { + return; + } + req->zone_write =3D NULL; + + req->zone_cond_before =3D zone->cond; + req->zone_wp_before =3D zone->wp; + + /* + * ufs_zbc_check_write() found that the zone can be opened. What ran + * since, scsi_req_new(), ufs_fit_data_buffer() and scsi_req_enqueue() + * of this request, changes no zone and does not run the event loop, so + * no other request completed in between. + */ + if (!ufs_open_zone(lu, zone, UFS_ZONE_COND_IMPLICIT_OPEN)) { + g_assert_not_reached(); + } + zone->wp +=3D req->zone_write_blocks; + zone->seq++; + if (zone->wp =3D=3D ufs_zone_end(zone)) { + ufs_assign_zone_cond(lu, zone, UFS_ZONE_COND_FULL); + } + req->zone =3D zone; + req->zone_seq =3D zone->seq; +} + +/* + * If scsi-hd fails or drops a started write (an aborted or purged request, + * an I/O error), move the write pointer back, unless the zone changed sin= ce: + * a later write started, or a zone operation changed it. + */ +static void ufs_zbc_write_undo(UfsLu *lu, UfsRequest *req) +{ + UfsZoneState *zone =3D req->zone; + + req->zone =3D NULL; + if (!zone || zone->seq !=3D req->zone_seq) { + return; + } + + trace_ufs_zbc_write_undone(lu->lun, zone->start, req->zone_wp_before); + + if (zone->cond =3D=3D UFS_ZONE_COND_FULL) { + /* + * The write filled the zone, which gave up its open resource, and + * another zone may hold it by now. So the zone comes back closed = or + * empty, also if it was open explicitly before the write, or if an + * OPEN ZONE found it full in the meantime. + */ + ufs_assign_zone_cond(lu, zone, req->zone_wp_before =3D=3D zone->st= art ? + UFS_ZONE_COND_EMPTY : + UFS_ZONE_COND_CLOSED); + } else if (req->zone_wp_before =3D=3D zone->start && + req->zone_cond_before !=3D UFS_ZONE_COND_EXPLICIT_OPEN) { + /* The write opened the zone implicitly */ + ufs_assign_zone_cond(lu, zone, UFS_ZONE_COND_EMPTY); + } + zone->wp =3D req->zone_wp_before; + zone->seq++; +} + +/* + * The wrapped scsi-hd is a direct-access (TYPE_DISK) device, so every + * command that a zoned LU forwards to it, including MODE SENSE, takes the + * regular disk path. Only the identification differs: a zoned LU reports + * itself as a host-managed zoned block device. Set the peripheral device + * type of the INQUIRY data that scsi-hd built to TYPE_ZBC, and add the ZBC + * device characteristics VPD page (emulated in ufs_emulate_zbc_cmd()) to = the + * list of supported VPD pages. Since UNMAP is rejected, also hide it in t= he + * Block Limits and Logical Block Provisioning VPD pages, and since WRITE = SAME + * is, clear its bits in the latter. scsi-hd pads its data with zeros up to + * the allocation length, and len is never larger than that. + */ +static void ufs_zoned_fixup_inquiry(const uint8_t *cdb, uint8_t *buf, + uint32_t len) +{ + uint8_t page_len; + + if (len =3D=3D 0) { + return; + } + + buf[0] =3D TYPE_ZBC; + + if (!(cdb[1] & 0x1) || len < 4) { + return; + } + + /* UNMAP and WRITE SAME are rejected, see ufs_emulate_zbc_cmd() */ + if (cdb[2] =3D=3D 0xb0) { + /* Block Limits: maximum UNMAP LBA and block descriptor counts */ + if (len > 20) { + memset(&buf[20], 0, MIN(len, 28) - 20); + } + return; + } + if (cdb[2] =3D=3D 0xb2) { + /* Logical Block Provisioning: clear LBPU, LBPWS and LBPWS10 */ + if (len > 5) { + buf[5] &=3D ~0xe0; + } + return; + } + if (cdb[2] !=3D 0x00) { + return; + } + + /* Supported VPD pages, in ascending order: 0xb6 goes last */ + page_len =3D buf[3]; + if (page_len < 255 && !memchr(&buf[4], 0xb6, MIN(page_len, len - 4))) { + buf[3] =3D page_len + 1; + if (4 + page_len < len) { + buf[4 + page_len] =3D 0xb6; + } + } +} + #define UFS_GROUP_NUMBER_MASK 0x1F #define UFS_WB_GROUP_NUMBER_DEFAULT 0x00 /* 00000b */ #define UFS_WB_GROUP_NUMBER_PINNED 0x18 /* 11000b */ @@ -170,6 +474,7 @@ static void ufs_hid_count_fragments(UfsRequest *req) static void ufs_scsi_command_complete(SCSIRequest *scsi_req, size_t resid) { UfsRequest *req =3D scsi_req->hba_private; + UfsLu *lu =3D container_of(scsi_req->bus, UfsLu, bus); int16_t status =3D scsi_req->status; uint32_t transfered_len =3D scsi_req->cmd.xfer - resid; =20 @@ -180,6 +485,9 @@ static void ufs_scsi_command_complete(SCSIRequest *scsi= _req, size_t resid) if (status =3D=3D GOOD) { ufs_wb_process_write_req(req, transfered_len); ufs_hid_count_fragments(req); + req->zone =3D NULL; + } else { + ufs_zbc_write_undo(lu, req); } =20 ufs_build_scsi_response_upiu(req, scsi_req->sense, scsi_req->sense_len, @@ -196,6 +504,7 @@ static void ufs_scsi_command_cancelled(SCSIRequest *scs= i_req) UfsRequest *req =3D scsi_req->hba_private; =20 assert(req !=3D NULL); + ufs_zbc_write_undo(container_of(scsi_req->bus, UfsLu, bus), req); req->sreq =3D NULL; scsi_req->hba_private =3D NULL; scsi_req_unref(scsi_req); @@ -204,9 +513,29 @@ static void ufs_scsi_command_cancelled(SCSIRequest *sc= si_req) static QEMUSGList *ufs_get_sg_list(SCSIRequest *scsi_req) { UfsRequest *req =3D scsi_req->hba_private; + UfsLu *lu =3D container_of(scsi_req->bus, UfsLu, bus); + + /* INQUIRY data of a zoned LU goes through ufs_scsi_transfer_data() */ + if (lu->zone_enabled && scsi_req->cmd.buf[0] =3D=3D INQUIRY) { + return NULL; + } + return req->sg; } =20 +static void ufs_scsi_transfer_data(SCSIRequest *scsi_req, uint32_t len) +{ + UfsRequest *req =3D scsi_req->hba_private; + uint8_t *buf =3D scsi_req_get_buf(scsi_req); + + /* Only INQUIRY of a zoned LU has no SG list, see ufs_get_sg_list() */ + assert(scsi_req->cmd.buf[0] =3D=3D INQUIRY && req->sg); + + ufs_zoned_fixup_inquiry(scsi_req->cmd.buf, buf, len); + dma_buf_read(buf, len, NULL, req->sg, MEMTXATTRS_UNSPECIFIED); + scsi_req_continue(scsi_req); +} + static const struct SCSIBusInfo ufs_scsi_info =3D { .tcq =3D true, .max_target =3D 0, @@ -214,6 +543,7 @@ static const struct SCSIBusInfo ufs_scsi_info =3D { .max_channel =3D 0, =20 .get_sg_list =3D ufs_get_sg_list, + .transfer_data =3D ufs_scsi_transfer_data, .complete =3D ufs_scsi_command_complete, .cancel =3D ufs_scsi_command_cancelled, }; @@ -322,6 +652,458 @@ static int ufs_emulate_wlun_inquiry(UfsRequest *req, = uint8_t *outbuf, return SCSI_INQUIRY_LEN; } =20 +/* ZBC device characteristics VPD page */ +static int ufs_emulate_zbc_vpd_page(UfsLu *lu, uint8_t *outbuf) +{ + memset(outbuf, 0, ZBC_VPD_B6_LEN); + outbuf[0] =3D TYPE_ZBC; + outbuf[1] =3D 0xb6; + stw_be_p(&outbuf[2], ZBC_VPD_B6_LEN - 4); + + /* URSWRZ: reads are not restricted by the write pointer */ + outbuf[4] =3D 0x01; + /* optimal number of open sequential write preferred zones */ + stl_be_p(&outbuf[8], 0xffffffff); + /* optimal number of non-seq. written seq. write preferred zones */ + stl_be_p(&outbuf[12], 0xffffffff); + /* maximum number of open sequential write required zones */ + stl_be_p(&outbuf[16], + lu->zone_info.max_open ? lu->zone_info.max_open : 0xffffffff); + /* zone alignment method 0 and starting LBA granularity 0: zone length= */ + + return ZBC_VPD_B6_LEN; +} + +/* ZBC additional sense codes */ +static const struct SCSISense sense_code_UNALIGNED_WRITE =3D { + .key =3D ILLEGAL_REQUEST, .asc =3D 0x21, .ascq =3D 0x04 +}; + +static const struct SCSISense sense_code_WRITE_BOUNDARY_VIOLATION =3D { + .key =3D ILLEGAL_REQUEST, .asc =3D 0x21, .ascq =3D 0x05 +}; + +static const struct SCSISense sense_code_INSUFFICIENT_ZONE_RESOURCES =3D { + .key =3D DATA_PROTECT, .asc =3D 0x55, .ascq =3D 0x0e +}; + +static const struct SCSISense sense_code_INSUFFICIENT_RESOURCES =3D { + .key =3D ILLEGAL_REQUEST, .asc =3D 0x55, .ascq =3D 0x03 +}; + +/* REPORT ZONES reporting options */ +static bool ufs_zbc_report_option_valid(uint8_t rep_opts) +{ + switch (rep_opts) { + case 0x00 ... 0x07: + case 0x10: + case 0x11: + case 0x3e: + case 0x3f: + return true; + default: + return false; + } +} + +static bool ufs_zbc_report_option_match(UfsZoneState *zone, uint8_t rep_op= ts) +{ + switch (rep_opts) { + case 0x00: /* all zones */ + case 0x3e: /* all zones except gap zones */ + return true; + case 0x01: + return zone->cond =3D=3D UFS_ZONE_COND_EMPTY; + case 0x02: + return zone->cond =3D=3D UFS_ZONE_COND_IMPLICIT_OPEN; + case 0x03: + return zone->cond =3D=3D UFS_ZONE_COND_EXPLICIT_OPEN; + case 0x04: + return zone->cond =3D=3D UFS_ZONE_COND_CLOSED; + case 0x05: + return zone->cond =3D=3D UFS_ZONE_COND_FULL; + default: + /* + * Read-only, offline, reset write pointer recommended, non-sequen= tial + * write resources active and not write pointer zones: none exist. + */ + return false; + } +} + +/* + * outbuf holds at least the header and otherwise no more than the smaller= of + * the allocation length and the whole report. + */ +static bool ufs_zbc_report_zones(UfsLu *lu, UfsRequest *req, uint8_t *outb= uf, + uint32_t outbuf_len, uint32_t *len, + SCSISense *sense) +{ + uint8_t *cdb =3D req->req_upiu.sc.cdb; + uint64_t nr_blocks =3D be64_to_cpu(lu->unit_desc.logical_block_count); + uint64_t zone_start_lba =3D ldq_be_p(&cdb[2]); + uint32_t alloc_len =3D ldl_be_p(&cdb[10]); + uint8_t rep_opts =3D cdb[14] & 0x3f; + bool partial =3D cdb[14] & 0x80; + uint32_t limit, max_descs, nr_descs =3D 0; + uint32_t offset =3D REPORT_ZONES_HEADER_SIZE; + uint32_t i; + + trace_ufs_zbc_report_zones(lu->lun, zone_start_lba, alloc_len, rep_opt= s); + + *len =3D 0; + + if ((cdb[1] & 0x1f) !=3D UFS_ZBC_REPORT_ZONES || + !ufs_zbc_report_option_valid(rep_opts)) { + *sense =3D SENSE_CODE(INVALID_FIELD); + return false; + } + + if (zone_start_lba >=3D nr_blocks) { + *sense =3D SENSE_CODE(LBA_OUT_OF_RANGE); + return false; + } + + if (alloc_len =3D=3D 0) { + return true; + } + + assert(outbuf_len >=3D REPORT_ZONES_HEADER_SIZE); + limit =3D MIN(alloc_len, outbuf_len); + max_descs =3D limit > REPORT_ZONES_HEADER_SIZE ? + (limit - REPORT_ZONES_HEADER_SIZE) / REPORT_ZONES_DESC_SIZ= E : + 0; + + for (i =3D ufs_zone_idx(lu, zone_start_lba); i < lu->zone_info.nr_zone= s; + i++) { + UfsZoneState *zone =3D &lu->zone_array[i]; + + if (!ufs_zbc_report_option_match(zone, rep_opts)) { + continue; + } + + if (nr_descs < max_descs) { + outbuf[offset] =3D zone->type; + outbuf[offset + 1] =3D zone->cond << 4; + stq_be_p(&outbuf[offset + 8], zone->size); + stq_be_p(&outbuf[offset + 16], zone->start); + stq_be_p(&outbuf[offset + 24], zone->wp); + offset +=3D REPORT_ZONES_DESC_SIZE; + } else if (partial) { + break; + } + + nr_descs++; + } + + /* Zone list length and maximum LBA */ + stl_be_p(&outbuf[0], nr_descs * REPORT_ZONES_DESC_SIZE); + stq_be_p(&outbuf[8], nr_blocks - 1); + + *len =3D MIN(offset, limit); + return true; +} + +/* + * OPEN ZONE with the ALL bit opens every closed zone, closing implicitly = open + * zones as needed. It fails without changing anything if the closed and t= he + * explicitly open zones together exceed the open zone limit. + */ +static bool ufs_open_all_zones(UfsLu *lu) +{ + uint32_t i, nr_explicit =3D 0, nr_closed =3D 0; + + for (i =3D 0; i < lu->zone_info.nr_zones; i++) { + nr_explicit +=3D lu->zone_array[i].cond =3D=3D UFS_ZONE_COND_EXPLI= CIT_OPEN; + nr_closed +=3D lu->zone_array[i].cond =3D=3D UFS_ZONE_COND_CLOSED; + } + + if (lu->zone_info.max_open && + nr_explicit + nr_closed > lu->zone_info.max_open) { + return false; + } + + for (i =3D 0; i < lu->zone_info.nr_zones; i++) { + if (lu->zone_array[i].cond =3D=3D UFS_ZONE_COND_CLOSED) { + ufs_assign_zone_cond(lu, &lu->zone_array[i], + UFS_ZONE_COND_EXPLICIT_OPEN); + } + } + + while (lu->zone_info.max_open && + lu->zone_info.nr_open > lu->zone_info.max_open) { + if (!ufs_close_implicit_open_zone(lu)) { + break; + } + } + + return true; +} + +static int ufs_zbc_zone_op(UfsLu *lu, UfsRequest *req, SCSISense *sense) +{ + uint8_t *cdb =3D req->req_upiu.sc.cdb; + uint8_t action =3D cdb[1] & 0x1f; + uint64_t zone_id =3D ldq_be_p(&cdb[2]); + uint16_t zone_count =3D lduw_be_p(&cdb[12]); + bool all =3D cdb[14] & 0x01; + UfsZoneState *zone; + uint32_t i; + + trace_ufs_zbc_zone_op(lu->lun, action, zone_id, all); + + switch (action) { + case UFS_ZBC_CLOSE_ZONE: + case UFS_ZBC_FINISH_ZONE: + case UFS_ZBC_OPEN_ZONE: + case UFS_ZBC_RESET_WRITE_POINTER: + break; + default: + *sense =3D SENSE_CODE(INVALID_FIELD); + return SCSI_COMMAND_FAIL; + } + + if (all) { + if (action =3D=3D UFS_ZBC_OPEN_ZONE) { + if (!ufs_open_all_zones(lu)) { + *sense =3D SENSE_CODE(INSUFFICIENT_ZONE_RESOURCES); + return SCSI_COMMAND_FAIL; + } + return 0; + } + + for (i =3D 0; i < lu->zone_info.nr_zones; i++) { + zone =3D &lu->zone_array[i]; + if (action =3D=3D UFS_ZBC_CLOSE_ZONE) { + ufs_close_zone(lu, zone); + } else if (action =3D=3D UFS_ZBC_FINISH_ZONE) { + ufs_finish_zone(lu, zone, true); + } else { + ufs_reset_write_pointer_zone(lu, zone); + } + } + return 0; + } + + /* Operations on a range of zones are not supported */ + if (zone_count > 1) { + *sense =3D SENSE_CODE(INVALID_FIELD); + return SCSI_COMMAND_FAIL; + } + + zone =3D ufs_get_zone_by_lba(lu, zone_id); + if (!zone) { + *sense =3D SENSE_CODE(LBA_OUT_OF_RANGE); + return SCSI_COMMAND_FAIL; + } + + if (zone_id !=3D zone->start) { + *sense =3D SENSE_CODE(INVALID_FIELD); + return SCSI_COMMAND_FAIL; + } + + switch (action) { + case UFS_ZBC_CLOSE_ZONE: + ufs_close_zone(lu, zone); + break; + case UFS_ZBC_FINISH_ZONE: + ufs_finish_zone(lu, zone, false); + break; + case UFS_ZBC_OPEN_ZONE: + if (!ufs_open_zone(lu, zone, UFS_ZONE_COND_EXPLICIT_OPEN)) { + *sense =3D SENSE_CODE(INSUFFICIENT_ZONE_RESOURCES); + return SCSI_COMMAND_FAIL; + } + break; + case UFS_ZBC_RESET_WRITE_POINTER: + ufs_reset_write_pointer_zone(lu, zone); + break; + } + + return 0; +} + +/* LBA and number of blocks of a command that writes the medium */ +static bool ufs_zbc_write_range(const uint8_t *cdb, uint64_t *lba, + uint32_t *nr_blocks) +{ + switch (cdb[0]) { + case WRITE_6: + *lba =3D ldl_be_p(&cdb[0]) & 0x1fffff; + *nr_blocks =3D cdb[4] ? cdb[4] : 256; + return true; + case WRITE_10: + case WRITE_VERIFY_10: + *lba =3D (uint32_t)ldl_be_p(&cdb[2]); + *nr_blocks =3D lduw_be_p(&cdb[7]); + return true; + case WRITE_12: + case WRITE_VERIFY_12: + *lba =3D (uint32_t)ldl_be_p(&cdb[2]); + *nr_blocks =3D ldl_be_p(&cdb[6]); + return true; + case WRITE_16: + case WRITE_VERIFY_16: + *lba =3D ldq_be_p(&cdb[2]); + *nr_blocks =3D ldl_be_p(&cdb[10]); + return true; + default: + return false; + } +} + +/* + * Check a write against the zone it targets and, if it may proceed, leave= it + * for ufs_zbc_start_write(). A write that reaches beyond the capacity or = has + * no blocks goes to scsi-hd unchecked: scsi-hd fails the former and + * completes the latter without writing anything. + */ +static bool ufs_zbc_check_write(UfsLu *lu, UfsRequest *req, SCSISense *sen= se) +{ + const uint8_t *cdb =3D req->req_upiu.sc.cdb; + uint64_t nr_blocks =3D be64_to_cpu(lu->unit_desc.logical_block_count); + uint64_t lba; + uint32_t len; + UfsZoneState *zone; + + if (!ufs_zbc_write_range(cdb, &lba, &len)) { + return true; + } + + if (len =3D=3D 0 || lba >=3D nr_blocks || len > nr_blocks - lba) { + return true; + } + + zone =3D ufs_get_zone_by_lba(lu, lba); + + if (lba + len > ufs_zone_end(zone)) { + *sense =3D SENSE_CODE(WRITE_BOUNDARY_VIOLATION); + } else if (zone->cond =3D=3D UFS_ZONE_COND_FULL) { + *sense =3D SENSE_CODE(INVALID_FIELD); + } else if (lba !=3D zone->wp) { + *sense =3D SENSE_CODE(UNALIGNED_WRITE); + } else if (!ufs_can_open_zone(lu, zone)) { + *sense =3D SENSE_CODE(INSUFFICIENT_ZONE_RESOURCES); + } else { + req->zone_write =3D zone; + req->zone_write_blocks =3D len; + return true; + } + + trace_ufs_zbc_write_rejected(lu->lun, lba, len, zone->wp, zone->cond, + sense->asc, sense->ascq); + return false; +} + +/* + * Commands that a zoned LU handles itself. Everything else, including REA= D, + * MODE SENSE, writes that passed ufs_zbc_check_write() and the INQUIRY da= ta + * that ufs_zoned_fixup_inquiry() adjusts, goes to the wrapped scsi-hd + * (UFS_REQUEST_NO_COMPLETE). + */ +static UfsReqResult ufs_emulate_zbc_cmd(UfsLu *lu, UfsRequest *req) +{ + g_autofree uint8_t *outbuf =3D NULL; + uint8_t *cdb =3D req->req_upiu.sc.cdb; + uint8_t sense_buf[UFS_SENSE_SIZE]; + uint8_t scsi_status =3D GOOD; + uint32_t len =3D 0, xfer_len; + SCSISense sense; + bool ok; + + /* + * Let scsi-hd report a pending unit attention first, as it does for + * every command it does not exempt (INQUIRY and REPORT LUNS among the= m), + * so that nothing here, including the write pointer of a write, chang= es + * before the guest has seen it. + */ + if (cdb[0] !=3D INQUIRY && + (lu->scsi_dev->unit_attention.key =3D=3D UNIT_ATTENTION || + lu->bus.unit_attention.key =3D=3D UNIT_ATTENTION)) { + return UFS_REQUEST_NO_COMPLETE; + } + + switch (cdb[0]) { + case UFS_ZBC_IN: { + uint32_t alloc_len =3D ldl_be_p(&cdb[10]); + uint64_t report_len =3D REPORT_ZONES_HEADER_SIZE + + (uint64_t)REPORT_ZONES_DESC_SIZE * + lu->zone_info.nr_zones; + uint32_t outbuf_len; + + /* As for the commands that reach scsi-hd, the PRDT must hold it */ + if (!ufs_fit_data_buffer(req, alloc_len)) { + return UFS_REQUEST_MISMATCH_DATA_BUF; + } + + /* Size the buffer by what can be returned, not by alloc_len alone= */ + outbuf_len =3D MIN(alloc_len, report_len); + outbuf_len =3D MAX(outbuf_len, REPORT_ZONES_HEADER_SIZE); + outbuf =3D g_try_malloc0(outbuf_len); + if (!outbuf) { + sense =3D SENSE_CODE(INSUFFICIENT_RESOURCES); + ok =3D false; + break; + } + ok =3D ufs_zbc_report_zones(lu, req, outbuf, outbuf_len, &len, &se= nse); + break; + } + case UFS_ZBC_OUT: + ok =3D ufs_zbc_zone_op(lu, req, &sense) =3D=3D 0; + break; + case INQUIRY: + /* Only the ZBC device characteristics VPD page is emulated here */ + if (!(cdb[1] & 0x1) || cdb[2] !=3D 0xb6) { + return UFS_REQUEST_NO_COMPLETE; + } + if (!ufs_fit_data_buffer(req, lduw_be_p(&cdb[3]))) { + return UFS_REQUEST_MISMATCH_DATA_BUF; + } + outbuf =3D g_malloc0(ZBC_VPD_B6_LEN); + len =3D MIN(ufs_emulate_zbc_vpd_page(lu, outbuf), lduw_be_p(&cdb[3= ])); + ok =3D true; + break; + case UNMAP: + /* Deallocating blocks would bypass the write pointers */ + sense =3D SENSE_CODE(INVALID_OPCODE); + ok =3D false; + break; + case WRITE_SAME_10: + case WRITE_SAME_16: + /* + * Not a UFS command (Linux sets no_write_same for UFS), and scsi-= hd + * completes one with NDOB set without writing anything. + */ + sense =3D SENSE_CODE(INVALID_OPCODE); + ok =3D false; + break; + default: + if (ufs_zbc_check_write(lu, req, &sense)) { + return UFS_REQUEST_NO_COMPLETE; + } + ok =3D false; + break; + } + + if (!ok) { + scsi_build_sense(sense_buf, sense); + scsi_status =3D CHECK_CONDITION; + len =3D 0; + } + + xfer_len =3D MIN(len, req->data_len); + if (xfer_len > 0 && + dma_buf_read(outbuf, xfer_len, NULL, req->sg, + MEMTXATTRS_UNSPECIFIED) !=3D MEMTX_OK) { + return UFS_REQUEST_FAIL; + } + + ufs_build_scsi_response_upiu(req, sense_buf, sizeof(sense_buf), xfer_l= en, + scsi_status); + + return UFS_REQUEST_SUCCESS; +} + static UfsReqResult ufs_emulate_scsi_cmd(UfsLu *lu, UfsRequest *req) { uint8_t lun =3D lu->lun; @@ -387,6 +1169,17 @@ static UfsReqResult ufs_process_scsi_cmd(UfsLu *lu, U= fsRequest *req) SCSIRequest *scsi_req; uint32_t len; =20 + req->zone_write =3D NULL; + req->zone =3D NULL; + + if (lu->zone_enabled) { + UfsReqResult result =3D ufs_emulate_zbc_cmd(lu, req); + /* UFS_REQUEST_NO_COMPLETE continues command processing */ + if (result !=3D UFS_REQUEST_NO_COMPLETE) { + return result; + } + } + /* * Each ufs-lu has its own independent virtual SCSI bus. Therefore, we= can't * use scsi_target_emulate_report_luns() which gets all lu information= over @@ -413,13 +1206,13 @@ static UfsReqResult ufs_process_scsi_cmd(UfsLu *lu, = UfsRequest *req) =20 /* * scsi-hd moves as many bytes as the SG list holds, and without an SG - * list it would call the transfer_data callback, which ufs-lu does not - * set. A UFS device moves what the CDB asks for, so fit the PRDT to t= he - * transfer length, and fail a request whose PRDT describes less, a - * missing PRDT included, with OCS MISMATCH DATA BUFFER SIZE, as - * virtio-scsi fails a buffer that does not match the command. The - * response reports the rest of the Expected Data Transfer Length as a - * residual. A request that reports a pending unit attention moves no + * list it would call the transfer_data callback, which only handles t= he + * INQUIRY data of a zoned LU. A UFS device moves what the CDB asks fo= r, + * so fit the PRDT to the transfer length, and fail a request whose PR= DT + * describes less, a missing PRDT included, with OCS MISMATCH DATA BUF= FER + * SIZE, as virtio-scsi fails a buffer that does not match the command. + * The response reports the rest of the Expected Data Transfer Length = as + * a residual. A request that reports a pending unit attention moves no * data; its sense is already set, so let it through and the guest sees * the UA. */ @@ -431,8 +1224,13 @@ static UfsReqResult ufs_process_scsi_cmd(UfsLu *lu, U= fsRequest *req) =20 req->sreq =3D scsi_req; =20 + /* + * 0 means scsi-hd completed the command already, for example because = it + * rejected it: only a write that it accepted starts. + */ len =3D scsi_req_enqueue(scsi_req); if (len) { + ufs_zbc_start_write(lu, req); scsi_req_continue(scsi_req); } =20 @@ -442,6 +1240,10 @@ static UfsReqResult ufs_process_scsi_cmd(UfsLu *lu, U= fsRequest *req) static const Property ufs_lu_props[] =3D { DEFINE_PROP_DRIVE("drive", UfsLu, conf.blk), DEFINE_PROP_UINT8("lun", UfsLu, lun, 0), + DEFINE_PROP_BOOL("zoned", UfsLu, params.zoned, false), + DEFINE_PROP_SIZE("zoned.zone_size", UfsLu, params.zone_size, + UFS_DEFAULT_ZONE_SIZE), + DEFINE_PROP_UINT32("zoned.max_open", UfsLu, params.zone_max_open, 6), }; =20 static bool ufs_add_lu(UfsHc *u, UfsLu *lu, Error **errp) @@ -474,12 +1276,71 @@ void ufs_init_wlu(UfsLu *wlu, uint8_t wlun) wlu->scsi_op =3D &ufs_emulate_scsi_cmd; } =20 +static bool ufs_lu_init_zoned(UfsLu *lu, Error **errp) +{ + uint64_t nr_blocks =3D be64_to_cpu(lu->unit_desc.logical_block_count); + uint64_t zone_size =3D lu->params.zone_size; + uint64_t nr_zones, start =3D 0; + UfsZoneState *zone; + uint32_t i; + + if (!zone_size || zone_size % UFS_BLOCK_SIZE || + zone_size / UFS_BLOCK_SIZE > UINT32_MAX) { + error_setg(errp, + "zoned.zone_size must be a non-zero multiple of %d byte= s " + "below 16 TiB", + UFS_BLOCK_SIZE); + return false; + } + + zone_size /=3D UFS_BLOCK_SIZE; + if (zone_size > nr_blocks) { + error_setg(errp, "zoned.zone_size is larger than the drive"); + return false; + } + + /* The REPORT ZONES zone list length must fit in 32 bits */ + nr_zones =3D DIV_ROUND_UP(nr_blocks, zone_size); + if (nr_zones > (UINT32_MAX - REPORT_ZONES_HEADER_SIZE) / + REPORT_ZONES_DESC_SIZE) { + error_setg(errp, "too many zones (%" PRIu64 "), raise zoned.zone_s= ize", + nr_zones); + return false; + } + + lu->zone_array =3D g_try_new0(UfsZoneState, nr_zones); + if (!lu->zone_array) { + error_setg(errp, "cannot allocate the state of %" PRIu64 " zones", + nr_zones); + return false; + } + + lu->zone_info.zone_size =3D zone_size; + lu->zone_info.nr_zones =3D nr_zones; + lu->zone_info.nr_open =3D 0; + lu->zone_info.max_open =3D lu->params.zone_max_open; + + for (i =3D 0, zone =3D lu->zone_array; i < nr_zones; i++, zone++) { + /* SEQUENTIAL WRITE REQUIRED */ + zone->type =3D 0x2; + zone->cond =3D UFS_ZONE_COND_EMPTY; + zone->start =3D start; + zone->size =3D MIN(zone_size, nr_blocks - start); + zone->wp =3D start; + start +=3D zone->size; + } + + lu->zone_enabled =3D true; + return true; +} + static void ufs_init_lu(UfsLu *lu) { BlockBackend *blk =3D lu->conf.blk; int64_t brdv_len =3D blk_getlength(blk); =20 memset(&lu->unit_desc, 0, sizeof(lu->unit_desc)); + lu->zone_enabled =3D false; lu->unit_desc.length =3D sizeof(UnitDescriptor); lu->unit_desc.descriptor_idn =3D UFS_QUERY_DESC_IDN_UNIT; lu->unit_desc.lu_enable =3D 0x01; @@ -527,6 +1388,10 @@ static void ufs_init_scsi_device(UfsLu *lu, BlockBack= end *blk, Error **errp) qdev_prop_set_uint32(scsi_dev, "logical_block_size", UFS_BLOCK_SIZE); qdev_prop_set_uint32(scsi_dev, "scsi-id", 0); qdev_prop_set_uint32(scsi_dev, "lun", lu->lun); + if (lu->params.zoned) { + /* Deallocating blocks would bypass the write pointers */ + qdev_prop_set_uint32(scsi_dev, "discard_granularity", 0); + } if (!qdev_prop_set_drive_err(scsi_dev, "drive", blk, errp)) { object_unparent(OBJECT(scsi_dev)); return; @@ -543,6 +1408,7 @@ static void ufs_init_scsi_device(UfsLu *lu, BlockBacke= nd *blk, Error **errp) =20 static void ufs_lu_realize(DeviceState *dev, Error **errp) { + ERRP_GUARD(); UfsLu *lu =3D DO_UPCAST(UfsLu, qdev, dev); BusState *s =3D qdev_get_parent_bus(dev); UfsHc *u =3D UFS_BUS(s)->hc; @@ -566,12 +1432,30 @@ static void ufs_lu_realize(DeviceState *dev, Error *= *errp) return; } =20 + if (lu->params.zoned && !blk_supports_write_perm(blk)) { + error_setg(errp, "a zoned logical unit needs a writable drive"); + return; + } + ufs_init_lu(lu); - if (!ufs_add_lu(u, lu, errp)) { + if (lu->params.zoned && !ufs_lu_init_zoned(lu, errp)) { return; } =20 + if (!ufs_add_lu(u, lu, errp)) { + goto fail; + } + ufs_init_scsi_device(lu, blk, errp); + if (*errp) { + goto fail; + } + return; + +fail: + lu->zone_enabled =3D false; + g_free(lu->zone_array); + lu->zone_array =3D NULL; } =20 static void ufs_lu_unrealize(DeviceState *dev) @@ -579,6 +1463,9 @@ static void ufs_lu_unrealize(DeviceState *dev) UfsLu *lu =3D DO_UPCAST(UfsLu, qdev, dev); =20 lu->scsi_dev =3D NULL; + lu->zone_enabled =3D false; + g_free(lu->zone_array); + lu->zone_array =3D NULL; } =20 static void ufs_lu_class_init(ObjectClass *oc, const void *data) diff --git a/hw/ufs/trace-events b/hw/ufs/trace-events index db9d172713..c5c148be91 100644 --- a/hw/ufs/trace-events +++ b/hw/ufs/trace-events @@ -59,3 +59,9 @@ ufs_err_mcq_delete_cq_sq_not_deleted(uint8_t sqid, uint8_= t cqid) "mcq sq %"PRIu8 # HID (Host Initiated Defragmentation) ufs_hid_defrag_operation(uint8_t op, uint8_t state) "HID defrag operation = 0x%"PRIx8", new state 0x%"PRIx8"" ufs_hid_defrag_progress(uint32_t remaining, uint8_t progress) "HID defrag = remaining %"PRIu32", progress %"PRIu8"%%" + +# lu.c (zoned logical units) +ufs_zbc_report_zones(uint8_t lun, uint64_t lba, uint32_t alloc_len, uint8_= t options) "lun 0x%"PRIx8", lba %"PRIu64", alloc_len %"PRIu32", options 0x%= "PRIx8"" +ufs_zbc_zone_op(uint8_t lun, uint8_t action, uint64_t zone_id, bool all) "= lun 0x%"PRIx8", action 0x%"PRIx8", zone id %"PRIu64", all %d" +ufs_zbc_write_rejected(uint8_t lun, uint64_t lba, uint32_t len, uint64_t w= p, uint8_t cond, uint8_t asc, uint8_t ascq) "lun 0x%"PRIx8", lba %"PRIu64",= len %"PRIu32", wp %"PRIu64", cond 0x%"PRIx8", asc 0x%"PRIx8", ascq 0x%"PRI= x8"" +ufs_zbc_write_undone(uint8_t lun, uint64_t zone_start, uint64_t wp) "lun 0= x%"PRIx8", zone start %"PRIu64", wp back to %"PRIu64"" diff --git a/hw/ufs/ufs.h b/hw/ufs/ufs.h index 15b3a352f3..f6583a5db7 100644 --- a/hw/ufs/ufs.h +++ b/hw/ufs/ufs.h @@ -17,6 +17,28 @@ #include "scsi/constants.h" #include "system/dma.h" =20 +typedef struct UfsZoneState { + uint8_t type; + UfsZoneCond cond; + uint64_t start; + uint64_t size; + uint64_t wp; + /* + * bumped by every change of cond or wp, except an implicit close, and= by + * FINISH ZONE of a full zone + */ + uint64_t seq; +} UfsZoneState; + +typedef struct UfsZoneInfo { + uint32_t zone_size; + uint32_t nr_zones; + uint32_t nr_open; + uint32_t max_open; +} UfsZoneInfo; + +#define UFS_DEFAULT_ZONE_SIZE (128 * MiB) + #define UFS_MAX_LUS 32 #define UFS_MAX_MCQ_QNUM 32 #define UFS_BLOCK_SIZE_SHIFT 12 @@ -66,6 +88,16 @@ typedef struct UfsRequest { QEMUSGList *sg; uint32_t data_len; =20 + /* write to a zoned LU that passed the zone checks, not yet started */ + UfsZoneState *zone_write; + uint32_t zone_write_blocks; + + /* write started on a zoned LU, undone if scsi-hd does not complete it= */ + UfsZoneState *zone; + UfsZoneCond zone_cond_before; + uint64_t zone_wp_before; + uint64_t zone_seq; + /* for MCQ */ struct UfsSq *sq; struct UfsCqEntry cqe; @@ -80,6 +112,12 @@ static inline bool ufs_mcq_req(UfsRequest *req) struct UfsLu; typedef UfsReqResult (*UfsScsiOp)(struct UfsLu *, UfsRequest *); =20 +typedef struct UfsLuParams { + bool zoned; + uint64_t zone_size; + uint32_t zone_max_open; +} UfsLuParams; + typedef struct UfsLu { DeviceState qdev; uint8_t lun; @@ -88,6 +126,11 @@ typedef struct UfsLu { SCSIDevice *scsi_dev; BlockConf conf; UfsScsiOp scsi_op; + /* For Zoned UFS */ + UfsLuParams params; + bool zone_enabled; + UfsZoneInfo zone_info; + UfsZoneState *zone_array; } UfsLu; =20 typedef struct UfsParams { diff --git a/include/block/ufs.h b/include/block/ufs.h index 4a614fbe52..a0bbe873d6 100644 --- a/include/block/ufs.h +++ b/include/block/ufs.h @@ -708,6 +708,33 @@ enum { UFS_MASK_OCS =3D 0xFF, }; =20 +/* ZBC zone conditions, table 26 of zbcr05 */ +typedef enum UfsZoneCond { + UFS_ZONE_COND_NOT_WP =3D 0x0, + UFS_ZONE_COND_EMPTY =3D 0x1, + UFS_ZONE_COND_IMPLICIT_OPEN =3D 0x2, + UFS_ZONE_COND_EXPLICIT_OPEN =3D 0x3, + UFS_ZONE_COND_CLOSED =3D 0x4, + UFS_ZONE_COND_READ_ONLY =3D 0xd, + UFS_ZONE_COND_FULL =3D 0xe, + UFS_ZONE_COND_OFFLINE =3D 0xf, +} UfsZoneCond; + +/* ZBC IN and ZBC OUT operation codes and their service actions */ +#define UFS_ZBC_OUT 0x94 +#define UFS_ZBC_IN 0x95 + +enum { + UFS_ZBC_REPORT_ZONES =3D 0x00, +}; + +enum { + UFS_ZBC_CLOSE_ZONE =3D 0x01, + UFS_ZBC_FINISH_ZONE =3D 0x02, + UFS_ZBC_OPEN_ZONE =3D 0x03, + UFS_ZBC_RESET_WRITE_POINTER =3D 0x04, +}; + /* * struct UfshcdSgEntry - UFSHCI PRD Entry * @addr: Physical address; DW-0 and DW-1. --=20 2.43.0 From nobody Fri Oct 23 15:09:53 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=samsung.com ARC-Seal: i=1; a=rsa-sha256; t=1792455777; cv=none; d=zohomail.com; s=zohoarc; b=nMPNAhyh6eRduiLf8aDnmEhwgldC+kkMCxfIx6Dh4GFkxPwdB0xPBIl8lxILd3SNpV+FIubL7NXlICjWsQ/Iy+udi4rUcfPRnxJ+hH5RKyRfZeNLFkMXkt78f94EGb4Y480j2FST/C48CHcBxOUV4cVKyfwc2W1LYsDRntRYw88= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1792455777; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:Reply-To:Reply-To:Sender:Subject:Subject:To:To:Message-Id; bh=iHEnuGQ0vn0bxGkok6NKoLLsIpZlyUze0rBzoAAm+yg=; b=XOXBIxSNYGQ4DkwOPbf+9J/s8QwJCgfddqxZdMCjmC6xwFi6ybO5a9GuduYYgAL8Rfqpc9Ga6WLb1IIT46V7ipUFhQmfkNVpIJPuGHeDACdSa6zVwx03W78zUcLV3MgR1E39oy/uesw7e3Vo2WucqSynOxLoSNYnh7+p4g/0ryo= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 179245577786379.74889390299745; Mon, 19 Oct 2026 17:22:57 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1xIxcb-0007rQ-Hh; Mon, 19 Oct 2026 20:22:29 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1xIxcZ-0007r5-JG for qemu-devel@nongnu.org; Mon, 19 Oct 2026 20:22:27 -0400 Received: from mailout1.samsung.com ([203.254.224.24]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1xIxcT-0003vK-AH for qemu-devel@nongnu.org; Mon, 19 Oct 2026 20:22:27 -0400 Received: from epcas2p4.samsung.com (unknown [182.195.41.56]) by mailout1.samsung.com (KnoxPortal) with ESMTP id 20261020002218epoutp017069b403bb49aaf0db147836eaf27e0f~gFP89qk7s2992229922epoutp01v for ; Tue, 20 Oct 2026 00:22:18 +0000 (GMT) Received: from epsnrtp01.localdomain (unknown [182.195.42.153]) by epcas2p4.samsung.com (KnoxPortal) with ESMTPS id 20261020002217epcas2p42a673fecb3c91e95ae409f8d41a0a2e8~gFP8mNvs_2580125801epcas2p4j; Tue, 20 Oct 2026 00:22:17 +0000 (GMT) Received: from epcas2p2.samsung.com (unknown [182.195.38.199]) by epsnrtp01.localdomain (Postfix) with ESMTP id 4j7tQd3CNzz6B9mF; Tue, 20 Oct 2026 00:22:17 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout1.samsung.com 20261020002218epoutp017069b403bb49aaf0db147836eaf27e0f~gFP89qk7s2992229922epoutp01v DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1792455738; bh=iHEnuGQ0vn0bxGkok6NKoLLsIpZlyUze0rBzoAAm+yg=; h=Subject:Reply-To:From:To:CC:In-Reply-To:Date:References:From; b=YVvlzf6oDwMFtwLO6JW3615c2Xf932YCBle1HjsTJB+MrB2lxk3VJgTiGKxK6BxTJ weVtFoFhwQziJH+eNclT3s55Fdkt2vMpG66UcCt2TucTGPLEv5bf9flZfKWbHkyNGC pvr/7SzartlpxQJeuy0uWrTJpTz6L2R0oPnxYiJI= Mime-Version: 1.0 Subject: [PATCH v3 6/6] tests/qtest: Add tests for Zoned UFS From: Daejun Park To: "qemu-devel@nongnu.org" CC: Jeuk Kim , "farosas@suse.de" , "lvivier@redhat.com" , "pbonzini@redhat.com" , "kwolf@redhat.com" , "hreitz@redhat.com" , "qemu-block@nongnu.org" , "pierrick.bouvier@oss.qualcomm.com" , "stanleyjhu@google.com" , Keoseong Park , Daejun Park X-Priority: 3 X-Content-Kind-Code: NORMAL In-Reply-To: <20261020001647epcms2p52cd58173164a43e252c33a3eb5935dc8@epcms2p5> X-CPGS-Detection: blocking_info_exchange X-Drm-Type: N,general X-Msg-Generator: Mail X-Msg-Type: PERSONAL X-Reply-Demand: N Message-ID: <20261020002216epcms2p4abf7ffd4848b322429dcb8e73b8da4eb@epcms2p4> Date: Tue, 20 Oct 2026 09:22:16 +0900 X-CMS-MailID: 20261020002216epcms2p4abf7ffd4848b322429dcb8e73b8da4eb Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" X-Sendblock-Type: AUTO_CONFIDENTIAL CMS-TYPE: 102P X-CPGSPASS: Y X-CPGSPASS: Y cpgsPolicy: CPGSC10-223,Y X-CFilter-Loop: Reflected X-CMS-RootMailID: 20261020001647epcms2p52cd58173164a43e252c33a3eb5935dc8 References: <20261020001647epcms2p52cd58173164a43e252c33a3eb5935dc8@epcms2p5> Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=203.254.224.24; envelope-from=daejun7.park@samsung.com; helo=mailout1.samsung.com X-Spam_score_int: -46 X-Spam_score: -4.7 X-Spam_bar: ---- X-Spam_report: (-4.7 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.329, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, RCVD_IN_MSPIKE_H3=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_PASS=-0.001, SPF_NONE=0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: daejun7.park@samsung.com Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @samsung.com) X-ZM-MESSAGEID: 1792455778170158501 Add ten qtests for zoned logical units. They use four zoned logical units: one with 2 MiB zones, one whose capacity is not a multiple of its 3 MiB zones, one on blkdebug with a single open zone, where the first writes at LBA 0, to the last block of zone 2 and to the first block of zone 3 fail with an I/O error, and one on null-co that is larger than 8 TiB. zufs-init checks the INQUIRY device type, the supported VPD pages with full and short allocation lengths, the ZBC device characteristics page, that neither READ CAPACITY(16) nor the Block Limits and Logical Block Provisioning VPD pages offer UNMAP and that the latter offers no WRITE SAME, the caching mode page, REPORT ZONES, and INQUIRY requests without a PRDT for the standard data, which scsi-hd builds, and for the ZBC page. zufs-write, also run with MCQ as zufs-mcq-write, checks write pointer handling, SYNCHRONIZE CACHE(16), resets of open zones, finishing an empty zone, writing to more zones than max_open allows, LBAs past the end of the logical unit, and that a REPORT ZONES allocation length larger than the data buffer fails with OCS MISMATCH DATA BUFFER SIZE. zufs-write-cmds checks that WRITE(6) and WRITE(10) move the write pointer, that WRITE SAME(10) and WRITE SAME(16) are rejected, the sense codes for writes crossing a zone and writes to a full zone, and that a WRITE(10) at an LBA above 2^31 is checked against the zone it addresses. zufs-open-zones checks explicit opens up to the limit, closing an implicitly open zone to make room, OPEN ALL with and without enough resources, FINISH ALL and RESET ALL. zufs-unit-attention checks that a zone operation is not performed while a unit attention is pending. zufs-io-error checks that a write failed by the drive moves the write pointer back. zufs-write-in-flight, with MCQ, holds writes in flight with a blkdebug breakpoint. It checks that a failed write moves the write pointer back also when, in the meantime, scsi-hd rejected another write to its zone or a write to another zone closed its zone implicitly, but not once a later write to its zone has started, and that a zone stays full when FINISH ZONE completed while a write that filled it was in flight and then failed. zufs-report-zones checks a start LBA inside a zone, PARTIAL, the reporting options 3Eh, 06h and 3Fh, and the smaller last zone. zufs-more-cmds checks WRITE(12), WRITE AND VERIFY(16), CLOSE ALL, finishing a closed zone, ZONE COUNT with and without the ALL bit, zone IDs that do not start a zone, and UNMAP. It also checks that writes refused before they start change no zone: a write without a PRDT leaves the write pointer alone; one with a shorter PRDT and one with a WRPROTECT field, which scsi-hd rejects, leave the write pointer and the condition of a closed or an explicitly open zone alone; the latter closes no implicitly open zone when all open resources are in use, and a read that follows does not start it. Signed-off-by: Daejun Park Reviewed-by: Jeuk Kim --- tests/qtest/ufs-test.c | 1136 ++++++++++++++++++++++++++++++++++++++++++++= ++++ 1 file changed, 1136 insertions(+) diff --git a/tests/qtest/ufs-test.c b/tests/qtest/ufs-test.c index e8993e8c24..e24caa2efc 100644 --- a/tests/qtest/ufs-test.c +++ b/tests/qtest/ufs-test.c @@ -1899,6 +1899,1118 @@ static void ufstest_hid(void *obj, void *data, QGu= estAllocator *alloc) ufs_exit(ufs, alloc); } =20 +#define ZONE_SIZE (2 * 1024 * 1024) +#define ZONE_BLOCKS (ZONE_SIZE / 4096) +#define MAX_OPEN_ZONE 6 +#define NUM_ZONES (TEST_IMAGE_SIZE / ZONE_SIZE) +#define REPORT_ZONES_DESC_SIZE 64 +#define ZONED_TEST_LUN 2 +/* A second zoned LU whose capacity is not a multiple of the zone size */ +#define ODD_ZONED_TEST_LUN 3 +#define ODD_ZONE_SIZE (3 * 1024 * 1024) +#define ODD_NUM_ZONES DIV_ROUND_UP(TEST_IMAGE_SIZE, ODD_ZONE_SIZE) +/* + * A zoned LU on blkdebug with one open zone: the first writes at LBA 0, to + * the last block of zone 2 and to the first block of zone 3 fail with an = I/O + * error, and a breakpoint can hold a write in flight. It is a -drive with= an + * id because qtest_qemu_io() needs a BlockBackend name: for a node name, + * x-qemu-io attaches a temporary BlockBackend, and detaching it drains the + * node, which never returns while a breakpoint holds a write. + */ +#define EIO_ZONED_TEST_LUN 4 +#define EIO_ZONED_TEST_DRIVE "drv4" +/* The last block of zone 2 */ +#define EIO_ZONE2_SECTOR ((3 * ZONE_SIZE - 4096) / 512) +/* The first block of zone 3 */ +#define EIO_ZONE3_SECTOR (3 * ZONE_SIZE / 512) +/* A zoned LU of more than 8 TiB with 128 MiB zones, for LBAs of 2^31 and = up */ +#define BIG_ZONED_TEST_LUN 5 +#define BIG_ZONED_TEST_SIZE ((8ULL << 40) + 128 * 1024 * 1024) + +static void ufs_zufs_build_write16(uint8_t *cdb, uint64_t lba, uint32_t bl= ocks) +{ + memset(cdb, 0, UFS_CDB_SIZE); + cdb[0] =3D WRITE_16; + stq_be_p(&cdb[2], lba); + stl_be_p(&cdb[10], blocks); +} + +static void ufs_zufs_build_zbc_out(uint8_t *cdb, uint8_t action, uint64_t = lba, + bool all) +{ + memset(cdb, 0, UFS_CDB_SIZE); + cdb[0] =3D UFS_ZBC_OUT; + cdb[1] =3D action; + stq_be_p(&cdb[2], lba); + cdb[14] =3D all ? 0x01 : 0x00; +} + +static void ufs_zufs_build_report_zones(uint8_t *cdb, uint8_t rep_opts, + uint32_t alloc_len) +{ + memset(cdb, 0, UFS_CDB_SIZE); + cdb[0] =3D UFS_ZBC_IN; + cdb[1] =3D UFS_ZBC_REPORT_ZONES; + stl_be_p(&cdb[10], alloc_len); + cdb[14] =3D rep_opts; +} + +static void ufs_zufs_clear_unit_attention(QUfs *ufs) +{ + const uint8_t request_sense_cdb[UFS_CDB_SIZE] =3D { + REQUEST_SENSE, + }; + uint8_t buf[4096]; + UtpUpiuRsp rsp_upiu; + enum UtpOcsCodes ocs; + + ocs =3D ufs_send_scsi_command(ufs, ZONED_TEST_LUN, request_sense_cdb, = NULL, 0, + buf, sizeof(buf), &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_SUCCESS); + g_assert_cmpuint(rsp_upiu.header.scsi_status, =3D=3D, CHECK_CONDITION); +} + +static uint8_t ufs_zufs_send(QUfs *ufs, const uint8_t *cdb, + const uint8_t *data_in, size_t data_in_len, + uint8_t *data_out, size_t data_out_len, + UtpUpiuRsp *rsp_upiu) +{ + enum UtpOcsCodes ocs; + + ocs =3D ufs_send_scsi_command(ufs, ZONED_TEST_LUN, cdb, data_in, data_= in_len, + data_out, data_out_len, rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_SUCCESS); + return rsp_upiu->header.scsi_status; +} + +static void ufs_zufs_assert_sense(UtpUpiuRsp *rsp_upiu, uint8_t key, + uint8_t asc, uint8_t ascq) +{ + g_assert_cmpuint(rsp_upiu->header.scsi_status, =3D=3D, CHECK_CONDITION= ); + g_assert_cmpuint(rsp_upiu->sr.sense_data[2] & 0xf, =3D=3D, key); + g_assert_cmpuint(rsp_upiu->sr.sense_data[12], =3D=3D, asc); + g_assert_cmpuint(rsp_upiu->sr.sense_data[13], =3D=3D, ascq); +} + +static uint32_t ufs_zufs_count_zones(QUfs *ufs, uint8_t rep_opts) +{ + uint8_t buf[4096]; + uint8_t cdb[UFS_CDB_SIZE]; + UtpUpiuRsp rsp_upiu; + + ufs_zufs_build_report_zones(cdb, rep_opts, sizeof(buf)); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, NULL, 0, buf, sizeof(buf), + &rsp_upiu), =3D=3D, GOOD); + return ldl_be_p(&buf[0]) / REPORT_ZONES_DESC_SIZE; +} + +/* Condition and write pointer of the zone at zone_start */ +static uint8_t ufs_zufs_zone(QUfs *ufs, uint8_t lun, uint64_t zone_start, + uint64_t *wp) +{ + uint8_t buf[4096]; + uint8_t cdb[UFS_CDB_SIZE]; + UtpUpiuRsp rsp_upiu; + enum UtpOcsCodes ocs; + + ufs_zufs_build_report_zones(cdb, 0x00, sizeof(buf)); + stq_be_p(&cdb[2], zone_start); + ocs =3D ufs_send_scsi_command(ufs, lun, cdb, NULL, 0, buf, sizeof(buf), + &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_SUCCESS); + g_assert_cmpuint(rsp_upiu.header.scsi_status, =3D=3D, GOOD); + g_assert_cmpuint(ldq_be_p(&buf[64 + 16]), =3D=3D, zone_start); + *wp =3D ldq_be_p(&buf[64 + 24]); + return buf[64 + 1] >> 4; +} + +static uint64_t ufs_zufs_write_pointer(QUfs *ufs, uint64_t zone_start) +{ + uint64_t wp; + + ufs_zufs_zone(ufs, ZONED_TEST_LUN, zone_start, &wp); + return wp; +} + +static uint8_t ufs_zufs_zone_cond(QUfs *ufs, uint64_t zone_start) +{ + uint64_t wp; + + return ufs_zufs_zone(ufs, ZONED_TEST_LUN, zone_start, &wp); +} + +static void ufstest_zufs_init(void *obj, void *data, QGuestAllocator *allo= c) +{ + QUfs *ufs =3D obj; + uint8_t buf[4096] =3D { 0 }; + const uint8_t inquiry_cdb[UFS_CDB_SIZE] =3D { + INQUIRY, 0x00, 0x00, 0x00, 0x60, + }; + const uint8_t inquiry_vpd_00_cdb[UFS_CDB_SIZE] =3D { + INQUIRY, 0x01 /* EVPD */, 0x00 /* page code */, 0x00, 0x40, + }; + const uint8_t inquiry_vpd_b6_cdb[UFS_CDB_SIZE] =3D { + INQUIRY, 0x01 /* EVPD */, 0xb6 /* page code */, 0x00, 0x40, + }; + const uint8_t mode_sense_caching_cdb[UFS_CDB_SIZE] =3D { + MODE_SENSE_10, 0x08 /* DBD */, MODE_PAGE_CACHING, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, + }; + const uint8_t read_capacity_cdb[UFS_CDB_SIZE] =3D { + SERVICE_ACTION_IN_16, SAI_READ_CAPACITY_16, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, + }; + uint8_t report_zones_cdb[UFS_CDB_SIZE]; + uint8_t cdb[UFS_CDB_SIZE]; + UtpUpiuRsp rsp_upiu; + uint32_t zone_list_len; + uint8_t vpd_00_len; + + ufs_init(ufs, alloc); + ufs_zufs_clear_unit_attention(ufs); + + /* Standard INQUIRY: host-managed zoned block device */ + g_assert_cmpuint(ufs_zufs_send(ufs, inquiry_cdb, NULL, 0, buf, 0x60, + &rsp_upiu), =3D=3D, GOOD); + g_assert_cmpuint(buf[0] & 0x1f, =3D=3D, TYPE_ZBC); + + /* Supported VPD pages include the ZBC device characteristics page */ + g_assert_cmpuint(ufs_zufs_send(ufs, inquiry_vpd_00_cdb, NULL, 0, buf, = 0x40, + &rsp_upiu), =3D=3D, GOOD); + g_assert_cmpuint(buf[0], =3D=3D, TYPE_ZBC); + g_assert_nonnull(memchr(&buf[4], 0xb6, buf[3])); + g_assert_cmpuint(buf[4 + buf[3] - 1], =3D=3D, 0xb6); + vpd_00_len =3D buf[3]; + + /* ZBC device characteristics VPD page */ + g_assert_cmpuint(ufs_zufs_send(ufs, inquiry_vpd_b6_cdb, NULL, 0, buf, = 0x40, + &rsp_upiu), =3D=3D, GOOD); + g_assert_cmpuint(buf[0], =3D=3D, TYPE_ZBC); + g_assert_cmpuint(buf[1], =3D=3D, 0xb6); + g_assert_cmpuint(lduw_be_p(&buf[2]), =3D=3D, 0x3c); + /* URSWRZ */ + g_assert_cmpuint(buf[4] & 0x1, =3D=3D, 1); + g_assert_cmpuint(ldl_be_p(&buf[16]), =3D=3D, MAX_OPEN_ZONE); + + /* Allocation length 4: only the header, whose page length counts 0xb6= */ + memset(cdb, 0, sizeof(cdb)); + memcpy(cdb, inquiry_vpd_00_cdb, 5); + cdb[4] =3D 4; + memset(buf, 0, sizeof(buf)); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, NULL, 0, buf, sizeof(buf), + &rsp_upiu), =3D=3D, GOOD); + g_assert_cmpuint(buf[0], =3D=3D, TYPE_ZBC); + g_assert_cmpuint(buf[3], =3D=3D, vpd_00_len); + g_assert_cmpuint(buf[4], =3D=3D, 0); + + /* Allocation length of the scsi-hd list: 0xb6 does not fit */ + cdb[4] =3D 4 + vpd_00_len - 1; + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, NULL, 0, buf, sizeof(buf), + &rsp_upiu), =3D=3D, GOOD); + g_assert_cmpuint(buf[3], =3D=3D, vpd_00_len); + g_assert_cmpuint(buf[4 + vpd_00_len - 2], !=3D, 0xb6); + g_assert_cmpuint(buf[4 + vpd_00_len - 2], !=3D, 0); + g_assert_cmpuint(buf[4 + vpd_00_len - 1], =3D=3D, 0); + + /* UNMAP is not advertised in the Block Limits and LBP pages */ + cdb[2] =3D 0xb0; + cdb[4] =3D 0x40; + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, NULL, 0, buf, sizeof(buf), + &rsp_upiu), =3D=3D, GOOD); + g_assert_cmpuint(buf[0], =3D=3D, TYPE_ZBC); + g_assert_cmpuint(ldl_be_p(&buf[20]), =3D=3D, 0); + g_assert_cmpuint(ldl_be_p(&buf[24]), =3D=3D, 0); + cdb[2] =3D 0xb2; + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, NULL, 0, buf, sizeof(buf), + &rsp_upiu), =3D=3D, GOOD); + /* LBPU, LBPWS and LBPWS10 */ + g_assert_cmpuint(buf[5] & 0xe0, =3D=3D, 0); + + /* + * An INQUIRY without a PRDT fails like on a regular LU, also for the = page + * that ufs-lu answers itself + */ + g_assert_cmpuint(ufs_send_scsi_command(ufs, ZONED_TEST_LUN, inquiry_cd= b, + NULL, 0, NULL, 0, &rsp_upiu), = =3D=3D, + UFS_OCS_MISMATCH_DATA_BUF_SIZE); + g_assert_cmpuint(ufs_send_scsi_command(ufs, ZONED_TEST_LUN, + inquiry_vpd_b6_cdb, NULL, 0, NU= LL, + 0, &rsp_upiu), =3D=3D, + UFS_OCS_MISMATCH_DATA_BUF_SIZE); + + /* No logical block provisioning: the guest gets no UNMAP */ + g_assert_cmpuint(ufs_zufs_send(ufs, read_capacity_cdb, NULL, 0, buf, + sizeof(buf), &rsp_upiu), =3D=3D, GOOD); + g_assert_cmpuint(buf[14] & 0x80, =3D=3D, 0); + + /* MODE SENSE takes the scsi-hd disk path: caching page, WCE and DPOFU= A */ + g_assert_cmpuint(ufs_zufs_send(ufs, mode_sense_caching_cdb, NULL, 0, b= uf, + 0xff, &rsp_upiu), =3D=3D, GOOD); + g_assert_cmpuint(buf[3] & 0x10, =3D=3D, 0x10); + g_assert_cmpuint(buf[8] & 0x3f, =3D=3D, MODE_PAGE_CACHING); + g_assert_cmpuint(buf[10] & 0x04, =3D=3D, 0x04); + + /* Report zones */ + ufs_zufs_build_report_zones(report_zones_cdb, 0x00, sizeof(buf)); + g_assert_cmpuint(ufs_zufs_send(ufs, report_zones_cdb, NULL, 0, buf, + sizeof(buf), &rsp_upiu), =3D=3D, GOOD); + zone_list_len =3D ldl_be_p(&buf[0]); + g_assert_cmpuint(zone_list_len, =3D=3D, REPORT_ZONES_DESC_SIZE * NUM_Z= ONES); + g_assert_cmpuint(ldq_be_p(&buf[8]), =3D=3D, TEST_IMAGE_SIZE / 4096 - 1= ); + /* First zone: sequential write required, empty */ + g_assert_cmpuint(buf[64] & 0xf, =3D=3D, 0x2); + g_assert_cmpuint(buf[65] >> 4, =3D=3D, 0x1); + g_assert_cmpuint(ldq_be_p(&buf[64 + 8]), =3D=3D, ZONE_BLOCKS); + + ufs_exit(ufs, alloc); +} + +static void ufstest_zufs_write(void *obj, void *data, QGuestAllocator *all= oc) +{ + QUfs *ufs =3D obj; + uint8_t read_buf[4096] =3D { 0 }; + uint8_t write_buf[4096]; + uint8_t cdb[UFS_CDB_SIZE]; + UtpUpiuRsp rsp_upiu; + int i; + + ufs_init(ufs, alloc); + ufs_zufs_clear_unit_attention(ufs); + memset(write_buf, 0xab, sizeof(write_buf)); + + /* Write at the write pointer of zone 0 */ + ufs_zufs_build_write16(cdb, 0, 1); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, write_buf, sizeof(write_buf), + NULL, 0, &rsp_upiu), =3D=3D, GOOD); + + /* Unaligned write error */ + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, write_buf, sizeof(write_buf), + NULL, 0, &rsp_upiu), =3D=3D, CHECK_COND= ITION); + g_assert_cmpint(rsp_upiu.sr.sense_data[12], =3D=3D, 0x21); + g_assert_cmpint(rsp_upiu.sr.sense_data[13], =3D=3D, 0x04); + + /* Linux flushes zoned disks with SYNCHRONIZE CACHE(16) */ + memset(cdb, 0, sizeof(cdb)); + cdb[0] =3D SYNCHRONIZE_CACHE_16; + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, NULL, 0, NULL, 0, &rsp_upiu),= =3D=3D, + GOOD); + + /* Zone 0 is implicitly open */ + ufs_zufs_build_report_zones(cdb, 0x02, sizeof(read_buf)); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, NULL, 0, read_buf, + sizeof(read_buf), &rsp_upiu), =3D=3D, G= OOD); + g_assert_cmpuint(ldl_be_p(&read_buf[0]), =3D=3D, REPORT_ZONES_DESC_SIZ= E); + g_assert_cmpuint(ldq_be_p(&read_buf[64 + 24]), =3D=3D, 1); + + /* Resetting an open zone empties it */ + ufs_zufs_build_zbc_out(cdb, UFS_ZBC_RESET_WRITE_POINTER, 0, false); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, NULL, 0, NULL, 0, &rsp_upiu),= =3D=3D, + GOOD); + ufs_zufs_build_write16(cdb, 0, 1); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, write_buf, sizeof(write_buf), + NULL, 0, &rsp_upiu), =3D=3D, GOOD); + + /* + * Writing to one zone more than MAX_OPEN_ZONE closes an implicitly op= en + * zone instead of failing. + */ + for (i =3D 1; i <=3D MAX_OPEN_ZONE; i++) { + ufs_zufs_build_write16(cdb, (uint64_t)i * ZONE_BLOCKS, 1); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, write_buf, sizeof(write_b= uf), + NULL, 0, &rsp_upiu), =3D=3D, GOOD); + } + ufs_zufs_build_report_zones(cdb, 0x04, sizeof(read_buf)); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, NULL, 0, read_buf, + sizeof(read_buf), &rsp_upiu), =3D=3D, G= OOD); + g_assert_cmpuint(ldl_be_p(&read_buf[0]), =3D=3D, REPORT_ZONES_DESC_SIZ= E); + + /* Finishing an empty zone makes it full */ + ufs_zufs_build_zbc_out(cdb, UFS_ZBC_FINISH_ZONE, + (uint64_t)(NUM_ZONES - 1) * ZONE_BLOCKS, false); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, NULL, 0, NULL, 0, &rsp_upiu),= =3D=3D, + GOOD); + ufs_zufs_build_report_zones(cdb, 0x05, sizeof(read_buf)); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, NULL, 0, read_buf, + sizeof(read_buf), &rsp_upiu), =3D=3D, G= OOD); + g_assert_cmpuint(ldl_be_p(&read_buf[0]), =3D=3D, REPORT_ZONES_DESC_SIZ= E); + + /* Write and zone management beyond the last LBA: LBA OUT OF RANGE */ + ufs_zufs_build_write16(cdb, TEST_IMAGE_SIZE / 4096, 1); + ufs_zufs_send(ufs, cdb, write_buf, sizeof(write_buf), NULL, 0, &rsp_up= iu); + ufs_zufs_assert_sense(&rsp_upiu, ILLEGAL_REQUEST, 0x21, 0x00); + ufs_zufs_build_zbc_out(cdb, UFS_ZBC_FINISH_ZONE, TEST_IMAGE_SIZE / 409= 6, + false); + ufs_zufs_send(ufs, cdb, NULL, 0, NULL, 0, &rsp_upiu); + ufs_zufs_assert_sense(&rsp_upiu, ILLEGAL_REQUEST, 0x21, 0x00); + ufs_zufs_build_zbc_out(cdb, UFS_ZBC_RESET_WRITE_POINTER, + TEST_IMAGE_SIZE / 4096, false); + ufs_zufs_send(ufs, cdb, NULL, 0, NULL, 0, &rsp_upiu); + ufs_zufs_assert_sense(&rsp_upiu, ILLEGAL_REQUEST, 0x21, 0x00); + ufs_zufs_build_report_zones(cdb, 0x00, sizeof(read_buf)); + stq_be_p(&cdb[2], TEST_IMAGE_SIZE / 4096); + ufs_zufs_send(ufs, cdb, NULL, 0, read_buf, sizeof(read_buf), &rsp_upiu= ); + ufs_zufs_assert_sense(&rsp_upiu, ILLEGAL_REQUEST, 0x21, 0x00); + + /* Unknown reporting option and zone operation: INVALID FIELD IN CDB */ + ufs_zufs_build_report_zones(cdb, 0x08, sizeof(read_buf)); + ufs_zufs_send(ufs, cdb, NULL, 0, read_buf, sizeof(read_buf), &rsp_upiu= ); + ufs_zufs_assert_sense(&rsp_upiu, ILLEGAL_REQUEST, 0x24, 0x00); + ufs_zufs_build_zbc_out(cdb, 0x05, 0, false); + ufs_zufs_send(ufs, cdb, NULL, 0, NULL, 0, &rsp_upiu); + ufs_zufs_assert_sense(&rsp_upiu, ILLEGAL_REQUEST, 0x24, 0x00); + + /* An allocation length above the data buffer fails like a short PRDT = */ + ufs_zufs_build_report_zones(cdb, 0x00, 1024 * 1024); + g_assert_cmpuint(ufs_send_scsi_command(ufs, ZONED_TEST_LUN, cdb, NULL,= 0, + read_buf, 256, &rsp_upiu), =3D= =3D, + UFS_OCS_MISMATCH_DATA_BUF_SIZE); + + /* Reset all zones */ + ufs_zufs_build_zbc_out(cdb, UFS_ZBC_RESET_WRITE_POINTER, 0, true); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, NULL, 0, NULL, 0, &rsp_upiu),= =3D=3D, + GOOD); + ufs_zufs_build_report_zones(cdb, 0x01, sizeof(read_buf)); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, NULL, 0, read_buf, + sizeof(read_buf), &rsp_upiu), =3D=3D, G= OOD); + g_assert_cmpuint(ldl_be_p(&read_buf[0]), =3D=3D, + REPORT_ZONES_DESC_SIZE * NUM_ZONES); + + ufs_exit(ufs, alloc); +} + +static void ufstest_zufs_write_cmds(void *obj, void *data, + QGuestAllocator *alloc) +{ + QUfs *ufs =3D obj; + uint8_t read_buf[4096]; + uint8_t write_buf[4096]; + uint8_t cdb[UFS_CDB_SIZE]; + UtpUpiuRsp rsp_upiu; + const uint64_t zone1 =3D ZONE_BLOCKS, zone2 =3D 2 * ZONE_BLOCKS, + zone3 =3D 3 * ZONE_BLOCKS; + + ufs_init(ufs, alloc); + ufs_zufs_clear_unit_attention(ufs); + memset(write_buf, 0xcd, sizeof(write_buf)); + + /* WRITE(10) and WRITE(6) move the write pointer like WRITE(16) */ + memset(cdb, 0, sizeof(cdb)); + cdb[0] =3D WRITE_10; + stl_be_p(&cdb[2], 0); + stw_be_p(&cdb[7], 1); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, write_buf, sizeof(write_buf), + NULL, 0, &rsp_upiu), =3D=3D, GOOD); + g_assert_cmpuint(ufs_zufs_write_pointer(ufs, 0), =3D=3D, 1); + + memset(cdb, 0, sizeof(cdb)); + cdb[0] =3D WRITE_6; + cdb[1] =3D (zone3 >> 16) & 0x1f; + cdb[2] =3D (zone3 >> 8) & 0xff; + cdb[3] =3D zone3 & 0xff; + cdb[4] =3D 1; + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, write_buf, sizeof(write_buf), + NULL, 0, &rsp_upiu), =3D=3D, GOOD); + g_assert_cmpuint(ufs_zufs_write_pointer(ufs, zone3), =3D=3D, zone3 + 1= ); + + /* WRITE SAME at the write pointer: INVALID COMMAND OPERATION CODE */ + memset(cdb, 0, sizeof(cdb)); + cdb[0] =3D WRITE_SAME_16; + stq_be_p(&cdb[2], 1); + stl_be_p(&cdb[10], 2); + ufs_zufs_send(ufs, cdb, write_buf, sizeof(write_buf), NULL, 0, &rsp_up= iu); + ufs_zufs_assert_sense(&rsp_upiu, ILLEGAL_REQUEST, 0x20, 0x00); + memset(cdb, 0, sizeof(cdb)); + cdb[0] =3D WRITE_SAME_10; + stl_be_p(&cdb[2], 1); + stw_be_p(&cdb[7], 2); + ufs_zufs_send(ufs, cdb, write_buf, sizeof(write_buf), NULL, 0, &rsp_up= iu); + ufs_zufs_assert_sense(&rsp_upiu, ILLEGAL_REQUEST, 0x20, 0x00); + g_assert_cmpuint(ufs_zufs_write_pointer(ufs, 0), =3D=3D, 1); + + /* A write crossing the end of its zone: WRITE BOUNDARY VIOLATION */ + ufs_zufs_build_write16(cdb, zone1, ZONE_BLOCKS + 1); + ufs_zufs_send(ufs, cdb, write_buf, sizeof(write_buf), NULL, 0, &rsp_up= iu); + ufs_zufs_assert_sense(&rsp_upiu, ILLEGAL_REQUEST, 0x21, 0x05); + g_assert_cmpuint(ufs_zufs_write_pointer(ufs, zone1), =3D=3D, zone1); + + /* A write to a full zone: INVALID FIELD IN CDB */ + ufs_zufs_build_zbc_out(cdb, UFS_ZBC_FINISH_ZONE, zone2, false); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, NULL, 0, NULL, 0, &rsp_upiu),= =3D=3D, + GOOD); + ufs_zufs_build_write16(cdb, zone2, 1); + ufs_zufs_send(ufs, cdb, write_buf, sizeof(write_buf), NULL, 0, &rsp_up= iu); + ufs_zufs_assert_sense(&rsp_upiu, ILLEGAL_REQUEST, 0x24, 0x00); + + /* A WRITE(10) at an LBA above 2^31 is checked like a WRITE(16) */ + memset(cdb, 0, sizeof(cdb)); + cdb[0] =3D REQUEST_SENSE; + g_assert_cmpuint(ufs_send_scsi_command(ufs, BIG_ZONED_TEST_LUN, cdb, N= ULL, + 0, read_buf, sizeof(read_buf), + &rsp_upiu), =3D=3D, UFS_OCS_SUC= CESS); + cdb[0] =3D WRITE_10; + stl_be_p(&cdb[2], 0x80000001); + stw_be_p(&cdb[7], 1); + g_assert_cmpuint(ufs_send_scsi_command(ufs, BIG_ZONED_TEST_LUN, cdb, + write_buf, sizeof(write_buf), N= ULL, + 0, &rsp_upiu), =3D=3D, UFS_OCS_= SUCCESS); + ufs_zufs_assert_sense(&rsp_upiu, ILLEGAL_REQUEST, 0x21, 0x04); + /* ... and one at the zone start moves that zone's write pointer */ + stl_be_p(&cdb[2], 0x80000000); + g_assert_cmpuint(ufs_send_scsi_command(ufs, BIG_ZONED_TEST_LUN, cdb, + write_buf, sizeof(write_buf), N= ULL, + 0, &rsp_upiu), =3D=3D, UFS_OCS_= SUCCESS); + g_assert_cmpuint(rsp_upiu.header.scsi_status, =3D=3D, GOOD); + ufs_zufs_build_report_zones(cdb, 0x00, sizeof(read_buf)); + stq_be_p(&cdb[2], 0x80000000); + g_assert_cmpuint(ufs_send_scsi_command(ufs, BIG_ZONED_TEST_LUN, cdb, N= ULL, + 0, read_buf, sizeof(read_buf), + &rsp_upiu), =3D=3D, UFS_OCS_SUC= CESS); + g_assert_cmpuint(rsp_upiu.header.scsi_status, =3D=3D, GOOD); + g_assert_cmpuint(ldq_be_p(&read_buf[64 + 16]), =3D=3D, 0x80000000); + g_assert_cmpuint(ldq_be_p(&read_buf[64 + 24]), =3D=3D, 0x80000001); + + ufs_exit(ufs, alloc); +} + +static void ufstest_zufs_open_zones(void *obj, void *data, + QGuestAllocator *alloc) +{ + QUfs *ufs =3D obj; + uint8_t write_buf[4096]; + uint8_t cdb[UFS_CDB_SIZE]; + UtpUpiuRsp rsp_upiu; + int i; + + ufs_init(ufs, alloc); + ufs_zufs_clear_unit_attention(ufs); + memset(write_buf, 0xef, sizeof(write_buf)); + + /* Open MAX_OPEN_ZONE zones explicitly */ + for (i =3D 0; i < MAX_OPEN_ZONE; i++) { + ufs_zufs_build_zbc_out(cdb, UFS_ZBC_OPEN_ZONE, + (uint64_t)i * ZONE_BLOCKS, false); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, NULL, 0, NULL, 0, &rsp_up= iu), + =3D=3D, GOOD); + } + g_assert_cmpuint(ufs_zufs_count_zones(ufs, 0x03), =3D=3D, MAX_OPEN_ZON= E); + + /* No implicitly open zone to close: INSUFFICIENT ZONE RESOURCES */ + ufs_zufs_build_write16(cdb, (uint64_t)MAX_OPEN_ZONE * ZONE_BLOCKS, 1); + ufs_zufs_send(ufs, cdb, write_buf, sizeof(write_buf), NULL, 0, &rsp_up= iu); + ufs_zufs_assert_sense(&rsp_upiu, DATA_PROTECT, 0x55, 0x0e); + ufs_zufs_build_zbc_out(cdb, UFS_ZBC_OPEN_ZONE, + (uint64_t)MAX_OPEN_ZONE * ZONE_BLOCKS, false); + ufs_zufs_send(ufs, cdb, NULL, 0, NULL, 0, &rsp_upiu); + ufs_zufs_assert_sense(&rsp_upiu, DATA_PROTECT, 0x55, 0x0e); + + /* Closing an open zone with nothing written empties it */ + ufs_zufs_build_zbc_out(cdb, UFS_ZBC_CLOSE_ZONE, 0, false); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, NULL, 0, NULL, 0, &rsp_upiu),= =3D=3D, + GOOD); + g_assert_cmpuint(ufs_zufs_count_zones(ufs, 0x01), =3D=3D, NUM_ZONES - = 5); + + /* + * Fill the freed resource with an implicitly open zone, then write to + * one more zone: the implicitly open zone is closed. + */ + ufs_zufs_build_write16(cdb, (uint64_t)MAX_OPEN_ZONE * ZONE_BLOCKS, 1); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, write_buf, sizeof(write_buf), + NULL, 0, &rsp_upiu), =3D=3D, GOOD); + ufs_zufs_build_write16(cdb, (uint64_t)(MAX_OPEN_ZONE + 1) * ZONE_BLOCK= S, + 1); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, write_buf, sizeof(write_buf), + NULL, 0, &rsp_upiu), =3D=3D, GOOD); + g_assert_cmpuint(ufs_zufs_count_zones(ufs, 0x04), =3D=3D, 1); + g_assert_cmpuint(ufs_zufs_count_zones(ufs, 0x02), =3D=3D, 1); + + /* OPEN ALL: 5 explicit + 1 closed fit, the implicit one is closed */ + ufs_zufs_build_zbc_out(cdb, UFS_ZBC_OPEN_ZONE, 0, true); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, NULL, 0, NULL, 0, &rsp_upiu),= =3D=3D, + GOOD); + g_assert_cmpuint(ufs_zufs_count_zones(ufs, 0x03), =3D=3D, MAX_OPEN_ZON= E); + g_assert_cmpuint(ufs_zufs_count_zones(ufs, 0x04), =3D=3D, 1); + g_assert_cmpuint(ufs_zufs_count_zones(ufs, 0x02), =3D=3D, 0); + + /* OPEN ALL again: 6 explicit + 1 closed do not fit, nothing changes */ + ufs_zufs_send(ufs, cdb, NULL, 0, NULL, 0, &rsp_upiu); + ufs_zufs_assert_sense(&rsp_upiu, DATA_PROTECT, 0x55, 0x0e); + g_assert_cmpuint(ufs_zufs_count_zones(ufs, 0x04), =3D=3D, 1); + + /* FINISH ALL fills open and closed zones, empty ones stay empty */ + ufs_zufs_build_zbc_out(cdb, UFS_ZBC_FINISH_ZONE, 0, true); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, NULL, 0, NULL, 0, &rsp_upiu),= =3D=3D, + GOOD); + g_assert_cmpuint(ufs_zufs_count_zones(ufs, 0x05), =3D=3D, MAX_OPEN_ZON= E + 1); + g_assert_cmpuint(ufs_zufs_count_zones(ufs, 0x01), =3D=3D, + NUM_ZONES - MAX_OPEN_ZONE - 1); + + /* RESET ALL */ + ufs_zufs_build_zbc_out(cdb, UFS_ZBC_RESET_WRITE_POINTER, 0, true); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, NULL, 0, NULL, 0, &rsp_upiu),= =3D=3D, + GOOD); + g_assert_cmpuint(ufs_zufs_count_zones(ufs, 0x01), =3D=3D, NUM_ZONES); + + ufs_exit(ufs, alloc); +} + +static void ufstest_zufs_unit_attention(void *obj, void *data, + QGuestAllocator *alloc) +{ + QUfs *ufs =3D obj; + uint8_t write_buf[4096]; + uint8_t cdb[UFS_CDB_SIZE]; + UtpUpiuRsp rsp_upiu; + + ufs_init(ufs, alloc); + memset(write_buf, 0x5a, sizeof(write_buf)); + + /* + * After the reset in ufs_init(), the first command that scsi-hd does = not + * exempt gets UNIT ATTENTION and is not performed, zone commands too: + * the zone stays empty and the write pointer stays put, so that the + * guest can retry. + */ + ufs_zufs_build_zbc_out(cdb, UFS_ZBC_FINISH_ZONE, 0, false); + ufs_zufs_send(ufs, cdb, NULL, 0, NULL, 0, &rsp_upiu); + g_assert_cmpuint(rsp_upiu.header.scsi_status, =3D=3D, CHECK_CONDITION); + g_assert_cmpuint(rsp_upiu.sr.sense_data[2] & 0xf, =3D=3D, UNIT_ATTENTI= ON); + ufs_zufs_build_write16(cdb, 0, 1); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, write_buf, sizeof(write_buf), + NULL, 0, &rsp_upiu), =3D=3D, GOOD); + g_assert_cmpuint(ufs_zufs_write_pointer(ufs, 0), =3D=3D, 1); + + ufs_exit(ufs, alloc); +} + +static void ufstest_zufs_io_error(void *obj, void *data, + QGuestAllocator *alloc) +{ + QUfs *ufs =3D obj; + const uint8_t request_sense_cdb[UFS_CDB_SIZE] =3D { + REQUEST_SENSE, + }; + uint8_t buf[4096]; + uint8_t write_buf[4096]; + uint8_t cdb[UFS_CDB_SIZE]; + UtpUpiuRsp rsp_upiu; + enum UtpOcsCodes ocs; + + ufs_init(ufs, alloc); + memset(write_buf, 0xa5, sizeof(write_buf)); + ocs =3D ufs_send_scsi_command(ufs, EIO_ZONED_TEST_LUN, request_sense_c= db, + NULL, 0, buf, sizeof(buf), &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_SUCCESS); + + /* scsi-hd fails the first write at LBA 0: the write pointer moves bac= k */ + ufs_zufs_build_write16(cdb, 0, 1); + ocs =3D ufs_send_scsi_command(ufs, EIO_ZONED_TEST_LUN, cdb, write_buf, + sizeof(write_buf), NULL, 0, &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_SUCCESS); + g_assert_cmpuint(rsp_upiu.header.scsi_status, =3D=3D, CHECK_CONDITION); + + ufs_zufs_build_report_zones(cdb, 0x00, sizeof(buf)); + ocs =3D ufs_send_scsi_command(ufs, EIO_ZONED_TEST_LUN, cdb, NULL, 0, b= uf, + sizeof(buf), &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_SUCCESS); + g_assert_cmpuint(buf[64 + 1] >> 4, =3D=3D, 0x1); + g_assert_cmpuint(ldq_be_p(&buf[64 + 24]), =3D=3D, 0); + + /* The retry succeeds */ + ufs_zufs_build_write16(cdb, 0, 1); + ocs =3D ufs_send_scsi_command(ufs, EIO_ZONED_TEST_LUN, cdb, write_buf, + sizeof(write_buf), NULL, 0, &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_SUCCESS); + g_assert_cmpuint(rsp_upiu.header.scsi_status, =3D=3D, GOOD); + + ufs_zufs_build_report_zones(cdb, 0x00, sizeof(buf)); + ocs =3D ufs_send_scsi_command(ufs, EIO_ZONED_TEST_LUN, cdb, NULL, 0, b= uf, + sizeof(buf), &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_SUCCESS); + g_assert_cmpuint(ldq_be_p(&buf[64 + 24]), =3D=3D, 1); + + ufs_exit(ufs, alloc); +} + +/* Submit a SCSI command that writes data_in through MCQ, without waiting = */ +static int ufs_zufs_send_write_async(QUfs *ufs, uint8_t lun, + const uint8_t *cdb, + const uint8_t *data_in, + size_t data_in_len) +{ + QTestState *qts =3D ufs->dev.bus->qts; + int cmd_desc_slot =3D alloc_cmd_desc_slot(ufs); + uint64_t req_upiu_addr =3D + ufs->cmd_desc_addr + cmd_desc_slot * UTP_COMMAND_DESCRIPTOR_SIZE; + UfshcdSgEntry entry =3D { 0 }; + UtpUpiuReq req_upiu =3D { 0 }; + UtpTransferReqDesc utrd; + + g_assert_true(ufs->support_mcq); + g_assert_cmpuint(data_in_len, <=3D, PRD_ENTRY_DATA_SIZE); + qtest_memwrite(qts, ufs->data_buffer_addr, data_in, data_in_len); + entry.addr =3D cpu_to_le64(ufs->data_buffer_addr); + entry.size =3D cpu_to_le32(data_in_len - 1); + qtest_memwrite(qts, req_upiu_addr + UTP_PRDT_UPIU_OFFSET, &entry, + sizeof(entry)); + + req_upiu.header.trans_type =3D UFS_UPIU_TRANSACTION_COMMAND; + req_upiu.header.flags =3D UFS_UPIU_CMD_FLAGS_WRITE; + req_upiu.header.lun =3D lun; + req_upiu.header.task_tag =3D cmd_desc_slot; + req_upiu.sc.exp_data_transfer_len =3D cpu_to_be32(data_in_len); + memcpy(req_upiu.sc.cdb, cdb, UFS_CDB_SIZE); + qtest_memwrite(qts, req_upiu_addr, &req_upiu, sizeof(req_upiu)); + + utrd =3D ufs_build_req_utrd(req_upiu_addr, UFS_UTP_HOST_TO_DEVICE, 1); + __ufs_send_transfer_request_mcq_async(ufs, lun, &utrd); + return cmd_desc_slot; +} + +/* Wait for the request of ufs_zufs_send_write_async(); returns its status= */ +static uint8_t ufs_zufs_wait_async(QUfs *ufs, int cmd_desc_slot, + UtpUpiuRsp *rsp_upiu) +{ + uint64_t rsp_upiu_addr =3D ufs->cmd_desc_addr + + cmd_desc_slot * UTP_COMMAND_DESCRIPTOR_SIZE + + UTP_RESPONSE_UPIU_OFFSET; + uint64_t end_time; + UfsCqEntry cqe; + int n_cqe; + + end_time =3D g_get_monotonic_time() + TIMEOUT_SECONDS * G_TIME_SPAN_SE= COND; + do { + qtest_clock_step(ufs->dev.bus->qts, 100); + n_cqe =3D ufs_mcq_poll_cq(ufs, &cqe, 1); + } while (!n_cqe && g_get_monotonic_time() < end_time); + g_assert_cmpint(n_cqe, =3D=3D, 1); + g_assert_cmpuint(cqe.status, =3D=3D, UFS_OCS_SUCCESS); + ufs_wreg(ufs, A_IS, FIELD_DP32(0, IS, CQES, 1)); + + qtest_memread(ufs->dev.bus->qts, rsp_upiu_addr, rsp_upiu, + sizeof(*rsp_upiu)); + g_assert_cmpuint(rsp_upiu->header.task_tag, =3D=3D, cmd_desc_slot); + release_cmd_desc_slot(ufs, cmd_desc_slot); + return rsp_upiu->header.scsi_status; +} + +static void ufstest_zufs_write_in_flight(void *obj, void *data, + QGuestAllocator *alloc) +{ + QUfs *ufs =3D obj; + QTestState *qts =3D ufs->dev.bus->qts; + const uint8_t request_sense_cdb[UFS_CDB_SIZE] =3D { + REQUEST_SENSE, + }; + const uint64_t zone1 =3D ZONE_BLOCKS, zone2 =3D 2 * ZONE_BLOCKS, + zone3 =3D 3 * ZONE_BLOCKS; + uint8_t buf[4096]; + uint8_t write_buf[4096]; + uint8_t fill_buf[8 * 4096]; + uint8_t cdb[UFS_CDB_SIZE]; + UtpUpiuRsp rsp_upiu; + enum UtpOcsCodes ocs; + uint64_t wp, lba; + uint32_t blocks; + int slot, slot2; + + ufs_init(ufs, alloc); + g_assert_true(ufs->support_mcq); + memset(write_buf, 0x5a, sizeof(write_buf)); + memset(fill_buf, 0x3c, sizeof(fill_buf)); + ocs =3D ufs_send_scsi_command(ufs, EIO_ZONED_TEST_LUN, request_sense_c= db, + NULL, 0, buf, sizeof(buf), &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_SUCCESS); + + /* + * A write to zone 1 takes the only open resource and closes zone 0, + * which a write in flight opened. That write fails and still moves the + * write pointer back. + */ + qtest_qemu_io(qts, EIO_ZONED_TEST_DRIVE, "break write_aio A"); + ufs_zufs_build_write16(cdb, 0, 1); + slot =3D ufs_zufs_send_write_async(ufs, EIO_ZONED_TEST_LUN, cdb, write= _buf, + sizeof(write_buf)); + g_assert_cmpuint(ufs_zufs_zone(ufs, EIO_ZONED_TEST_LUN, 0, &wp), =3D= =3D, 0x2); + g_assert_cmpuint(wp, =3D=3D, 1); + + /* A write that scsi-hd rejects leaves this one undoable */ + ufs_zufs_build_write16(cdb, 1, 1); + cdb[1] =3D 0x20; + ocs =3D ufs_send_scsi_command(ufs, EIO_ZONED_TEST_LUN, cdb, write_buf, + sizeof(write_buf), NULL, 0, &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_SUCCESS); + ufs_zufs_assert_sense(&rsp_upiu, ILLEGAL_REQUEST, 0x24, 0x00); + + ufs_zufs_build_write16(cdb, zone1, 1); + ocs =3D ufs_send_scsi_command(ufs, EIO_ZONED_TEST_LUN, cdb, write_buf, + sizeof(write_buf), NULL, 0, &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_SUCCESS); + g_assert_cmpuint(rsp_upiu.header.scsi_status, =3D=3D, GOOD); + g_assert_cmpuint(ufs_zufs_zone(ufs, EIO_ZONED_TEST_LUN, 0, &wp), =3D= =3D, 0x4); + g_assert_cmpuint(wp, =3D=3D, 1); + + qtest_qemu_io(qts, EIO_ZONED_TEST_DRIVE, "resume A"); + g_assert_cmpuint(ufs_zufs_wait_async(ufs, slot, &rsp_upiu), =3D=3D, + CHECK_CONDITION); + g_assert_cmpuint(ufs_zufs_zone(ufs, EIO_ZONED_TEST_LUN, 0, &wp), =3D= =3D, 0x1); + g_assert_cmpuint(wp, =3D=3D, 0); + + ufs_zufs_build_write16(cdb, 0, 1); + ocs =3D ufs_send_scsi_command(ufs, EIO_ZONED_TEST_LUN, cdb, write_buf, + sizeof(write_buf), NULL, 0, &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_SUCCESS); + g_assert_cmpuint(rsp_upiu.header.scsi_status, =3D=3D, GOOD); + + /* + * FINISH ZONE completes while a write that filled zone 2 is in flight. + * When the write fails, the zone stays full. + */ + for (lba =3D zone2; lba < zone2 + ZONE_BLOCKS - 1; lba +=3D blocks) { + blocks =3D MIN(8, zone2 + ZONE_BLOCKS - 1 - lba); + ufs_zufs_build_write16(cdb, lba, blocks); + ocs =3D ufs_send_scsi_command(ufs, EIO_ZONED_TEST_LUN, cdb, fill_b= uf, + blocks * 4096, NULL, 0, &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_SUCCESS); + g_assert_cmpuint(rsp_upiu.header.scsi_status, =3D=3D, GOOD); + } + g_assert_cmpuint(ufs_zufs_zone(ufs, EIO_ZONED_TEST_LUN, zone2, &wp), + =3D=3D, 0x2); + g_assert_cmpuint(wp, =3D=3D, zone2 + ZONE_BLOCKS - 1); + + qtest_qemu_io(qts, EIO_ZONED_TEST_DRIVE, "break write_aio B"); + ufs_zufs_build_write16(cdb, zone2 + ZONE_BLOCKS - 1, 1); + slot =3D ufs_zufs_send_write_async(ufs, EIO_ZONED_TEST_LUN, cdb, write= _buf, + sizeof(write_buf)); + g_assert_cmpuint(ufs_zufs_zone(ufs, EIO_ZONED_TEST_LUN, zone2, &wp), + =3D=3D, 0xe); + + ufs_zufs_build_zbc_out(cdb, UFS_ZBC_FINISH_ZONE, zone2, false); + ocs =3D ufs_send_scsi_command(ufs, EIO_ZONED_TEST_LUN, cdb, NULL, 0, N= ULL, 0, + &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_SUCCESS); + g_assert_cmpuint(rsp_upiu.header.scsi_status, =3D=3D, GOOD); + + qtest_qemu_io(qts, EIO_ZONED_TEST_DRIVE, "resume B"); + g_assert_cmpuint(ufs_zufs_wait_async(ufs, slot, &rsp_upiu), =3D=3D, + CHECK_CONDITION); + g_assert_cmpuint(ufs_zufs_zone(ufs, EIO_ZONED_TEST_LUN, zone2, &wp), + =3D=3D, 0xe); + + /* + * A write to zone 3 fails after a later write to the zone started: the + * write pointer is not moved back. + */ + qtest_qemu_io(qts, EIO_ZONED_TEST_DRIVE, "break write_aio C"); + ufs_zufs_build_write16(cdb, zone3, 1); + slot =3D ufs_zufs_send_write_async(ufs, EIO_ZONED_TEST_LUN, cdb, write= _buf, + sizeof(write_buf)); + qtest_qemu_io(qts, EIO_ZONED_TEST_DRIVE, "break write_aio D"); + ufs_zufs_build_write16(cdb, zone3 + 1, 1); + slot2 =3D ufs_zufs_send_write_async(ufs, EIO_ZONED_TEST_LUN, cdb, writ= e_buf, + sizeof(write_buf)); + qtest_qemu_io(qts, EIO_ZONED_TEST_DRIVE, "resume C"); + g_assert_cmpuint(ufs_zufs_wait_async(ufs, slot, &rsp_upiu), =3D=3D, + CHECK_CONDITION); + g_assert_cmpuint(ufs_zufs_zone(ufs, EIO_ZONED_TEST_LUN, zone3, &wp), + =3D=3D, 0x2); + g_assert_cmpuint(wp, =3D=3D, zone3 + 2); + qtest_qemu_io(qts, EIO_ZONED_TEST_DRIVE, "resume D"); + g_assert_cmpuint(ufs_zufs_wait_async(ufs, slot2, &rsp_upiu), =3D=3D, G= OOD); + g_assert_cmpuint(ufs_zufs_zone(ufs, EIO_ZONED_TEST_LUN, zone3, &wp), + =3D=3D, 0x2); + g_assert_cmpuint(wp, =3D=3D, zone3 + 2); + + ufs_exit(ufs, alloc); +} + +static void ufstest_zufs_report_zones(void *obj, void *data, + QGuestAllocator *alloc) +{ + QUfs *ufs =3D obj; + const uint8_t request_sense_cdb[UFS_CDB_SIZE] =3D { + REQUEST_SENSE, + }; + uint8_t buf[4096]; + uint8_t write_buf[4096]; + uint8_t write_buf2[2 * 4096] =3D { 0 }; + uint8_t cdb[UFS_CDB_SIZE]; + UtpUpiuRsp rsp_upiu; + uint64_t last_start; + enum UtpOcsCodes ocs; + + ufs_init(ufs, alloc); + ufs_zufs_clear_unit_attention(ufs); + memset(write_buf, 0x3c, sizeof(write_buf)); + + /* A start LBA inside a zone reports from that zone on */ + ufs_zufs_build_report_zones(cdb, 0x00, sizeof(buf)); + stq_be_p(&cdb[2], 2 * ZONE_BLOCKS + 5); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, NULL, 0, buf, sizeof(buf), + &rsp_upiu), =3D=3D, GOOD); + g_assert_cmpuint(ldl_be_p(&buf[0]), =3D=3D, + (NUM_ZONES - 2) * REPORT_ZONES_DESC_SIZE); + g_assert_cmpuint(ldq_be_p(&buf[64 + 16]), =3D=3D, 2 * ZONE_BLOCKS); + + /* PARTIAL: the zone list length counts only the returned zones */ + ufs_zufs_build_report_zones(cdb, 0x80, 64 + 3 * REPORT_ZONES_DESC_SIZE= ); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, NULL, 0, buf, sizeof(buf), + &rsp_upiu), =3D=3D, GOOD); + g_assert_cmpuint(ldl_be_p(&buf[0]), =3D=3D, 3 * REPORT_ZONES_DESC_SIZE= ); + ufs_zufs_build_report_zones(cdb, 0x00, 64 + 3 * REPORT_ZONES_DESC_SIZE= ); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, NULL, 0, buf, sizeof(buf), + &rsp_upiu), =3D=3D, GOOD); + g_assert_cmpuint(ldl_be_p(&buf[0]), =3D=3D, NUM_ZONES * REPORT_ZONES_D= ESC_SIZE); + + /* All zones except gap zones; no read-only or conventional zones */ + g_assert_cmpuint(ufs_zufs_count_zones(ufs, 0x3e), =3D=3D, NUM_ZONES); + g_assert_cmpuint(ufs_zufs_count_zones(ufs, 0x06), =3D=3D, 0); + g_assert_cmpuint(ufs_zufs_count_zones(ufs, 0x3f), =3D=3D, 0); + + /* A capacity that is not a multiple of the zone size: a smaller zone = */ + ocs =3D ufs_send_scsi_command(ufs, ODD_ZONED_TEST_LUN, request_sense_c= db, + NULL, 0, buf, sizeof(buf), &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_SUCCESS); + last_start =3D (uint64_t)(ODD_NUM_ZONES - 1) * (ODD_ZONE_SIZE / 4096); + ufs_zufs_build_report_zones(cdb, 0x00, sizeof(buf)); + stq_be_p(&cdb[2], last_start); + ocs =3D ufs_send_scsi_command(ufs, ODD_ZONED_TEST_LUN, cdb, NULL, 0, b= uf, + sizeof(buf), &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_SUCCESS); + g_assert_cmpuint(rsp_upiu.header.scsi_status, =3D=3D, GOOD); + g_assert_cmpuint(ldl_be_p(&buf[0]), =3D=3D, REPORT_ZONES_DESC_SIZE); + g_assert_cmpuint(ldq_be_p(&buf[64 + 8]), =3D=3D, + TEST_IMAGE_SIZE / 4096 - last_start); + + /* A write crossing the end of that zone and of the logical unit */ + ufs_zufs_build_write16(cdb, last_start, 1); + ocs =3D ufs_send_scsi_command(ufs, ODD_ZONED_TEST_LUN, cdb, write_buf, + sizeof(write_buf), NULL, 0, &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_SUCCESS); + g_assert_cmpuint(rsp_upiu.header.scsi_status, =3D=3D, GOOD); + ufs_zufs_build_write16(cdb, TEST_IMAGE_SIZE / 4096 - 1, 2); + ocs =3D ufs_send_scsi_command(ufs, ODD_ZONED_TEST_LUN, cdb, write_buf2, + sizeof(write_buf2), NULL, 0, &rsp_upiu); + g_assert_cmpuint(ocs, =3D=3D, UFS_OCS_SUCCESS); + ufs_zufs_assert_sense(&rsp_upiu, ILLEGAL_REQUEST, 0x21, 0x00); + + ufs_exit(ufs, alloc); +} + +static void ufstest_zufs_more_cmds(void *obj, void *data, + QGuestAllocator *alloc) +{ + QUfs *ufs =3D obj; + uint8_t write_buf[4096]; + uint8_t fill_buf[8 * 4096]; + uint8_t cdb[UFS_CDB_SIZE]; + UtpUpiuRsp rsp_upiu; + const uint64_t zone4 =3D 4 * ZONE_BLOCKS, zone5 =3D 5 * ZONE_BLOCKS, + zone6 =3D 6 * ZONE_BLOCKS, zone7 =3D 7 * ZONE_BLOCKS; + uint64_t lba; + uint32_t blocks; + int i; + + ufs_init(ufs, alloc); + ufs_zufs_clear_unit_attention(ufs); + memset(write_buf, 0x96, sizeof(write_buf)); + memset(fill_buf, 0x69, sizeof(fill_buf)); + + /* WRITE(12) and WRITE AND VERIFY(16) move the write pointer */ + memset(cdb, 0, sizeof(cdb)); + cdb[0] =3D WRITE_12; + stl_be_p(&cdb[2], zone4); + stl_be_p(&cdb[6], 1); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, write_buf, sizeof(write_buf), + NULL, 0, &rsp_upiu), =3D=3D, GOOD); + memset(cdb, 0, sizeof(cdb)); + cdb[0] =3D WRITE_VERIFY_16; + stq_be_p(&cdb[2], zone4 + 1); + stl_be_p(&cdb[10], 1); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, write_buf, sizeof(write_buf), + NULL, 0, &rsp_upiu), =3D=3D, GOOD); + g_assert_cmpuint(ufs_zufs_write_pointer(ufs, zone4), =3D=3D, zone4 + 2= ); + + /* CLOSE ALL closes it, FINISH ZONE fills the closed zone */ + ufs_zufs_build_zbc_out(cdb, UFS_ZBC_CLOSE_ZONE, 0, true); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, NULL, 0, NULL, 0, &rsp_upiu),= =3D=3D, + GOOD); + g_assert_cmpuint(ufs_zufs_count_zones(ufs, 0x04), =3D=3D, 1); + ufs_zufs_build_zbc_out(cdb, UFS_ZBC_FINISH_ZONE, zone4, false); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, NULL, 0, NULL, 0, &rsp_upiu),= =3D=3D, + GOOD); + g_assert_cmpuint(ufs_zufs_count_zones(ufs, 0x05), =3D=3D, 1); + + /* A zone operation on a range of zones is not supported */ + ufs_zufs_build_zbc_out(cdb, UFS_ZBC_FINISH_ZONE, zone5, false); + stw_be_p(&cdb[12], 2); + ufs_zufs_send(ufs, cdb, NULL, 0, NULL, 0, &rsp_upiu); + ufs_zufs_assert_sense(&rsp_upiu, ILLEGAL_REQUEST, 0x24, 0x00); + + /* With the ALL bit, ZONE COUNT does not matter */ + ufs_zufs_build_zbc_out(cdb, UFS_ZBC_CLOSE_ZONE, 0, true); + stw_be_p(&cdb[12], 2); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, NULL, 0, NULL, 0, &rsp_upiu),= =3D=3D, + GOOD); + + /* A zone ID that is not the start of a zone */ + ufs_zufs_build_zbc_out(cdb, UFS_ZBC_RESET_WRITE_POINTER, zone5 + 1, fa= lse); + ufs_zufs_send(ufs, cdb, NULL, 0, NULL, 0, &rsp_upiu); + ufs_zufs_assert_sense(&rsp_upiu, ILLEGAL_REQUEST, 0x24, 0x00); + + /* + * A write without a PRDT, or with one shorter than the transfer, fails + * and leaves the write pointer alone + */ + ufs_zufs_build_write16(cdb, zone5, 1); + g_assert_cmpuint(ufs_send_scsi_command(ufs, ZONED_TEST_LUN, cdb, NULL,= 0, + NULL, 0, &rsp_upiu), =3D=3D, + UFS_OCS_MISMATCH_DATA_BUF_SIZE); + ufs_zufs_build_write16(cdb, zone5, 2); + g_assert_cmpuint(ufs_send_scsi_command(ufs, ZONED_TEST_LUN, cdb, write= _buf, + sizeof(write_buf), NULL, 0, + &rsp_upiu), =3D=3D, + UFS_OCS_MISMATCH_DATA_BUF_SIZE); + g_assert_cmpuint(ufs_zufs_write_pointer(ufs, zone5), =3D=3D, zone5); + g_assert_cmpuint(ufs_zufs_count_zones(ufs, 0x02), =3D=3D, 0); + + /* UNMAP would bypass the write pointers */ + memset(cdb, 0, sizeof(cdb)); + cdb[0] =3D UNMAP; + stw_be_p(&cdb[7], 24); + memset(write_buf, 0, 24); + stw_be_p(&write_buf[0], 22); + stw_be_p(&write_buf[2], 16); + stq_be_p(&write_buf[8], zone4); + stl_be_p(&write_buf[16], 1); + ufs_zufs_send(ufs, cdb, write_buf, 24, NULL, 0, &rsp_upiu); + ufs_zufs_assert_sense(&rsp_upiu, ILLEGAL_REQUEST, 0x20, 0x00); + + /* + * A write whose PRDT is shorter than the write changes no zone: a clo= sed + * zone stays closed, an explicitly open one stays open. + */ + ufs_zufs_build_write16(cdb, zone6, 1); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, write_buf, sizeof(write_buf), + NULL, 0, &rsp_upiu), =3D=3D, GOOD); + ufs_zufs_build_zbc_out(cdb, UFS_ZBC_CLOSE_ZONE, zone6, false); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, NULL, 0, NULL, 0, &rsp_upiu),= =3D=3D, + GOOD); + ufs_zufs_build_write16(cdb, zone6 + 1, 2); + g_assert_cmpuint(ufs_send_scsi_command(ufs, ZONED_TEST_LUN, cdb, write= _buf, + sizeof(write_buf), NULL, 0, + &rsp_upiu), =3D=3D, + UFS_OCS_MISMATCH_DATA_BUF_SIZE); + g_assert_cmpuint(ufs_zufs_zone_cond(ufs, zone6), =3D=3D, 0x4); + g_assert_cmpuint(ufs_zufs_write_pointer(ufs, zone6), =3D=3D, zone6 + 1= ); + g_assert_cmpuint(ufs_zufs_count_zones(ufs, 0x02), =3D=3D, 0); + + ufs_zufs_build_zbc_out(cdb, UFS_ZBC_OPEN_ZONE, zone7, false); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, NULL, 0, NULL, 0, &rsp_upiu),= =3D=3D, + GOOD); + ufs_zufs_build_write16(cdb, zone7, ZONE_BLOCKS); + g_assert_cmpuint(ufs_send_scsi_command(ufs, ZONED_TEST_LUN, cdb, write= _buf, + sizeof(write_buf), NULL, 0, + &rsp_upiu), =3D=3D, + UFS_OCS_MISMATCH_DATA_BUF_SIZE); + g_assert_cmpuint(ufs_zufs_zone_cond(ufs, zone7), =3D=3D, 0x3); + g_assert_cmpuint(ufs_zufs_write_pointer(ufs, zone7), =3D=3D, zone7); + + /* + * Nor does a write that scsi-hd rejects, here for its WRPROTECT field: + * the closed zone stays closed, the explicitly open one that the write + * would fill stays open, and no implicitly open zone is closed to make + * room for the zone of the write. + */ + ufs_zufs_build_write16(cdb, zone6 + 1, 1); + cdb[1] =3D 0x20; + ufs_zufs_send(ufs, cdb, write_buf, sizeof(write_buf), NULL, 0, &rsp_up= iu); + ufs_zufs_assert_sense(&rsp_upiu, ILLEGAL_REQUEST, 0x24, 0x00); + /* A read that follows does not start the rejected write either */ + memset(cdb, 0, sizeof(cdb)); + cdb[0] =3D READ_16; + stq_be_p(&cdb[2], zone6); + stl_be_p(&cdb[10], 1); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, NULL, 0, fill_buf, 4096, + &rsp_upiu), =3D=3D, GOOD); + g_assert_cmpuint(ufs_zufs_zone_cond(ufs, zone6), =3D=3D, 0x4); + g_assert_cmpuint(ufs_zufs_write_pointer(ufs, zone6), =3D=3D, zone6 + 1= ); + g_assert_cmpuint(ufs_zufs_count_zones(ufs, 0x02), =3D=3D, 0); + + for (lba =3D zone7; lba < zone7 + ZONE_BLOCKS - 1; lba +=3D blocks) { + blocks =3D MIN(8, zone7 + ZONE_BLOCKS - 1 - lba); + ufs_zufs_build_write16(cdb, lba, blocks); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, fill_buf, blocks * 4096, = NULL, + 0, &rsp_upiu), =3D=3D, GOOD); + } + ufs_zufs_build_write16(cdb, zone7 + ZONE_BLOCKS - 1, 1); + cdb[1] =3D 0x20; + ufs_zufs_send(ufs, cdb, write_buf, sizeof(write_buf), NULL, 0, &rsp_up= iu); + ufs_zufs_assert_sense(&rsp_upiu, ILLEGAL_REQUEST, 0x24, 0x00); + g_assert_cmpuint(ufs_zufs_zone_cond(ufs, zone7), =3D=3D, 0x3); + g_assert_cmpuint(ufs_zufs_write_pointer(ufs, zone7), =3D=3D, + zone7 + ZONE_BLOCKS - 1); + + for (i =3D 0; i < MAX_OPEN_ZONE - 1; i++) { + ufs_zufs_build_write16(cdb, (8 + i) * ZONE_BLOCKS, 1); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, write_buf, sizeof(write_b= uf), + NULL, 0, &rsp_upiu), =3D=3D, GOOD); + } + ufs_zufs_build_write16(cdb, (8 + i) * ZONE_BLOCKS, 1); + cdb[1] =3D 0x20; + ufs_zufs_send(ufs, cdb, write_buf, sizeof(write_buf), NULL, 0, &rsp_up= iu); + ufs_zufs_assert_sense(&rsp_upiu, ILLEGAL_REQUEST, 0x24, 0x00); + g_assert_cmpuint(ufs_zufs_count_zones(ufs, 0x02), =3D=3D, MAX_OPEN_ZON= E - 1); + g_assert_cmpuint(ufs_zufs_zone_cond(ufs, (8 + i) * ZONE_BLOCKS), =3D= =3D, 0x1); + + ufs_exit(ufs, alloc); +} + +static void *ufs_zufs_test_setup(GString *cmd_line, void *arg) +{ + char *tmp_path =3D drive_create(); + char *odd_path =3D drive_create(); + char *eio_path =3D drive_create(); + + g_string_append_printf(cmd_line, + " -blockdev file,filename=3D%s,node-name=3Ddrv2= " + "-device ufs-lu,bus=3Dufs0,drive=3Ddrv2,lun=3D%= d," + "zoned=3Don,zoned.max_open=3D%d,zoned.zone_size= =3D%d " + "-blockdev file,filename=3D%s,node-name=3Ddrv3 " + "-device ufs-lu,bus=3Dufs0,drive=3Ddrv3,lun=3D%= d," + "zoned=3Don,zoned.zone_size=3D%d " + "-drive if=3Dnone,id=3D%s,driver=3Draw," + "file.driver=3Dblkdebug,file.image.filename=3D%= s," + "file.inject-error.0.event=3Dnone," + "file.inject-error.0.iotype=3Dwrite," + "file.inject-error.0.errno=3D5," + "file.inject-error.0.sector=3D0," + "file.inject-error.0.once=3Don," + "file.inject-error.1.event=3Dnone," + "file.inject-error.1.iotype=3Dwrite," + "file.inject-error.1.errno=3D5," + "file.inject-error.1.sector=3D%d," + "file.inject-error.1.once=3Don," + "file.inject-error.2.event=3Dnone," + "file.inject-error.2.iotype=3Dwrite," + "file.inject-error.2.errno=3D5," + "file.inject-error.2.sector=3D%d," + "file.inject-error.2.once=3Don " + "-device ufs-lu,bus=3Dufs0,drive=3D%s,lun=3D%d," + "zoned=3Don,zoned.zone_size=3D%d,zoned.max_open= =3D1 " + "-blockdev null-co,node-name=3Ddrv5,read-zeroes= =3Don," + "size=3D%" PRIu64 " " + "-device ufs-lu,bus=3Dufs0,drive=3Ddrv5,lun=3D%= d," + "zoned=3Don ", + tmp_path, ZONED_TEST_LUN, MAX_OPEN_ZONE, ZONE_S= IZE, + odd_path, ODD_ZONED_TEST_LUN, ODD_ZONE_SIZE, + EIO_ZONED_TEST_DRIVE, eio_path, EIO_ZONE2_SECTO= R, + EIO_ZONE3_SECTOR, + EIO_ZONED_TEST_DRIVE, EIO_ZONED_TEST_LUN, ZONE_= SIZE, + (uint64_t)BIG_ZONED_TEST_SIZE, BIG_ZONED_TEST_L= UN); + + return arg; +} + static void ufs_register_nodes(void) { const char *arch; @@ -1921,6 +3033,12 @@ static void ufs_register_nodes(void) "mcq=3Dfalse,nutrs=3D32,nutmr= s=3D8," "wb-max-size=3D1024," "wb-min-size=3D256" }; + QOSGraphTestOptions zufs_test_opts =3D { .before =3D ufs_zufs_test_set= up, + .edge.extra_device_opts =3D + "mcq=3Dfalse,nutrs=3D32,nut= mrs=3D8" }; + QOSGraphTestOptions zufs_mcq_test_opts =3D { .before =3D ufs_zufs_test= _setup, + .edge.extra_device_opts =3D + "mcq=3Dtrue,mcq-maxq=3D= 1" }; QOSGraphTestOptions acpi_eject_test_opts =3D { .subprocess =3D true }; =20 add_qpci_address(&edge_opts, &(QPCIAddress){ .devfn =3D QPCI_DEVFN(4, = 0) }); @@ -1962,6 +3080,24 @@ static void ufs_register_nodes(void) qos_add_test("wb-init", "ufs", ufstest_wb_init, &wb_test_opts); qos_add_test("wb-read-write", "ufs", ufstest_wb_read_write, &wb_test_o= pts); qos_add_test("hid", "ufs", ufstest_hid, &io_test_opts); + qos_add_test("zufs-init", "ufs", ufstest_zufs_init, &zufs_test_opts); + qos_add_test("zufs-write", "ufs", ufstest_zufs_write, &zufs_test_opts); + qos_add_test("zufs-mcq-write", "ufs", ufstest_zufs_write, + &zufs_mcq_test_opts); + qos_add_test("zufs-write-cmds", "ufs", ufstest_zufs_write_cmds, + &zufs_test_opts); + qos_add_test("zufs-open-zones", "ufs", ufstest_zufs_open_zones, + &zufs_test_opts); + qos_add_test("zufs-unit-attention", "ufs", ufstest_zufs_unit_attention, + &zufs_test_opts); + qos_add_test("zufs-io-error", "ufs", ufstest_zufs_io_error, + &zufs_test_opts); + qos_add_test("zufs-write-in-flight", "ufs", ufstest_zufs_write_in_flig= ht, + &zufs_mcq_test_opts); + qos_add_test("zufs-report-zones", "ufs", ufstest_zufs_report_zones, + &zufs_test_opts); + qos_add_test("zufs-more-cmds", "ufs", ufstest_zufs_more_cmds, + &zufs_test_opts); } =20 libqos_init(ufs_register_nodes); --=20 2.43.0