From nobody Tue Apr 7 13:09:46 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1773368380; cv=none; d=zohomail.com; s=zohoarc; b=mBFRG91xtIkIimPBanbHSrkzbKJ92xAwRix1lYkqwzA5mMyHkrJQR2f8mrw6TZ4+sy4w2XXCcz95w6MMzhnjLdXpBytFnl6qlLB4gGY7UlV6Ab1CwiERWWB4Y2GDtMnu3Y4u9PL0RgUmxSt/ejYmWgQ+N1s+l67t0KITUceRNm0= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1773368380; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=EKe+6BgKY+nLiLRqjwf5WJqTdYg9unFiTr26RFa2JMs=; b=mtmTqeJQP810lvCpygTqA0pJ7X5VzBj2QXmtfHIHzKCEpGovIpuS91D52NsO2H4UMkSRn9aCf55No5PdCEGkf+kaOkzjWel4Om9WGjDp88qt43wJqwEKRVGbB1+X7V4svJmM2aj3mEP2gLcg15wqQ3M7clPHwqX0WKRnJ+azTVY= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1773368380526562.8055291242395; Thu, 12 Mar 2026 19:19:40 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1w0s7G-0000R6-34; Thu, 12 Mar 2026 22:19:06 -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 1w0s7E-0000QE-Be for qemu-devel@nongnu.org; Thu, 12 Mar 2026 22:19:04 -0400 Received: from mail-dy1-x1331.google.com ([2607:f8b0:4864:20::1331]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1w0s7A-00080F-HT for qemu-devel@nongnu.org; Thu, 12 Mar 2026 22:19:04 -0400 Received: by mail-dy1-x1331.google.com with SMTP id 5a478bee46e88-2bea8a1c040so280514eec.0 for ; Thu, 12 Mar 2026 19:19:00 -0700 (PDT) Received: from 192.168.0.29 ([2804:14d:4c71:86dd:588a:39d7:d008:37c2]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2beab3eec52sm796218eec.14.2026.03.12.19.18.55 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Thu, 12 Mar 2026 19:18:57 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1773368339; x=1773973139; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=EKe+6BgKY+nLiLRqjwf5WJqTdYg9unFiTr26RFa2JMs=; b=RYCWBh1qFPrG+VKM6+dJAzCg8NZnVrr5pejRoSdyd3aqjXVgyCDZzz4501sMxSoNUs L3TCAV2B4MS+oL42bNKAw/2fhQ2mMnGdqued5V9SX26EcmndAUXACVhzWR3LkgGUGBSW POLQ3da22QWIqnb3wu7BSsSNeBOC1WMIvQHVurhlJ8/8eRZhU0EjF7w8wBUSRxWCuFCu Nv1oKBKuauJCwFmmOa/JVD+imZ5soQLDr3piLDtEdm2AV3+SF6QU5muiCsEyWv/7R5L8 7ANIDHln/bdGj0XTvCO4BvaueCRv24lwuduVDNk+YPH8w4og5zuY7smr3YrlnsNh6LYO wPcQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1773368339; x=1773973139; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=EKe+6BgKY+nLiLRqjwf5WJqTdYg9unFiTr26RFa2JMs=; b=MoJT8RT+WQnfhAnumWBLNQndU9Sl7Y/l+g9H0RXjJKX/VCpSYsmBZkBS3Z78e8gcyP A8UMzmY2pcFlslHF211kWzVW4Ldog2SZab8yFhhpyIZ7YU4D2OpcsjXaxNvnwrrOZHgk CO2pEby8gv6z/bDTgIP6/MR7xaXvMnrT8SC8eavSkyGG1GaOQoUD6Wm5M5v77w3gCzaZ ZLxw06Ux/swSjhXdU6RxvV59TFvIZtcNEd437b6eQzyYXT5Po4+/lC1j8BuYkOZfSnPA nHmnLmpN9VdX+OjxXnlIFnyr4/MZj8YMOXWNDlCkHHGE5qMVxAICp67eA+oSlVo6rnut vqrw== X-Gm-Message-State: AOJu0YzXCrznfkNK9woY/1CwlcFQ56DIk10PllulyqvIBsrddjit3i2Q vK3yIJrlYFqO79lfIYw+RHWBLcUw3tiXFzhgh3hRCjcBmEPPKdgQI9WTQI0PaLfJ X-Gm-Gg: ATEYQzzRlDYVUrDYxoS6pcSpaGUOmBHMAMZAZx9Xu+wVT0/E1sEXVoY7GvbsllePAlT uRsb7O3dSjOigZuEhhXChOlxsBWyKJm8qS2zZkwBBUBlKOXgNQIklxfSe0ZmW0amqzuLMy4mdf/ fmSikQN6Y7NI4gWuv9eUTSvOrUxPS5eccRGYgZs168Olrpm5NLuLFfBczldR+B58f4N1YpHoPYO XPPKC8+O5Mxbivq9z8Wp792vmnNLSgnpAJ2zveZ/3ILKUBi9FnM0mL3nq9Algro5PU9tvsc/lJq piDksHIRxdZiIe8b0bvedMhqbzv6nRmuro6w9tbYhjX845Z0J1/3iLlIMLoY8lmWHytNo3qW8mS E4FL+y7j1sYw8zACNbsFJehMfE3+BCleYq+60C/HqMYgFsjOftkM1YGjeIG0y1q07p0n+g1UrsM Kzd3ECclljRjx7PEx/S43uykCgNVaQDxV+dp6CNsr/EhnR2cVhYl0= X-Received: by 2002:a05:7300:8608:b0:2b7:2664:ae9f with SMTP id 5a478bee46e88-2bea53915d5mr806553eec.1.1773368338140; Thu, 12 Mar 2026 19:18:58 -0700 (PDT) From: Lucas Amaral To: qemu-devel@nongnu.org Cc: qemu-arm@nongnu.org, agraf@csgraf.de, Lucas Amaral Subject: [PATCH v2 1/3] target/arm: add AArch64 ISV=0 instruction emulation library Date: Thu, 12 Mar 2026 23:18:48 -0300 Message-ID: <20260313021850.42379-2-lucaaamaral@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260313021850.42379-1-lucaaamaral@gmail.com> References: <20260309214852.92545-1-lucaaamaral@gmail.com> <20260313021850.42379-1-lucaaamaral@gmail.com> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=2607:f8b0:4864:20::1331; envelope-from=lucaaamaral@gmail.com; helo=mail-dy1-x1331.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, FSL_HELO_BARE_IP_2=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: 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 @gmail.com) X-ZM-MESSAGEID: 1773368384088158500 Add a shared emulation library in target/arm/emulate/ using a decodetree decoder (a64-ldst.decode) and a callback-based interface (struct arm_emul_ops) that any hypervisor backend can implement. The hypervisor cannot emulate ISV=3D0 data aborts without decoding the faulting instruction, since the ESR syndrome does not carry the access size or target register. Signed-off-by: Lucas Amaral --- target/arm/emulate/a64-ldst.decode | 293 ++++++++++++ target/arm/emulate/arm_emulate.c | 738 +++++++++++++++++++++++++++++ target/arm/emulate/arm_emulate.h | 55 +++ target/arm/emulate/meson.build | 16 + target/arm/meson.build | 1 + 5 files changed, 1103 insertions(+) create mode 100644 target/arm/emulate/a64-ldst.decode create mode 100644 target/arm/emulate/arm_emulate.c create mode 100644 target/arm/emulate/arm_emulate.h create mode 100644 target/arm/emulate/meson.build diff --git a/target/arm/emulate/a64-ldst.decode b/target/arm/emulate/a64-ld= st.decode new file mode 100644 index 0000000..9a7b697 --- /dev/null +++ b/target/arm/emulate/a64-ldst.decode @@ -0,0 +1,293 @@ +# AArch64 load/store instruction patterns for ISV=3D0 emulation +# +# Copyright (c) 2026 Lucas Amaral +# +# SPDX-License-Identifier: GPL-2.0-or-later + +### Argument sets + +# Load/store exclusive +&stxr rn rt rt2 rs sz lasr + +# Load/store pair (GPR and SIMD/FP) +&ldstpair rt2 rt rn imm sz sign w p + +# Load/store immediate (unscaled, pre/post-index, unprivileged, unsigned o= ffset) +# 'u' flag: 0 =3D 9-bit signed immediate (byte offset), 1 =3D 12-bit unsig= ned (needs << sz) +&ldst_imm rt rn imm sz sign w p unpriv ext u + +# Load/store register offset +&ldst rm rn rt sign ext sz opt s + +# Atomic memory operations +&atomic rs rn rt a r sz + +# Compare-and-swap +&cas rs rn rt sz a r + +# Load with PAC (LDRAA/LDRAB, FEAT_PAuth) +%ldra_imm 22:s1 12:9 +&ldra rt rn imm m w + +### Format templates + +# Exclusives +@stxr sz:2 ...... ... rs:5 lasr:1 rt2:5 rn:5 rt:5 &stxr + +# Load/store pair: imm7 is signed, scaled by element size in handler +@ldstpair .. ... . ... . imm:s7 rt2:5 rn:5 rt:5 &ldstpair + +# Load/store immediate (9-bit signed) +@ldst_imm .. ... . .. .. . imm:s9 .. rn:5 rt:5 &ldst_imm u=3D0 unp= riv=3D0 p=3D0 w=3D0 +@ldst_imm_pre .. ... . .. .. . imm:s9 .. rn:5 rt:5 &ldst_imm u=3D0 unp= riv=3D0 p=3D0 w=3D1 +@ldst_imm_post .. ... . .. .. . imm:s9 .. rn:5 rt:5 &ldst_imm u=3D0 unp= riv=3D0 p=3D1 w=3D1 +@ldst_imm_user .. ... . .. .. . imm:s9 .. rn:5 rt:5 &ldst_imm u=3D0 unp= riv=3D1 p=3D0 w=3D0 + +# Load/store unsigned offset (12-bit, handler scales by << sz) +@ldst_uimm .. ... . .. .. imm:12 rn:5 rt:5 &ldst_imm u=3D1 unp= riv=3D0 p=3D0 w=3D0 + +# Load/store register offset +@ldst .. ... . .. .. . rm:5 opt:3 s:1 .. rn:5 rt:5 &ldst + +# Atomics +@atomic sz:2 ... . .. a:1 r:1 . rs:5 . ... .. rn:5 rt:5 &atomic + +# Compare-and-swap: sz extracted by pattern (CAS) or set constant (CASP) +@cas .. ...... . a:1 . rs:5 r:1 ..... rn:5 rt:5 &cas + +# Load with PAC +@ldra .. ... . .. m:1 . . ......... w:1 . rn:5 rt:5 &ldra im= m=3D%ldra_imm + +### Load/store exclusive + +# STXR / STLXR (sz encodes 8/16/32/64-bit) +STXR .. 001000 000 ..... . ..... ..... ..... @stxr + +# LDXR / LDAXR +LDXR .. 001000 010 ..... . ..... ..... ..... @stxr + +# STXP / STLXP (bit[31]=3D1, bit[30]=3Dsf =E2=86=92 sz=3D2 for 32-bit, sz= =3D3 for 64-bit) +STXP 10 001000 001 rs:5 lasr:1 rt2:5 rn:5 rt:5 &stxr sz=3D2 +STXP 11 001000 001 rs:5 lasr:1 rt2:5 rn:5 rt:5 &stxr sz=3D3 + +# LDXP / LDAXP +LDXP 10 001000 011 rs:5 lasr:1 rt2:5 rn:5 rt:5 &stxr sz=3D2 +LDXP 11 001000 011 rs:5 lasr:1 rt2:5 rn:5 rt:5 &stxr sz=3D3 + +### Compare-and-swap + +# CAS / CASA / CASAL / CASL +CAS sz:2 001000 1 . 1 ..... . 11111 ..... ..... @cas + +# CASP / CASPA / CASPAL / CASPL (pair: Rt,Rt+1 and Rs,Rs+1) +CASP 00 001000 0 . 1 ..... . 11111 ..... ..... @cas sz=3D2 +CASP 01 001000 0 . 1 ..... . 11111 ..... ..... @cas sz=3D3 + +### Load/store pair =E2=80=94 non-temporal (STNP/LDNP) + +# STNP/LDNP: offset only, no writeback. Non-temporal hint ignored. +STP 00 101 0 000 0 ....... ..... ..... ..... @ldstpair = sz=3D2 sign=3D0 p=3D0 w=3D0 +LDP 00 101 0 000 1 ....... ..... ..... ..... @ldstpair = sz=3D2 sign=3D0 p=3D0 w=3D0 +STP 10 101 0 000 0 ....... ..... ..... ..... @ldstpair = sz=3D3 sign=3D0 p=3D0 w=3D0 +LDP 10 101 0 000 1 ....... ..... ..... ..... @ldstpair = sz=3D3 sign=3D0 p=3D0 w=3D0 +STP_v 00 101 1 000 0 ....... ..... ..... ..... @ldstpair = sz=3D2 sign=3D0 p=3D0 w=3D0 +LDP_v 00 101 1 000 1 ....... ..... ..... ..... @ldstpair = sz=3D2 sign=3D0 p=3D0 w=3D0 +STP_v 01 101 1 000 0 ....... ..... ..... ..... @ldstpair = sz=3D3 sign=3D0 p=3D0 w=3D0 +LDP_v 01 101 1 000 1 ....... ..... ..... ..... @ldstpair = sz=3D3 sign=3D0 p=3D0 w=3D0 +STP_v 10 101 1 000 0 ....... ..... ..... ..... @ldstpair = sz=3D4 sign=3D0 p=3D0 w=3D0 +LDP_v 10 101 1 000 1 ....... ..... ..... ..... @ldstpair = sz=3D4 sign=3D0 p=3D0 w=3D0 + +### Load/store pair =E2=80=94 post-indexed + +STP 00 101 0 001 0 ....... ..... ..... ..... @ldstpair = sz=3D2 sign=3D0 p=3D1 w=3D1 +LDP 00 101 0 001 1 ....... ..... ..... ..... @ldstpair = sz=3D2 sign=3D0 p=3D1 w=3D1 +LDP 01 101 0 001 1 ....... ..... ..... ..... @ldstpair = sz=3D2 sign=3D1 p=3D1 w=3D1 +STP 10 101 0 001 0 ....... ..... ..... ..... @ldstpair = sz=3D3 sign=3D0 p=3D1 w=3D1 +LDP 10 101 0 001 1 ....... ..... ..... ..... @ldstpair = sz=3D3 sign=3D0 p=3D1 w=3D1 +STP_v 00 101 1 001 0 ....... ..... ..... ..... @ldstpair = sz=3D2 sign=3D0 p=3D1 w=3D1 +LDP_v 00 101 1 001 1 ....... ..... ..... ..... @ldstpair = sz=3D2 sign=3D0 p=3D1 w=3D1 +STP_v 01 101 1 001 0 ....... ..... ..... ..... @ldstpair = sz=3D3 sign=3D0 p=3D1 w=3D1 +LDP_v 01 101 1 001 1 ....... ..... ..... ..... @ldstpair = sz=3D3 sign=3D0 p=3D1 w=3D1 +STP_v 10 101 1 001 0 ....... ..... ..... ..... @ldstpair = sz=3D4 sign=3D0 p=3D1 w=3D1 +LDP_v 10 101 1 001 1 ....... ..... ..... ..... @ldstpair = sz=3D4 sign=3D0 p=3D1 w=3D1 + +### Load/store pair =E2=80=94 signed offset + +STP 00 101 0 010 0 ....... ..... ..... ..... @ldstpair = sz=3D2 sign=3D0 p=3D0 w=3D0 +LDP 00 101 0 010 1 ....... ..... ..... ..... @ldstpair = sz=3D2 sign=3D0 p=3D0 w=3D0 +LDP 01 101 0 010 1 ....... ..... ..... ..... @ldstpair = sz=3D2 sign=3D1 p=3D0 w=3D0 +STP 10 101 0 010 0 ....... ..... ..... ..... @ldstpair = sz=3D3 sign=3D0 p=3D0 w=3D0 +LDP 10 101 0 010 1 ....... ..... ..... ..... @ldstpair = sz=3D3 sign=3D0 p=3D0 w=3D0 +STP_v 00 101 1 010 0 ....... ..... ..... ..... @ldstpair = sz=3D2 sign=3D0 p=3D0 w=3D0 +LDP_v 00 101 1 010 1 ....... ..... ..... ..... @ldstpair = sz=3D2 sign=3D0 p=3D0 w=3D0 +STP_v 01 101 1 010 0 ....... ..... ..... ..... @ldstpair = sz=3D3 sign=3D0 p=3D0 w=3D0 +LDP_v 01 101 1 010 1 ....... ..... ..... ..... @ldstpair = sz=3D3 sign=3D0 p=3D0 w=3D0 +STP_v 10 101 1 010 0 ....... ..... ..... ..... @ldstpair = sz=3D4 sign=3D0 p=3D0 w=3D0 +LDP_v 10 101 1 010 1 ....... ..... ..... ..... @ldstpair = sz=3D4 sign=3D0 p=3D0 w=3D0 + +### Load/store pair =E2=80=94 pre-indexed + +STP 00 101 0 011 0 ....... ..... ..... ..... @ldstpair = sz=3D2 sign=3D0 p=3D0 w=3D1 +LDP 00 101 0 011 1 ....... ..... ..... ..... @ldstpair = sz=3D2 sign=3D0 p=3D0 w=3D1 +LDP 01 101 0 011 1 ....... ..... ..... ..... @ldstpair = sz=3D2 sign=3D1 p=3D0 w=3D1 +STP 10 101 0 011 0 ....... ..... ..... ..... @ldstpair = sz=3D3 sign=3D0 p=3D0 w=3D1 +LDP 10 101 0 011 1 ....... ..... ..... ..... @ldstpair = sz=3D3 sign=3D0 p=3D0 w=3D1 +STP_v 00 101 1 011 0 ....... ..... ..... ..... @ldstpair = sz=3D2 sign=3D0 p=3D0 w=3D1 +LDP_v 00 101 1 011 1 ....... ..... ..... ..... @ldstpair = sz=3D2 sign=3D0 p=3D0 w=3D1 +STP_v 01 101 1 011 0 ....... ..... ..... ..... @ldstpair = sz=3D3 sign=3D0 p=3D0 w=3D1 +LDP_v 01 101 1 011 1 ....... ..... ..... ..... @ldstpair = sz=3D3 sign=3D0 p=3D0 w=3D1 +STP_v 10 101 1 011 0 ....... ..... ..... ..... @ldstpair = sz=3D4 sign=3D0 p=3D0 w=3D1 +LDP_v 10 101 1 011 1 ....... ..... ..... ..... @ldstpair = sz=3D4 sign=3D0 p=3D0 w=3D1 + +### Load/store pair =E2=80=94 STGP (store allocation tag + pair) + +STGP 01 101 0 001 0 ....... ..... ..... ..... @ldstpair = sz=3D3 sign=3D0 p=3D1 w=3D1 +STGP 01 101 0 010 0 ....... ..... ..... ..... @ldstpair = sz=3D3 sign=3D0 p=3D0 w=3D0 +STGP 01 101 0 011 0 ....... ..... ..... ..... @ldstpair = sz=3D3 sign=3D0 p=3D0 w=3D1 + +### Load/store register =E2=80=94 unscaled immediate (LDUR/STUR) + +# GPR +STR_i sz:2 111 0 00 00 0 ......... 00 ..... ..... @ldst_imm s= ign=3D0 ext=3D0 +LDR_i 00 111 0 00 01 0 ......... 00 ..... ..... @ldst_imm s= ign=3D0 ext=3D1 sz=3D0 +LDR_i 01 111 0 00 01 0 ......... 00 ..... ..... @ldst_imm s= ign=3D0 ext=3D1 sz=3D1 +LDR_i 10 111 0 00 01 0 ......... 00 ..... ..... @ldst_imm s= ign=3D0 ext=3D1 sz=3D2 +LDR_i 11 111 0 00 01 0 ......... 00 ..... ..... @ldst_imm s= ign=3D0 ext=3D0 sz=3D3 +LDR_i 00 111 0 00 10 0 ......... 00 ..... ..... @ldst_imm s= ign=3D1 ext=3D0 sz=3D0 +LDR_i 01 111 0 00 10 0 ......... 00 ..... ..... @ldst_imm s= ign=3D1 ext=3D0 sz=3D1 +LDR_i 10 111 0 00 10 0 ......... 00 ..... ..... @ldst_imm s= ign=3D1 ext=3D0 sz=3D2 +LDR_i 00 111 0 00 11 0 ......... 00 ..... ..... @ldst_imm s= ign=3D1 ext=3D1 sz=3D0 +LDR_i 01 111 0 00 11 0 ......... 00 ..... ..... @ldst_imm s= ign=3D1 ext=3D1 sz=3D1 + +# SIMD/FP +STR_v_i sz:2 111 1 00 00 0 ......... 00 ..... ..... @ldst_imm s= ign=3D0 ext=3D0 +STR_v_i 00 111 1 00 10 0 ......... 00 ..... ..... @ldst_imm s= ign=3D0 ext=3D0 sz=3D4 +LDR_v_i sz:2 111 1 00 01 0 ......... 00 ..... ..... @ldst_imm s= ign=3D0 ext=3D0 +LDR_v_i 00 111 1 00 11 0 ......... 00 ..... ..... @ldst_imm s= ign=3D0 ext=3D0 sz=3D4 + +### Load/store register =E2=80=94 post-indexed + +# GPR +STR_i sz:2 111 0 00 00 0 ......... 01 ..... ..... @ldst_imm_p= ost sign=3D0 ext=3D0 +LDR_i 00 111 0 00 01 0 ......... 01 ..... ..... @ldst_imm_p= ost sign=3D0 ext=3D1 sz=3D0 +LDR_i 01 111 0 00 01 0 ......... 01 ..... ..... @ldst_imm_p= ost sign=3D0 ext=3D1 sz=3D1 +LDR_i 10 111 0 00 01 0 ......... 01 ..... ..... @ldst_imm_p= ost sign=3D0 ext=3D1 sz=3D2 +LDR_i 11 111 0 00 01 0 ......... 01 ..... ..... @ldst_imm_p= ost sign=3D0 ext=3D0 sz=3D3 +LDR_i 00 111 0 00 10 0 ......... 01 ..... ..... @ldst_imm_p= ost sign=3D1 ext=3D0 sz=3D0 +LDR_i 01 111 0 00 10 0 ......... 01 ..... ..... @ldst_imm_p= ost sign=3D1 ext=3D0 sz=3D1 +LDR_i 10 111 0 00 10 0 ......... 01 ..... ..... @ldst_imm_p= ost sign=3D1 ext=3D0 sz=3D2 +LDR_i 00 111 0 00 11 0 ......... 01 ..... ..... @ldst_imm_p= ost sign=3D1 ext=3D1 sz=3D0 +LDR_i 01 111 0 00 11 0 ......... 01 ..... ..... @ldst_imm_p= ost sign=3D1 ext=3D1 sz=3D1 + +# SIMD/FP +STR_v_i sz:2 111 1 00 00 0 ......... 01 ..... ..... @ldst_imm_p= ost sign=3D0 ext=3D0 +STR_v_i 00 111 1 00 10 0 ......... 01 ..... ..... @ldst_imm_p= ost sign=3D0 ext=3D0 sz=3D4 +LDR_v_i sz:2 111 1 00 01 0 ......... 01 ..... ..... @ldst_imm_p= ost sign=3D0 ext=3D0 +LDR_v_i 00 111 1 00 11 0 ......... 01 ..... ..... @ldst_imm_p= ost sign=3D0 ext=3D0 sz=3D4 + +### Load/store register =E2=80=94 unprivileged + +# GPR only (no SIMD/FP unprivileged forms) +STR_i sz:2 111 0 00 00 0 ......... 10 ..... ..... @ldst_imm_u= ser sign=3D0 ext=3D0 +LDR_i 00 111 0 00 01 0 ......... 10 ..... ..... @ldst_imm_u= ser sign=3D0 ext=3D1 sz=3D0 +LDR_i 01 111 0 00 01 0 ......... 10 ..... ..... @ldst_imm_u= ser sign=3D0 ext=3D1 sz=3D1 +LDR_i 10 111 0 00 01 0 ......... 10 ..... ..... @ldst_imm_u= ser sign=3D0 ext=3D1 sz=3D2 +LDR_i 11 111 0 00 01 0 ......... 10 ..... ..... @ldst_imm_u= ser sign=3D0 ext=3D0 sz=3D3 +LDR_i 00 111 0 00 10 0 ......... 10 ..... ..... @ldst_imm_u= ser sign=3D1 ext=3D0 sz=3D0 +LDR_i 01 111 0 00 10 0 ......... 10 ..... ..... @ldst_imm_u= ser sign=3D1 ext=3D0 sz=3D1 +LDR_i 10 111 0 00 10 0 ......... 10 ..... ..... @ldst_imm_u= ser sign=3D1 ext=3D0 sz=3D2 +LDR_i 00 111 0 00 11 0 ......... 10 ..... ..... @ldst_imm_u= ser sign=3D1 ext=3D1 sz=3D0 +LDR_i 01 111 0 00 11 0 ......... 10 ..... ..... @ldst_imm_u= ser sign=3D1 ext=3D1 sz=3D1 + +### Load/store register =E2=80=94 pre-indexed + +# GPR +STR_i sz:2 111 0 00 00 0 ......... 11 ..... ..... @ldst_imm_p= re sign=3D0 ext=3D0 +LDR_i 00 111 0 00 01 0 ......... 11 ..... ..... @ldst_imm_p= re sign=3D0 ext=3D1 sz=3D0 +LDR_i 01 111 0 00 01 0 ......... 11 ..... ..... @ldst_imm_p= re sign=3D0 ext=3D1 sz=3D1 +LDR_i 10 111 0 00 01 0 ......... 11 ..... ..... @ldst_imm_p= re sign=3D0 ext=3D1 sz=3D2 +LDR_i 11 111 0 00 01 0 ......... 11 ..... ..... @ldst_imm_p= re sign=3D0 ext=3D0 sz=3D3 +LDR_i 00 111 0 00 10 0 ......... 11 ..... ..... @ldst_imm_p= re sign=3D1 ext=3D0 sz=3D0 +LDR_i 01 111 0 00 10 0 ......... 11 ..... ..... @ldst_imm_p= re sign=3D1 ext=3D0 sz=3D1 +LDR_i 10 111 0 00 10 0 ......... 11 ..... ..... @ldst_imm_p= re sign=3D1 ext=3D0 sz=3D2 +LDR_i 00 111 0 00 11 0 ......... 11 ..... ..... @ldst_imm_p= re sign=3D1 ext=3D1 sz=3D0 +LDR_i 01 111 0 00 11 0 ......... 11 ..... ..... @ldst_imm_p= re sign=3D1 ext=3D1 sz=3D1 + +# SIMD/FP +STR_v_i sz:2 111 1 00 00 0 ......... 11 ..... ..... @ldst_imm_p= re sign=3D0 ext=3D0 +STR_v_i 00 111 1 00 10 0 ......... 11 ..... ..... @ldst_imm_p= re sign=3D0 ext=3D0 sz=3D4 +LDR_v_i sz:2 111 1 00 01 0 ......... 11 ..... ..... @ldst_imm_p= re sign=3D0 ext=3D0 +LDR_v_i 00 111 1 00 11 0 ......... 11 ..... ..... @ldst_imm_p= re sign=3D0 ext=3D0 sz=3D4 + +### PRFM =E2=80=94 unscaled immediate: prefetch is a NOP + +NOP 11 111 0 00 10 0 --------- 00 ----- ----- + +### Load/store register =E2=80=94 unsigned offset + +# GPR +STR_i sz:2 111 0 01 00 ............ ..... ..... @ldst_uimm= sign=3D0 ext=3D0 +LDR_i 00 111 0 01 01 ............ ..... ..... @ldst_uimm= sign=3D0 ext=3D1 sz=3D0 +LDR_i 01 111 0 01 01 ............ ..... ..... @ldst_uimm= sign=3D0 ext=3D1 sz=3D1 +LDR_i 10 111 0 01 01 ............ ..... ..... @ldst_uimm= sign=3D0 ext=3D1 sz=3D2 +LDR_i 11 111 0 01 01 ............ ..... ..... @ldst_uimm= sign=3D0 ext=3D0 sz=3D3 +LDR_i 00 111 0 01 10 ............ ..... ..... @ldst_uimm= sign=3D1 ext=3D0 sz=3D0 +LDR_i 01 111 0 01 10 ............ ..... ..... @ldst_uimm= sign=3D1 ext=3D0 sz=3D1 +LDR_i 10 111 0 01 10 ............ ..... ..... @ldst_uimm= sign=3D1 ext=3D0 sz=3D2 +LDR_i 00 111 0 01 11 ............ ..... ..... @ldst_uimm= sign=3D1 ext=3D1 sz=3D0 +LDR_i 01 111 0 01 11 ............ ..... ..... @ldst_uimm= sign=3D1 ext=3D1 sz=3D1 + +# PRFM =E2=80=94 unsigned offset +NOP 11 111 0 01 10 ------------ ----- ----- + +# SIMD/FP +STR_v_i sz:2 111 1 01 00 ............ ..... ..... @ldst_uimm= sign=3D0 ext=3D0 +STR_v_i 00 111 1 01 10 ............ ..... ..... @ldst_uimm= sign=3D0 ext=3D0 sz=3D4 +LDR_v_i sz:2 111 1 01 01 ............ ..... ..... @ldst_uimm= sign=3D0 ext=3D0 +LDR_v_i 00 111 1 01 11 ............ ..... ..... @ldst_uimm= sign=3D0 ext=3D0 sz=3D4 + +### Load/store register =E2=80=94 register offset + +# GPR +STR sz:2 111 0 00 00 1 ..... ... . 10 ..... ..... @ldst sign= =3D0 ext=3D0 +LDR 00 111 0 00 01 1 ..... ... . 10 ..... ..... @ldst sign= =3D0 ext=3D1 sz=3D0 +LDR 01 111 0 00 01 1 ..... ... . 10 ..... ..... @ldst sign= =3D0 ext=3D1 sz=3D1 +LDR 10 111 0 00 01 1 ..... ... . 10 ..... ..... @ldst sign= =3D0 ext=3D1 sz=3D2 +LDR 11 111 0 00 01 1 ..... ... . 10 ..... ..... @ldst sign= =3D0 ext=3D0 sz=3D3 +LDR 00 111 0 00 10 1 ..... ... . 10 ..... ..... @ldst sign= =3D1 ext=3D0 sz=3D0 +LDR 01 111 0 00 10 1 ..... ... . 10 ..... ..... @ldst sign= =3D1 ext=3D0 sz=3D1 +LDR 10 111 0 00 10 1 ..... ... . 10 ..... ..... @ldst sign= =3D1 ext=3D0 sz=3D2 +LDR 00 111 0 00 11 1 ..... ... . 10 ..... ..... @ldst sign= =3D1 ext=3D1 sz=3D0 +LDR 01 111 0 00 11 1 ..... ... . 10 ..... ..... @ldst sign= =3D1 ext=3D1 sz=3D1 + +# PRFM =E2=80=94 register offset +NOP 11 111 0 00 10 1 ----- -1- - 10 ----- ----- + +# SIMD/FP +STR_v sz:2 111 1 00 00 1 ..... ... . 10 ..... ..... @ldst sign= =3D0 ext=3D0 +STR_v 00 111 1 00 10 1 ..... ... . 10 ..... ..... @ldst sign= =3D0 ext=3D0 sz=3D4 +LDR_v sz:2 111 1 00 01 1 ..... ... . 10 ..... ..... @ldst sign= =3D0 ext=3D0 +LDR_v 00 111 1 00 11 1 ..... ... . 10 ..... ..... @ldst sign= =3D0 ext=3D0 sz=3D4 + +### Atomic memory operations + +LDADD .. 111 0 00 . . 1 ..... 0000 00 ..... ..... @atomic +LDCLR .. 111 0 00 . . 1 ..... 0001 00 ..... ..... @atomic +LDEOR .. 111 0 00 . . 1 ..... 0010 00 ..... ..... @atomic +LDSET .. 111 0 00 . . 1 ..... 0011 00 ..... ..... @atomic +LDSMAX .. 111 0 00 . . 1 ..... 0100 00 ..... ..... @atomic +LDSMIN .. 111 0 00 . . 1 ..... 0101 00 ..... ..... @atomic +LDUMAX .. 111 0 00 . . 1 ..... 0110 00 ..... ..... @atomic +LDUMIN .. 111 0 00 . . 1 ..... 0111 00 ..... ..... @atomic +SWP .. 111 0 00 . . 1 ..... 1000 00 ..... ..... @atomic + +### Load with PAC (FEAT_PAuth) + +# LDRAA (M=3D0) / LDRAB (M=3D1), offset (W=3D0) / pre-indexed (W=3D1) +LDRA 11 111 0 00 . . 1 ......... . 1 ..... ..... @ldra + +### System instructions =E2=80=94 DC cache maintenance + +# SYS with CRn=3DC7 covers all data cache operations (DC CIVAC, CVAC, etc.= ). +# On MMIO regions, cache maintenance is a harmless no-op. +NOP 1101 0101 0000 1 --- 0111 ---- --- ----- diff --git a/target/arm/emulate/arm_emulate.c b/target/arm/emulate/arm_emul= ate.c new file mode 100644 index 0000000..cd8f44d --- /dev/null +++ b/target/arm/emulate/arm_emulate.c @@ -0,0 +1,738 @@ +/* + * AArch64 instruction emulation for ISV=3D0 data aborts + * + * Copyright (c) 2026 Lucas Amaral + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "arm_emulate.h" +#include "qemu/bitops.h" +#include "qemu/error-report.h" + +/* Named "DisasContext" as required by the decodetree code generator */ +typedef struct { + CPUState *cpu; + const struct arm_emul_ops *ops; + ArmEmulResult result; +} DisasContext; + +#include "decode-a64-ldst.c.inc" + +/* GPR data access (Rt, Rs, Rt2) -- register 31 =3D XZR */ + +static uint64_t gpr_read(DisasContext *ctx, int reg) +{ + if (reg =3D=3D 31) { + return 0; /* XZR */ + } + return ctx->ops->read_gpr(ctx->cpu, reg); +} + +static void gpr_write(DisasContext *ctx, int reg, uint64_t val) +{ + if (reg =3D=3D 31) { + return; /* XZR -- discard */ + } + ctx->ops->write_gpr(ctx->cpu, reg, val); +} + +/* Base register access (Rn) -- register 31 =3D SP */ + +static uint64_t base_read(DisasContext *ctx, int rn) +{ + return ctx->ops->read_gpr(ctx->cpu, rn); +} + +static void base_write(DisasContext *ctx, int rn, uint64_t val) +{ + ctx->ops->write_gpr(ctx->cpu, rn, val); +} + +/* Memory access wrappers */ + +static int mem_read(DisasContext *ctx, uint64_t va, void *buf, int size) +{ + int ret =3D ctx->ops->read_mem(ctx->cpu, va, buf, size); + if (ret !=3D 0) { + ctx->result =3D ARM_EMUL_ERR_MEM; + } + return ret; +} + +static int mem_write(DisasContext *ctx, uint64_t va, const void *buf, int = size) +{ + int ret =3D ctx->ops->write_mem(ctx->cpu, va, buf, size); + if (ret !=3D 0) { + ctx->result =3D ARM_EMUL_ERR_MEM; + } + return ret; +} + +/* Sign/zero extension helpers */ + +static uint64_t sign_extend(uint64_t val, int from_bits) +{ + int shift =3D 64 - from_bits; + return (int64_t)(val << shift) >> shift; +} + +/* Apply sign/zero extension */ +static uint64_t load_extend(uint64_t val, int sz, int sign, int ext) +{ + int data_bits =3D 8 << sz; + + if (sign) { + val =3D sign_extend(val, data_bits); + if (ext) { + /* Sign-extend to 32 bits (W register) */ + val &=3D 0xFFFFFFFF; + } + } else if (ext) { + /* Zero-extend to 32 bits (W register) */ + val &=3D 0xFFFFFFFF; + } + return val; +} + +/* Register offset extension (DDI 0487 C6.2.131) */ + +static uint64_t extend_reg(uint64_t val, int option, int shift) +{ + switch (option) { + case 0: /* UXTB */ + val =3D (uint8_t)val; + break; + case 1: /* UXTH */ + val =3D (uint16_t)val; + break; + case 2: /* UXTW */ + val =3D (uint32_t)val; + break; + case 3: /* UXTX / LSL */ + break; + case 4: /* SXTB */ + val =3D (int64_t)(int8_t)val; + break; + case 5: /* SXTH */ + val =3D (int64_t)(int16_t)val; + break; + case 6: /* SXTW */ + val =3D (int64_t)(int32_t)val; + break; + case 7: /* SXTX */ + break; + } + return val << shift; +} + +/* + * Load/store pair: STP, LDP, STNP, LDNP, STGP, LDPSW + * (DDI 0487 C3.3.14 -- C3.3.16) + */ + +static bool trans_STP(DisasContext *ctx, arg_ldstpair *a) +{ + int esize =3D 1 << a->sz; /* 4 or 8 bytes */ + int64_t offset =3D (int64_t)a->imm << a->sz; + uint64_t base =3D base_read(ctx, a->rn); + uint64_t va =3D a->p ? base : base + offset; /* post-index: unmodified= base */ + uint8_t buf[16]; /* max 2 x 8 bytes */ + + uint64_t v1 =3D gpr_read(ctx, a->rt); + uint64_t v2 =3D gpr_read(ctx, a->rt2); + memcpy(buf, &v1, esize); + memcpy(buf + esize, &v2, esize); + + if (mem_write(ctx, va, buf, 2 * esize) !=3D 0) { + return true; + } + + if (a->w) { + base_write(ctx, a->rn, base + offset); + } + return true; +} + +static bool trans_LDP(DisasContext *ctx, arg_ldstpair *a) +{ + int esize =3D 1 << a->sz; + int64_t offset =3D (int64_t)a->imm << a->sz; + uint64_t base =3D base_read(ctx, a->rn); + uint64_t va =3D a->p ? base : base + offset; + uint8_t buf[16]; + uint64_t v1 =3D 0, v2 =3D 0; + + memset(buf, 0, sizeof(buf)); + if (mem_read(ctx, va, buf, 2 * esize) !=3D 0) { + return true; + } + memcpy(&v1, buf, esize); + memcpy(&v2, buf + esize, esize); + + /* LDPSW: sign-extend 32-bit values to 64-bit (sign=3D1, sz=3D2) */ + if (a->sign) { + v1 =3D sign_extend(v1, 8 * esize); + v2 =3D sign_extend(v2, 8 * esize); + } + + gpr_write(ctx, a->rt, v1); + gpr_write(ctx, a->rt2, v2); + + if (a->w) { + base_write(ctx, a->rn, base + offset); + } + return true; +} + +/* STGP: tag operation is a NOP for emulation; data stored via STP */ +static bool trans_STGP(DisasContext *ctx, arg_ldstpair *a) +{ + return trans_STP(ctx, a); +} + +/* + * SIMD/FP load/store pair: STP_v, LDP_v + * (DDI 0487 C3.3.14 -- C3.3.16) + */ + +static bool trans_STP_v(DisasContext *ctx, arg_ldstpair *a) +{ + int esize =3D 1 << a->sz; /* 4, 8, or 16 bytes */ + int64_t offset =3D (int64_t)a->imm << a->sz; + uint64_t base =3D base_read(ctx, a->rn); + uint64_t va =3D a->p ? base : base + offset; + uint8_t buf[32]; /* max 2 x 16 bytes */ + + ctx->ops->read_fpreg(ctx->cpu, a->rt, buf, esize); + ctx->ops->read_fpreg(ctx->cpu, a->rt2, buf + esize, esize); + + if (mem_write(ctx, va, buf, 2 * esize) !=3D 0) { + return true; + } + + if (a->w) { + base_write(ctx, a->rn, base + offset); + } + return true; +} + +static bool trans_LDP_v(DisasContext *ctx, arg_ldstpair *a) +{ + int esize =3D 1 << a->sz; + int64_t offset =3D (int64_t)a->imm << a->sz; + uint64_t base =3D base_read(ctx, a->rn); + uint64_t va =3D a->p ? base : base + offset; + uint8_t buf[32]; + + memset(buf, 0, sizeof(buf)); + if (mem_read(ctx, va, buf, 2 * esize) !=3D 0) { + return true; + } + + ctx->ops->write_fpreg(ctx->cpu, a->rt, buf, esize); + ctx->ops->write_fpreg(ctx->cpu, a->rt2, buf + esize, esize); + + if (a->w) { + base_write(ctx, a->rn, base + offset); + } + return true; +} + +/* Load/store single -- immediate (GPR) (DDI 0487 C3.3.8 -- C3.3.13) */ + +static bool trans_STR_i(DisasContext *ctx, arg_ldst_imm *a) +{ + int esize =3D (a->sz <=3D 3) ? (1 << a->sz) : 16; + int64_t offset =3D a->u ? ((int64_t)(uint64_t)a->imm << a->sz) + : (int64_t)a->imm; + uint64_t base =3D base_read(ctx, a->rn); + uint64_t va =3D a->p ? base : base + offset; + + uint64_t val =3D gpr_read(ctx, a->rt); + if (mem_write(ctx, va, &val, esize) !=3D 0) { + return true; + } + + if (a->w) { + base_write(ctx, a->rn, base + offset); + } + return true; +} + +static bool trans_LDR_i(DisasContext *ctx, arg_ldst_imm *a) +{ + int esize =3D (a->sz <=3D 3) ? (1 << a->sz) : 16; + int64_t offset =3D a->u ? ((int64_t)(uint64_t)a->imm << a->sz) + : (int64_t)a->imm; + uint64_t base =3D base_read(ctx, a->rn); + uint64_t va =3D a->p ? base : base + offset; + uint64_t val =3D 0; + + if (mem_read(ctx, va, &val, esize) !=3D 0) { + return true; + } + + val =3D load_extend(val, a->sz, a->sign, a->ext); + gpr_write(ctx, a->rt, val); + + if (a->w) { + base_write(ctx, a->rn, base + offset); + } + return true; +} + +/* + * Load/store single -- immediate (SIMD/FP) + * STR_v_i / LDR_v_i (DDI 0487 C3.3.10) + */ + +static bool trans_STR_v_i(DisasContext *ctx, arg_ldst_imm *a) +{ + int esize =3D (a->sz <=3D 3) ? (1 << a->sz) : 16; + int64_t offset =3D a->u ? ((int64_t)(uint64_t)a->imm << a->sz) + : (int64_t)a->imm; + uint64_t base =3D base_read(ctx, a->rn); + uint64_t va =3D a->p ? base : base + offset; + uint8_t buf[16]; + + ctx->ops->read_fpreg(ctx->cpu, a->rt, buf, esize); + if (mem_write(ctx, va, buf, esize) !=3D 0) { + return true; + } + + if (a->w) { + base_write(ctx, a->rn, base + offset); + } + return true; +} + +static bool trans_LDR_v_i(DisasContext *ctx, arg_ldst_imm *a) +{ + int esize =3D (a->sz <=3D 3) ? (1 << a->sz) : 16; + int64_t offset =3D a->u ? ((int64_t)(uint64_t)a->imm << a->sz) + : (int64_t)a->imm; + uint64_t base =3D base_read(ctx, a->rn); + uint64_t va =3D a->p ? base : base + offset; + uint8_t buf[16]; + + memset(buf, 0, sizeof(buf)); + if (mem_read(ctx, va, buf, esize) !=3D 0) { + return true; + } + + ctx->ops->write_fpreg(ctx->cpu, a->rt, buf, esize); + + if (a->w) { + base_write(ctx, a->rn, base + offset); + } + return true; +} + +/* + * Load/store single -- register offset (GPR) + * STR / LDR (DDI 0487 C3.3.9) + */ + +static bool trans_STR(DisasContext *ctx, arg_ldst *a) +{ + int esize =3D (a->sz <=3D 3) ? (1 << a->sz) : 16; + int shift =3D a->s ? a->sz : 0; + uint64_t rm_val =3D gpr_read(ctx, a->rm); + uint64_t offset =3D extend_reg(rm_val, a->opt, shift); + uint64_t va =3D base_read(ctx, a->rn) + offset; + + uint64_t val =3D gpr_read(ctx, a->rt); + mem_write(ctx, va, &val, esize); + return true; +} + +static bool trans_LDR(DisasContext *ctx, arg_ldst *a) +{ + int esize =3D (a->sz <=3D 3) ? (1 << a->sz) : 16; + int shift =3D a->s ? a->sz : 0; + uint64_t rm_val =3D gpr_read(ctx, a->rm); + uint64_t offset =3D extend_reg(rm_val, a->opt, shift); + uint64_t va =3D base_read(ctx, a->rn) + offset; + uint64_t val =3D 0; + + if (mem_read(ctx, va, &val, esize) !=3D 0) { + return true; + } + + val =3D load_extend(val, a->sz, a->sign, a->ext); + gpr_write(ctx, a->rt, val); + return true; +} + +/* + * Load/store single -- register offset (SIMD/FP) + * STR_v / LDR_v (DDI 0487 C3.3.10) + */ + +static bool trans_STR_v(DisasContext *ctx, arg_ldst *a) +{ + int esize =3D (a->sz <=3D 3) ? (1 << a->sz) : 16; + int shift =3D a->s ? a->sz : 0; + uint64_t rm_val =3D gpr_read(ctx, a->rm); + uint64_t offset =3D extend_reg(rm_val, a->opt, shift); + uint64_t va =3D base_read(ctx, a->rn) + offset; + uint8_t buf[16]; + + ctx->ops->read_fpreg(ctx->cpu, a->rt, buf, esize); + mem_write(ctx, va, buf, esize); + return true; +} + +static bool trans_LDR_v(DisasContext *ctx, arg_ldst *a) +{ + int esize =3D (a->sz <=3D 3) ? (1 << a->sz) : 16; + int shift =3D a->s ? a->sz : 0; + uint64_t rm_val =3D gpr_read(ctx, a->rm); + uint64_t offset =3D extend_reg(rm_val, a->opt, shift); + uint64_t va =3D base_read(ctx, a->rn) + offset; + uint8_t buf[16]; + + memset(buf, 0, sizeof(buf)); + if (mem_read(ctx, va, buf, esize) !=3D 0) { + return true; + } + + ctx->ops->write_fpreg(ctx->cpu, a->rt, buf, esize); + return true; +} + +/* + * Load/store exclusive: STXR, LDXR, STXP, LDXP + * (DDI 0487 C3.3.6) + * + * Exclusive monitors have no meaning on MMIO. STXR always reports + * success (Rs=3D0) and LDXR does not set an exclusive monitor. + */ + +static bool trans_STXR(DisasContext *ctx, arg_stxr *a) +{ + int esize =3D 1 << a->sz; + uint64_t va =3D base_read(ctx, a->rn); + uint64_t val =3D gpr_read(ctx, a->rt); + + if (mem_write(ctx, va, &val, esize) !=3D 0) { + return true; + } + + /* Report success -- no exclusive monitor on emulated access */ + gpr_write(ctx, a->rs, 0); + return true; +} + +static bool trans_LDXR(DisasContext *ctx, arg_stxr *a) +{ + int esize =3D 1 << a->sz; + uint64_t va =3D base_read(ctx, a->rn); + uint64_t val =3D 0; + + if (mem_read(ctx, va, &val, esize) !=3D 0) { + return true; + } + + gpr_write(ctx, a->rt, val); + return true; +} + +static bool trans_STXP(DisasContext *ctx, arg_stxr *a) +{ + int esize =3D 1 << a->sz; /* sz=3D2->4, sz=3D3->8 */ + uint64_t va =3D base_read(ctx, a->rn); + uint8_t buf[16]; + + uint64_t v1 =3D gpr_read(ctx, a->rt); + uint64_t v2 =3D gpr_read(ctx, a->rt2); + memcpy(buf, &v1, esize); + memcpy(buf + esize, &v2, esize); + + if (mem_write(ctx, va, buf, 2 * esize) !=3D 0) { + return true; + } + + gpr_write(ctx, a->rs, 0); /* success */ + return true; +} + +static bool trans_LDXP(DisasContext *ctx, arg_stxr *a) +{ + int esize =3D 1 << a->sz; + uint64_t va =3D base_read(ctx, a->rn); + uint8_t buf[16]; + uint64_t v1 =3D 0, v2 =3D 0; + + memset(buf, 0, sizeof(buf)); + if (mem_read(ctx, va, buf, 2 * esize) !=3D 0) { + return true; + } + + memcpy(&v1, buf, esize); + memcpy(&v2, buf + esize, esize); + gpr_write(ctx, a->rt, v1); + gpr_write(ctx, a->rt2, v2); + return true; +} + +/* + * Atomic memory operations (DDI 0487 C3.3.2) + * + * Non-atomic read-modify-write; sufficient for MMIO. + * Acquire/release semantics ignored (sequentially consistent by design). + */ + +typedef uint64_t (*atomic_op_fn)(uint64_t old, uint64_t operand, int bits); + +static uint64_t atomic_add(uint64_t old, uint64_t op, int bits) +{ + (void)bits; + return old + op; +} + +static uint64_t atomic_clr(uint64_t old, uint64_t op, int bits) +{ + (void)bits; + return old & ~op; +} + +static uint64_t atomic_eor(uint64_t old, uint64_t op, int bits) +{ + (void)bits; + return old ^ op; +} + +static uint64_t atomic_set(uint64_t old, uint64_t op, int bits) +{ + (void)bits; + return old | op; +} + +static uint64_t atomic_smax(uint64_t old, uint64_t op, int bits) +{ + int64_t a =3D sign_extend(old, bits); + int64_t b =3D sign_extend(op, bits); + return (a >=3D b) ? old : op; +} + +static uint64_t atomic_smin(uint64_t old, uint64_t op, int bits) +{ + int64_t a =3D sign_extend(old, bits); + int64_t b =3D sign_extend(op, bits); + return (a <=3D b) ? old : op; +} + +static uint64_t atomic_umax(uint64_t old, uint64_t op, int bits) +{ + uint64_t mask =3D (bits =3D=3D 64) ? UINT64_MAX : (1ULL << bits) - 1; + return ((old & mask) >=3D (op & mask)) ? old : op; +} + +static uint64_t atomic_umin(uint64_t old, uint64_t op, int bits) +{ + uint64_t mask =3D (bits =3D=3D 64) ? UINT64_MAX : (1ULL << bits) - 1; + return ((old & mask) <=3D (op & mask)) ? old : op; +} + +static bool do_atomic(DisasContext *ctx, arg_atomic *a, atomic_op_fn fn) +{ + int esize =3D 1 << a->sz; + int bits =3D 8 * esize; + uint64_t va =3D base_read(ctx, a->rn); + uint64_t old =3D 0; + + if (mem_read(ctx, va, &old, esize) !=3D 0) { + return true; + } + + uint64_t operand =3D gpr_read(ctx, a->rs); + uint64_t result =3D fn(old, operand, bits); + + if (mem_write(ctx, va, &result, esize) !=3D 0) { + return true; + } + + /* Rt receives the old value (before modification) */ + gpr_write(ctx, a->rt, old); + return true; +} + +static bool trans_LDADD(DisasContext *ctx, arg_atomic *a) +{ + return do_atomic(ctx, a, atomic_add); +} + +static bool trans_LDCLR(DisasContext *ctx, arg_atomic *a) +{ + return do_atomic(ctx, a, atomic_clr); +} + +static bool trans_LDEOR(DisasContext *ctx, arg_atomic *a) +{ + return do_atomic(ctx, a, atomic_eor); +} + +static bool trans_LDSET(DisasContext *ctx, arg_atomic *a) +{ + return do_atomic(ctx, a, atomic_set); +} + +static bool trans_LDSMAX(DisasContext *ctx, arg_atomic *a) +{ + return do_atomic(ctx, a, atomic_smax); +} + +static bool trans_LDSMIN(DisasContext *ctx, arg_atomic *a) +{ + return do_atomic(ctx, a, atomic_smin); +} + +static bool trans_LDUMAX(DisasContext *ctx, arg_atomic *a) +{ + return do_atomic(ctx, a, atomic_umax); +} + +static bool trans_LDUMIN(DisasContext *ctx, arg_atomic *a) +{ + return do_atomic(ctx, a, atomic_umin); +} + +static bool trans_SWP(DisasContext *ctx, arg_atomic *a) +{ + int esize =3D 1 << a->sz; + uint64_t va =3D base_read(ctx, a->rn); + uint64_t old =3D 0; + + if (mem_read(ctx, va, &old, esize) !=3D 0) { + return true; + } + + uint64_t newval =3D gpr_read(ctx, a->rs); + if (mem_write(ctx, va, &newval, esize) !=3D 0) { + return true; + } + + gpr_write(ctx, a->rt, old); + return true; +} + +/* Compare-and-swap: CAS, CASP (DDI 0487 C3.3.1) */ + +static bool trans_CAS(DisasContext *ctx, arg_cas *a) +{ + int esize =3D 1 << a->sz; + uint64_t va =3D base_read(ctx, a->rn); + uint64_t current =3D 0; + + if (mem_read(ctx, va, ¤t, esize) !=3D 0) { + return true; + } + + uint64_t mask =3D (esize =3D=3D 8) ? UINT64_MAX : (1ULL << (8 * esize)= ) - 1; + uint64_t compare =3D gpr_read(ctx, a->rs) & mask; + + if ((current & mask) =3D=3D compare) { + uint64_t newval =3D gpr_read(ctx, a->rt) & mask; + if (mem_write(ctx, va, &newval, esize) !=3D 0) { + return true; + } + } + + /* Rs receives the old memory value (whether or not swap occurred) */ + gpr_write(ctx, a->rs, current); + return true; +} + +/* CASP: compare-and-swap pair (Rs,Rs+1 compared; Rt,Rt+1 stored) */ +static bool trans_CASP(DisasContext *ctx, arg_cas *a) +{ + /* CASP requires even register pairs; odd or r31 is UNPREDICTABLE */ + if ((a->rs & 1) || a->rs >=3D 31 || (a->rt & 1) || a->rt >=3D 31) { + return false; + } + + int esize =3D 1 << a->sz; /* per-register size */ + uint64_t va =3D base_read(ctx, a->rn); + uint8_t buf[16]; + uint64_t cur1 =3D 0, cur2 =3D 0; + + memset(buf, 0, sizeof(buf)); + if (mem_read(ctx, va, buf, 2 * esize) !=3D 0) { + return true; + } + memcpy(&cur1, buf, esize); + memcpy(&cur2, buf + esize, esize); + + uint64_t mask =3D (esize =3D=3D 8) ? UINT64_MAX : (1ULL << (8 * esize)= ) - 1; + uint64_t cmp1 =3D gpr_read(ctx, a->rs) & mask; + uint64_t cmp2 =3D gpr_read(ctx, a->rs + 1) & mask; + + if ((cur1 & mask) =3D=3D cmp1 && (cur2 & mask) =3D=3D cmp2) { + uint64_t new1 =3D gpr_read(ctx, a->rt) & mask; + uint64_t new2 =3D gpr_read(ctx, a->rt + 1) & mask; + memcpy(buf, &new1, esize); + memcpy(buf + esize, &new2, esize); + if (mem_write(ctx, va, buf, 2 * esize) !=3D 0) { + return true; + } + } + + gpr_write(ctx, a->rs, cur1); + gpr_write(ctx, a->rs + 1, cur2); + return true; +} + +/* + * Load with PAC: LDRAA / LDRAB (FEAT_PAuth) + * (DDI 0487 C6.2.121) + * + * Pointer authentication is not emulated -- the base register is used + * directly (equivalent to auth always succeeding). + */ + +static bool trans_LDRA(DisasContext *ctx, arg_ldra *a) +{ + int64_t offset =3D (int64_t)a->imm << 3; /* S:imm9, scaled by 8 */ + uint64_t base =3D base_read(ctx, a->rn); + uint64_t va =3D base + offset; /* auth not emulated */ + uint64_t val =3D 0; + + if (mem_read(ctx, va, &val, 8) !=3D 0) { + return true; + } + + gpr_write(ctx, a->rt, val); + + if (a->w) { + base_write(ctx, a->rn, va); + } + return true; +} + +/* PRFM, DC cache maintenance -- treated as NOP */ +static bool trans_NOP(DisasContext *ctx, arg_NOP *a) +{ + (void)ctx; + (void)a; + return true; +} + +/* Entry point */ + +ArmEmulResult arm_emul_insn(CPUState *cpu, const struct arm_emul_ops *ops, + uint32_t insn) +{ + DisasContext ctx =3D { + .cpu =3D cpu, + .ops =3D ops, + .result =3D ARM_EMUL_OK, + }; + + if (!decode_a64_ldst(&ctx, insn)) { + return ARM_EMUL_UNHANDLED; + } + + return ctx.result; +} diff --git a/target/arm/emulate/arm_emulate.h b/target/arm/emulate/arm_emul= ate.h new file mode 100644 index 0000000..eef8a37 --- /dev/null +++ b/target/arm/emulate/arm_emulate.h @@ -0,0 +1,55 @@ +/* + * AArch64 instruction emulation library + * + * Copyright (c) 2026 Lucas Amaral + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef ARM_EMULATE_H +#define ARM_EMULATE_H + +#include "qemu/osdep.h" + +/* + * CPUState is only used as an opaque pointer (via qemu/typedefs.h). + * Callers that dereference CPUState include hw/core/cpu.h themselves. + */ + +/** + * ArmEmulResult - return status from arm_emul_insn() + */ +typedef enum { + ARM_EMUL_OK, /* Instruction emulated successfully */ + ARM_EMUL_UNHANDLED, /* Instruction not recognized by decoder */ + ARM_EMUL_ERR_MEM, /* Memory access callback failed */ +} ArmEmulResult; + +/** + * struct arm_emul_ops - hypervisor register/memory callbacks + * + * GPR reg 31 =3D SP (the XZR/SP distinction is handled internally). + * Memory callbacks use guest virtual addresses. + */ +struct arm_emul_ops { + uint64_t (*read_gpr)(CPUState *cpu, int reg); + void (*write_gpr)(CPUState *cpu, int reg, uint64_t val); + + /* @size: access width in bytes (4, 8, or 16) */ + void (*read_fpreg)(CPUState *cpu, int reg, void *buf, int size); + void (*write_fpreg)(CPUState *cpu, int reg, const void *buf, int size); + + /* Returns 0 on success, non-zero on failure */ + int (*read_mem)(CPUState *cpu, uint64_t va, void *buf, int size); + int (*write_mem)(CPUState *cpu, uint64_t va, const void *buf, int size= ); +}; + +/** + * arm_emul_insn - decode and emulate one AArch64 instruction + * + * Caller must synchronize CPU state and fetch @insn before calling. + */ +ArmEmulResult arm_emul_insn(CPUState *cpu, const struct arm_emul_ops *ops, + uint32_t insn); + +#endif /* ARM_EMULATE_H */ diff --git a/target/arm/emulate/meson.build b/target/arm/emulate/meson.build new file mode 100644 index 0000000..29b7879 --- /dev/null +++ b/target/arm/emulate/meson.build @@ -0,0 +1,16 @@ +gen_a64_ldst =3D decodetree.process('a64-ldst.decode', + extra_args: ['--static-decode=3Ddecode_a64_ldst']) + +arm_common_system_ss.add(when: 'TARGET_AARCH64', if_true: [ + gen_a64_ldst, files('arm_emulate.c') +]) + +# Static library for unit testing (links emulation code + decodetree decod= er) +arm_emulate_test_lib =3D static_library('arm-emulate-test', + sources: [files('arm_emulate.c'), gen_a64_ldst], + dependencies: [qemuutil], + include_directories: include_directories('.')) + +arm_emulate_test =3D declare_dependency( + link_with: arm_emulate_test_lib, + include_directories: include_directories('.')) diff --git a/target/arm/meson.build b/target/arm/meson.build index 6e0e504..a4b2291 100644 --- a/target/arm/meson.build +++ b/target/arm/meson.build @@ -57,6 +57,7 @@ arm_common_system_ss.add(files( 'vfp_fpscr.c', )) =20 +subdir('emulate') subdir('hvf') subdir('whpx') =20 --=20 2.52.0 From nobody Tue Apr 7 13:09:46 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1773368413; cv=none; d=zohomail.com; s=zohoarc; b=ksbVr308Soyzhg1IzMGfZXXYClCU9a+RNf62j3jk+QQrHqk2HeCSf/gbjbx+ykXXUwKd+VhVvRy5dWL32mQ6STWEGx2si23oYmy1q8O1GglcfSHovhTQJTn9xOWzOA5+eBCqvrKsdmel9c+gJjZZFEGsnt6brGpX4SqFEsnyF/A= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1773368413; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=E/gHPExaAQWiX7uDm1akcYXiBtM/aPtEAK3dJ8gjVZE=; b=R6jCjZUBFcTeMnpsyOee/BqQGkLyTSJ3Vq+1yV4s3JO4thc2MEirgyHPI0OEoH/CeCpcB7xI3FIcRiwXRzOnFOZtqwRdRwqxwToLKEPmYOiMQe5DTOPcVRUND8HzRNxRAahqngKuQWGMo/gaaOiLHreoWtr2vlkegZ9Uuw08nWE= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 177336841379537.16140960753876; Thu, 12 Mar 2026 19:20:13 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1w0s7H-0000Rn-3g; Thu, 12 Mar 2026 22:19:07 -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 1w0s7G-0000RC-DM for qemu-devel@nongnu.org; Thu, 12 Mar 2026 22:19:06 -0400 Received: from mail-dl1-x1235.google.com ([2607:f8b0:4864:20::1235]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1w0s7C-00080O-MG for qemu-devel@nongnu.org; Thu, 12 Mar 2026 22:19:06 -0400 Received: by mail-dl1-x1235.google.com with SMTP id a92af1059eb24-128b9b7e3edso1429244c88.0 for ; Thu, 12 Mar 2026 19:19:02 -0700 (PDT) Received: from 192.168.0.29 ([2804:14d:4c71:86dd:588a:39d7:d008:37c2]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2beab3eec52sm796218eec.14.2026.03.12.19.18.58 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Thu, 12 Mar 2026 19:19:00 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1773368341; x=1773973141; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=E/gHPExaAQWiX7uDm1akcYXiBtM/aPtEAK3dJ8gjVZE=; b=JlicgpqI/Vp2ObxQ5YHrJ7Ch0WWVisAnZXJ4UwAuJuQ9wrLnMLELCkKKTA091kqdm6 mKl4+CZKLrDTcRayGwSEmQl4QtEese3/6ggbd0xnkjAfwXEwFLeTQA0V29iekqHpXung 8Fgmx4vpoQH04unIIs3k7ctjcatq8AECT6bS80iRiAEThnFlqdA0OgzLJJdceDHimbyF 0pMGIBwPQtjkukyj5DL5rtLS3gl0Z2l+ARFjp73vzAFRLJq7qCBkmgG0cxnGOBK7YPEo WmcQOTKkJqoWkH/sbehg11JSKNe3O6sSaHdde2z0DNHm3cucnl7kyhZy3iVdB41NJxLi ISdA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1773368341; x=1773973141; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=E/gHPExaAQWiX7uDm1akcYXiBtM/aPtEAK3dJ8gjVZE=; b=mww6dAt+O+wbKJQwruFzPIZRMUufCuXsLjhWeOvXZsvpbfyLZEqRi73fh40tGdSPT9 5NpVO5Sd71uMpdjFFKY4KhUPENHnpV4AGcfYUXru53MAWgmbVDpDZwt/OUTDDJku/tsM 4OLOoqwcRIYDNKLEBHwMDuGrW2adwGmIw4dTlR50oHxmyOHNQLd/OdLOMYFNRTRTAliW VCtWGL4m1Sl3mCoPqUNrKO6Re1rWRIR2Wy8/x9VhvUF1yFBZMRBluZgsP31ZcWpKeyB1 37vXU0NturCsXSto9PoLP3r3pwETWT5cMXhnGTOm2zDtFTuenv4M80+SJ8nxhS1nFx+U WTLQ== X-Gm-Message-State: AOJu0Ywg1v25IDwDnjc8hW4Uce+eCN2bnhwfRI3YF1xO9iSzskouTK0+ dwd4WhmEsqBjOoHsw74oHwdeY0gZYisOKfR8Z/AHPIZjm09py6gqdL8RC0ts1wTL X-Gm-Gg: ATEYQzyhLH1lT4QokpELvwDVf8CqPLPEhCgLyOwfcdoAfZsT7mqQbWDIyZ90k9VFs5X 4Gb0PLU/ICn8aLZqoWUjv4n9RD5rBYFwSpFTRfFG1zzAyceB5+zjS7blngZXQi8vvBerPVHeaSK 89EFyg56yadF8pXmvtjcW+2LhKzTVcuF0uRU4DKzpGFRaVdOluEscQuus5b1h/n8peYiSACONnF v78uFF37rriwh1HeF9j0sDrUnYDc8zpMe27VXBMdr9XHh+50fcsv0x5R9YWzPhyEXq7VgbMtReh EuHPXiFDqp18mvomkilk07ZAv7e/0gqaGfm26cFKENOk4MJ1xeJiEiyMeB44UZKZRfznQKsDUQE mbShlVKvjttlwphfOuQ7cui0b3wf2+U85nyFO/3YAAWR7J8GnaWIiZjx7zjplcgRXf7oD6rGwZ6 EmLTrDJPTsx31ZAV+mv10sHJar8sjJD4DhVSQpgYg8bqpAOuFckYI= X-Received: by 2002:a05:7300:d513:b0:2be:1dc7:999a with SMTP id 5a478bee46e88-2bea5401bf0mr973181eec.4.1773368340639; Thu, 12 Mar 2026 19:19:00 -0700 (PDT) From: Lucas Amaral To: qemu-devel@nongnu.org Cc: qemu-arm@nongnu.org, agraf@csgraf.de, Lucas Amaral Subject: [PATCH v2 2/3] tests: add unit tests for ISV=0 emulation library Date: Thu, 12 Mar 2026 23:18:49 -0300 Message-ID: <20260313021850.42379-3-lucaaamaral@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260313021850.42379-1-lucaaamaral@gmail.com> References: <20260309214852.92545-1-lucaaamaral@gmail.com> <20260313021850.42379-1-lucaaamaral@gmail.com> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=2607:f8b0:4864:20::1235; envelope-from=lucaaamaral@gmail.com; helo=mail-dl1-x1235.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, FSL_HELO_BARE_IP_2=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=unavailable autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1773368415719158500 Add test-arm-emulate with 19 test cases covering the arm_emul_ops callback interface using a mock environment. Encodings and expected values verified against ARM architecture ground truth. Signed-off-by: Lucas Amaral --- tests/unit/meson.build | 1 + tests/unit/test-arm-emulate.c | 540 ++++++++++++++++++++++++++++++++++ 2 files changed, 541 insertions(+) create mode 100644 tests/unit/test-arm-emulate.c diff --git a/tests/unit/meson.build b/tests/unit/meson.build index 41e8b06..27a515b 100644 --- a/tests/unit/meson.build +++ b/tests/unit/meson.build @@ -157,6 +157,7 @@ if have_system } endif tests +=3D {'test-qdev': [qom, hwcore]} + tests +=3D {'test-arm-emulate': [arm_emulate_test]} endif =20 if have_ga and host_os =3D=3D 'linux' diff --git a/tests/unit/test-arm-emulate.c b/tests/unit/test-arm-emulate.c new file mode 100644 index 0000000..5ab7f04 --- /dev/null +++ b/tests/unit/test-arm-emulate.c @@ -0,0 +1,540 @@ +/* + * Unit tests for AArch64 ISV=3D0 instruction emulation library + * + * Copyright (c) 2026 Lucas Amaral + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "arm_emulate.h" + +/* Mock environment: GPR, FPR, and flat memory */ + +typedef struct MockEnv { + uint64_t gpr[32]; /* X0-X30, X31=3DSP */ + uint8_t fpr[32][16]; /* V0-V31, 128 bits each */ + uint8_t mem[0x1000]; /* 4 KiB flat address space */ + bool mem_fail; /* if true, memory ops return -1 */ +} MockEnv; + +static MockEnv *env_from_cpu(CPUState *cpu) +{ + return (MockEnv *)cpu; +} + +static uint64_t mock_read_gpr(CPUState *cpu, int reg) +{ + return env_from_cpu(cpu)->gpr[reg]; +} + +static void mock_write_gpr(CPUState *cpu, int reg, uint64_t val) +{ + env_from_cpu(cpu)->gpr[reg] =3D val; +} + +static void mock_read_fpreg(CPUState *cpu, int reg, void *buf, int size) +{ + memcpy(buf, env_from_cpu(cpu)->fpr[reg], size); +} + +static void mock_write_fpreg(CPUState *cpu, int reg, const void *buf, int = size) +{ + MockEnv *env =3D env_from_cpu(cpu); + memset(env->fpr[reg], 0, 16); + memcpy(env->fpr[reg], buf, size); +} + +static int mock_read_mem(CPUState *cpu, uint64_t va, void *buf, int size) +{ + MockEnv *env =3D env_from_cpu(cpu); + if (env->mem_fail || va + size > sizeof(env->mem)) { + return -1; + } + memcpy(buf, env->mem + va, size); + return 0; +} + +static int mock_write_mem(CPUState *cpu, uint64_t va, const void *buf, int= size) +{ + MockEnv *env =3D env_from_cpu(cpu); + if (env->mem_fail || va + size > sizeof(env->mem)) { + return -1; + } + memcpy(env->mem + va, buf, size); + return 0; +} + +static const struct arm_emul_ops mock_ops =3D { + .read_gpr =3D mock_read_gpr, + .write_gpr =3D mock_write_gpr, + .read_fpreg =3D mock_read_fpreg, + .write_fpreg =3D mock_write_fpreg, + .read_mem =3D mock_read_mem, + .write_mem =3D mock_write_mem, +}; + +/* Helper: reset mock environment */ +static MockEnv *fresh_env(MockEnv *env) +{ + memset(env, 0, sizeof(*env)); + return env; +} + +/* Helper: call arm_emul_insn with the mock environment */ +static ArmEmulResult emul(MockEnv *env, uint32_t insn) +{ + return arm_emul_insn((CPUState *)env, &mock_ops, insn); +} + +/* Helper: write a uint64_t to mock memory at a given VA */ +static void mem_write64(MockEnv *env, uint64_t va, uint64_t val) +{ + memcpy(env->mem + va, &val, 8); +} + +/* Helper: read a uint64_t from mock memory */ +static uint64_t mem_read64(MockEnv *env, uint64_t va) +{ + uint64_t val =3D 0; + memcpy(&val, env->mem + va, 8); + return val; +} + +/* Helper: read a uint32_t from mock memory */ +static uint32_t mem_read32(MockEnv *env, uint64_t va) +{ + uint32_t val =3D 0; + memcpy(&val, env->mem + va, 4); + return val; +} + +/* STP / LDP (64-bit store/load pair, signed offset) */ + +/* + * STP X0, X1, [X2] + * 10 101 0 010 0 0000000 00001 00010 00000 + * =3D 0xA9000440 + */ +static void test_stp_offset(void) +{ + MockEnv env; + fresh_env(&env); + + env.gpr[0] =3D 0xDEADBEEF; + env.gpr[1] =3D 0xCAFEBABE; + env.gpr[2] =3D 0x100; /* base address */ + + g_assert_cmpint(emul(&env, 0xA9000440), =3D=3D, ARM_EMUL_OK); + + g_assert_cmphex(mem_read64(&env, 0x100), =3D=3D, 0xDEADBEEF); + g_assert_cmphex(mem_read64(&env, 0x108), =3D=3D, 0xCAFEBABE); + /* No writeback =E2=80=94 base unchanged */ + g_assert_cmphex(env.gpr[2], =3D=3D, 0x100); +} + +/* + * LDP X3, X4, [X5] + * 10 101 0 010 1 0000000 00100 00101 00011 + * =3D 0xA94010A3 + */ +static void test_ldp_offset(void) +{ + MockEnv env; + fresh_env(&env); + + env.gpr[5] =3D 0x200; + mem_write64(&env, 0x200, 0x1111111111111111ULL); + mem_write64(&env, 0x208, 0x2222222222222222ULL); + + g_assert_cmpint(emul(&env, 0xA94010A3), =3D=3D, ARM_EMUL_OK); + + g_assert_cmphex(env.gpr[3], =3D=3D, 0x1111111111111111ULL); + g_assert_cmphex(env.gpr[4], =3D=3D, 0x2222222222222222ULL); +} + +/* STP pre-indexed (writeback) */ + +/* + * STP X0, X1, [X2, #16]! + * 10 101 0 011 0 0000010 00001 00010 00000 + * =3D 0xA9810440 + * imm7=3D+2, scaled by 8 =3D offset +16 + */ +static void test_stp_preindex(void) +{ + MockEnv env; + fresh_env(&env); + + env.gpr[0] =3D 0xAAAA; + env.gpr[1] =3D 0xBBBB; + env.gpr[2] =3D 0x100; + + g_assert_cmpint(emul(&env, 0xA9810440), =3D=3D, ARM_EMUL_OK); + + /* Data stored at base+16 =3D 0x110 */ + g_assert_cmphex(mem_read64(&env, 0x110), =3D=3D, 0xAAAA); + g_assert_cmphex(mem_read64(&env, 0x118), =3D=3D, 0xBBBB); + /* Writeback: base updated to base+16 */ + g_assert_cmphex(env.gpr[2], =3D=3D, 0x110); +} + +/* STR / LDR unsigned offset (64-bit) */ + +/* + * STR X0, [X1] + * 11 111 0 01 00 000000000000 00001 00000 + * =3D 0xF9000020 + */ +static void test_str_uoffset(void) +{ + MockEnv env; + fresh_env(&env); + + env.gpr[0] =3D 0x42; + env.gpr[1] =3D 0x80; + + g_assert_cmpint(emul(&env, 0xF9000020), =3D=3D, ARM_EMUL_OK); + g_assert_cmphex(mem_read64(&env, 0x80), =3D=3D, 0x42); +} + +/* + * LDR X2, [X1] + * 11 111 0 01 01 000000000000 00001 00010 + * =3D 0xF9400022 + */ +static void test_ldr_uoffset(void) +{ + MockEnv env; + fresh_env(&env); + + env.gpr[1] =3D 0x80; + mem_write64(&env, 0x80, 0xFEDCBA9876543210ULL); + + g_assert_cmpint(emul(&env, 0xF9400022), =3D=3D, ARM_EMUL_OK); + g_assert_cmphex(env.gpr[2], =3D=3D, 0xFEDCBA9876543210ULL); +} + +/* LDRB zero-extend / LDRSB sign-extend */ + +/* + * LDRB W2, [X1] (zero-extend byte to 32-bit) + * 00 111 0 01 01 000000000000 00001 00010 + * =3D 0x39400022 + */ +static void test_ldrb_zero_extend(void) +{ + MockEnv env; + fresh_env(&env); + + env.gpr[1] =3D 0x80; + env.mem[0x80] =3D 0xFF; + + g_assert_cmpint(emul(&env, 0x39400022), =3D=3D, ARM_EMUL_OK); + g_assert_cmphex(env.gpr[2], =3D=3D, 0xFF); +} + +/* + * LDRSB X2, [X1] (sign-extend byte to 64-bit) + * 00 111 0 01 10 000000000000 00001 00010 + * =3D 0x39800022 + */ +static void test_ldrsb_sign_extend(void) +{ + MockEnv env; + fresh_env(&env); + + env.gpr[1] =3D 0x80; + env.mem[0x80] =3D 0x80; /* -128 as signed byte */ + + g_assert_cmpint(emul(&env, 0x39800022), =3D=3D, ARM_EMUL_OK); + g_assert_cmphex(env.gpr[2], =3D=3D, 0xFFFFFFFFFFFFFF80ULL); +} + +/* XZR -- register 31 reads as zero for GPR data */ + +/* + * STR XZR, [X1] (store zero register) + * 11 111 0 01 00 000000000000 00001 11111 + * =3D 0xF900003F + */ +static void test_xzr_reads_zero(void) +{ + MockEnv env; + fresh_env(&env); + + env.gpr[31] =3D 0x9999; /* SP value =E2=80=94 should NOT be stored */ + env.gpr[1] =3D 0x80; + mem_write64(&env, 0x80, 0xFFFF); /* pre-fill to verify overwrite */ + + g_assert_cmpint(emul(&env, 0xF900003F), =3D=3D, ARM_EMUL_OK); + /* XZR is zero, not SP */ + g_assert_cmphex(mem_read64(&env, 0x80), =3D=3D, 0); +} + +/* Atomic -- LDADD */ + +/* + * LDADD X0, X1, [X2] + * 11 111 0 00 00 1 00000 0000 00 00010 00001 + * =3D 0xF8200041 + * sz=3D3, a=3D0, r=3D0, rs=3D0, opc=3D0000 (ADD), rn=3D2, rt=3D1 + */ +static void test_ldadd(void) +{ + MockEnv env; + fresh_env(&env); + + env.gpr[0] =3D 5; /* operand (Rs) */ + env.gpr[2] =3D 0x100; /* base address */ + mem_write64(&env, 0x100, 10); /* old value */ + + g_assert_cmpint(emul(&env, 0xF8200041), =3D=3D, ARM_EMUL_OK); + + /* Rt gets old value */ + g_assert_cmphex(env.gpr[1], =3D=3D, 10); + /* Memory gets old + operand */ + g_assert_cmphex(mem_read64(&env, 0x100), =3D=3D, 15); +} + +/* SWP */ + +/* + * SWP X0, X1, [X2] + * 11 111 0 00 00 1 00000 1000 00 00010 00001 + * =3D 0xF8208041 + */ +static void test_swp(void) +{ + MockEnv env; + fresh_env(&env); + + env.gpr[0] =3D 42; /* new value (Rs) */ + env.gpr[2] =3D 0x100; /* base address */ + mem_write64(&env, 0x100, 99); /* old value */ + + g_assert_cmpint(emul(&env, 0xF8208041), =3D=3D, ARM_EMUL_OK); + + /* Rt gets old value */ + g_assert_cmphex(env.gpr[1], =3D=3D, 99); + /* Memory gets new value */ + g_assert_cmphex(mem_read64(&env, 0x100), =3D=3D, 42); +} + +/* CAS (compare-and-swap, 64-bit) */ + +/* + * CAS X0, X2, [X4] + * 11 001000 1 0 1 00000 0 11111 00100 00010 + * =3D 0xC8A07C82 + * sz=3D3, a=3D0, r=3D0, rs=3D0 (compare), rt=3D2 (new), rn=3D4 (base) + */ +static void test_cas_match(void) +{ + MockEnv env; + fresh_env(&env); + + env.gpr[0] =3D 100; /* Rs: compare value */ + env.gpr[2] =3D 200; /* Rt: new value */ + env.gpr[4] =3D 0x100; /* Rn: base address */ + mem_write64(&env, 0x100, 100); /* memory =3D=3D compare, swap occurs = */ + + g_assert_cmpint(emul(&env, 0xC8A07C82), =3D=3D, ARM_EMUL_OK); + + /* Rs gets old memory value */ + g_assert_cmphex(env.gpr[0], =3D=3D, 100); + /* Memory updated to new value */ + g_assert_cmphex(mem_read64(&env, 0x100), =3D=3D, 200); +} + +/* CAS with no match =E2=80=94 memory unchanged */ +static void test_cas_nomatch(void) +{ + MockEnv env; + fresh_env(&env); + + env.gpr[0] =3D 100; /* compare */ + env.gpr[2] =3D 200; /* new */ + env.gpr[4] =3D 0x100; + mem_write64(&env, 0x100, 999); /* memory !=3D compare, no swap */ + + g_assert_cmpint(emul(&env, 0xC8A07C82), =3D=3D, ARM_EMUL_OK); + + g_assert_cmphex(env.gpr[0], =3D=3D, 999); /* Rs gets old value */ + g_assert_cmphex(mem_read64(&env, 0x100), =3D=3D, 999); /* unchanged */ +} + +/* CASP (pair compare-and-swap) */ + +/* + * CASP X0, X1, X2, X3, [X4] (64-bit pair) + * 01 001000 0 0 1 00000 0 11111 00100 00010 + * =3D 0x48207C82 + */ +static void test_casp_match(void) +{ + MockEnv env; + fresh_env(&env); + + env.gpr[0] =3D 0xAA; env.gpr[1] =3D 0xBB; /* Rs pair: compare */ + env.gpr[2] =3D 0xCC; env.gpr[3] =3D 0xDD; /* Rt pair: new */ + env.gpr[4] =3D 0x100; + mem_write64(&env, 0x100, 0xAA); + mem_write64(&env, 0x108, 0xBB); + + g_assert_cmpint(emul(&env, 0x48207C82), =3D=3D, ARM_EMUL_OK); + + g_assert_cmphex(mem_read64(&env, 0x100), =3D=3D, 0xCC); + g_assert_cmphex(mem_read64(&env, 0x108), =3D=3D, 0xDD); +} + +/* CASP with odd register -- validation rejects */ + +/* + * CASP with Rs=3DX1 (odd) -- trans_CASP returns false + * 01 001000 0 0 1 00001 0 11111 00100 00010 + * =3D 0x48217C82 + */ +static void test_casp_odd_register(void) +{ + MockEnv env; + fresh_env(&env); + + env.gpr[4] =3D 0x100; + + /* Odd Rs -- decoder returns false -- ARM_EMUL_UNHANDLED */ + g_assert_cmpint(emul(&env, 0x48217C82), =3D=3D, ARM_EMUL_UNHANDLED); +} + +/* Unrecognized instruction -- ARM_EMUL_UNHANDLED */ + +static void test_unhandled(void) +{ + MockEnv env; + fresh_env(&env); + + /* B #0 =3D 0x14000000 =E2=80=94 a branch, not a load/store */ + g_assert_cmpint(emul(&env, 0x14000000), =3D=3D, ARM_EMUL_UNHANDLED); +} + +/* Memory error -- ARM_EMUL_ERR_MEM */ + +static void test_mem_error(void) +{ + MockEnv env; + fresh_env(&env); + + env.gpr[1] =3D 0x80; + env.mem_fail =3D true; + + /* LDR X2, [X1] =E2=80=94 memory read will fail */ + g_assert_cmpint(emul(&env, 0xF9400022), =3D=3D, ARM_EMUL_ERR_MEM); +} + +/* PRFM (prefetch) -- NOP, returns OK */ + +/* + * PRFM #0, [X1] (unsigned offset, imm=3D0) + * 11 111 0 01 10 000000000000 00001 00000 + * =3D 0xF9800020 + */ +static void test_prfm_nop(void) +{ + MockEnv env; + fresh_env(&env); + + env.gpr[1] =3D 0x80; + + g_assert_cmpint(emul(&env, 0xF9800020), =3D=3D, ARM_EMUL_OK); + /* No state change =E2=80=94 it's a NOP */ +} + +/* SIMD/FP store/load pair */ + +/* + * STP S0, S1, [X2] (32-bit FP pair, signed offset, imm=3D0) + * 00 101 1 010 0 0000000 00001 00010 00000 + * =3D 0x2D000440 + */ +static void test_stp_fp(void) +{ + MockEnv env; + fresh_env(&env); + + uint32_t f0 =3D 0x3F800000; /* 1.0f */ + uint32_t f1 =3D 0x40000000; /* 2.0f */ + memcpy(env.fpr[0], &f0, 4); + memcpy(env.fpr[1], &f1, 4); + env.gpr[2] =3D 0x200; + + g_assert_cmpint(emul(&env, 0x2D000440), =3D=3D, ARM_EMUL_OK); + + g_assert_cmphex(mem_read32(&env, 0x200), =3D=3D, 0x3F800000); + g_assert_cmphex(mem_read32(&env, 0x204), =3D=3D, 0x40000000); +} + +/* LDR post-indexed (writeback after load) */ + +/* + * LDR X3, [X1], #8 (post-indexed, imm=3D+8) + * 11 111 0 00 01 0 000001000 01 00001 00011 + * =3D 0xF8408423 + * sz=3D3, opc=3D01, imm9=3D+8, type=3D01 (post-index), rn=3D1, rt=3D3 + */ +static void test_ldr_postindex(void) +{ + MockEnv env; + fresh_env(&env); + + env.gpr[1] =3D 0x100; + mem_write64(&env, 0x100, 0x55AA55AA55AA55AAULL); + + g_assert_cmpint(emul(&env, 0xF8408423), =3D=3D, ARM_EMUL_OK); + + /* Load from original base */ + g_assert_cmphex(env.gpr[3], =3D=3D, 0x55AA55AA55AA55AAULL); + /* Writeback: base +=3D 8 */ + g_assert_cmphex(env.gpr[1], =3D=3D, 0x108); +} + +/* Entry point */ + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + /* Load/store pair */ + g_test_add_func("/arm-emulate/stp-offset", test_stp_offset); + g_test_add_func("/arm-emulate/ldp-offset", test_ldp_offset); + g_test_add_func("/arm-emulate/stp-preindex", test_stp_preindex); + g_test_add_func("/arm-emulate/stp-fp", test_stp_fp); + + /* Load/store single */ + g_test_add_func("/arm-emulate/str-uoffset", test_str_uoffset); + g_test_add_func("/arm-emulate/ldr-uoffset", test_ldr_uoffset); + g_test_add_func("/arm-emulate/ldr-postindex", test_ldr_postindex); + g_test_add_func("/arm-emulate/ldrb-zero-extend", test_ldrb_zero_extend= ); + g_test_add_func("/arm-emulate/ldrsb-sign-extend", test_ldrsb_sign_exte= nd); + + /* XZR */ + g_test_add_func("/arm-emulate/xzr-reads-zero", test_xzr_reads_zero); + + /* Atomics */ + g_test_add_func("/arm-emulate/ldadd", test_ldadd); + g_test_add_func("/arm-emulate/swp", test_swp); + + /* Compare-and-swap */ + g_test_add_func("/arm-emulate/cas-match", test_cas_match); + g_test_add_func("/arm-emulate/cas-nomatch", test_cas_nomatch); + g_test_add_func("/arm-emulate/casp-match", test_casp_match); + g_test_add_func("/arm-emulate/casp-odd-register", test_casp_odd_regist= er); + + /* NOP */ + g_test_add_func("/arm-emulate/prfm-nop", test_prfm_nop); + + /* Error handling */ + g_test_add_func("/arm-emulate/unhandled", test_unhandled); + g_test_add_func("/arm-emulate/mem-error", test_mem_error); + + return g_test_run(); +} --=20 2.52.0 From nobody Tue Apr 7 13:09:46 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1773368389; cv=none; d=zohomail.com; s=zohoarc; b=S+6HO2rkL4o3EvV0uyxhmI5bJBufxy6+4y3rlQ1z3YFSUOgcLMPBDHwtRVrKXi4c86VapaY5Uma1IjNaf6mWwor38rLajCcVyVzhKyyIQwD3OadOPBiyhJilTfIZ4nJDCLMuViTA+9c7/1iGL0DTMEHt2aYZGVBRVqSL7MDtTXQ= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1773368389; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=NEo0bmu07ao6NQM9T5TABIQ6ozlil21ERYceiCcmYxE=; b=EJgkVVAWmgToAFA/1a+CE+e2efp8kfLMX9y8bhQEs2W3XfqodLMYn3z426RnpmoLdOuUOyKpNx5Ixqwefu/Y/Sq2EHec/5FeYqyK/7NYuOVkTjAgDO5NQ18CRcrbeoW7nH9f64pV/3TvaWpO/Au4EUMxIAk5exiQ8c3nR0eX5WQ= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1773368388992341.9701628817795; Thu, 12 Mar 2026 19:19:48 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1w0s7J-0000T7-94; Thu, 12 Mar 2026 22:19:09 -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 1w0s7G-0000Rd-Rq for qemu-devel@nongnu.org; Thu, 12 Mar 2026 22:19:06 -0400 Received: from mail-dy1-x1330.google.com ([2607:f8b0:4864:20::1330]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1w0s7E-00080i-Tx for qemu-devel@nongnu.org; Thu, 12 Mar 2026 22:19:06 -0400 Received: by mail-dy1-x1330.google.com with SMTP id 5a478bee46e88-2b6b0500e06so2024442eec.1 for ; Thu, 12 Mar 2026 19:19:04 -0700 (PDT) Received: from 192.168.0.29 ([2804:14d:4c71:86dd:588a:39d7:d008:37c2]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2beab3eec52sm796218eec.14.2026.03.12.19.19.01 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Thu, 12 Mar 2026 19:19:02 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1773368343; x=1773973143; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=NEo0bmu07ao6NQM9T5TABIQ6ozlil21ERYceiCcmYxE=; b=Fs6PD1kx7/LvqGOy3XUhrzN7Ve2ZexHs3pUx4uTF/tQh7MkixnjlRjv62T+LEXUFOz sqQxLepdHhtHXT8aODFB4TJHUS8k+uQE+2AJYfmg7OTwXF6NVwOe9nujsaAL1j3I+ZcI 6UhnGr27Jc1bPeIkGXkzAp2IB+Ij745VmU/0iKa6mAZm4atuMMosNzdrnB2BIJMgR38v yIWMLnTzj1mU68muLu7MTGZE5HEsVImJunMuTp6SzJ/ORcZCE03Jxt/dNAWiXqEV/QgF m2RG+YTKEcoO4gC/GOGi5uBs+3c6o3xDdkYrgaux9NyaWNUhMvIq6JprtW2I5bEFSnPx zjnw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1773368343; x=1773973143; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=NEo0bmu07ao6NQM9T5TABIQ6ozlil21ERYceiCcmYxE=; b=tDPcG215Y+7gRrFauW81q92nH0ftxZhdkibiN2/zDsHi0WwPQjwvRGaBAilJvmNUK5 SO+oazzNwIDqWHGaa2BEkwMHF3ri2aYtuP+mz3wfFenWU6xYhG58E54Ath3XMvlZtkaH m+V8MlzR8pdKgSzR++YehY8Ld9fYdLhpToNVsTvOU3vErM04ZGHyzYqlfG/t7VllqWX0 O7L0EiUNzkmUYlToWHMSiqIWq4UPAWrmMGjglNOpqaRFX4BE3N/mX41DAW1OI0j95SCY UTccz3UXvDV8wVO7snxURHZYrvzl5s7+FxffxX+iqHj+GVVEmA/HZkvpmcpiThZbaWXO GA0w== X-Gm-Message-State: AOJu0YyLQaBVDYnev5FshChfC5k1qAvMfZPuUyB4JqnxpH8MZ7GFEfwq WOwrpzGtWeYIiUdtfyB938skWSteF62fdM+W4Nys5MwnIHlahNvbF0NaPpdsG87A X-Gm-Gg: ATEYQzx/w7owlNWLrgWCr6a97zaUUkNziV6XqOExQ84+yLm/pFPQXHgGCN2wDpiB6g3 KIRn+sNmeiO0U/f0kw4C3wcLbDpdjBeQ5wt+1L1YOcicz9kK4gJPaNnAELnGsYXln1vQU8123Fl acgsCxuqEKDuTOSJnkL+sVt7hxijpmF0v/wk5aD2S1jx6HfqLO4fFCr6/0hoxRqaxOpqBhF2Djm eopHOIq2Gv8TncBcIMbHCuBwhFqfxQn9uo/uTXkCLXQErCBOl2PPXzPmNtNSUJs/Kswl21+7ibP iY82D8T2b+MrxZt1mtXfzoGbNwVSG6TCn6kjeBdad9cJurADta90HpPkfMsZfv/ntVdgbOxMptW yGLKom9POBCARUMpQ0AjFYbl6jXVr4YK8g2HRK3pbCsFdIvrNP3QNvZL/jXt1pQGUomojK5caYg fQ4VUYlaWQnyquZabcOkcfdG9Q3bIJ9wIlgTzFaDzSK1N4ghToMcQ= X-Received: by 2002:a05:7300:a987:b0:2be:2645:af00 with SMTP id 5a478bee46e88-2bea5508c4cmr827278eec.32.1773368343165; Thu, 12 Mar 2026 19:19:03 -0700 (PDT) From: Lucas Amaral To: qemu-devel@nongnu.org Cc: qemu-arm@nongnu.org, agraf@csgraf.de, Lucas Amaral Subject: [PATCH v2 3/3] target/arm: wire ISV=0 emulation into HVF and WHPX Date: Thu, 12 Mar 2026 23:18:50 -0300 Message-ID: <20260313021850.42379-4-lucaaamaral@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260313021850.42379-1-lucaaamaral@gmail.com> References: <20260309214852.92545-1-lucaaamaral@gmail.com> <20260313021850.42379-1-lucaaamaral@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=2607:f8b0:4864:20::1330; envelope-from=lucaaamaral@gmail.com; helo=mail-dy1-x1330.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, FSL_HELO_BARE_IP_2=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=unavailable autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1773368392283154100 Content-Type: text/plain; charset="utf-8" Connect the ISV=3D0 emulation library to the HVF and WHPX backends. Each implements arm_emul_ops callbacks over CPUARMState and cpu_memory_rw_debug(). Replaces the assert(isv) with instruction fetch, decode, and emulation via arm_emul_insn(). Signed-off-by: Lucas Amaral --- target/arm/hvf/hvf.c | 94 ++++++++++++++++++++++++++++++++++++-- target/arm/whpx/whpx-all.c | 86 +++++++++++++++++++++++++++++++++- 2 files changed, 176 insertions(+), 4 deletions(-) diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c index d79469c..2a57b97 100644 --- a/target/arm/hvf/hvf.c +++ b/target/arm/hvf/hvf.c @@ -30,6 +30,7 @@ #include "qemu/main-loop.h" #include "system/cpus.h" #include "arm-powerctl.h" +#include "emulate/arm_emulate.h" #include "target/arm/cpu.h" #include "target/arm/internals.h" #include "target/arm/multiprocessing.h" @@ -797,6 +798,59 @@ static uint64_t hvf_get_reg(CPUState *cpu, int rt) return val; } =20 +/* + * arm_emul_ops callbacks for HVF + * + * State must already be synchronized (cpu_synchronize_state) before + * calling arm_emul_insn(). Reads/writes env->xregs[] directly to + * correctly handle register 31 as SP and avoid redundant HVF API calls. + */ + +static uint64_t hvf_emul_read_gpr(CPUState *cpu, int reg) +{ + return ARM_CPU(cpu)->env.xregs[reg]; +} + +static void hvf_emul_write_gpr(CPUState *cpu, int reg, uint64_t val) +{ + ARM_CPU(cpu)->env.xregs[reg] =3D val; + cpu->vcpu_dirty =3D true; +} + +static void hvf_emul_read_fpreg(CPUState *cpu, int reg, void *buf, int siz= e) +{ + memcpy(buf, &ARM_CPU(cpu)->env.vfp.zregs[reg], size); +} + +static void hvf_emul_write_fpreg(CPUState *cpu, int reg, + const void *buf, int size) +{ + CPUARMState *env =3D &ARM_CPU(cpu)->env; + memset(&env->vfp.zregs[reg], 0, sizeof(env->vfp.zregs[reg])); + memcpy(&env->vfp.zregs[reg], buf, size); + cpu->vcpu_dirty =3D true; +} + +static int hvf_emul_read_mem(CPUState *cpu, uint64_t va, void *buf, int si= ze) +{ + return cpu_memory_rw_debug(cpu, va, buf, size, false); +} + +static int hvf_emul_write_mem(CPUState *cpu, uint64_t va, + const void *buf, int size) +{ + return cpu_memory_rw_debug(cpu, va, (void *)buf, size, true); +} + +static const struct arm_emul_ops hvf_arm_emul_ops =3D { + .read_gpr =3D hvf_emul_read_gpr, + .write_gpr =3D hvf_emul_write_gpr, + .read_fpreg =3D hvf_emul_read_fpreg, + .write_fpreg =3D hvf_emul_write_fpreg, + .read_mem =3D hvf_emul_read_mem, + .write_mem =3D hvf_emul_write_mem, +}; + static void clamp_id_aa64mmfr0_parange_to_ipa_size(ARMISARegisters *isar) { uint32_t ipa_size =3D chosen_ipa_bit_size ? @@ -1871,10 +1925,44 @@ static int hvf_handle_exception(CPUState *cpu, hv_v= cpu_exit_exception_t *excp) assert(!s1ptw); =20 /* - * TODO: ISV will be 0 for SIMD or SVE accesses. - * Inject the exception into the guest. + * ISV=3D0: syndrome doesn't carry access size/register info. + * Fetch and emulate via target/arm/emulate/. + * Unhandled instructions log an error and advance PC. */ - assert(isv); + if (!isv) { + ARMCPU *arm_cpu =3D ARM_CPU(cpu); + CPUARMState *env =3D &arm_cpu->env; + uint32_t insn; + ArmEmulResult r; + + cpu_synchronize_state(cpu); + + if (cpu_memory_rw_debug(cpu, env->pc, + (uint8_t *)&insn, 4, false) !=3D 0) { + error_report("HVF: cannot read insn at pc=3D0x%" PRIx64, + (uint64_t)env->pc); + advance_pc =3D true; + break; + } + + r =3D arm_emul_insn(cpu, &hvf_arm_emul_ops, insn); + if (r =3D=3D ARM_EMUL_UNHANDLED) { + /* + * TODO: Inject data abort into guest instead of + * advancing PC. Requires setting ESR_EL1/FAR_EL1/ + * ELR_EL1/SPSR_EL1 and redirecting to VBAR_EL1. + */ + error_report("HVF: ISV=3D0 unhandled insn 0x%08x at " + "pc=3D0x%" PRIx64, insn, (uint64_t)env->pc); + } else if (r =3D=3D ARM_EMUL_ERR_MEM) { + error_report("HVF: ISV=3D0 memory error emulating " + "insn 0x%08x at pc=3D0x%" PRIx64, + insn, (uint64_t)env->pc); + } + + advance_pc =3D true; + break; + } =20 /* * Emulate MMIO. diff --git a/target/arm/whpx/whpx-all.c b/target/arm/whpx/whpx-all.c index 40ada2d..c57abef 100644 --- a/target/arm/whpx/whpx-all.c +++ b/target/arm/whpx/whpx-all.c @@ -37,6 +37,7 @@ #include "whpx_arm.h" #include "hw/arm/bsa.h" #include "arm-powerctl.h" +#include "emulate/arm_emulate.h" =20 #include #include @@ -377,6 +378,53 @@ static void whpx_set_gp_reg(CPUState *cpu, int rt, uin= t64_t val) whpx_set_reg(cpu, reg, reg_val); } =20 +/* arm_emul_ops callbacks for WHPX */ + +static uint64_t whpx_emul_read_gpr(CPUState *cpu, int reg) +{ + return ARM_CPU(cpu)->env.xregs[reg]; +} + +static void whpx_emul_write_gpr(CPUState *cpu, int reg, uint64_t val) +{ + ARM_CPU(cpu)->env.xregs[reg] =3D val; + cpu->vcpu_dirty =3D true; +} + +static void whpx_emul_read_fpreg(CPUState *cpu, int reg, void *buf, int si= ze) +{ + memcpy(buf, &ARM_CPU(cpu)->env.vfp.zregs[reg], size); +} + +static void whpx_emul_write_fpreg(CPUState *cpu, int reg, + const void *buf, int size) +{ + CPUARMState *env =3D &ARM_CPU(cpu)->env; + memset(&env->vfp.zregs[reg], 0, sizeof(env->vfp.zregs[reg])); + memcpy(&env->vfp.zregs[reg], buf, size); + cpu->vcpu_dirty =3D true; +} + +static int whpx_emul_read_mem(CPUState *cpu, uint64_t va, void *buf, int s= ize) +{ + return cpu_memory_rw_debug(cpu, va, buf, size, false); +} + +static int whpx_emul_write_mem(CPUState *cpu, uint64_t va, + const void *buf, int size) +{ + return cpu_memory_rw_debug(cpu, va, (void *)buf, size, true); +} + +static const struct arm_emul_ops whpx_arm_emul_ops =3D { + .read_gpr =3D whpx_emul_read_gpr, + .write_gpr =3D whpx_emul_write_gpr, + .read_fpreg =3D whpx_emul_read_fpreg, + .write_fpreg =3D whpx_emul_write_fpreg, + .read_mem =3D whpx_emul_read_mem, + .write_mem =3D whpx_emul_write_mem, +}; + static int whpx_handle_mmio(CPUState *cpu, WHV_MEMORY_ACCESS_CONTEXT *ctx) { uint64_t syndrome =3D ctx->Syndrome; @@ -391,7 +439,43 @@ static int whpx_handle_mmio(CPUState *cpu, WHV_MEMORY_= ACCESS_CONTEXT *ctx) uint64_t val =3D 0; =20 assert(!cm); - assert(isv); + + /* + * ISV=3D0: syndrome doesn't carry access size/register info. + * Fetch and decode the faulting instruction via the emulation library. + */ + if (!isv) { + ARMCPU *arm_cpu =3D ARM_CPU(cpu); + CPUARMState *env =3D &arm_cpu->env; + uint32_t insn; + ArmEmulResult r; + + cpu_synchronize_state(cpu); + + if (cpu_memory_rw_debug(cpu, env->pc, + (uint8_t *)&insn, 4, false) !=3D 0) { + error_report("WHPX: cannot read insn at pc=3D0x%" PRIx64, + (uint64_t)env->pc); + return 0; + } + + r =3D arm_emul_insn(cpu, &whpx_arm_emul_ops, insn); + if (r =3D=3D ARM_EMUL_UNHANDLED) { + /* + * TODO: Inject data abort into guest instead of + * advancing PC. Requires setting ESR_EL1/FAR_EL1/ + * ELR_EL1/SPSR_EL1 and redirecting to VBAR_EL1. + */ + error_report("WHPX: ISV=3D0 unhandled insn 0x%08x at " + "pc=3D0x%" PRIx64, insn, (uint64_t)env->pc); + } else if (r =3D=3D ARM_EMUL_ERR_MEM) { + error_report("WHPX: ISV=3D0 memory error emulating " + "insn 0x%08x at pc=3D0x%" PRIx64, + insn, (uint64_t)env->pc); + } + + return 0; + } =20 if (iswrite) { val =3D whpx_get_gp_reg(cpu, srt); --=20 2.52.0