From nobody Sun May 12 20:04:00 2024 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=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1693986341; cv=none; d=zohomail.com; s=zohoarc; b=MIAmyg9o9kA6KVV3wAXaJg/w8k60KahT1w/9cxmcqRoPnA/pXSEUCPawc6CKpDhcG1XHwNnjNrkFvbFqffyDxQMEoh1uTmgNK+0sl3flLYpw2ah3sxCFcx9bTFa90dV3oWBBCfpRWIU6gzwLeXfyIrR3qVjwUyenYpyaao49O/Y= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1693986341; h=Content-Transfer-Encoding:Cc:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To; bh=9iKuGVPk33VIJ+BrnzQVgJUM0tNmY6ZfI2faqLhaOBc=; b=PBeDb1m7a+Elbs+dm89QdwabL/EZ0dwRQCaNLnvNlsgsefd44o99X7/BsGLXGtvL91vOfRVRVosjgqmEYSr0l297wKSiLc1YRJKmE+UEU2KeNSrxiuVpDhtvDI2uGnxJQ5IXrSjuzIS7zYfAyugXSioeJwSlG5VbS6A2RtvGrF8= 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 lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1693986341400257.92533186358173; Wed, 6 Sep 2023 00:45:41 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1qdnDU-0007Gm-Jg; Wed, 06 Sep 2023 03:44:48 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1qdnDS-0007GD-VS; Wed, 06 Sep 2023 03:44:47 -0400 Received: from mail-pj1-x1030.google.com ([2607:f8b0:4864:20::1030]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1qdnDN-000500-GU; Wed, 06 Sep 2023 03:44:46 -0400 Received: by mail-pj1-x1030.google.com with SMTP id 98e67ed59e1d1-27178b6417fso2467247a91.0; Wed, 06 Sep 2023 00:44:40 -0700 (PDT) Received: from jeuk-MS-7D42.. ([218.147.112.168]) by smtp.gmail.com with ESMTPSA id ck1-20020a17090afe0100b00262eb0d141esm10434901pjb.28.2023.09.06.00.44.34 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 06 Sep 2023 00:44:38 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20221208; t=1693986279; x=1694591079; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=9iKuGVPk33VIJ+BrnzQVgJUM0tNmY6ZfI2faqLhaOBc=; b=aSAPNb05UiblXzeLKxAFNImNTqzDByVAa3nw5aOtH5f6sAspYmXaDX+FX6geqnivWM pAvf10Ymu3nT7sWpKyt4xNq7LoDb6sd+UORTSnZUu98YBY3yBhlZP9trXjndB+bYcqZM CNGs26MHenkIFwm2zFLInD1i5nVRCgF+SGJPoVEEN77sdeTVZOti/JdG8wmyUtH7yBAI JutMfbxnkliXNa2xcZmbhcTfk+xbae5dn7DfCW4Uw3wOuUyvFStRs1oa8ftAqM/hjXQ4 ePITFeNREg/6IZW0p4bN2Re0dYYelK/kO22vua3EoI+hcJphh/Dixe9hHrEEhd1oKZPX WKxw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1693986279; x=1694591079; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=9iKuGVPk33VIJ+BrnzQVgJUM0tNmY6ZfI2faqLhaOBc=; b=dPxC/wol7LzZm3dYizExUJ1i6Zko4QI+jBy5mGybRMQcxRpULaBnE5fGNst7woWm33 H9DEBr+YWo9VXiKbnCTF8cxipSbj8+HpiBtwrH+NhCbnnDQ5Ej7mpM6nuTNyrsV/fOHX oBi0r62+QrZV70QXuD5MpNviZqouXQbUM1axbq8putPoqk2xXmIOPy62HlunS3VMEk2j litzY9LvUBMtQVAFKe72XyiVpkdq2NOUs02pSeKupJYPMBWeKoJNv5a6XHC9MYXIMIIz 2oPnPiGStKmaW9E3PMfTgbHUdb/xURN9w0hEgJsV3CCV8I0O+TxYF0zXfGctSO0LEwsU uDdw== X-Gm-Message-State: AOJu0Yx2IzZvmNg+oFJIlcvxydX1fF4RMK7w1WjUIUANZ7ZYlnxhhMWy g66GBXFCoj6gTy5/TCyp/RqXphSuez1FSQ== X-Google-Smtp-Source: AGHT+IG4ARnI1FpXqkV0dDkRYokFlGJ04DVJQEvs+onlk5rMcnCswFHomQFPOLs7SbrbiPmQJTPe0g== X-Received: by 2002:a17:90a:880f:b0:26f:d235:bff0 with SMTP id s15-20020a17090a880f00b0026fd235bff0mr13549577pjn.3.1693986278961; Wed, 06 Sep 2023 00:44:38 -0700 (PDT) From: Jeuk Kim To: qemu-devel@nongnu.org Cc: jeuk20.kim@gmail.com, berrange@redhat.com, fam@euphon.net, hreitz@redhat.com, jeuk20.kim@samsung.com, k.jensen@samsung.com, kwolf@redhat.com, lvivier@redhat.com, marcandre.lureau@redhat.com, marcel.apfelbaum@gmail.com, mst@redhat.com, pbonzini@redhat.com, philmd@linaro.org, qemu-block@nongnu.org, stefanha@redhat.com, thuth@redhat.com Subject: [PATCH v10 1/4] hw/ufs: Initial commit for emulated Universal-Flash-Storage Date: Wed, 6 Sep 2023 16:43:48 +0900 Message-Id: <10232660d462ee5cd10cf673f1a9a1205fc8276c.1693980783.git.jeuk20.kim@gmail.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable 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=lists.gnu.org; Received-SPF: pass client-ip=2607:f8b0:4864:20::1030; envelope-from=jeuk20.kim@gmail.com; helo=mail-pj1-x1030.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-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: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1693986343583100003 Content-Type: text/plain; charset="utf-8" From: Jeuk Kim Universal Flash Storage (UFS) is a high-performance mass storage device with a serial interface. It is primarily used as a high-performance data storage device for embedded applications. This commit contains code for UFS device to be recognized as a UFS PCI device. Patches to handle UFS logical unit and Transfer Request will follow. Signed-off-by: Jeuk Kim Reviewed-by: Stefan Hajnoczi --- MAINTAINERS | 6 + docs/specs/pci-ids.rst | 2 + hw/Kconfig | 1 + hw/meson.build | 1 + hw/ufs/Kconfig | 4 + hw/ufs/meson.build | 1 + hw/ufs/trace-events | 32 ++ hw/ufs/trace.h | 1 + hw/ufs/ufs.c | 278 ++++++++++ hw/ufs/ufs.h | 42 ++ include/block/ufs.h | 1090 ++++++++++++++++++++++++++++++++++++++ include/hw/pci/pci.h | 1 + include/hw/pci/pci_ids.h | 1 + meson.build | 1 + 14 files changed, 1461 insertions(+) create mode 100644 hw/ufs/Kconfig create mode 100644 hw/ufs/meson.build create mode 100644 hw/ufs/trace-events create mode 100644 hw/ufs/trace.h create mode 100644 hw/ufs/ufs.c create mode 100644 hw/ufs/ufs.h create mode 100644 include/block/ufs.h diff --git a/MAINTAINERS b/MAINTAINERS index 3b29568ed4..85cb0f261e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2248,6 +2248,12 @@ F: tests/qtest/nvme-test.c F: docs/system/devices/nvme.rst T: git git://git.infradead.org/qemu-nvme.git nvme-next =20 +ufs +M: Jeuk Kim +S: Supported +F: hw/ufs/* +F: include/block/ufs.h + megasas M: Hannes Reinecke L: qemu-block@nongnu.org diff --git a/docs/specs/pci-ids.rst b/docs/specs/pci-ids.rst index e302bea484..d6707fa069 100644 --- a/docs/specs/pci-ids.rst +++ b/docs/specs/pci-ids.rst @@ -92,6 +92,8 @@ PCI devices (other than virtio): PCI PVPanic device (``-device pvpanic-pci``) 1b36:0012 PCI ACPI ERST device (``-device acpi-erst``) +1b36:0013 + PCI UFS device (``-device ufs``) =20 All these devices are documented in :doc:`index`. =20 diff --git a/hw/Kconfig b/hw/Kconfig index ba62ff6417..9ca7b38c31 100644 --- a/hw/Kconfig +++ b/hw/Kconfig @@ -38,6 +38,7 @@ source smbios/Kconfig source ssi/Kconfig source timer/Kconfig source tpm/Kconfig +source ufs/Kconfig source usb/Kconfig source virtio/Kconfig source vfio/Kconfig diff --git a/hw/meson.build b/hw/meson.build index c7ac7d3d75..f01fac4617 100644 --- a/hw/meson.build +++ b/hw/meson.build @@ -37,6 +37,7 @@ subdir('smbios') subdir('ssi') subdir('timer') subdir('tpm') +subdir('ufs') subdir('usb') subdir('vfio') subdir('virtio') diff --git a/hw/ufs/Kconfig b/hw/ufs/Kconfig new file mode 100644 index 0000000000..b7b3392e85 --- /dev/null +++ b/hw/ufs/Kconfig @@ -0,0 +1,4 @@ +config UFS_PCI + bool + default y if PCI_DEVICES + depends on PCI diff --git a/hw/ufs/meson.build b/hw/ufs/meson.build new file mode 100644 index 0000000000..eb5164bde9 --- /dev/null +++ b/hw/ufs/meson.build @@ -0,0 +1 @@ +system_ss.add(when: 'CONFIG_UFS_PCI', if_true: files('ufs.c')) diff --git a/hw/ufs/trace-events b/hw/ufs/trace-events new file mode 100644 index 0000000000..d1badcad10 --- /dev/null +++ b/hw/ufs/trace-events @@ -0,0 +1,32 @@ +# ufs.c +ufs_irq_raise(void) "INTx" +ufs_irq_lower(void) "INTx" +ufs_mmio_read(uint64_t addr, uint64_t data, unsigned size) "addr 0x%"PRIx6= 4" data 0x%"PRIx64" size %d" +ufs_mmio_write(uint64_t addr, uint64_t data, unsigned size) "addr 0x%"PRIx= 64" data 0x%"PRIx64" size %d" +ufs_process_db(uint32_t slot) "UTRLDBR slot %"PRIu32"" +ufs_process_req(uint32_t slot) "UTRLDBR slot %"PRIu32"" +ufs_complete_req(uint32_t slot) "UTRLDBR slot %"PRIu32"" +ufs_sendback_req(uint32_t slot) "UTRLDBR slot %"PRIu32"" +ufs_exec_nop_cmd(uint32_t slot) "UTRLDBR slot %"PRIu32"" +ufs_exec_scsi_cmd(uint32_t slot, uint8_t lun, uint8_t opcode) "slot %"PRIu= 32", lun 0x%"PRIx8", opcode 0x%"PRIx8"" +ufs_exec_query_cmd(uint32_t slot, uint8_t opcode) "slot %"PRIu32", opcode = 0x%"PRIx8"" +ufs_process_uiccmd(uint32_t uiccmd, uint32_t ucmdarg1, uint32_t ucmdarg2, = uint32_t ucmdarg3) "uiccmd 0x%"PRIx32", ucmdarg1 0x%"PRIx32", ucmdarg2 0x%"= PRIx32", ucmdarg3 0x%"PRIx32"" + +# error condition +ufs_err_dma_read_utrd(uint32_t slot, uint64_t addr) "failed to read utrd. = UTRLDBR slot %"PRIu32", UTRD dma addr %"PRIu64"" +ufs_err_dma_read_req_upiu(uint32_t slot, uint64_t addr) "failed to read re= q upiu. UTRLDBR slot %"PRIu32", request upiu addr %"PRIu64"" +ufs_err_dma_read_prdt(uint32_t slot, uint64_t addr) "failed to read prdt. = UTRLDBR slot %"PRIu32", prdt addr %"PRIu64"" +ufs_err_dma_write_utrd(uint32_t slot, uint64_t addr) "failed to write utrd= . UTRLDBR slot %"PRIu32", UTRD dma addr %"PRIu64"" +ufs_err_dma_write_rsp_upiu(uint32_t slot, uint64_t addr) "failed to write = rsp upiu. UTRLDBR slot %"PRIu32", response upiu addr %"PRIu64"" +ufs_err_utrl_slot_busy(uint32_t slot) "UTRLDBR slot %"PRIu32" is busy" +ufs_err_unsupport_register_offset(uint32_t offset) "Register offset 0x%"PR= Ix32" is not yet supported" +ufs_err_invalid_register_offset(uint32_t offset) "Register offset 0x%"PRIx= 32" is invalid" +ufs_err_scsi_cmd_invalid_lun(uint8_t lun) "scsi command has invalid lun: 0= x%"PRIx8"" +ufs_err_query_flag_not_readable(uint8_t idn) "query flag idn 0x%"PRIx8" is= denied to read" +ufs_err_query_flag_not_writable(uint8_t idn) "query flag idn 0x%"PRIx8" is= denied to write" +ufs_err_query_attr_not_readable(uint8_t idn) "query attribute idn 0x%"PRIx= 8" is denied to read" +ufs_err_query_attr_not_writable(uint8_t idn) "query attribute idn 0x%"PRIx= 8" is denied to write" +ufs_err_query_invalid_opcode(uint8_t opcode) "query request has invalid op= code. opcode: 0x%"PRIx8"" +ufs_err_query_invalid_idn(uint8_t opcode, uint8_t idn) "query request has = invalid idn. opcode: 0x%"PRIx8", idn 0x%"PRIx8"" +ufs_err_query_invalid_index(uint8_t opcode, uint8_t index) "query request = has invalid index. opcode: 0x%"PRIx8", index 0x%"PRIx8"" +ufs_err_invalid_trans_code(uint32_t slot, uint8_t trans_code) "request upi= u has invalid transaction code. slot: %"PRIu32", trans_code: 0x%"PRIx8"" diff --git a/hw/ufs/trace.h b/hw/ufs/trace.h new file mode 100644 index 0000000000..2dbd6397c3 --- /dev/null +++ b/hw/ufs/trace.h @@ -0,0 +1 @@ +#include "trace/trace-hw_ufs.h" diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c new file mode 100644 index 0000000000..df87f2a6d5 --- /dev/null +++ b/hw/ufs/ufs.c @@ -0,0 +1,278 @@ +/* + * QEMU Universal Flash Storage (UFS) Controller + * + * Copyright (c) 2023 Samsung Electronics Co., Ltd. All rights reserved. + * + * Written by Jeuk Kim + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "migration/vmstate.h" +#include "trace.h" +#include "ufs.h" + +/* The QEMU-UFS device follows spec version 3.1 */ +#define UFS_SPEC_VER 0x00000310 +#define UFS_MAX_NUTRS 32 +#define UFS_MAX_NUTMRS 8 + +static void ufs_irq_check(UfsHc *u) +{ + PCIDevice *pci =3D PCI_DEVICE(u); + + if ((u->reg.is & UFS_INTR_MASK) & u->reg.ie) { + trace_ufs_irq_raise(); + pci_irq_assert(pci); + } else { + trace_ufs_irq_lower(); + pci_irq_deassert(pci); + } +} + +static void ufs_process_uiccmd(UfsHc *u, uint32_t val) +{ + trace_ufs_process_uiccmd(val, u->reg.ucmdarg1, u->reg.ucmdarg2, + u->reg.ucmdarg3); + /* + * Only the essential uic commands for running drivers on Linux and Wi= ndows + * are implemented. + */ + switch (val) { + case UFS_UIC_CMD_DME_LINK_STARTUP: + u->reg.hcs =3D FIELD_DP32(u->reg.hcs, HCS, DP, 1); + u->reg.hcs =3D FIELD_DP32(u->reg.hcs, HCS, UTRLRDY, 1); + u->reg.hcs =3D FIELD_DP32(u->reg.hcs, HCS, UTMRLRDY, 1); + u->reg.ucmdarg2 =3D UFS_UIC_CMD_RESULT_SUCCESS; + break; + /* TODO: Revisit it when Power Management is implemented */ + case UFS_UIC_CMD_DME_HIBER_ENTER: + u->reg.is =3D FIELD_DP32(u->reg.is, IS, UHES, 1); + u->reg.hcs =3D FIELD_DP32(u->reg.hcs, HCS, UPMCRS, UFS_PWR_LOCAL); + u->reg.ucmdarg2 =3D UFS_UIC_CMD_RESULT_SUCCESS; + break; + case UFS_UIC_CMD_DME_HIBER_EXIT: + u->reg.is =3D FIELD_DP32(u->reg.is, IS, UHXS, 1); + u->reg.hcs =3D FIELD_DP32(u->reg.hcs, HCS, UPMCRS, UFS_PWR_LOCAL); + u->reg.ucmdarg2 =3D UFS_UIC_CMD_RESULT_SUCCESS; + break; + default: + u->reg.ucmdarg2 =3D UFS_UIC_CMD_RESULT_FAILURE; + } + + u->reg.is =3D FIELD_DP32(u->reg.is, IS, UCCS, 1); + + ufs_irq_check(u); +} + +static void ufs_write_reg(UfsHc *u, hwaddr offset, uint32_t data, unsigned= size) +{ + switch (offset) { + case A_IS: + u->reg.is &=3D ~data; + ufs_irq_check(u); + break; + case A_IE: + u->reg.ie =3D data; + ufs_irq_check(u); + break; + case A_HCE: + if (!FIELD_EX32(u->reg.hce, HCE, HCE) && FIELD_EX32(data, HCE, HCE= )) { + u->reg.hcs =3D FIELD_DP32(u->reg.hcs, HCS, UCRDY, 1); + u->reg.hce =3D FIELD_DP32(u->reg.hce, HCE, HCE, 1); + } else if (FIELD_EX32(u->reg.hce, HCE, HCE) && + !FIELD_EX32(data, HCE, HCE)) { + u->reg.hcs =3D 0; + u->reg.hce =3D FIELD_DP32(u->reg.hce, HCE, HCE, 0); + } + break; + case A_UTRLBA: + u->reg.utrlba =3D data & R_UTRLBA_UTRLBA_MASK; + break; + case A_UTRLBAU: + u->reg.utrlbau =3D data; + break; + case A_UTRLDBR: + /* Not yet supported */ + break; + case A_UTRLRSR: + u->reg.utrlrsr =3D data; + break; + case A_UTRLCNR: + u->reg.utrlcnr &=3D ~data; + break; + case A_UTMRLBA: + u->reg.utmrlba =3D data & R_UTMRLBA_UTMRLBA_MASK; + break; + case A_UTMRLBAU: + u->reg.utmrlbau =3D data; + break; + case A_UICCMD: + ufs_process_uiccmd(u, data); + break; + case A_UCMDARG1: + u->reg.ucmdarg1 =3D data; + break; + case A_UCMDARG2: + u->reg.ucmdarg2 =3D data; + break; + case A_UCMDARG3: + u->reg.ucmdarg3 =3D data; + break; + case A_UTRLCLR: + case A_UTMRLDBR: + case A_UTMRLCLR: + case A_UTMRLRSR: + trace_ufs_err_unsupport_register_offset(offset); + break; + default: + trace_ufs_err_invalid_register_offset(offset); + break; + } +} + +static uint64_t ufs_mmio_read(void *opaque, hwaddr addr, unsigned size) +{ + UfsHc *u =3D (UfsHc *)opaque; + uint8_t *ptr =3D (uint8_t *)&u->reg; + uint64_t value; + + if (addr > sizeof(u->reg) - size) { + trace_ufs_err_invalid_register_offset(addr); + return 0; + } + + value =3D *(uint32_t *)(ptr + addr); + trace_ufs_mmio_read(addr, value, size); + return value; +} + +static void ufs_mmio_write(void *opaque, hwaddr addr, uint64_t data, + unsigned size) +{ + UfsHc *u =3D (UfsHc *)opaque; + + if (addr > sizeof(u->reg) - size) { + trace_ufs_err_invalid_register_offset(addr); + return; + } + + trace_ufs_mmio_write(addr, data, size); + ufs_write_reg(u, addr, data, size); +} + +static const MemoryRegionOps ufs_mmio_ops =3D { + .read =3D ufs_mmio_read, + .write =3D ufs_mmio_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .impl =3D { + .min_access_size =3D 4, + .max_access_size =3D 4, + }, +}; + +static bool ufs_check_constraints(UfsHc *u, Error **errp) +{ + if (u->params.nutrs > UFS_MAX_NUTRS) { + error_setg(errp, "nutrs must be less than or equal to %d", + UFS_MAX_NUTRS); + return false; + } + + if (u->params.nutmrs > UFS_MAX_NUTMRS) { + error_setg(errp, "nutmrs must be less than or equal to %d", + UFS_MAX_NUTMRS); + return false; + } + + return true; +} + +static void ufs_init_pci(UfsHc *u, PCIDevice *pci_dev) +{ + uint8_t *pci_conf =3D pci_dev->config; + + pci_conf[PCI_INTERRUPT_PIN] =3D 1; + pci_config_set_prog_interface(pci_conf, 0x1); + + memory_region_init_io(&u->iomem, OBJECT(u), &ufs_mmio_ops, u, "ufs", + u->reg_size); + pci_register_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &u->iomem); + u->irq =3D pci_allocate_irq(pci_dev); +} + +static void ufs_init_hc(UfsHc *u) +{ + uint32_t cap =3D 0; + + u->reg_size =3D pow2ceil(sizeof(UfsReg)); + + memset(&u->reg, 0, sizeof(u->reg)); + cap =3D FIELD_DP32(cap, CAP, NUTRS, (u->params.nutrs - 1)); + cap =3D FIELD_DP32(cap, CAP, RTT, 2); + cap =3D FIELD_DP32(cap, CAP, NUTMRS, (u->params.nutmrs - 1)); + cap =3D FIELD_DP32(cap, CAP, AUTOH8, 0); + cap =3D FIELD_DP32(cap, CAP, 64AS, 1); + cap =3D FIELD_DP32(cap, CAP, OODDS, 0); + cap =3D FIELD_DP32(cap, CAP, UICDMETMS, 0); + cap =3D FIELD_DP32(cap, CAP, CS, 0); + u->reg.cap =3D cap; + u->reg.ver =3D UFS_SPEC_VER; +} + +static void ufs_realize(PCIDevice *pci_dev, Error **errp) +{ + UfsHc *u =3D UFS(pci_dev); + + if (!ufs_check_constraints(u, errp)) { + return; + } + + ufs_init_hc(u); + ufs_init_pci(u, pci_dev); +} + +static Property ufs_props[] =3D { + DEFINE_PROP_STRING("serial", UfsHc, params.serial), + DEFINE_PROP_UINT8("nutrs", UfsHc, params.nutrs, 32), + DEFINE_PROP_UINT8("nutmrs", UfsHc, params.nutmrs, 8), + DEFINE_PROP_END_OF_LIST(), +}; + +static const VMStateDescription ufs_vmstate =3D { + .name =3D "ufs", + .unmigratable =3D 1, +}; + +static void ufs_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(oc); + PCIDeviceClass *pc =3D PCI_DEVICE_CLASS(oc); + + pc->realize =3D ufs_realize; + pc->vendor_id =3D PCI_VENDOR_ID_REDHAT; + pc->device_id =3D PCI_DEVICE_ID_REDHAT_UFS; + pc->class_id =3D PCI_CLASS_STORAGE_UFS; + + set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); + dc->desc =3D "Universal Flash Storage"; + device_class_set_props(dc, ufs_props); + dc->vmsd =3D &ufs_vmstate; +} + +static const TypeInfo ufs_info =3D { + .name =3D TYPE_UFS, + .parent =3D TYPE_PCI_DEVICE, + .class_init =3D ufs_class_init, + .instance_size =3D sizeof(UfsHc), + .interfaces =3D (InterfaceInfo[]){ { INTERFACE_PCIE_DEVICE }, {} }, +}; + +static void ufs_register_types(void) +{ + type_register_static(&ufs_info); +} + +type_init(ufs_register_types) diff --git a/hw/ufs/ufs.h b/hw/ufs/ufs.h new file mode 100644 index 0000000000..d9d195caec --- /dev/null +++ b/hw/ufs/ufs.h @@ -0,0 +1,42 @@ +/* + * QEMU UFS + * + * Copyright (c) 2023 Samsung Electronics Co., Ltd. All rights reserved. + * + * Written by Jeuk Kim + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_UFS_UFS_H +#define HW_UFS_UFS_H + +#include "hw/pci/pci_device.h" +#include "hw/scsi/scsi.h" +#include "block/ufs.h" + +#define UFS_MAX_LUS 32 +#define UFS_BLOCK_SIZE 4096 + +typedef struct UfsParams { + char *serial; + uint8_t nutrs; /* Number of UTP Transfer Request Slots */ + uint8_t nutmrs; /* Number of UTP Task Management Request Slots */ +} UfsParams; + +typedef struct UfsHc { + PCIDevice parent_obj; + MemoryRegion iomem; + UfsReg reg; + UfsParams params; + uint32_t reg_size; + + qemu_irq irq; + QEMUBH *doorbell_bh; + QEMUBH *complete_bh; +} UfsHc; + +#define TYPE_UFS "ufs" +#define UFS(obj) OBJECT_CHECK(UfsHc, (obj), TYPE_UFS) + +#endif /* HW_UFS_UFS_H */ diff --git a/include/block/ufs.h b/include/block/ufs.h new file mode 100644 index 0000000000..fd884eb8ce --- /dev/null +++ b/include/block/ufs.h @@ -0,0 +1,1090 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef BLOCK_UFS_H +#define BLOCK_UFS_H + +#include "hw/registerfields.h" + +typedef struct QEMU_PACKED UfsReg { + uint32_t cap; + uint32_t rsvd0; + uint32_t ver; + uint32_t rsvd1; + uint32_t hcpid; + uint32_t hcmid; + uint32_t ahit; + uint32_t rsvd2; + uint32_t is; + uint32_t ie; + uint32_t rsvd3[2]; + uint32_t hcs; + uint32_t hce; + uint32_t uecpa; + uint32_t uecdl; + uint32_t uecn; + uint32_t uect; + uint32_t uecdme; + uint32_t utriacr; + uint32_t utrlba; + uint32_t utrlbau; + uint32_t utrldbr; + uint32_t utrlclr; + uint32_t utrlrsr; + uint32_t utrlcnr; + uint32_t rsvd4[2]; + uint32_t utmrlba; + uint32_t utmrlbau; + uint32_t utmrldbr; + uint32_t utmrlclr; + uint32_t utmrlrsr; + uint32_t rsvd5[3]; + uint32_t uiccmd; + uint32_t ucmdarg1; + uint32_t ucmdarg2; + uint32_t ucmdarg3; + uint32_t rsvd6[4]; + uint32_t rsvd7[4]; + uint32_t rsvd8[16]; + uint32_t ccap; +} UfsReg; + +REG32(CAP, offsetof(UfsReg, cap)) + FIELD(CAP, NUTRS, 0, 5) + FIELD(CAP, RTT, 8, 8) + FIELD(CAP, NUTMRS, 16, 3) + FIELD(CAP, AUTOH8, 23, 1) + FIELD(CAP, 64AS, 24, 1) + FIELD(CAP, OODDS, 25, 1) + FIELD(CAP, UICDMETMS, 26, 1) + FIELD(CAP, CS, 28, 1) +REG32(VER, offsetof(UfsReg, ver)) +REG32(HCPID, offsetof(UfsReg, hcpid)) +REG32(HCMID, offsetof(UfsReg, hcmid)) +REG32(AHIT, offsetof(UfsReg, ahit)) +REG32(IS, offsetof(UfsReg, is)) + FIELD(IS, UTRCS, 0, 1) + FIELD(IS, UDEPRI, 1, 1) + FIELD(IS, UE, 2, 1) + FIELD(IS, UTMS, 3, 1) + FIELD(IS, UPMS, 4, 1) + FIELD(IS, UHXS, 5, 1) + FIELD(IS, UHES, 6, 1) + FIELD(IS, ULLS, 7, 1) + FIELD(IS, ULSS, 8, 1) + FIELD(IS, UTMRCS, 9, 1) + FIELD(IS, UCCS, 10, 1) + FIELD(IS, DFES, 11, 1) + FIELD(IS, UTPES, 12, 1) + FIELD(IS, HCFES, 16, 1) + FIELD(IS, SBFES, 17, 1) + FIELD(IS, CEFES, 18, 1) +REG32(IE, offsetof(UfsReg, ie)) + FIELD(IE, UTRCE, 0, 1) + FIELD(IE, UDEPRIE, 1, 1) + FIELD(IE, UEE, 2, 1) + FIELD(IE, UTMSE, 3, 1) + FIELD(IE, UPMSE, 4, 1) + FIELD(IE, UHXSE, 5, 1) + FIELD(IE, UHESE, 6, 1) + FIELD(IE, ULLSE, 7, 1) + FIELD(IE, ULSSE, 8, 1) + FIELD(IE, UTMRCE, 9, 1) + FIELD(IE, UCCE, 10, 1) + FIELD(IE, DFEE, 11, 1) + FIELD(IE, UTPEE, 12, 1) + FIELD(IE, HCFEE, 16, 1) + FIELD(IE, SBFEE, 17, 1) + FIELD(IE, CEFEE, 18, 1) +REG32(HCS, offsetof(UfsReg, hcs)) + FIELD(HCS, DP, 0, 1) + FIELD(HCS, UTRLRDY, 1, 1) + FIELD(HCS, UTMRLRDY, 2, 1) + FIELD(HCS, UCRDY, 3, 1) + FIELD(HCS, UPMCRS, 8, 3) +REG32(HCE, offsetof(UfsReg, hce)) + FIELD(HCE, HCE, 0, 1) + FIELD(HCE, CGE, 1, 1) +REG32(UECPA, offsetof(UfsReg, uecpa)) +REG32(UECDL, offsetof(UfsReg, uecdl)) +REG32(UECN, offsetof(UfsReg, uecn)) +REG32(UECT, offsetof(UfsReg, uect)) +REG32(UECDME, offsetof(UfsReg, uecdme)) +REG32(UTRIACR, offsetof(UfsReg, utriacr)) +REG32(UTRLBA, offsetof(UfsReg, utrlba)) + FIELD(UTRLBA, UTRLBA, 9, 22) +REG32(UTRLBAU, offsetof(UfsReg, utrlbau)) +REG32(UTRLDBR, offsetof(UfsReg, utrldbr)) +REG32(UTRLCLR, offsetof(UfsReg, utrlclr)) +REG32(UTRLRSR, offsetof(UfsReg, utrlrsr)) +REG32(UTRLCNR, offsetof(UfsReg, utrlcnr)) +REG32(UTMRLBA, offsetof(UfsReg, utmrlba)) + FIELD(UTMRLBA, UTMRLBA, 9, 22) +REG32(UTMRLBAU, offsetof(UfsReg, utmrlbau)) +REG32(UTMRLDBR, offsetof(UfsReg, utmrldbr)) +REG32(UTMRLCLR, offsetof(UfsReg, utmrlclr)) +REG32(UTMRLRSR, offsetof(UfsReg, utmrlrsr)) +REG32(UICCMD, offsetof(UfsReg, uiccmd)) +REG32(UCMDARG1, offsetof(UfsReg, ucmdarg1)) +REG32(UCMDARG2, offsetof(UfsReg, ucmdarg2)) +REG32(UCMDARG3, offsetof(UfsReg, ucmdarg3)) +REG32(CCAP, offsetof(UfsReg, ccap)) + +#define UFS_INTR_MASK \ + ((1 << R_IS_CEFES_SHIFT) | (1 << R_IS_SBFES_SHIFT) | \ + (1 << R_IS_HCFES_SHIFT) | (1 << R_IS_UTPES_SHIFT) | \ + (1 << R_IS_DFES_SHIFT) | (1 << R_IS_UCCS_SHIFT) | \ + (1 << R_IS_UTMRCS_SHIFT) | (1 << R_IS_ULSS_SHIFT) | \ + (1 << R_IS_ULLS_SHIFT) | (1 << R_IS_UHES_SHIFT) | \ + (1 << R_IS_UHXS_SHIFT) | (1 << R_IS_UPMS_SHIFT) | \ + (1 << R_IS_UTMS_SHIFT) | (1 << R_IS_UE_SHIFT) | \ + (1 << R_IS_UDEPRI_SHIFT) | (1 << R_IS_UTRCS_SHIFT)) + +#define UFS_UPIU_HEADER_TRANSACTION_TYPE_SHIFT 24 +#define UFS_UPIU_HEADER_TRANSACTION_TYPE_MASK 0xff +#define UFS_UPIU_HEADER_TRANSACTION_TYPE(dword0) \ + ((be32_to_cpu(dword0) >> UFS_UPIU_HEADER_TRANSACTION_TYPE_SHIFT) & \ + UFS_UPIU_HEADER_TRANSACTION_TYPE_MASK) + +#define UFS_UPIU_HEADER_QUERY_FUNC_SHIFT 16 +#define UFS_UPIU_HEADER_QUERY_FUNC_MASK 0xff +#define UFS_UPIU_HEADER_QUERY_FUNC(dword1) \ + ((be32_to_cpu(dword1) >> UFS_UPIU_HEADER_QUERY_FUNC_SHIFT) & \ + UFS_UPIU_HEADER_QUERY_FUNC_MASK) + +#define UFS_UPIU_HEADER_DATA_SEGMENT_LENGTH_SHIFT 0 +#define UFS_UPIU_HEADER_DATA_SEGMENT_LENGTH_MASK 0xffff +#define UFS_UPIU_HEADER_DATA_SEGMENT_LENGTH(dword2) \ + ((be32_to_cpu(dword2) >> UFS_UPIU_HEADER_DATA_SEGMENT_LENGTH_SHIFT) & \ + UFS_UPIU_HEADER_DATA_SEGMENT_LENGTH_MASK) + +typedef struct QEMU_PACKED DeviceDescriptor { + uint8_t length; + uint8_t descriptor_idn; + uint8_t device; + uint8_t device_class; + uint8_t device_sub_class; + uint8_t protocol; + uint8_t number_lu; + uint8_t number_wlu; + uint8_t boot_enable; + uint8_t descr_access_en; + uint8_t init_power_mode; + uint8_t high_priority_lun; + uint8_t secure_removal_type; + uint8_t security_lu; + uint8_t background_ops_term_lat; + uint8_t init_active_icc_level; + uint16_t spec_version; + uint16_t manufacture_date; + uint8_t manufacturer_name; + uint8_t product_name; + uint8_t serial_number; + uint8_t oem_id; + uint16_t manufacturer_id; + uint8_t ud_0_base_offset; + uint8_t ud_config_p_length; + uint8_t device_rtt_cap; + uint16_t periodic_rtc_update; + uint8_t ufs_features_support; + uint8_t ffu_timeout; + uint8_t queue_depth; + uint16_t device_version; + uint8_t num_secure_wp_area; + uint32_t psa_max_data_size; + uint8_t psa_state_timeout; + uint8_t product_revision_level; + uint8_t reserved[36]; + uint32_t extended_ufs_features_support; + uint8_t write_booster_buffer_preserve_user_space_en; + uint8_t write_booster_buffer_type; + uint32_t num_shared_write_booster_buffer_alloc_units; +} DeviceDescriptor; + +typedef struct QEMU_PACKED GeometryDescriptor { + uint8_t length; + uint8_t descriptor_idn; + uint8_t media_technology; + uint8_t reserved; + uint64_t total_raw_device_capacity; + uint8_t max_number_lu; + uint32_t segment_size; + uint8_t allocation_unit_size; + uint8_t min_addr_block_size; + uint8_t optimal_read_block_size; + uint8_t optimal_write_block_size; + uint8_t max_in_buffer_size; + uint8_t max_out_buffer_size; + uint8_t rpmb_read_write_size; + uint8_t dynamic_capacity_resource_policy; + uint8_t data_ordering; + uint8_t max_context_id_number; + uint8_t sys_data_tag_unit_size; + uint8_t sys_data_tag_res_size; + uint8_t supported_sec_r_types; + uint16_t supported_memory_types; + uint32_t system_code_max_n_alloc_u; + uint16_t system_code_cap_adj_fac; + uint32_t non_persist_max_n_alloc_u; + uint16_t non_persist_cap_adj_fac; + uint32_t enhanced_1_max_n_alloc_u; + uint16_t enhanced_1_cap_adj_fac; + uint32_t enhanced_2_max_n_alloc_u; + uint16_t enhanced_2_cap_adj_fac; + uint32_t enhanced_3_max_n_alloc_u; + uint16_t enhanced_3_cap_adj_fac; + uint32_t enhanced_4_max_n_alloc_u; + uint16_t enhanced_4_cap_adj_fac; + uint32_t optimal_logical_block_size; + uint8_t reserved2[7]; + uint32_t write_booster_buffer_max_n_alloc_units; + uint8_t device_max_write_booster_l_us; + uint8_t write_booster_buffer_cap_adj_fac; + uint8_t supported_write_booster_buffer_user_space_reduction_types; + uint8_t supported_write_booster_buffer_types; +} GeometryDescriptor; + +#define UFS_GEOMETRY_CAPACITY_SHIFT 9 + +typedef struct QEMU_PACKED UnitDescriptor { + uint8_t length; + uint8_t descriptor_idn; + uint8_t unit_index; + uint8_t lu_enable; + uint8_t boot_lun_id; + uint8_t lu_write_protect; + uint8_t lu_queue_depth; + uint8_t psa_sensitive; + uint8_t memory_type; + uint8_t data_reliability; + uint8_t logical_block_size; + uint64_t logical_block_count; + uint32_t erase_block_size; + uint8_t provisioning_type; + uint64_t phy_mem_resource_count; + uint16_t context_capabilities; + uint8_t large_unit_granularity_m1; + uint8_t reserved[6]; + uint32_t lu_num_write_booster_buffer_alloc_units; +} UnitDescriptor; + +typedef struct QEMU_PACKED RpmbUnitDescriptor { + uint8_t length; + uint8_t descriptor_idn; + uint8_t unit_index; + uint8_t lu_enable; + uint8_t boot_lun_id; + uint8_t lu_write_protect; + uint8_t lu_queue_depth; + uint8_t psa_sensitive; + uint8_t memory_type; + uint8_t reserved; + uint8_t logical_block_size; + uint64_t logical_block_count; + uint32_t erase_block_size; + uint8_t provisioning_type; + uint64_t phy_mem_resource_count; + uint8_t reserved2[3]; +} RpmbUnitDescriptor; + +typedef struct QEMU_PACKED PowerParametersDescriptor { + uint8_t length; + uint8_t descriptor_idn; + uint16_t active_icc_levels_vcc[16]; + uint16_t active_icc_levels_vccq[16]; + uint16_t active_icc_levels_vccq_2[16]; +} PowerParametersDescriptor; + +typedef struct QEMU_PACKED InterconnectDescriptor { + uint8_t length; + uint8_t descriptor_idn; + uint16_t bcd_unipro_version; + uint16_t bcd_mphy_version; +} InterconnectDescriptor; + +typedef struct QEMU_PACKED StringDescriptor { + uint8_t length; + uint8_t descriptor_idn; + uint16_t UC[126]; +} StringDescriptor; + +typedef struct QEMU_PACKED DeviceHealthDescriptor { + uint8_t length; + uint8_t descriptor_idn; + uint8_t pre_eol_info; + uint8_t device_life_time_est_a; + uint8_t device_life_time_est_b; + uint8_t vendor_prop_info[32]; + uint32_t refresh_total_count; + uint32_t refresh_progress; +} DeviceHealthDescriptor; + +typedef struct QEMU_PACKED Flags { + uint8_t reserved; + uint8_t device_init; + uint8_t permanent_wp_en; + uint8_t power_on_wp_en; + uint8_t background_ops_en; + uint8_t device_life_span_mode_en; + uint8_t purge_enable; + uint8_t refresh_enable; + uint8_t phy_resource_removal; + uint8_t busy_rtc; + uint8_t reserved2; + uint8_t permanently_disable_fw_update; + uint8_t reserved3[2]; + uint8_t wb_en; + uint8_t wb_buffer_flush_en; + uint8_t wb_buffer_flush_during_hibernate; + uint8_t reserved4[2]; +} Flags; + +typedef struct Attributes { + uint8_t boot_lun_en; + uint8_t reserved; + uint8_t current_power_mode; + uint8_t active_icc_level; + uint8_t out_of_order_data_en; + uint8_t background_op_status; + uint8_t purge_status; + uint8_t max_data_in_size; + uint8_t max_data_out_size; + uint32_t dyn_cap_needed; + uint8_t ref_clk_freq; + uint8_t config_descr_lock; + uint8_t max_num_of_rtt; + uint16_t exception_event_control; + uint16_t exception_event_status; + uint32_t seconds_passed; + uint16_t context_conf; + uint8_t device_ffu_status; + uint8_t psa_state; + uint32_t psa_data_size; + uint8_t ref_clk_gating_wait_time; + uint8_t device_case_rough_temperaure; + uint8_t device_too_high_temp_boundary; + uint8_t device_too_low_temp_boundary; + uint8_t throttling_status; + uint8_t wb_buffer_flush_status; + uint8_t available_wb_buffer_size; + uint8_t wb_buffer_life_time_est; + uint32_t current_wb_buffer_size; + uint8_t refresh_status; + uint8_t refresh_freq; + uint8_t refresh_unit; + uint8_t refresh_method; +} Attributes; + +#define UFS_TRANSACTION_SPECIFIC_FIELD_SIZE 20 +#define UFS_MAX_QUERY_DATA_SIZE 256 + +/* Command response result code */ +typedef enum CommandRespCode { + UFS_COMMAND_RESULT_SUCESS =3D 0x00, + UFS_COMMAND_RESULT_FAIL =3D 0x01, +} CommandRespCode; + +enum { + UFS_UPIU_FLAG_UNDERFLOW =3D 0x20, + UFS_UPIU_FLAG_OVERFLOW =3D 0x40, +}; + +typedef struct QEMU_PACKED UtpUpiuHeader { + uint8_t trans_type; + uint8_t flags; + uint8_t lun; + uint8_t task_tag; + uint8_t iid_cmd_set_type; + uint8_t query_func; + uint8_t response; + uint8_t scsi_status; + uint8_t ehs_len; + uint8_t device_inf; + uint16_t data_segment_length; +} UtpUpiuHeader; + +/* + * The code below is copied from the linux kernel + * ("include/uapi/scsi/scsi_bsg_ufs.h") and modified to fit the qemu style. + */ + +typedef struct QEMU_PACKED UtpUpiuQuery { + uint8_t opcode; + uint8_t idn; + uint8_t index; + uint8_t selector; + uint16_t reserved_osf; + uint16_t length; + uint32_t value; + uint32_t reserved[2]; + /* EHS length should be 0. We don't have to worry about EHS area. */ + uint8_t data[UFS_MAX_QUERY_DATA_SIZE]; +} UtpUpiuQuery; + +#define UFS_CDB_SIZE 16 + +/* + * struct UtpUpiuCmd - Command UPIU structure + * @data_transfer_len: Data Transfer Length DW-3 + * @cdb: Command Descriptor Block CDB DW-4 to DW-7 + */ +typedef struct QEMU_PACKED UtpUpiuCmd { + uint32_t exp_data_transfer_len; + uint8_t cdb[UFS_CDB_SIZE]; +} UtpUpiuCmd; + +/* + * struct UtpUpiuReq - general upiu request structure + * @header:UPIU header structure DW-0 to DW-2 + * @sc: fields structure for scsi command DW-3 to DW-7 + * @qr: fields structure for query request DW-3 to DW-7 + * @uc: use utp_upiu_query to host the 4 dwords of uic command + */ +typedef struct QEMU_PACKED UtpUpiuReq { + UtpUpiuHeader header; + union { + UtpUpiuCmd sc; + UtpUpiuQuery qr; + }; +} UtpUpiuReq; + +/* + * The code below is copied from the linux kernel ("include/ufs/ufshci.h")= and + * modified to fit the qemu style. + */ + +enum { + UFS_PWR_OK =3D 0x0, + UFS_PWR_LOCAL =3D 0x01, + UFS_PWR_REMOTE =3D 0x02, + UFS_PWR_BUSY =3D 0x03, + UFS_PWR_ERROR_CAP =3D 0x04, + UFS_PWR_FATAL_ERROR =3D 0x05, +}; + +/* UIC Commands */ +enum uic_cmd_dme { + UFS_UIC_CMD_DME_GET =3D 0x01, + UFS_UIC_CMD_DME_SET =3D 0x02, + UFS_UIC_CMD_DME_PEER_GET =3D 0x03, + UFS_UIC_CMD_DME_PEER_SET =3D 0x04, + UFS_UIC_CMD_DME_POWERON =3D 0x10, + UFS_UIC_CMD_DME_POWEROFF =3D 0x11, + UFS_UIC_CMD_DME_ENABLE =3D 0x12, + UFS_UIC_CMD_DME_RESET =3D 0x14, + UFS_UIC_CMD_DME_END_PT_RST =3D 0x15, + UFS_UIC_CMD_DME_LINK_STARTUP =3D 0x16, + UFS_UIC_CMD_DME_HIBER_ENTER =3D 0x17, + UFS_UIC_CMD_DME_HIBER_EXIT =3D 0x18, + UFS_UIC_CMD_DME_TEST_MODE =3D 0x1A, +}; + +/* UIC Config result code / Generic error code */ +enum { + UFS_UIC_CMD_RESULT_SUCCESS =3D 0x00, + UFS_UIC_CMD_RESULT_INVALID_ATTR =3D 0x01, + UFS_UIC_CMD_RESULT_FAILURE =3D 0x01, + UFS_UIC_CMD_RESULT_INVALID_ATTR_VALUE =3D 0x02, + UFS_UIC_CMD_RESULT_READ_ONLY_ATTR =3D 0x03, + UFS_UIC_CMD_RESULT_WRITE_ONLY_ATTR =3D 0x04, + UFS_UIC_CMD_RESULT_BAD_INDEX =3D 0x05, + UFS_UIC_CMD_RESULT_LOCKED_ATTR =3D 0x06, + UFS_UIC_CMD_RESULT_BAD_TEST_FEATURE_INDEX =3D 0x07, + UFS_UIC_CMD_RESULT_PEER_COMM_FAILURE =3D 0x08, + UFS_UIC_CMD_RESULT_BUSY =3D 0x09, + UFS_UIC_CMD_RESULT_DME_FAILURE =3D 0x0A, +}; + +#define UFS_MASK_UIC_COMMAND_RESULT 0xFF + +/* + * Request Descriptor Definitions + */ + +/* Transfer request command type */ +enum { + UFS_UTP_CMD_TYPE_SCSI =3D 0x0, + UFS_UTP_CMD_TYPE_UFS =3D 0x1, + UFS_UTP_CMD_TYPE_DEV_MANAGE =3D 0x2, +}; + +/* To accommodate UFS2.0 required Command type */ +enum { + UFS_UTP_CMD_TYPE_UFS_STORAGE =3D 0x1, +}; + +enum { + UFS_UTP_SCSI_COMMAND =3D 0x00000000, + UFS_UTP_NATIVE_UFS_COMMAND =3D 0x10000000, + UFS_UTP_DEVICE_MANAGEMENT_FUNCTION =3D 0x20000000, + UFS_UTP_REQ_DESC_INT_CMD =3D 0x01000000, + UFS_UTP_REQ_DESC_CRYPTO_ENABLE_CMD =3D 0x00800000, +}; + +/* UTP Transfer Request Data Direction (DD) */ +enum { + UFS_UTP_NO_DATA_TRANSFER =3D 0x00000000, + UFS_UTP_HOST_TO_DEVICE =3D 0x02000000, + UFS_UTP_DEVICE_TO_HOST =3D 0x04000000, +}; + +/* Overall command status values */ +enum UtpOcsCodes { + UFS_OCS_SUCCESS =3D 0x0, + UFS_OCS_INVALID_CMD_TABLE_ATTR =3D 0x1, + UFS_OCS_INVALID_PRDT_ATTR =3D 0x2, + UFS_OCS_MISMATCH_DATA_BUF_SIZE =3D 0x3, + UFS_OCS_MISMATCH_RESP_UPIU_SIZE =3D 0x4, + UFS_OCS_PEER_COMM_FAILURE =3D 0x5, + UFS_OCS_ABORTED =3D 0x6, + UFS_OCS_FATAL_ERROR =3D 0x7, + UFS_OCS_DEVICE_FATAL_ERROR =3D 0x8, + UFS_OCS_INVALID_CRYPTO_CONFIG =3D 0x9, + UFS_OCS_GENERAL_CRYPTO_ERROR =3D 0xa, + UFS_OCS_INVALID_COMMAND_STATUS =3D 0xf, +}; + +enum { + UFS_MASK_OCS =3D 0x0F, +}; + +/* + * struct UfshcdSgEntry - UFSHCI PRD Entry + * @addr: Physical address; DW-0 and DW-1. + * @reserved: Reserved for future use DW-2 + * @size: size of physical segment DW-3 + */ +typedef struct QEMU_PACKED UfshcdSgEntry { + uint64_t addr; + uint32_t reserved; + uint32_t size; + /* + * followed by variant-specific fields if + * CONFIG_SCSI_UFS_VARIABLE_SG_ENTRY_SIZE has been defined. + */ +} UfshcdSgEntry; + +/* + * struct RequestDescHeader - Descriptor Header common to both UTRD and UT= MRD + * @dword0: Descriptor Header DW0 + * @dword1: Descriptor Header DW1 + * @dword2: Descriptor Header DW2 + * @dword3: Descriptor Header DW3 + */ +typedef struct QEMU_PACKED RequestDescHeader { + uint32_t dword_0; + uint32_t dword_1; + uint32_t dword_2; + uint32_t dword_3; +} RequestDescHeader; + +/* + * struct UtpTransferReqDesc - UTP Transfer Request Descriptor (UTRD) + * @header: UTRD header DW-0 to DW-3 + * @command_desc_base_addr_lo: UCD base address low DW-4 + * @command_desc_base_addr_hi: UCD base address high DW-5 + * @response_upiu_length: response UPIU length DW-6 + * @response_upiu_offset: response UPIU offset DW-6 + * @prd_table_length: Physical region descriptor length DW-7 + * @prd_table_offset: Physical region descriptor offset DW-7 + */ +typedef struct QEMU_PACKED UtpTransferReqDesc { + /* DW 0-3 */ + RequestDescHeader header; + + /* DW 4-5*/ + uint32_t command_desc_base_addr_lo; + uint32_t command_desc_base_addr_hi; + + /* DW 6 */ + uint16_t response_upiu_length; + uint16_t response_upiu_offset; + + /* DW 7 */ + uint16_t prd_table_length; + uint16_t prd_table_offset; +} UtpTransferReqDesc; + +/* + * UTMRD structure. + */ +typedef struct QEMU_PACKED UtpTaskReqDesc { + /* DW 0-3 */ + RequestDescHeader header; + + /* DW 4-11 - Task request UPIU structure */ + struct { + UtpUpiuHeader req_header; + uint32_t input_param1; + uint32_t input_param2; + uint32_t input_param3; + uint32_t reserved1[2]; + } upiu_req; + + /* DW 12-19 - Task Management Response UPIU structure */ + struct { + UtpUpiuHeader rsp_header; + uint32_t output_param1; + uint32_t output_param2; + uint32_t reserved2[3]; + } upiu_rsp; +} UtpTaskReqDesc; + +/* + * The code below is copied from the linux kernel ("include/ufs/ufs.h") and + * modified to fit the qemu style. + */ + +#define UFS_GENERAL_UPIU_REQUEST_SIZE (sizeof(UtpUpiuReq)) +#define UFS_QUERY_DESC_MAX_SIZE 255 +#define UFS_QUERY_DESC_MIN_SIZE 2 +#define UFS_QUERY_DESC_HDR_SIZE 2 +#define UFS_QUERY_OSF_SIZE (GENERAL_UPIU_REQUEST_SIZE - (sizeof(UtpUpiuHea= der))) +#define UFS_SENSE_SIZE 18 + +/* + * UFS device may have standard LUs and LUN id could be from 0x00 to + * 0x7F. Standard LUs use "Peripheral Device Addressing Format". + * UFS device may also have the Well Known LUs (also referred as W-LU) + * which again could be from 0x00 to 0x7F. For W-LUs, device only use + * the "Extended Addressing Format" which means the W-LUNs would be + * from 0xc100 (SCSI_W_LUN_BASE) onwards. + * This means max. LUN number reported from UFS device could be 0xC17F. + */ +#define UFS_UPIU_MAX_UNIT_NUM_ID 0x7F +#define UFS_UPIU_WLUN_ID (1 << 7) + +/* WriteBooster buffer is available only for the logical unit from 0 to 7 = */ +#define UFS_UPIU_MAX_WB_LUN_ID 8 + +/* + * WriteBooster buffer lifetime has a limit setted by vendor. + * If it is over the limit, WriteBooster feature will be disabled. + */ +#define UFS_WB_EXCEED_LIFETIME 0x0B + +/* + * In UFS Spec, the Extra Header Segment (EHS) starts from byte 32 in UPIU + * request/response packet + */ +#define UFS_EHS_OFFSET_IN_RESPONSE 32 + +/* Well known logical unit id in LUN field of UPIU */ +enum { + UFS_UPIU_REPORT_LUNS_WLUN =3D 0x81, + UFS_UPIU_UFS_DEVICE_WLUN =3D 0xD0, + UFS_UPIU_BOOT_WLUN =3D 0xB0, + UFS_UPIU_RPMB_WLUN =3D 0xC4, +}; + +/* + * UFS Protocol Information Unit related definitions + */ + +/* Task management functions */ +enum { + UFS_ABORT_TASK =3D 0x01, + UFS_ABORT_TASK_SET =3D 0x02, + UFS_CLEAR_TASK_SET =3D 0x04, + UFS_LOGICAL_RESET =3D 0x08, + UFS_QUERY_TASK =3D 0x80, + UFS_QUERY_TASK_SET =3D 0x81, +}; + +/* UTP UPIU Transaction Codes Initiator to Target */ +enum { + UFS_UPIU_TRANSACTION_NOP_OUT =3D 0x00, + UFS_UPIU_TRANSACTION_COMMAND =3D 0x01, + UFS_UPIU_TRANSACTION_DATA_OUT =3D 0x02, + UFS_UPIU_TRANSACTION_TASK_REQ =3D 0x04, + UFS_UPIU_TRANSACTION_QUERY_REQ =3D 0x16, +}; + +/* UTP UPIU Transaction Codes Target to Initiator */ +enum { + UFS_UPIU_TRANSACTION_NOP_IN =3D 0x20, + UFS_UPIU_TRANSACTION_RESPONSE =3D 0x21, + UFS_UPIU_TRANSACTION_DATA_IN =3D 0x22, + UFS_UPIU_TRANSACTION_TASK_RSP =3D 0x24, + UFS_UPIU_TRANSACTION_READY_XFER =3D 0x31, + UFS_UPIU_TRANSACTION_QUERY_RSP =3D 0x36, + UFS_UPIU_TRANSACTION_REJECT_UPIU =3D 0x3F, +}; + +/* UPIU Read/Write flags */ +enum { + UFS_UPIU_CMD_FLAGS_NONE =3D 0x00, + UFS_UPIU_CMD_FLAGS_WRITE =3D 0x20, + UFS_UPIU_CMD_FLAGS_READ =3D 0x40, +}; + +/* UPIU Task Attributes */ +enum { + UFS_UPIU_TASK_ATTR_SIMPLE =3D 0x00, + UFS_UPIU_TASK_ATTR_ORDERED =3D 0x01, + UFS_UPIU_TASK_ATTR_HEADQ =3D 0x02, + UFS_UPIU_TASK_ATTR_ACA =3D 0x03, +}; + +/* UPIU Query request function */ +enum { + UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST =3D 0x01, + UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST =3D 0x81, +}; + +/* Flag idn for Query Requests*/ +enum flag_idn { + UFS_QUERY_FLAG_IDN_FDEVICEINIT =3D 0x01, + UFS_QUERY_FLAG_IDN_PERMANENT_WPE =3D 0x02, + UFS_QUERY_FLAG_IDN_PWR_ON_WPE =3D 0x03, + UFS_QUERY_FLAG_IDN_BKOPS_EN =3D 0x04, + UFS_QUERY_FLAG_IDN_LIFE_SPAN_MODE_ENABLE =3D 0x05, + UFS_QUERY_FLAG_IDN_PURGE_ENABLE =3D 0x06, + UFS_QUERY_FLAG_IDN_REFRESH_ENABLE =3D 0x07, + UFS_QUERY_FLAG_IDN_FPHYRESOURCEREMOVAL =3D 0x08, + UFS_QUERY_FLAG_IDN_BUSY_RTC =3D 0x09, + UFS_QUERY_FLAG_IDN_RESERVED3 =3D 0x0A, + UFS_QUERY_FLAG_IDN_PERMANENTLY_DISABLE_FW_UPDATE =3D 0x0B, + UFS_QUERY_FLAG_IDN_WB_EN =3D 0x0E, + UFS_QUERY_FLAG_IDN_WB_BUFF_FLUSH_EN =3D 0x0F, + UFS_QUERY_FLAG_IDN_WB_BUFF_FLUSH_DURING_HIBERN8 =3D 0x10, + UFS_QUERY_FLAG_IDN_HPB_RESET =3D 0x11, + UFS_QUERY_FLAG_IDN_HPB_EN =3D 0x12, + UFS_QUERY_FLAG_IDN_COUNT, +}; + +/* Attribute idn for Query requests */ +enum attr_idn { + UFS_QUERY_ATTR_IDN_BOOT_LU_EN =3D 0x00, + UFS_QUERY_ATTR_IDN_MAX_HPB_SINGLE_CMD =3D 0x01, + UFS_QUERY_ATTR_IDN_POWER_MODE =3D 0x02, + UFS_QUERY_ATTR_IDN_ACTIVE_ICC_LVL =3D 0x03, + UFS_QUERY_ATTR_IDN_OOO_DATA_EN =3D 0x04, + UFS_QUERY_ATTR_IDN_BKOPS_STATUS =3D 0x05, + UFS_QUERY_ATTR_IDN_PURGE_STATUS =3D 0x06, + UFS_QUERY_ATTR_IDN_MAX_DATA_IN =3D 0x07, + UFS_QUERY_ATTR_IDN_MAX_DATA_OUT =3D 0x08, + UFS_QUERY_ATTR_IDN_DYN_CAP_NEEDED =3D 0x09, + UFS_QUERY_ATTR_IDN_REF_CLK_FREQ =3D 0x0A, + UFS_QUERY_ATTR_IDN_CONF_DESC_LOCK =3D 0x0B, + UFS_QUERY_ATTR_IDN_MAX_NUM_OF_RTT =3D 0x0C, + UFS_QUERY_ATTR_IDN_EE_CONTROL =3D 0x0D, + UFS_QUERY_ATTR_IDN_EE_STATUS =3D 0x0E, + UFS_QUERY_ATTR_IDN_SECONDS_PASSED =3D 0x0F, + UFS_QUERY_ATTR_IDN_CNTX_CONF =3D 0x10, + UFS_QUERY_ATTR_IDN_CORR_PRG_BLK_NUM =3D 0x11, + UFS_QUERY_ATTR_IDN_RESERVED2 =3D 0x12, + UFS_QUERY_ATTR_IDN_RESERVED3 =3D 0x13, + UFS_QUERY_ATTR_IDN_FFU_STATUS =3D 0x14, + UFS_QUERY_ATTR_IDN_PSA_STATE =3D 0x15, + UFS_QUERY_ATTR_IDN_PSA_DATA_SIZE =3D 0x16, + UFS_QUERY_ATTR_IDN_REF_CLK_GATING_WAIT_TIME =3D 0x17, + UFS_QUERY_ATTR_IDN_CASE_ROUGH_TEMP =3D 0x18, + UFS_QUERY_ATTR_IDN_HIGH_TEMP_BOUND =3D 0x19, + UFS_QUERY_ATTR_IDN_LOW_TEMP_BOUND =3D 0x1A, + UFS_QUERY_ATTR_IDN_THROTTLING_STATUS =3D 0x1B, + UFS_QUERY_ATTR_IDN_WB_FLUSH_STATUS =3D 0x1C, + UFS_QUERY_ATTR_IDN_AVAIL_WB_BUFF_SIZE =3D 0x1D, + UFS_QUERY_ATTR_IDN_WB_BUFF_LIFE_TIME_EST =3D 0x1E, + UFS_QUERY_ATTR_IDN_CURR_WB_BUFF_SIZE =3D 0x1F, + UFS_QUERY_ATTR_IDN_REFRESH_STATUS =3D 0x2C, + UFS_QUERY_ATTR_IDN_REFRESH_FREQ =3D 0x2D, + UFS_QUERY_ATTR_IDN_REFRESH_UNIT =3D 0x2E, + UFS_QUERY_ATTR_IDN_COUNT, +}; + +/* Descriptor idn for Query requests */ +enum desc_idn { + UFS_QUERY_DESC_IDN_DEVICE =3D 0x0, + UFS_QUERY_DESC_IDN_CONFIGURATION =3D 0x1, + UFS_QUERY_DESC_IDN_UNIT =3D 0x2, + UFS_QUERY_DESC_IDN_RFU_0 =3D 0x3, + UFS_QUERY_DESC_IDN_INTERCONNECT =3D 0x4, + UFS_QUERY_DESC_IDN_STRING =3D 0x5, + UFS_QUERY_DESC_IDN_RFU_1 =3D 0x6, + UFS_QUERY_DESC_IDN_GEOMETRY =3D 0x7, + UFS_QUERY_DESC_IDN_POWER =3D 0x8, + UFS_QUERY_DESC_IDN_HEALTH =3D 0x9, + UFS_QUERY_DESC_IDN_MAX, +}; + +enum desc_header_offset { + UFS_QUERY_DESC_LENGTH_OFFSET =3D 0x00, + UFS_QUERY_DESC_DESC_TYPE_OFFSET =3D 0x01, +}; + +/* Unit descriptor parameters offsets in bytes*/ +enum unit_desc_param { + UFS_UNIT_DESC_PARAM_LEN =3D 0x0, + UFS_UNIT_DESC_PARAM_TYPE =3D 0x1, + UFS_UNIT_DESC_PARAM_UNIT_INDEX =3D 0x2, + UFS_UNIT_DESC_PARAM_LU_ENABLE =3D 0x3, + UFS_UNIT_DESC_PARAM_BOOT_LUN_ID =3D 0x4, + UFS_UNIT_DESC_PARAM_LU_WR_PROTECT =3D 0x5, + UFS_UNIT_DESC_PARAM_LU_Q_DEPTH =3D 0x6, + UFS_UNIT_DESC_PARAM_PSA_SENSITIVE =3D 0x7, + UFS_UNIT_DESC_PARAM_MEM_TYPE =3D 0x8, + UFS_UNIT_DESC_PARAM_DATA_RELIABILITY =3D 0x9, + UFS_UNIT_DESC_PARAM_LOGICAL_BLK_SIZE =3D 0xA, + UFS_UNIT_DESC_PARAM_LOGICAL_BLK_COUNT =3D 0xB, + UFS_UNIT_DESC_PARAM_ERASE_BLK_SIZE =3D 0x13, + UFS_UNIT_DESC_PARAM_PROVISIONING_TYPE =3D 0x17, + UFS_UNIT_DESC_PARAM_PHY_MEM_RSRC_CNT =3D 0x18, + UFS_UNIT_DESC_PARAM_CTX_CAPABILITIES =3D 0x20, + UFS_UNIT_DESC_PARAM_LARGE_UNIT_SIZE_M1 =3D 0x22, + UFS_UNIT_DESC_PARAM_HPB_LU_MAX_ACTIVE_RGNS =3D 0x23, + UFS_UNIT_DESC_PARAM_HPB_PIN_RGN_START_OFF =3D 0x25, + UFS_UNIT_DESC_PARAM_HPB_NUM_PIN_RGNS =3D 0x27, + UFS_UNIT_DESC_PARAM_WB_BUF_ALLOC_UNITS =3D 0x29, +}; + +/* RPMB Unit descriptor parameters offsets in bytes*/ +enum rpmb_unit_desc_param { + UFS_RPMB_UNIT_DESC_PARAM_LEN =3D 0x0, + UFS_RPMB_UNIT_DESC_PARAM_TYPE =3D 0x1, + UFS_RPMB_UNIT_DESC_PARAM_UNIT_INDEX =3D 0x2, + UFS_RPMB_UNIT_DESC_PARAM_LU_ENABLE =3D 0x3, + UFS_RPMB_UNIT_DESC_PARAM_BOOT_LUN_ID =3D 0x4, + UFS_RPMB_UNIT_DESC_PARAM_LU_WR_PROTECT =3D 0x5, + UFS_RPMB_UNIT_DESC_PARAM_LU_Q_DEPTH =3D 0x6, + UFS_RPMB_UNIT_DESC_PARAM_PSA_SENSITIVE =3D 0x7, + UFS_RPMB_UNIT_DESC_PARAM_MEM_TYPE =3D 0x8, + UFS_RPMB_UNIT_DESC_PARAM_REGION_EN =3D 0x9, + UFS_RPMB_UNIT_DESC_PARAM_LOGICAL_BLK_SIZE =3D 0xA, + UFS_RPMB_UNIT_DESC_PARAM_LOGICAL_BLK_COUNT =3D 0xB, + UFS_RPMB_UNIT_DESC_PARAM_REGION0_SIZE =3D 0x13, + UFS_RPMB_UNIT_DESC_PARAM_REGION1_SIZE =3D 0x14, + UFS_RPMB_UNIT_DESC_PARAM_REGION2_SIZE =3D 0x15, + UFS_RPMB_UNIT_DESC_PARAM_REGION3_SIZE =3D 0x16, + UFS_RPMB_UNIT_DESC_PARAM_PROVISIONING_TYPE =3D 0x17, + UFS_RPMB_UNIT_DESC_PARAM_PHY_MEM_RSRC_CNT =3D 0x18, +}; + +/* Device descriptor parameters offsets in bytes*/ +enum device_desc_param { + UFS_DEVICE_DESC_PARAM_LEN =3D 0x0, + UFS_DEVICE_DESC_PARAM_TYPE =3D 0x1, + UFS_DEVICE_DESC_PARAM_DEVICE_TYPE =3D 0x2, + UFS_DEVICE_DESC_PARAM_DEVICE_CLASS =3D 0x3, + UFS_DEVICE_DESC_PARAM_DEVICE_SUB_CLASS =3D 0x4, + UFS_DEVICE_DESC_PARAM_PRTCL =3D 0x5, + UFS_DEVICE_DESC_PARAM_NUM_LU =3D 0x6, + UFS_DEVICE_DESC_PARAM_NUM_WLU =3D 0x7, + UFS_DEVICE_DESC_PARAM_BOOT_ENBL =3D 0x8, + UFS_DEVICE_DESC_PARAM_DESC_ACCSS_ENBL =3D 0x9, + UFS_DEVICE_DESC_PARAM_INIT_PWR_MODE =3D 0xA, + UFS_DEVICE_DESC_PARAM_HIGH_PR_LUN =3D 0xB, + UFS_DEVICE_DESC_PARAM_SEC_RMV_TYPE =3D 0xC, + UFS_DEVICE_DESC_PARAM_SEC_LU =3D 0xD, + UFS_DEVICE_DESC_PARAM_BKOP_TERM_LT =3D 0xE, + UFS_DEVICE_DESC_PARAM_ACTVE_ICC_LVL =3D 0xF, + UFS_DEVICE_DESC_PARAM_SPEC_VER =3D 0x10, + UFS_DEVICE_DESC_PARAM_MANF_DATE =3D 0x12, + UFS_DEVICE_DESC_PARAM_MANF_NAME =3D 0x14, + UFS_DEVICE_DESC_PARAM_PRDCT_NAME =3D 0x15, + UFS_DEVICE_DESC_PARAM_SN =3D 0x16, + UFS_DEVICE_DESC_PARAM_OEM_ID =3D 0x17, + UFS_DEVICE_DESC_PARAM_MANF_ID =3D 0x18, + UFS_DEVICE_DESC_PARAM_UD_OFFSET =3D 0x1A, + UFS_DEVICE_DESC_PARAM_UD_LEN =3D 0x1B, + UFS_DEVICE_DESC_PARAM_RTT_CAP =3D 0x1C, + UFS_DEVICE_DESC_PARAM_FRQ_RTC =3D 0x1D, + UFS_DEVICE_DESC_PARAM_UFS_FEAT =3D 0x1F, + UFS_DEVICE_DESC_PARAM_FFU_TMT =3D 0x20, + UFS_DEVICE_DESC_PARAM_Q_DPTH =3D 0x21, + UFS_DEVICE_DESC_PARAM_DEV_VER =3D 0x22, + UFS_DEVICE_DESC_PARAM_NUM_SEC_WPA =3D 0x24, + UFS_DEVICE_DESC_PARAM_PSA_MAX_DATA =3D 0x25, + UFS_DEVICE_DESC_PARAM_PSA_TMT =3D 0x29, + UFS_DEVICE_DESC_PARAM_PRDCT_REV =3D 0x2A, + UFS_DEVICE_DESC_PARAM_HPB_VER =3D 0x40, + UFS_DEVICE_DESC_PARAM_HPB_CONTROL =3D 0x42, + UFS_DEVICE_DESC_PARAM_EXT_UFS_FEATURE_SUP =3D 0x4F, + UFS_DEVICE_DESC_PARAM_WB_PRESRV_USRSPC_EN =3D 0x53, + UFS_DEVICE_DESC_PARAM_WB_TYPE =3D 0x54, + UFS_DEVICE_DESC_PARAM_WB_SHARED_ALLOC_UNITS =3D 0x55, +}; + +/* Interconnect descriptor parameters offsets in bytes*/ +enum interconnect_desc_param { + UFS_INTERCONNECT_DESC_PARAM_LEN =3D 0x0, + UFS_INTERCONNECT_DESC_PARAM_TYPE =3D 0x1, + UFS_INTERCONNECT_DESC_PARAM_UNIPRO_VER =3D 0x2, + UFS_INTERCONNECT_DESC_PARAM_MPHY_VER =3D 0x4, +}; + +/* Geometry descriptor parameters offsets in bytes*/ +enum geometry_desc_param { + UFS_GEOMETRY_DESC_PARAM_LEN =3D 0x0, + UFS_GEOMETRY_DESC_PARAM_TYPE =3D 0x1, + UFS_GEOMETRY_DESC_PARAM_DEV_CAP =3D 0x4, + UFS_GEOMETRY_DESC_PARAM_MAX_NUM_LUN =3D 0xC, + UFS_GEOMETRY_DESC_PARAM_SEG_SIZE =3D 0xD, + UFS_GEOMETRY_DESC_PARAM_ALLOC_UNIT_SIZE =3D 0x11, + UFS_GEOMETRY_DESC_PARAM_MIN_BLK_SIZE =3D 0x12, + UFS_GEOMETRY_DESC_PARAM_OPT_RD_BLK_SIZE =3D 0x13, + UFS_GEOMETRY_DESC_PARAM_OPT_WR_BLK_SIZE =3D 0x14, + UFS_GEOMETRY_DESC_PARAM_MAX_IN_BUF_SIZE =3D 0x15, + UFS_GEOMETRY_DESC_PARAM_MAX_OUT_BUF_SIZE =3D 0x16, + UFS_GEOMETRY_DESC_PARAM_RPMB_RW_SIZE =3D 0x17, + UFS_GEOMETRY_DESC_PARAM_DYN_CAP_RSRC_PLC =3D 0x18, + UFS_GEOMETRY_DESC_PARAM_DATA_ORDER =3D 0x19, + UFS_GEOMETRY_DESC_PARAM_MAX_NUM_CTX =3D 0x1A, + UFS_GEOMETRY_DESC_PARAM_TAG_UNIT_SIZE =3D 0x1B, + UFS_GEOMETRY_DESC_PARAM_TAG_RSRC_SIZE =3D 0x1C, + UFS_GEOMETRY_DESC_PARAM_SEC_RM_TYPES =3D 0x1D, + UFS_GEOMETRY_DESC_PARAM_MEM_TYPES =3D 0x1E, + UFS_GEOMETRY_DESC_PARAM_SCM_MAX_NUM_UNITS =3D 0x20, + UFS_GEOMETRY_DESC_PARAM_SCM_CAP_ADJ_FCTR =3D 0x24, + UFS_GEOMETRY_DESC_PARAM_NPM_MAX_NUM_UNITS =3D 0x26, + UFS_GEOMETRY_DESC_PARAM_NPM_CAP_ADJ_FCTR =3D 0x2A, + UFS_GEOMETRY_DESC_PARAM_ENM1_MAX_NUM_UNITS =3D 0x2C, + UFS_GEOMETRY_DESC_PARAM_ENM1_CAP_ADJ_FCTR =3D 0x30, + UFS_GEOMETRY_DESC_PARAM_ENM2_MAX_NUM_UNITS =3D 0x32, + UFS_GEOMETRY_DESC_PARAM_ENM2_CAP_ADJ_FCTR =3D 0x36, + UFS_GEOMETRY_DESC_PARAM_ENM3_MAX_NUM_UNITS =3D 0x38, + UFS_GEOMETRY_DESC_PARAM_ENM3_CAP_ADJ_FCTR =3D 0x3C, + UFS_GEOMETRY_DESC_PARAM_ENM4_MAX_NUM_UNITS =3D 0x3E, + UFS_GEOMETRY_DESC_PARAM_ENM4_CAP_ADJ_FCTR =3D 0x42, + UFS_GEOMETRY_DESC_PARAM_OPT_LOG_BLK_SIZE =3D 0x44, + UFS_GEOMETRY_DESC_PARAM_HPB_REGION_SIZE =3D 0x48, + UFS_GEOMETRY_DESC_PARAM_HPB_NUMBER_LU =3D 0x49, + UFS_GEOMETRY_DESC_PARAM_HPB_SUBREGION_SIZE =3D 0x4A, + UFS_GEOMETRY_DESC_PARAM_HPB_MAX_ACTIVE_REGS =3D 0x4B, + UFS_GEOMETRY_DESC_PARAM_WB_MAX_ALLOC_UNITS =3D 0x4F, + UFS_GEOMETRY_DESC_PARAM_WB_MAX_WB_LUNS =3D 0x53, + UFS_GEOMETRY_DESC_PARAM_WB_BUFF_CAP_ADJ =3D 0x54, + UFS_GEOMETRY_DESC_PARAM_WB_SUP_RED_TYPE =3D 0x55, + UFS_GEOMETRY_DESC_PARAM_WB_SUP_WB_TYPE =3D 0x56, +}; + +/* Health descriptor parameters offsets in bytes*/ +enum health_desc_param { + UFS_HEALTH_DESC_PARAM_LEN =3D 0x0, + UFS_HEALTH_DESC_PARAM_TYPE =3D 0x1, + UFS_HEALTH_DESC_PARAM_EOL_INFO =3D 0x2, + UFS_HEALTH_DESC_PARAM_LIFE_TIME_EST_A =3D 0x3, + UFS_HEALTH_DESC_PARAM_LIFE_TIME_EST_B =3D 0x4, +}; + +/* WriteBooster buffer mode */ +enum { + UFS_WB_BUF_MODE_LU_DEDICATED =3D 0x0, + UFS_WB_BUF_MODE_SHARED =3D 0x1, +}; + +/* + * Logical Unit Write Protect + * 00h: LU not write protected + * 01h: LU write protected when fPowerOnWPEn =3D1 + * 02h: LU permanently write protected when fPermanentWPEn =3D1 + */ +enum ufs_lu_wp_type { + UFS_LU_NO_WP =3D 0x00, + UFS_LU_POWER_ON_WP =3D 0x01, + UFS_LU_PERM_WP =3D 0x02, +}; + +/* UTP QUERY Transaction Specific Fields OpCode */ +enum query_opcode { + UFS_UPIU_QUERY_OPCODE_NOP =3D 0x0, + UFS_UPIU_QUERY_OPCODE_READ_DESC =3D 0x1, + UFS_UPIU_QUERY_OPCODE_WRITE_DESC =3D 0x2, + UFS_UPIU_QUERY_OPCODE_READ_ATTR =3D 0x3, + UFS_UPIU_QUERY_OPCODE_WRITE_ATTR =3D 0x4, + UFS_UPIU_QUERY_OPCODE_READ_FLAG =3D 0x5, + UFS_UPIU_QUERY_OPCODE_SET_FLAG =3D 0x6, + UFS_UPIU_QUERY_OPCODE_CLEAR_FLAG =3D 0x7, + UFS_UPIU_QUERY_OPCODE_TOGGLE_FLAG =3D 0x8, +}; + +/* Query response result code */ +typedef enum QueryRespCode { + UFS_QUERY_RESULT_SUCCESS =3D 0x00, + UFS_QUERY_RESULT_NOT_READABLE =3D 0xF6, + UFS_QUERY_RESULT_NOT_WRITEABLE =3D 0xF7, + UFS_QUERY_RESULT_ALREADY_WRITTEN =3D 0xF8, + UFS_QUERY_RESULT_INVALID_LENGTH =3D 0xF9, + UFS_QUERY_RESULT_INVALID_VALUE =3D 0xFA, + UFS_QUERY_RESULT_INVALID_SELECTOR =3D 0xFB, + UFS_QUERY_RESULT_INVALID_INDEX =3D 0xFC, + UFS_QUERY_RESULT_INVALID_IDN =3D 0xFD, + UFS_QUERY_RESULT_INVALID_OPCODE =3D 0xFE, + UFS_QUERY_RESULT_GENERAL_FAILURE =3D 0xFF, +} QueryRespCode; + +/* UTP Transfer Request Command Type (CT) */ +enum { + UFS_UPIU_COMMAND_SET_TYPE_SCSI =3D 0x0, + UFS_UPIU_COMMAND_SET_TYPE_UFS =3D 0x1, + UFS_UPIU_COMMAND_SET_TYPE_QUERY =3D 0x2, +}; + +/* Task management service response */ +enum { + UFS_UPIU_TASK_MANAGEMENT_FUNC_COMPL =3D 0x00, + UFS_UPIU_TASK_MANAGEMENT_FUNC_NOT_SUPPORTED =3D 0x04, + UFS_UPIU_TASK_MANAGEMENT_FUNC_SUCCEEDED =3D 0x08, + UFS_UPIU_TASK_MANAGEMENT_FUNC_FAILED =3D 0x05, + UFS_UPIU_INCORRECT_LOGICAL_UNIT_NO =3D 0x09, +}; + +/* UFS device power modes */ +enum ufs_dev_pwr_mode { + UFS_ACTIVE_PWR_MODE =3D 1, + UFS_SLEEP_PWR_MODE =3D 2, + UFS_POWERDOWN_PWR_MODE =3D 3, + UFS_DEEPSLEEP_PWR_MODE =3D 4, +}; + +/* + * struct UtpCmdRsp - Response UPIU structure + * @residual_transfer_count: Residual transfer count DW-3 + * @reserved: Reserved double words DW-4 to DW-7 + * @sense_data_len: Sense data length DW-8 U16 + * @sense_data: Sense data field DW-8 to DW-12 + */ +typedef struct QEMU_PACKED UtpCmdRsp { + uint32_t residual_transfer_count; + uint32_t reserved[4]; + uint16_t sense_data_len; + uint8_t sense_data[UFS_SENSE_SIZE]; +} UtpCmdRsp; + +/* + * struct UtpUpiuRsp - general upiu response structure + * @header: UPIU header structure DW-0 to DW-2 + * @sr: fields structure for scsi command DW-3 to DW-12 + * @qr: fields structure for query request DW-3 to DW-7 + */ +typedef struct QEMU_PACKED UtpUpiuRsp { + UtpUpiuHeader header; + union { + UtpCmdRsp sr; + UtpUpiuQuery qr; + }; +} UtpUpiuRsp; + +static inline void _ufs_check_size(void) +{ + QEMU_BUILD_BUG_ON(sizeof(UfsReg) !=3D 0x104); + QEMU_BUILD_BUG_ON(sizeof(DeviceDescriptor) !=3D 89); + QEMU_BUILD_BUG_ON(sizeof(GeometryDescriptor) !=3D 87); + QEMU_BUILD_BUG_ON(sizeof(UnitDescriptor) !=3D 45); + QEMU_BUILD_BUG_ON(sizeof(RpmbUnitDescriptor) !=3D 35); + QEMU_BUILD_BUG_ON(sizeof(PowerParametersDescriptor) !=3D 98); + QEMU_BUILD_BUG_ON(sizeof(InterconnectDescriptor) !=3D 6); + QEMU_BUILD_BUG_ON(sizeof(StringDescriptor) !=3D 254); + QEMU_BUILD_BUG_ON(sizeof(DeviceHealthDescriptor) !=3D 45); + QEMU_BUILD_BUG_ON(sizeof(Flags) !=3D 0x13); + QEMU_BUILD_BUG_ON(sizeof(UtpUpiuHeader) !=3D 12); + QEMU_BUILD_BUG_ON(sizeof(UtpUpiuQuery) !=3D 276); + QEMU_BUILD_BUG_ON(sizeof(UtpUpiuCmd) !=3D 20); + QEMU_BUILD_BUG_ON(sizeof(UtpUpiuReq) !=3D 288); + QEMU_BUILD_BUG_ON(sizeof(UfshcdSgEntry) !=3D 16); + QEMU_BUILD_BUG_ON(sizeof(RequestDescHeader) !=3D 16); + QEMU_BUILD_BUG_ON(sizeof(UtpTransferReqDesc) !=3D 32); + QEMU_BUILD_BUG_ON(sizeof(UtpTaskReqDesc) !=3D 80); + QEMU_BUILD_BUG_ON(sizeof(UtpCmdRsp) !=3D 40); + QEMU_BUILD_BUG_ON(sizeof(UtpUpiuRsp) !=3D 288); +} +#endif diff --git a/include/hw/pci/pci.h b/include/hw/pci/pci.h index abdc1ef103..b70a0b95ff 100644 --- a/include/hw/pci/pci.h +++ b/include/hw/pci/pci.h @@ -114,6 +114,7 @@ extern bool pci_available; #define PCI_DEVICE_ID_REDHAT_NVME 0x0010 #define PCI_DEVICE_ID_REDHAT_PVPANIC 0x0011 #define PCI_DEVICE_ID_REDHAT_ACPI_ERST 0x0012 +#define PCI_DEVICE_ID_REDHAT_UFS 0x0013 #define PCI_DEVICE_ID_REDHAT_QXL 0x0100 =20 #define FMT_PCIBUS PRIx64 diff --git a/include/hw/pci/pci_ids.h b/include/hw/pci/pci_ids.h index e4386ebb20..85469b9b53 100644 --- a/include/hw/pci/pci_ids.h +++ b/include/hw/pci/pci_ids.h @@ -26,6 +26,7 @@ #define PCI_CLASS_STORAGE_SATA 0x0106 #define PCI_CLASS_STORAGE_SAS 0x0107 #define PCI_CLASS_STORAGE_EXPRESS 0x0108 +#define PCI_CLASS_STORAGE_UFS 0x0109 #define PCI_CLASS_STORAGE_OTHER 0x0180 =20 #define PCI_BASE_CLASS_NETWORK 0x02 diff --git a/meson.build b/meson.build index 296f6ef1d2..013db9b1ae 100644 --- a/meson.build +++ b/meson.build @@ -3273,6 +3273,7 @@ if have_system 'hw/ssi', 'hw/timer', 'hw/tpm', + 'hw/ufs', 'hw/usb', 'hw/vfio', 'hw/virtio', --=20 2.34.1 From nobody Sun May 12 20:04:00 2024 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=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1693986372; cv=none; d=zohomail.com; s=zohoarc; b=ip1zeSTGAYooWhO+qKiuk5NUz2rKVhlUGh/fy7aVejvGR8HIg2x5NUNswv/B0VjttxWZain/kY9MBqUab+56MtVkU+T/y/XlD68Aj+X1q5Mekh0H8eWmA7Rjq8QMw05ru+Kqsv+x1SRAhvB47VGgNn3WcUnWa4mWlwuqtX5iYGY= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1693986372; h=Content-Transfer-Encoding:Cc:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To; bh=0QN0vNv5YP571mXompTgU084U6rpV/v4RKwdFFUWnXI=; b=mpDdZN+NHbxFy96wH5ZYmB4lLnncSfmap4AFQzLR7y4mPjvdohdUqJtKmJmjzDV5eoPcIEKR6nzLmH7cKGLQBecSNoECpmbUM5XSbR+H9X8iWyfhzwlP8JYAETSkyRnGX62+5B27jsMPPUct7Qu+1UtQsLAktYMlFY+PoQuVrvA= 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 lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1693986372600572.7901872459271; Wed, 6 Sep 2023 00:46:12 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1qdnDl-0007Zc-3X; Wed, 06 Sep 2023 03:45:05 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1qdnDi-0007Yc-82; Wed, 06 Sep 2023 03:45:02 -0400 Received: from mail-il1-x12f.google.com ([2607:f8b0:4864:20::12f]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1qdnDU-00050Q-KB; Wed, 06 Sep 2023 03:45:01 -0400 Received: by mail-il1-x12f.google.com with SMTP id e9e14a558f8ab-34e1ddc38c6so12276135ab.3; Wed, 06 Sep 2023 00:44:47 -0700 (PDT) Received: from jeuk-MS-7D42.. ([218.147.112.168]) by smtp.gmail.com with ESMTPSA id ck1-20020a17090afe0100b00262eb0d141esm10434901pjb.28.2023.09.06.00.44.42 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 06 Sep 2023 00:44:45 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20221208; t=1693986287; x=1694591087; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=0QN0vNv5YP571mXompTgU084U6rpV/v4RKwdFFUWnXI=; b=aL1/B2y4mzAf5T9OJhEH06x8N58kbbKmmniEnsvXdENJ4a4Xpcr35bP8vMGnE4Y0f4 Xz4CtHVaWmG1/UXftLcHs1QNc7WlDvfC6AGX81MAuOW619PocSSmDwZ6VB0SR2ZteDv4 2kGSv8lDBFIrR7iJzjBA7djLODPh7R2h9ic1hjrzSlz3Y2qadbHFzXq9JmSJDOtNYd/x 8G07Eu9o+TzGy647QSQxUHl4Y7p8JJ9aXYH5ZrsOffLwraxmWG+6K6amwTu2T6/VkHYN Hi7fV4vDDzLViRfY70Y1iXHZqPCmcGMTraGMjQWZnFyeKflzp6kUHzHt3VZc1Dy75zoF 5qQw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1693986287; x=1694591087; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=0QN0vNv5YP571mXompTgU084U6rpV/v4RKwdFFUWnXI=; b=FOIyBipQIlYD6svrbTKxMm+SbnMvlN/277X9M66ZNsC++nmbL61rI1Q5JxJ3XgKT6z nTGu3UkK5/uEO460MBfVyvleF/+aBPa79+ZK0QaoGTqNOc1tkAMtrF1QXXDsPalMe/Tg 17zwen3uZ30+pRX83AlzWG0PU+Y1PiRsJCSGc3iy1j8lPODMgcmtMGZ7Z+ZSp/odAln9 P1jmdrHG1z0ahpfCD1lEzzETNF3Wuln4bssdB1TZr0NjHK+TEi9ifbOtXKrfMrrrywF6 b8fByB5lVzweGEWGnIut+kvPu/nYgvvbtIVjYcO1QNi4TqqI8MX53gja29Qpocjz1fzJ qlCA== X-Gm-Message-State: AOJu0YxNsUijFAIpMDLReK5Cy3O6Lg3hhCLNpNDDirSbOyXuqoNnbM3z q4oCwnxmFZ84aPAGlpOd+EQ9nnLxOzYshQ== X-Google-Smtp-Source: AGHT+IEt6JzqiErRklA/PEhIVTrxfeUTwboLPF64eR9FbV0ldVAWplnwo1VIAuQCJr641vbtcT90cA== X-Received: by 2002:a92:dccc:0:b0:346:7c6d:c667 with SMTP id b12-20020a92dccc000000b003467c6dc667mr16193373ilr.13.1693986286332; Wed, 06 Sep 2023 00:44:46 -0700 (PDT) From: Jeuk Kim To: qemu-devel@nongnu.org Cc: jeuk20.kim@gmail.com, berrange@redhat.com, fam@euphon.net, hreitz@redhat.com, jeuk20.kim@samsung.com, k.jensen@samsung.com, kwolf@redhat.com, lvivier@redhat.com, marcandre.lureau@redhat.com, marcel.apfelbaum@gmail.com, mst@redhat.com, pbonzini@redhat.com, philmd@linaro.org, qemu-block@nongnu.org, stefanha@redhat.com, thuth@redhat.com Subject: [PATCH v10 2/4] hw/ufs: Support for Query Transfer Requests Date: Wed, 6 Sep 2023 16:43:49 +0900 Message-Id: X-Mailer: git-send-email 2.34.1 In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable 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=lists.gnu.org; Received-SPF: pass client-ip=2607:f8b0:4864:20::12f; envelope-from=jeuk20.kim@gmail.com; helo=mail-il1-x12f.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, SPF_HELO_NONE=0.001, SPF_PASS=-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: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1693986374030100003 Content-Type: text/plain; charset="utf-8" From: Jeuk Kim This commit makes the UFS device support query and nop out transfer requests. The next patch would be support for UFS logical unit and scsi command transfer request. Signed-off-by: Jeuk Kim Reviewed-by: Stefan Hajnoczi --- hw/ufs/trace-events | 1 + hw/ufs/ufs.c | 988 +++++++++++++++++++++++++++++++++++++++++++- hw/ufs/ufs.h | 46 +++ 3 files changed, 1033 insertions(+), 2 deletions(-) diff --git a/hw/ufs/trace-events b/hw/ufs/trace-events index d1badcad10..665e1a942b 100644 --- a/hw/ufs/trace-events +++ b/hw/ufs/trace-events @@ -18,6 +18,7 @@ ufs_err_dma_read_req_upiu(uint32_t slot, uint64_t addr) "= failed to read req upiu ufs_err_dma_read_prdt(uint32_t slot, uint64_t addr) "failed to read prdt. = UTRLDBR slot %"PRIu32", prdt addr %"PRIu64"" ufs_err_dma_write_utrd(uint32_t slot, uint64_t addr) "failed to write utrd= . UTRLDBR slot %"PRIu32", UTRD dma addr %"PRIu64"" ufs_err_dma_write_rsp_upiu(uint32_t slot, uint64_t addr) "failed to write = rsp upiu. UTRLDBR slot %"PRIu32", response upiu addr %"PRIu64"" +ufs_err_utrl_slot_error(uint32_t slot) "UTRLDBR slot %"PRIu32" is in error" ufs_err_utrl_slot_busy(uint32_t slot) "UTRLDBR slot %"PRIu32" is busy" ufs_err_unsupport_register_offset(uint32_t offset) "Register offset 0x%"PR= Ix32" is not yet supported" ufs_err_invalid_register_offset(uint32_t offset) "Register offset 0x%"PRIx= 32" is invalid" diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c index df87f2a6d5..56a8ec286b 100644 --- a/hw/ufs/ufs.c +++ b/hw/ufs/ufs.c @@ -15,10 +15,221 @@ #include "ufs.h" =20 /* The QEMU-UFS device follows spec version 3.1 */ -#define UFS_SPEC_VER 0x00000310 +#define UFS_SPEC_VER 0x0310 #define UFS_MAX_NUTRS 32 #define UFS_MAX_NUTMRS 8 =20 +static MemTxResult ufs_addr_read(UfsHc *u, hwaddr addr, void *buf, int siz= e) +{ + hwaddr hi =3D addr + size - 1; + + if (hi < addr) { + return MEMTX_DECODE_ERROR; + } + + if (!FIELD_EX32(u->reg.cap, CAP, 64AS) && (hi >> 32)) { + return MEMTX_DECODE_ERROR; + } + + return pci_dma_read(PCI_DEVICE(u), addr, buf, size); +} + +static MemTxResult ufs_addr_write(UfsHc *u, hwaddr addr, const void *buf, + int size) +{ + hwaddr hi =3D addr + size - 1; + if (hi < addr) { + return MEMTX_DECODE_ERROR; + } + + if (!FIELD_EX32(u->reg.cap, CAP, 64AS) && (hi >> 32)) { + return MEMTX_DECODE_ERROR; + } + + return pci_dma_write(PCI_DEVICE(u), addr, buf, size); +} + +static void ufs_complete_req(UfsRequest *req, UfsReqResult req_result); + +static inline hwaddr ufs_get_utrd_addr(UfsHc *u, uint32_t slot) +{ + hwaddr utrl_base_addr =3D (((hwaddr)u->reg.utrlbau) << 32) + u->reg.ut= rlba; + hwaddr utrd_addr =3D utrl_base_addr + slot * sizeof(UtpTransferReqDesc= ); + + return utrd_addr; +} + +static inline hwaddr ufs_get_req_upiu_base_addr(const UtpTransferReqDesc *= utrd) +{ + uint32_t cmd_desc_base_addr_lo =3D + le32_to_cpu(utrd->command_desc_base_addr_lo); + uint32_t cmd_desc_base_addr_hi =3D + le32_to_cpu(utrd->command_desc_base_addr_hi); + + return (((hwaddr)cmd_desc_base_addr_hi) << 32) + cmd_desc_base_addr_lo; +} + +static inline hwaddr ufs_get_rsp_upiu_base_addr(const UtpTransferReqDesc *= utrd) +{ + hwaddr req_upiu_base_addr =3D ufs_get_req_upiu_base_addr(utrd); + uint32_t rsp_upiu_byte_off =3D + le16_to_cpu(utrd->response_upiu_offset) * sizeof(uint32_t); + return req_upiu_base_addr + rsp_upiu_byte_off; +} + +static MemTxResult ufs_dma_read_utrd(UfsRequest *req) +{ + UfsHc *u =3D req->hc; + hwaddr utrd_addr =3D ufs_get_utrd_addr(u, req->slot); + MemTxResult ret; + + ret =3D ufs_addr_read(u, utrd_addr, &req->utrd, sizeof(req->utrd)); + if (ret) { + trace_ufs_err_dma_read_utrd(req->slot, utrd_addr); + } + return ret; +} + +static MemTxResult ufs_dma_read_req_upiu(UfsRequest *req) +{ + UfsHc *u =3D req->hc; + hwaddr req_upiu_base_addr =3D ufs_get_req_upiu_base_addr(&req->utrd); + UtpUpiuReq *req_upiu =3D &req->req_upiu; + uint32_t copy_size; + uint16_t data_segment_length; + MemTxResult ret; + + /* + * To know the size of the req_upiu, we need to read the + * data_segment_length in the header first. + */ + ret =3D ufs_addr_read(u, req_upiu_base_addr, &req_upiu->header, + sizeof(UtpUpiuHeader)); + if (ret) { + trace_ufs_err_dma_read_req_upiu(req->slot, req_upiu_base_addr); + return ret; + } + data_segment_length =3D be16_to_cpu(req_upiu->header.data_segment_leng= th); + + copy_size =3D sizeof(UtpUpiuHeader) + UFS_TRANSACTION_SPECIFIC_FIELD_S= IZE + + data_segment_length; + + ret =3D ufs_addr_read(u, req_upiu_base_addr, &req->req_upiu, copy_size= ); + if (ret) { + trace_ufs_err_dma_read_req_upiu(req->slot, req_upiu_base_addr); + } + return ret; +} + +static MemTxResult ufs_dma_read_prdt(UfsRequest *req) +{ + UfsHc *u =3D req->hc; + uint16_t prdt_len =3D le16_to_cpu(req->utrd.prd_table_length); + uint16_t prdt_byte_off =3D + le16_to_cpu(req->utrd.prd_table_offset) * sizeof(uint32_t); + uint32_t prdt_size =3D prdt_len * sizeof(UfshcdSgEntry); + g_autofree UfshcdSgEntry *prd_entries =3D NULL; + hwaddr req_upiu_base_addr, prdt_base_addr; + int err; + + assert(!req->sg); + + if (prdt_size =3D=3D 0) { + return MEMTX_OK; + } + prd_entries =3D g_new(UfshcdSgEntry, prdt_size); + + req_upiu_base_addr =3D ufs_get_req_upiu_base_addr(&req->utrd); + prdt_base_addr =3D req_upiu_base_addr + prdt_byte_off; + + err =3D ufs_addr_read(u, prdt_base_addr, prd_entries, prdt_size); + if (err) { + trace_ufs_err_dma_read_prdt(req->slot, prdt_base_addr); + return err; + } + + req->sg =3D g_malloc0(sizeof(QEMUSGList)); + pci_dma_sglist_init(req->sg, PCI_DEVICE(u), prdt_len); + + for (uint16_t i =3D 0; i < prdt_len; ++i) { + hwaddr data_dma_addr =3D le64_to_cpu(prd_entries[i].addr); + uint32_t data_byte_count =3D le32_to_cpu(prd_entries[i].size) + 1; + qemu_sglist_add(req->sg, data_dma_addr, data_byte_count); + } + return MEMTX_OK; +} + +static MemTxResult ufs_dma_read_upiu(UfsRequest *req) +{ + MemTxResult ret; + + ret =3D ufs_dma_read_utrd(req); + if (ret) { + return ret; + } + + ret =3D ufs_dma_read_req_upiu(req); + if (ret) { + return ret; + } + + ret =3D ufs_dma_read_prdt(req); + if (ret) { + return ret; + } + + return 0; +} + +static MemTxResult ufs_dma_write_utrd(UfsRequest *req) +{ + UfsHc *u =3D req->hc; + hwaddr utrd_addr =3D ufs_get_utrd_addr(u, req->slot); + MemTxResult ret; + + ret =3D ufs_addr_write(u, utrd_addr, &req->utrd, sizeof(req->utrd)); + if (ret) { + trace_ufs_err_dma_write_utrd(req->slot, utrd_addr); + } + return ret; +} + +static MemTxResult ufs_dma_write_rsp_upiu(UfsRequest *req) +{ + UfsHc *u =3D req->hc; + hwaddr rsp_upiu_base_addr =3D ufs_get_rsp_upiu_base_addr(&req->utrd); + uint32_t rsp_upiu_byte_len =3D + le16_to_cpu(req->utrd.response_upiu_length) * sizeof(uint32_t); + uint16_t data_segment_length =3D + be16_to_cpu(req->rsp_upiu.header.data_segment_length); + uint32_t copy_size =3D sizeof(UtpUpiuHeader) + + UFS_TRANSACTION_SPECIFIC_FIELD_SIZE + + data_segment_length; + MemTxResult ret; + + if (copy_size > rsp_upiu_byte_len) { + copy_size =3D rsp_upiu_byte_len; + } + + ret =3D ufs_addr_write(u, rsp_upiu_base_addr, &req->rsp_upiu, copy_siz= e); + if (ret) { + trace_ufs_err_dma_write_rsp_upiu(req->slot, rsp_upiu_base_addr); + } + return ret; +} + +static MemTxResult ufs_dma_write_upiu(UfsRequest *req) +{ + MemTxResult ret; + + ret =3D ufs_dma_write_rsp_upiu(req); + if (ret) { + return ret; + } + + return ufs_dma_write_utrd(req); +} + static void ufs_irq_check(UfsHc *u) { PCIDevice *pci =3D PCI_DEVICE(u); @@ -32,6 +243,41 @@ static void ufs_irq_check(UfsHc *u) } } =20 +static void ufs_process_db(UfsHc *u, uint32_t val) +{ + unsigned long doorbell; + uint32_t slot; + uint32_t nutrs =3D u->params.nutrs; + UfsRequest *req; + + val &=3D ~u->reg.utrldbr; + if (!val) { + return; + } + + doorbell =3D val; + slot =3D find_first_bit(&doorbell, nutrs); + + while (slot < nutrs) { + req =3D &u->req_list[slot]; + if (req->state =3D=3D UFS_REQUEST_ERROR) { + trace_ufs_err_utrl_slot_error(req->slot); + return; + } + + if (req->state !=3D UFS_REQUEST_IDLE) { + trace_ufs_err_utrl_slot_busy(req->slot); + return; + } + + trace_ufs_process_db(slot); + req->state =3D UFS_REQUEST_READY; + slot =3D find_next_bit(&doorbell, nutrs, slot + 1); + } + + qemu_bh_schedule(u->doorbell_bh); +} + static void ufs_process_uiccmd(UfsHc *u, uint32_t val) { trace_ufs_process_uiccmd(val, u->reg.ucmdarg1, u->reg.ucmdarg2, @@ -95,7 +341,8 @@ static void ufs_write_reg(UfsHc *u, hwaddr offset, uint3= 2_t data, unsigned size) u->reg.utrlbau =3D data; break; case A_UTRLDBR: - /* Not yet supported */ + ufs_process_db(u, data); + u->reg.utrldbr |=3D data; break; case A_UTRLRSR: u->reg.utrlrsr =3D data; @@ -173,6 +420,665 @@ static const MemoryRegionOps ufs_mmio_ops =3D { }, }; =20 +static void ufs_build_upiu_header(UfsRequest *req, uint8_t trans_type, + uint8_t flags, uint8_t response, + uint8_t scsi_status, + uint16_t data_segment_length) +{ + memcpy(&req->rsp_upiu.header, &req->req_upiu.header, sizeof(UtpUpiuHea= der)); + req->rsp_upiu.header.trans_type =3D trans_type; + req->rsp_upiu.header.flags =3D flags; + req->rsp_upiu.header.response =3D response; + req->rsp_upiu.header.scsi_status =3D scsi_status; + req->rsp_upiu.header.data_segment_length =3D cpu_to_be16(data_segment_= length); +} + +static UfsReqResult ufs_exec_nop_cmd(UfsRequest *req) +{ + trace_ufs_exec_nop_cmd(req->slot); + ufs_build_upiu_header(req, UFS_UPIU_TRANSACTION_NOP_IN, 0, 0, 0, 0); + return UFS_REQUEST_SUCCESS; +} + +/* + * This defines the permission of flags based on their IDN. There are some + * things that are declared read-only, which is inconsistent with the ufs = spec, + * because we want to return an error for features that are not yet suppor= ted. + */ +static const int flag_permission[UFS_QUERY_FLAG_IDN_COUNT] =3D { + [UFS_QUERY_FLAG_IDN_FDEVICEINIT] =3D UFS_QUERY_FLAG_READ | UFS_QUERY_F= LAG_SET, + /* Write protection is not supported */ + [UFS_QUERY_FLAG_IDN_PERMANENT_WPE] =3D UFS_QUERY_FLAG_READ, + [UFS_QUERY_FLAG_IDN_PWR_ON_WPE] =3D UFS_QUERY_FLAG_READ, + [UFS_QUERY_FLAG_IDN_BKOPS_EN] =3D UFS_QUERY_FLAG_READ | UFS_QUERY_FLAG= _SET | + UFS_QUERY_FLAG_CLEAR | + UFS_QUERY_FLAG_TOGGLE, + [UFS_QUERY_FLAG_IDN_LIFE_SPAN_MODE_ENABLE] =3D + UFS_QUERY_FLAG_READ | UFS_QUERY_FLAG_SET | UFS_QUERY_FLAG_CLEAR | + UFS_QUERY_FLAG_TOGGLE, + /* Purge Operation is not supported */ + [UFS_QUERY_FLAG_IDN_PURGE_ENABLE] =3D UFS_QUERY_FLAG_NONE, + /* Refresh Operation is not supported */ + [UFS_QUERY_FLAG_IDN_REFRESH_ENABLE] =3D UFS_QUERY_FLAG_NONE, + /* Physical Resource Removal is not supported */ + [UFS_QUERY_FLAG_IDN_FPHYRESOURCEREMOVAL] =3D UFS_QUERY_FLAG_READ, + [UFS_QUERY_FLAG_IDN_BUSY_RTC] =3D UFS_QUERY_FLAG_READ, + [UFS_QUERY_FLAG_IDN_PERMANENTLY_DISABLE_FW_UPDATE] =3D UFS_QUERY_FLAG_= READ, + /* Write Booster is not supported */ + [UFS_QUERY_FLAG_IDN_WB_EN] =3D UFS_QUERY_FLAG_READ, + [UFS_QUERY_FLAG_IDN_WB_BUFF_FLUSH_EN] =3D UFS_QUERY_FLAG_READ, + [UFS_QUERY_FLAG_IDN_WB_BUFF_FLUSH_DURING_HIBERN8] =3D UFS_QUERY_FLAG_R= EAD, +}; + +static inline QueryRespCode ufs_flag_check_idn_valid(uint8_t idn, int op) +{ + if (idn >=3D UFS_QUERY_FLAG_IDN_COUNT) { + return UFS_QUERY_RESULT_INVALID_IDN; + } + + if (!(flag_permission[idn] & op)) { + if (op =3D=3D UFS_QUERY_FLAG_READ) { + trace_ufs_err_query_flag_not_readable(idn); + return UFS_QUERY_RESULT_NOT_READABLE; + } + trace_ufs_err_query_flag_not_writable(idn); + return UFS_QUERY_RESULT_NOT_WRITEABLE; + } + + return UFS_QUERY_RESULT_SUCCESS; +} + +static const int attr_permission[UFS_QUERY_ATTR_IDN_COUNT] =3D { + /* booting is not supported */ + [UFS_QUERY_ATTR_IDN_BOOT_LU_EN] =3D UFS_QUERY_ATTR_READ, + [UFS_QUERY_ATTR_IDN_POWER_MODE] =3D UFS_QUERY_ATTR_READ, + [UFS_QUERY_ATTR_IDN_ACTIVE_ICC_LVL] =3D + UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, + [UFS_QUERY_ATTR_IDN_OOO_DATA_EN] =3D UFS_QUERY_ATTR_READ, + [UFS_QUERY_ATTR_IDN_BKOPS_STATUS] =3D UFS_QUERY_ATTR_READ, + [UFS_QUERY_ATTR_IDN_PURGE_STATUS] =3D UFS_QUERY_ATTR_READ, + [UFS_QUERY_ATTR_IDN_MAX_DATA_IN] =3D + UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, + [UFS_QUERY_ATTR_IDN_MAX_DATA_OUT] =3D + UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, + [UFS_QUERY_ATTR_IDN_DYN_CAP_NEEDED] =3D UFS_QUERY_ATTR_READ, + [UFS_QUERY_ATTR_IDN_REF_CLK_FREQ] =3D + UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, + [UFS_QUERY_ATTR_IDN_CONF_DESC_LOCK] =3D UFS_QUERY_ATTR_READ, + [UFS_QUERY_ATTR_IDN_MAX_NUM_OF_RTT] =3D + UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, + [UFS_QUERY_ATTR_IDN_EE_CONTROL] =3D + UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, + [UFS_QUERY_ATTR_IDN_EE_STATUS] =3D UFS_QUERY_ATTR_READ, + [UFS_QUERY_ATTR_IDN_SECONDS_PASSED] =3D UFS_QUERY_ATTR_WRITE, + [UFS_QUERY_ATTR_IDN_CNTX_CONF] =3D UFS_QUERY_ATTR_READ, + [UFS_QUERY_ATTR_IDN_FFU_STATUS] =3D UFS_QUERY_ATTR_READ, + [UFS_QUERY_ATTR_IDN_PSA_STATE] =3D UFS_QUERY_ATTR_READ | UFS_QUERY_ATT= R_WRITE, + [UFS_QUERY_ATTR_IDN_PSA_DATA_SIZE] =3D + UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, + [UFS_QUERY_ATTR_IDN_REF_CLK_GATING_WAIT_TIME] =3D UFS_QUERY_ATTR_READ, + [UFS_QUERY_ATTR_IDN_CASE_ROUGH_TEMP] =3D UFS_QUERY_ATTR_READ, + [UFS_QUERY_ATTR_IDN_HIGH_TEMP_BOUND] =3D UFS_QUERY_ATTR_READ, + [UFS_QUERY_ATTR_IDN_LOW_TEMP_BOUND] =3D UFS_QUERY_ATTR_READ, + [UFS_QUERY_ATTR_IDN_THROTTLING_STATUS] =3D UFS_QUERY_ATTR_READ, + [UFS_QUERY_ATTR_IDN_WB_FLUSH_STATUS] =3D UFS_QUERY_ATTR_READ, + [UFS_QUERY_ATTR_IDN_AVAIL_WB_BUFF_SIZE] =3D UFS_QUERY_ATTR_READ, + [UFS_QUERY_ATTR_IDN_WB_BUFF_LIFE_TIME_EST] =3D UFS_QUERY_ATTR_READ, + [UFS_QUERY_ATTR_IDN_CURR_WB_BUFF_SIZE] =3D UFS_QUERY_ATTR_READ, + /* refresh operation is not supported */ + [UFS_QUERY_ATTR_IDN_REFRESH_STATUS] =3D UFS_QUERY_ATTR_READ, + [UFS_QUERY_ATTR_IDN_REFRESH_FREQ] =3D UFS_QUERY_ATTR_READ, + [UFS_QUERY_ATTR_IDN_REFRESH_UNIT] =3D UFS_QUERY_ATTR_READ, +}; + +static inline QueryRespCode ufs_attr_check_idn_valid(uint8_t idn, int op) +{ + if (idn >=3D UFS_QUERY_ATTR_IDN_COUNT) { + return UFS_QUERY_RESULT_INVALID_IDN; + } + + if (!(attr_permission[idn] & op)) { + if (op =3D=3D UFS_QUERY_ATTR_READ) { + trace_ufs_err_query_attr_not_readable(idn); + return UFS_QUERY_RESULT_NOT_READABLE; + } + trace_ufs_err_query_attr_not_writable(idn); + return UFS_QUERY_RESULT_NOT_WRITEABLE; + } + + return UFS_QUERY_RESULT_SUCCESS; +} + +static QueryRespCode ufs_exec_query_flag(UfsRequest *req, int op) +{ + UfsHc *u =3D req->hc; + uint8_t idn =3D req->req_upiu.qr.idn; + uint32_t value; + QueryRespCode ret; + + ret =3D ufs_flag_check_idn_valid(idn, op); + if (ret) { + return ret; + } + + if (idn =3D=3D UFS_QUERY_FLAG_IDN_FDEVICEINIT) { + value =3D 0; + } else if (op =3D=3D UFS_QUERY_FLAG_READ) { + value =3D *(((uint8_t *)&u->flags) + idn); + } else if (op =3D=3D UFS_QUERY_FLAG_SET) { + value =3D 1; + } else if (op =3D=3D UFS_QUERY_FLAG_CLEAR) { + value =3D 0; + } else if (op =3D=3D UFS_QUERY_FLAG_TOGGLE) { + value =3D *(((uint8_t *)&u->flags) + idn); + value =3D !value; + } else { + trace_ufs_err_query_invalid_opcode(op); + return UFS_QUERY_RESULT_INVALID_OPCODE; + } + + *(((uint8_t *)&u->flags) + idn) =3D value; + req->rsp_upiu.qr.value =3D cpu_to_be32(value); + return UFS_QUERY_RESULT_SUCCESS; +} + +static uint32_t ufs_read_attr_value(UfsHc *u, uint8_t idn) +{ + switch (idn) { + case UFS_QUERY_ATTR_IDN_BOOT_LU_EN: + return u->attributes.boot_lun_en; + case UFS_QUERY_ATTR_IDN_POWER_MODE: + return u->attributes.current_power_mode; + case UFS_QUERY_ATTR_IDN_ACTIVE_ICC_LVL: + return u->attributes.active_icc_level; + case UFS_QUERY_ATTR_IDN_OOO_DATA_EN: + return u->attributes.out_of_order_data_en; + case UFS_QUERY_ATTR_IDN_BKOPS_STATUS: + return u->attributes.background_op_status; + case UFS_QUERY_ATTR_IDN_PURGE_STATUS: + return u->attributes.purge_status; + case UFS_QUERY_ATTR_IDN_MAX_DATA_IN: + return u->attributes.max_data_in_size; + case UFS_QUERY_ATTR_IDN_MAX_DATA_OUT: + return u->attributes.max_data_out_size; + case UFS_QUERY_ATTR_IDN_DYN_CAP_NEEDED: + return be32_to_cpu(u->attributes.dyn_cap_needed); + case UFS_QUERY_ATTR_IDN_REF_CLK_FREQ: + return u->attributes.ref_clk_freq; + case UFS_QUERY_ATTR_IDN_CONF_DESC_LOCK: + return u->attributes.config_descr_lock; + case UFS_QUERY_ATTR_IDN_MAX_NUM_OF_RTT: + return u->attributes.max_num_of_rtt; + case UFS_QUERY_ATTR_IDN_EE_CONTROL: + return be16_to_cpu(u->attributes.exception_event_control); + case UFS_QUERY_ATTR_IDN_EE_STATUS: + return be16_to_cpu(u->attributes.exception_event_status); + case UFS_QUERY_ATTR_IDN_SECONDS_PASSED: + return be32_to_cpu(u->attributes.seconds_passed); + case UFS_QUERY_ATTR_IDN_CNTX_CONF: + return be16_to_cpu(u->attributes.context_conf); + case UFS_QUERY_ATTR_IDN_FFU_STATUS: + return u->attributes.device_ffu_status; + case UFS_QUERY_ATTR_IDN_PSA_STATE: + return be32_to_cpu(u->attributes.psa_state); + case UFS_QUERY_ATTR_IDN_PSA_DATA_SIZE: + return be32_to_cpu(u->attributes.psa_data_size); + case UFS_QUERY_ATTR_IDN_REF_CLK_GATING_WAIT_TIME: + return u->attributes.ref_clk_gating_wait_time; + case UFS_QUERY_ATTR_IDN_CASE_ROUGH_TEMP: + return u->attributes.device_case_rough_temperaure; + case UFS_QUERY_ATTR_IDN_HIGH_TEMP_BOUND: + return u->attributes.device_too_high_temp_boundary; + case UFS_QUERY_ATTR_IDN_LOW_TEMP_BOUND: + return u->attributes.device_too_low_temp_boundary; + case UFS_QUERY_ATTR_IDN_THROTTLING_STATUS: + return u->attributes.throttling_status; + case UFS_QUERY_ATTR_IDN_WB_FLUSH_STATUS: + return u->attributes.wb_buffer_flush_status; + case UFS_QUERY_ATTR_IDN_AVAIL_WB_BUFF_SIZE: + return u->attributes.available_wb_buffer_size; + case UFS_QUERY_ATTR_IDN_WB_BUFF_LIFE_TIME_EST: + return u->attributes.wb_buffer_life_time_est; + case UFS_QUERY_ATTR_IDN_CURR_WB_BUFF_SIZE: + return be32_to_cpu(u->attributes.current_wb_buffer_size); + case UFS_QUERY_ATTR_IDN_REFRESH_STATUS: + return u->attributes.refresh_status; + case UFS_QUERY_ATTR_IDN_REFRESH_FREQ: + return u->attributes.refresh_freq; + case UFS_QUERY_ATTR_IDN_REFRESH_UNIT: + return u->attributes.refresh_unit; + } + return 0; +} + +static void ufs_write_attr_value(UfsHc *u, uint8_t idn, uint32_t value) +{ + switch (idn) { + case UFS_QUERY_ATTR_IDN_ACTIVE_ICC_LVL: + u->attributes.active_icc_level =3D value; + break; + case UFS_QUERY_ATTR_IDN_MAX_DATA_IN: + u->attributes.max_data_in_size =3D value; + break; + case UFS_QUERY_ATTR_IDN_MAX_DATA_OUT: + u->attributes.max_data_out_size =3D value; + break; + case UFS_QUERY_ATTR_IDN_REF_CLK_FREQ: + u->attributes.ref_clk_freq =3D value; + break; + case UFS_QUERY_ATTR_IDN_MAX_NUM_OF_RTT: + u->attributes.max_num_of_rtt =3D value; + break; + case UFS_QUERY_ATTR_IDN_EE_CONTROL: + u->attributes.exception_event_control =3D cpu_to_be16(value); + break; + case UFS_QUERY_ATTR_IDN_SECONDS_PASSED: + u->attributes.seconds_passed =3D cpu_to_be32(value); + break; + case UFS_QUERY_ATTR_IDN_PSA_STATE: + u->attributes.psa_state =3D value; + break; + case UFS_QUERY_ATTR_IDN_PSA_DATA_SIZE: + u->attributes.psa_data_size =3D cpu_to_be32(value); + break; + } +} + +static QueryRespCode ufs_exec_query_attr(UfsRequest *req, int op) +{ + UfsHc *u =3D req->hc; + uint8_t idn =3D req->req_upiu.qr.idn; + uint32_t value; + QueryRespCode ret; + + ret =3D ufs_attr_check_idn_valid(idn, op); + if (ret) { + return ret; + } + + if (op =3D=3D UFS_QUERY_ATTR_READ) { + value =3D ufs_read_attr_value(u, idn); + } else { + value =3D be32_to_cpu(req->req_upiu.qr.value); + ufs_write_attr_value(u, idn, value); + } + + req->rsp_upiu.qr.value =3D cpu_to_be32(value); + return UFS_QUERY_RESULT_SUCCESS; +} + +static const RpmbUnitDescriptor rpmb_unit_desc =3D { + .length =3D sizeof(RpmbUnitDescriptor), + .descriptor_idn =3D 2, + .unit_index =3D UFS_UPIU_RPMB_WLUN, + .lu_enable =3D 0, +}; + +static QueryRespCode ufs_read_unit_desc(UfsRequest *req) +{ + uint8_t lun =3D req->req_upiu.qr.index; + + if (lun !=3D UFS_UPIU_RPMB_WLUN && lun > UFS_MAX_LUS) { + trace_ufs_err_query_invalid_index(req->req_upiu.qr.opcode, lun); + return UFS_QUERY_RESULT_INVALID_INDEX; + } + + if (lun =3D=3D UFS_UPIU_RPMB_WLUN) { + memcpy(&req->rsp_upiu.qr.data, &rpmb_unit_desc, rpmb_unit_desc.len= gth); + } else { + /* unit descriptor is not yet supported */ + return UFS_QUERY_RESULT_INVALID_INDEX; + } + + return UFS_QUERY_RESULT_SUCCESS; +} + +static inline StringDescriptor manufacturer_str_desc(void) +{ + StringDescriptor desc =3D { + .length =3D 0x12, + .descriptor_idn =3D UFS_QUERY_DESC_IDN_STRING, + }; + desc.UC[0] =3D cpu_to_be16('R'); + desc.UC[1] =3D cpu_to_be16('E'); + desc.UC[2] =3D cpu_to_be16('D'); + desc.UC[3] =3D cpu_to_be16('H'); + desc.UC[4] =3D cpu_to_be16('A'); + desc.UC[5] =3D cpu_to_be16('T'); + return desc; +} + +static inline StringDescriptor product_name_str_desc(void) +{ + StringDescriptor desc =3D { + .length =3D 0x22, + .descriptor_idn =3D UFS_QUERY_DESC_IDN_STRING, + }; + desc.UC[0] =3D cpu_to_be16('Q'); + desc.UC[1] =3D cpu_to_be16('E'); + desc.UC[2] =3D cpu_to_be16('M'); + desc.UC[3] =3D cpu_to_be16('U'); + desc.UC[4] =3D cpu_to_be16(' '); + desc.UC[5] =3D cpu_to_be16('U'); + desc.UC[6] =3D cpu_to_be16('F'); + desc.UC[7] =3D cpu_to_be16('S'); + return desc; +} + +static inline StringDescriptor product_rev_level_str_desc(void) +{ + StringDescriptor desc =3D { + .length =3D 0x0a, + .descriptor_idn =3D UFS_QUERY_DESC_IDN_STRING, + }; + desc.UC[0] =3D cpu_to_be16('0'); + desc.UC[1] =3D cpu_to_be16('0'); + desc.UC[2] =3D cpu_to_be16('0'); + desc.UC[3] =3D cpu_to_be16('1'); + return desc; +} + +static const StringDescriptor null_str_desc =3D { + .length =3D 0x02, + .descriptor_idn =3D UFS_QUERY_DESC_IDN_STRING, +}; + +static QueryRespCode ufs_read_string_desc(UfsRequest *req) +{ + UfsHc *u =3D req->hc; + uint8_t index =3D req->req_upiu.qr.index; + StringDescriptor desc; + + if (index =3D=3D u->device_desc.manufacturer_name) { + desc =3D manufacturer_str_desc(); + memcpy(&req->rsp_upiu.qr.data, &desc, desc.length); + } else if (index =3D=3D u->device_desc.product_name) { + desc =3D product_name_str_desc(); + memcpy(&req->rsp_upiu.qr.data, &desc, desc.length); + } else if (index =3D=3D u->device_desc.serial_number) { + memcpy(&req->rsp_upiu.qr.data, &null_str_desc, null_str_desc.lengt= h); + } else if (index =3D=3D u->device_desc.oem_id) { + memcpy(&req->rsp_upiu.qr.data, &null_str_desc, null_str_desc.lengt= h); + } else if (index =3D=3D u->device_desc.product_revision_level) { + desc =3D product_rev_level_str_desc(); + memcpy(&req->rsp_upiu.qr.data, &desc, desc.length); + } else { + trace_ufs_err_query_invalid_index(req->req_upiu.qr.opcode, index); + return UFS_QUERY_RESULT_INVALID_INDEX; + } + return UFS_QUERY_RESULT_SUCCESS; +} + +static inline InterconnectDescriptor interconnect_desc(void) +{ + InterconnectDescriptor desc =3D { + .length =3D sizeof(InterconnectDescriptor), + .descriptor_idn =3D UFS_QUERY_DESC_IDN_INTERCONNECT, + }; + desc.bcd_unipro_version =3D cpu_to_be16(0x180); + desc.bcd_mphy_version =3D cpu_to_be16(0x410); + return desc; +} + +static QueryRespCode ufs_read_desc(UfsRequest *req) +{ + UfsHc *u =3D req->hc; + QueryRespCode status; + uint8_t idn =3D req->req_upiu.qr.idn; + uint16_t length =3D be16_to_cpu(req->req_upiu.qr.length); + InterconnectDescriptor desc; + + switch (idn) { + case UFS_QUERY_DESC_IDN_DEVICE: + memcpy(&req->rsp_upiu.qr.data, &u->device_desc, sizeof(u->device_d= esc)); + status =3D UFS_QUERY_RESULT_SUCCESS; + break; + case UFS_QUERY_DESC_IDN_UNIT: + status =3D ufs_read_unit_desc(req); + break; + case UFS_QUERY_DESC_IDN_GEOMETRY: + memcpy(&req->rsp_upiu.qr.data, &u->geometry_desc, + sizeof(u->geometry_desc)); + status =3D UFS_QUERY_RESULT_SUCCESS; + break; + case UFS_QUERY_DESC_IDN_INTERCONNECT: { + desc =3D interconnect_desc(); + memcpy(&req->rsp_upiu.qr.data, &desc, sizeof(InterconnectDescripto= r)); + status =3D UFS_QUERY_RESULT_SUCCESS; + break; + } + case UFS_QUERY_DESC_IDN_STRING: + status =3D ufs_read_string_desc(req); + break; + case UFS_QUERY_DESC_IDN_POWER: + /* mocking of power descriptor is not supported */ + memset(&req->rsp_upiu.qr.data, 0, sizeof(PowerParametersDescriptor= )); + req->rsp_upiu.qr.data[0] =3D sizeof(PowerParametersDescriptor); + req->rsp_upiu.qr.data[1] =3D UFS_QUERY_DESC_IDN_POWER; + status =3D UFS_QUERY_RESULT_SUCCESS; + break; + case UFS_QUERY_DESC_IDN_HEALTH: + /* mocking of health descriptor is not supported */ + memset(&req->rsp_upiu.qr.data, 0, sizeof(DeviceHealthDescriptor)); + req->rsp_upiu.qr.data[0] =3D sizeof(DeviceHealthDescriptor); + req->rsp_upiu.qr.data[1] =3D UFS_QUERY_DESC_IDN_HEALTH; + status =3D UFS_QUERY_RESULT_SUCCESS; + break; + default: + length =3D 0; + trace_ufs_err_query_invalid_idn(req->req_upiu.qr.opcode, idn); + status =3D UFS_QUERY_RESULT_INVALID_IDN; + } + + if (length > req->rsp_upiu.qr.data[0]) { + length =3D req->rsp_upiu.qr.data[0]; + } + req->rsp_upiu.qr.opcode =3D req->req_upiu.qr.opcode; + req->rsp_upiu.qr.idn =3D req->req_upiu.qr.idn; + req->rsp_upiu.qr.index =3D req->req_upiu.qr.index; + req->rsp_upiu.qr.selector =3D req->req_upiu.qr.selector; + req->rsp_upiu.qr.length =3D cpu_to_be16(length); + + return status; +} + +static QueryRespCode ufs_exec_query_read(UfsRequest *req) +{ + QueryRespCode status; + switch (req->req_upiu.qr.opcode) { + case UFS_UPIU_QUERY_OPCODE_NOP: + status =3D UFS_QUERY_RESULT_SUCCESS; + break; + case UFS_UPIU_QUERY_OPCODE_READ_DESC: + status =3D ufs_read_desc(req); + break; + case UFS_UPIU_QUERY_OPCODE_READ_ATTR: + status =3D ufs_exec_query_attr(req, UFS_QUERY_ATTR_READ); + break; + case UFS_UPIU_QUERY_OPCODE_READ_FLAG: + status =3D ufs_exec_query_flag(req, UFS_QUERY_FLAG_READ); + break; + default: + trace_ufs_err_query_invalid_opcode(req->req_upiu.qr.opcode); + status =3D UFS_QUERY_RESULT_INVALID_OPCODE; + break; + } + + return status; +} + +static QueryRespCode ufs_exec_query_write(UfsRequest *req) +{ + QueryRespCode status; + switch (req->req_upiu.qr.opcode) { + case UFS_UPIU_QUERY_OPCODE_NOP: + status =3D UFS_QUERY_RESULT_SUCCESS; + break; + case UFS_UPIU_QUERY_OPCODE_WRITE_DESC: + /* write descriptor is not supported */ + status =3D UFS_QUERY_RESULT_NOT_WRITEABLE; + break; + case UFS_UPIU_QUERY_OPCODE_WRITE_ATTR: + status =3D ufs_exec_query_attr(req, UFS_QUERY_ATTR_WRITE); + break; + case UFS_UPIU_QUERY_OPCODE_SET_FLAG: + status =3D ufs_exec_query_flag(req, UFS_QUERY_FLAG_SET); + break; + case UFS_UPIU_QUERY_OPCODE_CLEAR_FLAG: + status =3D ufs_exec_query_flag(req, UFS_QUERY_FLAG_CLEAR); + break; + case UFS_UPIU_QUERY_OPCODE_TOGGLE_FLAG: + status =3D ufs_exec_query_flag(req, UFS_QUERY_FLAG_TOGGLE); + break; + default: + trace_ufs_err_query_invalid_opcode(req->req_upiu.qr.opcode); + status =3D UFS_QUERY_RESULT_INVALID_OPCODE; + break; + } + + return status; +} + +static UfsReqResult ufs_exec_query_cmd(UfsRequest *req) +{ + uint8_t query_func =3D req->req_upiu.header.query_func; + uint16_t data_segment_length; + QueryRespCode status; + + trace_ufs_exec_query_cmd(req->slot, req->req_upiu.qr.opcode); + if (query_func =3D=3D UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST) { + status =3D ufs_exec_query_read(req); + } else if (query_func =3D=3D UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUES= T) { + status =3D ufs_exec_query_write(req); + } else { + status =3D UFS_QUERY_RESULT_GENERAL_FAILURE; + } + + data_segment_length =3D be16_to_cpu(req->rsp_upiu.qr.length); + ufs_build_upiu_header(req, UFS_UPIU_TRANSACTION_QUERY_RSP, 0, status, = 0, + data_segment_length); + + if (status !=3D UFS_QUERY_RESULT_SUCCESS) { + return UFS_REQUEST_FAIL; + } + return UFS_REQUEST_SUCCESS; +} + +static void ufs_exec_req(UfsRequest *req) +{ + UfsReqResult req_result; + + if (ufs_dma_read_upiu(req)) { + return; + } + + switch (req->req_upiu.header.trans_type) { + case UFS_UPIU_TRANSACTION_NOP_OUT: + req_result =3D ufs_exec_nop_cmd(req); + break; + case UFS_UPIU_TRANSACTION_COMMAND: + /* Not yet implemented */ + req_result =3D UFS_REQUEST_FAIL; + break; + case UFS_UPIU_TRANSACTION_QUERY_REQ: + req_result =3D ufs_exec_query_cmd(req); + break; + default: + trace_ufs_err_invalid_trans_code(req->slot, + req->req_upiu.header.trans_type); + req_result =3D UFS_REQUEST_FAIL; + } + + ufs_complete_req(req, req_result); +} + +static void ufs_process_req(void *opaque) +{ + UfsHc *u =3D opaque; + UfsRequest *req; + int slot; + + for (slot =3D 0; slot < u->params.nutrs; slot++) { + req =3D &u->req_list[slot]; + + if (req->state !=3D UFS_REQUEST_READY) { + continue; + } + trace_ufs_process_req(slot); + req->state =3D UFS_REQUEST_RUNNING; + + ufs_exec_req(req); + } +} + +static void ufs_complete_req(UfsRequest *req, UfsReqResult req_result) +{ + UfsHc *u =3D req->hc; + assert(req->state =3D=3D UFS_REQUEST_RUNNING); + + if (req_result =3D=3D UFS_REQUEST_SUCCESS) { + req->utrd.header.dword_2 =3D cpu_to_le32(UFS_OCS_SUCCESS); + } else { + req->utrd.header.dword_2 =3D cpu_to_le32(UFS_OCS_INVALID_CMD_TABLE= _ATTR); + } + + trace_ufs_complete_req(req->slot); + req->state =3D UFS_REQUEST_COMPLETE; + qemu_bh_schedule(u->complete_bh); +} + +static void ufs_clear_req(UfsRequest *req) +{ + if (req->sg !=3D NULL) { + qemu_sglist_destroy(req->sg); + g_free(req->sg); + req->sg =3D NULL; + } + + memset(&req->utrd, 0, sizeof(req->utrd)); + memset(&req->req_upiu, 0, sizeof(req->req_upiu)); + memset(&req->rsp_upiu, 0, sizeof(req->rsp_upiu)); +} + +static void ufs_sendback_req(void *opaque) +{ + UfsHc *u =3D opaque; + UfsRequest *req; + int slot; + + for (slot =3D 0; slot < u->params.nutrs; slot++) { + req =3D &u->req_list[slot]; + + if (req->state !=3D UFS_REQUEST_COMPLETE) { + continue; + } + + if (ufs_dma_write_upiu(req)) { + req->state =3D UFS_REQUEST_ERROR; + continue; + } + + /* + * TODO: UTP Transfer Request Interrupt Aggregation Control is not= yet + * supported + */ + if (le32_to_cpu(req->utrd.header.dword_2) !=3D UFS_OCS_SUCCESS || + le32_to_cpu(req->utrd.header.dword_0) & UFS_UTP_REQ_DESC_INT_C= MD) { + u->reg.is =3D FIELD_DP32(u->reg.is, IS, UTRCS, 1); + } + + u->reg.utrldbr &=3D ~(1 << slot); + u->reg.utrlcnr |=3D (1 << slot); + + trace_ufs_sendback_req(req->slot); + + ufs_clear_req(req); + req->state =3D UFS_REQUEST_IDLE; + } + + ufs_irq_check(u); +} + static bool ufs_check_constraints(UfsHc *u, Error **errp) { if (u->params.nutrs > UFS_MAX_NUTRS) { @@ -203,6 +1109,23 @@ static void ufs_init_pci(UfsHc *u, PCIDevice *pci_dev) u->irq =3D pci_allocate_irq(pci_dev); } =20 +static void ufs_init_state(UfsHc *u) +{ + u->req_list =3D g_new0(UfsRequest, u->params.nutrs); + + for (int i =3D 0; i < u->params.nutrs; i++) { + u->req_list[i].hc =3D u; + u->req_list[i].slot =3D i; + u->req_list[i].sg =3D NULL; + u->req_list[i].state =3D UFS_REQUEST_IDLE; + } + + u->doorbell_bh =3D qemu_bh_new_guarded(ufs_process_req, u, + &DEVICE(u)->mem_reentrancy_guard); + u->complete_bh =3D qemu_bh_new_guarded(ufs_sendback_req, u, + &DEVICE(u)->mem_reentrancy_guard); +} + static void ufs_init_hc(UfsHc *u) { uint32_t cap =3D 0; @@ -220,6 +1143,52 @@ static void ufs_init_hc(UfsHc *u) cap =3D FIELD_DP32(cap, CAP, CS, 0); u->reg.cap =3D cap; u->reg.ver =3D UFS_SPEC_VER; + + memset(&u->device_desc, 0, sizeof(DeviceDescriptor)); + u->device_desc.length =3D sizeof(DeviceDescriptor); + u->device_desc.descriptor_idn =3D UFS_QUERY_DESC_IDN_DEVICE; + u->device_desc.device_sub_class =3D 0x01; + u->device_desc.number_lu =3D 0x00; + u->device_desc.number_wlu =3D 0x04; + /* TODO: Revisit it when Power Management is implemented */ + u->device_desc.init_power_mode =3D 0x01; /* Active Mode */ + u->device_desc.high_priority_lun =3D 0x7F; /* Same Priority */ + u->device_desc.spec_version =3D cpu_to_be16(UFS_SPEC_VER); + u->device_desc.manufacturer_name =3D 0x00; + u->device_desc.product_name =3D 0x01; + u->device_desc.serial_number =3D 0x02; + u->device_desc.oem_id =3D 0x03; + u->device_desc.ud_0_base_offset =3D 0x16; + u->device_desc.ud_config_p_length =3D 0x1A; + u->device_desc.device_rtt_cap =3D 0x02; + u->device_desc.queue_depth =3D u->params.nutrs; + u->device_desc.product_revision_level =3D 0x04; + + memset(&u->geometry_desc, 0, sizeof(GeometryDescriptor)); + u->geometry_desc.length =3D sizeof(GeometryDescriptor); + u->geometry_desc.descriptor_idn =3D UFS_QUERY_DESC_IDN_GEOMETRY; + u->geometry_desc.max_number_lu =3D (UFS_MAX_LUS =3D=3D 32) ? 0x1 : 0x0; + u->geometry_desc.segment_size =3D cpu_to_be32(0x2000); /* 4KB */ + u->geometry_desc.allocation_unit_size =3D 0x1; /* 4KB */ + u->geometry_desc.min_addr_block_size =3D 0x8; /* 4KB */ + u->geometry_desc.max_in_buffer_size =3D 0x8; + u->geometry_desc.max_out_buffer_size =3D 0x8; + u->geometry_desc.rpmb_read_write_size =3D 0x40; + u->geometry_desc.data_ordering =3D + 0x0; /* out-of-order data transfer is not supported */ + u->geometry_desc.max_context_id_number =3D 0x5; + u->geometry_desc.supported_memory_types =3D cpu_to_be16(0x8001); + + memset(&u->attributes, 0, sizeof(u->attributes)); + u->attributes.max_data_in_size =3D 0x08; + u->attributes.max_data_out_size =3D 0x08; + u->attributes.ref_clk_freq =3D 0x01; /* 26 MHz */ + /* configure descriptor is not supported */ + u->attributes.config_descr_lock =3D 0x01; + u->attributes.max_num_of_rtt =3D 0x02; + + memset(&u->flags, 0, sizeof(u->flags)); + u->flags.permanently_disable_fw_update =3D 1; } =20 static void ufs_realize(PCIDevice *pci_dev, Error **errp) @@ -230,10 +1199,24 @@ static void ufs_realize(PCIDevice *pci_dev, Error **= errp) return; } =20 + ufs_init_state(u); ufs_init_hc(u); ufs_init_pci(u, pci_dev); } =20 +static void ufs_exit(PCIDevice *pci_dev) +{ + UfsHc *u =3D UFS(pci_dev); + + qemu_bh_delete(u->doorbell_bh); + qemu_bh_delete(u->complete_bh); + + for (int i =3D 0; i < u->params.nutrs; i++) { + ufs_clear_req(&u->req_list[i]); + } + g_free(u->req_list); +} + static Property ufs_props[] =3D { DEFINE_PROP_STRING("serial", UfsHc, params.serial), DEFINE_PROP_UINT8("nutrs", UfsHc, params.nutrs, 32), @@ -252,6 +1235,7 @@ static void ufs_class_init(ObjectClass *oc, void *data) PCIDeviceClass *pc =3D PCI_DEVICE_CLASS(oc); =20 pc->realize =3D ufs_realize; + pc->exit =3D ufs_exit; pc->vendor_id =3D PCI_VENDOR_ID_REDHAT; pc->device_id =3D PCI_DEVICE_ID_REDHAT_UFS; pc->class_id =3D PCI_CLASS_STORAGE_UFS; diff --git a/hw/ufs/ufs.h b/hw/ufs/ufs.h index d9d195caec..3d1b2cff4e 100644 --- a/hw/ufs/ufs.h +++ b/hw/ufs/ufs.h @@ -18,6 +18,32 @@ #define UFS_MAX_LUS 32 #define UFS_BLOCK_SIZE 4096 =20 +typedef enum UfsRequestState { + UFS_REQUEST_IDLE =3D 0, + UFS_REQUEST_READY =3D 1, + UFS_REQUEST_RUNNING =3D 2, + UFS_REQUEST_COMPLETE =3D 3, + UFS_REQUEST_ERROR =3D 4, +} UfsRequestState; + +typedef enum UfsReqResult { + UFS_REQUEST_SUCCESS =3D 0, + UFS_REQUEST_FAIL =3D 1, +} UfsReqResult; + +typedef struct UfsRequest { + struct UfsHc *hc; + UfsRequestState state; + int slot; + + UtpTransferReqDesc utrd; + UtpUpiuReq req_upiu; + UtpUpiuRsp rsp_upiu; + + /* for scsi command */ + QEMUSGList *sg; +} UfsRequest; + typedef struct UfsParams { char *serial; uint8_t nutrs; /* Number of UTP Transfer Request Slots */ @@ -30,6 +56,12 @@ typedef struct UfsHc { UfsReg reg; UfsParams params; uint32_t reg_size; + UfsRequest *req_list; + + DeviceDescriptor device_desc; + GeometryDescriptor geometry_desc; + Attributes attributes; + Flags flags; =20 qemu_irq irq; QEMUBH *doorbell_bh; @@ -39,4 +71,18 @@ typedef struct UfsHc { #define TYPE_UFS "ufs" #define UFS(obj) OBJECT_CHECK(UfsHc, (obj), TYPE_UFS) =20 +typedef enum UfsQueryFlagPerm { + UFS_QUERY_FLAG_NONE =3D 0x0, + UFS_QUERY_FLAG_READ =3D 0x1, + UFS_QUERY_FLAG_SET =3D 0x2, + UFS_QUERY_FLAG_CLEAR =3D 0x4, + UFS_QUERY_FLAG_TOGGLE =3D 0x8, +} UfsQueryFlagPerm; + +typedef enum UfsQueryAttrPerm { + UFS_QUERY_ATTR_NONE =3D 0x0, + UFS_QUERY_ATTR_READ =3D 0x1, + UFS_QUERY_ATTR_WRITE =3D 0x2, +} UfsQueryAttrPerm; + #endif /* HW_UFS_UFS_H */ --=20 2.34.1 From nobody Sun May 12 20:04:00 2024 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=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1693986329; cv=none; d=zohomail.com; s=zohoarc; b=igcaIIRtXMTzPDU7P1UE4K9e37CcV4wi3Ho8fQpXh83GIzXV/IDsCOK359jg3/YPSRnmnS1fE93NolAcjQ2uAhHcy8ZoEcHvy5bkId7nRGbDwYmWzXMj4SV/uCunGJ0m4ylFrcgIVYfqYMdMy9O1NSHMV12/PRduYjJ8Fn45e+0= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1693986329; h=Content-Transfer-Encoding:Cc:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To; bh=6E4AKI7kHV7nYt5C8cdXGs2BPjYo7nwz+DLP+JtLFsU=; b=CMpnV2ZD9jW7tS70kI3KGgbEWbZ/gC4MB2UMaFNvOfw3HSul9hFA2PfvAgmLXMYd/C+NdxahZ7qwUIxm7dW/ItaM55ajhHRUtIBgOGJ5kBmwScZUZkg1sMe2u4F6w1P0gtaFHObWp17K1DmWaZ4qrr+JkmTZa4uQghDhC4v3hws= 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 lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1693986329778342.83674443745724; Wed, 6 Sep 2023 00:45:29 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1qdnDj-0007Yv-0e; Wed, 06 Sep 2023 03:45:03 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1qdnDh-0007Y9-6T; Wed, 06 Sep 2023 03:45:01 -0400 Received: from mail-pj1-x1034.google.com ([2607:f8b0:4864:20::1034]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1qdnDb-00050b-Mp; Wed, 06 Sep 2023 03:45:00 -0400 Received: by mail-pj1-x1034.google.com with SMTP id 98e67ed59e1d1-26d1a17ce06so1949886a91.0; Wed, 06 Sep 2023 00:44:54 -0700 (PDT) Received: from jeuk-MS-7D42.. ([218.147.112.168]) by smtp.gmail.com with ESMTPSA id ck1-20020a17090afe0100b00262eb0d141esm10434901pjb.28.2023.09.06.00.44.48 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 06 Sep 2023 00:44:52 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20221208; t=1693986293; x=1694591093; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=6E4AKI7kHV7nYt5C8cdXGs2BPjYo7nwz+DLP+JtLFsU=; b=YstQTPGCNiL6aSlozL3+f0Hy7oNNyqTpeR2c10CONEhaWHhvXUpkZZVocEaMiQEzSI D0xwbhotg8ReWmIHaNwXyWyfRKh49xa8sF7q04Zb4pL7mFJ7L850IsXUWwDG5rbkbTIw P019xniHhJnA9hnbnnpn40MgzPz6dO8/Z8wj8dumpxKISfjUbBI078PKS0Zcow+dy1lB b6e8qMwm17LIwHkfOyVKMeAfRJKrb1nWjh1z60HVivwR+4nJLwT3yHF1d8Vhu95aFuff t9tSnVOJHQeaXq7BKrMBfIN9vAKK5B7/UUQ9ZP6LDwFvL960mul2/g+1OGiw0Gzrz7pY F5XA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1693986293; x=1694591093; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=6E4AKI7kHV7nYt5C8cdXGs2BPjYo7nwz+DLP+JtLFsU=; b=Eb2NtZnbKN+vSKqn62pJw+YF/IjpoK4o3b0+Bgj8nIWuuQmJ6XrousfzLKkf6161gg 5hOtjRwbAvONTjIMCoLpMzczuabAU31G+AEUOWhZNkNdeYUGdNCieJCP9BYH38SjiWya mIhkR/3aQdYIaJNt51ctsUp2zOFTW78QvFl60fA4zcu//TGPG/PtaEJFdOsE9ysZOoXM SjrRSFtTDeVU4048rtvt1TNUdQ+zUd+pruIBH+CstVZBpyzqr1tPSsBCQsJnG4ULbEKM z13+CJaodWEfiYJ2HIYNIFfqToNe/+ypX8/gZL9T+mtUvgPvmau6Q5UT7a1nnE1MapqT 1w0A== X-Gm-Message-State: AOJu0YwedzvZCBv+FsVL6PdbyrBJdqlyIzlF9qbk0DMsgnsP0/0mhofA DUlWPq13loCsVkUFsjncVjoZEnZPBhzAzQ== X-Google-Smtp-Source: AGHT+IEnroYjWshwXqhBG6BWF+waA+iFocKLSYveSrULm32OipyCFBTwRY3QEqpz2LvWMMy/G1dkcg== X-Received: by 2002:a17:90a:3006:b0:267:f9c4:c0a8 with SMTP id g6-20020a17090a300600b00267f9c4c0a8mr12299203pjb.4.1693986292514; Wed, 06 Sep 2023 00:44:52 -0700 (PDT) From: Jeuk Kim To: qemu-devel@nongnu.org Cc: jeuk20.kim@gmail.com, berrange@redhat.com, fam@euphon.net, hreitz@redhat.com, jeuk20.kim@samsung.com, k.jensen@samsung.com, kwolf@redhat.com, lvivier@redhat.com, marcandre.lureau@redhat.com, marcel.apfelbaum@gmail.com, mst@redhat.com, pbonzini@redhat.com, philmd@linaro.org, qemu-block@nongnu.org, stefanha@redhat.com, thuth@redhat.com Subject: [PATCH v10 3/4] hw/ufs: Support for UFS logical unit Date: Wed, 6 Sep 2023 16:43:50 +0900 Message-Id: X-Mailer: git-send-email 2.34.1 In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable 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=lists.gnu.org; Received-SPF: pass client-ip=2607:f8b0:4864:20::1034; envelope-from=jeuk20.kim@gmail.com; helo=mail-pj1-x1034.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-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: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1693986330509100001 Content-Type: text/plain; charset="utf-8" From: Jeuk Kim This commit adds support for ufs logical unit. The LU handles processing for the SCSI command, unit descriptor query request. This commit enables the UFS device to process IO requests. Signed-off-by: Jeuk Kim Reviewed-by: Stefan Hajnoczi --- hw/ufs/lu.c | 1445 ++++++++++++++++++++++++++++++++++++++ hw/ufs/meson.build | 2 +- hw/ufs/trace-events | 25 + hw/ufs/ufs.c | 252 ++++++- hw/ufs/ufs.h | 43 ++ include/scsi/constants.h | 1 + 6 files changed, 1761 insertions(+), 7 deletions(-) create mode 100644 hw/ufs/lu.c diff --git a/hw/ufs/lu.c b/hw/ufs/lu.c new file mode 100644 index 0000000000..e1c46bddb1 --- /dev/null +++ b/hw/ufs/lu.c @@ -0,0 +1,1445 @@ +/* + * QEMU UFS Logical Unit + * + * Copyright (c) 2023 Samsung Electronics Co., Ltd. All rights reserved. + * + * Written by Jeuk Kim + * + * This code is licensed under the GNU GPL v2 or later. + */ + +#include "qemu/osdep.h" +#include "qemu/units.h" +#include "qapi/error.h" +#include "qemu/memalign.h" +#include "hw/scsi/scsi.h" +#include "scsi/constants.h" +#include "sysemu/block-backend.h" +#include "qemu/cutils.h" +#include "trace.h" +#include "ufs.h" + +/* + * The code below handling SCSI commands is copied from hw/scsi/scsi-disk.= c, + * with minor adjustments to make it work for UFS. + */ + +#define SCSI_DMA_BUF_SIZE (128 * KiB) +#define SCSI_MAX_INQUIRY_LEN 256 +#define SCSI_INQUIRY_DATA_SIZE 36 +#define SCSI_MAX_MODE_LEN 256 + +typedef struct UfsSCSIReq { + SCSIRequest req; + /* Both sector and sector_count are in terms of BDRV_SECTOR_SIZE bytes= . */ + uint64_t sector; + uint32_t sector_count; + uint32_t buflen; + bool started; + bool need_fua_emulation; + struct iovec iov; + QEMUIOVector qiov; + BlockAcctCookie acct; +} UfsSCSIReq; + +static void ufs_scsi_free_request(SCSIRequest *req) +{ + UfsSCSIReq *r =3D DO_UPCAST(UfsSCSIReq, req, req); + + qemu_vfree(r->iov.iov_base); +} + +static void scsi_check_condition(UfsSCSIReq *r, SCSISense sense) +{ + trace_ufs_scsi_check_condition(r->req.tag, sense.key, sense.asc, + sense.ascq); + scsi_req_build_sense(&r->req, sense); + scsi_req_complete(&r->req, CHECK_CONDITION); +} + +static int ufs_scsi_emulate_vpd_page(SCSIRequest *req, uint8_t *outbuf, + uint32_t outbuf_len) +{ + UfsHc *u =3D UFS(req->bus->qbus.parent); + UfsLu *lu =3D DO_UPCAST(UfsLu, qdev, req->dev); + uint8_t page_code =3D req->cmd.buf[2]; + int start, buflen =3D 0; + + if (outbuf_len < SCSI_INQUIRY_DATA_SIZE) { + return -1; + } + + outbuf[buflen++] =3D lu->qdev.type & 0x1f; + outbuf[buflen++] =3D page_code; + outbuf[buflen++] =3D 0x00; + outbuf[buflen++] =3D 0x00; + start =3D buflen; + + switch (page_code) { + case 0x00: /* Supported page codes, mandatory */ + { + trace_ufs_scsi_emulate_vpd_page_00(req->cmd.xfer); + outbuf[buflen++] =3D 0x00; /* list of supported pages (this page) = */ + if (u->params.serial) { + outbuf[buflen++] =3D 0x80; /* unit serial number */ + } + outbuf[buflen++] =3D 0x87; /* mode page policy */ + break; + } + case 0x80: /* Device serial number, optional */ + { + int l; + + if (!u->params.serial) { + trace_ufs_scsi_emulate_vpd_page_80_not_supported(); + return -1; + } + + l =3D strlen(u->params.serial); + if (l > SCSI_INQUIRY_DATA_SIZE) { + l =3D SCSI_INQUIRY_DATA_SIZE; + } + + trace_ufs_scsi_emulate_vpd_page_80(req->cmd.xfer); + memcpy(outbuf + buflen, u->params.serial, l); + buflen +=3D l; + break; + } + case 0x87: /* Mode Page Policy, mandatory */ + { + trace_ufs_scsi_emulate_vpd_page_87(req->cmd.xfer); + outbuf[buflen++] =3D 0x3f; /* apply to all mode pages and subpages= */ + outbuf[buflen++] =3D 0xff; + outbuf[buflen++] =3D 0; /* shared */ + outbuf[buflen++] =3D 0; + break; + } + default: + return -1; + } + /* done with EVPD */ + assert(buflen - start <=3D 255); + outbuf[start - 1] =3D buflen - start; + return buflen; +} + +static int ufs_scsi_emulate_inquiry(SCSIRequest *req, uint8_t *outbuf, + uint32_t outbuf_len) +{ + int buflen =3D 0; + + if (outbuf_len < SCSI_INQUIRY_DATA_SIZE) { + return -1; + } + + if (req->cmd.buf[1] & 0x1) { + /* Vital product data */ + return ufs_scsi_emulate_vpd_page(req, outbuf, outbuf_len); + } + + /* Standard INQUIRY data */ + if (req->cmd.buf[2] !=3D 0) { + return -1; + } + + /* PAGE CODE =3D=3D 0 */ + buflen =3D req->cmd.xfer; + if (buflen > SCSI_MAX_INQUIRY_LEN) { + buflen =3D SCSI_MAX_INQUIRY_LEN; + } + + if (is_wlun(req->lun)) { + outbuf[0] =3D TYPE_WLUN; + } else { + outbuf[0] =3D 0; + } + outbuf[1] =3D 0; + + strpadcpy((char *)&outbuf[16], 16, "QEMU UFS", ' '); + strpadcpy((char *)&outbuf[8], 8, "QEMU", ' '); + + memset(&outbuf[32], 0, 4); + + outbuf[2] =3D 0x06; /* SPC-4 */ + outbuf[3] =3D 0x2; + + if (buflen > SCSI_INQUIRY_DATA_SIZE) { + outbuf[4] =3D buflen - 5; /* Additional Length =3D (Len - 1) - 4 */ + } else { + /* + * If the allocation length of CDB is too small, the additional + * length is not adjusted + */ + outbuf[4] =3D SCSI_INQUIRY_DATA_SIZE - 5; + } + + /* Support TCQ. */ + outbuf[7] =3D req->bus->info->tcq ? 0x02 : 0; + return buflen; +} + +static int mode_sense_page(UfsLu *lu, int page, uint8_t **p_outbuf, + int page_control) +{ + static const int mode_sense_valid[0x3f] =3D { + [MODE_PAGE_CACHING] =3D 1, + [MODE_PAGE_R_W_ERROR] =3D 1, + [MODE_PAGE_CONTROL] =3D 1, + }; + + uint8_t *p =3D *p_outbuf + 2; + int length; + + assert(page < ARRAY_SIZE(mode_sense_valid)); + if ((mode_sense_valid[page]) =3D=3D 0) { + return -1; + } + + /* + * If Changeable Values are requested, a mask denoting those mode para= meters + * that are changeable shall be returned. As we currently don't support + * parameter changes via MODE_SELECT all bits are returned set to zero. + * The buffer was already memset to zero by the caller of this functio= n. + */ + switch (page) { + case MODE_PAGE_CACHING: + length =3D 0x12; + if (page_control =3D=3D 1 || /* Changeable Values */ + blk_enable_write_cache(lu->qdev.conf.blk)) { + p[0] =3D 4; /* WCE */ + } + break; + + case MODE_PAGE_R_W_ERROR: + length =3D 10; + if (page_control =3D=3D 1) { /* Changeable Values */ + break; + } + p[0] =3D 0x80; /* Automatic Write Reallocation Enabled */ + break; + + case MODE_PAGE_CONTROL: + length =3D 10; + if (page_control =3D=3D 1) { /* Changeable Values */ + break; + } + p[1] =3D 0x10; /* Queue Algorithm modifier */ + p[8] =3D 0xff; /* Busy Timeout Period */ + p[9] =3D 0xff; + break; + + default: + return -1; + } + + assert(length < 256); + (*p_outbuf)[0] =3D page; + (*p_outbuf)[1] =3D length; + *p_outbuf +=3D length + 2; + return length + 2; +} + +static int ufs_scsi_emulate_mode_sense(UfsSCSIReq *r, uint8_t *outbuf) +{ + UfsLu *lu =3D DO_UPCAST(UfsLu, qdev, r->req.dev); + bool dbd; + int page, buflen, ret, page_control; + uint8_t *p; + uint8_t dev_specific_param =3D 0; + + dbd =3D (r->req.cmd.buf[1] & 0x8) !=3D 0; + if (!dbd) { + return -1; + } + + page =3D r->req.cmd.buf[2] & 0x3f; + page_control =3D (r->req.cmd.buf[2] & 0xc0) >> 6; + + trace_ufs_scsi_emulate_mode_sense((r->req.cmd.buf[0] =3D=3D MODE_SENSE= ) ? 6 : + = 10, + page, r->req.cmd.xfer, page_control); + memset(outbuf, 0, r->req.cmd.xfer); + p =3D outbuf; + + if (!blk_is_writable(lu->qdev.conf.blk)) { + dev_specific_param |=3D 0x80; /* Readonly. */ + } + + p[2] =3D 0; /* Medium type. */ + p[3] =3D dev_specific_param; + p[6] =3D p[7] =3D 0; /* Block descriptor length. */ + p +=3D 8; + + if (page_control =3D=3D 3) { + /* Saved Values */ + scsi_check_condition(r, SENSE_CODE(SAVING_PARAMS_NOT_SUPPORTED)); + return -1; + } + + if (page =3D=3D 0x3f) { + for (page =3D 0; page <=3D 0x3e; page++) { + mode_sense_page(lu, page, &p, page_control); + } + } else { + ret =3D mode_sense_page(lu, page, &p, page_control); + if (ret =3D=3D -1) { + return -1; + } + } + + buflen =3D p - outbuf; + /* + * The mode data length field specifies the length in bytes of the + * following data that is available to be transferred. The mode data + * length does not include itself. + */ + outbuf[0] =3D ((buflen - 2) >> 8) & 0xff; + outbuf[1] =3D (buflen - 2) & 0xff; + return buflen; +} + +/* + * scsi_handle_rw_error has two return values. False means that the error + * must be ignored, true means that the error has been processed and the + * caller should not do anything else for this request. Note that + * scsi_handle_rw_error always manages its reference counts, independent + * of the return value. + */ +static bool scsi_handle_rw_error(UfsSCSIReq *r, int ret, bool acct_failed) +{ + bool is_read =3D (r->req.cmd.mode =3D=3D SCSI_XFER_FROM_DEV); + UfsLu *lu =3D DO_UPCAST(UfsLu, qdev, r->req.dev); + SCSISense sense =3D SENSE_CODE(NO_SENSE); + int error =3D 0; + bool req_has_sense =3D false; + BlockErrorAction action; + int status; + + if (ret < 0) { + status =3D scsi_sense_from_errno(-ret, &sense); + error =3D -ret; + } else { + /* A passthrough command has completed with nonzero status. */ + status =3D ret; + if (status =3D=3D CHECK_CONDITION) { + req_has_sense =3D true; + error =3D scsi_sense_buf_to_errno(r->req.sense, sizeof(r->req.= sense)); + } else { + error =3D EINVAL; + } + } + + /* + * Check whether the error has to be handled by the guest or should + * rather follow the rerror=3D/werror=3D settings. Guest-handled erro= rs + * are usually retried immediately, so do not post them to QMP and + * do not account them as failed I/O. + */ + if (req_has_sense && scsi_sense_buf_is_guest_recoverable( + r->req.sense, sizeof(r->req.sense))) { + action =3D BLOCK_ERROR_ACTION_REPORT; + acct_failed =3D false; + } else { + action =3D blk_get_error_action(lu->qdev.conf.blk, is_read, error); + blk_error_action(lu->qdev.conf.blk, action, is_read, error); + } + + switch (action) { + case BLOCK_ERROR_ACTION_REPORT: + if (acct_failed) { + block_acct_failed(blk_get_stats(lu->qdev.conf.blk), &r->acct); + } + if (!req_has_sense && status =3D=3D CHECK_CONDITION) { + scsi_req_build_sense(&r->req, sense); + } + scsi_req_complete(&r->req, status); + return true; + + case BLOCK_ERROR_ACTION_IGNORE: + return false; + + case BLOCK_ERROR_ACTION_STOP: + scsi_req_retry(&r->req); + return true; + + default: + g_assert_not_reached(); + } +} + +static bool ufs_scsi_req_check_error(UfsSCSIReq *r, int ret, bool acct_fai= led) +{ + if (r->req.io_canceled) { + scsi_req_cancel_complete(&r->req); + return true; + } + + if (ret < 0) { + return scsi_handle_rw_error(r, ret, acct_failed); + } + + return false; +} + +static void scsi_aio_complete(void *opaque, int ret) +{ + UfsSCSIReq *r =3D (UfsSCSIReq *)opaque; + UfsLu *lu =3D DO_UPCAST(UfsLu, qdev, r->req.dev); + + assert(r->req.aiocb !=3D NULL); + r->req.aiocb =3D NULL; + aio_context_acquire(blk_get_aio_context(lu->qdev.conf.blk)); + if (ufs_scsi_req_check_error(r, ret, true)) { + goto done; + } + + block_acct_done(blk_get_stats(lu->qdev.conf.blk), &r->acct); + scsi_req_complete(&r->req, GOOD); + +done: + aio_context_release(blk_get_aio_context(lu->qdev.conf.blk)); + scsi_req_unref(&r->req); +} + +static int32_t ufs_scsi_emulate_command(SCSIRequest *req, uint8_t *buf) +{ + UfsSCSIReq *r =3D DO_UPCAST(UfsSCSIReq, req, req); + UfsLu *lu =3D DO_UPCAST(UfsLu, qdev, req->dev); + uint32_t last_block =3D 0; + uint8_t *outbuf; + int buflen; + + switch (req->cmd.buf[0]) { + case INQUIRY: + case MODE_SENSE_10: + case START_STOP: + case REQUEST_SENSE: + break; + + default: + if (!blk_is_available(lu->qdev.conf.blk)) { + scsi_check_condition(r, SENSE_CODE(NO_MEDIUM)); + return 0; + } + break; + } + + /* + * FIXME: we shouldn't return anything bigger than 4k, but the code + * requires the buffer to be as big as req->cmd.xfer in several + * places. So, do not allow CDBs with a very large ALLOCATION + * LENGTH. The real fix would be to modify scsi_read_data and + * dma_buf_read, so that they return data beyond the buflen + * as all zeros. + */ + if (req->cmd.xfer > 65536) { + goto illegal_request; + } + r->buflen =3D MAX(4096, req->cmd.xfer); + + if (!r->iov.iov_base) { + r->iov.iov_base =3D blk_blockalign(lu->qdev.conf.blk, r->buflen); + } + + outbuf =3D r->iov.iov_base; + memset(outbuf, 0, r->buflen); + switch (req->cmd.buf[0]) { + case TEST_UNIT_READY: + assert(blk_is_available(lu->qdev.conf.blk)); + break; + case INQUIRY: + buflen =3D ufs_scsi_emulate_inquiry(req, outbuf, r->buflen); + if (buflen < 0) { + goto illegal_request; + } + break; + case MODE_SENSE_10: + buflen =3D ufs_scsi_emulate_mode_sense(r, outbuf); + if (buflen < 0) { + goto illegal_request; + } + break; + case READ_CAPACITY_10: + /* The normal LEN field for this command is zero. */ + memset(outbuf, 0, 8); + if (lu->qdev.max_lba > 0) { + last_block =3D lu->qdev.max_lba - 1; + }; + outbuf[0] =3D (last_block >> 24) & 0xff; + outbuf[1] =3D (last_block >> 16) & 0xff; + outbuf[2] =3D (last_block >> 8) & 0xff; + outbuf[3] =3D last_block & 0xff; + outbuf[4] =3D (lu->qdev.blocksize >> 24) & 0xff; + outbuf[5] =3D (lu->qdev.blocksize >> 16) & 0xff; + outbuf[6] =3D (lu->qdev.blocksize >> 8) & 0xff; + outbuf[7] =3D lu->qdev.blocksize & 0xff; + break; + case REQUEST_SENSE: + /* Just return "NO SENSE". */ + buflen =3D scsi_convert_sense(NULL, 0, outbuf, r->buflen, + (req->cmd.buf[1] & 1) =3D=3D 0); + if (buflen < 0) { + goto illegal_request; + } + break; + case SYNCHRONIZE_CACHE: + /* The request is used as the AIO opaque value, so add a ref. */ + scsi_req_ref(&r->req); + block_acct_start(blk_get_stats(lu->qdev.conf.blk), &r->acct, 0, + BLOCK_ACCT_FLUSH); + r->req.aiocb =3D blk_aio_flush(lu->qdev.conf.blk, scsi_aio_complet= e, r); + return 0; + case VERIFY_10: + trace_ufs_scsi_emulate_command_VERIFY((req->cmd.buf[1] >> 1) & 3); + if (req->cmd.buf[1] & 6) { + goto illegal_request; + } + break; + case SERVICE_ACTION_IN_16: + /* Service Action In subcommands. */ + if ((req->cmd.buf[1] & 31) =3D=3D SAI_READ_CAPACITY_16) { + trace_ufs_scsi_emulate_command_SAI_16(); + memset(outbuf, 0, req->cmd.xfer); + + if (lu->qdev.max_lba > 0) { + last_block =3D lu->qdev.max_lba - 1; + }; + outbuf[0] =3D 0; + outbuf[1] =3D 0; + outbuf[2] =3D 0; + outbuf[3] =3D 0; + outbuf[4] =3D (last_block >> 24) & 0xff; + outbuf[5] =3D (last_block >> 16) & 0xff; + outbuf[6] =3D (last_block >> 8) & 0xff; + outbuf[7] =3D last_block & 0xff; + outbuf[8] =3D (lu->qdev.blocksize >> 24) & 0xff; + outbuf[9] =3D (lu->qdev.blocksize >> 16) & 0xff; + outbuf[10] =3D (lu->qdev.blocksize >> 8) & 0xff; + outbuf[11] =3D lu->qdev.blocksize & 0xff; + outbuf[12] =3D 0; + outbuf[13] =3D get_physical_block_exp(&lu->qdev.conf); + + if (lu->unit_desc.provisioning_type =3D=3D 2 || + lu->unit_desc.provisioning_type =3D=3D 3) { + outbuf[14] =3D 0x80; + } + /* Protection, exponent and lowest lba field left blank. */ + break; + } + trace_ufs_scsi_emulate_command_SAI_unsupported(); + goto illegal_request; + case MODE_SELECT_10: + trace_ufs_scsi_emulate_command_MODE_SELECT_10(r->req.cmd.xfer); + break; + case START_STOP: + /* + * TODO: START_STOP is not yet implemented. It always returns succ= ess. + * Revisit it when ufs power management is implemented. + */ + trace_ufs_scsi_emulate_command_START_STOP(); + break; + case FORMAT_UNIT: + trace_ufs_scsi_emulate_command_FORMAT_UNIT(); + break; + case SEND_DIAGNOSTIC: + trace_ufs_scsi_emulate_command_SEND_DIAGNOSTIC(); + break; + default: + trace_ufs_scsi_emulate_command_UNKNOWN(buf[0], + scsi_command_name(buf[0])); + scsi_check_condition(r, SENSE_CODE(INVALID_OPCODE)); + return 0; + } + assert(!r->req.aiocb); + r->iov.iov_len =3D MIN(r->buflen, req->cmd.xfer); + if (r->iov.iov_len =3D=3D 0) { + scsi_req_complete(&r->req, GOOD); + } + if (r->req.cmd.mode =3D=3D SCSI_XFER_TO_DEV) { + assert(r->iov.iov_len =3D=3D req->cmd.xfer); + return -r->iov.iov_len; + } else { + return r->iov.iov_len; + } + +illegal_request: + if (r->req.status =3D=3D -1) { + scsi_check_condition(r, SENSE_CODE(INVALID_FIELD)); + } + return 0; +} + +static void ufs_scsi_emulate_read_data(SCSIRequest *req) +{ + UfsSCSIReq *r =3D DO_UPCAST(UfsSCSIReq, req, req); + int buflen =3D r->iov.iov_len; + + if (buflen) { + trace_ufs_scsi_emulate_read_data(buflen); + r->iov.iov_len =3D 0; + r->started =3D true; + scsi_req_data(&r->req, buflen); + return; + } + + /* This also clears the sense buffer for REQUEST SENSE. */ + scsi_req_complete(&r->req, GOOD); +} + +static int ufs_scsi_check_mode_select(UfsLu *lu, int page, uint8_t *inbuf, + int inlen) +{ + uint8_t mode_current[SCSI_MAX_MODE_LEN]; + uint8_t mode_changeable[SCSI_MAX_MODE_LEN]; + uint8_t *p; + int len, expected_len, changeable_len, i; + + /* + * The input buffer does not include the page header, so it is + * off by 2 bytes. + */ + expected_len =3D inlen + 2; + if (expected_len > SCSI_MAX_MODE_LEN) { + return -1; + } + + /* MODE_PAGE_ALLS is only valid for MODE SENSE commands */ + if (page =3D=3D MODE_PAGE_ALLS) { + return -1; + } + + p =3D mode_current; + memset(mode_current, 0, inlen + 2); + len =3D mode_sense_page(lu, page, &p, 0); + if (len < 0 || len !=3D expected_len) { + return -1; + } + + p =3D mode_changeable; + memset(mode_changeable, 0, inlen + 2); + changeable_len =3D mode_sense_page(lu, page, &p, 1); + assert(changeable_len =3D=3D len); + + /* + * Check that unchangeable bits are the same as what MODE SENSE + * would return. + */ + for (i =3D 2; i < len; i++) { + if (((mode_current[i] ^ inbuf[i - 2]) & ~mode_changeable[i]) !=3D = 0) { + return -1; + } + } + return 0; +} + +static void ufs_scsi_apply_mode_select(UfsLu *lu, int page, uint8_t *p) +{ + switch (page) { + case MODE_PAGE_CACHING: + blk_set_enable_write_cache(lu->qdev.conf.blk, (p[0] & 4) !=3D 0); + break; + + default: + break; + } +} + +static int mode_select_pages(UfsSCSIReq *r, uint8_t *p, int len, bool chan= ge) +{ + UfsLu *lu =3D DO_UPCAST(UfsLu, qdev, r->req.dev); + + while (len > 0) { + int page, page_len; + + page =3D p[0] & 0x3f; + if (p[0] & 0x40) { + goto invalid_param; + } else { + if (len < 2) { + goto invalid_param_len; + } + page_len =3D p[1]; + p +=3D 2; + len -=3D 2; + } + + if (page_len > len) { + goto invalid_param_len; + } + + if (!change) { + if (ufs_scsi_check_mode_select(lu, page, p, page_len) < 0) { + goto invalid_param; + } + } else { + ufs_scsi_apply_mode_select(lu, page, p); + } + + p +=3D page_len; + len -=3D page_len; + } + return 0; + +invalid_param: + scsi_check_condition(r, SENSE_CODE(INVALID_PARAM)); + return -1; + +invalid_param_len: + scsi_check_condition(r, SENSE_CODE(INVALID_PARAM_LEN)); + return -1; +} + +static void ufs_scsi_emulate_mode_select(UfsSCSIReq *r, uint8_t *inbuf) +{ + UfsLu *lu =3D DO_UPCAST(UfsLu, qdev, r->req.dev); + uint8_t *p =3D inbuf; + int len =3D r->req.cmd.xfer; + int hdr_len =3D 8; + int bd_len; + int pass; + + /* We only support PF=3D1, SP=3D0. */ + if ((r->req.cmd.buf[1] & 0x11) !=3D 0x10) { + goto invalid_field; + } + + if (len < hdr_len) { + goto invalid_param_len; + } + + bd_len =3D lduw_be_p(&p[6]); + if (bd_len !=3D 0) { + goto invalid_param; + } + + len -=3D hdr_len; + p +=3D hdr_len; + + /* Ensure no change is made if there is an error! */ + for (pass =3D 0; pass < 2; pass++) { + if (mode_select_pages(r, p, len, pass =3D=3D 1) < 0) { + assert(pass =3D=3D 0); + return; + } + } + + if (!blk_enable_write_cache(lu->qdev.conf.blk)) { + /* The request is used as the AIO opaque value, so add a ref. */ + scsi_req_ref(&r->req); + block_acct_start(blk_get_stats(lu->qdev.conf.blk), &r->acct, 0, + BLOCK_ACCT_FLUSH); + r->req.aiocb =3D blk_aio_flush(lu->qdev.conf.blk, scsi_aio_complet= e, r); + return; + } + + scsi_req_complete(&r->req, GOOD); + return; + +invalid_param: + scsi_check_condition(r, SENSE_CODE(INVALID_PARAM)); + return; + +invalid_param_len: + scsi_check_condition(r, SENSE_CODE(INVALID_PARAM_LEN)); + return; + +invalid_field: + scsi_check_condition(r, SENSE_CODE(INVALID_FIELD)); +} + +/* block_num and nb_blocks expected to be in qdev blocksize */ +static inline bool check_lba_range(UfsLu *lu, uint64_t block_num, + uint32_t nb_blocks) +{ + /* + * The first line tests that no overflow happens when computing the la= st + * block. The second line tests that the last accessed block is in + * range. + * + * Careful, the computations should not underflow for nb_blocks =3D=3D= 0, + * and a 0-block read to the first LBA beyond the end of device is + * valid. + */ + return (block_num <=3D block_num + nb_blocks && + block_num + nb_blocks <=3D lu->qdev.max_lba + 1); +} + +static void ufs_scsi_emulate_write_data(SCSIRequest *req) +{ + UfsSCSIReq *r =3D DO_UPCAST(UfsSCSIReq, req, req); + + if (r->iov.iov_len) { + int buflen =3D r->iov.iov_len; + trace_ufs_scsi_emulate_write_data(buflen); + r->iov.iov_len =3D 0; + scsi_req_data(&r->req, buflen); + return; + } + + switch (req->cmd.buf[0]) { + case MODE_SELECT_10: + /* This also clears the sense buffer for REQUEST SENSE. */ + ufs_scsi_emulate_mode_select(r, r->iov.iov_base); + break; + default: + abort(); + } +} + +/* Return a pointer to the data buffer. */ +static uint8_t *ufs_scsi_get_buf(SCSIRequest *req) +{ + UfsSCSIReq *r =3D DO_UPCAST(UfsSCSIReq, req, req); + + return (uint8_t *)r->iov.iov_base; +} + +static int32_t ufs_scsi_dma_command(SCSIRequest *req, uint8_t *buf) +{ + UfsSCSIReq *r =3D DO_UPCAST(UfsSCSIReq, req, req); + UfsLu *lu =3D DO_UPCAST(UfsLu, qdev, req->dev); + uint32_t len; + uint8_t command; + + command =3D buf[0]; + + if (!blk_is_available(lu->qdev.conf.blk)) { + scsi_check_condition(r, SENSE_CODE(NO_MEDIUM)); + return 0; + } + + len =3D scsi_data_cdb_xfer(r->req.cmd.buf); + switch (command) { + case READ_6: + case READ_10: + trace_ufs_scsi_dma_command_READ(r->req.cmd.lba, len); + if (r->req.cmd.buf[1] & 0xe0) { + goto illegal_request; + } + if (!check_lba_range(lu, r->req.cmd.lba, len)) { + goto illegal_lba; + } + r->sector =3D r->req.cmd.lba * (lu->qdev.blocksize / BDRV_SECTOR_S= IZE); + r->sector_count =3D len * (lu->qdev.blocksize / BDRV_SECTOR_SIZE); + break; + case WRITE_6: + case WRITE_10: + trace_ufs_scsi_dma_command_WRITE(r->req.cmd.lba, len); + if (!blk_is_writable(lu->qdev.conf.blk)) { + scsi_check_condition(r, SENSE_CODE(WRITE_PROTECTED)); + return 0; + } + if (r->req.cmd.buf[1] & 0xe0) { + goto illegal_request; + } + if (!check_lba_range(lu, r->req.cmd.lba, len)) { + goto illegal_lba; + } + r->sector =3D r->req.cmd.lba * (lu->qdev.blocksize / BDRV_SECTOR_S= IZE); + r->sector_count =3D len * (lu->qdev.blocksize / BDRV_SECTOR_SIZE); + break; + default: + abort(); + illegal_request: + scsi_check_condition(r, SENSE_CODE(INVALID_FIELD)); + return 0; + illegal_lba: + scsi_check_condition(r, SENSE_CODE(LBA_OUT_OF_RANGE)); + return 0; + } + r->need_fua_emulation =3D ((r->req.cmd.buf[1] & 8) !=3D 0); + if (r->sector_count =3D=3D 0) { + scsi_req_complete(&r->req, GOOD); + } + assert(r->iov.iov_len =3D=3D 0); + if (r->req.cmd.mode =3D=3D SCSI_XFER_TO_DEV) { + return -r->sector_count * BDRV_SECTOR_SIZE; + } else { + return r->sector_count * BDRV_SECTOR_SIZE; + } +} + +static void scsi_write_do_fua(UfsSCSIReq *r) +{ + UfsLu *lu =3D DO_UPCAST(UfsLu, qdev, r->req.dev); + + assert(r->req.aiocb =3D=3D NULL); + assert(!r->req.io_canceled); + + if (r->need_fua_emulation) { + block_acct_start(blk_get_stats(lu->qdev.conf.blk), &r->acct, 0, + BLOCK_ACCT_FLUSH); + r->req.aiocb =3D blk_aio_flush(lu->qdev.conf.blk, scsi_aio_complet= e, r); + return; + } + + scsi_req_complete(&r->req, GOOD); + scsi_req_unref(&r->req); +} + +static void scsi_dma_complete_noio(UfsSCSIReq *r, int ret) +{ + assert(r->req.aiocb =3D=3D NULL); + if (ufs_scsi_req_check_error(r, ret, false)) { + goto done; + } + + r->sector +=3D r->sector_count; + r->sector_count =3D 0; + if (r->req.cmd.mode =3D=3D SCSI_XFER_TO_DEV) { + scsi_write_do_fua(r); + return; + } else { + scsi_req_complete(&r->req, GOOD); + } + +done: + scsi_req_unref(&r->req); +} + +static void scsi_dma_complete(void *opaque, int ret) +{ + UfsSCSIReq *r =3D (UfsSCSIReq *)opaque; + UfsLu *lu =3D DO_UPCAST(UfsLu, qdev, r->req.dev); + + assert(r->req.aiocb !=3D NULL); + r->req.aiocb =3D NULL; + + aio_context_acquire(blk_get_aio_context(lu->qdev.conf.blk)); + if (ret < 0) { + block_acct_failed(blk_get_stats(lu->qdev.conf.blk), &r->acct); + } else { + block_acct_done(blk_get_stats(lu->qdev.conf.blk), &r->acct); + } + scsi_dma_complete_noio(r, ret); + aio_context_release(blk_get_aio_context(lu->qdev.conf.blk)); +} + +static BlockAIOCB *scsi_dma_readv(int64_t offset, QEMUIOVector *iov, + BlockCompletionFunc *cb, void *cb_opaque, + void *opaque) +{ + UfsSCSIReq *r =3D opaque; + UfsLu *lu =3D DO_UPCAST(UfsLu, qdev, r->req.dev); + return blk_aio_preadv(lu->qdev.conf.blk, offset, iov, 0, cb, cb_opaque= ); +} + +static void scsi_init_iovec(UfsSCSIReq *r, size_t size) +{ + UfsLu *lu =3D DO_UPCAST(UfsLu, qdev, r->req.dev); + + if (!r->iov.iov_base) { + r->buflen =3D size; + r->iov.iov_base =3D blk_blockalign(lu->qdev.conf.blk, r->buflen); + } + r->iov.iov_len =3D MIN(r->sector_count * BDRV_SECTOR_SIZE, r->buflen); + qemu_iovec_init_external(&r->qiov, &r->iov, 1); +} + +static void scsi_read_complete_noio(UfsSCSIReq *r, int ret) +{ + uint32_t n; + + assert(r->req.aiocb =3D=3D NULL); + if (ufs_scsi_req_check_error(r, ret, false)) { + goto done; + } + + n =3D r->qiov.size / BDRV_SECTOR_SIZE; + r->sector +=3D n; + r->sector_count -=3D n; + scsi_req_data(&r->req, r->qiov.size); + +done: + scsi_req_unref(&r->req); +} + +static void scsi_read_complete(void *opaque, int ret) +{ + UfsSCSIReq *r =3D (UfsSCSIReq *)opaque; + UfsLu *lu =3D DO_UPCAST(UfsLu, qdev, r->req.dev); + + assert(r->req.aiocb !=3D NULL); + r->req.aiocb =3D NULL; + trace_ufs_scsi_read_data_count(r->sector_count); + aio_context_acquire(blk_get_aio_context(lu->qdev.conf.blk)); + if (ret < 0) { + block_acct_failed(blk_get_stats(lu->qdev.conf.blk), &r->acct); + } else { + block_acct_done(blk_get_stats(lu->qdev.conf.blk), &r->acct); + trace_ufs_scsi_read_complete(r->req.tag, r->qiov.size); + } + scsi_read_complete_noio(r, ret); + aio_context_release(blk_get_aio_context(lu->qdev.conf.blk)); +} + +/* Actually issue a read to the block device. */ +static void scsi_do_read(UfsSCSIReq *r, int ret) +{ + UfsLu *lu =3D DO_UPCAST(UfsLu, qdev, r->req.dev); + + assert(r->req.aiocb =3D=3D NULL); + if (ufs_scsi_req_check_error(r, ret, false)) { + goto done; + } + + /* The request is used as the AIO opaque value, so add a ref. */ + scsi_req_ref(&r->req); + + if (r->req.sg) { + dma_acct_start(lu->qdev.conf.blk, &r->acct, r->req.sg, BLOCK_ACCT_= READ); + r->req.residual -=3D r->req.sg->size; + r->req.aiocb =3D dma_blk_io( + blk_get_aio_context(lu->qdev.conf.blk), r->req.sg, + r->sector << BDRV_SECTOR_BITS, BDRV_SECTOR_SIZE, scsi_dma_read= v, r, + scsi_dma_complete, r, DMA_DIRECTION_FROM_DEVICE); + } else { + scsi_init_iovec(r, SCSI_DMA_BUF_SIZE); + block_acct_start(blk_get_stats(lu->qdev.conf.blk), &r->acct, + r->qiov.size, BLOCK_ACCT_READ); + r->req.aiocb =3D scsi_dma_readv(r->sector << BDRV_SECTOR_BITS, &r-= >qiov, + scsi_read_complete, r, r); + } + +done: + scsi_req_unref(&r->req); +} + +static void scsi_do_read_cb(void *opaque, int ret) +{ + UfsSCSIReq *r =3D (UfsSCSIReq *)opaque; + UfsLu *lu =3D DO_UPCAST(UfsLu, qdev, r->req.dev); + + assert(r->req.aiocb !=3D NULL); + r->req.aiocb =3D NULL; + + aio_context_acquire(blk_get_aio_context(lu->qdev.conf.blk)); + if (ret < 0) { + block_acct_failed(blk_get_stats(lu->qdev.conf.blk), &r->acct); + } else { + block_acct_done(blk_get_stats(lu->qdev.conf.blk), &r->acct); + } + scsi_do_read(opaque, ret); + aio_context_release(blk_get_aio_context(lu->qdev.conf.blk)); +} + +/* Read more data from scsi device into buffer. */ +static void scsi_read_data(SCSIRequest *req) +{ + UfsSCSIReq *r =3D DO_UPCAST(UfsSCSIReq, req, req); + UfsLu *lu =3D DO_UPCAST(UfsLu, qdev, r->req.dev); + bool first; + + trace_ufs_scsi_read_data_count(r->sector_count); + if (r->sector_count =3D=3D 0) { + /* This also clears the sense buffer for REQUEST SENSE. */ + scsi_req_complete(&r->req, GOOD); + return; + } + + /* No data transfer may already be in progress */ + assert(r->req.aiocb =3D=3D NULL); + + /* The request is used as the AIO opaque value, so add a ref. */ + scsi_req_ref(&r->req); + if (r->req.cmd.mode =3D=3D SCSI_XFER_TO_DEV) { + trace_ufs_scsi_read_data_invalid(); + scsi_read_complete_noio(r, -EINVAL); + return; + } + + if (!blk_is_available(req->dev->conf.blk)) { + scsi_read_complete_noio(r, -ENOMEDIUM); + return; + } + + first =3D !r->started; + r->started =3D true; + if (first && r->need_fua_emulation) { + block_acct_start(blk_get_stats(lu->qdev.conf.blk), &r->acct, 0, + BLOCK_ACCT_FLUSH); + r->req.aiocb =3D blk_aio_flush(lu->qdev.conf.blk, scsi_do_read_cb,= r); + } else { + scsi_do_read(r, 0); + } +} + +static void scsi_write_complete_noio(UfsSCSIReq *r, int ret) +{ + uint32_t n; + + assert(r->req.aiocb =3D=3D NULL); + if (ufs_scsi_req_check_error(r, ret, false)) { + goto done; + } + + n =3D r->qiov.size / BDRV_SECTOR_SIZE; + r->sector +=3D n; + r->sector_count -=3D n; + if (r->sector_count =3D=3D 0) { + scsi_write_do_fua(r); + return; + } else { + scsi_init_iovec(r, SCSI_DMA_BUF_SIZE); + trace_ufs_scsi_write_complete_noio(r->req.tag, r->qiov.size); + scsi_req_data(&r->req, r->qiov.size); + } + +done: + scsi_req_unref(&r->req); +} + +static void scsi_write_complete(void *opaque, int ret) +{ + UfsSCSIReq *r =3D (UfsSCSIReq *)opaque; + UfsLu *lu =3D DO_UPCAST(UfsLu, qdev, r->req.dev); + + assert(r->req.aiocb !=3D NULL); + r->req.aiocb =3D NULL; + + aio_context_acquire(blk_get_aio_context(lu->qdev.conf.blk)); + if (ret < 0) { + block_acct_failed(blk_get_stats(lu->qdev.conf.blk), &r->acct); + } else { + block_acct_done(blk_get_stats(lu->qdev.conf.blk), &r->acct); + } + scsi_write_complete_noio(r, ret); + aio_context_release(blk_get_aio_context(lu->qdev.conf.blk)); +} + +static BlockAIOCB *scsi_dma_writev(int64_t offset, QEMUIOVector *iov, + BlockCompletionFunc *cb, void *cb_opaqu= e, + void *opaque) +{ + UfsSCSIReq *r =3D opaque; + UfsLu *lu =3D DO_UPCAST(UfsLu, qdev, r->req.dev); + return blk_aio_pwritev(lu->qdev.conf.blk, offset, iov, 0, cb, cb_opaqu= e); +} + +static void scsi_write_data(SCSIRequest *req) +{ + UfsSCSIReq *r =3D DO_UPCAST(UfsSCSIReq, req, req); + UfsLu *lu =3D DO_UPCAST(UfsLu, qdev, r->req.dev); + + /* No data transfer may already be in progress */ + assert(r->req.aiocb =3D=3D NULL); + + /* The request is used as the AIO opaque value, so add a ref. */ + scsi_req_ref(&r->req); + if (r->req.cmd.mode !=3D SCSI_XFER_TO_DEV) { + trace_ufs_scsi_write_data_invalid(); + scsi_write_complete_noio(r, -EINVAL); + return; + } + + if (!r->req.sg && !r->qiov.size) { + /* Called for the first time. Ask the driver to send us more data= . */ + r->started =3D true; + scsi_write_complete_noio(r, 0); + return; + } + if (!blk_is_available(req->dev->conf.blk)) { + scsi_write_complete_noio(r, -ENOMEDIUM); + return; + } + + if (r->req.sg) { + dma_acct_start(lu->qdev.conf.blk, &r->acct, r->req.sg, + BLOCK_ACCT_WRITE); + r->req.residual -=3D r->req.sg->size; + r->req.aiocb =3D dma_blk_io( + blk_get_aio_context(lu->qdev.conf.blk), r->req.sg, + r->sector << BDRV_SECTOR_BITS, BDRV_SECTOR_SIZE, scsi_dma_writ= ev, r, + scsi_dma_complete, r, DMA_DIRECTION_TO_DEVICE); + } else { + block_acct_start(blk_get_stats(lu->qdev.conf.blk), &r->acct, + r->qiov.size, BLOCK_ACCT_WRITE); + r->req.aiocb =3D scsi_dma_writev(r->sector << BDRV_SECTOR_BITS, &r= ->qiov, + scsi_write_complete, r, r); + } +} + +static const SCSIReqOps ufs_scsi_emulate_reqops =3D { + .size =3D sizeof(UfsSCSIReq), + .free_req =3D ufs_scsi_free_request, + .send_command =3D ufs_scsi_emulate_command, + .read_data =3D ufs_scsi_emulate_read_data, + .write_data =3D ufs_scsi_emulate_write_data, + .get_buf =3D ufs_scsi_get_buf, +}; + +static const SCSIReqOps ufs_scsi_dma_reqops =3D { + .size =3D sizeof(UfsSCSIReq), + .free_req =3D ufs_scsi_free_request, + .send_command =3D ufs_scsi_dma_command, + .read_data =3D scsi_read_data, + .write_data =3D scsi_write_data, + .get_buf =3D ufs_scsi_get_buf, +}; + +/* + * Following commands are not yet supported + * PRE_FETCH(10), + * UNMAP, + * WRITE_BUFFER, READ_BUFFER, + * SECURITY_PROTOCOL_IN, SECURITY_PROTOCOL_OUT + */ +static const SCSIReqOps *const ufs_scsi_reqops_dispatch[256] =3D { + [TEST_UNIT_READY] =3D &ufs_scsi_emulate_reqops, + [INQUIRY] =3D &ufs_scsi_emulate_reqops, + [MODE_SENSE_10] =3D &ufs_scsi_emulate_reqops, + [START_STOP] =3D &ufs_scsi_emulate_reqops, + [READ_CAPACITY_10] =3D &ufs_scsi_emulate_reqops, + [REQUEST_SENSE] =3D &ufs_scsi_emulate_reqops, + [SYNCHRONIZE_CACHE] =3D &ufs_scsi_emulate_reqops, + [MODE_SELECT_10] =3D &ufs_scsi_emulate_reqops, + [VERIFY_10] =3D &ufs_scsi_emulate_reqops, + [FORMAT_UNIT] =3D &ufs_scsi_emulate_reqops, + [SERVICE_ACTION_IN_16] =3D &ufs_scsi_emulate_reqops, + [SEND_DIAGNOSTIC] =3D &ufs_scsi_emulate_reqops, + + [READ_6] =3D &ufs_scsi_dma_reqops, + [READ_10] =3D &ufs_scsi_dma_reqops, + [WRITE_6] =3D &ufs_scsi_dma_reqops, + [WRITE_10] =3D &ufs_scsi_dma_reqops, +}; + +static SCSIRequest *scsi_new_request(SCSIDevice *dev, uint32_t tag, + uint32_t lun, uint8_t *buf, + void *hba_private) +{ + UfsLu *lu =3D DO_UPCAST(UfsLu, qdev, dev); + SCSIRequest *req; + const SCSIReqOps *ops; + uint8_t command; + + command =3D buf[0]; + ops =3D ufs_scsi_reqops_dispatch[command]; + if (!ops) { + ops =3D &ufs_scsi_emulate_reqops; + } + req =3D scsi_req_alloc(ops, &lu->qdev, tag, lun, hba_private); + + return req; +} + +static Property ufs_lu_props[] =3D { + DEFINE_PROP_DRIVE("drive", UfsLu, qdev.conf.blk), + DEFINE_PROP_END_OF_LIST(), +}; + +static bool ufs_lu_brdv_init(UfsLu *lu, Error **errp) +{ + SCSIDevice *dev =3D &lu->qdev; + bool read_only; + + if (!lu->qdev.conf.blk) { + error_setg(errp, "drive property not set"); + return false; + } + + if (!blkconf_blocksizes(&lu->qdev.conf, errp)) { + return false; + } + + if (blk_get_aio_context(lu->qdev.conf.blk) !=3D qemu_get_aio_context()= && + !lu->qdev.hba_supports_iothread) { + error_setg(errp, "HBA does not support iothreads"); + return false; + } + + read_only =3D !blk_supports_write_perm(lu->qdev.conf.blk); + + if (!blkconf_apply_backend_options(&dev->conf, read_only, + dev->type =3D=3D TYPE_DISK, errp)) { + return false; + } + + if (blk_is_sg(lu->qdev.conf.blk)) { + error_setg(errp, "unwanted /dev/sg*"); + return false; + } + + blk_iostatus_enable(lu->qdev.conf.blk); + return true; +} + +static bool ufs_add_lu(UfsHc *u, UfsLu *lu, Error **errp) +{ + BlockBackend *blk =3D lu->qdev.conf.blk; + int64_t brdv_len =3D blk_getlength(blk); + uint64_t raw_dev_cap =3D + be64_to_cpu(u->geometry_desc.total_raw_device_capacity); + + if (u->device_desc.number_lu >=3D UFS_MAX_LUS) { + error_setg(errp, "ufs host controller has too many logical units."= ); + return false; + } + + if (u->lus[lu->lun] !=3D NULL) { + error_setg(errp, "ufs logical unit %d already exists.", lu->lun); + return false; + } + + u->lus[lu->lun] =3D lu; + u->device_desc.number_lu++; + raw_dev_cap +=3D (brdv_len >> UFS_GEOMETRY_CAPACITY_SHIFT); + u->geometry_desc.total_raw_device_capacity =3D cpu_to_be64(raw_dev_cap= ); + return true; +} + +static inline uint8_t ufs_log2(uint64_t input) +{ + int log =3D 0; + while (input >>=3D 1) { + log++; + } + return log; +} + +static void ufs_init_lu(UfsLu *lu) +{ + BlockBackend *blk =3D lu->qdev.conf.blk; + int64_t brdv_len =3D blk_getlength(blk); + + lu->lun =3D lu->qdev.lun; + memset(&lu->unit_desc, 0, sizeof(lu->unit_desc)); + 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; + lu->unit_desc.logical_block_size =3D ufs_log2(lu->qdev.blocksize); + lu->unit_desc.unit_index =3D lu->qdev.lun; + lu->unit_desc.logical_block_count =3D + cpu_to_be64(brdv_len / (1 << lu->unit_desc.logical_block_size)); +} + +static bool ufs_lu_check_constraints(UfsLu *lu, Error **errp) +{ + if (!lu->qdev.conf.blk) { + error_setg(errp, "drive property not set"); + return false; + } + + if (lu->qdev.channel !=3D 0) { + error_setg(errp, "ufs logical unit does not support channel"); + return false; + } + + if (lu->qdev.lun >=3D UFS_MAX_LUS) { + error_setg(errp, "lun must be between 1 and %d", UFS_MAX_LUS - 1); + return false; + } + + return true; +} + +static void ufs_lu_realize(SCSIDevice *dev, Error **errp) +{ + UfsLu *lu =3D DO_UPCAST(UfsLu, qdev, dev); + BusState *s =3D qdev_get_parent_bus(&dev->qdev); + UfsHc *u =3D UFS(s->parent); + AioContext *ctx =3D NULL; + uint64_t nb_sectors, nb_blocks; + + if (!ufs_lu_check_constraints(lu, errp)) { + return; + } + + if (lu->qdev.conf.blk) { + ctx =3D blk_get_aio_context(lu->qdev.conf.blk); + aio_context_acquire(ctx); + if (!blkconf_blocksizes(&lu->qdev.conf, errp)) { + goto out; + } + } + lu->qdev.blocksize =3D UFS_BLOCK_SIZE; + blk_get_geometry(lu->qdev.conf.blk, &nb_sectors); + nb_blocks =3D nb_sectors / (lu->qdev.blocksize / BDRV_SECTOR_SIZE); + if (nb_blocks > UINT32_MAX) { + nb_blocks =3D UINT32_MAX; + } + lu->qdev.max_lba =3D nb_blocks; + lu->qdev.type =3D TYPE_DISK; + + ufs_init_lu(lu); + if (!ufs_add_lu(u, lu, errp)) { + goto out; + } + + ufs_lu_brdv_init(lu, errp); +out: + if (ctx) { + aio_context_release(ctx); + } +} + +static void ufs_lu_unrealize(SCSIDevice *dev) +{ + UfsLu *lu =3D DO_UPCAST(UfsLu, qdev, dev); + + blk_drain(lu->qdev.conf.blk); +} + +static void ufs_wlu_realize(DeviceState *qdev, Error **errp) +{ + UfsWLu *wlu =3D UFSWLU(qdev); + SCSIDevice *dev =3D &wlu->qdev; + + if (!is_wlun(dev->lun)) { + error_setg(errp, "not well-known logical unit number"); + return; + } + + QTAILQ_INIT(&dev->requests); +} + +static void ufs_lu_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(oc); + SCSIDeviceClass *sc =3D SCSI_DEVICE_CLASS(oc); + + sc->realize =3D ufs_lu_realize; + sc->unrealize =3D ufs_lu_unrealize; + sc->alloc_req =3D scsi_new_request; + dc->bus_type =3D TYPE_UFS_BUS; + device_class_set_props(dc, ufs_lu_props); + dc->desc =3D "Virtual UFS logical unit"; +} + +static void ufs_wlu_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(oc); + SCSIDeviceClass *sc =3D SCSI_DEVICE_CLASS(oc); + + /* + * The realize() function of TYPE_SCSI_DEVICE causes a segmentation fa= ult + * if a block drive does not exist. Define a new realize function for + * well-known LUs that do not have a block drive. + */ + dc->realize =3D ufs_wlu_realize; + sc->alloc_req =3D scsi_new_request; + dc->bus_type =3D TYPE_UFS_BUS; + dc->desc =3D "Virtual UFS well-known logical unit"; +} + +static const TypeInfo ufs_lu_info =3D { + .name =3D TYPE_UFS_LU, + .parent =3D TYPE_SCSI_DEVICE, + .class_init =3D ufs_lu_class_init, + .instance_size =3D sizeof(UfsLu), +}; + +static const TypeInfo ufs_wlu_info =3D { + .name =3D TYPE_UFS_WLU, + .parent =3D TYPE_SCSI_DEVICE, + .class_init =3D ufs_wlu_class_init, + .instance_size =3D sizeof(UfsWLu), +}; + +static void ufs_lu_register_types(void) +{ + type_register_static(&ufs_lu_info); + type_register_static(&ufs_wlu_info); +} + +type_init(ufs_lu_register_types) diff --git a/hw/ufs/meson.build b/hw/ufs/meson.build index eb5164bde9..6e68328b93 100644 --- a/hw/ufs/meson.build +++ b/hw/ufs/meson.build @@ -1 +1 @@ -system_ss.add(when: 'CONFIG_UFS_PCI', if_true: files('ufs.c')) +system_ss.add(when: 'CONFIG_UFS_PCI', if_true: files('ufs.c', 'lu.c')) diff --git a/hw/ufs/trace-events b/hw/ufs/trace-events index 665e1a942b..1e55fb0d08 100644 --- a/hw/ufs/trace-events +++ b/hw/ufs/trace-events @@ -12,6 +12,31 @@ ufs_exec_scsi_cmd(uint32_t slot, uint8_t lun, uint8_t op= code) "slot %"PRIu32", l ufs_exec_query_cmd(uint32_t slot, uint8_t opcode) "slot %"PRIu32", opcode = 0x%"PRIx8"" ufs_process_uiccmd(uint32_t uiccmd, uint32_t ucmdarg1, uint32_t ucmdarg2, = uint32_t ucmdarg3) "uiccmd 0x%"PRIx32", ucmdarg1 0x%"PRIx32", ucmdarg2 0x%"= PRIx32", ucmdarg3 0x%"PRIx32"" =20 +# lu.c +ufs_scsi_check_condition(uint32_t tag, uint8_t key, uint8_t asc, uint8_t a= scq) "Command complete tag=3D0x%x sense=3D%d/%d/%d" +ufs_scsi_read_complete(uint32_t tag, size_t size) "Data ready tag=3D0x%x l= en=3D%zd" +ufs_scsi_read_data_count(uint32_t sector_count) "Read sector_count=3D%d" +ufs_scsi_read_data_invalid(void) "Data transfer direction invalid" +ufs_scsi_write_complete_noio(uint32_t tag, size_t size) "Write complete ta= g=3D0x%x more=3D%zd" +ufs_scsi_write_data_invalid(void) "Data transfer direction invalid" +ufs_scsi_emulate_vpd_page_00(size_t xfer) "Inquiry EVPD[Supported pages] b= uffer size %zd" +ufs_scsi_emulate_vpd_page_80_not_supported(void) "Inquiry EVPD[Serial numb= er] not supported" +ufs_scsi_emulate_vpd_page_80(size_t xfer) "Inquiry EVPD[Serial number] buf= fer size %zd" +ufs_scsi_emulate_vpd_page_87(size_t xfer) "Inquiry EVPD[Mode Page Policy] = buffer size %zd" +ufs_scsi_emulate_mode_sense(int cmd, int page, size_t xfer, int control) "= Mode Sense(%d) (page %d, xfer %zd, page_control %d)" +ufs_scsi_emulate_read_data(int buflen) "Read buf_len=3D%d" +ufs_scsi_emulate_write_data(int buflen) "Write buf_len=3D%d" +ufs_scsi_emulate_command_START_STOP(void) "START STOP UNIT" +ufs_scsi_emulate_command_FORMAT_UNIT(void) "FORMAT UNIT" +ufs_scsi_emulate_command_SEND_DIAGNOSTIC(void) "SEND DIAGNOSTIC" +ufs_scsi_emulate_command_SAI_16(void) "SAI READ CAPACITY(16)" +ufs_scsi_emulate_command_SAI_unsupported(void) "Unsupported Service Action= In" +ufs_scsi_emulate_command_MODE_SELECT_10(size_t xfer) "Mode Select(10) (len= %zd)" +ufs_scsi_emulate_command_VERIFY(int bytchk) "Verify (bytchk %d)" +ufs_scsi_emulate_command_UNKNOWN(int cmd, const char *name) "Unknown SCSI = command (0x%2.2x=3D%s)" +ufs_scsi_dma_command_READ(uint64_t lba, uint32_t len) "Read (block %" PRIu= 64 ", count %u)" +ufs_scsi_dma_command_WRITE(uint64_t lba, int len) "Write (block %" PRIu64 = ", count %u)" + # error condition ufs_err_dma_read_utrd(uint32_t slot, uint64_t addr) "failed to read utrd. = UTRLDBR slot %"PRIu32", UTRD dma addr %"PRIu64"" ufs_err_dma_read_req_upiu(uint32_t slot, uint64_t addr) "failed to read re= q upiu. UTRLDBR slot %"PRIu32", request upiu addr %"PRIu64"" diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c index 56a8ec286b..0ecedb9aed 100644 --- a/hw/ufs/ufs.c +++ b/hw/ufs/ufs.c @@ -8,6 +8,19 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ =20 +/** + * Reference Specs: https://www.jedec.org/, 3.1 + * + * Usage + * ----- + * + * Add options: + * -drive file=3D,if=3Dnone,id=3D + * -device ufs,serial=3D,id=3D, \ + * nutrs=3D,nutmrs=3D + * -device ufs-lu,drive=3D,bus=3D + */ + #include "qemu/osdep.h" #include "qapi/error.h" #include "migration/vmstate.h" @@ -420,6 +433,19 @@ static const MemoryRegionOps ufs_mmio_ops =3D { }, }; =20 +static QEMUSGList *ufs_get_sg_list(SCSIRequest *scsi_req) +{ + UfsRequest *req =3D scsi_req->hba_private; + return req->sg; +} + +static void ufs_build_upiu_sense_data(UfsRequest *req, SCSIRequest *scsi_r= eq) +{ + req->rsp_upiu.sr.sense_data_len =3D cpu_to_be16(scsi_req->sense_len); + assert(scsi_req->sense_len <=3D SCSI_SENSE_LEN); + memcpy(req->rsp_upiu.sr.sense_data, scsi_req->sense, scsi_req->sense_l= en); +} + static void ufs_build_upiu_header(UfsRequest *req, uint8_t trans_type, uint8_t flags, uint8_t response, uint8_t scsi_status, @@ -433,6 +459,98 @@ static void ufs_build_upiu_header(UfsRequest *req, uin= t8_t trans_type, req->rsp_upiu.header.data_segment_length =3D cpu_to_be16(data_segment_= length); } =20 +static void ufs_scsi_command_complete(SCSIRequest *scsi_req, size_t resid) +{ + UfsRequest *req =3D scsi_req->hba_private; + int16_t status =3D scsi_req->status; + uint32_t expected_len =3D be32_to_cpu(req->req_upiu.sc.exp_data_transf= er_len); + uint32_t transfered_len =3D scsi_req->cmd.xfer - resid; + uint8_t flags =3D 0, response =3D UFS_COMMAND_RESULT_SUCESS; + uint16_t data_segment_length; + + if (expected_len > transfered_len) { + req->rsp_upiu.sr.residual_transfer_count =3D + cpu_to_be32(expected_len - transfered_len); + flags |=3D UFS_UPIU_FLAG_UNDERFLOW; + } else if (expected_len < transfered_len) { + req->rsp_upiu.sr.residual_transfer_count =3D + cpu_to_be32(transfered_len - expected_len); + flags |=3D UFS_UPIU_FLAG_OVERFLOW; + } + + if (status !=3D 0) { + ufs_build_upiu_sense_data(req, scsi_req); + response =3D UFS_COMMAND_RESULT_FAIL; + } + + data_segment_length =3D cpu_to_be16(scsi_req->sense_len + + sizeof(req->rsp_upiu.sr.sense_data_l= en)); + ufs_build_upiu_header(req, UFS_UPIU_TRANSACTION_RESPONSE, flags, respo= nse, + status, data_segment_length); + + ufs_complete_req(req, UFS_REQUEST_SUCCESS); + + scsi_req->hba_private =3D NULL; + scsi_req_unref(scsi_req); +} + +static const struct SCSIBusInfo ufs_scsi_info =3D { + .tcq =3D true, + .max_target =3D 0, + .max_lun =3D UFS_MAX_LUS, + .max_channel =3D 0, + + .get_sg_list =3D ufs_get_sg_list, + .complete =3D ufs_scsi_command_complete, +}; + +static UfsReqResult ufs_exec_scsi_cmd(UfsRequest *req) +{ + UfsHc *u =3D req->hc; + uint8_t lun =3D req->req_upiu.header.lun; + uint8_t task_tag =3D req->req_upiu.header.task_tag; + SCSIDevice *dev =3D NULL; + + trace_ufs_exec_scsi_cmd(req->slot, lun, req->req_upiu.sc.cdb[0]); + + if (!is_wlun(lun)) { + if (lun >=3D u->device_desc.number_lu) { + trace_ufs_err_scsi_cmd_invalid_lun(lun); + return UFS_REQUEST_FAIL; + } else if (u->lus[lun] =3D=3D NULL) { + trace_ufs_err_scsi_cmd_invalid_lun(lun); + return UFS_REQUEST_FAIL; + } + } + + switch (lun) { + case UFS_UPIU_REPORT_LUNS_WLUN: + dev =3D &u->report_wlu->qdev; + break; + case UFS_UPIU_UFS_DEVICE_WLUN: + dev =3D &u->dev_wlu->qdev; + break; + case UFS_UPIU_BOOT_WLUN: + dev =3D &u->boot_wlu->qdev; + break; + case UFS_UPIU_RPMB_WLUN: + dev =3D &u->rpmb_wlu->qdev; + break; + default: + dev =3D &u->lus[lun]->qdev; + } + + SCSIRequest *scsi_req =3D scsi_req_new( + dev, task_tag, lun, req->req_upiu.sc.cdb, UFS_CDB_SIZE, req); + + uint32_t len =3D scsi_req_enqueue(scsi_req); + if (len) { + scsi_req_continue(scsi_req); + } + + return UFS_REQUEST_NO_COMPLETE; +} + static UfsReqResult ufs_exec_nop_cmd(UfsRequest *req) { trace_ufs_exec_nop_cmd(req->slot); @@ -716,9 +834,11 @@ static const RpmbUnitDescriptor rpmb_unit_desc =3D { =20 static QueryRespCode ufs_read_unit_desc(UfsRequest *req) { + UfsHc *u =3D req->hc; uint8_t lun =3D req->req_upiu.qr.index; =20 - if (lun !=3D UFS_UPIU_RPMB_WLUN && lun > UFS_MAX_LUS) { + if (lun !=3D UFS_UPIU_RPMB_WLUN && + (lun > UFS_MAX_LUS || u->lus[lun] =3D=3D NULL)) { trace_ufs_err_query_invalid_index(req->req_upiu.qr.opcode, lun); return UFS_QUERY_RESULT_INVALID_INDEX; } @@ -726,8 +846,8 @@ static QueryRespCode ufs_read_unit_desc(UfsRequest *req) if (lun =3D=3D UFS_UPIU_RPMB_WLUN) { memcpy(&req->rsp_upiu.qr.data, &rpmb_unit_desc, rpmb_unit_desc.len= gth); } else { - /* unit descriptor is not yet supported */ - return UFS_QUERY_RESULT_INVALID_INDEX; + memcpy(&req->rsp_upiu.qr.data, &u->lus[lun]->unit_desc, + sizeof(u->lus[lun]->unit_desc)); } =20 return UFS_QUERY_RESULT_SUCCESS; @@ -977,8 +1097,7 @@ static void ufs_exec_req(UfsRequest *req) req_result =3D ufs_exec_nop_cmd(req); break; case UFS_UPIU_TRANSACTION_COMMAND: - /* Not yet implemented */ - req_result =3D UFS_REQUEST_FAIL; + req_result =3D ufs_exec_scsi_cmd(req); break; case UFS_UPIU_TRANSACTION_QUERY_REQ: req_result =3D ufs_exec_query_cmd(req); @@ -989,7 +1108,14 @@ static void ufs_exec_req(UfsRequest *req) req_result =3D UFS_REQUEST_FAIL; } =20 - ufs_complete_req(req, req_result); + /* + * The ufs_complete_req for scsi commands is handled by the + * ufs_scsi_command_complete() callback function. Therefore, to avoid + * duplicate processing, ufs_complete_req() is not called for scsi com= mands. + */ + if (req_result !=3D UFS_REQUEST_NO_COMPLETE) { + ufs_complete_req(req, req_result); + } } =20 static void ufs_process_req(void *opaque) @@ -1191,6 +1317,28 @@ static void ufs_init_hc(UfsHc *u) u->flags.permanently_disable_fw_update =3D 1; } =20 +static bool ufs_init_wlu(UfsHc *u, UfsWLu **wlu, uint8_t wlun, Error **err= p) +{ + UfsWLu *new_wlu =3D UFSWLU(qdev_new(TYPE_UFS_WLU)); + + qdev_prop_set_uint32(DEVICE(new_wlu), "lun", wlun); + + /* + * The well-known lu shares the same bus as the normal lu. If the well= -known + * lu writes the same channel value as the normal lu, the report will = be + * made not only for the normal lu but also for the well-known lu at + * REPORT_LUN time. To prevent this, the channel value of normal lu is= fixed + * to 0 and the channel value of well-known lu is fixed to 1. + */ + qdev_prop_set_uint32(DEVICE(new_wlu), "channel", 1); + if (!qdev_realize_and_unref(DEVICE(new_wlu), BUS(&u->bus), errp)) { + return false; + } + + *wlu =3D new_wlu; + return true; +} + static void ufs_realize(PCIDevice *pci_dev, Error **errp) { UfsHc *u =3D UFS(pci_dev); @@ -1199,15 +1347,55 @@ static void ufs_realize(PCIDevice *pci_dev, Error *= *errp) return; } =20 + qbus_init(&u->bus, sizeof(UfsBus), TYPE_UFS_BUS, &pci_dev->qdev, + u->parent_obj.qdev.id); + u->bus.parent_bus.info =3D &ufs_scsi_info; + ufs_init_state(u); ufs_init_hc(u); ufs_init_pci(u, pci_dev); + + if (!ufs_init_wlu(u, &u->report_wlu, UFS_UPIU_REPORT_LUNS_WLUN, errp))= { + return; + } + + if (!ufs_init_wlu(u, &u->dev_wlu, UFS_UPIU_UFS_DEVICE_WLUN, errp)) { + return; + } + + if (!ufs_init_wlu(u, &u->boot_wlu, UFS_UPIU_BOOT_WLUN, errp)) { + return; + } + + if (!ufs_init_wlu(u, &u->rpmb_wlu, UFS_UPIU_RPMB_WLUN, errp)) { + return; + } } =20 static void ufs_exit(PCIDevice *pci_dev) { UfsHc *u =3D UFS(pci_dev); =20 + if (u->dev_wlu) { + object_unref(OBJECT(u->dev_wlu)); + u->dev_wlu =3D NULL; + } + + if (u->report_wlu) { + object_unref(OBJECT(u->report_wlu)); + u->report_wlu =3D NULL; + } + + if (u->rpmb_wlu) { + object_unref(OBJECT(u->rpmb_wlu)); + u->rpmb_wlu =3D NULL; + } + + if (u->boot_wlu) { + object_unref(OBJECT(u->boot_wlu)); + u->boot_wlu =3D NULL; + } + qemu_bh_delete(u->doorbell_bh); qemu_bh_delete(u->complete_bh); =20 @@ -1246,6 +1434,49 @@ static void ufs_class_init(ObjectClass *oc, void *da= ta) dc->vmsd =3D &ufs_vmstate; } =20 +static bool ufs_bus_check_address(BusState *qbus, DeviceState *qdev, + Error **errp) +{ + SCSIDevice *dev =3D SCSI_DEVICE(qdev); + UfsBusClass *ubc =3D UFS_BUS_GET_CLASS(qbus); + UfsHc *u =3D UFS(qbus->parent); + + if (strcmp(object_get_typename(OBJECT(dev)), TYPE_UFS_WLU) =3D=3D 0) { + if (dev->lun !=3D UFS_UPIU_REPORT_LUNS_WLUN && + dev->lun !=3D UFS_UPIU_UFS_DEVICE_WLUN && + dev->lun !=3D UFS_UPIU_BOOT_WLUN && dev->lun !=3D UFS_UPIU_RPM= B_WLUN) { + error_setg(errp, "bad well-known lun: %d", dev->lun); + return false; + } + + if ((dev->lun =3D=3D UFS_UPIU_REPORT_LUNS_WLUN && u->report_wlu != =3D NULL) || + (dev->lun =3D=3D UFS_UPIU_UFS_DEVICE_WLUN && u->dev_wlu !=3D N= ULL) || + (dev->lun =3D=3D UFS_UPIU_BOOT_WLUN && u->boot_wlu !=3D NULL) = || + (dev->lun =3D=3D UFS_UPIU_RPMB_WLUN && u->rpmb_wlu !=3D NULL))= { + error_setg(errp, "well-known lun %d already exists", dev->lun); + return false; + } + + return true; + } + + if (strcmp(object_get_typename(OBJECT(dev)), TYPE_UFS_LU) !=3D 0) { + error_setg(errp, "%s cannot be connected to ufs-bus", + object_get_typename(OBJECT(dev))); + return false; + } + + return ubc->parent_check_address(qbus, qdev, errp); +} + +static void ufs_bus_class_init(ObjectClass *class, void *data) +{ + BusClass *bc =3D BUS_CLASS(class); + UfsBusClass *ubc =3D UFS_BUS_CLASS(class); + ubc->parent_check_address =3D bc->check_address; + bc->check_address =3D ufs_bus_check_address; +} + static const TypeInfo ufs_info =3D { .name =3D TYPE_UFS, .parent =3D TYPE_PCI_DEVICE, @@ -1254,9 +1485,18 @@ static const TypeInfo ufs_info =3D { .interfaces =3D (InterfaceInfo[]){ { INTERFACE_PCIE_DEVICE }, {} }, }; =20 +static const TypeInfo ufs_bus_info =3D { + .name =3D TYPE_UFS_BUS, + .parent =3D TYPE_SCSI_BUS, + .class_init =3D ufs_bus_class_init, + .class_size =3D sizeof(UfsBusClass), + .instance_size =3D sizeof(UfsBus), +}; + static void ufs_register_types(void) { type_register_static(&ufs_info); + type_register_static(&ufs_bus_info); } =20 type_init(ufs_register_types) diff --git a/hw/ufs/ufs.h b/hw/ufs/ufs.h index 3d1b2cff4e..f244228617 100644 --- a/hw/ufs/ufs.h +++ b/hw/ufs/ufs.h @@ -18,6 +18,18 @@ #define UFS_MAX_LUS 32 #define UFS_BLOCK_SIZE 4096 =20 +typedef struct UfsBusClass { + BusClass parent_class; + bool (*parent_check_address)(BusState *bus, DeviceState *dev, Error **= errp); +} UfsBusClass; + +typedef struct UfsBus { + SCSIBus parent_bus; +} UfsBus; + +#define TYPE_UFS_BUS "ufs-bus" +DECLARE_OBJ_CHECKERS(UfsBus, UfsBusClass, UFS_BUS, TYPE_UFS_BUS) + typedef enum UfsRequestState { UFS_REQUEST_IDLE =3D 0, UFS_REQUEST_READY =3D 1, @@ -29,6 +41,7 @@ typedef enum UfsRequestState { typedef enum UfsReqResult { UFS_REQUEST_SUCCESS =3D 0, UFS_REQUEST_FAIL =3D 1, + UFS_REQUEST_NO_COMPLETE =3D 2, } UfsReqResult; =20 typedef struct UfsRequest { @@ -44,6 +57,17 @@ typedef struct UfsRequest { QEMUSGList *sg; } UfsRequest; =20 +typedef struct UfsLu { + SCSIDevice qdev; + uint8_t lun; + UnitDescriptor unit_desc; +} UfsLu; + +typedef struct UfsWLu { + SCSIDevice qdev; + uint8_t lun; +} UfsWLu; + typedef struct UfsParams { char *serial; uint8_t nutrs; /* Number of UTP Transfer Request Slots */ @@ -52,12 +76,18 @@ typedef struct UfsParams { =20 typedef struct UfsHc { PCIDevice parent_obj; + UfsBus bus; MemoryRegion iomem; UfsReg reg; UfsParams params; uint32_t reg_size; UfsRequest *req_list; =20 + UfsLu *lus[UFS_MAX_LUS]; + UfsWLu *report_wlu; + UfsWLu *dev_wlu; + UfsWLu *boot_wlu; + UfsWLu *rpmb_wlu; DeviceDescriptor device_desc; GeometryDescriptor geometry_desc; Attributes attributes; @@ -71,6 +101,12 @@ typedef struct UfsHc { #define TYPE_UFS "ufs" #define UFS(obj) OBJECT_CHECK(UfsHc, (obj), TYPE_UFS) =20 +#define TYPE_UFS_LU "ufs-lu" +#define UFSLU(obj) OBJECT_CHECK(UfsLu, (obj), TYPE_UFS_LU) + +#define TYPE_UFS_WLU "ufs-wlu" +#define UFSWLU(obj) OBJECT_CHECK(UfsWLu, (obj), TYPE_UFS_WLU) + typedef enum UfsQueryFlagPerm { UFS_QUERY_FLAG_NONE =3D 0x0, UFS_QUERY_FLAG_READ =3D 0x1, @@ -85,4 +121,11 @@ typedef enum UfsQueryAttrPerm { UFS_QUERY_ATTR_WRITE =3D 0x2, } UfsQueryAttrPerm; =20 +static inline bool is_wlun(uint8_t lun) +{ + return (lun =3D=3D UFS_UPIU_REPORT_LUNS_WLUN || + lun =3D=3D UFS_UPIU_UFS_DEVICE_WLUN || lun =3D=3D UFS_UPIU_BOO= T_WLUN || + lun =3D=3D UFS_UPIU_RPMB_WLUN); +} + #endif /* HW_UFS_UFS_H */ diff --git a/include/scsi/constants.h b/include/scsi/constants.h index 6a8bad556a..9b98451912 100644 --- a/include/scsi/constants.h +++ b/include/scsi/constants.h @@ -231,6 +231,7 @@ #define MODE_PAGE_FLEXIBLE_DISK_GEOMETRY 0x05 #define MODE_PAGE_CACHING 0x08 #define MODE_PAGE_AUDIO_CTL 0x0e +#define MODE_PAGE_CONTROL 0x0a #define MODE_PAGE_POWER 0x1a #define MODE_PAGE_FAULT_FAIL 0x1c #define MODE_PAGE_TO_PROTECT 0x1d --=20 2.34.1 From nobody Sun May 12 20:04:00 2024 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=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1693986389; cv=none; d=zohomail.com; s=zohoarc; b=AryZE1AMwpMXFZIjrXxNwqlQuJRGgiolBXM0BvUHSMlWmtKQNdO6qhGfvOkt2ZhsQmmFfF4v5DYjJVv7JHYQZDsCKc94zNZ36H23X3arwyKcwJiGPYPBWXzhAKfstZD2Cc9gTp7i0rbNsD31EloWeHZeRNAHi1Wv8uZJ2RmD2CA= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1693986389; h=Content-Transfer-Encoding:Cc:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To; bh=1LgUTmOh8UjEcqGMDtSBWRP15PAajwrzPF1+820Rd1E=; b=Bu4z7hVEDfZ+teKHD4QcgJ5VqTIWp1BwfykHBHE1Yzu6qgT94Xud+91FqJCDbK55LBfzqRlbkydYIESoId3mnUzOqTS837Vl+we60QrG5hBYYhFLKNVz/ojerye+CDab0MehBO1Uwrnk8EGxACB51piRone+akKVo3mR7xOVk/c= 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 lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1693986389020733.96124592282; Wed, 6 Sep 2023 00:46:29 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1qdnEB-0007qi-Ct; Wed, 06 Sep 2023 03:45:31 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1qdnDo-0007dJ-HM; Wed, 06 Sep 2023 03:45:15 -0400 Received: from mail-pj1-x1030.google.com ([2607:f8b0:4864:20::1030]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1qdnDk-00051y-5O; Wed, 06 Sep 2023 03:45:07 -0400 Received: by mail-pj1-x1030.google.com with SMTP id 98e67ed59e1d1-271c700efb2so1886973a91.0; Wed, 06 Sep 2023 00:45:01 -0700 (PDT) Received: from jeuk-MS-7D42.. ([218.147.112.168]) by smtp.gmail.com with ESMTPSA id ck1-20020a17090afe0100b00262eb0d141esm10434901pjb.28.2023.09.06.00.44.56 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 06 Sep 2023 00:44:59 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20221208; t=1693986300; x=1694591100; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=1LgUTmOh8UjEcqGMDtSBWRP15PAajwrzPF1+820Rd1E=; b=iATvTDwnjc4KlRbPEKy4O9oJwLPtdo01kCzbE/IajfL8lW+DuR2Vhdxjd60jR85zKn 8IPsJ68hgfBAOb+PpcAIlXorF28666xKxLn2xl2qovVAaziQ1CJQmCtEDL/+JX4B6KnC aWItPO0r0JKhbA+F3pq88fVLYPpOnJakCpjA0KKgPUMOXpo89nbZSseK24RzNTH0SjGD dRfX5+A+qHJ9xejSzK+aJUCaYdBEh81W1bWYKY7wj5Kuv/zdG5MDH2fClFah81hn0JcJ 4tXXZxQ+PEDbCMOwkim4wxQKQXNOnVR5qGV4fA2zgTlPBIj0SqkKuC2Tuh5nt8q7PHuQ WQ2g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1693986300; x=1694591100; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=1LgUTmOh8UjEcqGMDtSBWRP15PAajwrzPF1+820Rd1E=; b=MBb4G4Nyn5KlNxPOMWXEZi73GDIXF2IIKkKm5wRCY/Yfp5HMJayrJW2MOP/pBYMPJd KSEz451bnY5E2pjVvKJYPD3pZOYVwFEA2N8g5z4dpucydBYXgIPv8HExGcenq9Rkq5OL NncHENrBXVy6aiR0yCgkHs8EsTnJdGkcVyUREhWiG+z0tmphng1KPbQhKXfzAS9n+4eB qLa75CF1OCZ4TDGiS7d8frKqRWqs6XvVgRedLBOyjNkyYKZrEALKXzQEzipzCCd2eD8D Wh0X3+6WZ+lIyR2CRxtup1r//ZU+K5xUEL+lO1NNo6rgy2/piAAZErRVfTIAVi16jZNx eI6g== X-Gm-Message-State: AOJu0YyOHcDaPOZ95L9hmyVMXLqcEJHjS61ZCvwPKpxMSE50aP9w7PcE uy9Rz21lwGQXFnBHfU6PKLJcYNcMs9KHfw== X-Google-Smtp-Source: AGHT+IG2mJqCrtTZ8hCHOMTu5H1r1LoXy0oN/Z9nDg8tnrFOdFxOL0PNaVOTbMadw8sef7Yp4lstTQ== X-Received: by 2002:a17:90b:3716:b0:26d:1201:a8c4 with SMTP id mg22-20020a17090b371600b0026d1201a8c4mr11436223pjb.13.1693986299984; Wed, 06 Sep 2023 00:44:59 -0700 (PDT) From: Jeuk Kim To: qemu-devel@nongnu.org Cc: jeuk20.kim@gmail.com, berrange@redhat.com, fam@euphon.net, hreitz@redhat.com, jeuk20.kim@samsung.com, k.jensen@samsung.com, kwolf@redhat.com, lvivier@redhat.com, marcandre.lureau@redhat.com, marcel.apfelbaum@gmail.com, mst@redhat.com, pbonzini@redhat.com, philmd@linaro.org, qemu-block@nongnu.org, stefanha@redhat.com, thuth@redhat.com Subject: [PATCH v10 4/4] tests/qtest: Introduce tests for UFS Date: Wed, 6 Sep 2023 16:43:51 +0900 Message-Id: <9e9207f54505e9ba30931849f949ff6f474ac333.1693980783.git.jeuk20.kim@gmail.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable 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=lists.gnu.org; Received-SPF: pass client-ip=2607:f8b0:4864:20::1030; envelope-from=jeuk20.kim@gmail.com; helo=mail-pj1-x1030.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-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: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1693986390609100003 Content-Type: text/plain; charset="utf-8" From: Jeuk Kim This patch includes the following tests Test mmio read Test ufs device initialization and ufs-lu recognition Test I/O (Performs a write followed by a read to verify) Signed-off-by: Jeuk Kim Acked-by: Thomas Huth Reviewed-by: Stefan Hajnoczi --- MAINTAINERS | 1 + tests/qtest/meson.build | 1 + tests/qtest/ufs-test.c | 587 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 589 insertions(+) create mode 100644 tests/qtest/ufs-test.c diff --git a/MAINTAINERS b/MAINTAINERS index 85cb0f261e..84af034447 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2253,6 +2253,7 @@ M: Jeuk Kim S: Supported F: hw/ufs/* F: include/block/ufs.h +F: tests/qtest/ufs-test.c =20 megasas M: Hannes Reinecke diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index 3afe9e9ee3..8c08604e6d 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -269,6 +269,7 @@ qos_test_ss.add( 'virtio-iommu-test.c', 'vmxnet3-test.c', 'igb-test.c', + 'ufs-test.c', ) =20 if config_all_devices.has_key('CONFIG_VIRTIO_SERIAL') diff --git a/tests/qtest/ufs-test.c b/tests/qtest/ufs-test.c new file mode 100644 index 0000000000..ed3dbca154 --- /dev/null +++ b/tests/qtest/ufs-test.c @@ -0,0 +1,587 @@ +/* + * QTest testcase for UFS + * + * Copyright (c) 2023 Samsung Electronics Co., Ltd. All rights reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/module.h" +#include "qemu/units.h" +#include "libqtest.h" +#include "libqos/qgraph.h" +#include "libqos/pci.h" +#include "scsi/constants.h" +#include "include/block/ufs.h" + +/* Test images sizes in Bytes */ +#define TEST_IMAGE_SIZE (64 * 1024 * 1024) +/* Timeout for various operations, in seconds. */ +#define TIMEOUT_SECONDS 10 +/* Maximum PRD entry count */ +#define MAX_PRD_ENTRY_COUNT 10 +#define PRD_ENTRY_DATA_SIZE 4096 +/* Constants to build upiu */ +#define UTP_COMMAND_DESCRIPTOR_SIZE 4096 +#define UTP_RESPONSE_UPIU_OFFSET 1024 +#define UTP_PRDT_UPIU_OFFSET 2048 + +typedef struct QUfs QUfs; + +struct QUfs { + QOSGraphObject obj; + QPCIDevice dev; + QPCIBar bar; + + uint64_t utrlba; + uint64_t utmrlba; + uint64_t cmd_desc_addr; + uint64_t data_buffer_addr; + + bool enabled; +}; + +static inline uint32_t ufs_rreg(QUfs *ufs, size_t offset) +{ + return qpci_io_readl(&ufs->dev, ufs->bar, offset); +} + +static inline void ufs_wreg(QUfs *ufs, size_t offset, uint32_t value) +{ + qpci_io_writel(&ufs->dev, ufs->bar, offset, value); +} + +static void ufs_wait_for_irq(QUfs *ufs) +{ + uint64_t end_time; + uint32_t is; + /* Wait for device to reset as the linux driver does. */ + end_time =3D g_get_monotonic_time() + TIMEOUT_SECONDS * G_TIME_SPAN_SE= COND; + do { + qtest_clock_step(ufs->dev.bus->qts, 100); + is =3D ufs_rreg(ufs, A_IS); + } while (is =3D=3D 0 && g_get_monotonic_time() < end_time); +} + +static UtpTransferReqDesc ufs_build_req_utrd(uint64_t cmd_desc_addr, + uint8_t slot, + uint32_t data_direction, + uint16_t prd_table_length) +{ + UtpTransferReqDesc req =3D { 0 }; + uint64_t command_desc_base_addr =3D + cmd_desc_addr + slot * UTP_COMMAND_DESCRIPTOR_SIZE; + + req.header.dword_0 =3D + cpu_to_le32(1 << 28 | data_direction | UFS_UTP_REQ_DESC_INT_CMD); + req.header.dword_2 =3D cpu_to_le32(UFS_OCS_INVALID_COMMAND_STATUS); + + req.command_desc_base_addr_hi =3D cpu_to_le32(command_desc_base_addr >= > 32); + req.command_desc_base_addr_lo =3D + cpu_to_le32(command_desc_base_addr & 0xffffffff); + req.response_upiu_offset =3D + cpu_to_le16(UTP_RESPONSE_UPIU_OFFSET / sizeof(uint32_t)); + req.response_upiu_length =3D cpu_to_le16(sizeof(UtpUpiuRsp)); + req.prd_table_offset =3D cpu_to_le16(UTP_PRDT_UPIU_OFFSET / sizeof(uin= t32_t)); + req.prd_table_length =3D cpu_to_le16(prd_table_length); + return req; +} + +static void ufs_send_nop_out(QUfs *ufs, uint8_t slot, + UtpTransferReqDesc *utrd_out, UtpUpiuRsp *rsp= _out) +{ + /* Build up utp transfer request descriptor */ + UtpTransferReqDesc utrd =3D ufs_build_req_utrd(ufs->cmd_desc_addr, slo= t, + UFS_UTP_NO_DATA_TRANSFER,= 0); + uint64_t utrd_addr =3D ufs->utrlba + slot * sizeof(UtpTransferReqDesc); + uint64_t req_upiu_addr =3D + ufs->cmd_desc_addr + slot * UTP_COMMAND_DESCRIPTOR_SIZE; + uint64_t rsp_upiu_addr =3D req_upiu_addr + UTP_RESPONSE_UPIU_OFFSET; + qtest_memwrite(ufs->dev.bus->qts, utrd_addr, &utrd, sizeof(utrd)); + + /* Build up request upiu */ + UtpUpiuReq req_upiu =3D { 0 }; + req_upiu.header.trans_type =3D UFS_UPIU_TRANSACTION_NOP_OUT; + req_upiu.header.task_tag =3D slot; + qtest_memwrite(ufs->dev.bus->qts, req_upiu_addr, &req_upiu, + sizeof(req_upiu)); + + /* Ring Doorbell */ + ufs_wreg(ufs, A_UTRLDBR, 1); + ufs_wait_for_irq(ufs); + g_assert_true(FIELD_EX32(ufs_rreg(ufs, A_IS), IS, UTRCS)); + ufs_wreg(ufs, A_IS, FIELD_DP32(0, IS, UTRCS, 1)); + + qtest_memread(ufs->dev.bus->qts, utrd_addr, utrd_out, sizeof(*utrd_out= )); + qtest_memread(ufs->dev.bus->qts, rsp_upiu_addr, rsp_out, sizeof(*rsp_o= ut)); +} + +static void ufs_send_query(QUfs *ufs, uint8_t slot, uint8_t query_function, + uint8_t query_opcode, uint8_t idn, uint8_t inde= x, + UtpTransferReqDesc *utrd_out, UtpUpiuRsp *rsp_o= ut) +{ + /* Build up utp transfer request descriptor */ + UtpTransferReqDesc utrd =3D ufs_build_req_utrd(ufs->cmd_desc_addr, slo= t, + UFS_UTP_NO_DATA_TRANSFER,= 0); + uint64_t utrd_addr =3D ufs->utrlba + slot * sizeof(UtpTransferReqDesc); + uint64_t req_upiu_addr =3D + ufs->cmd_desc_addr + slot * UTP_COMMAND_DESCRIPTOR_SIZE; + uint64_t rsp_upiu_addr =3D req_upiu_addr + UTP_RESPONSE_UPIU_OFFSET; + qtest_memwrite(ufs->dev.bus->qts, utrd_addr, &utrd, sizeof(utrd)); + + /* Build up request upiu */ + UtpUpiuReq req_upiu =3D { 0 }; + req_upiu.header.trans_type =3D UFS_UPIU_TRANSACTION_QUERY_REQ; + req_upiu.header.query_func =3D query_function; + req_upiu.header.task_tag =3D slot; + /* + * QEMU UFS does not currently support Write descriptor and Write attr= ibute, + * so the value of data_segment_length is always 0. + */ + req_upiu.header.data_segment_length =3D 0; + req_upiu.qr.opcode =3D query_opcode; + req_upiu.qr.idn =3D idn; + req_upiu.qr.index =3D index; + qtest_memwrite(ufs->dev.bus->qts, req_upiu_addr, &req_upiu, + sizeof(req_upiu)); + + /* Ring Doorbell */ + ufs_wreg(ufs, A_UTRLDBR, 1); + ufs_wait_for_irq(ufs); + g_assert_true(FIELD_EX32(ufs_rreg(ufs, A_IS), IS, UTRCS)); + ufs_wreg(ufs, A_IS, FIELD_DP32(0, IS, UTRCS, 1)); + + qtest_memread(ufs->dev.bus->qts, utrd_addr, utrd_out, sizeof(*utrd_out= )); + qtest_memread(ufs->dev.bus->qts, rsp_upiu_addr, rsp_out, sizeof(*rsp_o= ut)); +} + +static void ufs_send_scsi_command(QUfs *ufs, uint8_t slot, uint8_t lun, + const uint8_t *cdb, const uint8_t *data_= in, + size_t data_in_len, uint8_t *data_out, + size_t data_out_len, + UtpTransferReqDesc *utrd_out, + UtpUpiuRsp *rsp_out) + +{ + /* Build up PRDT */ + UfshcdSgEntry entries[MAX_PRD_ENTRY_COUNT] =3D { + 0, + }; + uint8_t flags; + uint16_t prd_table_length, i; + uint32_t data_direction, data_len; + uint64_t req_upiu_addr =3D + ufs->cmd_desc_addr + slot * UTP_COMMAND_DESCRIPTOR_SIZE; + uint64_t prdt_addr =3D req_upiu_addr + UTP_PRDT_UPIU_OFFSET; + + g_assert_true(data_in_len < MAX_PRD_ENTRY_COUNT * PRD_ENTRY_DATA_SIZE); + g_assert_true(data_out_len < MAX_PRD_ENTRY_COUNT * PRD_ENTRY_DATA_SIZE= ); + if (data_in_len > 0) { + g_assert_nonnull(data_in); + data_direction =3D UFS_UTP_HOST_TO_DEVICE; + data_len =3D data_in_len; + flags =3D UFS_UPIU_CMD_FLAGS_WRITE; + } else if (data_out_len > 0) { + g_assert_nonnull(data_out); + data_direction =3D UFS_UTP_DEVICE_TO_HOST; + data_len =3D data_out_len; + flags =3D UFS_UPIU_CMD_FLAGS_READ; + } else { + data_direction =3D UFS_UTP_NO_DATA_TRANSFER; + data_len =3D 0; + flags =3D UFS_UPIU_CMD_FLAGS_NONE; + } + prd_table_length =3D DIV_ROUND_UP(data_len, PRD_ENTRY_DATA_SIZE); + + qtest_memset(ufs->dev.bus->qts, ufs->data_buffer_addr, 0, + MAX_PRD_ENTRY_COUNT * PRD_ENTRY_DATA_SIZE); + if (data_in_len) { + qtest_memwrite(ufs->dev.bus->qts, ufs->data_buffer_addr, data_in, + data_in_len); + } + + for (i =3D 0; i < prd_table_length; i++) { + entries[i].addr =3D + cpu_to_le64(ufs->data_buffer_addr + i * sizeof(UfshcdSgEntry)); + if (i + 1 !=3D prd_table_length) { + entries[i].size =3D cpu_to_le32(PRD_ENTRY_DATA_SIZE - 1); + } else { + entries[i].size =3D cpu_to_le32( + data_len - (PRD_ENTRY_DATA_SIZE * (prd_table_length - 1)) = - 1); + } + } + qtest_memwrite(ufs->dev.bus->qts, prdt_addr, entries, + prd_table_length * sizeof(UfshcdSgEntry)); + + /* Build up utp transfer request descriptor */ + UtpTransferReqDesc utrd =3D ufs_build_req_utrd( + ufs->cmd_desc_addr, slot, data_direction, prd_table_length); + uint64_t utrd_addr =3D ufs->utrlba + slot * sizeof(UtpTransferReqDesc); + uint64_t rsp_upiu_addr =3D req_upiu_addr + UTP_RESPONSE_UPIU_OFFSET; + qtest_memwrite(ufs->dev.bus->qts, utrd_addr, &utrd, sizeof(utrd)); + + /* Build up request upiu */ + UtpUpiuReq req_upiu =3D { 0 }; + req_upiu.header.trans_type =3D UFS_UPIU_TRANSACTION_COMMAND; + req_upiu.header.flags =3D flags; + req_upiu.header.lun =3D lun; + req_upiu.header.task_tag =3D slot; + req_upiu.sc.exp_data_transfer_len =3D cpu_to_be32(data_len); + memcpy(req_upiu.sc.cdb, cdb, UFS_CDB_SIZE); + qtest_memwrite(ufs->dev.bus->qts, req_upiu_addr, &req_upiu, + sizeof(req_upiu)); + + /* Ring Doorbell */ + ufs_wreg(ufs, A_UTRLDBR, 1); + ufs_wait_for_irq(ufs); + g_assert_true(FIELD_EX32(ufs_rreg(ufs, A_IS), IS, UTRCS)); + ufs_wreg(ufs, A_IS, FIELD_DP32(0, IS, UTRCS, 1)); + + qtest_memread(ufs->dev.bus->qts, utrd_addr, utrd_out, sizeof(*utrd_out= )); + qtest_memread(ufs->dev.bus->qts, rsp_upiu_addr, rsp_out, sizeof(*rsp_o= ut)); + if (data_out_len) { + qtest_memread(ufs->dev.bus->qts, ufs->data_buffer_addr, data_out, + data_out_len); + } +} + +/** + * Initialize Ufs host controller and logical unit. + * After running this function, you can make a transfer request to the UFS. + */ +static void ufs_init(QUfs *ufs, QGuestAllocator *alloc) +{ + uint64_t end_time; + uint32_t nutrs, nutmrs; + uint32_t hcs, is, ucmdarg2, cap; + uint32_t hce =3D 0, ie =3D 0; + UtpTransferReqDesc utrd; + UtpUpiuRsp rsp_upiu; + + ufs->bar =3D qpci_iomap(&ufs->dev, 0, NULL); + qpci_device_enable(&ufs->dev); + + /* Start host controller initialization */ + hce =3D FIELD_DP32(hce, HCE, HCE, 1); + ufs_wreg(ufs, A_HCE, hce); + + /* Wait for device to reset */ + end_time =3D g_get_monotonic_time() + TIMEOUT_SECONDS * G_TIME_SPAN_SE= COND; + do { + qtest_clock_step(ufs->dev.bus->qts, 100); + hce =3D FIELD_EX32(ufs_rreg(ufs, A_HCE), HCE, HCE); + } while (hce =3D=3D 0 && g_get_monotonic_time() < end_time); + g_assert_cmpuint(hce, =3D=3D, 1); + + /* Enable interrupt */ + ie =3D FIELD_DP32(ie, IE, UCCE, 1); + ie =3D FIELD_DP32(ie, IE, UHESE, 1); + ie =3D FIELD_DP32(ie, IE, UHXSE, 1); + ie =3D FIELD_DP32(ie, IE, UPMSE, 1); + ufs_wreg(ufs, A_IE, ie); + + /* Send DME_LINK_STARTUP uic command */ + hcs =3D ufs_rreg(ufs, A_HCS); + g_assert_true(FIELD_EX32(hcs, HCS, UCRDY)); + + ufs_wreg(ufs, A_UCMDARG1, 0); + ufs_wreg(ufs, A_UCMDARG2, 0); + ufs_wreg(ufs, A_UCMDARG3, 0); + ufs_wreg(ufs, A_UICCMD, UFS_UIC_CMD_DME_LINK_STARTUP); + + is =3D ufs_rreg(ufs, A_IS); + g_assert_true(FIELD_EX32(is, IS, UCCS)); + ufs_wreg(ufs, A_IS, FIELD_DP32(0, IS, UCCS, 1)); + + ucmdarg2 =3D ufs_rreg(ufs, A_UCMDARG2); + g_assert_cmpuint(ucmdarg2, =3D=3D, 0); + is =3D ufs_rreg(ufs, A_IS); + g_assert_cmpuint(is, =3D=3D, 0); + hcs =3D ufs_rreg(ufs, A_HCS); + g_assert_true(FIELD_EX32(hcs, HCS, DP)); + g_assert_true(FIELD_EX32(hcs, HCS, UTRLRDY)); + g_assert_true(FIELD_EX32(hcs, HCS, UTMRLRDY)); + g_assert_true(FIELD_EX32(hcs, HCS, UCRDY)); + + /* Enable all interrupt functions */ + ie =3D FIELD_DP32(ie, IE, UTRCE, 1); + ie =3D FIELD_DP32(ie, IE, UEE, 1); + ie =3D FIELD_DP32(ie, IE, UPMSE, 1); + ie =3D FIELD_DP32(ie, IE, UHXSE, 1); + ie =3D FIELD_DP32(ie, IE, UHESE, 1); + ie =3D FIELD_DP32(ie, IE, UTMRCE, 1); + ie =3D FIELD_DP32(ie, IE, UCCE, 1); + ie =3D FIELD_DP32(ie, IE, DFEE, 1); + ie =3D FIELD_DP32(ie, IE, HCFEE, 1); + ie =3D FIELD_DP32(ie, IE, SBFEE, 1); + ie =3D FIELD_DP32(ie, IE, CEFEE, 1); + ufs_wreg(ufs, A_IE, ie); + ufs_wreg(ufs, A_UTRIACR, 0); + + /* Enable tranfer request and task management request */ + cap =3D ufs_rreg(ufs, A_CAP); + nutrs =3D FIELD_EX32(cap, CAP, NUTRS) + 1; + nutmrs =3D FIELD_EX32(cap, CAP, NUTMRS) + 1; + ufs->cmd_desc_addr =3D + guest_alloc(alloc, nutrs * UTP_COMMAND_DESCRIPTOR_SIZE); + ufs->data_buffer_addr =3D + guest_alloc(alloc, MAX_PRD_ENTRY_COUNT * PRD_ENTRY_DATA_SIZE); + ufs->utrlba =3D guest_alloc(alloc, nutrs * sizeof(UtpTransferReqDesc)); + ufs->utmrlba =3D guest_alloc(alloc, nutmrs * sizeof(UtpTaskReqDesc)); + + ufs_wreg(ufs, A_UTRLBA, ufs->utrlba & 0xffffffff); + ufs_wreg(ufs, A_UTRLBAU, ufs->utrlba >> 32); + ufs_wreg(ufs, A_UTMRLBA, ufs->utmrlba & 0xffffffff); + ufs_wreg(ufs, A_UTMRLBAU, ufs->utmrlba >> 32); + ufs_wreg(ufs, A_UTRLRSR, 1); + ufs_wreg(ufs, A_UTMRLRSR, 1); + + /* Send nop out to test transfer request */ + ufs_send_nop_out(ufs, 0, &utrd, &rsp_upiu); + g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), =3D=3D, UFS_OCS_SUC= CESS); + + /* Set fDeviceInit flag via query request */ + ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST, + UFS_UPIU_QUERY_OPCODE_SET_FLAG, + UFS_QUERY_FLAG_IDN_FDEVICEINIT, 0, &utrd, &rsp_upiu); + g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), =3D=3D, UFS_OCS_SUC= CESS); + + /* Wait for device to reset */ + end_time =3D g_get_monotonic_time() + TIMEOUT_SECONDS * G_TIME_SPAN_SE= COND; + do { + qtest_clock_step(ufs->dev.bus->qts, 100); + ufs_send_query(ufs, 0, UFS_UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, + UFS_UPIU_QUERY_OPCODE_READ_FLAG, + UFS_QUERY_FLAG_IDN_FDEVICEINIT, 0, &utrd, &rsp_upiu= ); + } while (be32_to_cpu(rsp_upiu.qr.value) !=3D 0 && + g_get_monotonic_time() < end_time); + g_assert_cmpuint(be32_to_cpu(rsp_upiu.qr.value), =3D=3D, 0); + + ufs->enabled =3D true; +} + +static void ufs_exit(QUfs *ufs, QGuestAllocator *alloc) +{ + if (ufs->enabled) { + guest_free(alloc, ufs->utrlba); + guest_free(alloc, ufs->utmrlba); + guest_free(alloc, ufs->cmd_desc_addr); + guest_free(alloc, ufs->data_buffer_addr); + } + + qpci_iounmap(&ufs->dev, ufs->bar); +} + +static void *ufs_get_driver(void *obj, const char *interface) +{ + QUfs *ufs =3D obj; + + if (!g_strcmp0(interface, "pci-device")) { + return &ufs->dev; + } + + fprintf(stderr, "%s not present in ufs\n", interface); + g_assert_not_reached(); +} + +static void *ufs_create(void *pci_bus, QGuestAllocator *alloc, void *addr) +{ + QUfs *ufs =3D g_new0(QUfs, 1); + QPCIBus *bus =3D pci_bus; + + qpci_device_init(&ufs->dev, bus, addr); + ufs->obj.get_driver =3D ufs_get_driver; + + return &ufs->obj; +} + +static void ufstest_reg_read(void *obj, void *data, QGuestAllocator *alloc) +{ + QUfs *ufs =3D obj; + uint32_t cap; + + ufs->bar =3D qpci_iomap(&ufs->dev, 0, NULL); + qpci_device_enable(&ufs->dev); + + cap =3D ufs_rreg(ufs, A_CAP); + g_assert_cmpuint(FIELD_EX32(cap, CAP, NUTRS), =3D=3D, 31); + g_assert_cmpuint(FIELD_EX32(cap, CAP, NUTMRS), =3D=3D, 7); + g_assert_cmpuint(FIELD_EX32(cap, CAP, 64AS), =3D=3D, 1); + + qpci_iounmap(&ufs->dev, ufs->bar); +} + +static void ufstest_init(void *obj, void *data, QGuestAllocator *alloc) +{ + QUfs *ufs =3D obj; + + uint8_t buf[4096] =3D { 0 }; + const uint8_t report_luns_cdb[UFS_CDB_SIZE] =3D { + /* allocation length 4096 */ + REPORT_LUNS, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x10, 0x00, 0x00, 0x00 + }; + const uint8_t test_unit_ready_cdb[UFS_CDB_SIZE] =3D { + TEST_UNIT_READY, + }; + UtpTransferReqDesc utrd; + UtpUpiuRsp rsp_upiu; + + ufs_init(ufs, alloc); + + /* Check REPORT_LUNS */ + ufs_send_scsi_command(ufs, 0, 0, report_luns_cdb, NULL, 0, buf, sizeof= (buf), + &utrd, &rsp_upiu); + g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), =3D=3D, UFS_OCS_SUC= CESS); + g_assert_cmpuint(rsp_upiu.header.scsi_status, =3D=3D, GOOD); + /* LUN LIST LENGTH should be 8, in big endian */ + g_assert_cmpuint(buf[3], =3D=3D, 8); + /* There is one logical unit whose lun is 0 */ + g_assert_cmpuint(buf[9], =3D=3D, 0); + + /* Check TEST_UNIT_READY */ + ufs_send_scsi_command(ufs, 0, 0, test_unit_ready_cdb, NULL, 0, NULL, 0, + &utrd, &rsp_upiu); + g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), =3D=3D, UFS_OCS_SUC= CESS); + g_assert_cmpuint(rsp_upiu.header.scsi_status, =3D=3D, GOOD); + + ufs_exit(ufs, alloc); +} + +static void ufstest_read_write(void *obj, void *data, QGuestAllocator *all= oc) +{ + QUfs *ufs =3D obj; + uint8_t read_buf[4096] =3D { 0 }; + uint8_t write_buf[4096] =3D { 0 }; + const uint8_t read_capacity_cdb[UFS_CDB_SIZE] =3D { + /* allocation length 4096 */ + SERVICE_ACTION_IN_16, + SAI_READ_CAPACITY_16, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x10, + 0x00, + 0x00, + 0x00 + }; + const uint8_t read_cdb[UFS_CDB_SIZE] =3D { + /* READ(10) to LBA 0, transfer length 1 */ + READ_10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00 + }; + 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 + }; + uint32_t block_size; + UtpTransferReqDesc utrd; + UtpUpiuRsp rsp_upiu; + + ufs_init(ufs, alloc); + + /* Read capacity */ + ufs_send_scsi_command(ufs, 0, 1, read_capacity_cdb, NULL, 0, read_buf, + sizeof(read_buf), &utrd, &rsp_upiu); + g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), =3D=3D, UFS_OCS_SUC= CESS); + g_assert_cmpuint(rsp_upiu.header.scsi_status, =3D=3D, + UFS_COMMAND_RESULT_SUCESS); + block_size =3D ldl_be_p(&read_buf[8]); + g_assert_cmpuint(block_size, =3D=3D, 4096); + + /* Write data */ + memset(write_buf, rand() % 255 + 1, block_size); + ufs_send_scsi_command(ufs, 0, 1, write_cdb, write_buf, block_size, NUL= L, 0, + &utrd, &rsp_upiu); + g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), =3D=3D, UFS_OCS_SUC= CESS); + g_assert_cmpuint(rsp_upiu.header.scsi_status, =3D=3D, + UFS_COMMAND_RESULT_SUCESS); + + /* Read data and verify */ + ufs_send_scsi_command(ufs, 0, 1, read_cdb, NULL, 0, read_buf, block_si= ze, + &utrd, &rsp_upiu); + g_assert_cmpuint(le32_to_cpu(utrd.header.dword_2), =3D=3D, UFS_OCS_SUC= CESS); + g_assert_cmpuint(rsp_upiu.header.scsi_status, =3D=3D, + UFS_COMMAND_RESULT_SUCESS); + g_assert_cmpint(memcmp(read_buf, write_buf, block_size), =3D=3D, 0); + + ufs_exit(ufs, alloc); +} + +static void drive_destroy(void *path) +{ + unlink(path); + g_free(path); + qos_invalidate_command_line(); +} + +static char *drive_create(void) +{ + int fd, ret; + char *t_path; + + /* Create a temporary raw image */ + fd =3D g_file_open_tmp("qtest-ufs.XXXXXX", &t_path, NULL); + g_assert_cmpint(fd, >=3D, 0); + ret =3D ftruncate(fd, TEST_IMAGE_SIZE); + g_assert_cmpint(ret, =3D=3D, 0); + close(fd); + + g_test_queue_destroy(drive_destroy, t_path); + return t_path; +} + +static void *ufs_blk_test_setup(GString *cmd_line, void *arg) +{ + char *tmp_path =3D drive_create(); + + g_string_append_printf(cmd_line, + " -blockdev file,filename=3D%s,node-name=3Ddrv1= " + "-device ufs-lu,bus=3Dufs0,drive=3Ddrv1,lun=3D1= ", + tmp_path); + + return arg; +} + +static void ufs_register_nodes(void) +{ + const char *arch; + QOSGraphEdgeOptions edge_opts =3D { + .before_cmd_line =3D "-blockdev null-co,node-name=3Ddrv0,read-zero= es=3Don", + .after_cmd_line =3D "-device ufs-lu,bus=3Dufs0,drive=3Ddrv0,lun=3D= 0", + .extra_device_opts =3D "addr=3D04.0,id=3Dufs0,nutrs=3D32,nutmrs=3D= 8" + }; + + QOSGraphTestOptions io_test_opts =3D { + .before =3D ufs_blk_test_setup, + }; + + add_qpci_address(&edge_opts, &(QPCIAddress){ .devfn =3D QPCI_DEVFN(4, = 0) }); + + qos_node_create_driver("ufs", ufs_create); + qos_node_consumes("ufs", "pci-bus", &edge_opts); + qos_node_produces("ufs", "pci-device"); + + qos_add_test("reg-read", "ufs", ufstest_reg_read, NULL); + + /* + * Check architecture + * TODO: Enable ufs io tests for ppc64 + */ + arch =3D qtest_get_arch(); + if (!strcmp(arch, "ppc64")) { + g_test_message("Skipping ufs io tests for ppc64"); + return; + } + qos_add_test("init", "ufs", ufstest_init, NULL); + qos_add_test("read-write", "ufs", ufstest_read_write, &io_test_opts); +} + +libqos_init(ufs_register_nodes); --=20 2.34.1