From patchwork Fri Jun 16 06:58:21 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jeuk Kim X-Patchwork-Id: 13282131 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 5DE32EB64D7 for ; Fri, 16 Jun 2023 06:59:49 +0000 (UTC) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1qA3Pu-0005oy-AA; Fri, 16 Jun 2023 02:58:42 -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 1qA3Pq-0005n9-8d for qemu-devel@nongnu.org; Fri, 16 Jun 2023 02:58:38 -0400 Received: from mailout1.samsung.com ([203.254.224.24]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1qA3Pi-0003uu-6L for qemu-devel@nongnu.org; Fri, 16 Jun 2023 02:58:37 -0400 Received: from epcas2p3.samsung.com (unknown [182.195.41.55]) by mailout1.samsung.com (KnoxPortal) with ESMTP id 20230616065822epoutp0198cdd499a29d012ad420a551559a2aff~pEW7LrojX3265732657epoutp01T for ; Fri, 16 Jun 2023 06:58:22 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout1.samsung.com 20230616065822epoutp0198cdd499a29d012ad420a551559a2aff~pEW7LrojX3265732657epoutp01T DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1686898703; bh=90uXTkH5yJXbNof4qXJNVqiSbGUaGQ8EIM0nNMe2Jhc=; h=Subject:Reply-To:From:To:CC:Date:References:From; b=qB+GJEzvcmysyMv7d74UldiwXiA+3M0AGmuV0fZxEyVIDFLAvKyVy8a45rQnuZtVY x/6Hx2AHDQt5Pe6OSHB53KfDJl4RIKsnrDzbcD3rporhNiR70jo/3OaU4zfdcgU8pR 9NLpWgWuFJOz3XEm5wLwUvRm/Co/r5yOvcY/BLCc= Received: from epsnrtp3.localdomain (unknown [182.195.42.164]) by epcas2p4.samsung.com (KnoxPortal) with ESMTP id 20230616065822epcas2p441b79b575a32492c82baa1d542380459~pEW60Mpjh0752507525epcas2p4p; Fri, 16 Jun 2023 06:58:22 +0000 (GMT) Received: from epsmges2p1.samsung.com (unknown [182.195.36.99]) by epsnrtp3.localdomain (Postfix) with ESMTP id 4Qj93d5mFpz4x9Q3; Fri, 16 Jun 2023 06:58:21 +0000 (GMT) X-AuditID: b6c32a45-1dbff70000022cba-33-648c080df0c4 Received: from epcas2p3.samsung.com ( [182.195.41.55]) by epsmges2p1.samsung.com (Symantec Messaging Gateway) with SMTP id 38.30.11450.D080C846; Fri, 16 Jun 2023 15:58:21 +0900 (KST) Mime-Version: 1.0 Subject: [PATCH v2 1/3] hw/ufs: Initial commit for emulated Universal-Flash-Storage From: Jeuk Kim To: "qemu-devel@nongnu.org" CC: "kwolf@redhat.com" , "hreitz@redhat.com" , "mst@redhat.com" , "marcel.apfelbaum@gmail.com" , "pbonzini@redhat.com" , "marcandre.lureau@redhat.com" , "berrange@redhat.com" , "thuth@redhat.com" , "philmd@linaro.org" , "qemu-block@nongnu.org" , Klaus Birkelund Jensen , "stefanha@redhat.com" X-Priority: 3 X-Content-Kind-Code: NORMAL X-CPGS-Detection: blocking_info_exchange X-Drm-Type: N,general X-Msg-Generator: Mail X-Msg-Type: PERSONAL X-Reply-Demand: N Message-ID: <20230616065821epcms2p473cc98e116f5dde77baaab5626c129ec@epcms2p4> Date: Fri, 16 Jun 2023 15:58:21 +0900 X-CMS-MailID: 20230616065821epcms2p473cc98e116f5dde77baaab5626c129ec X-Sendblock-Type: AUTO_CONFIDENTIAL CMS-TYPE: 102P X-CPGSPASS: Y X-CPGSPASS: Y X-Brightmail-Tracker: H4sIAAAAAAAAA+NgFlrKJsWRmVeSWpSXmKPExsWy7bCmuS4vR0+KwfEtWhZv3qxhsnh5SNPi wd1+RouTjXtYLZZcTLVY0NbOavGy/QSzxf9fr1gt9m/7x2qxZoWwxax37WwWx3t3sFi8nvSf 1WLlMRsHPo+ds+6ye9y5tofN48m1zUwe7/ddZfPo27KKMYA1KtsmIzUxJbVIITUvOT8lMy/d Vsk7ON453tTMwFDX0NLCXEkhLzE31VbJxSdA1y0zB+hMJYWyxJxSoFBAYnGxkr6dTVF+aUmq QkZ+cYmtUmpBSk6BeYFecWJucWleul5eaomVoYGBkSlQYUJ2RkerRcGBWcwVe64+Ym1gvHuE qYuRk0NCwETi9/L7zF2MXBxCAjsYJboWT2PvYuTg4BUQlPi7QxikRlggVOL5jefsILaQgILE nG0d7BBxTYnp6ycygZSzCahLnF5oDhIWETCWOHZ4CdhIZoEtLBKPNjxlg9jFKzGj/SkLhC0t sX35VkYIW0Pix7JeZghbVOLm6rfsMPb7Y/OhakQkWu+dhaoRlHjwczdUXFLi1LfHrBD2dEaJ Bf9NQRZLCCxglPjVPB2qQV/iWsdGsMW8Ar4St/Z8ZwM5mkVAVeLOMS+IEheJsz2rwe5kFpCX 2P52DjNICTPQj+t36YOYEgLKEkdusUBU8El0HP7LDvNVw8bfWNk75j2BhrKKxOLmw6wQY6Qk NuwOncCoNAsRzLOQrJ2FsHYBI/MqRrHUguLc9NRiowJDeNQm5+duYgSnVy3XHYyT337QO8TI xMF4iFGCg1lJhHfZia4UId6UxMqq1KL8+KLSnNTiQ4ymQP9OZJYSTc4HJvi8knhDE0sDEzMz Q3MjUwNzJXFeaduTyUIC6YklqdmpqQWpRTB9TBycUg1MfpoZMtPfX9M1nDUnmav3vQDD0nln AxuOvzzSYLNu5rEvnBd/PUhT6g72vmkursniE8GmEOIa8a9si9rBCzsu3JqzslA7+dvN/eyP Mi6tP5RxO3DK0RMvd0lc/RIowZ1zge+GxP4Xj3zqdn5LMxFySTILu2knHfHc6qUPywxJrjdr GgpUlqk3Xm7j9lnyev/uCSkbOu8Z8t653/Ssgu+HzgznN/bi06ZOYlKedCH9efSbvHmnljHs P+Ey/Wiz9ILNSeJummwb3gicPvLRtSI6xufQOpvMX1xVntE3X6yY7n4h3NGtw86xYt3iCVvf XJxUU/HikWV1ROuCVD1Nz8XFyVrS4i8bf7ox6k+QnL/jvBJLcUaioRZzUXEiACpSHhY4BAAA DLP-Filter: Pass X-CFilter-Loop: Reflected X-CMS-RootMailID: 20230616065821epcms2p473cc98e116f5dde77baaab5626c129ec References: Received-SPF: pass client-ip=203.254.224.24; envelope-from=jeuk20.kim@samsung.com; helo=mailout1.samsung.com X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.001, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, RCVD_IN_MSPIKE_H3=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 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: , Reply-To: jeuk20.kim@samsung.com Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org 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 --- MAINTAINERS | 6 + hw/Kconfig | 1 + hw/meson.build | 1 + hw/ufs/Kconfig | 4 + hw/ufs/meson.build | 1 + hw/ufs/trace-events | 33 ++ hw/ufs/trace.h | 1 + hw/ufs/ufs.c | 305 +++++++++++ hw/ufs/ufs.h | 42 ++ include/block/ufs.h | 1048 ++++++++++++++++++++++++++++++++++++++ include/hw/pci/pci.h | 1 + include/hw/pci/pci_ids.h | 1 + meson.build | 1 + 13 files changed, 1445 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 88b5a7ee0a..91c2bfbb09 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2231,6 +2231,12 @@ F: tests/qtest/nvme-test.c F: docs/system/devices/nvme.rst T: git git://git.infradead.org/qemu-nvme.git nvme-next +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/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..c1d90eeea6 --- /dev/null +++ b/hw/ufs/meson.build @@ -0,0 +1 @@ +softmmu_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..17793929b1 --- /dev/null +++ b/hw/ufs/trace-events @@ -0,0 +1,33 @@ +# 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%"PRIx64" data 0x%"PRIx64" size %d" +ufs_mmio_write(uint64_t addr, uint64_t data, unsigned size) "addr 0x%"PRIx64" 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 %"PRIu32", 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_memory_allocation(void) "failed to allocate memory" +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 req 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%"PRIx32" is not yet supported" +ufs_err_invalid_register_offset(uint32_t offset) "Register offset 0x%"PRIx32" is invalid" +ufs_err_scsi_cmd_invalid_lun(uint8_t lun) "scsi command has invalid lun: 0x%"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%"PRIx8" is denied to read" +ufs_err_query_attr_not_writable(uint8_t idn) "query attribute idn 0x%"PRIx8" is denied to write" +ufs_err_query_invalid_opcode(uint8_t opcode) "query request has invalid opcode. 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 upiu 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..9dba1073a8 --- /dev/null +++ b/hw/ufs/ufs.c @@ -0,0 +1,305 @@ +/* + * QEMU Universal Flash Storage (UFS) Controller + * + * 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 "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 = PCI_DEVICE(u); + uint32_t is = ldl_le_p(&u->reg.is); + uint32_t ie = ldl_le_p(&u->reg.ie); + + if ((is & UFS_INTR_MASK) & 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) +{ + uint32_t is = ldl_le_p(&u->reg.is); + uint32_t hcs = ldl_le_p(&u->reg.hcs); + uint32_t ucmdarg1 = ldl_le_p(&u->reg.ucmdarg1); + uint32_t ucmdarg2 = ldl_le_p(&u->reg.ucmdarg2); + uint32_t ucmdarg3 = ldl_le_p(&u->reg.ucmdarg3); + + trace_ufs_process_uiccmd(val, ucmdarg1, ucmdarg2, ucmdarg3); + /* + * Only the essential uic commands for running drivers on Linux and Windows + * are implemented. + */ + switch (val) { + case UIC_CMD_DME_LINK_STARTUP: + hcs = FIELD_DP32(hcs, HCS, DP, 1); + hcs = FIELD_DP32(hcs, HCS, UTRLRDY, 1); + hcs = FIELD_DP32(hcs, HCS, UTMRLRDY, 1); + ucmdarg2 = UIC_CMD_RESULT_SUCCESS; + break; + /* TODO: Revisit it when Power Management is implemented */ + case UIC_CMD_DME_HIBER_ENTER: + is = FIELD_DP32(is, IS, UHES, 1); + hcs = FIELD_DP32(hcs, HCS, UPMCRS, PWR_LOCAL); + ucmdarg2 = UIC_CMD_RESULT_SUCCESS; + break; + case UIC_CMD_DME_HIBER_EXIT: + is = FIELD_DP32(is, IS, UHXS, 1); + hcs = FIELD_DP32(hcs, HCS, UPMCRS, PWR_LOCAL); + ucmdarg2 = UIC_CMD_RESULT_SUCCESS; + break; + default: + ucmdarg2 = UIC_CMD_RESULT_FAILURE; + } + + is = FIELD_DP32(is, IS, UCCS, 1); + + stl_le_p(&u->reg.is, is); + stl_le_p(&u->reg.hcs, hcs); + stl_le_p(&u->reg.ucmdarg1, ucmdarg1); + stl_le_p(&u->reg.ucmdarg2, ucmdarg2); + stl_le_p(&u->reg.ucmdarg3, ucmdarg3); + + ufs_irq_check(u); +} + +static void ufs_write_reg(UfsHc *u, hwaddr offset, uint32_t data, unsigned size) +{ + uint32_t is = ldl_le_p(&u->reg.is); + uint32_t hcs = ldl_le_p(&u->reg.hcs); + uint32_t hce = ldl_le_p(&u->reg.hce); + uint32_t utrlcnr = ldl_le_p(&u->reg.utrlcnr); + uint32_t utrlba, utmrlba; + + switch (offset) { + case A_IS: + is &= ~data; + stl_le_p(&u->reg.is, is); + ufs_irq_check(u); + break; + case A_IE: + stl_le_p(&u->reg.ie, data); + ufs_irq_check(u); + break; + case A_HCE: + if (!FIELD_EX32(hce, HCE, HCE) && FIELD_EX32(data, HCE, HCE)) { + hcs = FIELD_DP32(hcs, HCS, UCRDY, 1); + hce = FIELD_DP32(hce, HCE, HCE, 1); + stl_le_p(&u->reg.hcs, hcs); + stl_le_p(&u->reg.hce, hce); + } else if (FIELD_EX32(hce, HCE, HCE) && !FIELD_EX32(data, HCE, HCE)) { + hcs = 0; + hce = FIELD_DP32(hce, HCE, HCE, 0); + stl_le_p(&u->reg.hcs, hcs); + stl_le_p(&u->reg.hce, hce); + } + break; + case A_UTRLBA: + utrlba = data & R_UTRLBA_UTRLBA_MASK; + stl_le_p(&u->reg.utrlba, utrlba); + break; + case A_UTRLBAU: + stl_le_p(&u->reg.utrlbau, data); + break; + case A_UTRLDBR: + /* Not yet supported */ + break; + case A_UTRLRSR: + stl_le_p(&u->reg.utrlrsr, data); + break; + case A_UTRLCNR: + utrlcnr &= ~data; + stl_le_p(&u->reg.utrlcnr, utrlcnr); + break; + case A_UTMRLBA: + utmrlba = data & R_UTMRLBA_UTMRLBA_MASK; + stl_le_p(&u->reg.utmrlba, utmrlba); + break; + case A_UTMRLBAU: + stl_le_p(&u->reg.utmrlbau, data); + break; + case A_UICCMD: + ufs_process_uiccmd(u, data); + break; + case A_UCMDARG1: + stl_le_p(&u->reg.ucmdarg1, data); + break; + case A_UCMDARG2: + stl_le_p(&u->reg.ucmdarg2, data); + break; + case A_UCMDARG3: + stl_le_p(&u->reg.ucmdarg3, 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 = (UfsHc *)opaque; + uint8_t *ptr = (uint8_t *)&u->reg; + uint64_t value; + + if (addr > sizeof(u->reg) - size) { + trace_ufs_err_invalid_register_offset(addr); + return 0; + } + + value = ldn_le_p(ptr + addr, size); + 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 = (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 = { + .read = ufs_mmio_read, + .write = ufs_mmio_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 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 %d", UFS_MAX_NUTRS); + return false; + } + + if (u->params.nutmrs > UFS_MAX_NUTMRS) { + error_setg(errp, "nutmrs must be less than %d", UFS_MAX_NUTMRS); + return false; + } + + return true; +} + +static void ufs_init_pci(UfsHc *u, PCIDevice *pci_dev) +{ + uint8_t *pci_conf = pci_dev->config; + + pci_conf[PCI_INTERRUPT_PIN] = 1; + pci_config_set_prog_interface(pci_conf, 0x1); + + pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_REDHAT); + pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_REDHAT_UFS); + + pci_config_set_class(pci_conf, PCI_CLASS_STORAGE_UFS); + + 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 = pci_allocate_irq(pci_dev); +} + +static void ufs_init_hc(UfsHc *u) +{ + uint32_t cap = 0; + + u->reg_size = pow2ceil(sizeof(UfsReg)); + + memset(&u->reg, 0, sizeof(u->reg)); + cap = FIELD_DP32(cap, CAP, NUTRS, (u->params.nutrs - 1)); + cap = FIELD_DP32(cap, CAP, RTT, 2); + cap = FIELD_DP32(cap, CAP, NUTMRS, (u->params.nutmrs - 1)); + cap = FIELD_DP32(cap, CAP, AUTOH8, 0); + cap = FIELD_DP32(cap, CAP, 64AS, 1); + cap = FIELD_DP32(cap, CAP, OODDS, 0); + cap = FIELD_DP32(cap, CAP, UICDMETMS, 0); + cap = FIELD_DP32(cap, CAP, CS, 0); + stl_le_p(&u->reg.cap, cap); + stl_le_p(&u->reg.ver, UFS_SPEC_VER); +} + +static void ufs_realize(PCIDevice *pci_dev, Error **errp) +{ + UfsHc *u = UFS(pci_dev); + + if (!ufs_check_constraints(u, errp)) { + return; + } + + ufs_init_hc(u); + ufs_init_pci(u, pci_dev); +} + +static Property ufs_props[] = { + 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 = { + .name = "ufs", + .unmigratable = 1, +}; + +static void ufs_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + PCIDeviceClass *pc = PCI_DEVICE_CLASS(oc); + + pc->realize = ufs_realize; + pc->class_id = PCI_CLASS_STORAGE_UFS; + + set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); + dc->desc = "Universal Flash Storage"; + device_class_set_props(dc, ufs_props); + dc->vmsd = &ufs_vmstate; +} + +static const TypeInfo ufs_info = { + .name = TYPE_UFS, + .parent = TYPE_PCI_DEVICE, + .class_init = ufs_class_init, + .instance_size = sizeof(UfsHc), + .interfaces = (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..3c28f4e62d --- /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 + * + * This code is licensed under the GNU GPL v2 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_LOGICAL_BLK_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..0e4c0e5d93 --- /dev/null +++ b/include/block/ufs.h @@ -0,0 +1,1048 @@ +#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)) +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 { + COMMAND_RESULT_SUCESS = 0x00, + COMMAND_RESULT_FAIL = 0x01, +} CommandRespCode; + +enum { + UFS_UPIU_FLAG_UNDERFLOW = 0x20, + UFS_UPIU_FLAG_OVERFLOW = 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 { + PWR_OK = 0x0, + PWR_LOCAL = 0x01, + PWR_REMOTE = 0x02, + PWR_BUSY = 0x03, + PWR_ERROR_CAP = 0x04, + PWR_FATAL_ERROR = 0x05, +}; + +/* UIC Commands */ +enum uic_cmd_dme { + UIC_CMD_DME_GET = 0x01, + UIC_CMD_DME_SET = 0x02, + UIC_CMD_DME_PEER_GET = 0x03, + UIC_CMD_DME_PEER_SET = 0x04, + UIC_CMD_DME_POWERON = 0x10, + UIC_CMD_DME_POWEROFF = 0x11, + UIC_CMD_DME_ENABLE = 0x12, + UIC_CMD_DME_RESET = 0x14, + UIC_CMD_DME_END_PT_RST = 0x15, + UIC_CMD_DME_LINK_STARTUP = 0x16, + UIC_CMD_DME_HIBER_ENTER = 0x17, + UIC_CMD_DME_HIBER_EXIT = 0x18, + UIC_CMD_DME_TEST_MODE = 0x1A, +}; + +/* UIC Config result code / Generic error code */ +enum { + UIC_CMD_RESULT_SUCCESS = 0x00, + UIC_CMD_RESULT_INVALID_ATTR = 0x01, + UIC_CMD_RESULT_FAILURE = 0x01, + UIC_CMD_RESULT_INVALID_ATTR_VALUE = 0x02, + UIC_CMD_RESULT_READ_ONLY_ATTR = 0x03, + UIC_CMD_RESULT_WRITE_ONLY_ATTR = 0x04, + UIC_CMD_RESULT_BAD_INDEX = 0x05, + UIC_CMD_RESULT_LOCKED_ATTR = 0x06, + UIC_CMD_RESULT_BAD_TEST_FEATURE_INDEX = 0x07, + UIC_CMD_RESULT_PEER_COMM_FAILURE = 0x08, + UIC_CMD_RESULT_BUSY = 0x09, + UIC_CMD_RESULT_DME_FAILURE = 0x0A, +}; + +#define MASK_UIC_COMMAND_RESULT 0xFF + +/* + * Request Descriptor Definitions + */ + +/* Transfer request command type */ +enum { + UTP_CMD_TYPE_SCSI = 0x0, + UTP_CMD_TYPE_UFS = 0x1, + UTP_CMD_TYPE_DEV_MANAGE = 0x2, +}; + +/* To accommodate UFS2.0 required Command type */ +enum { + UTP_CMD_TYPE_UFS_STORAGE = 0x1, +}; + +enum { + UTP_SCSI_COMMAND = 0x00000000, + UTP_NATIVE_UFS_COMMAND = 0x10000000, + UTP_DEVICE_MANAGEMENT_FUNCTION = 0x20000000, + UTP_REQ_DESC_INT_CMD = 0x01000000, + UTP_REQ_DESC_CRYPTO_ENABLE_CMD = 0x00800000, +}; + +/* UTP Transfer Request Data Direction (DD) */ +enum { + UTP_NO_DATA_TRANSFER = 0x00000000, + UTP_HOST_TO_DEVICE = 0x02000000, + UTP_DEVICE_TO_HOST = 0x04000000, +}; + +/* Overall command status values */ +enum UtpOcsCodes { + OCS_SUCCESS = 0x0, + OCS_INVALID_CMD_TABLE_ATTR = 0x1, + OCS_INVALID_PRDT_ATTR = 0x2, + OCS_MISMATCH_DATA_BUF_SIZE = 0x3, + OCS_MISMATCH_RESP_UPIU_SIZE = 0x4, + OCS_PEER_COMM_FAILURE = 0x5, + OCS_ABORTED = 0x6, + OCS_FATAL_ERROR = 0x7, + OCS_DEVICE_FATAL_ERROR = 0x8, + OCS_INVALID_CRYPTO_CONFIG = 0x9, + OCS_GENERAL_CRYPTO_ERROR = 0xa, + OCS_INVALID_COMMAND_STATUS = 0xf, +}; + +enum { + MASK_OCS = 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 { + __le64 addr; + __le32 reserved; + __le32 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 UTMRD + * @dword0: Descriptor Header DW0 + * @dword1: Descriptor Header DW1 + * @dword2: Descriptor Header DW2 + * @dword3: Descriptor Header DW3 + */ +typedef struct QEMU_PACKED RequestDescHeader { + __le32 dword_0; + __le32 dword_1; + __le32 dword_2; + __le32 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*/ + __le32 command_desc_base_addr_lo; + __le32 command_desc_base_addr_hi; + + /* DW 6 */ + __le16 response_upiu_length; + __le16 response_upiu_offset; + + /* DW 7 */ + __le16 prd_table_length; + __le16 prd_table_offset; +} UtpTransferReqDesc; + +/* + * The code below is copied from the linux kernel ("include/ufs/ufs.h") and + * modified to fit the qemu style. + */ + +#define GENERAL_UPIU_REQUEST_SIZE (sizeof(UtpUpiuReq)) +#define QUERY_DESC_MAX_SIZE 255 +#define QUERY_DESC_MIN_SIZE 2 +#define QUERY_DESC_HDR_SIZE 2 +#define QUERY_OSF_SIZE (GENERAL_UPIU_REQUEST_SIZE - (sizeof(UtpUpiuHeader))) +#define UFS_SENSE_SIZE 18 + +#define UPIU_HEADER_DWORD(byte3, byte2, byte1, byte0) \ + cpu_to_be32((byte3 << 24) | (byte2 << 16) | (byte1 << 8) | (byte0)) +/* + * 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 EHS_OFFSET_IN_RESPONSE 32 + +/* Well known logical unit id in LUN field of UPIU */ +enum { + UFS_UPIU_REPORT_LUNS_WLUN = 0x81, + UFS_UPIU_UFS_DEVICE_WLUN = 0xD0, + UFS_UPIU_BOOT_WLUN = 0xB0, + UFS_UPIU_RPMB_WLUN = 0xC4, +}; + +/* + * UFS Protocol Information Unit related definitions + */ + +/* Task management functions */ +enum { + UFS_ABORT_TASK = 0x01, + UFS_ABORT_TASK_SET = 0x02, + UFS_CLEAR_TASK_SET = 0x04, + UFS_LOGICAL_RESET = 0x08, + UFS_QUERY_TASK = 0x80, + UFS_QUERY_TASK_SET = 0x81, +}; + +/* UTP UPIU Transaction Codes Initiator to Target */ +enum { + UPIU_TRANSACTION_NOP_OUT = 0x00, + UPIU_TRANSACTION_COMMAND = 0x01, + UPIU_TRANSACTION_DATA_OUT = 0x02, + UPIU_TRANSACTION_TASK_REQ = 0x04, + UPIU_TRANSACTION_QUERY_REQ = 0x16, +}; + +/* UTP UPIU Transaction Codes Target to Initiator */ +enum { + UPIU_TRANSACTION_NOP_IN = 0x20, + UPIU_TRANSACTION_RESPONSE = 0x21, + UPIU_TRANSACTION_DATA_IN = 0x22, + UPIU_TRANSACTION_TASK_RSP = 0x24, + UPIU_TRANSACTION_READY_XFER = 0x31, + UPIU_TRANSACTION_QUERY_RSP = 0x36, + UPIU_TRANSACTION_REJECT_UPIU = 0x3F, +}; + +/* UPIU Read/Write flags */ +enum { + UPIU_CMD_FLAGS_NONE = 0x00, + UPIU_CMD_FLAGS_WRITE = 0x20, + UPIU_CMD_FLAGS_READ = 0x40, +}; + +/* UPIU Task Attributes */ +enum { + UPIU_TASK_ATTR_SIMPLE = 0x00, + UPIU_TASK_ATTR_ORDERED = 0x01, + UPIU_TASK_ATTR_HEADQ = 0x02, + UPIU_TASK_ATTR_ACA = 0x03, +}; + +/* UPIU Query request function */ +enum { + UPIU_QUERY_FUNC_STANDARD_READ_REQUEST = 0x01, + UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST = 0x81, +}; + +/* Flag idn for Query Requests*/ +enum flag_idn { + QUERY_FLAG_IDN_FDEVICEINIT = 0x01, + QUERY_FLAG_IDN_PERMANENT_WPE = 0x02, + QUERY_FLAG_IDN_PWR_ON_WPE = 0x03, + QUERY_FLAG_IDN_BKOPS_EN = 0x04, + QUERY_FLAG_IDN_LIFE_SPAN_MODE_ENABLE = 0x05, + QUERY_FLAG_IDN_PURGE_ENABLE = 0x06, + QUERY_FLAG_IDN_REFRESH_ENABLE = 0x07, + QUERY_FLAG_IDN_FPHYRESOURCEREMOVAL = 0x08, + QUERY_FLAG_IDN_BUSY_RTC = 0x09, + QUERY_FLAG_IDN_RESERVED3 = 0x0A, + QUERY_FLAG_IDN_PERMANENTLY_DISABLE_FW_UPDATE = 0x0B, + QUERY_FLAG_IDN_WB_EN = 0x0E, + QUERY_FLAG_IDN_WB_BUFF_FLUSH_EN = 0x0F, + QUERY_FLAG_IDN_WB_BUFF_FLUSH_DURING_HIBERN8 = 0x10, + QUERY_FLAG_IDN_HPB_RESET = 0x11, + QUERY_FLAG_IDN_HPB_EN = 0x12, + QUERY_FLAG_IDN_COUNT, +}; + +/* Attribute idn for Query requests */ +enum attr_idn { + QUERY_ATTR_IDN_BOOT_LU_EN = 0x00, + QUERY_ATTR_IDN_MAX_HPB_SINGLE_CMD = 0x01, + QUERY_ATTR_IDN_POWER_MODE = 0x02, + QUERY_ATTR_IDN_ACTIVE_ICC_LVL = 0x03, + QUERY_ATTR_IDN_OOO_DATA_EN = 0x04, + QUERY_ATTR_IDN_BKOPS_STATUS = 0x05, + QUERY_ATTR_IDN_PURGE_STATUS = 0x06, + QUERY_ATTR_IDN_MAX_DATA_IN = 0x07, + QUERY_ATTR_IDN_MAX_DATA_OUT = 0x08, + QUERY_ATTR_IDN_DYN_CAP_NEEDED = 0x09, + QUERY_ATTR_IDN_REF_CLK_FREQ = 0x0A, + QUERY_ATTR_IDN_CONF_DESC_LOCK = 0x0B, + QUERY_ATTR_IDN_MAX_NUM_OF_RTT = 0x0C, + QUERY_ATTR_IDN_EE_CONTROL = 0x0D, + QUERY_ATTR_IDN_EE_STATUS = 0x0E, + QUERY_ATTR_IDN_SECONDS_PASSED = 0x0F, + QUERY_ATTR_IDN_CNTX_CONF = 0x10, + QUERY_ATTR_IDN_CORR_PRG_BLK_NUM = 0x11, + QUERY_ATTR_IDN_RESERVED2 = 0x12, + QUERY_ATTR_IDN_RESERVED3 = 0x13, + QUERY_ATTR_IDN_FFU_STATUS = 0x14, + QUERY_ATTR_IDN_PSA_STATE = 0x15, + QUERY_ATTR_IDN_PSA_DATA_SIZE = 0x16, + QUERY_ATTR_IDN_REF_CLK_GATING_WAIT_TIME = 0x17, + QUERY_ATTR_IDN_CASE_ROUGH_TEMP = 0x18, + QUERY_ATTR_IDN_HIGH_TEMP_BOUND = 0x19, + QUERY_ATTR_IDN_LOW_TEMP_BOUND = 0x1A, + QUERY_ATTR_IDN_THROTTLING_STATUS = 0x1B, + QUERY_ATTR_IDN_WB_FLUSH_STATUS = 0x1C, + QUERY_ATTR_IDN_AVAIL_WB_BUFF_SIZE = 0x1D, + QUERY_ATTR_IDN_WB_BUFF_LIFE_TIME_EST = 0x1E, + QUERY_ATTR_IDN_CURR_WB_BUFF_SIZE = 0x1F, + QUERY_ATTR_IDN_REFRESH_STATUS = 0x2C, + QUERY_ATTR_IDN_REFRESH_FREQ = 0x2D, + QUERY_ATTR_IDN_REFRESH_UNIT = 0x2E, + QUERY_ATTR_IDN_COUNT, +}; + +/* Descriptor idn for Query requests */ +enum desc_idn { + QUERY_DESC_IDN_DEVICE = 0x0, + QUERY_DESC_IDN_CONFIGURATION = 0x1, + QUERY_DESC_IDN_UNIT = 0x2, + QUERY_DESC_IDN_RFU_0 = 0x3, + QUERY_DESC_IDN_INTERCONNECT = 0x4, + QUERY_DESC_IDN_STRING = 0x5, + QUERY_DESC_IDN_RFU_1 = 0x6, + QUERY_DESC_IDN_GEOMETRY = 0x7, + QUERY_DESC_IDN_POWER = 0x8, + QUERY_DESC_IDN_HEALTH = 0x9, + QUERY_DESC_IDN_MAX, +}; + +enum desc_header_offset { + QUERY_DESC_LENGTH_OFFSET = 0x00, + QUERY_DESC_DESC_TYPE_OFFSET = 0x01, +}; + +/* Unit descriptor parameters offsets in bytes*/ +enum unit_desc_param { + UNIT_DESC_PARAM_LEN = 0x0, + UNIT_DESC_PARAM_TYPE = 0x1, + UNIT_DESC_PARAM_UNIT_INDEX = 0x2, + UNIT_DESC_PARAM_LU_ENABLE = 0x3, + UNIT_DESC_PARAM_BOOT_LUN_ID = 0x4, + UNIT_DESC_PARAM_LU_WR_PROTECT = 0x5, + UNIT_DESC_PARAM_LU_Q_DEPTH = 0x6, + UNIT_DESC_PARAM_PSA_SENSITIVE = 0x7, + UNIT_DESC_PARAM_MEM_TYPE = 0x8, + UNIT_DESC_PARAM_DATA_RELIABILITY = 0x9, + UNIT_DESC_PARAM_LOGICAL_BLK_SIZE = 0xA, + UNIT_DESC_PARAM_LOGICAL_BLK_COUNT = 0xB, + UNIT_DESC_PARAM_ERASE_BLK_SIZE = 0x13, + UNIT_DESC_PARAM_PROVISIONING_TYPE = 0x17, + UNIT_DESC_PARAM_PHY_MEM_RSRC_CNT = 0x18, + UNIT_DESC_PARAM_CTX_CAPABILITIES = 0x20, + UNIT_DESC_PARAM_LARGE_UNIT_SIZE_M1 = 0x22, + UNIT_DESC_PARAM_HPB_LU_MAX_ACTIVE_RGNS = 0x23, + UNIT_DESC_PARAM_HPB_PIN_RGN_START_OFF = 0x25, + UNIT_DESC_PARAM_HPB_NUM_PIN_RGNS = 0x27, + UNIT_DESC_PARAM_WB_BUF_ALLOC_UNITS = 0x29, +}; + +/* RPMB Unit descriptor parameters offsets in bytes*/ +enum rpmb_unit_desc_param { + RPMB_UNIT_DESC_PARAM_LEN = 0x0, + RPMB_UNIT_DESC_PARAM_TYPE = 0x1, + RPMB_UNIT_DESC_PARAM_UNIT_INDEX = 0x2, + RPMB_UNIT_DESC_PARAM_LU_ENABLE = 0x3, + RPMB_UNIT_DESC_PARAM_BOOT_LUN_ID = 0x4, + RPMB_UNIT_DESC_PARAM_LU_WR_PROTECT = 0x5, + RPMB_UNIT_DESC_PARAM_LU_Q_DEPTH = 0x6, + RPMB_UNIT_DESC_PARAM_PSA_SENSITIVE = 0x7, + RPMB_UNIT_DESC_PARAM_MEM_TYPE = 0x8, + RPMB_UNIT_DESC_PARAM_REGION_EN = 0x9, + RPMB_UNIT_DESC_PARAM_LOGICAL_BLK_SIZE = 0xA, + RPMB_UNIT_DESC_PARAM_LOGICAL_BLK_COUNT = 0xB, + RPMB_UNIT_DESC_PARAM_REGION0_SIZE = 0x13, + RPMB_UNIT_DESC_PARAM_REGION1_SIZE = 0x14, + RPMB_UNIT_DESC_PARAM_REGION2_SIZE = 0x15, + RPMB_UNIT_DESC_PARAM_REGION3_SIZE = 0x16, + RPMB_UNIT_DESC_PARAM_PROVISIONING_TYPE = 0x17, + RPMB_UNIT_DESC_PARAM_PHY_MEM_RSRC_CNT = 0x18, +}; + +/* Device descriptor parameters offsets in bytes*/ +enum device_desc_param { + DEVICE_DESC_PARAM_LEN = 0x0, + DEVICE_DESC_PARAM_TYPE = 0x1, + DEVICE_DESC_PARAM_DEVICE_TYPE = 0x2, + DEVICE_DESC_PARAM_DEVICE_CLASS = 0x3, + DEVICE_DESC_PARAM_DEVICE_SUB_CLASS = 0x4, + DEVICE_DESC_PARAM_PRTCL = 0x5, + DEVICE_DESC_PARAM_NUM_LU = 0x6, + DEVICE_DESC_PARAM_NUM_WLU = 0x7, + DEVICE_DESC_PARAM_BOOT_ENBL = 0x8, + DEVICE_DESC_PARAM_DESC_ACCSS_ENBL = 0x9, + DEVICE_DESC_PARAM_INIT_PWR_MODE = 0xA, + DEVICE_DESC_PARAM_HIGH_PR_LUN = 0xB, + DEVICE_DESC_PARAM_SEC_RMV_TYPE = 0xC, + DEVICE_DESC_PARAM_SEC_LU = 0xD, + DEVICE_DESC_PARAM_BKOP_TERM_LT = 0xE, + DEVICE_DESC_PARAM_ACTVE_ICC_LVL = 0xF, + DEVICE_DESC_PARAM_SPEC_VER = 0x10, + DEVICE_DESC_PARAM_MANF_DATE = 0x12, + DEVICE_DESC_PARAM_MANF_NAME = 0x14, + DEVICE_DESC_PARAM_PRDCT_NAME = 0x15, + DEVICE_DESC_PARAM_SN = 0x16, + DEVICE_DESC_PARAM_OEM_ID = 0x17, + DEVICE_DESC_PARAM_MANF_ID = 0x18, + DEVICE_DESC_PARAM_UD_OFFSET = 0x1A, + DEVICE_DESC_PARAM_UD_LEN = 0x1B, + DEVICE_DESC_PARAM_RTT_CAP = 0x1C, + DEVICE_DESC_PARAM_FRQ_RTC = 0x1D, + DEVICE_DESC_PARAM_UFS_FEAT = 0x1F, + DEVICE_DESC_PARAM_FFU_TMT = 0x20, + DEVICE_DESC_PARAM_Q_DPTH = 0x21, + DEVICE_DESC_PARAM_DEV_VER = 0x22, + DEVICE_DESC_PARAM_NUM_SEC_WPA = 0x24, + DEVICE_DESC_PARAM_PSA_MAX_DATA = 0x25, + DEVICE_DESC_PARAM_PSA_TMT = 0x29, + DEVICE_DESC_PARAM_PRDCT_REV = 0x2A, + DEVICE_DESC_PARAM_HPB_VER = 0x40, + DEVICE_DESC_PARAM_HPB_CONTROL = 0x42, + DEVICE_DESC_PARAM_EXT_UFS_FEATURE_SUP = 0x4F, + DEVICE_DESC_PARAM_WB_PRESRV_USRSPC_EN = 0x53, + DEVICE_DESC_PARAM_WB_TYPE = 0x54, + DEVICE_DESC_PARAM_WB_SHARED_ALLOC_UNITS = 0x55, +}; + +/* Interconnect descriptor parameters offsets in bytes*/ +enum interconnect_desc_param { + INTERCONNECT_DESC_PARAM_LEN = 0x0, + INTERCONNECT_DESC_PARAM_TYPE = 0x1, + INTERCONNECT_DESC_PARAM_UNIPRO_VER = 0x2, + INTERCONNECT_DESC_PARAM_MPHY_VER = 0x4, +}; + +/* Geometry descriptor parameters offsets in bytes*/ +enum geometry_desc_param { + GEOMETRY_DESC_PARAM_LEN = 0x0, + GEOMETRY_DESC_PARAM_TYPE = 0x1, + GEOMETRY_DESC_PARAM_DEV_CAP = 0x4, + GEOMETRY_DESC_PARAM_MAX_NUM_LUN = 0xC, + GEOMETRY_DESC_PARAM_SEG_SIZE = 0xD, + GEOMETRY_DESC_PARAM_ALLOC_UNIT_SIZE = 0x11, + GEOMETRY_DESC_PARAM_MIN_BLK_SIZE = 0x12, + GEOMETRY_DESC_PARAM_OPT_RD_BLK_SIZE = 0x13, + GEOMETRY_DESC_PARAM_OPT_WR_BLK_SIZE = 0x14, + GEOMETRY_DESC_PARAM_MAX_IN_BUF_SIZE = 0x15, + GEOMETRY_DESC_PARAM_MAX_OUT_BUF_SIZE = 0x16, + GEOMETRY_DESC_PARAM_RPMB_RW_SIZE = 0x17, + GEOMETRY_DESC_PARAM_DYN_CAP_RSRC_PLC = 0x18, + GEOMETRY_DESC_PARAM_DATA_ORDER = 0x19, + GEOMETRY_DESC_PARAM_MAX_NUM_CTX = 0x1A, + GEOMETRY_DESC_PARAM_TAG_UNIT_SIZE = 0x1B, + GEOMETRY_DESC_PARAM_TAG_RSRC_SIZE = 0x1C, + GEOMETRY_DESC_PARAM_SEC_RM_TYPES = 0x1D, + GEOMETRY_DESC_PARAM_MEM_TYPES = 0x1E, + GEOMETRY_DESC_PARAM_SCM_MAX_NUM_UNITS = 0x20, + GEOMETRY_DESC_PARAM_SCM_CAP_ADJ_FCTR = 0x24, + GEOMETRY_DESC_PARAM_NPM_MAX_NUM_UNITS = 0x26, + GEOMETRY_DESC_PARAM_NPM_CAP_ADJ_FCTR = 0x2A, + GEOMETRY_DESC_PARAM_ENM1_MAX_NUM_UNITS = 0x2C, + GEOMETRY_DESC_PARAM_ENM1_CAP_ADJ_FCTR = 0x30, + GEOMETRY_DESC_PARAM_ENM2_MAX_NUM_UNITS = 0x32, + GEOMETRY_DESC_PARAM_ENM2_CAP_ADJ_FCTR = 0x36, + GEOMETRY_DESC_PARAM_ENM3_MAX_NUM_UNITS = 0x38, + GEOMETRY_DESC_PARAM_ENM3_CAP_ADJ_FCTR = 0x3C, + GEOMETRY_DESC_PARAM_ENM4_MAX_NUM_UNITS = 0x3E, + GEOMETRY_DESC_PARAM_ENM4_CAP_ADJ_FCTR = 0x42, + GEOMETRY_DESC_PARAM_OPT_LOG_BLK_SIZE = 0x44, + GEOMETRY_DESC_PARAM_HPB_REGION_SIZE = 0x48, + GEOMETRY_DESC_PARAM_HPB_NUMBER_LU = 0x49, + GEOMETRY_DESC_PARAM_HPB_SUBREGION_SIZE = 0x4A, + GEOMETRY_DESC_PARAM_HPB_MAX_ACTIVE_REGS = 0x4B, + GEOMETRY_DESC_PARAM_WB_MAX_ALLOC_UNITS = 0x4F, + GEOMETRY_DESC_PARAM_WB_MAX_WB_LUNS = 0x53, + GEOMETRY_DESC_PARAM_WB_BUFF_CAP_ADJ = 0x54, + GEOMETRY_DESC_PARAM_WB_SUP_RED_TYPE = 0x55, + GEOMETRY_DESC_PARAM_WB_SUP_WB_TYPE = 0x56, +}; + +/* Health descriptor parameters offsets in bytes*/ +enum health_desc_param { + HEALTH_DESC_PARAM_LEN = 0x0, + HEALTH_DESC_PARAM_TYPE = 0x1, + HEALTH_DESC_PARAM_EOL_INFO = 0x2, + HEALTH_DESC_PARAM_LIFE_TIME_EST_A = 0x3, + HEALTH_DESC_PARAM_LIFE_TIME_EST_B = 0x4, +}; + +/* WriteBooster buffer mode */ +enum { + WB_BUF_MODE_LU_DEDICATED = 0x0, + WB_BUF_MODE_SHARED = 0x1, +}; + +/* + * Logical Unit Write Protect + * 00h: LU not write protected + * 01h: LU write protected when fPowerOnWPEn =1 + * 02h: LU permanently write protected when fPermanentWPEn =1 + */ +enum ufs_lu_wp_type { + UFS_LU_NO_WP = 0x00, + UFS_LU_POWER_ON_WP = 0x01, + UFS_LU_PERM_WP = 0x02, +}; + +/* UTP QUERY Transaction Specific Fields OpCode */ +enum query_opcode { + UPIU_QUERY_OPCODE_NOP = 0x0, + UPIU_QUERY_OPCODE_READ_DESC = 0x1, + UPIU_QUERY_OPCODE_WRITE_DESC = 0x2, + UPIU_QUERY_OPCODE_READ_ATTR = 0x3, + UPIU_QUERY_OPCODE_WRITE_ATTR = 0x4, + UPIU_QUERY_OPCODE_READ_FLAG = 0x5, + UPIU_QUERY_OPCODE_SET_FLAG = 0x6, + UPIU_QUERY_OPCODE_CLEAR_FLAG = 0x7, + UPIU_QUERY_OPCODE_TOGGLE_FLAG = 0x8, +}; + +/* Query response result code */ +typedef enum QueryRespCode { + QUERY_RESULT_SUCCESS = 0x00, + QUERY_RESULT_NOT_READABLE = 0xF6, + QUERY_RESULT_NOT_WRITEABLE = 0xF7, + QUERY_RESULT_ALREADY_WRITTEN = 0xF8, + QUERY_RESULT_INVALID_LENGTH = 0xF9, + QUERY_RESULT_INVALID_VALUE = 0xFA, + QUERY_RESULT_INVALID_SELECTOR = 0xFB, + QUERY_RESULT_INVALID_INDEX = 0xFC, + QUERY_RESULT_INVALID_IDN = 0xFD, + QUERY_RESULT_INVALID_OPCODE = 0xFE, + QUERY_RESULT_GENERAL_FAILURE = 0xFF, +} QueryRespCode; + +/* UTP Transfer Request Command Type (CT) */ +enum { + UPIU_COMMAND_SET_TYPE_SCSI = 0x0, + UPIU_COMMAND_SET_TYPE_UFS = 0x1, + UPIU_COMMAND_SET_TYPE_QUERY = 0x2, +}; + +/* Task management service response */ +enum { + UPIU_TASK_MANAGEMENT_FUNC_COMPL = 0x00, + UPIU_TASK_MANAGEMENT_FUNC_NOT_SUPPORTED = 0x04, + UPIU_TASK_MANAGEMENT_FUNC_SUCCEEDED = 0x08, + UPIU_TASK_MANAGEMENT_FUNC_FAILED = 0x05, + UPIU_INCORRECT_LOGICAL_UNIT_NO = 0x09, +}; + +/* UFS device power modes */ +enum ufs_dev_pwr_mode { + UFS_ACTIVE_PWR_MODE = 1, + UFS_SLEEP_PWR_MODE = 2, + UFS_POWERDOWN_PWR_MODE = 3, + UFS_DEEPSLEEP_PWR_MODE = 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) != 0x104); + QEMU_BUILD_BUG_ON(sizeof(DeviceDescriptor) != 89); + QEMU_BUILD_BUG_ON(sizeof(GeometryDescriptor) != 87); + QEMU_BUILD_BUG_ON(sizeof(UnitDescriptor) != 45); + QEMU_BUILD_BUG_ON(sizeof(RpmbUnitDescriptor) != 35); + QEMU_BUILD_BUG_ON(sizeof(PowerParametersDescriptor) != 98); + QEMU_BUILD_BUG_ON(sizeof(InterconnectDescriptor) != 6); + QEMU_BUILD_BUG_ON(sizeof(StringDescriptor) != 254); + QEMU_BUILD_BUG_ON(sizeof(DeviceHealthDescriptor) != 45); + QEMU_BUILD_BUG_ON(sizeof(Flags) != 0x13); + QEMU_BUILD_BUG_ON(sizeof(UtpUpiuHeader) != 12); + QEMU_BUILD_BUG_ON(sizeof(UtpUpiuQuery) != 276); + QEMU_BUILD_BUG_ON(sizeof(UtpUpiuCmd) != 20); + QEMU_BUILD_BUG_ON(sizeof(UtpUpiuReq) != 288); + QEMU_BUILD_BUG_ON(sizeof(UfshcdSgEntry) != 16); + QEMU_BUILD_BUG_ON(sizeof(RequestDescHeader) != 16); + QEMU_BUILD_BUG_ON(sizeof(UtpTransferReqDesc) != 32); + QEMU_BUILD_BUG_ON(sizeof(UtpCmdRsp) != 40); + QEMU_BUILD_BUG_ON(sizeof(UtpUpiuRsp) != 288); +} +#endif diff --git a/include/hw/pci/pci.h b/include/hw/pci/pci.h index e6d0574a29..1094274546 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 #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 #define PCI_BASE_CLASS_NETWORK 0x02 diff --git a/meson.build b/meson.build index 34306a6205..6aeaa155db 100644 --- a/meson.build +++ b/meson.build @@ -3268,6 +3268,7 @@ if have_system 'hw/ssi', 'hw/timer', 'hw/tpm', + 'hw/ufs', 'hw/usb', 'hw/vfio', 'hw/virtio', From patchwork Fri Jun 16 06:58:25 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jeuk Kim X-Patchwork-Id: 13282132 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id EEEBDEB64DA for ; Fri, 16 Jun 2023 06:59:52 +0000 (UTC) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1qA3Pr-0005nS-OF; Fri, 16 Jun 2023 02:58:39 -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 1qA3Pp-0005mw-89 for qemu-devel@nongnu.org; Fri, 16 Jun 2023 02:58:37 -0400 Received: from mailout3.samsung.com ([203.254.224.33]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1qA3Pi-0003vh-7g for qemu-devel@nongnu.org; Fri, 16 Jun 2023 02:58:36 -0400 Received: from epcas2p1.samsung.com (unknown [182.195.41.53]) by mailout3.samsung.com (KnoxPortal) with ESMTP id 20230616065828epoutp03fec31f91153640e71c4b38eefa3fb075~pEW--D4NB3019730197epoutp03M for ; Fri, 16 Jun 2023 06:58:28 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout3.samsung.com 20230616065828epoutp03fec31f91153640e71c4b38eefa3fb075~pEW--D4NB3019730197epoutp03M DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1686898708; bh=AphzJ3uoKFzizzZJemc31yjV9VWrwHAjFNJtA6SjqLk=; h=Subject:Reply-To:From:To:CC:Date:References:From; b=rI1eHyzf0bz9HV7OWsOxJcy5B9u6Lqu93buxNeoerhvi9iBmfmQKQw3dnAA651Tts CizI2aeGN3L6D99pD/koi6ekiTheG51rmGVQIKLWZy7znxVOpZ6p/AWq4EUGXR2lpn NKr9zA6dMnDz+1+YsaOfj8xUYvK7ZIuImbD+/x5s= Received: from epsnrtp4.localdomain (unknown [182.195.42.165]) by epcas2p1.samsung.com (KnoxPortal) with ESMTP id 20230616065827epcas2p1e2fa7e0f521996391e4e96383c81dabc~pEW-hWzI20109001090epcas2p1y; Fri, 16 Jun 2023 06:58:27 +0000 (GMT) Received: from epsmges2p3.samsung.com (unknown [182.195.36.102]) by epsnrtp4.localdomain (Postfix) with ESMTP id 4Qj93k6HTvz4x9QD; Fri, 16 Jun 2023 06:58:26 +0000 (GMT) X-AuditID: b6c32a47-eedff70000001ce0-2c-648c081183d2 Received: from epcas2p4.samsung.com ( [182.195.41.56]) by epsmges2p3.samsung.com (Symantec Messaging Gateway) with SMTP id CB.25.07392.1180C846; Fri, 16 Jun 2023 15:58:25 +0900 (KST) Mime-Version: 1.0 Subject: [PATCH v2 2/3] hw/ufs: Support for Query Transfer Requests From: Jeuk Kim To: "qemu-devel@nongnu.org" CC: "kwolf@redhat.com" , "hreitz@redhat.com" , "pbonzini@redhat.com" , "fam@euphon.net" , "qemu-block@nongnu.org" , Klaus Birkelund Jensen , "stefanha@redhat.com" X-Priority: 3 X-Content-Kind-Code: NORMAL X-CPGS-Detection: blocking_info_exchange X-Drm-Type: N,general X-Msg-Generator: Mail X-Msg-Type: PERSONAL X-Reply-Demand: N Message-ID: <20230616065825epcms2p5682ea0edcd1fb0366a344708bea875cb@epcms2p5> Date: Fri, 16 Jun 2023 15:58:25 +0900 X-CMS-MailID: 20230616065825epcms2p5682ea0edcd1fb0366a344708bea875cb X-Sendblock-Type: AUTO_CONFIDENTIAL CMS-TYPE: 102P X-CPGSPASS: Y X-CPGSPASS: Y X-Brightmail-Tracker: H4sIAAAAAAAAA+NgFupgk+LIzCtJLcpLzFFi42LZdljTQleQoyfFYO9pLYuXhzQtruw/z2jx 4G4/o8XJxj2sFksuplrs3/aP1WLWu3Y2i+O9O1gsXk/6z+rA6fHjXDubx5Nrm5k83u+7yubR t2UVYwBLVLZNRmpiSmqRQmpecn5KZl66rZJ3cLxzvKmZgaGuoaWFuZJCXmJuqq2Si0+Arltm DtApSgpliTmlQKGAxOJiJX07m6L80pJUhYz84hJbpdSClJwC8wK94sTc4tK8dL281BIrQwMD I1OgwoTsjKnfvrMWLFvBVLGzbR5zA+O7R4xdjBwcEgImEvNupnQxcnIICexglPhzQBQkzCsg KPF3hzBIWFjAWWLKlP+sECUKEnO2dbBDxDUlpq+fyARSziagLnF6oTlIWETAWOLY4SXMXYxc HMwC25kkzs87yASSkBDglZjR/pQFwpaW2L58KyOErSHxY1kvM4QtKnFz9Vt2GPv9sflQNSIS rffOQtUISjz4uRsqLilx6ttjVgh7OqPEgv+mIIslBBYwSvxqng7VoC9xrWMj2GJeAV+Js+cv gh3NIqAqcfWVJkSJi8Su5RPAypkF5CW2v53DDFLCDPTj+l36kIBSljhyiwWigk+i4/Bfdpiv Gjb+xsreMe8J1OcqEoubD7NCjJGS2LA7dAKj0ixEMM9CsnYWwtoFjMyrGMVSC4pz01OLjQqM 4TGbnJ+7iRGcJLXcdzDOePtB7xAjEwfjIUYJDmYlEd5lJ7pShHhTEiurUovy44tKc1KLDzGa Av07kVlKNDkfmKbzSuINTSwNTMzMDM2NTA3MlcR5pW1PJgsJpCeWpGanphakFsH0MXFwSjUw 8Tdz7lre9DuNb8rxiQHinp67eGewGDVpb3zc+ffDR26LnurZiRekkz2/GxjtOp/uxTwvpvte sOirNT5Zhiv3BPJWNS8Nbnz815rjT2Z97ncx/bptH6JLfL9NarsoIHvS7TYnp4HJrreLKtRv uVharJyyTSr13VuDfXVnbeWsjTIm2rJd/WUkKee1+id3s7dLuJeT8l/RN/Nnr+7KV/tQE6Iz t+XSo6fe844eL7m2ctrRlIC7l05d+bcrbFv+Nd4HateLJtc8dOuLFIjtOHl998MYuR2N1xrX JZ9oMzeaGeb96fDhCQFWjY/2MV7fpKY41/jemSO3pFetmMz3Z6nXZvHpeRydzf7r+2QFtR9u UGIpzkg01GIuKk4EAKIwwdQbBAAA DLP-Filter: Pass X-CFilter-Loop: Reflected X-CMS-RootMailID: 20230616065825epcms2p5682ea0edcd1fb0366a344708bea875cb References: Received-SPF: pass client-ip=203.254.224.33; envelope-from=jeuk20.kim@samsung.com; helo=mailout3.samsung.com X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.001, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, RCVD_IN_MSPIKE_H3=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 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: , Reply-To: jeuk20.kim@samsung.com Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org 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 --- hw/ufs/ufs.c | 968 ++++++++++++++++++++++++++++++++++++++++++++++++++- hw/ufs/ufs.h | 45 +++ 2 files changed, 1012 insertions(+), 1 deletion(-) diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c index 9dba1073a8..10ecc8cd7b 100644 --- a/hw/ufs/ufs.c +++ b/hw/ufs/ufs.c @@ -19,6 +19,233 @@ #define UFS_MAX_NUTRS 32 #define UFS_MAX_NUTMRS 8 +static MemTxResult ufs_addr_read(UfsHc *u, hwaddr addr, void *buf, int size) +{ + uint32_t cap = ldl_le_p(&u->reg.cap); + hwaddr hi = addr + size - 1; + + if (hi < addr) { + return MEMTX_DECODE_ERROR; + } + + if (!FIELD_EX32(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) +{ + uint32_t cap = ldl_le_p(&u->reg.cap); + hwaddr hi = addr + size - 1; + if (hi < addr) { + return MEMTX_DECODE_ERROR; + } + + if (!FIELD_EX32(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) +{ + uint32_t utrlba = ldl_le_p(&u->reg.utrlba); + uint32_t utrlbau = ldl_le_p(&u->reg.utrlbau); + hwaddr utrl_base_addr = (((hwaddr)utrlbau) << 32) + utrlba; + hwaddr utrd_addr = 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 = + le32_to_cpu(utrd->command_desc_base_addr_lo); + uint32_t cmd_desc_base_addr_hi = + 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 = ufs_get_req_upiu_base_addr(utrd); + uint32_t rsp_upiu_byte_off = + 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 = req->hc; + hwaddr utrd_addr = ufs_get_utrd_addr(u, req->slot); + MemTxResult ret; + + ret = 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 = req->hc; + hwaddr req_upiu_base_addr = ufs_get_req_upiu_base_addr(&req->utrd); + UtpUpiuReq *req_upiu = &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 = 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 = be16_to_cpu(req_upiu->header.data_segment_length); + + copy_size = sizeof(UtpUpiuHeader) + UFS_TRANSACTION_SPECIFIC_FIELD_SIZE + + data_segment_length; + + ret = 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 = req->hc; + uint16_t prdt_len = le16_to_cpu(req->utrd.prd_table_length); + uint16_t prdt_byte_off = + le16_to_cpu(req->utrd.prd_table_offset) * sizeof(uint32_t); + uint32_t prdt_size = prdt_len * sizeof(UfshcdSgEntry); + UfshcdSgEntry *prd_entries; + hwaddr req_upiu_base_addr, prdt_base_addr; + int err; + + assert(!req->sg); + + if (prdt_len == 0) { + return MEMTX_OK; + } + + prd_entries = g_new(UfshcdSgEntry, prdt_size); + if (!prd_entries) { + trace_ufs_err_memory_allocation(); + return MEMTX_ERROR; + } + + req_upiu_base_addr = ufs_get_req_upiu_base_addr(&req->utrd); + prdt_base_addr = req_upiu_base_addr + prdt_byte_off; + + err = 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 = g_malloc0(sizeof(QEMUSGList)); + if (!req->sg) { + trace_ufs_err_memory_allocation(); + g_free(prd_entries); + return MEMTX_ERROR; + } + pci_dma_sglist_init(req->sg, PCI_DEVICE(u), prdt_len); + + for (uint16_t i = 0; i < prdt_len; ++i) { + hwaddr data_dma_addr = le64_to_cpu(prd_entries[i].addr); + int32_t data_byte_count = le32_to_cpu(prd_entries[i].size) + 1; + qemu_sglist_add(req->sg, data_dma_addr, data_byte_count); + } + g_free(prd_entries); + + return MEMTX_OK; +} + +static MemTxResult ufs_dma_read_upiu(UfsRequest *req) +{ + MemTxResult ret; + + ret = ufs_dma_read_utrd(req); + if (ret) { + return ret; + } + + ret = ufs_dma_read_req_upiu(req); + if (ret) { + return ret; + } + + ret = ufs_dma_read_prdt(req); + if (ret) { + return ret; + } + + return 0; +} + +static MemTxResult ufs_dma_write_utrd(UfsRequest *req) +{ + UfsHc *u = req->hc; + hwaddr utrd_addr = ufs_get_utrd_addr(u, req->slot); + MemTxResult ret; + + ret = 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 = req->hc; + hwaddr rsp_upiu_base_addr = ufs_get_rsp_upiu_base_addr(&req->utrd); + uint32_t rsp_upiu_byte_len = + le16_to_cpu(req->utrd.response_upiu_length) * sizeof(uint32_t); + uint16_t data_segment_length = + be16_to_cpu(req->rsp_upiu.header.data_segment_length); + uint32_t copy_size = sizeof(UtpUpiuHeader) + + UFS_TRANSACTION_SPECIFIC_FIELD_SIZE + + data_segment_length; + MemTxResult ret; + + if (copy_size > rsp_upiu_byte_len) { + copy_size = rsp_upiu_byte_len; + } + + ret = ufs_addr_write(u, rsp_upiu_base_addr, &req->rsp_upiu, copy_size); + 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 = 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 = PCI_DEVICE(u); @@ -34,6 +261,36 @@ static void ufs_irq_check(UfsHc *u) } } +static void ufs_process_db(UfsHc *u, uint64_t val) +{ + uint32_t slot; + uint32_t nutrs = u->params.nutrs; + uint32_t utrldbr = ldl_le_p(&u->reg.utrldbr); + UfsRequest *req; + + val &= ~utrldbr; + if (!val) { + return; + } + stl_le_p(&u->reg.utrldbr, utrldbr | val); + + slot = find_first_bit(&val, nutrs); + + while (slot < nutrs) { + req = &u->req_list[slot]; + if (req->state != UFS_REQUEST_IDLE) { + trace_ufs_err_utrl_slot_busy(req->slot); + return; + } + + trace_ufs_process_db(slot); + req->state = UFS_REQUEST_READY; + slot = find_next_bit(&val, nutrs, slot + 1); + } + + qemu_bh_schedule(u->doorbell_bh); +} + static void ufs_process_uiccmd(UfsHc *u, uint32_t val) { uint32_t is = ldl_le_p(&u->reg.is); @@ -85,6 +342,7 @@ static void ufs_write_reg(UfsHc *u, hwaddr offset, uint32_t data, unsigned size) uint32_t is = ldl_le_p(&u->reg.is); uint32_t hcs = ldl_le_p(&u->reg.hcs); uint32_t hce = ldl_le_p(&u->reg.hce); + uint32_t utrldbr = ldl_le_p(&u->reg.utrldbr); uint32_t utrlcnr = ldl_le_p(&u->reg.utrlcnr); uint32_t utrlba, utmrlba; @@ -119,7 +377,9 @@ static void ufs_write_reg(UfsHc *u, hwaddr offset, uint32_t data, unsigned size) stl_le_p(&u->reg.utrlbau, data); break; case A_UTRLDBR: - /* Not yet supported */ + ufs_process_db(u, data); + utrldbr |= data; + stl_le_p(&u->reg.utrldbr, utrldbr); break; case A_UTRLRSR: stl_le_p(&u->reg.utrlrsr, data); @@ -199,6 +459,632 @@ static const MemoryRegionOps ufs_mmio_ops = { }, }; +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(UtpUpiuHeader)); + req->rsp_upiu.header.trans_type = trans_type; + req->rsp_upiu.header.flags = flags; + req->rsp_upiu.header.response = response; + req->rsp_upiu.header.scsi_status = scsi_status; + req->rsp_upiu.header.data_segment_length = 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, 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 supported. + */ +static const int flag_permission[QUERY_FLAG_IDN_COUNT] = { + [QUERY_FLAG_IDN_FDEVICEINIT] = UFS_QUERY_FLAG_READ | UFS_QUERY_FLAG_SET, + /* Write protection is not supported */ + [QUERY_FLAG_IDN_PERMANENT_WPE] = UFS_QUERY_FLAG_READ, + [QUERY_FLAG_IDN_PWR_ON_WPE] = UFS_QUERY_FLAG_READ, + [QUERY_FLAG_IDN_BKOPS_EN] = UFS_QUERY_FLAG_READ | UFS_QUERY_FLAG_SET | + UFS_QUERY_FLAG_CLEAR | UFS_QUERY_FLAG_TOGGLE, + [QUERY_FLAG_IDN_LIFE_SPAN_MODE_ENABLE] = + UFS_QUERY_FLAG_READ | UFS_QUERY_FLAG_SET | UFS_QUERY_FLAG_CLEAR | + UFS_QUERY_FLAG_TOGGLE, + /* Purge Operation is not supported */ + [QUERY_FLAG_IDN_PURGE_ENABLE] = UFS_QUERY_FLAG_NONE, + /* Refresh Operation is not supported */ + [QUERY_FLAG_IDN_REFRESH_ENABLE] = UFS_QUERY_FLAG_NONE, + /* Physical Resource Removal is not supported */ + [QUERY_FLAG_IDN_FPHYRESOURCEREMOVAL] = UFS_QUERY_FLAG_READ, + [QUERY_FLAG_IDN_BUSY_RTC] = UFS_QUERY_FLAG_READ, + [QUERY_FLAG_IDN_PERMANENTLY_DISABLE_FW_UPDATE] = UFS_QUERY_FLAG_READ, + /* Write Booster is not supported */ + [QUERY_FLAG_IDN_WB_EN] = UFS_QUERY_FLAG_READ, + [QUERY_FLAG_IDN_WB_BUFF_FLUSH_EN] = UFS_QUERY_FLAG_READ, + [QUERY_FLAG_IDN_WB_BUFF_FLUSH_DURING_HIBERN8] = UFS_QUERY_FLAG_READ, +}; + +static inline QueryRespCode ufs_flag_check_idn_valid(uint8_t idn, int op) +{ + if (idn >= QUERY_FLAG_IDN_COUNT) { + return QUERY_RESULT_INVALID_IDN; + } + + if (!(flag_permission[idn] & op)) { + if (op == UFS_QUERY_FLAG_READ) { + trace_ufs_err_query_flag_not_readable(idn); + return QUERY_RESULT_NOT_READABLE; + } + trace_ufs_err_query_flag_not_writable(idn); + return QUERY_RESULT_NOT_WRITEABLE; + } + + return QUERY_RESULT_SUCCESS; +} + +static const int attr_permission[QUERY_ATTR_IDN_COUNT] = { + /* booting is not supported */ + [QUERY_ATTR_IDN_BOOT_LU_EN] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_POWER_MODE] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_ACTIVE_ICC_LVL] = + UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, + [QUERY_ATTR_IDN_OOO_DATA_EN] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_BKOPS_STATUS] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_PURGE_STATUS] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_MAX_DATA_IN] = UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, + [QUERY_ATTR_IDN_MAX_DATA_OUT] = UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, + [QUERY_ATTR_IDN_DYN_CAP_NEEDED] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_REF_CLK_FREQ] = UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, + [QUERY_ATTR_IDN_CONF_DESC_LOCK] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_MAX_NUM_OF_RTT] = + UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, + [QUERY_ATTR_IDN_EE_CONTROL] = UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, + [QUERY_ATTR_IDN_EE_STATUS] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_SECONDS_PASSED] = UFS_QUERY_ATTR_WRITE, + [QUERY_ATTR_IDN_CNTX_CONF] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_FFU_STATUS] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_PSA_STATE] = UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, + [QUERY_ATTR_IDN_PSA_DATA_SIZE] = UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, + [QUERY_ATTR_IDN_REF_CLK_GATING_WAIT_TIME] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_CASE_ROUGH_TEMP] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_HIGH_TEMP_BOUND] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_LOW_TEMP_BOUND] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_THROTTLING_STATUS] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_WB_FLUSH_STATUS] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_AVAIL_WB_BUFF_SIZE] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_WB_BUFF_LIFE_TIME_EST] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_CURR_WB_BUFF_SIZE] = UFS_QUERY_ATTR_READ, + /* refresh operation is not supported */ + [QUERY_ATTR_IDN_REFRESH_STATUS] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_REFRESH_FREQ] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_REFRESH_UNIT] = UFS_QUERY_ATTR_READ, +}; + +static inline QueryRespCode ufs_attr_check_idn_valid(uint8_t idn, int op) +{ + if (idn >= QUERY_ATTR_IDN_COUNT) { + return QUERY_RESULT_INVALID_IDN; + } + + if (!(attr_permission[idn] & op)) { + if (op == UFS_QUERY_ATTR_READ) { + trace_ufs_err_query_attr_not_readable(idn); + return QUERY_RESULT_NOT_READABLE; + } + trace_ufs_err_query_attr_not_writable(idn); + return QUERY_RESULT_NOT_WRITEABLE; + } + + return QUERY_RESULT_SUCCESS; +} + +static QueryRespCode ufs_exec_query_flag(UfsRequest *req, int op) +{ + UfsHc *u = req->hc; + uint8_t idn = req->req_upiu.qr.idn; + uint32_t value; + QueryRespCode ret; + + ret = ufs_flag_check_idn_valid(idn, op); + if (ret) { + return ret; + } + + value = *(((uint8_t *)&u->flags) + idn); + if (idn == QUERY_FLAG_IDN_FDEVICEINIT) { + value = 0; + } else if (op == UFS_QUERY_FLAG_READ) { + value = *(((uint8_t *)&u->flags) + idn); + } else if (op == UFS_QUERY_FLAG_SET) { + value = 1; + } else if (op == UFS_QUERY_FLAG_CLEAR) { + value = 0; + } else if (op == UFS_QUERY_FLAG_TOGGLE) { + value = !value; + } else { + trace_ufs_err_query_invalid_opcode(op); + return QUERY_RESULT_INVALID_OPCODE; + } + + *(((uint8_t *)&u->flags) + idn) = value; + req->rsp_upiu.qr.value = cpu_to_be32(value); + return QUERY_RESULT_SUCCESS; +} + +static uint32_t ufs_read_attr_value(UfsHc *u, uint8_t idn) +{ + switch (idn) { + case QUERY_ATTR_IDN_BOOT_LU_EN: + return u->attributes.boot_lun_en; + case QUERY_ATTR_IDN_POWER_MODE: + return u->attributes.current_power_mode; + case QUERY_ATTR_IDN_ACTIVE_ICC_LVL: + return u->attributes.active_icc_level; + case QUERY_ATTR_IDN_OOO_DATA_EN: + return u->attributes.out_of_order_data_en; + case QUERY_ATTR_IDN_BKOPS_STATUS: + return u->attributes.background_op_status; + case QUERY_ATTR_IDN_PURGE_STATUS: + return u->attributes.purge_status; + case QUERY_ATTR_IDN_MAX_DATA_IN: + return u->attributes.max_data_in_size; + case QUERY_ATTR_IDN_MAX_DATA_OUT: + return u->attributes.max_data_out_size; + case QUERY_ATTR_IDN_DYN_CAP_NEEDED: + return be32_to_cpu(u->attributes.dyn_cap_needed); + case QUERY_ATTR_IDN_REF_CLK_FREQ: + return u->attributes.ref_clk_freq; + case QUERY_ATTR_IDN_CONF_DESC_LOCK: + return u->attributes.config_descr_lock; + case QUERY_ATTR_IDN_MAX_NUM_OF_RTT: + return u->attributes.max_num_of_rtt; + case QUERY_ATTR_IDN_EE_CONTROL: + return be16_to_cpu(u->attributes.exception_event_control); + case QUERY_ATTR_IDN_EE_STATUS: + return be16_to_cpu(u->attributes.exception_event_status); + case QUERY_ATTR_IDN_SECONDS_PASSED: + return be32_to_cpu(u->attributes.seconds_passed); + case QUERY_ATTR_IDN_CNTX_CONF: + return be16_to_cpu(u->attributes.context_conf); + case QUERY_ATTR_IDN_FFU_STATUS: + return u->attributes.device_ffu_status; + case QUERY_ATTR_IDN_PSA_STATE: + return be32_to_cpu(u->attributes.psa_state); + case QUERY_ATTR_IDN_PSA_DATA_SIZE: + return u->attributes.psa_data_size; + case QUERY_ATTR_IDN_REF_CLK_GATING_WAIT_TIME: + return u->attributes.ref_clk_gating_wait_time; + case QUERY_ATTR_IDN_CASE_ROUGH_TEMP: + return u->attributes.device_case_rough_temperaure; + case QUERY_ATTR_IDN_HIGH_TEMP_BOUND: + return u->attributes.device_too_high_temp_boundary; + case QUERY_ATTR_IDN_LOW_TEMP_BOUND: + return u->attributes.device_too_low_temp_boundary; + case QUERY_ATTR_IDN_THROTTLING_STATUS: + return u->attributes.throttling_status; + case QUERY_ATTR_IDN_WB_FLUSH_STATUS: + return u->attributes.wb_buffer_flush_status; + case QUERY_ATTR_IDN_AVAIL_WB_BUFF_SIZE: + return u->attributes.available_wb_buffer_size; + case QUERY_ATTR_IDN_WB_BUFF_LIFE_TIME_EST: + return u->attributes.wb_buffer_life_time_est; + case QUERY_ATTR_IDN_CURR_WB_BUFF_SIZE: + return be32_to_cpu(u->attributes.current_wb_buffer_size); + case QUERY_ATTR_IDN_REFRESH_STATUS: + return u->attributes.refresh_status; + case QUERY_ATTR_IDN_REFRESH_FREQ: + return u->attributes.refresh_freq; + case 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 QUERY_ATTR_IDN_ACTIVE_ICC_LVL: + u->attributes.active_icc_level = value; + break; + case QUERY_ATTR_IDN_MAX_DATA_IN: + u->attributes.max_data_in_size = value; + break; + case QUERY_ATTR_IDN_MAX_DATA_OUT: + u->attributes.max_data_out_size = value; + break; + case QUERY_ATTR_IDN_REF_CLK_FREQ: + u->attributes.ref_clk_freq = value; + break; + case QUERY_ATTR_IDN_MAX_NUM_OF_RTT: + u->attributes.max_num_of_rtt = value; + break; + case QUERY_ATTR_IDN_EE_CONTROL: + u->attributes.exception_event_control = cpu_to_be16(value); + break; + case QUERY_ATTR_IDN_SECONDS_PASSED: + u->attributes.seconds_passed = cpu_to_be32(value); + break; + case QUERY_ATTR_IDN_PSA_STATE: + u->attributes.psa_state = value; + break; + case QUERY_ATTR_IDN_PSA_DATA_SIZE: + u->attributes.psa_data_size = cpu_to_be32(value); + break; + } +} + +static QueryRespCode ufs_exec_query_attr(UfsRequest *req, int op) +{ + UfsHc *u = req->hc; + uint8_t idn = req->req_upiu.qr.idn; + uint32_t value; + QueryRespCode ret; + + ret = ufs_attr_check_idn_valid(idn, op); + if (ret) { + return ret; + } + + if (op == UFS_QUERY_ATTR_READ) { + value = ufs_read_attr_value(u, idn); + } else { + value = be32_to_cpu(req->req_upiu.qr.value); + ufs_write_attr_value(u, idn, value); + } + + req->rsp_upiu.qr.value = cpu_to_be32(value); + return QUERY_RESULT_SUCCESS; +} + +static const RpmbUnitDescriptor rpmb_unit_desc = { + .length = sizeof(RpmbUnitDescriptor), + .descriptor_idn = 2, + .unit_index = UFS_UPIU_RPMB_WLUN, + .lu_enable = 0, +}; + +static QueryRespCode ufs_read_unit_desc(UfsRequest *req) +{ + uint8_t lun = req->req_upiu.qr.index; + + if (lun != UFS_UPIU_RPMB_WLUN && lun > UFS_MAX_LUS) { + trace_ufs_err_query_invalid_index(req->req_upiu.qr.opcode, lun); + return QUERY_RESULT_INVALID_INDEX; + } + + if (lun == UFS_UPIU_RPMB_WLUN) { + memcpy(&req->rsp_upiu.qr.data, &rpmb_unit_desc, rpmb_unit_desc.length); + } else { + /* unit descriptor is not yet supported */ + return QUERY_RESULT_INVALID_INDEX; + } + + return QUERY_RESULT_SUCCESS; +} + +static const StringDescriptor manufacturer_str_desc = { + .length = 0x12, + .descriptor_idn = QUERY_DESC_IDN_STRING, + .UC = { 'S', 'A', 'M', 'S', 'U', 'N', 'G' }, +}; + +static const StringDescriptor product_name_str_desc = { + .length = 0x22, + .descriptor_idn = QUERY_DESC_IDN_STRING, + .UC = { 'Q', 'E', 'M', 'U', '-', 'U', 'F', 'S' }, +}; + +static const StringDescriptor product_rev_level_str_desc = { + .length = 0x0a, + .descriptor_idn = QUERY_DESC_IDN_STRING, + .UC = { '0', '0', '0', '1' }, +}; + +static const StringDescriptor null_str_desc = { + .length = 0x02, + .descriptor_idn = QUERY_DESC_IDN_STRING, +}; + +static QueryRespCode ufs_read_string_desc(UfsRequest *req) +{ + UfsHc *u = req->hc; + uint8_t index = req->req_upiu.qr.index; + if (index == u->device_desc.manufacturer_name) { + memcpy(&req->rsp_upiu.qr.data, &manufacturer_str_desc, + manufacturer_str_desc.length); + } else if (index == u->device_desc.product_name) { + memcpy(&req->rsp_upiu.qr.data, &product_name_str_desc, + product_name_str_desc.length); + } else if (index == u->device_desc.serial_number) { + memcpy(&req->rsp_upiu.qr.data, &null_str_desc, null_str_desc.length); + } else if (index == u->device_desc.oem_id) { + memcpy(&req->rsp_upiu.qr.data, &null_str_desc, null_str_desc.length); + } else if (index == u->device_desc.product_revision_level) { + memcpy(&req->rsp_upiu.qr.data, &product_rev_level_str_desc, + product_rev_level_str_desc.length); + } else { + trace_ufs_err_query_invalid_index(req->req_upiu.qr.opcode, index); + return QUERY_RESULT_INVALID_INDEX; + } + return QUERY_RESULT_SUCCESS; +} + +static const InterconnectDescriptor interconnect_desc = { + .length = sizeof(InterconnectDescriptor), + .descriptor_idn = QUERY_DESC_IDN_INTERCONNECT, + .bcd_unipro_version = 0x180, + .bcd_mphy_version = 0x410, +}; + +static QueryRespCode ufs_read_desc(UfsRequest *req) +{ + UfsHc *u = req->hc; + QueryRespCode status; + uint8_t idn = req->req_upiu.qr.idn; + uint16_t length = be16_to_cpu(req->req_upiu.qr.length); + + switch (idn) { + case QUERY_DESC_IDN_DEVICE: + memcpy(&req->rsp_upiu.qr.data, &u->device_desc, sizeof(u->device_desc)); + status = QUERY_RESULT_SUCCESS; + break; + case QUERY_DESC_IDN_UNIT: + status = ufs_read_unit_desc(req); + break; + case QUERY_DESC_IDN_GEOMETRY: + memcpy(&req->rsp_upiu.qr.data, &u->geometry_desc, + sizeof(u->geometry_desc)); + status = QUERY_RESULT_SUCCESS; + break; + case QUERY_DESC_IDN_INTERCONNECT: { + memcpy(&req->rsp_upiu.qr.data, &interconnect_desc, + sizeof(interconnect_desc)); + status = QUERY_RESULT_SUCCESS; + break; + } + case QUERY_DESC_IDN_STRING: + status = ufs_read_string_desc(req); + break; + case 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] = sizeof(PowerParametersDescriptor); + req->rsp_upiu.qr.data[1] = QUERY_DESC_IDN_POWER; + status = QUERY_RESULT_SUCCESS; + break; + case 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] = sizeof(DeviceHealthDescriptor); + req->rsp_upiu.qr.data[1] = QUERY_DESC_IDN_HEALTH; + status = QUERY_RESULT_SUCCESS; + break; + default: + length = 0; + trace_ufs_err_query_invalid_idn(req->req_upiu.qr.opcode, idn); + status = QUERY_RESULT_INVALID_IDN; + } + + if (length > req->rsp_upiu.qr.data[0]) { + length = req->rsp_upiu.qr.data[0]; + } + req->rsp_upiu.qr.opcode = req->req_upiu.qr.opcode; + req->rsp_upiu.qr.idn = req->req_upiu.qr.idn; + req->rsp_upiu.qr.index = req->req_upiu.qr.index; + req->rsp_upiu.qr.selector = req->req_upiu.qr.selector; + req->rsp_upiu.qr.length = cpu_to_be16(length); + + return status; +} + +static QueryRespCode ufs_exec_query_read(UfsRequest *req) +{ + QueryRespCode status; + switch (req->req_upiu.qr.opcode) { + case UPIU_QUERY_OPCODE_NOP: + status = QUERY_RESULT_SUCCESS; + break; + case UPIU_QUERY_OPCODE_READ_DESC: + status = ufs_read_desc(req); + break; + case UPIU_QUERY_OPCODE_READ_ATTR: + status = ufs_exec_query_attr(req, UFS_QUERY_ATTR_READ); + break; + case UPIU_QUERY_OPCODE_READ_FLAG: + status = ufs_exec_query_flag(req, UFS_QUERY_FLAG_READ); + break; + default: + trace_ufs_err_query_invalid_opcode(req->req_upiu.qr.opcode); + status = QUERY_RESULT_INVALID_OPCODE; + break; + } + + return status; +} + +static QueryRespCode ufs_exec_query_write(UfsRequest *req) +{ + QueryRespCode status; + switch (req->req_upiu.qr.opcode) { + case UPIU_QUERY_OPCODE_NOP: + status = QUERY_RESULT_SUCCESS; + break; + case UPIU_QUERY_OPCODE_WRITE_DESC: + /* write descriptor is not supported */ + status = QUERY_RESULT_NOT_WRITEABLE; + break; + case UPIU_QUERY_OPCODE_WRITE_ATTR: + status = ufs_exec_query_attr(req, UFS_QUERY_ATTR_WRITE); + break; + case UPIU_QUERY_OPCODE_SET_FLAG: + status = ufs_exec_query_flag(req, UFS_QUERY_FLAG_SET); + break; + case UPIU_QUERY_OPCODE_CLEAR_FLAG: + status = ufs_exec_query_flag(req, UFS_QUERY_FLAG_CLEAR); + break; + case UPIU_QUERY_OPCODE_TOGGLE_FLAG: + status = ufs_exec_query_flag(req, UFS_QUERY_FLAG_TOGGLE); + break; + default: + trace_ufs_err_query_invalid_opcode(req->req_upiu.qr.opcode); + status = QUERY_RESULT_INVALID_OPCODE; + break; + } + + return status; +} + +static UfsReqResult ufs_exec_query_cmd(UfsRequest *req) +{ + uint8_t query_func = 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 == UPIU_QUERY_FUNC_STANDARD_READ_REQUEST) { + status = ufs_exec_query_read(req); + } else if (query_func == UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST) { + status = ufs_exec_query_write(req); + } else { + status = QUERY_RESULT_GENERAL_FAILURE; + } + + data_segment_length = be16_to_cpu(req->rsp_upiu.qr.length); + ufs_build_upiu_header(req, UPIU_TRANSACTION_QUERY_RSP, 0, status, 0, + data_segment_length); + + if (status != QUERY_RESULT_SUCCESS) { + return UFS_REQUEST_ERROR; + } + 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 UPIU_TRANSACTION_NOP_OUT: + req_result = ufs_exec_nop_cmd(req); + break; + case UPIU_TRANSACTION_COMMAND: + /* Not yet implemented */ + req_result = UFS_REQUEST_ERROR; + break; + case UPIU_TRANSACTION_QUERY_REQ: + req_result = ufs_exec_query_cmd(req); + break; + default: + trace_ufs_err_invalid_trans_code(req->slot, + req->req_upiu.header.trans_type); + req_result = UFS_REQUEST_ERROR; + } + + ufs_complete_req(req, req_result); +} + +static void ufs_process_req(void *opaque) +{ + UfsHc *u = opaque; + UfsRequest *req; + int slot; + + for (slot = 0; slot < u->params.nutrs; slot++) { + req = &u->req_list[slot]; + + if (req->state != UFS_REQUEST_READY) { + continue; + } + trace_ufs_process_req(slot); + req->state = UFS_REQUEST_RUNNING; + + ufs_exec_req(req); + } +} + +static void ufs_complete_req(UfsRequest *req, UfsReqResult req_result) +{ + UfsHc *u = req->hc; + assert(req->state == UFS_REQUEST_RUNNING); + + if (req_result == UFS_REQUEST_SUCCESS) { + req->utrd.header.dword_2 = OCS_SUCCESS; + } else { + req->utrd.header.dword_2 = OCS_INVALID_CMD_TABLE_ATTR; + } + + trace_ufs_complete_req(req->slot); + req->state = UFS_REQUEST_COMPLETE; + qemu_bh_schedule(u->complete_bh); +} + +static void ufs_clear_req(UfsRequest *req) +{ + if (req->sg != NULL) { + qemu_sglist_destroy(req->sg); + g_free(req->sg); + req->sg = 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 = opaque; + UfsRequest *req; + int slot; + + for (slot = 0; slot < u->params.nutrs; slot++) { + uint32_t is = ldl_le_p(&u->reg.is); + uint32_t utrldbr = ldl_le_p(&u->reg.utrldbr); + uint32_t utrlcnr = ldl_le_p(&u->reg.utrlcnr); + + req = &u->req_list[slot]; + + if (req->state != UFS_REQUEST_COMPLETE) { + continue; + } + + if (ufs_dma_write_upiu(req)) { + continue; + } + + /* + * TODO: UTP Transfer Request Interrupt Aggregation Control is not yet + * supported + */ + if (req->utrd.header.dword_2 != OCS_SUCCESS || + req->utrd.header.dword_0 & UTP_REQ_DESC_INT_CMD) { + is = FIELD_DP32(is, IS, UTRCS, 1); + } + + utrldbr &= ~(1 << slot); + utrlcnr |= (1 << slot); + + stl_le_p(&u->reg.is, is); + stl_le_p(&u->reg.utrldbr, utrldbr); + stl_le_p(&u->reg.utrlcnr, utrlcnr); + + trace_ufs_sendback_req(req->slot); + + ufs_clear_req(req); + req->state = UFS_REQUEST_IDLE; + } + + ufs_irq_check(u); +} + static bool ufs_check_constraints(UfsHc *u, Error **errp) { if (u->params.nutrs > UFS_MAX_NUTRS) { @@ -232,6 +1118,23 @@ static void ufs_init_pci(UfsHc *u, PCIDevice *pci_dev) u->irq = pci_allocate_irq(pci_dev); } +static void ufs_init_state(UfsHc *u) +{ + u->req_list = g_new0(UfsRequest, u->params.nutrs); + + for (int i = 0; i < u->params.nutrs; i++) { + u->req_list[i].hc = u; + u->req_list[i].slot = i; + u->req_list[i].sg = NULL; + u->req_list[i].state = UFS_REQUEST_IDLE; + } + + u->doorbell_bh = qemu_bh_new_guarded(ufs_process_req, u, + &DEVICE(u)->mem_reentrancy_guard); + u->complete_bh = qemu_bh_new_guarded(ufs_sendback_req, u, + &DEVICE(u)->mem_reentrancy_guard); +} + static void ufs_init_hc(UfsHc *u) { uint32_t cap = 0; @@ -249,6 +1152,54 @@ static void ufs_init_hc(UfsHc *u) cap = FIELD_DP32(cap, CAP, CS, 0); stl_le_p(&u->reg.cap, cap); stl_le_p(&u->reg.ver, UFS_SPEC_VER); + + memset(&u->device_desc, 0, sizeof(DeviceDescriptor)); + u->device_desc.length = sizeof(DeviceDescriptor); + u->device_desc.descriptor_idn = QUERY_DESC_IDN_DEVICE; + u->device_desc.device_sub_class = 0x01; + u->device_desc.number_lu = 0x00; + u->device_desc.number_wlu = 0x04; + /* TODO: Revisit it when Power Management is implemented */ + u->device_desc.init_power_mode = 0x01; /* Active Mode */ + u->device_desc.high_priority_lun = 0x7F; /* Same Priority */ + u->device_desc.spec_version = cpu_to_be16(0x0310); + u->device_desc.manufacturer_name = 0x00; + u->device_desc.product_name = 0x01; + u->device_desc.serial_number = 0x02; + u->device_desc.oem_id = 0x03; + u->device_desc.ud_0_base_offset = 0x16; + u->device_desc.ud_config_p_length = 0x1A; + u->device_desc.device_rtt_cap = 0x02; + u->device_desc.queue_depth = u->params.nutrs; + u->device_desc.product_revision_level = 0x04; + u->device_desc.extended_ufs_features_support = 0x00; + + memset(&u->geometry_desc, 0, sizeof(GeometryDescriptor)); + u->geometry_desc.length = sizeof(GeometryDescriptor); + u->geometry_desc.descriptor_idn = QUERY_DESC_IDN_GEOMETRY; + u->geometry_desc.total_raw_device_capacity = 0; + u->geometry_desc.max_number_lu = (UFS_MAX_LUS == 32) ? 0x1 : 0x0; + u->geometry_desc.segment_size = cpu_to_be32(0x2000); /* 4KB */ + u->geometry_desc.allocation_unit_size = 0x1; /* 4KB */ + u->geometry_desc.min_addr_block_size = 0x8; /* 4KB */ + u->geometry_desc.max_in_buffer_size = 0x8; + u->geometry_desc.max_out_buffer_size = 0x8; + u->geometry_desc.rpmb_read_write_size = 0x40; + u->geometry_desc.data_ordering = + 0x0; /* out-of-order data transfer is not supported */ + u->geometry_desc.max_context_id_number = 0x5; + u->geometry_desc.supported_memory_types = cpu_to_be16(0x8001); + + memset(&u->attributes, 0, sizeof(u->attributes)); + u->attributes.max_data_in_size = 0x08; + u->attributes.max_data_out_size = 0x08; + u->attributes.ref_clk_freq = 0x01; /* 26 MHz */ + /* configure descriptor is not supported */ + u->attributes.config_descr_lock = 0x01; + u->attributes.max_num_of_rtt = 0x02; + + memset(&u->flags, 0, sizeof(u->flags)); + u->flags.permanently_disable_fw_update = 1; } static void ufs_realize(PCIDevice *pci_dev, Error **errp) @@ -259,10 +1210,24 @@ static void ufs_realize(PCIDevice *pci_dev, Error **errp) return; } + ufs_init_state(u); ufs_init_hc(u); ufs_init_pci(u, pci_dev); } +static void ufs_exit(PCIDevice *pci_dev) +{ + UfsHc *u = UFS(pci_dev); + + qemu_bh_delete(u->doorbell_bh); + qemu_bh_delete(u->complete_bh); + + for (int i = 0; i < u->params.nutrs; i++) { + ufs_clear_req(&u->req_list[i]); + } + g_free(u->req_list); +} + static Property ufs_props[] = { DEFINE_PROP_STRING("serial", UfsHc, params.serial), DEFINE_PROP_UINT8("nutrs", UfsHc, params.nutrs, 32), @@ -281,6 +1246,7 @@ static void ufs_class_init(ObjectClass *oc, void *data) PCIDeviceClass *pc = PCI_DEVICE_CLASS(oc); pc->realize = ufs_realize; + pc->exit = ufs_exit; pc->class_id = PCI_CLASS_STORAGE_UFS; set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); diff --git a/hw/ufs/ufs.h b/hw/ufs/ufs.h index 3c28f4e62d..5d4fd818f9 100644 --- a/hw/ufs/ufs.h +++ b/hw/ufs/ufs.h @@ -18,6 +18,31 @@ #define UFS_MAX_LUS 32 #define UFS_LOGICAL_BLK_SIZE 4096 +typedef enum UfsRequestState { + UFS_REQUEST_IDLE = 0, + UFS_REQUEST_READY = 1, + UFS_REQUEST_RUNNING = 2, + UFS_REQUEST_COMPLETE = 3, +} UfsRequestState; + +typedef enum UfsReqResult { + UFS_REQUEST_SUCCESS = 0, + UFS_REQUEST_ERROR = 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 +55,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; qemu_irq irq; QEMUBH *doorbell_bh; @@ -39,4 +70,18 @@ typedef struct UfsHc { #define TYPE_UFS "ufs" #define UFS(obj) OBJECT_CHECK(UfsHc, (obj), TYPE_UFS) +typedef enum UfsQueryFlagPerm { + UFS_QUERY_FLAG_NONE = 0x0, + UFS_QUERY_FLAG_READ = 0x1, + UFS_QUERY_FLAG_SET = 0x2, + UFS_QUERY_FLAG_CLEAR = 0x4, + UFS_QUERY_FLAG_TOGGLE = 0x8, +} UfsQueryFlagPerm; + +typedef enum UfsQueryAttrPerm { + UFS_QUERY_ATTR_NONE = 0x0, + UFS_QUERY_ATTR_READ = 0x1, + UFS_QUERY_ATTR_WRITE = 0x2, +} UfsQueryAttrPerm; + #endif /* HW_UFS_UFS_H */ From patchwork Fri Jun 16 06:58:27 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jeuk Kim X-Patchwork-Id: 13282130 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 60214EB64DA for ; Fri, 16 Jun 2023 06:59:43 +0000 (UTC) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1qA3Px-0005pa-H2; Fri, 16 Jun 2023 02:58:45 -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 1qA3Ps-0005nz-LO for qemu-devel@nongnu.org; Fri, 16 Jun 2023 02:58:40 -0400 Received: from mailout1.samsung.com ([203.254.224.24]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1qA3Pk-0003wE-5s for qemu-devel@nongnu.org; Fri, 16 Jun 2023 02:58:40 -0400 Received: from epcas2p4.samsung.com (unknown [182.195.41.56]) by mailout1.samsung.com (KnoxPortal) with ESMTP id 20230616065829epoutp016ebb4d5a3bbd65940c4b76810d3c725b~pEXBjKUvj2952229522epoutp01i for ; Fri, 16 Jun 2023 06:58:29 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout1.samsung.com 20230616065829epoutp016ebb4d5a3bbd65940c4b76810d3c725b~pEXBjKUvj2952229522epoutp01i DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1686898709; bh=J/P8Uiyfal+L2ELBuoyrmSHXn8nfeK1gnEH5owGk3cw=; h=Subject:Reply-To:From:To:CC:Date:References:From; b=Asx83lL/wEY9p8y0c+1LUZvg1qD3l/qQvvUZbk11etj2xvz4Bo4kgQoHudT9C3WDc 0c+eOMqeKOKKf/hQkZygawKOB1duFUDi5JjJ3pAJjjHl2Sz9WRW3e52DAEZFp2kiAJ W4qcLWcbsYoVWouuIRio0zvlY+nRhUI1bSFSmzM4= Received: from epsnrtp2.localdomain (unknown [182.195.42.163]) by epcas2p1.samsung.com (KnoxPortal) with ESMTP id 20230616065829epcas2p1cd27f3eaa69894d2c83defa257f3b27d~pEXA62mkq3012230122epcas2p1-; Fri, 16 Jun 2023 06:58:29 +0000 (GMT) Received: from epsmges2p3.samsung.com (unknown [182.195.36.98]) by epsnrtp2.localdomain (Postfix) with ESMTP id 4Qj93m2WvYz4x9Q6; Fri, 16 Jun 2023 06:58:28 +0000 (GMT) X-AuditID: b6c32a47-eedff70000001ce0-39-648c0814559d Received: from epcas2p3.samsung.com ( [182.195.41.55]) by epsmges2p3.samsung.com (Symantec Messaging Gateway) with SMTP id 3E.25.07392.4180C846; Fri, 16 Jun 2023 15:58:28 +0900 (KST) Mime-Version: 1.0 Subject: [PATCH v2 3/3] hw/ufs: Support for UFS logical unit From: Jeuk Kim To: "qemu-devel@nongnu.org" CC: "pbonzini@redhat.com" , "fam@euphon.net" , "qemu-block@nongnu.org" , "kwolf@redhat.com" , "hreitz@redhat.com" , Klaus Birkelund Jensen , "stefanha@redhat.com" X-Priority: 3 X-Content-Kind-Code: NORMAL X-CPGS-Detection: blocking_info_exchange X-Drm-Type: N,general X-Msg-Generator: Mail X-Msg-Type: PERSONAL X-Reply-Demand: N Message-ID: <20230616065827epcms2p2ad3eefa7762feafe673ff9c0a54ba007@epcms2p2> Date: Fri, 16 Jun 2023 15:58:27 +0900 X-CMS-MailID: 20230616065827epcms2p2ad3eefa7762feafe673ff9c0a54ba007 X-Sendblock-Type: AUTO_CONFIDENTIAL CMS-TYPE: 102P X-CPGSPASS: Y X-CPGSPASS: Y X-Brightmail-Tracker: H4sIAAAAAAAAA+NgFupik+LIzCtJLcpLzFFi42LZdljTXFeEoyfFYMpzHouXhzQtruw/z2jx 4G4/o8XJxj2sFksuplrs3/aP1WLWu3Y2i+O9O1gsXk/6z+rA6fHjXDubx5Nrm5k83u+7yubR t2UVYwBLVLZNRmpiSmqRQmpecn5KZl66rZJ3cLxzvKmZgaGuoaWFuZJCXmJuqq2Si0+Arltm DtApSgpliTmlQKGAxOJiJX07m6L80pJUhYz84hJbpdSClJwC8wK94sTc4tK8dL281BIrQwMD I1OgwoTsjEUvNzAWLHjFXNH6cztzA+ORZ0xdjJwcEgImEosvPGHuYuTiEBLYwSgxZVYXaxcj BwevgKDE3x3CIDXCAjYSv2Y2gdULCShIzNnWwQ4R15SYvn4iE0g5m4C6xOmF5iBhEQFjiWOH l4CNZBbYziSxZ+FfdohdvBIz2p+yQNjSEtuXb2WEsDUkfizrZYawRSVurn7LDmO/PzYfqkZE ovXeWagaQYkHP3dDxSUlTn17zAphT2eUWPDfFGSxhMACRolfzdOhGvQlrnVsBFvMK+ArsbXn NNgzLAKqEnd2t4P9KyHgIvH7pi1ImFlAXmL72znMIGFmoB/X79KHqFCWOHKLBaKCT6LjMMJX DRt/Y2XvmPcEGsoqEoubD0MtkpLYsDt0AqPSLEQwz0KydhbC2gWMzKsYxVILinPTU4uNCozh cZucn7uJEZwotdx3MM54+0HvECMTB+MhRgkOZiUR3mUnulKEeFMSK6tSi/Lji0pzUosPMZoC /TuRWUo0OR+YqvNK4g1NLA1MzMwMzY1MDcyVxHmlbU8mCwmkJ5akZqemFqQWwfQxcXBKNTAt ye9p2bfwI4/bPG/12o/SBvv80u6r8iilLb596+zVmxsMF+0KFG17EXW4umJFZcSd8GPlJ/L8 D15Sbpb9ccP+S6Vdo+Lsd9smTpqawBtZMmcb70U96z+BVifuONjt637nsPavVv/Fm40X08+o hS6NaLx3Ye8to1btQ8V/rL9GZwYsv5N/SdngaOYPxYSlOStjvptnyDtsqa+fmDJ5ae7Lz79/ PL0z9XSYffmfnyLyPJ0bM9R3MWz/ab3vcv5Vvr+/7jLMy65du8bg/b0px4TuMzW4uvGf7J/w 5vgdJ/fJQtcyuLOqlRWMjF/euDM58rzK+97VljEnDs+4/qpnefPcuPchAkfCNI++Sd0V2/aE VYmlOCPRUIu5qDgRAMgRT6wdBAAA DLP-Filter: Pass X-CFilter-Loop: Reflected X-CMS-RootMailID: 20230616065827epcms2p2ad3eefa7762feafe673ff9c0a54ba007 References: Received-SPF: pass client-ip=203.254.224.24; envelope-from=jeuk20.kim@samsung.com; helo=mailout1.samsung.com X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.001, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, RCVD_IN_MSPIKE_H3=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=unavailable autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: jeuk20.kim@samsung.com Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org 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 --- hw/ufs/lu.c | 1441 ++++++++++++++++++++++++++++++++++++++ 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, 1757 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..ef69de61a5 --- /dev/null +++ b/hw/ufs/lu.c @@ -0,0 +1,1441 @@ +/* + * 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 = 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 = UFS(req->bus->qbus.parent); + UfsLu *lu = DO_UPCAST(UfsLu, qdev, req->dev); + uint8_t page_code = req->cmd.buf[2]; + int start, buflen = 0; + + if (outbuf_len < SCSI_INQUIRY_DATA_SIZE) { + return -1; + } + + outbuf[buflen++] = lu->qdev.type & 0x1f; + outbuf[buflen++] = page_code; + outbuf[buflen++] = 0x00; + outbuf[buflen++] = 0x00; + start = buflen; + + switch (page_code) { + case 0x00: /* Supported page codes, mandatory */ + { + trace_ufs_scsi_emulate_vpd_page_00(req->cmd.xfer); + outbuf[buflen++] = 0x00; /* list of supported pages (this page) */ + if (u->params.serial) { + outbuf[buflen++] = 0x80; /* unit serial number */ + } + outbuf[buflen++] = 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 = strlen(u->params.serial); + if (l > SCSI_INQUIRY_DATA_SIZE) { + l = SCSI_INQUIRY_DATA_SIZE; + } + + trace_ufs_scsi_emulate_vpd_page_80(req->cmd.xfer); + memcpy(outbuf + buflen, u->params.serial, l); + buflen += l; + break; + } + case 0x87: /* Mode Page Policy, mandatory */ + { + trace_ufs_scsi_emulate_vpd_page_87(req->cmd.xfer); + outbuf[buflen++] = 0x3f; /* apply to all mode pages and subpages */ + outbuf[buflen++] = 0xff; + outbuf[buflen++] = 0; /* shared */ + outbuf[buflen++] = 0; + break; + } + default: + return -1; + } + /* done with EVPD */ + assert(buflen - start <= 255); + outbuf[start - 1] = buflen - start; + return buflen; +} + +static int ufs_scsi_emulate_inquiry(SCSIRequest *req, uint8_t *outbuf, + uint32_t outbuf_len) +{ + int buflen = 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] != 0) { + return -1; + } + + /* PAGE CODE == 0 */ + buflen = req->cmd.xfer; + if (buflen > SCSI_MAX_INQUIRY_LEN) { + buflen = SCSI_MAX_INQUIRY_LEN; + } + + if (is_wlun(req->lun)) { + outbuf[0] = TYPE_WLUN; + } else { + outbuf[0] = 0; + } + outbuf[1] = 0; + + strpadcpy((char *)&outbuf[16], 16, "QEMU UFS", ' '); + strpadcpy((char *)&outbuf[8], 8, "QEMU", ' '); + + memset(&outbuf[32], 0, 4); + + outbuf[2] = 0x06; /* SPC-4 */ + outbuf[3] = 0x2; + + if (buflen > SCSI_INQUIRY_DATA_SIZE) { + outbuf[4] = buflen - 5; /* Additional Length = (Len - 1) - 4 */ + } else { + /* + * If the allocation length of CDB is too small, the additional + * length is not adjusted + */ + outbuf[4] = SCSI_INQUIRY_DATA_SIZE - 5; + } + + /* Support TCQ. */ + outbuf[7] = 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] = { + [MODE_PAGE_CACHING] = 1, + [MODE_PAGE_R_W_ERROR] = 1, + [MODE_PAGE_CONTROL] = 1, + }; + + uint8_t *p = *p_outbuf + 2; + int length; + + assert(page < ARRAY_SIZE(mode_sense_valid)); + if ((mode_sense_valid[page]) == 0) { + return -1; + } + + /* + * If Changeable Values are requested, a mask denoting those mode parameters + * 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 function. + */ + switch (page) { + case MODE_PAGE_CACHING: + length = 0x12; + if (page_control == 1 || /* Changeable Values */ + blk_enable_write_cache(lu->qdev.conf.blk)) { + p[0] = 4; /* WCE */ + } + break; + + case MODE_PAGE_R_W_ERROR: + length = 10; + if (page_control == 1) { /* Changeable Values */ + break; + } + p[0] = 0x80; /* Automatic Write Reallocation Enabled */ + break; + + case MODE_PAGE_CONTROL: + length = 10; + if (page_control == 1) { /* Changeable Values */ + break; + } + p[1] = 0x10; /* Queue Algorithm modifier */ + p[8] = 0xff; /* Busy Timeout Period */ + p[9] = 0xff; + break; + + default: + return -1; + } + + assert(length < 256); + (*p_outbuf)[0] = page; + (*p_outbuf)[1] = length; + *p_outbuf += length + 2; + return length + 2; +} + +static int ufs_scsi_emulate_mode_sense(UfsSCSIReq *r, uint8_t *outbuf) +{ + UfsLu *lu = DO_UPCAST(UfsLu, qdev, r->req.dev); + bool dbd; + int page, buflen, ret, page_control; + uint8_t *p; + uint8_t dev_specific_param = 0; + + dbd = (r->req.cmd.buf[1] & 0x8) != 0; + if (!dbd) { + return -1; + } + + page = r->req.cmd.buf[2] & 0x3f; + page_control = (r->req.cmd.buf[2] & 0xc0) >> 6; + + trace_ufs_scsi_emulate_mode_sense((r->req.cmd.buf[0] == MODE_SENSE) ? 6 : + 10, + page, r->req.cmd.xfer, page_control); + memset(outbuf, 0, r->req.cmd.xfer); + p = outbuf; + + if (!blk_is_writable(lu->qdev.conf.blk)) { + dev_specific_param |= 0x80; /* Readonly. */ + } + + p[2] = 0; /* Medium type. */ + p[3] = dev_specific_param; + p[6] = p[7] = 0; /* Block descriptor length. */ + p += 8; + + if (page_control == 3) { + /* Saved Values */ + scsi_check_condition(r, SENSE_CODE(SAVING_PARAMS_NOT_SUPPORTED)); + return -1; + } + + if (page == 0x3f) { + for (page = 0; page <= 0x3e; page++) { + mode_sense_page(lu, page, &p, page_control); + } + } else { + ret = mode_sense_page(lu, page, &p, page_control); + if (ret == -1) { + return -1; + } + } + + buflen = 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] = ((buflen - 2) >> 8) & 0xff; + outbuf[1] = (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 = (r->req.cmd.mode == SCSI_XFER_FROM_DEV); + UfsLu *lu = DO_UPCAST(UfsLu, qdev, r->req.dev); + SCSISense sense = SENSE_CODE(NO_SENSE); + int error = 0; + bool req_has_sense = false; + BlockErrorAction action; + int status; + + if (ret < 0) { + status = scsi_sense_from_errno(-ret, &sense); + error = -ret; + } else { + /* A passthrough command has completed with nonzero status. */ + status = ret; + if (status == CHECK_CONDITION) { + req_has_sense = true; + error = scsi_sense_buf_to_errno(r->req.sense, sizeof(r->req.sense)); + } else { + error = EINVAL; + } + } + + /* + * Check whether the error has to be handled by the guest or should + * rather follow the rerror=/werror= settings. Guest-handled errors + * 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 = BLOCK_ERROR_ACTION_REPORT; + acct_failed = false; + } else { + action = 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 == 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_failed) +{ + 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 = (UfsSCSIReq *)opaque; + UfsLu *lu = DO_UPCAST(UfsLu, qdev, r->req.dev); + + assert(r->req.aiocb != NULL); + r->req.aiocb = 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 = DO_UPCAST(UfsSCSIReq, req, req); + UfsLu *lu = DO_UPCAST(UfsLu, qdev, req->dev); + 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 = MAX(4096, req->cmd.xfer); + + if (!r->iov.iov_base) { + r->iov.iov_base = blk_blockalign(lu->qdev.conf.blk, r->buflen); + } + + outbuf = 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 = ufs_scsi_emulate_inquiry(req, outbuf, r->buflen); + if (buflen < 0) { + goto illegal_request; + } + break; + case MODE_SENSE_10: + buflen = 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); + + outbuf[0] = (lu->qdev.max_lba >> 24) & 0xff; + outbuf[1] = (lu->qdev.max_lba >> 16) & 0xff; + outbuf[2] = (lu->qdev.max_lba >> 8) & 0xff; + outbuf[3] = lu->qdev.max_lba & 0xff; + outbuf[4] = (lu->qdev.blocksize >> 24) & 0xff; + outbuf[5] = (lu->qdev.blocksize >> 16) & 0xff; + outbuf[6] = (lu->qdev.blocksize >> 8) & 0xff; + outbuf[7] = lu->qdev.blocksize & 0xff; + break; + case REQUEST_SENSE: + /* Just return "NO SENSE". */ + buflen = scsi_convert_sense(NULL, 0, outbuf, r->buflen, + (req->cmd.buf[1] & 1) == 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 = blk_aio_flush(lu->qdev.conf.blk, scsi_aio_complete, 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) == SAI_READ_CAPACITY_16) { + trace_ufs_scsi_emulate_command_SAI_16(); + memset(outbuf, 0, req->cmd.xfer); + + outbuf[0] = (lu->qdev.max_lba >> 56) & 0xff; + outbuf[1] = (lu->qdev.max_lba >> 48) & 0xff; + outbuf[2] = (lu->qdev.max_lba >> 40) & 0xff; + outbuf[3] = (lu->qdev.max_lba >> 32) & 0xff; + outbuf[4] = (lu->qdev.max_lba >> 24) & 0xff; + outbuf[5] = (lu->qdev.max_lba >> 16) & 0xff; + outbuf[6] = (lu->qdev.max_lba >> 8) & 0xff; + outbuf[7] = lu->qdev.max_lba & 0xff; + outbuf[8] = (lu->qdev.blocksize >> 24) & 0xff; + outbuf[9] = (lu->qdev.blocksize >> 16) & 0xff; + outbuf[10] = (lu->qdev.blocksize >> 8) & 0xff; + outbuf[11] = lu->qdev.blocksize & 0xff; + outbuf[12] = 0; + outbuf[13] = get_physical_block_exp(&lu->qdev.conf); + + if (lu->unit_desc.provisioning_type == 2 || + lu->unit_desc.provisioning_type == 3) { + outbuf[14] = 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 success. + * 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 = MIN(r->buflen, req->cmd.xfer); + if (r->iov.iov_len == 0) { + scsi_req_complete(&r->req, GOOD); + } + if (r->req.cmd.mode == SCSI_XFER_TO_DEV) { + assert(r->iov.iov_len == req->cmd.xfer); + return -r->iov.iov_len; + } else { + return r->iov.iov_len; + } + +illegal_request: + if (r->req.status == -1) { + scsi_check_condition(r, SENSE_CODE(INVALID_FIELD)); + } + return 0; +} + +static void ufs_scsi_emulate_read_data(SCSIRequest *req) +{ + UfsSCSIReq *r = DO_UPCAST(UfsSCSIReq, req, req); + int buflen = r->iov.iov_len; + + if (buflen) { + trace_ufs_scsi_emulate_read_data(buflen); + r->iov.iov_len = 0; + r->started = 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 = inlen + 2; + if (expected_len > SCSI_MAX_MODE_LEN) { + return -1; + } + + /* MODE_PAGE_ALLS is only valid for MODE SENSE commands */ + if (page == MODE_PAGE_ALLS) { + return -1; + } + + p = mode_current; + memset(mode_current, 0, inlen + 2); + len = mode_sense_page(lu, page, &p, 0); + if (len < 0 || len != expected_len) { + return -1; + } + + p = mode_changeable; + memset(mode_changeable, 0, inlen + 2); + changeable_len = mode_sense_page(lu, page, &p, 1); + assert(changeable_len == len); + + /* + * Check that unchangeable bits are the same as what MODE SENSE + * would return. + */ + for (i = 2; i < len; i++) { + if (((mode_current[i] ^ inbuf[i - 2]) & ~mode_changeable[i]) != 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) != 0); + break; + + default: + break; + } +} + +static int mode_select_pages(UfsSCSIReq *r, uint8_t *p, int len, bool change) +{ + UfsLu *lu = DO_UPCAST(UfsLu, qdev, r->req.dev); + + while (len > 0) { + int page, page_len; + + page = p[0] & 0x3f; + if (p[0] & 0x40) { + goto invalid_param; + } else { + if (len < 2) { + goto invalid_param_len; + } + page_len = p[1]; + p += 2; + len -= 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 += page_len; + len -= 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 = DO_UPCAST(UfsLu, qdev, r->req.dev); + uint8_t *p = inbuf; + int len = r->req.cmd.xfer; + int hdr_len = 8; + int bd_len; + int pass; + + /* We only support PF=1, SP=0. */ + if ((r->req.cmd.buf[1] & 0x11) != 0x10) { + goto invalid_field; + } + + if (len < hdr_len) { + goto invalid_param_len; + } + + bd_len = lduw_be_p(&p[6]); + if (bd_len != 0) { + goto invalid_param; + } + + len -= hdr_len; + p += hdr_len; + + /* Ensure no change is made if there is an error! */ + for (pass = 0; pass < 2; pass++) { + if (mode_select_pages(r, p, len, pass == 1) < 0) { + assert(pass == 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 = blk_aio_flush(lu->qdev.conf.blk, scsi_aio_complete, 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)); +} + +/* sector_num and nb_sectors expected to be in qdev blocksize */ +static inline bool check_lba_range(UfsLu *lu, uint64_t sector_num, + uint32_t nb_sectors) +{ + /* + * The first line tests that no overflow happens when computing the last + * sector. The second line tests that the last accessed sector is in + * range. + * + * Careful, the computations should not underflow for nb_sectors == 0, + * and a 0-block read to the first LBA beyond the end of device is + * valid. + */ + return (sector_num <= sector_num + nb_sectors && + sector_num + nb_sectors <= lu->qdev.max_lba + 1); +} + +static void ufs_scsi_emulate_write_data(SCSIRequest *req) +{ + UfsSCSIReq *r = DO_UPCAST(UfsSCSIReq, req, req); + + if (r->iov.iov_len) { + int buflen = r->iov.iov_len; + trace_ufs_scsi_emulate_write_data(buflen); + r->iov.iov_len = 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 = 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 = DO_UPCAST(UfsSCSIReq, req, req); + UfsLu *lu = DO_UPCAST(UfsLu, qdev, req->dev); + uint32_t len; + uint8_t command; + + command = buf[0]; + + if (!blk_is_available(lu->qdev.conf.blk)) { + scsi_check_condition(r, SENSE_CODE(NO_MEDIUM)); + return 0; + } + + len = 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 = r->req.cmd.lba * (lu->qdev.blocksize / BDRV_SECTOR_SIZE); + r->sector_count = 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 = r->req.cmd.lba * (lu->qdev.blocksize / BDRV_SECTOR_SIZE); + r->sector_count = 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 = ((r->req.cmd.buf[1] & 8) != 0); + if (r->sector_count == 0) { + scsi_req_complete(&r->req, GOOD); + } + assert(r->iov.iov_len == 0); + if (r->req.cmd.mode == 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 = DO_UPCAST(UfsLu, qdev, r->req.dev); + + assert(r->req.aiocb == 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 = blk_aio_flush(lu->qdev.conf.blk, scsi_aio_complete, 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 == NULL); + if (ufs_scsi_req_check_error(r, ret, false)) { + goto done; + } + + r->sector += r->sector_count; + r->sector_count = 0; + if (r->req.cmd.mode == 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 = (UfsSCSIReq *)opaque; + UfsLu *lu = DO_UPCAST(UfsLu, qdev, r->req.dev); + + assert(r->req.aiocb != NULL); + r->req.aiocb = 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 = opaque; + UfsLu *lu = 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 = DO_UPCAST(UfsLu, qdev, r->req.dev); + + if (!r->iov.iov_base) { + r->buflen = size; + r->iov.iov_base = blk_blockalign(lu->qdev.conf.blk, r->buflen); + } + r->iov.iov_len = 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 == NULL); + if (ufs_scsi_req_check_error(r, ret, false)) { + goto done; + } + + n = r->qiov.size / BDRV_SECTOR_SIZE; + r->sector += n; + r->sector_count -= 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 = (UfsSCSIReq *)opaque; + UfsLu *lu = DO_UPCAST(UfsLu, qdev, r->req.dev); + + assert(r->req.aiocb != NULL); + r->req.aiocb = 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 = DO_UPCAST(UfsLu, qdev, r->req.dev); + + assert(r->req.aiocb == 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 -= r->req.sg->size; + r->req.aiocb = dma_blk_io( + blk_get_aio_context(lu->qdev.conf.blk), r->req.sg, + r->sector << BDRV_SECTOR_BITS, BDRV_SECTOR_SIZE, scsi_dma_readv, 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 = 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 = (UfsSCSIReq *)opaque; + UfsLu *lu = DO_UPCAST(UfsLu, qdev, r->req.dev); + + assert(r->req.aiocb != NULL); + r->req.aiocb = 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 = DO_UPCAST(UfsSCSIReq, req, req); + UfsLu *lu = DO_UPCAST(UfsLu, qdev, r->req.dev); + bool first; + + trace_ufs_scsi_read_data_count(r->sector_count); + if (r->sector_count == 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 == NULL); + + /* The request is used as the AIO opaque value, so add a ref. */ + scsi_req_ref(&r->req); + if (r->req.cmd.mode == 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 = !r->started; + r->started = 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 = 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 == NULL); + if (ufs_scsi_req_check_error(r, ret, false)) { + goto done; + } + + n = r->qiov.size / BDRV_SECTOR_SIZE; + r->sector += n; + r->sector_count -= n; + if (r->sector_count == 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 = (UfsSCSIReq *)opaque; + UfsLu *lu = DO_UPCAST(UfsLu, qdev, r->req.dev); + + assert(r->req.aiocb != NULL); + r->req.aiocb = 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_opaque, + void *opaque) +{ + UfsSCSIReq *r = opaque; + UfsLu *lu = DO_UPCAST(UfsLu, qdev, r->req.dev); + return blk_aio_pwritev(lu->qdev.conf.blk, offset, iov, 0, cb, cb_opaque); +} + +static void scsi_write_data(SCSIRequest *req) +{ + UfsSCSIReq *r = DO_UPCAST(UfsSCSIReq, req, req); + UfsLu *lu = DO_UPCAST(UfsLu, qdev, r->req.dev); + + /* No data transfer may already be in progress */ + assert(r->req.aiocb == NULL); + + /* The request is used as the AIO opaque value, so add a ref. */ + scsi_req_ref(&r->req); + if (r->req.cmd.mode != 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 = 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 -= r->req.sg->size; + r->req.aiocb = dma_blk_io( + blk_get_aio_context(lu->qdev.conf.blk), r->req.sg, + r->sector << BDRV_SECTOR_BITS, BDRV_SECTOR_SIZE, scsi_dma_writev, 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 = scsi_dma_writev(r->sector << BDRV_SECTOR_BITS, &r->qiov, + scsi_write_complete, r, r); + } +} + +static const SCSIReqOps ufs_scsi_emulate_reqops = { + .size = sizeof(UfsSCSIReq), + .free_req = ufs_scsi_free_request, + .send_command = ufs_scsi_emulate_command, + .read_data = ufs_scsi_emulate_read_data, + .write_data = ufs_scsi_emulate_write_data, + .get_buf = ufs_scsi_get_buf, +}; + +static const SCSIReqOps ufs_scsi_dma_reqops = { + .size = sizeof(UfsSCSIReq), + .free_req = ufs_scsi_free_request, + .send_command = ufs_scsi_dma_command, + .read_data = scsi_read_data, + .write_data = scsi_write_data, + .get_buf = 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] = { + [TEST_UNIT_READY] = &ufs_scsi_emulate_reqops, + [INQUIRY] = &ufs_scsi_emulate_reqops, + [MODE_SENSE_10] = &ufs_scsi_emulate_reqops, + [START_STOP] = &ufs_scsi_emulate_reqops, + [READ_CAPACITY_10] = &ufs_scsi_emulate_reqops, + [REQUEST_SENSE] = &ufs_scsi_emulate_reqops, + [SYNCHRONIZE_CACHE] = &ufs_scsi_emulate_reqops, + [MODE_SELECT_10] = &ufs_scsi_emulate_reqops, + [VERIFY_10] = &ufs_scsi_emulate_reqops, + [FORMAT_UNIT] = &ufs_scsi_emulate_reqops, + [SERVICE_ACTION_IN_16] = &ufs_scsi_emulate_reqops, + [SEND_DIAGNOSTIC] = &ufs_scsi_emulate_reqops, + + [READ_6] = &ufs_scsi_dma_reqops, + [READ_10] = &ufs_scsi_dma_reqops, + [WRITE_6] = &ufs_scsi_dma_reqops, + [WRITE_10] = &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 = DO_UPCAST(UfsLu, qdev, dev); + SCSIRequest *req; + const SCSIReqOps *ops; + uint8_t command; + + command = buf[0]; + ops = ufs_scsi_reqops_dispatch[command]; + if (!ops) { + ops = &ufs_scsi_emulate_reqops; + } + req = scsi_req_alloc(ops, &lu->qdev, tag, lun, hba_private); + + return req; +} + +static Property ufs_lu_props[] = { + DEFINE_PROP_DRIVE_IOTHREAD("drive", UfsLu, qdev.conf.blk), + DEFINE_BLOCK_PROPERTIES_BASE(UfsLu, qdev.conf), + DEFINE_BLOCK_ERROR_PROPERTIES(UfsLu, qdev.conf), + DEFINE_PROP_END_OF_LIST(), +}; + +static bool ufs_lu_brdv_init(UfsLu *lu, Error **errp) +{ + SCSIDevice *dev = &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) != qemu_get_aio_context() && + !lu->qdev.hba_supports_iothread) { + error_setg(errp, "HBA does not support iothreads"); + return false; + } + + read_only = !blk_supports_write_perm(lu->qdev.conf.blk); + + if (!blkconf_apply_backend_options(&dev->conf, read_only, + dev->type == 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 = lu->qdev.conf.blk; + int64_t brdv_len = blk_getlength(blk); + uint64_t raw_dev_cap = + be64_to_cpu(u->geometry_desc.total_raw_device_capacity); + + if (u->device_desc.number_lu >= UFS_MAX_LUS) { + error_setg(errp, "ufs host controller has too many logical units."); + return false; + } + + if (u->lus[lu->lun] != NULL) { + error_setg(errp, "ufs logical unit %d already exists.", lu->lun); + return false; + } + + u->lus[lu->lun] = lu; + u->device_desc.number_lu++; + raw_dev_cap += (brdv_len >> UFS_GEOMETRY_CAPACITY_SHIFT); + u->geometry_desc.total_raw_device_capacity = cpu_to_be64(raw_dev_cap); + return true; +} + +static inline uint8_t ufs_log2(uint64_t input) +{ + int log = 1; + while (input >> log) { + log++; + } + return log; +} + +static void ufs_init_lu(UfsLu *lu) +{ + BlockBackend *blk = lu->qdev.conf.blk; + int64_t brdv_len = blk_getlength(blk); + + lu->lun = lu->qdev.lun; + memset(&lu->unit_desc, 0, sizeof(lu->unit_desc)); + lu->unit_desc.length = sizeof(UnitDescriptor); + lu->unit_desc.descriptor_idn = QUERY_DESC_IDN_UNIT; + lu->unit_desc.lu_enable = 0x01; + lu->unit_desc.logical_block_size = ufs_log2(lu->qdev.blocksize); + lu->unit_desc.unit_index = lu->qdev.lun; + lu->unit_desc.logical_block_count = + cpu_to_be64(brdv_len / (1 << lu->unit_desc.logical_block_size)); +} + +static bool ufs_lu_check_constraints(UfsLu *lu, Error **errp) +{ + if (!blk_is_available(lu->qdev.conf.blk)) { + error_setg(errp, "block backend not configured"); + return false; + } + + if (lu->qdev.channel != 0) { + error_setg(errp, "ufs logical unit does not support channel"); + return false; + } + + if (lu->qdev.lun >= 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 = DO_UPCAST(UfsLu, qdev, dev); + BusState *s = qdev_get_parent_bus(&dev->qdev); + UfsHc *u = UFS(s->parent); + AioContext *ctx = NULL; + uint64_t nb_sectors, nb_blocks; + + if (!ufs_lu_check_constraints(lu, errp)) { + return; + } + + if (lu->qdev.conf.blk) { + ctx = blk_get_aio_context(lu->qdev.conf.blk); + aio_context_acquire(ctx); + if (!blkconf_blocksizes(&lu->qdev.conf, errp)) { + goto out; + } + } + lu->qdev.blocksize = lu->qdev.conf.logical_block_size; + blk_get_geometry(lu->qdev.conf.blk, &nb_sectors); + nb_blocks = nb_sectors / (lu->qdev.blocksize / BDRV_SECTOR_SIZE) - 1; + if (nb_blocks > UINT32_MAX) { + nb_blocks = UINT32_MAX; + } + lu->qdev.max_lba = nb_blocks; + lu->qdev.type = 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 = DO_UPCAST(UfsLu, qdev, dev); + + blk_drain(lu->qdev.conf.blk); +} + +static void ufs_wlu_realize(DeviceState *qdev, Error **errp) +{ + UfsWLu *wlu = UFSWLU(qdev); + SCSIDevice *dev = &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 = DEVICE_CLASS(oc); + SCSIDeviceClass *sc = SCSI_DEVICE_CLASS(oc); + + sc->realize = ufs_lu_realize; + sc->unrealize = ufs_lu_unrealize; + sc->alloc_req = scsi_new_request; + dc->bus_type = TYPE_UFS_BUS; + device_class_set_props(dc, ufs_lu_props); + dc->desc = "Virtual UFS logical unit"; +} + +static void ufs_wlu_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + SCSIDeviceClass *sc = SCSI_DEVICE_CLASS(oc); + + /* + * The realize() function of TYPE_SCSI_DEVICE causes a segmentation fault + * 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 = ufs_wlu_realize; + sc->alloc_req = scsi_new_request; + dc->bus_type = TYPE_UFS_BUS; + dc->desc = "Virtual UFS well-known logical unit"; +} + +static const TypeInfo ufs_lu_info = { + .name = TYPE_UFS_LU, + .parent = TYPE_SCSI_DEVICE, + .class_init = ufs_lu_class_init, + .instance_size = sizeof(UfsLu), +}; + +static const TypeInfo ufs_wlu_info = { + .name = TYPE_UFS_WLU, + .parent = TYPE_SCSI_DEVICE, + .class_init = ufs_wlu_class_init, + .instance_size = 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 c1d90eeea6..9953b4fd9b 100644 --- a/hw/ufs/meson.build +++ b/hw/ufs/meson.build @@ -1 +1 @@ -softmmu_ss.add(when: 'CONFIG_UFS_PCI', if_true: files('ufs.c')) +softmmu_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 17793929b1..644a4c60bc 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 opcode) "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"" +# lu.c +ufs_scsi_check_condition(uint32_t tag, uint8_t key, uint8_t asc, uint8_t ascq) "Command complete tag=0x%x sense=%d/%d/%d" +ufs_scsi_read_complete(uint32_t tag, size_t size) "Data ready tag=0x%x len=%zd" +ufs_scsi_read_data_count(uint32_t sector_count) "Read sector_count=%d" +ufs_scsi_read_data_invalid(void) "Data transfer direction invalid" +ufs_scsi_write_complete_noio(uint32_t tag, size_t size) "Write complete tag=0x%x more=%zd" +ufs_scsi_write_data_invalid(void) "Data transfer direction invalid" +ufs_scsi_emulate_vpd_page_00(size_t xfer) "Inquiry EVPD[Supported pages] buffer size %zd" +ufs_scsi_emulate_vpd_page_80_not_supported(void) "Inquiry EVPD[Serial number] not supported" +ufs_scsi_emulate_vpd_page_80(size_t xfer) "Inquiry EVPD[Serial number] buffer 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=%d" +ufs_scsi_emulate_write_data(int buflen) "Write buf_len=%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=%s)" +ufs_scsi_dma_command_READ(uint64_t lba, uint32_t len) "Read (block %" PRIu64 ", count %u)" +ufs_scsi_dma_command_WRITE(uint64_t lba, int len) "Write (block %" PRIu64 ", count %u)" + # error condition ufs_err_memory_allocation(void) "failed to allocate memory" ufs_err_dma_read_utrd(uint32_t slot, uint64_t addr) "failed to read utrd. UTRLDBR slot %"PRIu32", UTRD dma addr %"PRIu64"" diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c index 10ecc8cd7b..d17f75446a 100644 --- a/hw/ufs/ufs.c +++ b/hw/ufs/ufs.c @@ -8,6 +8,19 @@ * This code is licensed under the GNU GPL v2 or later. */ +/** + * Reference Specs: https://www.jedec.org/, 3.1 + * + * Usage + * ----- + * + * Add options: + * -drive file=,if=none,id= + * -device ufs,serial=,id=, \ + * nutrs=,nutmrs= + * -device ufs-lu,drive=,bus= + */ + #include "qemu/osdep.h" #include "qapi/error.h" #include "migration/vmstate.h" @@ -459,6 +472,19 @@ static const MemoryRegionOps ufs_mmio_ops = { }, }; +static QEMUSGList *ufs_get_sg_list(SCSIRequest *scsi_req) +{ + UfsRequest *req = scsi_req->hba_private; + return req->sg; +} + +static void ufs_build_upiu_sense_data(UfsRequest *req, SCSIRequest *scsi_req) +{ + req->rsp_upiu.sr.sense_data_len = cpu_to_be16(scsi_req->sense_len); + assert(scsi_req->sense_len <= SCSI_SENSE_LEN); + memcpy(req->rsp_upiu.sr.sense_data, scsi_req->sense, scsi_req->sense_len); +} + static void ufs_build_upiu_header(UfsRequest *req, uint8_t trans_type, uint8_t flags, uint8_t response, uint8_t scsi_status, @@ -472,6 +498,98 @@ static void ufs_build_upiu_header(UfsRequest *req, uint8_t trans_type, req->rsp_upiu.header.data_segment_length = cpu_to_be16(data_segment_length); } +static void ufs_scsi_command_complete(SCSIRequest *scsi_req, size_t resid) +{ + UfsRequest *req = scsi_req->hba_private; + int16_t status = scsi_req->status; + uint32_t expected_len = be32_to_cpu(req->req_upiu.sc.exp_data_transfer_len); + uint32_t transfered_len = scsi_req->cmd.xfer - resid; + uint8_t flags = 0, response = COMMAND_RESULT_SUCESS; + uint16_t data_segment_length; + + if (expected_len > transfered_len) { + req->rsp_upiu.sr.residual_transfer_count = + cpu_to_be32(expected_len - transfered_len); + flags |= UFS_UPIU_FLAG_UNDERFLOW; + } else if (expected_len < transfered_len) { + req->rsp_upiu.sr.residual_transfer_count = + cpu_to_be32(transfered_len - expected_len); + flags |= UFS_UPIU_FLAG_OVERFLOW; + } + + if (status != 0) { + ufs_build_upiu_sense_data(req, scsi_req); + response = COMMAND_RESULT_FAIL; + } + + data_segment_length = cpu_to_be16(scsi_req->sense_len + + sizeof(req->rsp_upiu.sr.sense_data_len)); + ufs_build_upiu_header(req, UPIU_TRANSACTION_RESPONSE, flags, response, + status, data_segment_length); + + ufs_complete_req(req, UFS_REQUEST_SUCCESS); + + scsi_req->hba_private = NULL; + scsi_req_unref(scsi_req); +} + +static const struct SCSIBusInfo ufs_scsi_info = { + .tcq = true, + .max_target = 0, + .max_lun = UFS_MAX_LUS, + .max_channel = 0, + + .get_sg_list = ufs_get_sg_list, + .complete = ufs_scsi_command_complete, +}; + +static UfsReqResult ufs_exec_scsi_cmd(UfsRequest *req) +{ + UfsHc *u = req->hc; + uint8_t lun = req->req_upiu.header.lun; + uint8_t task_tag = req->req_upiu.header.task_tag; + SCSIDevice *dev = NULL; + + trace_ufs_exec_scsi_cmd(req->slot, lun, req->req_upiu.sc.cdb[0]); + + if (!is_wlun(lun)) { + if (lun >= u->device_desc.number_lu) { + trace_ufs_err_scsi_cmd_invalid_lun(lun); + return UFS_REQUEST_ERROR; + } else if (u->lus[lun] == NULL) { + trace_ufs_err_scsi_cmd_invalid_lun(lun); + return UFS_REQUEST_ERROR; + } + } + + switch (lun) { + case UFS_UPIU_REPORT_LUNS_WLUN: + dev = &u->report_wlu->qdev; + break; + case UFS_UPIU_UFS_DEVICE_WLUN: + dev = &u->dev_wlu->qdev; + break; + case UFS_UPIU_BOOT_WLUN: + dev = &u->boot_wlu->qdev; + break; + case UFS_UPIU_RPMB_WLUN: + dev = &u->rpmb_wlu->qdev; + break; + default: + dev = &u->lus[lun]->qdev; + } + + SCSIRequest *scsi_req = scsi_req_new( + dev, task_tag, lun, req->req_upiu.sc.cdb, UFS_CDB_SIZE, req); + + uint32_t len = 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); @@ -749,9 +867,11 @@ static const RpmbUnitDescriptor rpmb_unit_desc = { static QueryRespCode ufs_read_unit_desc(UfsRequest *req) { + UfsHc *u = req->hc; uint8_t lun = req->req_upiu.qr.index; - if (lun != UFS_UPIU_RPMB_WLUN && lun > UFS_MAX_LUS) { + if (lun != UFS_UPIU_RPMB_WLUN && + (lun > UFS_MAX_LUS || u->lus[lun] == NULL)) { trace_ufs_err_query_invalid_index(req->req_upiu.qr.opcode, lun); return QUERY_RESULT_INVALID_INDEX; } @@ -759,8 +879,8 @@ static QueryRespCode ufs_read_unit_desc(UfsRequest *req) if (lun == UFS_UPIU_RPMB_WLUN) { memcpy(&req->rsp_upiu.qr.data, &rpmb_unit_desc, rpmb_unit_desc.length); } else { - /* unit descriptor is not yet supported */ - return QUERY_RESULT_INVALID_INDEX; + memcpy(&req->rsp_upiu.qr.data, &u->lus[lun]->unit_desc, + sizeof(u->lus[lun]->unit_desc)); } return QUERY_RESULT_SUCCESS; @@ -976,8 +1096,7 @@ static void ufs_exec_req(UfsRequest *req) req_result = ufs_exec_nop_cmd(req); break; case UPIU_TRANSACTION_COMMAND: - /* Not yet implemented */ - req_result = UFS_REQUEST_ERROR; + req_result = ufs_exec_scsi_cmd(req); break; case UPIU_TRANSACTION_QUERY_REQ: req_result = ufs_exec_query_cmd(req); @@ -988,7 +1107,14 @@ static void ufs_exec_req(UfsRequest *req) req_result = UFS_REQUEST_ERROR; } - 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 commands. + */ + if (req_result != UFS_REQUEST_NO_COMPLETE) { + ufs_complete_req(req, req_result); + } } static void ufs_process_req(void *opaque) @@ -1202,6 +1328,28 @@ static void ufs_init_hc(UfsHc *u) u->flags.permanently_disable_fw_update = 1; } +static bool ufs_init_wlu(UfsHc *u, UfsWLu **wlu, uint8_t wlun, Error **errp) +{ + UfsWLu *new_wlu = 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 = new_wlu; + return true; +} + static void ufs_realize(PCIDevice *pci_dev, Error **errp) { UfsHc *u = UFS(pci_dev); @@ -1210,15 +1358,55 @@ static void ufs_realize(PCIDevice *pci_dev, Error **errp) return; } + qbus_init(&u->bus, sizeof(UfsBus), TYPE_UFS_BUS, &pci_dev->qdev, + u->parent_obj.qdev.id); + u->bus.parent_bus.info = &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; + } } static void ufs_exit(PCIDevice *pci_dev) { UfsHc *u = UFS(pci_dev); + if (u->dev_wlu) { + object_unref(OBJECT(u->dev_wlu)); + u->dev_wlu = NULL; + } + + if (u->report_wlu) { + object_unref(OBJECT(u->report_wlu)); + u->report_wlu = NULL; + } + + if (u->rpmb_wlu) { + object_unref(OBJECT(u->rpmb_wlu)); + u->rpmb_wlu = NULL; + } + + if (u->boot_wlu) { + object_unref(OBJECT(u->boot_wlu)); + u->boot_wlu = NULL; + } + qemu_bh_delete(u->doorbell_bh); qemu_bh_delete(u->complete_bh); @@ -1255,6 +1443,49 @@ static void ufs_class_init(ObjectClass *oc, void *data) dc->vmsd = &ufs_vmstate; } +static bool ufs_bus_check_address(BusState *qbus, DeviceState *qdev, + Error **errp) +{ + SCSIDevice *dev = SCSI_DEVICE(qdev); + UfsBusClass *ubc = UFS_BUS_GET_CLASS(qbus); + UfsHc *u = UFS(qbus->parent); + + if (strcmp(object_get_typename(OBJECT(dev)), TYPE_UFS_WLU) == 0) { + if (dev->lun != UFS_UPIU_REPORT_LUNS_WLUN && + dev->lun != UFS_UPIU_UFS_DEVICE_WLUN && + dev->lun != UFS_UPIU_BOOT_WLUN && dev->lun != UFS_UPIU_RPMB_WLUN) { + error_setg(errp, "bad well-known lun: %d", dev->lun); + return false; + } + + if ((dev->lun == UFS_UPIU_REPORT_LUNS_WLUN && u->report_wlu != NULL) || + (dev->lun == UFS_UPIU_UFS_DEVICE_WLUN && u->dev_wlu != NULL) || + (dev->lun == UFS_UPIU_BOOT_WLUN && u->boot_wlu != NULL) || + (dev->lun == UFS_UPIU_RPMB_WLUN && u->rpmb_wlu != 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) != 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 = BUS_CLASS(class); + UfsBusClass *ubc = UFS_BUS_CLASS(class); + ubc->parent_check_address = bc->check_address; + bc->check_address = ufs_bus_check_address; +} + static const TypeInfo ufs_info = { .name = TYPE_UFS, .parent = TYPE_PCI_DEVICE, @@ -1263,9 +1494,18 @@ static const TypeInfo ufs_info = { .interfaces = (InterfaceInfo[]){ { INTERFACE_PCIE_DEVICE }, {} }, }; +static const TypeInfo ufs_bus_info = { + .name = TYPE_UFS_BUS, + .parent = TYPE_SCSI_BUS, + .class_init = ufs_bus_class_init, + .class_size = sizeof(UfsBusClass), + .instance_size = sizeof(UfsBus), +}; + static void ufs_register_types(void) { type_register_static(&ufs_info); + type_register_static(&ufs_bus_info); } type_init(ufs_register_types) diff --git a/hw/ufs/ufs.h b/hw/ufs/ufs.h index 5d4fd818f9..6d2dc000e9 100644 --- a/hw/ufs/ufs.h +++ b/hw/ufs/ufs.h @@ -18,6 +18,18 @@ #define UFS_MAX_LUS 32 #define UFS_LOGICAL_BLK_SIZE 4096 +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 = 0, UFS_REQUEST_READY = 1, @@ -28,6 +40,7 @@ typedef enum UfsRequestState { typedef enum UfsReqResult { UFS_REQUEST_SUCCESS = 0, UFS_REQUEST_ERROR = 1, + UFS_REQUEST_NO_COMPLETE = 2, } UfsReqResult; typedef struct UfsRequest { @@ -43,6 +56,17 @@ typedef struct UfsRequest { QEMUSGList *sg; } UfsRequest; +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 */ @@ -51,12 +75,18 @@ typedef struct UfsParams { typedef struct UfsHc { PCIDevice parent_obj; + UfsBus bus; MemoryRegion iomem; UfsReg reg; UfsParams params; uint32_t reg_size; UfsRequest *req_list; + 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; @@ -70,6 +100,12 @@ typedef struct UfsHc { #define TYPE_UFS "ufs" #define UFS(obj) OBJECT_CHECK(UfsHc, (obj), TYPE_UFS) +#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 = 0x0, UFS_QUERY_FLAG_READ = 0x1, @@ -84,4 +120,11 @@ typedef enum UfsQueryAttrPerm { UFS_QUERY_ATTR_WRITE = 0x2, } UfsQueryAttrPerm; +static inline bool is_wlun(uint8_t lun) +{ + return (lun == UFS_UPIU_REPORT_LUNS_WLUN || + lun == UFS_UPIU_UFS_DEVICE_WLUN || lun == UFS_UPIU_BOOT_WLUN || + lun == 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