From nobody Fri Oct 23 14:30:59 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=1792142770; cv=none; d=zohomail.com; s=zohoarc; b=Xyvya/5SIsGs3NR+DXj/y/KohGYj42LX/7xuFnKcQmNruUONCLy8hYHAsOpZeDyo2/IZqirhYb8tgE1q4QnQTh/nZwh7HJJhf6PTbwmAExxeKtmQdtEnZALe7UyLBYkhAaLxKRsiic6HG+Ezha7OZiQ7+4mtU+BIeIKkFSTbd28= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1792142770; 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=rNKDRC7J/oEq5whf0iU0ZDInkelcmeYhdHSXAt/Jhag=; b=BVvvJbTCK2i+x7sga9dwCccnCPDpAcrf0ZwAPYV4rA7DLRnxodvBllK0j/2a7yAWsnnzM2WMz9MDOYLgS/AN4fi4q1RpFBSyZLSpdBpr8n6YnvywB99RpW2isC10GfHoZd+ssglbpMg5/BrLKpwxLQ0PUxM+gMQxJN2b7W7hIp0= 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 1792142769962903.98828178418; Fri, 16 Oct 2026 02:26:09 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1xHeCC-0006Ba-94; Fri, 16 Oct 2026 05:25:50 -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 1xHeC6-000682-7b for qemu-devel@nongnu.org; Fri, 16 Oct 2026 05:25:43 -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 1xHeC3-0004pb-KP for qemu-devel@nongnu.org; Fri, 16 Oct 2026 05:25:41 -0400 Received: from epcas2p2.samsung.com (unknown [182.195.41.54]) by mailout2.samsung.com (KnoxPortal) with ESMTP id 20261016092534epoutp02ca7ed7a1a1974a187d3489fb71814eae~e_FJnk4Wk1336013360epoutp02H for ; Fri, 16 Oct 2026 09:25:34 +0000 (GMT) Received: from epsnrtp02.localdomain (unknown [182.195.42.154]) by epcas2p2.samsung.com (KnoxPortal) with ESMTPS id 20261016092534epcas2p2d4fb088400a59981610f121042021c11~e_FJVYhxR2464624646epcas2p2Z; Fri, 16 Oct 2026 09:25:34 +0000 (GMT) Received: from epcas2p4.samsung.com (unknown [182.195.38.201]) by epsnrtp02.localdomain (Postfix) with ESMTP id 4j5fgK6rzlz2SSKd; Fri, 16 Oct 2026 09:25:33 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout2.samsung.com 20261016092534epoutp02ca7ed7a1a1974a187d3489fb71814eae~e_FJnk4Wk1336013360epoutp02H DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1792142734; bh=rNKDRC7J/oEq5whf0iU0ZDInkelcmeYhdHSXAt/Jhag=; h=Subject:Reply-To:From:To:CC:In-Reply-To:Date:References:From; b=sbU9ozjhzQHBROeBWLA5olMN6H8zhmZhq12tDTbcf4Ts/vrEuEYih3l/LmDMa4ql4 HH471T/FLm5ejQKBrBi31+F1E3NwbOT6cr3V0ZJ2bilR9WlqYHQPpBkkwCNF6p5Y9O 0eNkJSdu0Vn/FCDgE67X148NR6LVJ4Dvplmv1iM4= Mime-Version: 1.0 Subject: [PATCH v2 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" , "qemu-stable@nongnu.org" , Daejun Park X-Priority: 3 X-Content-Kind-Code: NORMAL In-Reply-To: <20261016092423epcms2p83f80a8dd8f7c1ddc3a94541e0771c306@epcms2p8> 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: <20261016092533epcms2p5d94645ba46656ca173090548261fc17b@epcms2p5> Date: Fri, 16 Oct 2026 18:25:33 +0900 X-CMS-MailID: 20261016092533epcms2p5d94645ba46656ca173090548261fc17b 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: 20261016092423epcms2p83f80a8dd8f7c1ddc3a94541e0771c306 References: <20261016092423epcms2p83f80a8dd8f7c1ddc3a94541e0771c306@epcms2p8> 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: -29 X-Spam_score: -3.0 X-Spam_bar: --- X-Spam_report: (-3.0 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.24, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_LOW=-0.7, RCVD_IN_MSPIKE_H4=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: 1792142771490158500 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 INVALID COMMAND TABLE ATTRIBUTES before it reaches scsi-hd. 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 --- hw/ufs/lu.c | 24 ++++++++++++++++++++---- tests/qtest/ufs-test.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/hw/ufs/lu.c b/hw/ufs/lu.c index 5905c49fac..1853284acf 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, as virtio-scsi does when the + * buffer does not match the command. A request that reports a pending + * unit attention instead 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) { + scsi_req_unref(scsi_req); + return UFS_REQUEST_FAIL; + } + 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/tests/qtest/ufs-test.c b/tests/qtest/ufs-test.c index 9a9c5ca834..63f805419e 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_INVALID_CMD_TABLE_ATTR); + + 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_INVALID_CMD_TABLE_ATTR); + + /* 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 14:30:59 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=1792142855; cv=none; d=zohomail.com; s=zohoarc; b=nO/HJDpEl+d5LAt7FP0rXuO/qYa6Qhom1VQHpNwanucEL8eqRucFKitlpGu9R8jTgXcyKr7hOXffJGhXGtLn+9gD+spH17omzq5zuN/2s13ZURo2+QaKJkNDPDQTYcfohgsNOdDfXyS62voPjfGnVyIGByRNILwP+62NRacoTHs= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1792142855; 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=KHAs9mQ56a6x4fJ6V3V8u5xtPz/AKCTs3XHUdV+JN7M=; b=WoKtOK6KM0SN7AjMfqUp1c8IVx/4HnW4JEgZ+6fpOFIfIHpgkBt8DFSHGMrD46VaRvHtuCVNGvH3EMc1Pm4+qtm5XGotuz0+vZYIgQHko3lpapN0k9xav0JRBcigKz9UlsIHy7KjXdqvFmDFegfFnVs68VofJ8zZcDjBgyrhfaw= 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 1792142855613304.9336718283315; Fri, 16 Oct 2026 02:27:35 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1xHeDe-0007iM-0Z; Fri, 16 Oct 2026 05:27:18 -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 1xHeDc-0007hj-C9 for qemu-devel@nongnu.org; Fri, 16 Oct 2026 05:27:16 -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 1xHeDa-0004uj-1V for qemu-devel@nongnu.org; Fri, 16 Oct 2026 05:27:16 -0400 Received: from epcas2p1.samsung.com (unknown [182.195.41.53]) by mailout2.samsung.com (KnoxPortal) with ESMTP id 20261016092711epoutp0247cf1ede1a7187f720515eef6509f19f~e_GjeJpr71366213662epoutp020 for ; Fri, 16 Oct 2026 09:27:11 +0000 (GMT) Received: from epsnrtp03.localdomain (unknown [182.195.42.155]) by epcas2p3.samsung.com (KnoxPortal) with ESMTPS id 20261016092710epcas2p392b81a918fd2ffd47e1ee9cbd9d60bbb~e_Gi5KdXd1657416574epcas2p3i; Fri, 16 Oct 2026 09:27:10 +0000 (GMT) Received: from epcas2p4.samsung.com (unknown [182.195.38.201]) by epsnrtp03.localdomain (Postfix) with ESMTP id 4j5fjB0Qbnz3hhT4; Fri, 16 Oct 2026 09:27:10 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout2.samsung.com 20261016092711epoutp0247cf1ede1a7187f720515eef6509f19f~e_GjeJpr71366213662epoutp020 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1792142831; bh=KHAs9mQ56a6x4fJ6V3V8u5xtPz/AKCTs3XHUdV+JN7M=; h=Subject:Reply-To:From:To:CC:In-Reply-To:Date:References:From; b=Fm5LgqiG3oox+LWB6yceoZ9akcZgntuEECCtKJXQmgVhjjN4YrdYP+xQ7D534ro+u 7cxefpwpreHN/Pg0Uvx+QXZ/hYkrMKs0fXbCLfPO2L9/+qWAKDfj3GUp/8pBumzeCj mWykWcPfevCD2q6DeAv8YSvQAzvO9bXaUT5fQO9A= Mime-Version: 1.0 Subject: [PATCH v2 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" , Daejun Park X-Priority: 3 X-Content-Kind-Code: NORMAL In-Reply-To: <20261016092423epcms2p83f80a8dd8f7c1ddc3a94541e0771c306@epcms2p8> 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: <20261016092709epcms2p63a2477a684b07157efc8cc8daaa2d5cb@epcms2p6> Date: Fri, 16 Oct 2026 18:27:09 +0900 X-CMS-MailID: 20261016092709epcms2p63a2477a684b07157efc8cc8daaa2d5cb 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: 20261016092423epcms2p83f80a8dd8f7c1ddc3a94541e0771c306 References: <20261016092423epcms2p83f80a8dd8f7c1ddc3a94541e0771c306@epcms2p8> 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: -29 X-Spam_score: -3.0 X-Spam_bar: --- X-Spam_report: (-3.0 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.24, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_LOW=-0.7, 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: 1792142857075158500 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 --- 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 1853284acf..b812e1bef8 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 63f805419e..594b91706b 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 14:30:59 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=1792143110; cv=none; d=zohomail.com; s=zohoarc; b=BiddLBE7ufXr3f7ECPLf+kRwcy40QGt0csI/LpWQONNwUoekEZVQwi9hzSS/AL39g86fn7gQNrZ1LOr5zWBZ3fxa7UsrosTmrLAKGdpYbF8Kwi+cVyWk7DpSocTdXrBTwZ9GpT5NDie/YDlPpK/Awq1AojXw6OoAUPRr1Bb7W54= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1792143110; 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=qzs23J8Ek+xWmCIsXyDoe6aI3DHypACfRA2VUFrB5ug=; b=NiZxMgTzm0GYKKlhTfpP88WlMdEg6YHkx103ey1G0TPJzVfh/WDHCJrKzqNN6m7ZMsC93QCgFtJQ25TmtD01RLZa/nMvr8Q7+xbpsZxcwg+Ef5rDaUOlp6ga8AOKIncjpme6PxAaORrx9sOQ3jLrGzLf4nP5mJuErEhg5oZSaq4= 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 1792143110323983.5416636002354; Fri, 16 Oct 2026 02:31:50 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1xHeHb-0000zU-5T; Fri, 16 Oct 2026 05:31:23 -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 1xHeHX-0000z6-79 for qemu-devel@nongnu.org; Fri, 16 Oct 2026 05:31:19 -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 1xHeHT-0005Oe-2H for qemu-devel@nongnu.org; Fri, 16 Oct 2026 05:31:18 -0400 Received: from epcas2p1.samsung.com (unknown [182.195.41.53]) by mailout3.samsung.com (KnoxPortal) with ESMTP id 20261016093111epoutp03545d075fa79dbe03ce5cc75991d4d3c9~e_KDIW7Ay0398403984epoutp03X for ; Fri, 16 Oct 2026 09:31:11 +0000 (GMT) Received: from epsnrtp04.localdomain (unknown [182.195.42.156]) by epcas2p2.samsung.com (KnoxPortal) with ESMTPS id 20261016093110epcas2p239fd52014abae3fe8c8f23e0865ca43b~e_KC1phr13025730257epcas2p2j; Fri, 16 Oct 2026 09:31:10 +0000 (GMT) Received: from epcas2p2.samsung.com (unknown [182.195.38.206]) by epsnrtp04.localdomain (Postfix) with ESMTP id 4j5fnp3hJlz6B9m6; Fri, 16 Oct 2026 09:31:10 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout3.samsung.com 20261016093111epoutp03545d075fa79dbe03ce5cc75991d4d3c9~e_KDIW7Ay0398403984epoutp03X DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1792143071; bh=qzs23J8Ek+xWmCIsXyDoe6aI3DHypACfRA2VUFrB5ug=; h=Subject:Reply-To:From:To:CC:In-Reply-To:Date:References:From; b=GBjIpvUbMdlhneCqm3TULsAtUDxDKa1hmB6loDL4H+otoRIedyiG+mUXycOCs2vOx R+uukZpx/LQo37UDjgw5423lkBjHNebJNgtZpxRPyXEpHO4yUIj8f2aJ1W7eX9D8Mq GjAqm8L5skSioEoW+HoG7DK+2CVKurCm34PcPsEg= Mime-Version: 1.0 Subject: [PATCH v2 3/6] hw/ufs: Limit data transfers to 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" , Daejun Park X-Priority: 3 X-Content-Kind-Code: NORMAL In-Reply-To: <20261016092423epcms2p83f80a8dd8f7c1ddc3a94541e0771c306@epcms2p8> 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: <20261016093109epcms2p4a1f77d99f9f4fd50fa786eeb79fe1cf5@epcms2p4> Date: Fri, 16 Oct 2026 18:31:09 +0900 X-CMS-MailID: 20261016093109epcms2p4a1f77d99f9f4fd50fa786eeb79fe1cf5 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: 20261016092423epcms2p83f80a8dd8f7c1ddc3a94541e0771c306 References: <20261016092423epcms2p83f80a8dd8f7c1ddc3a94541e0771c306@epcms2p8> 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: -45 X-Spam_score: -4.6 X-Spam_bar: ---- X-Spam_report: (-4.6 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.24, 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: 1792143112319158500 scsi-hd moves as many bytes as the SG list of a request holds, and ufs-lu builds that list from the whole PRDT. When the PRDT is larger than the transfer length in the CDB, a WRITE writes more blocks than the CDB names and a READ reads more. A one-block WRITE(10) at LBA 0 with an 8 KiB PRDT also wrote LBA 1, and its response reported no residual. A UFS device transfers what the CDB asks for and reports the rest of the Expected Data Transfer Length as a residual. The commands that scsi-hd emulates had their residual computed against the PRDT too: an INQUIRY with an allocation length of 36, a 4 KiB PRDT and an Expected Data Transfer Length of 4096 reported an overflow of 4294959176 bytes. It now reports an underflow of 4060. Cut the SG list to the transfer length of the parsed command before the request reaches scsi-hd. The new long-prdt qtest failed before this change. Fixes: 2a8b36a4967d ("hw/ufs: Support for UFS logical unit") Signed-off-by: Daejun Park --- hw/ufs/lu.c | 28 ++++++++++++++++++++ tests/qtest/ufs-test.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++= ++++ 2 files changed, 99 insertions(+) diff --git a/hw/ufs/lu.c b/hw/ufs/lu.c index b812e1bef8..bebe4eaac2 100644 --- a/hw/ufs/lu.c +++ b/hw/ufs/lu.c @@ -379,6 +379,24 @@ static UfsReqResult ufs_emulate_scsi_cmd(UfsLu *lu, Uf= sRequest *req) return UFS_REQUEST_SUCCESS; } =20 +/* Keep only the first len bytes of the SG list built from the PRDT */ +static void ufs_limit_sg_list(UfsRequest *req, uint64_t len) +{ + QEMUSGList *prdt_sg =3D req->sg; + QEMUSGList *sg =3D g_new0(QEMUSGList, 1); + int i; + + 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; +} + static UfsReqResult ufs_process_scsi_cmd(UfsLu *lu, UfsRequest *req) { uint8_t task_tag =3D req->req_upiu.header.task_tag; @@ -425,6 +443,16 @@ static UfsReqResult ufs_process_scsi_cmd(UfsLu *lu, Uf= sRequest *req) return UFS_REQUEST_FAIL; } =20 + /* + * scsi-hd moves as many bytes as the SG list holds, while a UFS device + * transfers what the CDB asks for. Cut a longer SG list to the transf= er + * length; the response reports the rest of the Expected Data Transfer + * Length as a residual. + */ + if (req->sg && req->sg->size > scsi_req->cmd.xfer) { + ufs_limit_sg_list(req, scsi_req->cmd.xfer); + } + req->sreq =3D scsi_req; =20 len =3D scsi_req_enqueue(scsi_req); diff --git a/tests/qtest/ufs-test.c b/tests/qtest/ufs-test.c index 594b91706b..4e0c2aee5f 100644 --- a/tests/qtest/ufs-test.c +++ b/tests/qtest/ufs-test.c @@ -859,6 +859,76 @@ static void ufstest_sync_cache(void *obj, void *data, = QGuestAllocator *alloc) ufs_exit(ufs, alloc); } =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_mcq_cq_wraparound(void *obj, void *data, QGuestAllocator *alloc) { @@ -1812,6 +1882,7 @@ static void ufs_register_nodes(void) qos_add_test("mcq-read-write", "ufs", ufstest_read_write, &mcq_test_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("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 14:30:59 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=1792143248; cv=none; d=zohomail.com; s=zohoarc; b=KnvPw2min0HO7dWea/MSolp8erbPH3i/3AsG4rCG1M9PWM3NtnmNv9t90FeW2QFhIV+trXSrivxDQDbHRNBkGY5tsOtWXUXEZcov2tDo+jMDprn/b5fvxzhS/F+N264ujU+LdyqXcBiSoZRMthgK9ew6VOPd2DHexvoIi/mcjnU= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1792143248; 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=ZFuMios3uY9cekfabVxZuoFu5Rl4zW2Vu0UHeqF5ico=; b=WA8nyxxbEHwryAjYpfiNB8r9kK0I8SLMFL7VMgqlMEjYCV0/LdSJJE09ysIaqhBvnR4j4q7ymh+UkdecFUV7xiiDCDD9E3/wCTmeeBN9441Fi/WKA6LtoeOqNp/bp5R+eYfHj1DSJu6PvW9JL4puQDYjalKrgbub4TiLTqV3Nz8= 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 1792143248370259.75567776249284; Fri, 16 Oct 2026 02:34:08 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1xHeJW-000342-FQ; Fri, 16 Oct 2026 05:33:22 -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 1xHeJ4-0002d6-SW for qemu-devel@nongnu.org; Fri, 16 Oct 2026 05:32:55 -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 1xHeJ2-0005kV-Oc for qemu-devel@nongnu.org; Fri, 16 Oct 2026 05:32:54 -0400 Received: from epcas2p2.samsung.com (unknown [182.195.41.54]) by mailout3.samsung.com (KnoxPortal) with ESMTP id 20261016093249epoutp0343c6bd4b9c1ebc16584c88dd27b42162~e_Leex1xQ0599005990epoutp03I for ; Fri, 16 Oct 2026 09:32:49 +0000 (GMT) Received: from epsnrtp02.localdomain (unknown [182.195.42.154]) by epcas2p3.samsung.com (KnoxPortal) with ESMTPS id 20261016093248epcas2p350711f09d06906241ee18fed1e723791~e_LeBEttZ0725407254epcas2p3o; Fri, 16 Oct 2026 09:32:48 +0000 (GMT) Received: from epcas2p4.samsung.com (unknown [182.195.38.206]) by epsnrtp02.localdomain (Postfix) with ESMTP id 4j5fqh31D2z2SSKb; Fri, 16 Oct 2026 09:32:48 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout3.samsung.com 20261016093249epoutp0343c6bd4b9c1ebc16584c88dd27b42162~e_Leex1xQ0599005990epoutp03I DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1792143169; bh=ZFuMios3uY9cekfabVxZuoFu5Rl4zW2Vu0UHeqF5ico=; h=Subject:Reply-To:From:To:CC:In-Reply-To:Date:References:From; b=Zl9RIT4sDC+PpG54GkWCv7hwC9Md1fr8VtY02McnkW8iTZgzTxKR+w/gA8+hxjoCY 7oKPvvR9Vc3OT0md5jql5R3WXkLC8JZ8CwB9ysxZ9NwK5QL/gEEQZ0Hv3mMXo6Nwpl AD5qhhgQe7RYK0x7fxH7cTv7dqvMHqF6VIEHFnDs= Mime-Version: 1.0 Subject: [PATCH v2 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" , Daejun Park X-Priority: 3 X-Content-Kind-Code: NORMAL In-Reply-To: <20261016092423epcms2p83f80a8dd8f7c1ddc3a94541e0771c306@epcms2p8> 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: <20261016093247epcms2p87539ea375c914865748c0c8e6b6b2a29@epcms2p8> Date: Fri, 16 Oct 2026 18:32:47 +0900 X-CMS-MailID: 20261016093247epcms2p87539ea375c914865748c0c8e6b6b2a29 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: 20261016092423epcms2p83f80a8dd8f7c1ddc3a94541e0771c306 References: <20261016092423epcms2p83f80a8dd8f7c1ddc3a94541e0771c306@epcms2p8> 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: -45 X-Spam_score: -4.6 X-Spam_bar: ---- X-Spam_report: (-4.6 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.24, 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: 1792143250458158500 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 --- 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..f719b2f145 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, WRITE AND VERIFY and WRITE SAME +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 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 14:30:59 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=1792143264; cv=none; d=zohomail.com; s=zohoarc; b=KTx6Kv5T7+i9vUEHjDsvLJ5sYxmdSnfilavwchM/rP4SYLgKZNIcPFrdX74JcIoKCQBUbgjSQbgasEyVBC2EWUbcagY+91FtYmgXdcknjCDVaAoVjGzPXXtq3Tp0khOoOo+D1h6EbVnjS8uDGaSmvcS/aRvd+PADoVWiCE5s/NM= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1792143264; 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=uuKZFG5+SAXuan4THhft6FFoR+I0YHS3AY9QdyHKQe8=; b=f8xkrW8myAFgkxYgmRpwoDRU1difZlhDU10eFIN+6b0fb4tjZ5ve6W+YAGJbYmt9l6kRUWfjdmqsHnkS/eqVvoBcscp93DNl+U0zP3AJTSoZvZtpadG2+HwwGPVZcZbGPJ16AVGqnnZcUPW0tEH8OL83qoZd2+cU03AWcwnXd+4= 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 17921432649861006.0680512866422; Fri, 16 Oct 2026 02:34:24 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1xHeKI-0003aC-GE; Fri, 16 Oct 2026 05:34:13 -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 1xHeK3-0003La-NQ for qemu-devel@nongnu.org; Fri, 16 Oct 2026 05:33:58 -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 1xHeJv-000651-Sh for qemu-devel@nongnu.org; Fri, 16 Oct 2026 05:33:52 -0400 Received: from epcas2p2.samsung.com (unknown [182.195.41.54]) by mailout3.samsung.com (KnoxPortal) with ESMTP id 20261016093345epoutp03ac276d156c3b348504cc017d227b0aab~e_MSf7hWc0405904059epoutp03g for ; Fri, 16 Oct 2026 09:33:45 +0000 (GMT) Received: from epsnrtp02.localdomain (unknown [182.195.42.154]) by epcas2p3.samsung.com (KnoxPortal) with ESMTPS id 20261016093344epcas2p3dbaedc4546d17dbbe3a4144e2ba82cee~e_MRoqZDt1849818498epcas2p39; Fri, 16 Oct 2026 09:33:44 +0000 (GMT) Received: from epcas2p1.samsung.com (unknown [182.195.38.212]) by epsnrtp02.localdomain (Postfix) with ESMTP id 4j5frl62DPz2SSKX; Fri, 16 Oct 2026 09:33:43 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout3.samsung.com 20261016093345epoutp03ac276d156c3b348504cc017d227b0aab~e_MSf7hWc0405904059epoutp03g DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1792143225; bh=uuKZFG5+SAXuan4THhft6FFoR+I0YHS3AY9QdyHKQe8=; h=Subject:Reply-To:From:To:CC:In-Reply-To:Date:References:From; b=jke5mrXY7OG89rbwFEnmjM88HYIfHjLtsasY95vbMhQ5r5B894n3bMhEA4j/TWDcQ oSBVzUYof8SyD3wQco4dtRdpNmAY1+8HjQZa8D6lt71gofC91Tzg7RX+xuBnu0Km3v QIFZdKgvKZvDxU+ObUSEqDwJG378GC+3WO5GgAxE= Mime-Version: 1.0 Subject: [PATCH v2 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" , Daejun Park X-Priority: 3 X-Content-Kind-Code: NORMAL In-Reply-To: <20261016092423epcms2p83f80a8dd8f7c1ddc3a94541e0771c306@epcms2p8> 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: <20261016093343epcms2p8d5801bd57384c44d5120ae0b7c4eb206@epcms2p8> Date: Fri, 16 Oct 2026 18:33:43 +0900 X-CMS-MailID: 20261016093343epcms2p8d5801bd57384c44d5120ae0b7c4eb206 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: 20261016092423epcms2p83f80a8dd8f7c1ddc3a94541e0771c306 References: <20261016092423epcms2p83f80a8dd8f7c1ddc3a94541e0771c306@epcms2p8> 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: -45 X-Spam_score: -4.6 X-Spam_bar: ---- X-Spam_report: (-4.6 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.24, 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: 1792143266852158500 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, WRITE AND VERIFY and WRITE SAME 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 write pointer moves when a write is submitted, 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). It moves back if scsi-hd fails or drops that write, unless the zone changed since, for example because a later write to it was accepted. 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: READ CAPACITY(16) reports no logical block provisioning, and the Block Limits and Logical Block Provisioning VPD pages do not advertise UNMAP. 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. 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 --- hw/ufs/lu.c | 829 ++++++++++++++++++++++++++++++++++++++++++++++++= +++- hw/ufs/trace-events | 6 + hw/ufs/ufs.h | 36 +++ include/block/ufs.h | 27 ++ 4 files changed, 893 insertions(+), 5 deletions(-) diff --git a/hw/ufs/lu.c b/hw/ufs/lu.c index bebe4eaac2..19bab236a2 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,249 @@ 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; + } +} + +/* Close one implicitly open zone; false if there is none */ +static bool ufs_close_implicit_open_zone(UfsLu *lu) +{ + uint32_t i; + + for (i =3D 0; i < lu->zone_info.nr_zones; i++) { + UfsZoneState *zone =3D &lu->zone_array[i]; + + if (zone->cond =3D=3D UFS_ZONE_COND_IMPLICIT_OPEN) { + uint64_t seq =3D zone->seq; + + /* + * Closing moves no write pointer: keep seq, so that a write in + * flight to this zone can still be undone. + */ + ufs_assign_zone_cond(lu, zone, UFS_ZONE_COND_CLOSED); + zone->seq =3D seq; + return true; + } + } + + return false; +} + +/* + * 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; + } +} + +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; + } +} + +/* + * The write pointer moves when a write is accepted, so that queued writes= to + * one zone pass the check in submission order. If scsi-hd then fails or + * drops the write (an aborted or purged request, an I/O error), move the + * write pointer back, unless the zone changed since: a later write was + * accepted, 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. 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 is 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 */ + if (len > 5) { + buf[5] &=3D ~0x80; + } + 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 +416,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 +427,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 +446,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 +455,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 +485,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 +594,453 @@ 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 at most what the guest can take: t= he + * smaller of the allocation length and its data buffer. + */ +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: + case WRITE_SAME_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: + case WRITE_SAME_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, move = the + * write pointer, remembering enough in req to undo that if scsi-hd does n= ot + * complete the write. A write that reaches beyond the capacity or has no + * blocks goes to scsi-hd unchecked: scsi-hd fails the former and a + * zero-length WRITE SAME, and completes other zero-length writes 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 { + req->zone_cond_before =3D zone->cond; + req->zone_wp_before =3D zone->wp; + + if (ufs_open_zone(lu, zone, UFS_ZONE_COND_IMPLICIT_OPEN)) { + zone->wp +=3D len; + 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; + return true; + } + *sense =3D SENSE_CODE(INSUFFICIENT_ZONE_RESOURCES); + } + + 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: { + /* Size the buffer by what can be returned, not by the PRDT alone = */ + uint64_t report_len =3D REPORT_ZONES_HEADER_SIZE + + (uint64_t)REPORT_ZONES_DESC_SIZE * + lu->zone_info.nr_zones; + uint32_t outbuf_len =3D MIN(MIN(ldl_be_p(&cdb[10]), req->data_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; + } + 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; + 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; @@ -405,6 +1124,16 @@ static UfsReqResult ufs_process_scsi_cmd(UfsLu *lu, U= fsRequest *req) SCSIRequest *scsi_req; uint32_t len; =20 + 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 @@ -432,14 +1161,15 @@ static UfsReqResult ufs_process_scsi_cmd(UfsLu *lu, = UfsRequest *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, as virtio-scsi does when the - * buffer does not match the command. A request that reports a pending - * unit attention instead moves no data; its sense is already set, so = let - * it through and the guest sees the UA. + * only handles the INQUIRY data of a zoned LU. Fail the request, as + * virtio-scsi does when the buffer does not match the command. A requ= est + * that reports a pending unit attention instead moves no data; its se= nse + * 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); + ufs_zbc_write_undo(lu, req); return UFS_REQUEST_FAIL; } =20 @@ -466,6 +1196,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) @@ -498,12 +1232,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; @@ -551,6 +1344,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; @@ -567,6 +1364,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; @@ -590,12 +1388,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) @@ -603,6 +1419,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 5e5a54a3fb..1977a9c9ad 100644 --- a/hw/ufs/trace-events +++ b/hw/ufs/trace-events @@ -58,3 +58,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 265a43faaa..4819e2522f 100644 --- a/hw/ufs/ufs.h +++ b/hw/ufs/ufs.h @@ -17,6 +17,25 @@ #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 */ + 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 @@ -64,6 +83,12 @@ typedef struct UfsRequest { QEMUSGList *sg; uint32_t data_len; =20 + /* write accepted by a zoned LU, undone if scsi-hd does not complete i= t */ + UfsZoneState *zone; + UfsZoneCond zone_cond_before; + uint64_t zone_wp_before; + uint64_t zone_seq; + /* for MCQ */ struct UfsSq *sq; struct UfsCqEntry cqe; @@ -78,6 +103,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; @@ -86,6 +117,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 14:30:59 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=1792143357; cv=none; d=zohomail.com; s=zohoarc; b=LHpAyR7o7LYSMylacbIaBZ1KJTpnCZAC8BVyBgYN5UL2nc0Nz1o6awQxqonfDDh1MdSTBg6cKRiJEcFcbE45Jez3PG7IZZP9gTqC6unbbaKUoKeGmCNYGn9iz228aoVGtubMO6whvW01I6Xen6h0EMTWUxXtAcMA2+Pr17MCNs8= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1792143357; 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=OceVVUGjs0RrwDx66uQH2XhSVhUMq1g4mq0H+qkuTqo=; b=ehA5izXVsZbuCcQLUzu49CjTgagL83cl8fR2P/AR/tO8onSKix2rLMh/cgQ+IimqWPQVOYZh75Zbb1PPMqafCQ3ypt7tNvm4sOVjQlzdsEkQkmElknILbj6MMolppl7Di1xQsafTch3+fu9Cp9No7OpEBwMZU8QV+pefBpdFYFo= 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 1792143357974200.84709189048544; Fri, 16 Oct 2026 02:35:57 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1xHeLt-0005Ht-4y; Fri, 16 Oct 2026 05:35:49 -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 1xHeLm-00058t-CW for qemu-devel@nongnu.org; Fri, 16 Oct 2026 05:35:42 -0400 Received: from mailout4.samsung.com ([203.254.224.34]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1xHeLe-0006U9-Vs for qemu-devel@nongnu.org; Fri, 16 Oct 2026 05:35:41 -0400 Received: from epcas2p3.samsung.com (unknown [182.195.41.55]) by mailout4.samsung.com (KnoxPortal) with ESMTP id 20261016093530epoutp04eee02dc2000afd426773c658ed10686d~e_N0LYMIw3039230392epoutp04X for ; Fri, 16 Oct 2026 09:35:30 +0000 (GMT) Received: from epsnrtp03.localdomain (unknown [182.195.42.155]) by epcas2p2.samsung.com (KnoxPortal) with ESMTPS id 20261016093529epcas2p2332b52b0457afcb6428f53c5af609b8a~e_NzmLFI82854328543epcas2p2p; Fri, 16 Oct 2026 09:35:29 +0000 (GMT) Received: from epcas2p1.samsung.com (unknown [182.195.38.205]) by epsnrtp03.localdomain (Postfix) with ESMTP id 4j5ftn056Yz3hhT3; Fri, 16 Oct 2026 09:35:29 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout4.samsung.com 20261016093530epoutp04eee02dc2000afd426773c658ed10686d~e_N0LYMIw3039230392epoutp04X DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1792143330; bh=OceVVUGjs0RrwDx66uQH2XhSVhUMq1g4mq0H+qkuTqo=; h=Subject:Reply-To:From:To:CC:In-Reply-To:Date:References:From; b=HobePQgZ6ScYfiBqDUdNA9MspjWi7r6v24PPGa/accm9EZ1D8h3JrLO29ToN74q9g tFdmClNH+iGzndIFtLaHuf1gX6PwtQ0auJvqjgwFNrBdFIP17QMa1O+nPj0Istaek5 wpJE1oCKUlqbjfo7Utck56I8E7fEhTt4bQioPo+4= Mime-Version: 1.0 Subject: [PATCH v2 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" , Daejun Park X-Priority: 3 X-Content-Kind-Code: NORMAL In-Reply-To: <20261016092423epcms2p83f80a8dd8f7c1ddc3a94541e0771c306@epcms2p8> 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: <20261016093528epcms2p456d7c535220a0c6a2a63e880e30c75ba@epcms2p4> Date: Fri, 16 Oct 2026 18:35:28 +0900 X-CMS-MailID: 20261016093528epcms2p456d7c535220a0c6a2a63e880e30c75ba 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: 20261016092423epcms2p83f80a8dd8f7c1ddc3a94541e0771c306 References: <20261016092423epcms2p83f80a8dd8f7c1ddc3a94541e0771c306@epcms2p8> 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.34; envelope-from=daejun7.park@samsung.com; helo=mailout4.samsung.com X-Spam_score_int: -45 X-Spam_score: -4.6 X-Spam_bar: ---- X-Spam_report: (-4.6 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.24, 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: 1792143358566158500 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 write at LBA 0 and the first write at the start of zone 2 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 UNMAP is advertised nowhere, the caching mode page, REPORT ZONES, and an INQUIRY without a PRDT. 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 a REPORT ZONES allocation length larger than the data buffer. Without the bound on that length, QEMU writes past its own buffer; a debug build without sanitizers aborted with "double free or corruption". zufs-write-cmds checks that WRITE(6), WRITE(10) and WRITE SAME(16) move the write pointer, a zero length WRITE SAME, 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 a write to another zone closed its zone implicitly in the meantime, 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, a write without a PRDT, and UNMAP. Signed-off-by: Daejun Park --- tests/qtest/ufs-test.c | 984 +++++++++++++++++++++++++++++++++++++++++++++= ++++ 1 file changed, 984 insertions(+) diff --git a/tests/qtest/ufs-test.c b/tests/qtest/ufs-test.c index 4e0c2aee5f..0b3e764b19 100644 --- a/tests/qtest/ufs-test.c +++ b/tests/qtest/ufs-test.c @@ -1832,6 +1832,966 @@ static void ufstest_hid(void *obj, void *data, QGue= stAllocator *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 write at LBA 0 and= the + * first write at the start of zone 2 fail with an I/O error, and a breakp= oint + * 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, wh= ich + * never returns while a breakpoint holds a write. + */ +#define EIO_ZONED_TEST_LUN 4 +#define EIO_ZONED_TEST_DRIVE "drv4" +#define EIO_ZONE2_SECTOR (2 * 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; +} + +static uint64_t ufs_zufs_write_pointer(QUfs *ufs, uint64_t zone_start) +{ + uint8_t buf[4096]; + uint8_t cdb[UFS_CDB_SIZE]; + UtpUpiuRsp rsp_upiu; + + ufs_zufs_build_report_zones(cdb, 0x00, sizeof(buf)); + stq_be_p(&cdb[2], zone_start); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, NULL, 0, buf, sizeof(buf), + &rsp_upiu), =3D=3D, GOOD); + g_assert_cmpuint(ldq_be_p(&buf[64 + 16]), =3D=3D, zone_start); + return ldq_be_p(&buf[64 + 24]); +} + +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); + g_assert_cmpuint(buf[5] & 0x80, =3D=3D, 0); + + /* An INQUIRY without a PRDT fails like on a regular LU */ + g_assert_cmpuint(ufs_send_scsi_command(ufs, ZONED_TEST_LUN, inquiry_cd= b, + NULL, 0, NULL, 0, &rsp_upiu), = =3D=3D, + UFS_OCS_INVALID_CMD_TABLE_ATTR); + + /* 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 stays in bounds: QEMU mu= st + * not fill its buffer beyond the data buffer size. + */ + ufs_zufs_build_report_zones(cdb, 0x00, 1024 * 1024); + g_assert_cmpuint(ufs_zufs_send(ufs, cdb, NULL, 0, read_buf, 256, + &rsp_upiu), =3D=3D, GOOD); + g_assert_cmpuint(ldl_be_p(&read_buf[0]), =3D=3D, + REPORT_ZONES_DESC_SIZE * NUM_ZONES); + + /* 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(16) of two blocks at the write pointer */ + memset(cdb, 0, sizeof(cdb)); + cdb[0] =3D WRITE_SAME_16; + stq_be_p(&cdb[2], 1); + stl_be_p(&cdb[10], 2); + 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, 3); + + /* scsi-hd reports WSNZ=3D1 and fails a zero length WRITE SAME */ + stq_be_p(&cdb[2], 3); + stl_be_p(&cdb[10], 0); + 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_write_pointer(ufs, 0), =3D=3D, 3); + + /* 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; +} + +/* Condition and write pointer of the zone at zone_start of the EIO LU */ +static uint8_t ufs_zufs_eio_zone(QUfs *ufs, 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, 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(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 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; + uint8_t buf[4096]; + uint8_t write_buf[4096]; + uint8_t cdb[UFS_CDB_SIZE]; + UtpUpiuRsp rsp_upiu; + enum UtpOcsCodes ocs; + uint64_t wp; + int slot; + + ufs_init(ufs, alloc); + g_assert_true(ufs->support_mcq); + memset(write_buf, 0x5a, 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); + + /* + * 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_eio_zone(ufs, 0, &wp), =3D=3D, 0x2); + g_assert_cmpuint(wp, =3D=3D, 1); + + 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_eio_zone(ufs, 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_eio_zone(ufs, 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. + */ + qtest_qemu_io(qts, EIO_ZONED_TEST_DRIVE, "break write_aio B"); + memset(cdb, 0, sizeof(cdb)); + cdb[0] =3D WRITE_SAME_16; + stq_be_p(&cdb[2], zone2); + stl_be_p(&cdb[10], ZONE_BLOCKS); + slot =3D ufs_zufs_send_write_async(ufs, EIO_ZONED_TEST_LUN, cdb, write= _buf, + sizeof(write_buf)); + g_assert_cmpuint(ufs_zufs_eio_zone(ufs, 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_eio_zone(ufs, zone2, &wp), =3D=3D, 0xe); + + 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 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, last_start + 1, + TEST_IMAGE_SIZE / 4096 - last_start); + 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); + 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 cdb[UFS_CDB_SIZE]; + UtpUpiuRsp rsp_upiu; + const uint64_t zone4 =3D 4 * ZONE_BLOCKS, zone5 =3D 5 * ZONE_BLOCKS; + + ufs_init(ufs, alloc); + ufs_zufs_clear_unit_attention(ufs); + memset(write_buf, 0x96, sizeof(write_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 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_INVALID_CMD_TABLE_ATTR); + 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); + + 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 " + "-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_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; @@ -1854,6 +2814,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) }); @@ -1894,6 +2860,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