From nobody Sat Sep 26 20:51:02 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=reject dis=none) header.from=google.com ARC-Seal: i=1; a=rsa-sha256; t=1788503276; cv=none; d=zohomail.com; s=zohoarc; b=K0FNf/cStkKXn3nbYE2h3n0zs+NQjOkEqcn3jdo58aHobFCRRgADImwj5RXx8eEB+Zf9+ehsJBn5QOVMcTPYLBtxe18Xbx70AYadcNIaOHxP5nQapfzKEjmLIhH9M+RxCRE8EE3hyaN9ow7uDPw7NJZNpUNbdCixTmMSZvXDqjw= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788503276; h=Content-Type:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=nCtO4gELwPq20yztFyVCinNc8WEEbXyTCynQ9QwGoZc=; b=Naz4vbNzSRw5/bpJDCrrmsb9qc6gK06+jYaxP+9HRt7yHEAutHgH49UFhgnpUIIiR1jdYGpAI9zsuDNA6zvwANJEgfNrm+KdDOB8MmVMKTt6Zd7g4AQefNMjz5OueMyXO4Gi4kf9riDO7bRzV3M/XaQhfZym6X3buKBulia9kFw= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=reject dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 178850327676259.17973179408625; Thu, 3 Sep 2026 23:27:56 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x2NOO-0008Hz-KG; Fri, 04 Sep 2026 02:27:16 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from <3vWSaagoKCkw67o1zsCxv8u22uzs.q204s08-rs9sz121u18.25u@flex--stanleyjhu.bounces.google.com>) id 1x2NOM-0008HA-Mv for qemu-devel@nongnu.org; Fri, 04 Sep 2026 02:27:14 -0400 Received: from mail-pj1-x1046.google.com ([2607:f8b0:4864:20::1046]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from <3vWSaagoKCkw67o1zsCxv8u22uzs.q204s08-rs9sz121u18.25u@flex--stanleyjhu.bounces.google.com>) id 1x2NOK-0005FV-Kn for qemu-devel@nongnu.org; Fri, 04 Sep 2026 02:27:14 -0400 Received: by mail-pj1-x1046.google.com with SMTP id 98e67ed59e1d1-398e1fafe17so1024762a91.0 for ; Thu, 03 Sep 2026 23:27:11 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1788503230; x=1789108030; darn=nongnu.org; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:from:to:cc:subject:date:message-id:reply-to :content-type; bh=nCtO4gELwPq20yztFyVCinNc8WEEbXyTCynQ9QwGoZc=; b=JHx6gVfUVSx9qbNszNuMR26St47l44rxCSorjk9ypjdNdTMflt+JCkZe2EUo4ymLxq 1Ef77wLhK6SLXmsZbbPZwKCT2RHg6jwkzQT3D43vsF527pCFFclmAJNTPdlppsta/v9U pnz/9W49RiWA3et6U0MhgEENOw7YjVE0KL7Ra7lWBhulTul5+2IJaUSBCVJYJuEStaXp nc++Pb4ZLmpuh29ZH9CE5NA1b3kvX4/qQ0berQ8xNTK8V7Th7pZJcs+recU0g1futzJM 8EWNR0P1IYTRqltkeZ0zZ2uU0wNGB7150P+la90z9Ahza/5s1FSVdeIpbZsegcp3ikin gxLg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788503230; x=1789108030; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=nCtO4gELwPq20yztFyVCinNc8WEEbXyTCynQ9QwGoZc=; b=G+9QvOYOJxvlvzD06xKugjBdqTWTpUTj6J9vCTw3EVMYUPuOq3W2dkuHdkt2u8gY0O Q/yoFbODggPEZvZHdKYKv2ZEztvY8va5wiGe0azOmLVBaCBY2hWpBoGsqQBHYfrKF/on GrQYQOGA2DbUnp2+2+BwN3LsPxmLb+0nQ6/B4kdZ8WYH3OhsS6wpjdLqdqVeZWy5VzCn E4jR86l8FOPt7AaqAXgKMwqsX4FkbZ4aRNizSVwVOeOh+rSNL9FC+mse+t0Oo/LZpDcS zr0T5ujLMEc55yelvd3ws6dvmvydZnpfmCtp1OU5EXnMz+mRB8gG8cvnVymXS5SBoJt3 0sag== X-Forwarded-Encrypted: i=1; AKwUvBxL6jmggmBRWSuMatW21inILEUqHAeIdeTOGbQR7wGEZiBGZtwB3bVTLsKd5iwspy4/eYsg3UCunHtD@nongnu.org X-Gm-Message-State: AFuF++lEKrLaGxiTrN6nlvWtceYd7ruAnKWYJHstmhkbldTzbEKQielw TEaUlyejvcxRvHNaFI4aeyU5O09rNRrRJT0jfhmQdOnbFhZn7zTwDeawp5VAyF9tusEh2iw4CGS E7nBSTaa9bvxfxjDdu8QHTA== X-Received: from pjzb15.prod.google.com ([2002:a17:90a:e38f:b0:384:aeb6:624c]) (user=stanleyjhu job=prod-delivery.src-stubby-dispatcher) by 2002:a17:90b:2f46:b0:398:9c39:520f with SMTP id 98e67ed59e1d1-39b261e763dmr6705198a91.15.1788503229802; Thu, 03 Sep 2026 23:27:09 -0700 (PDT) Date: Fri, 4 Sep 2026 14:27:04 +0800 In-Reply-To: <20260904062706.2684958-1-stanleyjhu@google.com> Mime-Version: 1.0 References: <20260904062706.2684958-1-stanleyjhu@google.com> X-Mailer: git-send-email 2.55.0.979.g7e5102b832-goog Message-ID: <20260904062706.2684958-2-stanleyjhu@google.com> Subject: [PATCH 1/3] hw/ufs: Support MCQ runtime interrupt and queue status registers From: Stanley Jhu To: Jeuk Kim , qemu-devel@nongnu.org Cc: Jeuk Kim , Jinyoung Choi , qemu-block@nongnu.org, Stanley Jhu Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2607:f8b0:4864:20::1046; envelope-from=3vWSaagoKCkw67o1zsCxv8u22uzs.q204s08-rs9sz121u18.25u@flex--stanleyjhu.bounces.google.com; helo=mail-pj1-x1046.google.com X-Spam_score_int: -95 X-Spam_score: -9.6 X-Spam_bar: --------- X-Spam_report: (-9.6 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_MED=-0.001, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, USER_IN_DEF_DKIM_WL=-7.5 autolearn=unavailable autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @google.com) X-ZM-MESSAGEID: 1788503278269158500 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" According to JEDEC UFSHCI 5.2.1 and 5.6, Multi-Circular Queue (MCQ) architecture provides per-queue runtime control and interrupt registers. When Linux initializes MCQ via ufshcd_mcq_make_queues_operational(), it accesses SQnRTC, SQnCTI, SQnIS, SQnIE, CQnIS, CQnIE, and CQnIACR. Currently, QEMU logs "invalid register offset" warnings for these registers. Implement handling for these operational registers: - Define REG32 and FIELD macros for SQRTC (STOP, ICU) and SQRTS (STS, CUS, = RTC). - SQnRTC: Handle SQSTART (bit 0=3D0) and SQSTOP (bit 0=3D1) commands to upd= ate SQnRTS (Run-Time Status), scheduling or cancelling the SQ bottom half. Enforce SQSTOP in ufs_mcq_process_sq() to halt processing when stopped. - SQnRTC / SQnRTS: Handle SQ_ICU (bit 1=3D1) queue cleanup command, reporti= ng SQ_CUS (bit 1=3D1) and RTC completion code 0 in SQnRTS. - SQnCTI: Store Completion Timeout Interval configuration. - SQnIS: Handle Write-1-to-Clear interrupt status and invoke ufs_irq_check(= ). - CQnIS: Handle Write-1-to-Clear interrupt status. Recalculate whether any CQ has pending interrupts, and clear the global CQES (CQ Event Status) bit in IS before calling ufs_irq_check() to prevent IRQ storms. - SQnIE / CQnIE: Store Interrupt Enable configuration. - CQnIACR: Store Interrupt Aggregation Control Register configuration. - In ufs_hce_reset(), reset operational registers (utriacr, utrlclr, ie, utrlba/utrlbau) while preserving the Max Active Channels (MAC) capability field in MCQCONFIG according to params.mcq. - Add trace_ufs_write_mcq_op_reg trace event with explicit offset cast. Signed-off-by: Stanley Jhu --- hw/ufs/trace-events | 1 + hw/ufs/ufs.c | 69 +++++++++++++++++++++++++++++++++++++++++++-- include/block/ufs.h | 9 ++++++ 3 files changed, 77 insertions(+), 2 deletions(-) diff --git a/hw/ufs/trace-events b/hw/ufs/trace-events index 922293355b..d8173b12b6 100644 --- a/hw/ufs/trace-events +++ b/hw/ufs/trace-events @@ -14,6 +14,7 @@ ufs_process_uiccmd(uint32_t uiccmd, uint32_t ucmdarg1, ui= nt32_t ucmdarg2, uint32 ufs_mcq_complete_req(uint8_t qid) "sqid %"PRIu8"" ufs_mcq_create_sq(uint8_t sqid, uint8_t cqid, uint64_t addr, uint16_t size= ) "mcq create sq sqid %"PRIu8", cqid %"PRIu8", addr 0x%"PRIx64", size %"PRI= u16"" ufs_mcq_create_cq(uint8_t cqid, uint64_t addr, uint16_t size) "mcq create = cq cqid %"PRIu8", addr 0x%"PRIx64", size %"PRIu16"" +ufs_write_mcq_op_reg(uint8_t qid, uint32_t offset, uint32_t data) "qid %"P= RIu8", offset 0x%"PRIx32", data 0x%"PRIx32"" ufs_hce_reset(void) "HCE 1 -> 0 reset: cancelling BHs, resetting MCQ and r= equest lists" =20 # error condition diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c index 3c4d7424da..5064878028 100644 --- a/hw/ufs/ufs.c +++ b/hw/ufs/ufs.c @@ -446,13 +446,14 @@ static void ufs_mcq_process_sq(void *opaque) { UfsSq *sq =3D opaque; UfsHc *u =3D sq->u; + UfsMcqOpReg *opr =3D &u->mcq_op_reg[sq->sqid]; UfsSqEntry sqe; UfsRequest *req; hwaddr addr; uint16_t head =3D ufs_mcq_sq_head(u, sq->sqid); int err; =20 - if (u->resetting) { + if (u->resetting || FIELD_EX32(opr->sq.rts, SQRTS, STS)) { return; } =20 @@ -770,7 +771,17 @@ static void ufs_hce_reset(UfsHc *u) u->reg.utmrldbr =3D 0; u->reg.utrlcnr =3D 0; u->reg.utrlrsr =3D 0; + u->reg.utriacr =3D 0; + u->reg.utrlclr =3D 0; + if (u->params.mcq) { + u->reg.mcqconfig =3D FIELD_DP32(0, MCQCONFIG, MAC, 0x1f); + } else { + u->reg.mcqconfig =3D 0; + } + u->reg.ie =3D 0; u->reg.is =3D 0; + u->reg.utrlba =3D 0; + u->reg.utrlbau =3D 0; =20 /* 4. Free MCQ Queues and reset MCQ dynamic registers */ if (u->params.mcq) { @@ -983,6 +994,9 @@ static void ufs_write_mcq_op_reg(UfsHc *u, hwaddr offse= t, uint32_t data, =20 opr =3D &u->mcq_op_reg[qid]; =20 + trace_ufs_write_mcq_op_reg(qid, (uint32_t)(offset % sizeof(UfsMcqOpReg= )), + data); + switch (offset % sizeof(UfsMcqOpReg)) { case offsetof(UfsMcqOpReg, sq.tp): if (opr->sq.tp !=3D data) { @@ -990,6 +1004,38 @@ static void ufs_write_mcq_op_reg(UfsHc *u, hwaddr off= set, uint32_t data, } opr->sq.tp =3D data; break; + case offsetof(UfsMcqOpReg, sq.rtc): + opr->sq.rtc =3D data; + if (FIELD_EX32(data, SQRTC, ICU)) { + /* SQ_ICU: Initiate Cleanup (SQ_CUS =3D 1, RTC =3D 0) */ + opr->sq.rts =3D FIELD_DP32(opr->sq.rts, SQRTS, CUS, 1); + opr->sq.rts =3D FIELD_DP32(opr->sq.rts, SQRTS, RTC, 0); + } + if (FIELD_EX32(data, SQRTC, STOP)) { + /* SQ_STOP: Stop queue */ + opr->sq.rts =3D FIELD_DP32(opr->sq.rts, SQRTS, STS, 1); + if (u->sq[qid] && u->sq[qid]->bh) { + qemu_bh_cancel(u->sq[qid]->bh); + } + } else { + /* SQ_START: Start queue */ + opr->sq.rts =3D FIELD_DP32(opr->sq.rts, SQRTS, STS, 0); + opr->sq.rts =3D FIELD_DP32(opr->sq.rts, SQRTS, CUS, 0); + if (u->sq[qid] && u->sq[qid]->bh) { + qemu_bh_schedule(u->sq[qid]->bh); + } + } + break; + case offsetof(UfsMcqOpReg, sq.cti): + opr->sq.cti =3D data; + break; + case offsetof(UfsMcqOpReg, sq_int.is): + opr->sq_int.is &=3D ~data; + ufs_irq_check(u); + break; + case offsetof(UfsMcqOpReg, sq_int.ie): + opr->sq_int.ie =3D data; + break; case offsetof(UfsMcqOpReg, cq.hp): { UfsCq *cq =3D u->cq[qid]; =20 @@ -1006,8 +1052,27 @@ static void ufs_write_mcq_op_reg(UfsHc *u, hwaddr of= fset, uint32_t data, ufs_mcq_update_cq_head(u, qid, data); break; } - case offsetof(UfsMcqOpReg, cq_int.is): + case offsetof(UfsMcqOpReg, cq_int.is): { + bool pending =3D false; + opr->cq_int.is &=3D ~data; + for (int i =3D 0; i < ARRAY_SIZE(u->mcq_op_reg); i++) { + if (u->mcq_op_reg[i].cq_int.is) { + pending =3D true; + break; + } + } + if (!pending) { + u->reg.is =3D FIELD_DP32(u->reg.is, IS, CQES, 0); + } + ufs_irq_check(u); + break; + } + case offsetof(UfsMcqOpReg, cq_int.ie): + opr->cq_int.ie =3D data; + break; + case offsetof(UfsMcqOpReg, cq_int.iacr): + opr->cq_int.iacr =3D data; break; default: trace_ufs_err_invalid_register_offset(offset); diff --git a/include/block/ufs.h b/include/block/ufs.h index d19b3c65ef..00591aa755 100644 --- a/include/block/ufs.h +++ b/include/block/ufs.h @@ -224,6 +224,15 @@ typedef struct QEMU_PACKED UfsMcqSqReg { uint32_t rts; } UfsMcqSqReg; =20 +REG32(SQRTC, offsetof(UfsMcqSqReg, rtc)) + FIELD(SQRTC, STOP, 0, 1) + FIELD(SQRTC, ICU, 1, 1) + +REG32(SQRTS, offsetof(UfsMcqSqReg, rts)) + FIELD(SQRTS, STS, 0, 1) + FIELD(SQRTS, CUS, 1, 1) + FIELD(SQRTS, RTC, 4, 4) + typedef struct QEMU_PACKED UfsMcqCqReg { uint32_t hp; uint32_t tp; --=20 2.55.0.979.g7e5102b832-goog From nobody Sat Sep 26 20:51:02 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=reject dis=none) header.from=google.com ARC-Seal: i=1; a=rsa-sha256; t=1788503277; cv=none; d=zohomail.com; s=zohoarc; b=XsE24nATqqkAGfc3+QXucuxYk+csVHfzFWQvQmtJAMXuvhIjxsHHLnnb8NB+9csbl20Wf9taIRPuUqWH7DOgzYlmCtabt2/7iCZZ1vtbklMdRLQ4WMrfSBn33PwnCOUR/ybrD4zYp0wx6mCfUNxpBwlUrw2hp5JZgbhwdyuWSS8= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788503277; h=Content-Type:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=BWI75te92WAOR10DOoSbgBN2CFVLAtdlPgPv1u+hVgo=; b=S4vzNGwclV0XzbiDtFyEIehq/s1zPzBxotVYtyL6ukgXA97c2OEcgI+n/8TLRvw0YhM12JLDeZ/0NDp6Ba0rN0h3JXZC1IyVJ1Y80zlneydj+iFW/xgIPL7WHpOGFgUz3opnBYwtIPsMQVjWgO42agkYR6zAb20NerDZjmoeewk= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=reject dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788503277824839.8814497159498; Thu, 3 Sep 2026 23:27:57 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x2NOP-0008IK-H7; Fri, 04 Sep 2026 02:27:17 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from <3v2SaagoKCk489q31uEzxAw44w1u.s426u2A-tuBu1343w3A.47w@flex--stanleyjhu.bounces.google.com>) id 1x2NOO-0008Hv-ES for qemu-devel@nongnu.org; Fri, 04 Sep 2026 02:27:16 -0400 Received: from mail-pj1-x1048.google.com ([2607:f8b0:4864:20::1048]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from <3v2SaagoKCk489q31uEzxAw44w1u.s426u2A-tuBu1343w3A.47w@flex--stanleyjhu.bounces.google.com>) id 1x2NOM-0005Fv-Gr for qemu-devel@nongnu.org; Fri, 04 Sep 2026 02:27:16 -0400 Received: by mail-pj1-x1048.google.com with SMTP id 98e67ed59e1d1-38dc085b0a7so1240729a91.2 for ; Thu, 03 Sep 2026 23:27:13 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1788503232; x=1789108032; darn=nongnu.org; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:from:to:cc:subject:date:message-id:reply-to :content-type; bh=BWI75te92WAOR10DOoSbgBN2CFVLAtdlPgPv1u+hVgo=; b=Hv0BrAIhu0n5W1TQRqKFvpb+9HF9crF978y7pv0X+NFADFmnvmy8+l2OkQHjzpL2rP k5urwaOzOo2mBoQx37kAExuI9WvkA7mFPNzUp34pnxtdEyOg7uqxaO03WAbcQeoRTQUF JCHFMzcUAMQ+vmV162ak5Lw7OBWU0kqgrEkvClAyf5JVbpJJ0LHaHFBYwxrKCIVjj8rq uaN+RpQiZwICWdB6MWAjV6vKUP/mulde/a5WOQjMwkhQbXHOuC1DYKfgT5BtYmR61mMp zQV0FTwE2EW4EX6QztZcD1fNfFz2YCEH4eTjEdCmYH8qDiDMGR6sARrsDbfALCQuP/Pf KZ2w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788503232; x=1789108032; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=BWI75te92WAOR10DOoSbgBN2CFVLAtdlPgPv1u+hVgo=; b=NnCkI2mZxxBV2e4iWjo7wwJZCRqee828lJctgrNt4dwo+9ACmdB6vamUq50+IuYykl rS9HNN8aBlYoG+yHQCUYegB6GodxLmgh8hVOv2I/LcW4uHLsjz6Kckzn6/xgCvd6Xte7 tyXv8IIO0w16rTzAu4D2ZernAvOFzdtO/EZDVb1JvBJcfIspxKZpVAF4OEQbOigLCQih kwoxRC/j5Bo1leeK1MmjBgYH3ZRiLukwshA0CKteIynsqivN+oFEOS5Ud0BrbF5nWW9I MtDJ8oaaSPV5iAydjbLhSXSKP9RTuLgJSlUC+eU9MPEZdyFCpoySWlZ+LmjUv188Tpr0 rISA== X-Forwarded-Encrypted: i=1; AKwUvBw1hTKI90kIHg4XSXxXPi4967WpCRvdi10rRcbXOlviHRV6dwi19H0Tokag6u+rEVHVruSQglgxFetX@nongnu.org X-Gm-Message-State: AFuF++nJATSMeUz8GA4/jrHrdmyMh1GVwH1YhEoCVNYIqOwkIHMNC8AB Kl4Boq/e5tT6a+g0LUsjc39tWTlKoRljKPLkTqwsaL1sVB+H+cxPE3Wf++ldF06j+SXogBa6Wfe WtJWJENpY9Ot8uCMt4GrEXg== X-Received: from pjbbh6.prod.google.com ([2002:a17:90b:486:b0:38e:3cf9:2e8b]) (user=stanleyjhu job=prod-delivery.src-stubby-dispatcher) by 2002:a17:90b:528e:b0:38e:ad9d:1161 with SMTP id 98e67ed59e1d1-39b25ee67f0mr6138957a91.0.1788503231809; Thu, 03 Sep 2026 23:27:11 -0700 (PDT) Date: Fri, 4 Sep 2026 14:27:05 +0800 In-Reply-To: <20260904062706.2684958-1-stanleyjhu@google.com> Mime-Version: 1.0 References: <20260904062706.2684958-1-stanleyjhu@google.com> X-Mailer: git-send-email 2.55.0.979.g7e5102b832-goog Message-ID: <20260904062706.2684958-3-stanleyjhu@google.com> Subject: [PATCH 2/3] hw/ufs: Implement Task Management Request (TMR) handling From: Stanley Jhu To: Jeuk Kim , qemu-devel@nongnu.org Cc: Jeuk Kim , Jinyoung Choi , qemu-block@nongnu.org, Stanley Jhu Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2607:f8b0:4864:20::1048; envelope-from=3v2SaagoKCk489q31uEzxAw44w1u.s426u2A-tuBu1343w3A.47w@flex--stanleyjhu.bounces.google.com; helo=mail-pj1-x1048.google.com X-Spam_score_int: -95 X-Spam_score: -9.6 X-Spam_bar: --------- X-Spam_report: (-9.6 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_MED=-0.001, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, USER_IN_DEF_DKIM_WL=-7.5 autolearn=unavailable autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @google.com) X-ZM-MESSAGEID: 1788503278271158500 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" According to JEDEC UFSHCI 5.2.1 (Section 7.3 "UTP Task Management"), the host controller processes Task Management Requests via the UTP Task Management Request List (UTMRL). When software writes to UTMRLDBR, the controller processes descriptors, dispatches the requested function (e.g., UFS_ABORT_TASK, UFS_QUERY_TASK), returns the completion code in the response UPIU, and triggers the UTMRCS interrupt. Currently, QEMU treats UTMRLDBR, UTMRLCLR, and UTMRLRSR as unsupported. Consequently, when Linux kernel SCSI error recovery invokes task aborts via ufshcd_abort() or ufshcd_mcq_abort(), requests time out and escalate unnecessarily to full device or host resets. Implement Task Management Request handling: - Add sreq tracking in UfsRequest and cancel pending SCSIRequests in ufs_clear_req() to eliminate Use-After-Free hazards during abort. - Implement ufs_find_req_by_tag() supporting both legacy UTRL and MCQ execution queues. - Implement ufs_process_tmr() to dispatch UFS_QUERY_TASK and UFS_ABORT_TASK, releasing resources and returning completion response UPIU. - For UFS_QUERY_TASK, return UFS_UPIU_TASK_MANAGEMENT_FUNC_SUCCEEDED if the task is pending in the controller/device, and return UFS_UPIU_TASK_MANAGEMENT_FUNC_COMPL if the task does not exist. - Set both rsp_header.response and output_param1 to satisfy both JEDEC and Linux kernel driver response validation. - Mask and preserve descriptor header dword_2 when updating OCS. - Connect A_UTMRLDBR, A_UTMRLCLR, and A_UTMRLRSR in ufs_write_reg. - Latch pending doorbell bits in u->reg.utmrldbr upon UTMRLDBR write. - In ufs_hce_reset(), reset task management operational registers (utmrlrsr, utmrlclr, utmrlba/utmrlbau). - Add trace_ufs_process_tmr trace event. Signed-off-by: Stanley Jhu --- hw/ufs/lu.c | 7 +++ hw/ufs/trace-events | 1 + hw/ufs/ufs.c | 122 +++++++++++++++++++++++++++++++++++++++++++- hw/ufs/ufs.h | 1 + 4 files changed, 130 insertions(+), 1 deletion(-) diff --git a/hw/ufs/lu.c b/hw/ufs/lu.c index bdb1650851..fb7eaa99fb 100644 --- a/hw/ufs/lu.c +++ b/hw/ufs/lu.c @@ -173,6 +173,12 @@ static void ufs_scsi_command_complete(SCSIRequest *scs= i_req, size_t resid) int16_t status =3D scsi_req->status; uint32_t transfered_len =3D scsi_req->cmd.xfer - resid; =20 + if (!req) { + return; + } + + req->sreq =3D NULL; + /* WB / HID accounting should only happen for successful commands */ if (status =3D=3D GOOD) { ufs_wb_process_write_req(req, transfered_len); @@ -389,6 +395,7 @@ static UfsReqResult ufs_process_scsi_cmd(UfsLu *lu, Ufs= Request *req) SCSIRequest *scsi_req =3D scsi_req_new(lu->scsi_dev, task_tag, lu->lun, req->req_upiu.sc.cdb, UFS_CDB_SIZE, req); + req->sreq =3D scsi_req; =20 uint32_t len =3D scsi_req_enqueue(scsi_req); if (len) { diff --git a/hw/ufs/trace-events b/hw/ufs/trace-events index d8173b12b6..5e5a54a3fb 100644 --- a/hw/ufs/trace-events +++ b/hw/ufs/trace-events @@ -15,6 +15,7 @@ ufs_mcq_complete_req(uint8_t qid) "sqid %"PRIu8"" ufs_mcq_create_sq(uint8_t sqid, uint8_t cqid, uint64_t addr, uint16_t size= ) "mcq create sq sqid %"PRIu8", cqid %"PRIu8", addr 0x%"PRIx64", size %"PRI= u16"" ufs_mcq_create_cq(uint8_t cqid, uint64_t addr, uint16_t size) "mcq create = cq cqid %"PRIu8", addr 0x%"PRIx64", size %"PRIu16"" ufs_write_mcq_op_reg(uint8_t qid, uint32_t offset, uint32_t data) "qid %"P= RIu8", offset 0x%"PRIx32", data 0x%"PRIx32"" +ufs_process_tmr(uint8_t func, uint32_t tag, uint8_t resp) "query_func 0x%"= PRIx8", task_tag %"PRIu32", tm_resp 0x%"PRIx8"" ufs_hce_reset(void) "HCE 1 -> 0 reset: cancelling BHs, resetting MCQ and r= equest lists" =20 # error condition diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c index 5064878028..c7064f1825 100644 --- a/hw/ufs/ufs.c +++ b/hw/ufs/ufs.c @@ -771,8 +771,10 @@ static void ufs_hce_reset(UfsHc *u) u->reg.utmrldbr =3D 0; u->reg.utrlcnr =3D 0; u->reg.utrlrsr =3D 0; + u->reg.utmrlrsr =3D 0; u->reg.utriacr =3D 0; u->reg.utrlclr =3D 0; + u->reg.utmrlclr =3D 0; if (u->params.mcq) { u->reg.mcqconfig =3D FIELD_DP32(0, MCQCONFIG, MAC, 0x1f); } else { @@ -782,6 +784,8 @@ static void ufs_hce_reset(UfsHc *u) u->reg.is =3D 0; u->reg.utrlba =3D 0; u->reg.utrlbau =3D 0; + u->reg.utmrlba =3D 0; + u->reg.utmrlbau =3D 0; =20 /* 4. Free MCQ Queues and reset MCQ dynamic registers */ if (u->params.mcq) { @@ -818,6 +822,111 @@ static void ufs_hce_reset(UfsHc *u) ufs_irq_check(u); } =20 +static UfsRequest *ufs_find_req_by_tag(UfsHc *u, uint32_t task_tag) +{ + if (task_tag < u->params.nutrs) { + UfsRequest *req =3D &u->req_list[task_tag]; + if (req->state =3D=3D UFS_REQUEST_RUNNING || + req->state =3D=3D UFS_REQUEST_READY) { + return req; + } + } + + if (u->params.mcq) { + for (int q =3D 0; q < ARRAY_SIZE(u->sq); q++) { + UfsSq *sq =3D u->sq[q]; + if (!sq) { + continue; + } + for (int i =3D 0; i < sq->size; i++) { + UfsRequest *req =3D &sq->req[i]; + if (req->state =3D=3D UFS_REQUEST_RUNNING && + req->req_upiu.header.task_tag =3D=3D task_tag) { + return req; + } + } + } + } + + return NULL; +} + +static void ufs_process_tmr(UfsHc *u, uint32_t val) +{ + hwaddr base_addr =3D (((hwaddr)u->reg.utmrlbau) << 32) + u->reg.utmrlb= a; + uint32_t completed_mask =3D 0; + + u->reg.utmrldbr |=3D val; + + for (int i =3D 0; i < u->params.nutmrs; i++) { + if (val & (1 << i)) { + uint64_t desc_addr =3D base_addr + i * sizeof(UtpTaskReqDesc); + UtpTaskReqDesc desc; + uint8_t tm_func, tm_resp; + uint32_t task_tag; + + if (ufs_addr_read(u, desc_addr, &desc, sizeof(desc))) { + continue; + } + + tm_func =3D desc.upiu_req.req_header.query_func; + task_tag =3D be32_to_cpu(desc.upiu_req.input_param2); + + if (tm_func =3D=3D UFS_QUERY_TASK) { + UfsRequest *req =3D ufs_find_req_by_tag(u, task_tag); + + if (req) { + tm_resp =3D UFS_UPIU_TASK_MANAGEMENT_FUNC_SUCCEEDED; + } else { + tm_resp =3D UFS_UPIU_TASK_MANAGEMENT_FUNC_COMPL; + } + } else if (tm_func =3D=3D UFS_ABORT_TASK) { + UfsRequest *req =3D ufs_find_req_by_tag(u, task_tag); + + if (req) { + ufs_clear_req(req); + req->state =3D UFS_REQUEST_IDLE; + if (ufs_mcq_req(req)) { + QTAILQ_INSERT_TAIL(&req->sq->req_list, req, entry); + qemu_bh_schedule(req->sq->bh); + } else { + u->reg.utrldbr &=3D ~(1 << req->slot); + } + } + tm_resp =3D UFS_UPIU_TASK_MANAGEMENT_FUNC_COMPL; + } else { + tm_resp =3D UFS_UPIU_TASK_MANAGEMENT_FUNC_NOT_SUPPORTED; + } + + memset(&desc.upiu_rsp, 0, sizeof(desc.upiu_rsp)); + desc.header.dword_2 =3D cpu_to_le32( + (le32_to_cpu(desc.header.dword_2) & ~UFS_MASK_OCS) | + UFS_OCS_SUCCESS); + desc.upiu_rsp.rsp_header.trans_type =3D + UFS_UPIU_TRANSACTION_TASK_RSP; + desc.upiu_rsp.rsp_header.flags =3D 0; + desc.upiu_rsp.rsp_header.lun =3D desc.upiu_req.req_header.lun; + desc.upiu_rsp.rsp_header.task_tag =3D + desc.upiu_req.req_header.task_tag; + desc.upiu_rsp.rsp_header.response =3D tm_resp; + desc.upiu_rsp.output_param1 =3D cpu_to_be32(tm_resp); + + if (ufs_addr_write(u, desc_addr, &desc, sizeof(desc))) { + continue; + } + + trace_ufs_process_tmr(tm_func, task_tag, tm_resp); + u->reg.utmrldbr &=3D ~(1 << i); + completed_mask |=3D (1 << i); + } + } + + if (completed_mask) { + u->reg.is =3D FIELD_DP32(u->reg.is, IS, UTMRCS, 1); + ufs_irq_check(u); + } +} + static void ufs_write_reg(UfsHc *u, hwaddr offset, uint32_t data, unsigned= size) { switch (offset) { @@ -880,10 +989,16 @@ static void ufs_write_reg(UfsHc *u, hwaddr offset, ui= nt32_t data, unsigned size) case A_MCQCONFIG: u->reg.mcqconfig =3D data; break; - case A_UTRLCLR: case A_UTMRLDBR: + ufs_process_tmr(u, data); + break; case A_UTMRLCLR: + u->reg.utmrldbr &=3D ~data; + break; case A_UTMRLRSR: + u->reg.utmrlrsr =3D data; + break; + case A_UTRLCLR: trace_ufs_err_unsupport_register_offset(offset); break; default: @@ -2286,6 +2401,11 @@ void ufs_complete_req(UfsRequest *req, UfsReqResult = req_result) =20 static void ufs_clear_req(UfsRequest *req) { + if (req->sreq !=3D NULL) { + scsi_req_cancel(req->sreq); + req->sreq =3D NULL; + } + if (req->sg !=3D NULL) { qemu_sglist_destroy(req->sg); g_free(req->sg); diff --git a/hw/ufs/ufs.h b/hw/ufs/ufs.h index 6f2693b7ca..265a43faaa 100644 --- a/hw/ufs/ufs.h +++ b/hw/ufs/ufs.h @@ -60,6 +60,7 @@ typedef struct UfsRequest { UtpUpiuRsp rsp_upiu; =20 /* for scsi command */ + SCSIRequest *sreq; QEMUSGList *sg; uint32_t data_len; =20 --=20 2.55.0.979.g7e5102b832-goog From nobody Sat Sep 26 20:51:02 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=reject dis=none) header.from=google.com ARC-Seal: i=1; a=rsa-sha256; t=1788503267; cv=none; d=zohomail.com; s=zohoarc; b=EZS8f8uLs18RfIgcmJt4ss4ShVAAQgJD6Rs+Mt9EwGKooSvpcI1MoY1M98x9Hn9mzTxAp6f4KfkTuK6PCjdTUERC/F9vTPQC1DXPl7w8rs9yROMmXFOuBmB9YY9z8Pb7VA0Bga7kY4pHTRFNb6NkWOztiF/YCC2wdvRMpBqrEt0= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788503267; h=Content-Type:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=oSxdKRCoxJ45xFJYh1BrvaQrPuHDiqV9wvIaYazWdJs=; b=X8FzDuUcgbhWdA9it2cQ5BGFB9Y27SO/HZjPza5i4LbbwYdtK6WytS5JWq8RHyPFYtKCQ5U2hSc1wzgWEb9DVlOcSv4WGh4h+7iQ/n5lyIe3Bf11nlkHIM6zKzJKj1B11h2HZHLA5ZrlEPzlKa3gRKTBQYJ4lBMjythDfmAwSH0= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=reject dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788503267529359.803711999597; Thu, 3 Sep 2026 23:27:47 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x2NOS-0008JK-Kb; Fri, 04 Sep 2026 02:27:20 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from <3wWSaagoKClAABs53wG1zCy66y3w.u648w4C-vwDw3565y5C.69y@flex--stanleyjhu.bounces.google.com>) id 1x2NOQ-0008Ix-Qf for qemu-devel@nongnu.org; Fri, 04 Sep 2026 02:27:18 -0400 Received: from mail-pj1-x1045.google.com ([2607:f8b0:4864:20::1045]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from <3wWSaagoKClAABs53wG1zCy66y3w.u648w4C-vwDw3565y5C.69y@flex--stanleyjhu.bounces.google.com>) id 1x2NOO-0005GL-7b for qemu-devel@nongnu.org; Fri, 04 Sep 2026 02:27:18 -0400 Received: by mail-pj1-x1045.google.com with SMTP id 98e67ed59e1d1-38ea32e57e2so1369917a91.1 for ; Thu, 03 Sep 2026 23:27:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1788503234; x=1789108034; darn=nongnu.org; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:from:to:cc:subject:date:message-id:reply-to :content-type; bh=oSxdKRCoxJ45xFJYh1BrvaQrPuHDiqV9wvIaYazWdJs=; b=twqzY8BZqgS1hL1z6DInEte7SDjLYc9lsiOi1glximJGYW6Pnpn1U57WRXGqcUgXI5 Izc05ZiNEuC4BMjPaaskAlLOWFcNbe6Twy0VVmW8rARKJp+DzAAtYBU1Noe0HFKgFLF8 CUwKUu3L169dmL6zUGC4pMIiwXTvD3MMFpH+x6acMutfEVVm3ETeqP4ZAqle5LIOffD9 JMIYbJoRA4/2w8kXVYUTN6KOFh329Kb86PS68aEw8HKHjlQdldldOrHHER+5JzxEIOlF p67maJx8uOiKPanoOpD+g89gdY8xRZUQUjGaXpl0ltJjO2xF820gNJTw/IMeTp82tWe+ OwAg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788503234; x=1789108034; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=oSxdKRCoxJ45xFJYh1BrvaQrPuHDiqV9wvIaYazWdJs=; b=fX9QhPASA0vZqZBVDrGykuqhLVHIUq8WR9n5fCpMfKJW3tevJ6EAPsc5IIIzXEPf3I DBRleIw9LjojHUh68VavSFVWETmWdVzuE3Vsq3i7wLSMVFjvFGG8gVdycGdVyWuOVIK0 n6gxr4AnfZciowdWuzl2D7a6R5BkSgE1MHAwic270jXkNwis/rZbWtqeEH7nGVsMpXAm d0ZuXnZevVnm01eBM1i1zZA0d5vjGjFY0QlLbbj7m2k1KRC6tFPGRdR+eibsFs8wVtI7 973/qgul/Hh1r+G+qOxEU3Z7lJOol7ZwaiR2t2DVLlt1kDeqcKCwg6Y+AxjhCZkrz31V Q6nQ== X-Forwarded-Encrypted: i=1; AKwUvBxWTY0ka1zA02EOWUPMWJJFVoAXD5hvQ+HtA/WGxNmbwK8X9iL/7a7nQbyumK281mQe32EoNrc0cYvK@nongnu.org X-Gm-Message-State: AFuF++nIunzXKuThO88nVvYjxnsxwSBOTGPw39XF9BOd+1HFY+fDnqNR bAgu8Idcl3Ng0OKH7pagyAafgpcZ//MW3ZBtEkRbRF/DISwlEXYsAcI+kbq0KMRrVKaFEy9apHG uSBOV9HuL7WLwCPB+oCpf7Q== X-Received: from pjbmv9.prod.google.com ([2002:a17:90b:1989:b0:398:c7c7:ede1]) (user=stanleyjhu job=prod-delivery.src-stubby-dispatcher) by 2002:a17:90b:3809:b0:381:a766:efc9 with SMTP id 98e67ed59e1d1-39b261014e2mr5300992a91.7.1788503233771; Thu, 03 Sep 2026 23:27:13 -0700 (PDT) Date: Fri, 4 Sep 2026 14:27:06 +0800 In-Reply-To: <20260904062706.2684958-1-stanleyjhu@google.com> Mime-Version: 1.0 References: <20260904062706.2684958-1-stanleyjhu@google.com> X-Mailer: git-send-email 2.55.0.979.g7e5102b832-goog Message-ID: <20260904062706.2684958-4-stanleyjhu@google.com> Subject: [PATCH 3/3] hw/ufs: Add experimental fault injection properties for task abort testing From: Stanley Jhu To: Jeuk Kim , qemu-devel@nongnu.org Cc: Jeuk Kim , Jinyoung Choi , qemu-block@nongnu.org, Stanley Jhu Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2607:f8b0:4864:20::1045; envelope-from=3wWSaagoKClAABs53wG1zCy66y3w.u648w4C-vwDw3565y5C.69y@flex--stanleyjhu.bounces.google.com; helo=mail-pj1-x1045.google.com X-Spam_score_int: -95 X-Spam_score: -9.6 X-Spam_bar: --------- X-Spam_report: (-9.6 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_MED=-0.001, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, USER_IN_DEF_DKIM_WL=-7.5 autolearn=unavailable autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @google.com) X-ZM-MESSAGEID: 1788503268397158500 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add experimental properties x-hold-tag and x-hold-mode to ufs-pci and ufs-sysbus devices to facilitate end-to-end testing of host driver error handling and task abort recovery routines. When x-hold-tag is set to a specific tag (0..255), the controller holds (delays completing) the matching transfer request until a Task Management Request (TMR) targeting the held request is issued by the host driver. If x-hold-tag=3D0xfffffffe (UFS_HOLD_TAG_ANY), the controller automatically intercepts the first READ_10/WRITE_10 request with LBA >=3D 512, avoiding guest boot-time metadata requests. The default value is 0xffffffff (UFS_HOLD_TAG_NONE), which disables fault injection while preserving full testability of Tag 0. The x-hold-mode property controls how the held request and subsequent TMR are resolved: - "abort-success": TMR ABORT_TASK aborts the held request, clears its state, and returns SUCCESS (COMPL). - "abort-failed": TMR ABORT_TASK returns TASK_MANAGEMENT_FUNC_FAILED. - "in-transition": TMR QUERY_TASK / ABORT_TASK completes the held request successfully and returns COMPL (simulating a command completing in transi= tion). - "timeout": TMR ABORT_TASK does not complete, triggering TMR timeout. Both properties allow runtime modification on realized devices via QOM (e.g., QMP qom-set), enabling continuous, multi-case fault injection testing within a single running VM without requiring reboot cycles. Together with the companion Linux kernel patch: "[PATCH] scsi: ufs: core: Add fault injection for task abort failures" Link: https://lore.kernel.org/r/20260903235308.1240963-1-stanleyjhu@googl= e.com/ these QEMU changes provide a deterministic testbed to verify UFS exception cases and ensure controller/driver health. This setup is particularly useful for establishing automated CI/CD pipelines to prevent regressions across future Linux kernel UFS driver and QEMU emulation changes. Signed-off-by: Stanley Jhu --- hw/ufs/trace-events | 3 + hw/ufs/ufs-pci.c | 4 + hw/ufs/ufs-sysbus.c | 4 + hw/ufs/ufs.c | 289 +++++++++++++++++++++++++++++++++++++++++--- hw/ufs/ufs.h | 54 +++++++++ 5 files changed, 339 insertions(+), 15 deletions(-) diff --git a/hw/ufs/trace-events b/hw/ufs/trace-events index 5e5a54a3fb..a2c8f80811 100644 --- a/hw/ufs/trace-events +++ b/hw/ufs/trace-events @@ -16,6 +16,9 @@ ufs_mcq_create_sq(uint8_t sqid, uint8_t cqid, uint64_t ad= dr, uint16_t size) "mcq ufs_mcq_create_cq(uint8_t cqid, uint64_t addr, uint16_t size) "mcq create = cq cqid %"PRIu8", addr 0x%"PRIx64", size %"PRIu16"" ufs_write_mcq_op_reg(uint8_t qid, uint32_t offset, uint32_t data) "qid %"P= RIu8", offset 0x%"PRIx32", data 0x%"PRIx32"" ufs_process_tmr(uint8_t func, uint32_t tag, uint8_t resp) "query_func 0x%"= PRIx8", task_tag %"PRIu32", tm_resp 0x%"PRIx8"" +ufs_inject_hold_req(uint8_t tag, uint32_t lba) "held command tag %"PRIu8",= lba %"PRIu32"" +ufs_set_hold_tag(uint32_t tag) "hold-tag set to 0x%"PRIx32"" +ufs_set_hold_mode(const char *mode) "hold-mode set to %s" ufs_hce_reset(void) "HCE 1 -> 0 reset: cancelling BHs, resetting MCQ and r= equest lists" =20 # error condition diff --git a/hw/ufs/ufs-pci.c b/hw/ufs/ufs-pci.c index 8abd7d98e3..fe6c497dc2 100644 --- a/hw/ufs/ufs-pci.c +++ b/hw/ufs/ufs-pci.c @@ -72,6 +72,10 @@ static const Property ufs_pci_props[] =3D { 0x400), DEFINE_PROP_UINT32("wb-min-size", UfsPciState, ufs.params.wb_min_size, 0x100), + DEFINE_PROP_UNSIGNED("x-hold-tag", UfsPciState, ufs.params.x_hold_tag, + UFS_HOLD_TAG_NONE, ufs_prop_hold_tag, uint32_t), + DEFINE_PROP("x-hold-mode", UfsPciState, ufs.params.x_hold_mode, + ufs_prop_hold_mode, char *), }; =20 static const VMStateDescription ufs_pci_vmstate =3D { diff --git a/hw/ufs/ufs-sysbus.c b/hw/ufs/ufs-sysbus.c index 84de2e95ac..f0e4dfcd71 100644 --- a/hw/ufs/ufs-sysbus.c +++ b/hw/ufs/ufs-sysbus.c @@ -45,6 +45,10 @@ static const Property ufs_sysbus_props[] =3D { 0x400), DEFINE_PROP_UINT32("wb-min-size", SysbusUfsState, ufs.params.wb_min_si= ze, 0x100), + DEFINE_PROP_UNSIGNED("x-hold-tag", SysbusUfsState, ufs.params.x_hold_t= ag, + UFS_HOLD_TAG_NONE, ufs_prop_hold_tag, uint32_t), + DEFINE_PROP("x-hold-mode", SysbusUfsState, ufs.params.x_hold_mode, + ufs_prop_hold_mode, char *), }; =20 static const VMStateDescription ufs_sysbus_vmstate =3D { diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c index c7064f1825..2c3d608c1e 100644 --- a/hw/ufs/ufs.c +++ b/hw/ufs/ufs.c @@ -15,6 +15,7 @@ =20 #include "qemu/osdep.h" #include "qapi/error.h" +#include "qapi/visitor.h" #include "scsi/constants.h" #include "hw/core/irq.h" #include "trace.h" @@ -816,6 +817,14 @@ static void ufs_hce_reset(UfsHc *u) memset(u->mcq_op_reg, 0, sizeof(u->mcq_op_reg)); } =20 + if (u->held_req) { + u->held_req =3D NULL; + u->active_hold_tag =3D UFS_HOLD_TAG_NONE; + u->params.x_hold_tag =3D UFS_HOLD_TAG_NONE; + } else { + u->active_hold_tag =3D u->params.x_hold_tag; + } + u->resetting =3D false; =20 /* 5. De-assert IRQ */ @@ -873,27 +882,95 @@ static void ufs_process_tmr(UfsHc *u, uint32_t val) task_tag =3D be32_to_cpu(desc.upiu_req.input_param2); =20 if (tm_func =3D=3D UFS_QUERY_TASK) { - UfsRequest *req =3D ufs_find_req_by_tag(u, task_tag); - - if (req) { - tm_resp =3D UFS_UPIU_TASK_MANAGEMENT_FUNC_SUCCEEDED; + if (u->held_req && task_tag =3D=3D u->active_hold_tag) { + if (u->hold_mode =3D=3D UFS_HOLD_IN_TRANSITION) { + UfsRequest *hreq =3D u->held_req; + u->held_req =3D NULL; + u->active_hold_tag =3D UFS_HOLD_TAG_NONE; + u->params.x_hold_tag =3D UFS_HOLD_TAG_NONE; + tm_resp =3D UFS_UPIU_TASK_MANAGEMENT_FUNC_COMPL; + if (hreq) { + uint16_t data_seg_len =3D + sizeof(hreq->rsp_upiu.sr.sense_data_len); + hreq->rsp_upiu.sr.sense_data_len =3D 0; + hreq->rsp_upiu.sr.residual_transfer_count =3D = 0; + ufs_build_upiu_header(hreq, + UFS_UPIU_TRANSACTION_RES= PONSE, + 0, + UFS_COMMAND_RESULT_SUCCE= SS, + 0, + data_seg_len); + ufs_complete_req(hreq, UFS_REQUEST_SUCCESS); + } + } else { + tm_resp =3D UFS_UPIU_TASK_MANAGEMENT_FUNC_SUCCEEDE= D; + } } else { - tm_resp =3D UFS_UPIU_TASK_MANAGEMENT_FUNC_COMPL; + UfsRequest *req =3D ufs_find_req_by_tag(u, task_tag); + + if (req) { + tm_resp =3D UFS_UPIU_TASK_MANAGEMENT_FUNC_SUCCEEDE= D; + } else { + tm_resp =3D UFS_UPIU_TASK_MANAGEMENT_FUNC_COMPL; + } } } else if (tm_func =3D=3D UFS_ABORT_TASK) { - UfsRequest *req =3D ufs_find_req_by_tag(u, task_tag); - - if (req) { - ufs_clear_req(req); - req->state =3D UFS_REQUEST_IDLE; - if (ufs_mcq_req(req)) { - QTAILQ_INSERT_TAIL(&req->sq->req_list, req, entry); - qemu_bh_schedule(req->sq->bh); + if (u->held_req && task_tag =3D=3D u->active_hold_tag) { + if (u->hold_mode =3D=3D UFS_HOLD_ABORT_FAILED) { + tm_resp =3D UFS_UPIU_TASK_MANAGEMENT_FUNC_FAILED; + } else if (u->hold_mode =3D=3D UFS_HOLD_TIMEOUT) { + continue; + } else if (u->hold_mode =3D=3D UFS_HOLD_IN_TRANSITION)= { + UfsRequest *hreq =3D u->held_req; + u->held_req =3D NULL; + u->active_hold_tag =3D UFS_HOLD_TAG_NONE; + u->params.x_hold_tag =3D UFS_HOLD_TAG_NONE; + tm_resp =3D UFS_UPIU_TASK_MANAGEMENT_FUNC_COMPL; + if (hreq) { + uint16_t data_seg_len =3D + sizeof(hreq->rsp_upiu.sr.sense_data_len); + hreq->rsp_upiu.sr.sense_data_len =3D 0; + hreq->rsp_upiu.sr.residual_transfer_count =3D = 0; + ufs_build_upiu_header(hreq, + UFS_UPIU_TRANSACTION_RES= PONSE, + 0, + UFS_COMMAND_RESULT_SUCCE= SS, + 0, + data_seg_len); + ufs_complete_req(hreq, UFS_REQUEST_SUCCESS); + } } else { - u->reg.utrldbr &=3D ~(1 << req->slot); + /* UFS_HOLD_ABORT_SUCCESS or default */ + UfsRequest *hreq =3D u->held_req; + u->held_req =3D NULL; + ufs_clear_req(hreq); + hreq->state =3D UFS_REQUEST_IDLE; + if (ufs_mcq_req(hreq)) { + QTAILQ_INSERT_TAIL(&hreq->sq->req_list, + hreq, entry); + qemu_bh_schedule(hreq->sq->bh); + } else { + u->reg.utrldbr &=3D ~(1 << hreq->slot); + } + u->active_hold_tag =3D UFS_HOLD_TAG_NONE; + u->params.x_hold_tag =3D UFS_HOLD_TAG_NONE; + tm_resp =3D UFS_UPIU_TASK_MANAGEMENT_FUNC_COMPL; + } + } else { + UfsRequest *req =3D ufs_find_req_by_tag(u, task_tag); + + if (req) { + ufs_clear_req(req); + req->state =3D UFS_REQUEST_IDLE; + if (ufs_mcq_req(req)) { + QTAILQ_INSERT_TAIL(&req->sq->req_list, req, en= try); + qemu_bh_schedule(req->sq->bh); + } else { + u->reg.utrldbr &=3D ~(1 << req->slot); + } } + tm_resp =3D UFS_UPIU_TASK_MANAGEMENT_FUNC_COMPL; } - tm_resp =3D UFS_UPIU_TASK_MANAGEMENT_FUNC_COMPL; } else { tm_resp =3D UFS_UPIU_TASK_MANAGEMENT_FUNC_NOT_SUPPORTED; } @@ -2323,6 +2400,42 @@ static void ufs_exec_req(UfsRequest *req) return; } =20 + if (unlikely(req->hc->active_hold_tag !=3D UFS_HOLD_TAG_NONE)) { + if (req->hc->active_hold_tag =3D=3D UFS_HOLD_TAG_ANY) { + if (req->req_upiu.header.trans_type =3D=3D + UFS_UPIU_TRANSACTION_COMMAND) { + uint8_t op =3D req->req_upiu.sc.cdb[0]; + + if (op =3D=3D READ_10 || op =3D=3D WRITE_10) { + /* + * In 10-byte SCSI read/write commands (SBC-4), bytes = 2..5 + * encode the 32-bit Logical Block Address (LBA) in + * big-endian. + */ + uint32_t lba =3D ldl_be_p(&req->req_upiu.sc.cdb[2]); + + /* + * Only intercept target I/O targeting LBA >=3D + * UFS_HOLD_MIN_LBA. Skips guest boot-time partition t= able + * and superblock scanning. + */ + if (lba >=3D UFS_HOLD_MIN_LBA) { + req->hc->active_hold_tag =3D + req->req_upiu.header.task_tag; + req->hc->held_req =3D req; + trace_ufs_inject_hold_req(req->req_upiu.header.tas= k_tag, + lba); + return; + } + } + } + } else if (req->req_upiu.header.task_tag =3D=3D req->hc->active_ho= ld_tag) { + req->hc->held_req =3D req; + trace_ufs_inject_hold_req(req->req_upiu.header.task_tag, 0); + return; + } + } + switch (req->req_upiu.header.trans_type) { case UFS_UPIU_TRANSACTION_NOP_OUT: req_result =3D ufs_exec_nop_cmd(req); @@ -2942,8 +3055,140 @@ static void ufs_init_hc(UfsHc *u) =20 timer_init_ms(&u->idle_timer, QEMU_CLOCK_VIRTUAL_RT, ufs_idle_timer_cb= , u); timer_mod(&u->idle_timer, now + UFS_IDLE_TIMER_TICK); + + u->active_hold_tag =3D u->params.x_hold_tag; + u->held_req =3D NULL; +} + +static void ufs_prop_get_hold_tag(Object *obj, Visitor *v, const char *nam= e, + void *opaque, Error **errp) +{ + Property *prop =3D opaque; + uint32_t *ptr =3D object_field_prop_ptr(obj, prop); + UfsParams *params =3D container_of(ptr, UfsParams, x_hold_tag); + UfsHc *u =3D container_of(params, UfsHc, params); + + visit_type_uint32(v, name, &u->active_hold_tag, errp); +} + +static void ufs_prop_set_hold_tag(Object *obj, Visitor *v, const char *nam= e, + void *opaque, Error **errp) +{ + Property *prop =3D opaque; + uint32_t *ptr =3D object_field_prop_ptr(obj, prop); + uint32_t val; + + if (!visit_type_uint32(v, name, &val, errp)) { + return; + } + + *ptr =3D val; + UfsParams *params =3D container_of(ptr, UfsParams, x_hold_tag); + UfsHc *u =3D container_of(params, UfsHc, params); + u->active_hold_tag =3D val; + trace_ufs_set_hold_tag(val); +} + +static void ufs_prop_set_default_hold_tag(ObjectProperty *op, + const Property *prop) +{ + object_property_set_default_uint(op, prop->defval.u); } =20 +const PropertyInfo ufs_prop_hold_tag =3D { + .type =3D "uint32", + .description =3D "Task tag to hold for fault injection", + .get =3D ufs_prop_get_hold_tag, + .set =3D ufs_prop_set_hold_tag, + .set_default_value =3D ufs_prop_set_default_hold_tag, + .realized_set_allowed =3D true, +}; + +static void ufs_prop_get_hold_mode(Object *obj, Visitor *v, const char *na= me, + void *opaque, Error **errp) +{ + Property *prop =3D opaque; + char **ptr =3D object_field_prop_ptr(obj, prop); + UfsParams *params =3D container_of(ptr, UfsParams, x_hold_mode); + UfsHc *u =3D container_of(params, UfsHc, params); + const char *str; + char *val; + + switch (u->hold_mode) { + case UFS_HOLD_ABORT_FAILED: + str =3D "abort-failed"; + break; + case UFS_HOLD_IN_TRANSITION: + str =3D "in-transition"; + break; + case UFS_HOLD_TIMEOUT: + str =3D "timeout"; + break; + case UFS_HOLD_ABORT_SUCCESS: + default: + str =3D "abort-success"; + break; + } + + val =3D g_strdup(str); + visit_type_str(v, name, &val, errp); + g_free(val); +} + +static void ufs_prop_set_hold_mode(Object *obj, Visitor *v, const char *na= me, + void *opaque, Error **errp) +{ + Property *prop =3D opaque; + char **ptr =3D object_field_prop_ptr(obj, prop); + char *str; + UfsHoldMode mode; + + if (!visit_type_str(v, name, &str, errp)) { + return; + } + + if (!str || g_strcmp0(str, "abort-success") =3D=3D 0) { + mode =3D UFS_HOLD_ABORT_SUCCESS; + } else if (g_strcmp0(str, "abort-failed") =3D=3D 0) { + mode =3D UFS_HOLD_ABORT_FAILED; + } else if (g_strcmp0(str, "in-transition") =3D=3D 0) { + mode =3D UFS_HOLD_IN_TRANSITION; + } else if (g_strcmp0(str, "timeout") =3D=3D 0) { + mode =3D UFS_HOLD_TIMEOUT; + } else { + error_setg(errp, "invalid x-hold-mode: %s", str); + g_free(str); + return; + } + + g_free(*ptr); + *ptr =3D str; + UfsParams *params =3D container_of(ptr, UfsParams, x_hold_mode); + UfsHc *u =3D container_of(params, UfsHc, params); + u->hold_mode =3D mode; + trace_ufs_set_hold_mode(str ? str : "abort-success"); +} + +static void ufs_prop_release_hold_mode(Object *obj, const char *name, + void *opaque) +{ + Property *prop =3D opaque; + char **ptr =3D object_field_prop_ptr(obj, prop); + + g_free(*ptr); + *ptr =3D NULL; +} + +const PropertyInfo ufs_prop_hold_mode =3D { + .type =3D "str", + .description =3D "Fault injection mode: abort-success, abort-failed, " + "in-transition, timeout", + .get =3D ufs_prop_get_hold_mode, + .set =3D ufs_prop_set_hold_mode, + .release =3D ufs_prop_release_hold_mode, + .realized_set_allowed =3D true, +}; + bool ufs_realize(UfsHc *u, DeviceState *dev, AddressSpace *dma_as, Error *= *errp) { u->dev =3D dev; @@ -2953,6 +3198,20 @@ bool ufs_realize(UfsHc *u, DeviceState *dev, Address= Space *dma_as, Error **errp) return false; } =20 + if (!u->params.x_hold_mode || + g_strcmp0(u->params.x_hold_mode, "abort-success") =3D=3D 0) { + u->hold_mode =3D UFS_HOLD_ABORT_SUCCESS; + } else if (g_strcmp0(u->params.x_hold_mode, "abort-failed") =3D=3D 0) { + u->hold_mode =3D UFS_HOLD_ABORT_FAILED; + } else if (g_strcmp0(u->params.x_hold_mode, "in-transition") =3D=3D 0)= { + u->hold_mode =3D UFS_HOLD_IN_TRANSITION; + } else if (g_strcmp0(u->params.x_hold_mode, "timeout") =3D=3D 0) { + u->hold_mode =3D UFS_HOLD_TIMEOUT; + } else { + error_setg(errp, "invalid x-hold-mode: %s", u->params.x_hold_mode); + return false; + } + qbus_init(&u->bus, sizeof(UfsBus), TYPE_UFS_BUS, dev, dev->id); u->bus.hc =3D u; =20 diff --git a/hw/ufs/ufs.h b/hw/ufs/ufs.h index 265a43faaa..6edeab9db9 100644 --- a/hw/ufs/ufs.h +++ b/hw/ufs/ufs.h @@ -12,6 +12,7 @@ #define HW_UFS_UFS_H =20 #include "hw/core/qdev.h" +#include "hw/core/qdev-properties.h" #include "hw/scsi/scsi.h" #include "block/ufs.h" #include "scsi/constants.h" @@ -88,6 +89,48 @@ typedef struct UfsLu { UfsScsiOp scsi_op; } UfsLu; =20 +/* + * Fault injection modes for x-hold-mode property. + * Controls how the held transfer request and subsequent Task Management + * Request (TMR) are resolved by the controller: + * + * UFS_HOLD_ABORT_SUCCESS: TMR ABORT_TASK aborts the held request, clears = its + * state, and returns SUCCESS (COMPL). + * UFS_HOLD_ABORT_FAILED: TMR ABORT_TASK returns TASK_MANAGEMENT_FUNC_FAI= LED, + * forcing host driver to escalate to Host Reset. + * UFS_HOLD_IN_TRANSITION: TMR QUERY_TASK / ABORT_TASK completes the held + * request successfully and returns COMPL (simulat= ing + * a command completing in transition / grace peri= od). + * UFS_HOLD_TIMEOUT: TMR ABORT_TASK does not complete or clear doorb= ell, + * triggering hardware-level TMR timeout. + */ +typedef enum UfsHoldMode { + UFS_HOLD_ABORT_SUCCESS =3D 0, + UFS_HOLD_ABORT_FAILED, + UFS_HOLD_IN_TRANSITION, + UFS_HOLD_TIMEOUT, +} UfsHoldMode; + +/* + * Sentinel values for x-hold-tag property. + * Task Tags in UFS are 8-bit (0..255). Values >=3D 256 are reserved as + * sentinel flags to preserve full testability of Tag 0. + * + * UFS_HOLD_TAG_NONE: Disable fault injection (default). + * UFS_HOLD_TAG_ANY: Automatically intercept the first user-space READ_10 + * or WRITE_10 command targeting LBA >=3D UFS_HOLD_MIN_= LBA. + */ +#define UFS_HOLD_TAG_NONE 0xffffffff +#define UFS_HOLD_TAG_ANY 0xfffffffe + +/* + * Minimum Logical Block Address (LBA) for automatic fault injection. + * Skip early disk blocks (LBA 0..511, e.g. MBR, GPT partition tables, EFI, + * and filesystem superblocks) to ensure guest OS boot-time disk discovery + * and partition scanning complete cleanly without being held. + */ +#define UFS_HOLD_MIN_LBA 512 + typedef struct UfsParams { char *serial; uint8_t nutrs; /* Number of UTP Transfer Request Slots */ @@ -97,6 +140,8 @@ typedef struct UfsParams { uint8_t mcq_maxq; /* MCQ Maximum number of Queues */ uint32_t wb_max_size; /* WB Maximum allocation units */ uint32_t wb_min_size; /* WB Minimum allocation units */ + uint32_t x_hold_tag; + char *x_hold_mode; } UfsParams; =20 /* @@ -186,6 +231,11 @@ typedef struct UfsHc { uint32_t hid_fragment_count; /* Remaining fragmented 4KB units */ uint32_t hid_defrag_total; /* Requested units at defrag start */ uint32_t hid_defrag_remaining; /* Requested units left to move */ + + /* Test and fault injection properties */ + uint32_t active_hold_tag; + UfsHoldMode hold_mode; + UfsRequest *held_req; } UfsHc; =20 static inline uint32_t ufs_mcq_sq_tail(UfsHc *u, uint32_t qid) @@ -309,4 +359,8 @@ void ufs_init_wlu(UfsLu *wlu, uint8_t wlun); bool ufs_realize(UfsHc *u, DeviceState *dev, AddressSpace *dma_as, Error **errp); void ufs_unrealize(UfsHc *u); + +extern const PropertyInfo ufs_prop_hold_tag; +extern const PropertyInfo ufs_prop_hold_mode; + #endif /* HW_UFS_UFS_H */ --=20 2.55.0.979.g7e5102b832-goog