From nobody Sat Sep 26 20:51:35 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; 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 Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1789329056347168.3904651901015; Sun, 13 Sep 2026 12:50:56 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x5qD1-0005MM-Aq; Sun, 13 Sep 2026 15:49:51 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x5oqL-00014j-8H; Sun, 13 Sep 2026 14:22:22 -0400 Received: from www675.your-server.de ([85.10.213.182]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x5oqE-0003bc-1w; Sun, 13 Sep 2026 14:22:20 -0400 Received: from sslproxy07.your-server.de ([78.47.199.104]) by www675.your-server.de with esmtpsa (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.96.2) (envelope-from ) id 1x5oq5-000LWi-1x; Sun, 13 Sep 2026 20:22:05 +0200 Received: from localhost ([127.0.0.1]) by sslproxy07.your-server.de with esmtpsa (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1x5oq5-000PM1-0n; Sun, 13 Sep 2026 20:22:05 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sayyadumar.com; s=default2511; h=Cc:To:In-Reply-To:References:Message-Id: Content-Transfer-Encoding:Content-Type:MIME-Version:Subject:Date:From:Sender: Reply-To:Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender :Resent-To:Resent-Cc:Resent-Message-ID; bh=wc5lQlvp8CNVHVe7111Dz46QS8I5PO6Y8V1dN6cMXUs=; b=p34VGaQdx0bFJPXNyVwodNsepJ CRLgA3aze7g6c0bgbuVVCkTmZIpsXAxvEAFfRJurFzz0vUv2ztYI5rZ5xympO1wADR8HYzN69ey2W ZoJn207o3xX0+FPxGswR8YmwXP8IDJipaR4VVhccXH77+jFxS7+NG+ly/ijLeb5NskSyDEQa5eTTI SnNJCMC55OAtZwvM7ukaJeV/U7b9xeV9ukDnPEkDkMeSpUrcvSDwZAwKoUit4T2a4HTegD+g3DRxE ieXHxpowDlcCVQT52nhufdICtEC3ZpVcEThvMUhzyI+YaEUIm6n2L1mY1xXbQThhi/00xsmsA/0Xi kjsNoYXw==; From: Sayyad Mahammad Umar Date: Sun, 13 Sep 2026 20:21:05 +0200 Subject: [PATCH 1/2] target/rx: add RXv2 and RXv3 CPU support MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260913-umar-rx-series-v1-1-5a5841a5d1e9@sayyadumar.com> References: <20260913-umar-rx-series-v1-0-5a5841a5d1e9@sayyadumar.com> In-Reply-To: <20260913-umar-rx-series-v1-0-5a5841a5d1e9@sayyadumar.com> To: qemu-devel@nongnu.org Cc: Yoshinori Sato , Alistair Francis , Peter Maydell , Jason Wang , qemu-arm@nongnu.org, Sayyad Mahammad Umar X-Mailer: b4 0.16.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1789323724; l=120104; i=contact@sayyadumar.com; s=qemu; h=from:subject:message-id; bh=w9y5rQ8CBRgGIz+2n80n3UwCJuUyZ+cD9JKA/YKB6OE=; b=1UoCwOZqTFt9ekKGIPKNL8/dOx+m6suJEJ4DF06OkDvL0OMdaRzKRrHxLw4ugoI5WL759xiUn E3syv2qA+FNAC92FmDfzoyYVOQNAffPC7Qerv6RkbCsstZ2rbCkEtSN X-Developer-Key: i=contact@sayyadumar.com; a=ed25519; pk=CsAjTLRCB7+pnzmj8902JER4gL8CTzNQFxlMZTl4wDk= X-Virus-Scanned: Clear (ClamAV 1.4.3/28122/Sun Sep 13 08:26:25 2026) 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=85.10.213.182; envelope-from=contact@sayyadumar.com; helo=www675.your-server.de X-Spam_score_int: -16 X-Spam_score: -1.7 X-Spam_bar: - X-Spam_report: (-1.7 / 5.0 requ) BAYES_00=-1.9, DKIM_INVALID=0.1, DKIM_SIGNED=0.1, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=no autolearn_force=no X-Spam_action: no action X-Mailman-Approved-At: Sun, 13 Sep 2026 15:49:41 -0400 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-ZM-MESSAGEID: 1789329057684158500 Add RX65N (RXv2) and RX72M (RXv3) CPU model definitions and select the appropriate ISA revision for each model. Initialize the architectural state and translator support required by the newer cores. Signed-off-by: Sayyad Mahammad Umar --- target/rx/cpu-param.h | 3 + target/rx/cpu-qom.h | 2 + target/rx/cpu.c | 57 ++- target/rx/cpu.h | 124 +++++ target/rx/disas.c | 383 ++++++++++++++- target/rx/gdbstub.c | 44 +- target/rx/helper.c | 56 +-- target/rx/helper.h | 22 + target/rx/insns.decode | 125 ++++- target/rx/meson.build | 6 +- target/rx/op_helper.c | 357 ++++++++++++++ target/rx/translate.c | 1225 ++++++++++++++++++++++++++++++++++++++------= ---- 12 files changed, 2093 insertions(+), 311 deletions(-) diff --git a/target/rx/cpu-param.h b/target/rx/cpu-param.h index 3806f41b2f..84934f3bca 100644 --- a/target/rx/cpu-param.h +++ b/target/rx/cpu-param.h @@ -21,6 +21,9 @@ =20 #define TARGET_PAGE_BITS 12 =20 +#define TARGET_PHYS_ADDR_SPACE_BITS 32 #define TARGET_VIRT_ADDR_SPACE_BITS 32 =20 +#define TARGET_INSN_START_EXTRA_WORDS 0 + #endif diff --git a/target/rx/cpu-qom.h b/target/rx/cpu-qom.h index ac2e5785ef..a0b5f35015 100644 --- a/target/rx/cpu-qom.h +++ b/target/rx/cpu-qom.h @@ -24,6 +24,8 @@ #define TYPE_RX_CPU "rx-cpu" =20 #define TYPE_RX62N_CPU RX_CPU_TYPE_NAME("rx62n") +#define TYPE_RX65N_CPU RX_CPU_TYPE_NAME("rx65n") +#define TYPE_RX72M_CPU RX_CPU_TYPE_NAME("rx72m") =20 OBJECT_DECLARE_CPU_TYPE(RXCPU, RXCPUClass, RX_CPU) =20 diff --git a/target/rx/cpu.c b/target/rx/cpu.c index 9b8473d71c..b7b6e69c72 100644 --- a/target/rx/cpu.c +++ b/target/rx/cpu.c @@ -99,11 +99,14 @@ static void rx_cpu_reset_hold(Object *obj, ResetType ty= pe) resetvec =3D rom_ptr(0xfffffffc, 4); if (resetvec) { /* In the case of kernel, it is ignored because it is not set. */ - env->pc =3D ldl_le_p(resetvec); + env->pc =3D ldl_p(resetvec); } rx_cpu_unpack_psw(env, 0, 1); env->regs[0] =3D env->isp =3D env->usp =3D 0; + env->extb =3D 0xffffff80; env->fpsw =3D 0; + /* DECNT comes out of reset with EHM set; the rest of DPSW is zero. */ + env->dcr[RX_DCR_DECNT] =3D RX_DECNT_RESET; set_flush_to_zero(1, &env->fp_status); set_flush_inputs_to_zero(1, &env->fp_status); /* @@ -123,6 +126,17 @@ static void rx_cpu_reset_hold(Object *obj, ResetType t= ype) * be the correct setting. */ set_float_ftz_detection(float_ftz_before_rounding, &env->fp_status); + + /* + * The DPFPU keeps its own softfloat state: DPSW carries a separate + * rounding mode (DRM) and denormal setting (DDN) from the FPSW. The + * NaN and denormal conventions match the single-precision unit. + */ + set_flush_to_zero(1, &env->dp_status); + set_flush_inputs_to_zero(1, &env->dp_status); + set_float_2nan_prop_rule(float_2nan_prop_x87, &env->dp_status); + set_float_default_nan_pattern(0b01000000, &env->dp_status); + set_float_ftz_detection(float_ftz_before_rounding, &env->dp_status); } =20 static ObjectClass *rx_cpu_class_by_name(const char *cpu_model) @@ -256,6 +270,32 @@ static void rx_cpu_class_init(ObjectClass *klass, cons= t void *data) =20 cc->gdb_core_xml_file =3D "rx-core.xml"; cc->tcg_ops =3D &rx_tcg_ops; + + /* + * Base of the hierarchy: the abstract type is never instantiated, so + * this only provides a conservative default for models that forget to + * declare their revision. + */ + rcc->isa_version =3D RX_ISA_V1; +} + +static void rx62n_cpu_class_init(ObjectClass *klass, const void *data) +{ + RX_CPU_CLASS(klass)->isa_version =3D RX_ISA_V1; +} + +static void rx65n_cpu_class_init(ObjectClass *klass, const void *data) +{ + RX_CPU_CLASS(klass)->isa_version =3D RX_ISA_V2; +} + +static void rx72m_cpu_class_init(ObjectClass *klass, const void *data) +{ + RXCPUClass *rcc =3D RX_CPU_CLASS(klass); + + rcc->isa_version =3D RX_ISA_V3; + /* RX72M provides 16 save register banks, selected by bank numbers 0-1= 5. */ + rcc->num_save_banks =3D 16; } =20 static const TypeInfo rx_cpu_info =3D { @@ -272,12 +312,27 @@ static const TypeInfo rx_cpu_info =3D { static const TypeInfo rx62n_rx_cpu_info =3D { .name =3D TYPE_RX62N_CPU, .parent =3D TYPE_RX_CPU, + .class_init =3D rx62n_cpu_class_init, +}; + +static const TypeInfo rx65n_rx_cpu_info =3D { + .name =3D TYPE_RX65N_CPU, + .parent =3D TYPE_RX_CPU, + .class_init =3D rx65n_cpu_class_init, +}; + +static const TypeInfo rx72m_rx_cpu_info =3D { + .name =3D TYPE_RX72M_CPU, + .parent =3D TYPE_RX_CPU, + .class_init =3D rx72m_cpu_class_init, }; =20 static void rx_cpu_register_types(void) { type_register_static(&rx_cpu_info); type_register_static(&rx62n_rx_cpu_info); + type_register_static(&rx65n_rx_cpu_info); + type_register_static(&rx72m_rx_cpu_info); } =20 type_init(rx_cpu_register_types) diff --git a/target/rx/cpu.h b/target/rx/cpu.h index 64ba48a732..cbffcf9c0a 100644 --- a/target/rx/cpu.h +++ b/target/rx/cpu.h @@ -24,6 +24,7 @@ #include "cpu-qom.h" =20 #include "exec/cpu-common.h" +#include "exec/cpu-defs.h" #include "exec/cpu-interrupt.h" #include "qemu/cpu-float.h" =20 @@ -67,10 +68,112 @@ FIELD(FPSW, FX, 30, 1) FIELD(FPSW, FLAGS, 26, 4) FIELD(FPSW, FS, 31, 1) =20 +/* DPSW define. The DPFPU status word mirrors the FPSW layout exactly. */ +REG32(DPSW, 0) +FIELD(DPSW, DRM, 0, 2) +FIELD(DPSW, DCV, 2, 1) +FIELD(DPSW, DCO, 3, 1) +FIELD(DPSW, DCZ, 4, 1) +FIELD(DPSW, DCU, 5, 1) +FIELD(DPSW, DCX, 6, 1) +FIELD(DPSW, DCE, 7, 1) +FIELD(DPSW, CAUSE, 2, 6) +FIELD(DPSW, DDN, 8, 1) +FIELD(DPSW, DEV, 10, 1) +FIELD(DPSW, DEO, 11, 1) +FIELD(DPSW, DEZ, 12, 1) +FIELD(DPSW, DEU, 13, 1) +FIELD(DPSW, DEX, 14, 1) +FIELD(DPSW, ENABLE, 10, 5) +FIELD(DPSW, DFV, 26, 1) +FIELD(DPSW, DFO, 27, 1) +FIELD(DPSW, DFZ, 28, 1) +FIELD(DPSW, DFU, 29, 1) +FIELD(DPSW, DFX, 30, 1) +/* DFS is the OR of DFV, DFO, DFZ and DFU only; DFX is not included. */ +FIELD(DPSW, FLAGS, 26, 4) +FIELD(DPSW, DFS, 31, 1) + +/* + * Writable bits of DPSW: DRM, the DC* causes, DDN, the DE* enables and the + * DF* flags. The reserved bits and the read-only DFS summary are masked o= ut. + */ +#define RX_DPSW_WRITE_MASK 0x7c007dff +/* The DC* cause bits, which MVTDC can only clear, never set. */ +#define RX_DPSW_CAUSE_MASK 0x000000fc + +/* DECNT define: DP exception information preservation. */ +REG32(DECNT, 0) +FIELD(DECNT, EHM, 0, 1) +FIELD(DECNT, EHS, 16, 1) +#define RX_DECNT_RESET 0x00000001 /* EHM =3D 1 out of reset */ + +/* DCMR holds only the RES bit; everything else reads as 0. */ +#define RX_DCMR_WRITE_MASK 0x00000001 + enum { NUM_REGS =3D 16, + /* RXv3 DPFPU: DR0-DR15 are dedicated 64-bit registers, not GPR pairs.= */ + NUM_DREGS =3D 16, + /* RXv3 DPFPU control registers: DPSW, DCMR, DECNT, DEPC. */ + NUM_DCREGS =3D 4, + /* + * RXv3 register bank save function. The banks are internal CPU state + * reachable only through the SAVE and RSTR instructions, so no memory + * layout is involved. How many banks exist is implementation defined, + * so the per-model count lives in RXCPUClass::num_save_banks and this + * is only the storage ceiling. RX72M/RX72N/RX72T provide 16, selected + * by bank numbers 0-15. + */ + RX_MAX_SAVE_BANKS =3D 16, +}; + +/* RXv3 DPFPU control register numbers (MVFDC/MVTDC/DPUSHM.L/DPOPM.L). */ +enum { + RX_DCR_DPSW =3D 0, + RX_DCR_DCMR =3D 1, + RX_DCR_DECNT =3D 2, + RX_DCR_DEPC =3D 3, }; =20 +/* + * DCMR.RES, bit 0, holds the result of the last DCMP; MVFDR copies it into + * the PSW Z flag. + */ +#define RX_DCMR_RES_BIT 0 + +/* + * DCMP condition field. The four documented mnemonics (un, eq, lt, le) are + * a three bit mask of the relations that make DCMR.RES true, which is why + * le =3D=3D lt | eq =3D=3D 6. + */ +#define RX_DCMP_UN 1 +#define RX_DCMP_EQ 2 +#define RX_DCMP_LT 4 + +/* + * Instruction set architecture revision implemented by a CPU model. + * Later revisions are supersets of earlier ones, so instruction gating is= a + * simple >=3D comparison. + */ +typedef enum RXISAVersion { + RX_ISA_V1 =3D 1, + RX_ISA_V2 =3D 2, + RX_ISA_V3 =3D 3, +} RXISAVersion; + +/* + * One save register bank, written by SAVE and read back by RSTR. A bank + * holds R1-R15 (R0/SP is excluded), the USP, the FPSW and the accumulator= s. + * QEMU models a single accumulator, so only ACC0 is covered here. + */ +typedef struct RXSaveBank { + uint32_t regs[NUM_REGS]; /* [0] unused: R0 is not banked */ + uint32_t usp; + uint32_t fpsw; + uint64_t acc; +} RXSaveBank; + typedef struct CPUArchState { /* CPU registers */ uint32_t regs[NUM_REGS]; /* general registers */ @@ -88,9 +191,19 @@ typedef struct CPUArchState { uint32_t usp; /* vector base register */ uint32_t pc; /* program counter */ uint32_t intb; /* interrupt vector */ + uint32_t extb; /* exception vector table base */ uint32_t fintv; uint32_t fpsw; uint64_t acc; + uint64_t acc1; + uint32_t acc_guard[2]; /* Sign-extended 8-bit RXv2 guard fields. */ + + /* RXv3 double-precision FPU (DPFPU) */ + uint64_t dr[NUM_DREGS]; /* DR0-DR15 */ + uint32_t dcr[NUM_DCREGS]; /* DPSW, DCMR, DECNT, DEPC */ + + /* RXv3 register bank save function (SAVE/RSTR) */ + RXSaveBank bank[RX_MAX_SAVE_BANKS]; =20 /* Fields up to this point are cleared by a CPU reset */ struct {} end_reset_fields; @@ -102,6 +215,7 @@ typedef struct CPUArchState { uint32_t ack_irq; /* execute irq */ uint32_t ack_ipl; /* execute ipl */ float_status fp_status; + float_status dp_status; qemu_irq ack; /* Interrupt acknowledge */ } CPURXState; =20 @@ -129,6 +243,16 @@ struct RXCPUClass { =20 DeviceRealize parent_realize; ResettablePhases parent_phases; + + /* Instruction set revision this model implements. */ + RXISAVersion isa_version; + + /* + * Number of save register banks reachable by SAVE/RSTR, or 0 on models + * without the register bank save function. Must not exceed + * RX_MAX_SAVE_BANKS. + */ + uint32_t num_save_banks; }; =20 #define CPU_RESOLVING_TYPE TYPE_RX_CPU diff --git a/target/rx/disas.c b/target/rx/disas.c index 0eb2ee6f45..0ac48b019e 100644 --- a/target/rx/disas.c +++ b/target/rx/disas.c @@ -19,7 +19,6 @@ #include "qemu/osdep.h" #include "disas/dis-asm.h" #include "qemu/bitops.h" -#include "monitor/hmp.h" #include "cpu.h" =20 typedef struct DisasContext { @@ -133,6 +132,10 @@ static const char cond[][4] =3D { "eq", "ne", "c", "nc", "gtu", "leu", "pz", "n", "ge", "lt", "gt", "le", "o", "no", "ra", "f" }; +static const char dcmp_cond[8][3] =3D { + "", "un", "eq", "", "lt", "", "le", "" +}; + static const char psw[] =3D { 'c', 'z', 's', 'o', 0, 0, 0, 0, 'i', 'u', 0, 0, 0, 0, 0, 0, @@ -174,7 +177,7 @@ static void prt_ldmi(DisasContext *ctx, const char *ins= n, int ld, int mi, int rs, int rd) { static const char sizes[][4] =3D {".b", ".w", ".l", ".uw", ".ub"}; - char dsp[8]; + char dsp[12]; =20 if (ld < 3) { rx_index_addr(ctx, dsp, ld, mi); @@ -375,7 +378,7 @@ static bool trans_PUSH_r(DisasContext *ctx, arg_PUSH_r = *a) /* push dsp[rs] */ static bool trans_PUSH_m(DisasContext *ctx, arg_PUSH_m *a) { - char dsp[8]; + char dsp[12]; =20 rx_index_addr(ctx, dsp, a->ld, a->sz); prt("push\t%s[r%d]", dsp, a->rs); @@ -554,7 +557,7 @@ static bool trans_ADC_rr(DisasContext *ctx, arg_ADC_rr = *a) /* adc dsp[rs], rd */ static bool trans_ADC_mr(DisasContext *ctx, arg_ADC_mr *a) { - char dsp[8]; + char dsp[12]; =20 rx_index_addr(ctx, dsp, a->ld, 2); prt("adc\t%s[r%d], r%d", dsp, a->rs, a->rd); @@ -1074,6 +1077,42 @@ static bool trans_MVTACLO(DisasContext *ctx, arg_MVT= ACLO *a) return true; } =20 +static bool trans_MVTACHI_A1(DisasContext *ctx, arg_MVTACHI_A1 *a) +{ + prt("mvtachi\tr%d, a%d", a->r, 1); + return true; +} + +static bool trans_MVTACLO_A1(DisasContext *ctx, arg_MVTACLO_A1 *a) +{ + prt("mvtaclo\tr%d, a%d", a->r, 1); + return true; +} + +static bool trans_MVTACGU(DisasContext *ctx, arg_MVTACGU *a) +{ + prt("mvtacgu\tr%d, a%d", a->r, a->a); + return true; +} + +static bool trans_MVFACHI_A1(DisasContext *ctx, arg_MVFACHI_A1 *a) +{ + prt("mvfachi\t#0, a%d, r%d", 1, a->r); + return true; +} + +static bool trans_MVFACLO(DisasContext *ctx, arg_MVFACLO *a) +{ + prt("mvfaclo\t#0, a%d, r%d", a->a, a->r); + return true; +} + +static bool trans_MVFACGU(DisasContext *ctx, arg_MVFACGU *a) +{ + prt("mvfacgu\t#0, a%d, r%d", a->a, a->r); + return true; +} + /* racw #imm */ static bool trans_RACW(DisasContext *ctx, arg_RACW *a) { @@ -1148,6 +1187,14 @@ static bool trans_FTOI(DisasContext *ctx, arg_FTOI *= a) return true; } =20 +/* ftou dsp[rs], rd */ +/* ftou rs, rd */ +static bool trans_FTOU(DisasContext *ctx, arg_FTOU *a) +{ + prt_ldmi(ctx, "ftou", a->ld, RX_IM_LONG, a->rs, a->rd); + return true; +} + /* fmul #imm, rd */ static bool trans_FMUL_ir(DisasContext *ctx, arg_FMUL_ir *a) { @@ -1194,9 +1241,329 @@ static bool trans_ITOF(DisasContext *ctx, arg_ITOF = *a) return true; } =20 +/* utof dsp[rs], rd */ +static bool trans_UTOF(DisasContext *ctx, arg_UTOF *a) +{ + prt_ldmi(ctx, "utof", a->ld, a->mi, a->rs, a->rd); + return true; +} + +/* RXv2 DPFPU disassembly */ +static bool trans_DADD(DisasContext *ctx, arg_DADD *a) +{ + prt("dadd\tdr%d, dr%d, dr%d", a->drs1, a->drs2, a->drd); + return true; +} + +static bool trans_DSUB(DisasContext *ctx, arg_DSUB *a) +{ + prt("dsub\tdr%d, dr%d, dr%d", a->drs1, a->drs2, a->drd); + return true; +} + +static bool trans_DMUL(DisasContext *ctx, arg_DMUL *a) +{ + prt("dmul\tdr%d, dr%d, dr%d", a->drs1, a->drs2, a->drd); + return true; +} + +static bool trans_DDIV(DisasContext *ctx, arg_DDIV *a) +{ + prt("ddiv\tdr%d, dr%d, dr%d", a->drs1, a->drs2, a->drd); + return true; +} + +static bool trans_DCMP(DisasContext *ctx, arg_DCMP *a) +{ + prt("dcmp%s\tdr%d, dr%d", dcmp_cond[a->cd & 7], a->drs1, a->drs2); + return true; +} + +static bool trans_DMOV(DisasContext *ctx, arg_DMOV *a) +{ + prt("dmov\tdr%d, dr%d", a->drs, a->drd); + return true; +} + +static bool trans_DABS(DisasContext *ctx, arg_DABS *a) +{ + prt("dabs\tdr%d, dr%d", a->drs, a->drd); + return true; +} + +static bool trans_DNEG(DisasContext *ctx, arg_DNEG *a) +{ + prt("dneg\tdr%d, dr%d", a->drs, a->drd); + return true; +} + +static bool trans_DSQRT(DisasContext *ctx, arg_DSQRT *a) +{ + prt("dsqrt\tdr%d, dr%d", a->drs, a->drd); + return true; +} + +static bool trans_DROUND(DisasContext *ctx, arg_DROUND *a) +{ + prt("dround\tdr%d, dr%d", a->drs, a->drd); + return true; +} + +static bool trans_DTOI(DisasContext *ctx, arg_DTOI *a) +{ + prt("dtoi\tdr%d, dr%d", a->drs, a->drd); + return true; +} + +static bool trans_DTOU(DisasContext *ctx, arg_DTOU *a) +{ + prt("dtou\tdr%d, dr%d", a->drs, a->drd); + return true; +} + +static bool trans_DTOF(DisasContext *ctx, arg_DTOF *a) +{ + prt("dtof\tdr%d, dr%d", a->drs, a->drd); + return true; +} + +static bool trans_ITOD(DisasContext *ctx, arg_ITOD *a) +{ + prt("itod\tr%d, dr%d", a->rs, a->drd); + return true; +} + +static bool trans_UTOD(DisasContext *ctx, arg_UTOD *a) +{ + prt("utod\tr%d, dr%d", a->rs, a->drd); + return true; +} + +static bool trans_FTOD(DisasContext *ctx, arg_FTOD *a) +{ + prt("ftod\tr%d, dr%d", a->rs, a->drd); + return true; +} + +static bool trans_DPUSHM(DisasContext *ctx, arg_DPUSHM *a) +{ + prt("dpushm.d\tdr%d-dr%d", a->drd, a->drd + a->rnum); + return true; +} + +static bool trans_DPOPM(DisasContext *ctx, arg_DPOPM *a) +{ + prt("dpopm.d\tdr%d-dr%d", a->drd, a->drd + a->rnum); + return true; +} + +/* RXv3 disassembly: remaining DPFPU, bit field and register bank ops */ +static const char dcrname[][6] =3D { "dpsw", "dcmr", "decnt", "depc" }; + +static bool trans_DPUSHM_l(DisasContext *ctx, arg_DPUSHM_l *a) +{ + prt("dpushm.l\t%s-%s", dcrname[a->dcrd & 3], + dcrname[(a->dcrd + a->rnum) & 3]); + return true; +} + +static bool trans_DPOPM_l(DisasContext *ctx, arg_DPOPM_l *a) +{ + prt("dpopm.l\t%s-%s", dcrname[a->dcrd & 3], + dcrname[(a->dcrd + a->rnum) & 3]); + return true; +} + +static bool trans_DMOV_rdrl(DisasContext *ctx, arg_DMOV_rdrl *a) +{ + prt("dmov.l\tr%d, drl%d", a->rs, a->drd); + return true; +} + +static bool trans_DMOV_rdrh(DisasContext *ctx, arg_DMOV_rdrh *a) +{ + prt("dmov.l\tr%d, drh%d", a->rs, a->drd); + return true; +} + +static bool trans_DMOVD_rdrh(DisasContext *ctx, arg_DMOVD_rdrh *a) +{ + prt("dmov.d\tr%d, drh%d", a->rs, a->drd); + return true; +} + +static bool trans_DMOV_drlr(DisasContext *ctx, arg_DMOV_drlr *a) +{ + prt("dmov.l\tdrl%d, r%d", a->drs, a->rd); + return true; +} + +static bool trans_DMOV_drhr(DisasContext *ctx, arg_DMOV_drhr *a) +{ + prt("dmov.l\tdrh%d, r%d", a->drs, a->rd); + return true; +} + +static bool trans_DMOVL_irl(DisasContext *ctx, arg_DMOVL_irl *a) +{ + uint32_t imm =3D li(ctx, 0); + + prt("dmov.l\t#0x%08x, drl%d", imm, a->drd); + return true; +} + +static bool trans_DMOVL_irh(DisasContext *ctx, arg_DMOVL_irh *a) +{ + uint32_t imm =3D li(ctx, 0); + + prt("dmov.l\t#0x%08x, drh%d", imm, a->drd); + return true; +} + +static bool trans_DMOVD_irh(DisasContext *ctx, arg_DMOVD_irh *a) +{ + uint32_t imm =3D li(ctx, 0); + + prt("dmov.d\t#0x%08x, drh%d", imm, a->drd); + return true; +} + +/* Fetch n more instruction bytes into the dump buffer and return them. */ +static uint32_t disas_fetch(DisasContext *ctx, int n) +{ + uint32_t addr =3D ctx->addr; + uintptr_t len =3D ctx->len; + uint32_t val =3D 0; + int i; + + g_assert(len + n <=3D ARRAY_SIZE(ctx->bytes)); + ctx->addr +=3D n; + ctx->len +=3D n; + ctx->dis->read_memory_func(addr, ctx->bytes + len, n, ctx->dis); + for (i =3D n - 1; i >=3D 0; i--) { + val =3D (val << 8) | ctx->bytes[len + i]; + } + return val; +} + +/* + * DMOV.D displacements are scaled by 4 (see the note in translate.c), whi= ch + * rx_index_addr() cannot express, so decode them here. + */ +static void dmov_index_addr(DisasContext *ctx, char out[12], int ld) +{ + if (ld =3D=3D 0) { + out[0] =3D '\0'; + return; + } + snprintf(out, 12, "%u", disas_fetch(ctx, ld) << 2); +} + +/* The DR operand of the DMOV.D memory forms sits in a trailing post-byte.= */ +static int dmov_postbyte(DisasContext *ctx) +{ + return disas_fetch(ctx, 1) >> 4; +} + +static bool trans_DMOV_mst(DisasContext *ctx, arg_DMOV_mst *a) +{ + char dsp[12]; + int dr; + + dmov_index_addr(ctx, dsp, a->ld); + dr =3D dmov_postbyte(ctx); + prt("dmov.d\tdr%d, %s[r%d]", dr, dsp, a->rd); + return true; +} + +static bool trans_DMOV_mld(DisasContext *ctx, arg_DMOV_mld *a) +{ + char dsp[12]; + int dr; + + dmov_index_addr(ctx, dsp, a->ld); + dr =3D dmov_postbyte(ctx); + prt("dmov.d\t%s[r%d], dr%d", dsp, a->rs, dr); + return true; +} + +static bool trans_MVFDC(DisasContext *ctx, arg_MVFDC *a) +{ + prt("mvfdc\t%s, r%d", dcrname[a->dcrs & 3], a->rd); + return true; +} + +static bool trans_MVTDC(DisasContext *ctx, arg_MVTDC *a) +{ + prt("mvtdc\tr%d, %s", a->rs, dcrname[a->dcrd & 3]); + return true; +} + +static bool trans_MVFDR(DisasContext *ctx, arg_MVFDR *a) +{ + prt("mvfdr"); + return true; +} + +static void prt_bfmov(DisasContext *ctx, const char *name, int rs, int rd) +{ + unsigned bf =3D disas_fetch(ctx, 2); + unsigned dlsb =3D (bf >> 5) & 0x1f; + unsigned msb =3D (bf >> 10) & 0x1f; + int delta =3D bf & 0x1f; + + if (delta >=3D 0x10) { + delta -=3D 0x20; + } + prt("%s\t#%d, #%d, #%d, r%d, r%d", name, + (int)dlsb - delta, dlsb, (int)msb - (int)dlsb, rs, rd); +} + +static bool trans_BFMOV(DisasContext *ctx, arg_BFMOV *a) +{ + prt_bfmov(ctx, "bfmov", a->rs, a->rd); + return true; +} + +static bool trans_BFMOVZ(DisasContext *ctx, arg_BFMOVZ *a) +{ + prt_bfmov(ctx, "bfmovz", a->rs, a->rd); + return true; +} + +static bool trans_SAVE_r(DisasContext *ctx, arg_SAVE_r *a) +{ + prt("save\tr%d", a->rs); + return true; +} + +static bool trans_SAVE_i(DisasContext *ctx, arg_SAVE_i *a) +{ + prt("save\t#%d", a->imm); + return true; +} + +static bool trans_RSTR_r(DisasContext *ctx, arg_RSTR_r *a) +{ + prt("rstr\tr%d", a->rs); + return true; +} + +static bool trans_RSTR_i(DisasContext *ctx, arg_RSTR_i *a) +{ + prt("rstr\t#%d", a->imm); + return true; +} + +static bool trans_XOR_rrr(DisasContext *ctx, arg_XOR_rrr *a) +{ + prt("xor\tr%d, r%d, r%d", a->rs, a->rs2, a->rd); + return true; +} + #define BOP_IM(name, reg) \ do { \ - char dsp[8]; \ + char dsp[12]; \ rx_index_addr(ctx, dsp, a->ld, RX_MEMORY_BYTE); \ prt("b%s\t#%d, %s[r%d]", #name, a->imm, dsp, reg); \ return true; \ @@ -1204,7 +1571,7 @@ static bool trans_ITOF(DisasContext *ctx, arg_ITOF *a) =20 #define BOP_RM(name) \ do { \ - char dsp[8]; \ + char dsp[12]; \ rx_index_addr(ctx, dsp, a->ld, RX_MEMORY_BYTE); \ prt("b%s\tr%d, %s[r%d]", #name, a->rd, dsp, a->rs); \ return true; \ @@ -1317,7 +1684,7 @@ static bool trans_BNOT_ir(DisasContext *ctx, arg_BNOT= _ir *a) /* bmcond #imm, dsp[rd] */ static bool trans_BMCnd_im(DisasContext *ctx, arg_BMCnd_im *a) { - char dsp[8]; + char dsp[12]; =20 rx_index_addr(ctx, dsp, a->ld, RX_MEMORY_BYTE); prt("bm%s\t#%d, %s[r%d]", cond[a->cd], a->imm, dsp, a->rd); @@ -1413,7 +1780,7 @@ static bool trans_WAIT(DisasContext *ctx, arg_WAIT *a) static bool trans_SCCnd(DisasContext *ctx, arg_SCCnd *a) { if (a->ld < 3) { - char dsp[8]; + char dsp[12]; rx_index_addr(ctx, dsp, a->sz, a->ld); prt("sc%s.%c\t%s[r%d]", cond[a->cd], size[a->sz], dsp, a->rd); } else { diff --git a/target/rx/gdbstub.c b/target/rx/gdbstub.c index 8be5141f5b..f222bf003b 100644 --- a/target/rx/gdbstub.c +++ b/target/rx/gdbstub.c @@ -25,25 +25,25 @@ int rx_cpu_gdb_read_register(CPUState *cs, GByteArray *= mem_buf, int n) =20 switch (n) { case 0 ... 15: - return gdb_get_reg32(mem_buf, env->regs[n]); + return gdb_get_regl(mem_buf, env->regs[n]); case 16: - return gdb_get_reg32(mem_buf, (env->psw_u) ? env->regs[0] : env->u= sp); + return gdb_get_regl(mem_buf, (env->psw_u) ? env->regs[0] : env->us= p); case 17: - return gdb_get_reg32(mem_buf, (!env->psw_u) ? env->regs[0] : env->= isp); + return gdb_get_regl(mem_buf, (!env->psw_u) ? env->regs[0] : env->i= sp); case 18: - return gdb_get_reg32(mem_buf, rx_cpu_pack_psw(env)); + return gdb_get_regl(mem_buf, rx_cpu_pack_psw(env)); case 19: - return gdb_get_reg32(mem_buf, env->pc); + return gdb_get_regl(mem_buf, env->pc); case 20: - return gdb_get_reg32(mem_buf, env->intb); + return gdb_get_regl(mem_buf, env->intb); case 21: - return gdb_get_reg32(mem_buf, env->bpsw); + return gdb_get_regl(mem_buf, env->bpsw); case 22: - return gdb_get_reg32(mem_buf, env->bpc); + return gdb_get_regl(mem_buf, env->bpc); case 23: - return gdb_get_reg32(mem_buf, env->fintv); + return gdb_get_regl(mem_buf, env->fintv); case 24: - return gdb_get_reg32(mem_buf, env->fpsw); + return gdb_get_regl(mem_buf, env->fpsw); case 25: return 0; } @@ -56,7 +56,7 @@ int rx_cpu_gdb_write_register(CPUState *cs, uint8_t *mem_= buf, int n) uint32_t psw; switch (n) { case 0 ... 15: - env->regs[n] =3D ldl_le_p(mem_buf); + env->regs[n] =3D ldl_p(mem_buf); if (n =3D=3D 0) { if (env->psw_u) { env->usp =3D env->regs[0]; @@ -66,38 +66,38 @@ int rx_cpu_gdb_write_register(CPUState *cs, uint8_t *me= m_buf, int n) } break; case 16: - env->usp =3D ldl_le_p(mem_buf); + env->usp =3D ldl_p(mem_buf); if (env->psw_u) { - env->regs[0] =3D ldl_le_p(mem_buf); + env->regs[0] =3D ldl_p(mem_buf); } break; case 17: - env->isp =3D ldl_le_p(mem_buf); + env->isp =3D ldl_p(mem_buf); if (!env->psw_u) { - env->regs[0] =3D ldl_le_p(mem_buf); + env->regs[0] =3D ldl_p(mem_buf); } break; case 18: - psw =3D ldl_le_p(mem_buf); + psw =3D ldl_p(mem_buf); rx_cpu_unpack_psw(env, psw, 1); break; case 19: - env->pc =3D ldl_le_p(mem_buf); + env->pc =3D ldl_p(mem_buf); break; case 20: - env->intb =3D ldl_le_p(mem_buf); + env->intb =3D ldl_p(mem_buf); break; case 21: - env->bpsw =3D ldl_le_p(mem_buf); + env->bpsw =3D ldl_p(mem_buf); break; case 22: - env->bpc =3D ldl_le_p(mem_buf); + env->bpc =3D ldl_p(mem_buf); break; case 23: - env->fintv =3D ldl_le_p(mem_buf); + env->fintv =3D ldl_p(mem_buf); break; case 24: - env->fpsw =3D ldl_le_p(mem_buf); + env->fpsw =3D ldl_p(mem_buf); break; case 25: return 8; diff --git a/target/rx/helper.c b/target/rx/helper.c index 0f99279bba..2482b9fdd6 100644 --- a/target/rx/helper.c +++ b/target/rx/helper.c @@ -22,7 +22,6 @@ #include "exec/log.h" #include "accel/tcg/cpu-ldst.h" #include "hw/core/irq.h" -#include "qemu/plugin.h" =20 void rx_cpu_unpack_psw(CPURXState *env, uint32_t psw, int rte) { @@ -41,11 +40,13 @@ void rx_cpu_unpack_psw(CPURXState *env, uint32_t psw, i= nt rte) env->psw_c =3D FIELD_EX32(psw, PSW, C); } =20 +#define INT_FLAGS (CPU_INTERRUPT_HARD | CPU_INTERRUPT_FIR) void rx_cpu_do_interrupt(CPUState *cs) { CPURXState *env =3D cpu_env(cs); + bool fir =3D cpu_test_interrupt(cs, CPU_INTERRUPT_FIR); + bool hard =3D cpu_test_interrupt(cs, CPU_INTERRUPT_HARD); uint32_t save_psw; - uint64_t last_pc =3D env->pc; =20 env->in_sleep =3D 0; =20 @@ -57,26 +58,27 @@ void rx_cpu_do_interrupt(CPUState *cs) save_psw =3D rx_cpu_pack_psw(env); env->psw_pm =3D env->psw_i =3D env->psw_u =3D 0; =20 - if (cpu_test_interrupt(cs, CPU_INTERRUPT_FIR)) { - env->bpc =3D env->pc; - env->bpsw =3D save_psw; - env->pc =3D env->fintv; - env->psw_ipl =3D 15; - cpu_reset_interrupt(cs, CPU_INTERRUPT_FIR); - qemu_set_irq(env->ack, env->ack_irq); - qemu_plugin_vcpu_interrupt_cb(cs, last_pc); - qemu_log_mask(CPU_LOG_INT, "fast interrupt raised\n"); - } else if (cpu_test_interrupt(cs, CPU_INTERRUPT_HARD)) { - env->isp -=3D 4; - cpu_stl_le_data(env, env->isp, save_psw); - env->isp -=3D 4; - cpu_stl_le_data(env, env->isp, env->pc); - env->pc =3D cpu_ldl_le_data(env, env->intb + env->ack_irq * 4); - env->psw_ipl =3D env->ack_ipl; - cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD); - qemu_set_irq(env->ack, env->ack_irq); - qemu_plugin_vcpu_interrupt_cb(cs, last_pc); - qemu_log_mask(CPU_LOG_INT, "interrupt 0x%02x raised\n", env->ack_i= rq); + if (fir || hard) { + if (fir) { + env->bpc =3D env->pc; + env->bpsw =3D save_psw; + env->pc =3D env->fintv; + env->psw_ipl =3D 15; + cpu_reset_interrupt(cs, CPU_INTERRUPT_FIR); + qemu_set_irq(env->ack, env->ack_irq); + qemu_log_mask(CPU_LOG_INT, "fast interrupt raised\n"); + } else { + env->isp -=3D 4; + cpu_stl_le_data(env, env->isp, save_psw); + env->isp -=3D 4; + cpu_stl_le_data(env, env->isp, env->pc); + env->pc =3D cpu_ldl_le_data(env, env->intb + env->ack_irq * 4); + env->psw_ipl =3D env->ack_ipl; + cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD); + qemu_set_irq(env->ack, env->ack_irq); + qemu_log_mask(CPU_LOG_INT, + "interrupt 0x%02x raised\n", env->ack_irq); + } } else { uint32_t vec =3D cs->exception_index; const char *expname =3D "unknown exception"; @@ -87,18 +89,10 @@ void rx_cpu_do_interrupt(CPUState *cs) cpu_stl_le_data(env, env->isp, env->pc); =20 if (vec < 0x100) { - env->pc =3D cpu_ldl_le_data(env, 0xffffff80 + vec * 4); + env->pc =3D cpu_ldl_le_data(env, env->extb + vec * 4); } else { env->pc =3D cpu_ldl_le_data(env, env->intb + (vec & 0xff) * 4); } - - if (vec =3D=3D 30) { - /* Non-maskable interrupt */ - qemu_plugin_vcpu_interrupt_cb(cs, last_pc); - } else { - qemu_plugin_vcpu_exception_cb(cs, last_pc); - } - switch (vec) { case 20: expname =3D "privilege violation"; diff --git a/target/rx/helper.h b/target/rx/helper.h index 8cc38b0cb7..92bd9b9ed3 100644 --- a/target/rx/helper.h +++ b/target/rx/helper.h @@ -10,8 +10,30 @@ DEF_HELPER_3(fmul, f32, env, f32, f32) DEF_HELPER_3(fdiv, f32, env, f32, f32) DEF_HELPER_3(fcmp, void, env, f32, f32) DEF_HELPER_2(ftoi, i32, env, f32) +DEF_HELPER_2(ftou, i32, env, f32) DEF_HELPER_2(round, i32, env, f32) DEF_HELPER_2(itof, f32, env, i32) +DEF_HELPER_2(utof, f32, env, i32) +/* RXv3 double-precision FPU (DPFPU) helpers */ +DEF_HELPER_3(dadd, f64, env, f64, f64) +DEF_HELPER_3(dsub, f64, env, f64, f64) +DEF_HELPER_3(dmul, f64, env, f64, f64) +DEF_HELPER_3(ddiv, f64, env, f64, f64) +DEF_HELPER_4(dcmp, void, env, i32, f64, f64) +DEF_HELPER_2(dabs, f64, env, f64) +DEF_HELPER_2(dneg, f64, env, f64) +DEF_HELPER_2(dsqrt, f64, env, f64) +DEF_HELPER_2(dround, f64, env, f64) +DEF_HELPER_2(dtoi, i32, env, f64) +DEF_HELPER_2(dtou, i32, env, f64) +DEF_HELPER_2(dtof, f32, env, f64) +DEF_HELPER_2(itod, f64, env, i32) +DEF_HELPER_2(utod, f64, env, i32) +DEF_HELPER_2(ftod, f64, env, f32) +DEF_HELPER_3(mvtdc, void, env, i32, i32) +/* RXv3 register bank save function */ +DEF_HELPER_2(save, void, env, i32) +DEF_HELPER_2(rstr, void, env, i32) DEF_HELPER_2(set_fpsw, void, env, i32) DEF_HELPER_2(racw, void, env, i32) DEF_HELPER_2(set_psw_rte, void, env, i32) diff --git a/target/rx/insns.decode b/target/rx/insns.decode index ca9334b37a..2dd908d6b2 100644 --- a/target/rx/insns.decode +++ b/target/rx/insns.decode @@ -29,6 +29,26 @@ &mi rs ld mi imm &mr rs ld mi rs2 &mcnd ld sz rd cd +# RXv3 DPFPU argument types +&drr drd drs +&drrr drd drs1 drs2 +&dcmpd drs1 drs2 cd +&idr rs drd +&dppm drd rnum +# RXv3 DPFPU: GPR/control-register/memory/immediate transfers +&rdrh rd drs +&drhr drd rs +&rdcr rd dcrs +&dcrr dcrd rs +&dcppm dcrd rnum +&dmovi drd imm +&dmovst rd ld +&dmovld rs ld +# RXv3 bit field move +&bfm rd rs +# RXv3 register bank save/restore +&svr rs +&svi imm ######## %b1_bdsp 24:3 !function=3Dbdsp_s =20 @@ -285,6 +305,10 @@ FSUB_mr 1111 1100 1000 00.. .... .... @b3_rd_ld_ul # FTOI dsp[rs], rd FTOI 1111 1100 1001 01.. .... .... @b3_rd_ld_ul =20 +# FTOU rs, rd (RXv2) +# FTOU dsp[rs], rd (RXv2) +FTOU 1111 1100 1010 01.. .... .... @b3_rd_ld_ul + # INT #uimm8 INT 0111 0101 0110 0000 imm:8 =20 @@ -294,6 +318,11 @@ ITOF 1111 1100 0100 01.. .... .... @b3_rd_ld_ub # ITOF dsp[rs], rd ITOF 0000 0110 ..10 00.. 0001 0001 .... .... @b4_rd_ldmi =20 +# UTOF rs, rd (RXv2) +UTOF 1111 1100 0101 01.. .... .... @b3_rd_ld_ub +# UTOF dsp[rs], rd (RXv2) +UTOF 0000 0110 ..10 00.. 0001 0101 .... .... @b4_rd_ldmi + # JMP rs JMP 0111 1111 0000 rs:4 &jreg # JSR rs @@ -347,7 +376,15 @@ MOV_ir 1111 1011 rd:4 .. 10 imm=3D%b2_li_2 # MOV. #imm, [rd] MOV_im 1111 1000 rd:4 .. sz:2 dsp=3D0 imm=3D%b2_li_2 # MOV. #imm, dsp8[rd] -MOV_im 1111 1001 rd:4 .. sz:2 dsp:8 imm=3D%b3_li_10 +# The RXv3 DMOV immediate forms reuse this opcode with rd=3D0 and li/sz=3D= 0011, +# an encoding MOV. never produces (sz=3D3 is not a valid transfer siz= e), +# so they have to be matched ahead of it. +{ + DMOVL_irl 1111 1001 0000 0011 drd:4 0000 &dmovi imm=3D0 + DMOVL_irh 1111 1001 0000 0011 drd:4 0010 &dmovi imm=3D0 + DMOVD_irh 1111 1001 0000 0011 drd:4 0011 &dmovi imm=3D0 + MOV_im 1111 1001 rd:4 .. sz:2 dsp:8 imm=3D%b3_li_10 +} # MOV. #imm, dsp16[rd] MOV_im 1111 1010 rd:4 .. sz:2 .... .... .... .... \ imm=3D%b4_li_18 dsp=3D%b4_dsp_16 @@ -619,3 +656,89 @@ XOR_ir 1111 1101 0111 ..00 1101 .... @b3_rd_li XOR_mr 1111 1100 0011 01.. .... .... @b3_rd_ld_ub # XOR dsp[rs], rd XOR_mr 0000 0110 ..10 00.. 0000 1101 .... .... @b4_rd_ldmi + +######## +# RXv2 DPFPU instructions +# All 4-byte register-to-register ops use prefix 0x76 0x90 +# +# Binary ops: 0111 0110 1001 0000 drs2:4 op:4 drd:4 drs1:4 +# Result: DRdrd =3D DRdrs1 op DRdrs2 +DADD 0111 0110 1001 0000 drs2:4 0000 drd:4 drs1:4 &drrr +DSUB 0111 0110 1001 0000 drs2:4 0001 drd:4 drs1:4 &drrr +DMUL 0111 0110 1001 0000 drs2:4 0010 drd:4 drs1:4 &drrr +DDIV 0111 0110 1001 0000 drs2:4 0101 drd:4 drs1:4 &drrr +DCMP 0111 0110 1001 0000 drs2:4 1000 cd:4 drs1:4 &dcmpd + +# Unary ops group A (1100): DMOV, DABS, DNEG +DMOV 0111 0110 1001 0000 drs:4 1100 drd:4 0000 &drr +DABS 0111 0110 1001 0000 drs:4 1100 drd:4 0001 &drr +DNEG 0111 0110 1001 0000 drs:4 1100 drd:4 0010 &drr + +# Unary ops group B (1101): DSQRT, DROUND; DR-to-GPR conversions +DSQRT 0111 0110 1001 0000 drs:4 1101 drd:4 0000 &drr +DROUND 0111 0110 1001 0000 drs:4 1101 drd:4 1101 &drr +# DTOI/DTOU/DTOF convert within the DPFPU: the result lands in a DR regist= er +# and is moved out to a GPR with DMOV.L DRLn, Rd. +DTOI 0111 0110 1001 0000 drs:4 1101 drd:4 1000 &drr +DTOU 0111 0110 1001 0000 drs:4 1101 drd:4 1001 &drr +DTOF 0111 0110 1001 0000 drs:4 1101 drd:4 1100 &drr + +# GPR/float-to-DR conversions using 0xFD 0x77 prefix +# Note: 0xFD 0x77 with byte2 in 0x00-0x0F is MVTC_i (li=3D01); no conflict= with 0x8x +ITOD 1111 1101 0111 0111 1000 rs:4 drd:4 1001 &idr +FTOD 1111 1101 0111 0111 1000 rs:4 drd:4 1010 &idr +UTOD 1111 1101 0111 0111 1000 rs:4 drd:4 1101 &idr + +# DPUSHM.D/DPOPM.D: push/pop range of DR registers (3-byte: 0x75 prefix) +DPUSHM 0111 0101 1011 0000 drd:4 rnum:4 &dppm +DPOPM 0111 0101 1011 1000 drd:4 rnum:4 &dppm +# DPUSHM.L/DPOPM.L: same, for the DPFPU control registers +DPUSHM_l 0111 0101 1010 0000 dcrd:4 rnum:4 &dcppm +DPOPM_l 0111 0101 1010 1000 dcrd:4 rnum:4 &dcppm + +######## +# RXv3: remaining DPFPU transfers +# +# GPR -> DR half (0xFD 0x77 prefix): byte3 low nibble is the source GPR, +# byte4 high nibble the destination DR. +DMOV_rdrl 1111 1101 0111 0111 1000 rs:4 drd:4 0000 &drhr +DMOV_rdrh 1111 1101 0111 0111 1000 rs:4 drd:4 0010 &drhr +DMOVD_rdrh 1111 1101 0111 0111 1000 rs:4 drd:4 0011 &drhr +# DR half -> GPR (0xFD 0x75 prefix): byte3 low nibble is the destination G= PR. +DMOV_drlr 1111 1101 0111 0101 1000 rd:4 drs:4 0000 &rdrh +DMOV_drhr 1111 1101 0111 0101 1000 rd:4 drs:4 0010 &rdrh + +# DPFPU control register transfers +MVFDC 1111 1101 0111 0101 1000 rd:4 dcrs:4 0100 &rdcr +MVTDC 1111 1101 0111 0111 1000 rs:4 dcrd:4 0100 &dcrr +MVFDR 0111 0101 1001 0000 0001 1011 + +# DMOV.D DRs, dsp[Rd] / DMOV.D dsp[Rs], DRd. +# The DR register is supplied by a trailing post-byte that follows the +# displacement, so it is read during translation rather than by decodetree. +DMOV_mst 1111 1100 0111 10 ld:2 rd:4 1000 &dmovst +DMOV_mld 1111 1100 1100 10 ld:2 rs:4 1000 &dmovld + +######## +# RXv3: bit field move. The 16-bit field descriptor follows the opcode. +BFMOVZ 1111 1100 0101 1010 rs:4 rd:4 &bfm +BFMOV 1111 1100 0101 1110 rs:4 rd:4 &bfm + +######## +# RXv3: register bank save and restore +SAVE_r 1111 1101 0111 0110 1100 rs:4 0000 0000 &svr +RSTR_r 1111 1101 0111 0110 1101 rs:4 0000 0000 &svr +SAVE_i 1111 1101 0111 0110 1110 0000 imm:8 &svi +RSTR_i 1111 1101 0111 0110 1111 0000 imm:8 &svi + +######## +# RXv3: three-operand XOR +XOR_rrr 1111 1111 0110 .... .... .... @b3_rd_rs_rs2 + +# RXv2 accumulator context save/restore (unshifted reads). +MVTACHI_A1 1111 1101 0001 0111 1000 r:4 +MVTACLO_A1 1111 1101 0001 0111 1001 r:4 +MVTACGU 1111 1101 0001 0111 a:1 011 r:4 +MVFACHI_A1 1111 1101 0001 1111 1000 r:4 +MVFACLO 1111 1101 0001 1111 a:1 001 r:4 +MVFACGU 1111 1101 0001 1111 a:1 011 r:4 diff --git a/target/rx/meson.build b/target/rx/meson.build index d80ced11e2..d196737ce3 100644 --- a/target/rx/meson.build +++ b/target/rx/meson.build @@ -9,10 +9,8 @@ rx_ss.add(files( 'op_helper.c', 'helper.c', 'cpu.c', + 'gdbstub.c', 'disas.c')) =20 -rx_common_system_ss =3D ss.source_set() -rx_common_system_ss.add(files('gdbstub.c')) - target_arch +=3D {'rx': rx_ss} -target_common_system_arch +=3D {'rx': rx_common_system_ss} +target_system_arch +=3D {'rx': ss.source_set()} diff --git a/target/rx/op_helper.c b/target/rx/op_helper.c index 36df7d377e..534ae6f83b 100644 --- a/target/rx/op_helper.c +++ b/target/rx/op_helper.c @@ -18,6 +18,7 @@ =20 #include "qemu/osdep.h" #include "qemu/bitops.h" +#include "qemu/log.h" #include "cpu.h" #include "exec/helper-proto.h" #include "accel/tcg/cpu-ldst.h" @@ -120,6 +121,65 @@ static void update_fpsw(CPURXState *env, float32 ret, = uintptr_t retaddr) } } =20 +/* + * RXv3 register bank save function. SAVE copies R1-R15, the USP, the FPSW + * and the accumulator into the selected save register bank; RSTR copies t= hem + * back. R0 is deliberately excluded: the stack pointer is not banked. + * + * The banks are internal CPU state that only these two instructions can + * reach, so there is no memory layout to model. How many banks a part + * provides is implementation defined and comes from the CPU model. + */ +static RXSaveBank *rx_save_bank(CPURXState *env, uint32_t bank, + const char *insn) +{ + uint32_t n =3D RX_CPU_GET_CLASS(env_cpu(env))->num_save_banks; + + if (bank >=3D n) { + qemu_log_mask(LOG_GUEST_ERROR, + "rx: %s with out of range bank %u (this CPU has %u)\= n", + insn, bank, n); + return NULL; + } + return &env->bank[bank]; +} + +void helper_save(CPURXState *env, uint32_t bank) +{ + RXSaveBank *b =3D rx_save_bank(env, bank, "save"); + int i; + + if (!b) { + return; + } + for (i =3D 1; i < NUM_REGS; i++) { + b->regs[i] =3D env->regs[i]; + } + b->usp =3D env->psw_u ? env->regs[0] : env->usp; + b->fpsw =3D env->fpsw; + b->acc =3D env->acc; +} + +void helper_rstr(CPURXState *env, uint32_t bank) +{ + RXSaveBank *b =3D rx_save_bank(env, bank, "rstr"); + int i; + + if (!b) { + return; + } + for (i =3D 1; i < NUM_REGS; i++) { + env->regs[i] =3D b->regs[i]; + } + if (env->psw_u) { + env->regs[0] =3D b->usp; + } else { + env->usp =3D b->usp; + } + env->fpsw =3D b->fpsw; + env->acc =3D b->acc; +} + void helper_set_fpsw(CPURXState *env, uint32_t val) { static const int roundmode[] =3D { @@ -152,6 +212,287 @@ FLOATOP(fsub, float32_sub) FLOATOP(fmul, float32_mul) FLOATOP(fdiv, float32_div) =20 +/* + * RXv3 double-precision FPU helpers. + * + * The DPFPU has its own status word: "The single-precision floating-point + * status word (FPSW) is neither referred to nor updated in double-precisi= on + * floating-point arithmetic operations." DPSW carries its own rounding mo= de + * (DRM) and denormal handling (DDN), so double precision runs on a separa= te + * softfloat state rather than sharing fp_status. The PSW is not touched + * either; DCMP records its answer in DCMR and MVFDR moves it to PSW.Z. + */ +#define SET_DPSW(b) \ + do { \ + dpsw =3D FIELD_DP32(dpsw, DPSW, DC ## b, 1); \ + if (!FIELD_EX32(dpsw, DPSW, DE ## b)) { \ + dpsw =3D FIELD_DP32(dpsw, DPSW, DF ## b, 1); \ + } \ + } while (0) + +/* Apply DPSW's rounding mode and clear the sticky softfloat flags. */ +static void dp_begin(CPURXState *env) +{ + static const int roundmode[] =3D { + float_round_nearest_even, + float_round_to_zero, + float_round_up, + float_round_down, + }; + uint32_t dpsw =3D env->dcr[RX_DCR_DPSW]; + + set_float_rounding_mode(roundmode[FIELD_EX32(dpsw, DPSW, DRM)], + &env->dp_status); + set_float_exception_flags(0, &env->dp_status); +} + +/* + * Record the outcome of a DP arithmetic instruction in DPSW. Every such + * instruction except DABS and DNEG recomputes the DC* cause bits, so they + * are cleared first; the DF* flags accumulate and are only set while the + * matching DE* enable is clear. + */ +static void update_dpsw(CPURXState *env) +{ + uint32_t decnt =3D env->dcr[RX_DCR_DECNT]; + uint32_t dpsw =3D env->dcr[RX_DCR_DPSW]; + int xcpt =3D get_float_exception_flags(&env->dp_status); + int cause, enable; + + /* DPSW and DEPC stop updating while exception info is preserved. */ + if (FIELD_EX32(decnt, DECNT, EHM) && FIELD_EX32(decnt, DECNT, EHS)) { + return; + } + + dpsw =3D FIELD_DP32(dpsw, DPSW, CAUSE, 0); + + if (xcpt & float_flag_invalid) { + SET_DPSW(V); + } + if (xcpt & float_flag_divbyzero) { + SET_DPSW(Z); + } + if (xcpt & float_flag_overflow) { + SET_DPSW(O); + } + if (xcpt & float_flag_underflow) { + SET_DPSW(U); + } + if (xcpt & float_flag_inexact) { + SET_DPSW(X); + } + /* + * A denormal the DPFPU cannot handle is an unimplemented processing + * exception, which is reported only while DDN says denormals are to be + * treated as denormals. + */ + if ((xcpt & (float_flag_input_denormal_flushed + | float_flag_output_denormal_flushed)) + && !FIELD_EX32(dpsw, DPSW, DDN)) { + dpsw =3D FIELD_DP32(dpsw, DPSW, DCE, 1); + } + + dpsw =3D FIELD_DP32(dpsw, DPSW, DFS, + FIELD_EX32(dpsw, DPSW, FLAGS) !=3D 0); + env->dcr[RX_DCR_DPSW] =3D dpsw; + + /* + * An enabled DP exception is delivered as an interrupt request to the + * interrupt controller rather than as a CPU exception, and suppresses + * the write to the destination register. Neither is modelled: the cau= se + * and flag bits above are correct, but the interrupt is not raised and + * the destination is written regardless. + */ + cause =3D FIELD_EX32(dpsw, DPSW, CAUSE); + enable =3D FIELD_EX32(dpsw, DPSW, ENABLE); + if (cause & enable) { + qemu_log_mask(LOG_UNIMP, + "rx: enabled DPFPU exception (DPSW=3D0x%08x) does no= t " + "raise an interrupt\n", dpsw); + } +} + +#define FLOATOP64(op, func) \ + float64 helper_##op(CPURXState *env, float64 t0, float64 t1) \ + { \ + float64 ret; \ + dp_begin(env); \ + ret =3D func(t0, t1, &env->dp_status); \ + update_dpsw(env); \ + return ret; \ + } + +FLOATOP64(dadd, float64_add) +FLOATOP64(dsub, float64_sub) +FLOATOP64(dmul, float64_mul) +FLOATOP64(ddiv, float64_div) + +/* + * dcmp src, src2 -- compare src2 against src as directed by cm and + * record the answer in DCMR.RES. + * + * Mind the operand order: the manual defines every relation as src2 REL + * src, so LT is true when the *second* operand is the smaller one. src is + * the first encoded register (drs1) and src2 the second (drs2). + * + * The PSW is left alone -- C, Z, S and O are all listed as unchanged -- + * which is why MVFDR exists to move DCMR.RES into PSW.Z. + * + * The condition field is a mask of the relations that make RES true, which + * is why LE (6) is LT (4) | EQ (2); it agrees with the manual on all four + * documented conditions. UN is defined as isNaN(src) || isNaN(src2), which + * is exactly what an unordered comparison reports. The comparison is quiet + * because testing for unordered is one of the conditions on offer. + */ +void helper_dcmp(CPURXState *env, uint32_t cm, float64 src, float64 src2) +{ + int st; + bool res =3D false; + + dp_begin(env); + st =3D float64_compare_quiet(src2, src, &env->dp_status); + + if ((cm & RX_DCMP_UN) && st =3D=3D float_relation_unordered) { + res =3D true; + } + if ((cm & RX_DCMP_EQ) && st =3D=3D float_relation_equal) { + res =3D true; + } + if ((cm & RX_DCMP_LT) && st =3D=3D float_relation_less) { + res =3D true; + } + env->dcr[RX_DCR_DCMR] =3D deposit32(env->dcr[RX_DCR_DCMR], + RX_DCMR_RES_BIT, 1, res); + update_dpsw(env); +} + +/* + * DABS and DNEG only change the sign bit. They cannot raise an exception, + * and are the two DP arithmetic instructions that leave the DC* cause bits + * as they were rather than recomputing them. + */ +float64 helper_dabs(CPURXState *env, float64 t0) +{ + return float64_abs(t0); +} + +float64 helper_dneg(CPURXState *env, float64 t0) +{ + return float64_chs(t0); +} + +float64 helper_dsqrt(CPURXState *env, float64 t0) +{ + float64 ret; + + dp_begin(env); + ret =3D float64_sqrt(t0, &env->dp_status); + update_dpsw(env); + return ret; +} + +float64 helper_dround(CPURXState *env, float64 t0) +{ + float64 ret; + + dp_begin(env); + ret =3D float64_round_to_int(t0, &env->dp_status); + update_dpsw(env); + return ret; +} + +uint32_t helper_dtoi(CPURXState *env, float64 t0) +{ + uint32_t ret; + + dp_begin(env); + ret =3D float64_to_int32_round_to_zero(t0, &env->dp_status); + update_dpsw(env); + return ret; +} + +uint32_t helper_dtou(CPURXState *env, float64 t0) +{ + uint32_t ret; + + dp_begin(env); + ret =3D float64_to_uint32_round_to_zero(t0, &env->dp_status); + update_dpsw(env); + return ret; +} + +/* dtof narrows to single precision but is a DPFPU instruction: DPSW. */ +float32 helper_dtof(CPURXState *env, float64 t0) +{ + float32 ret; + + dp_begin(env); + ret =3D float64_to_float32(t0, &env->dp_status); + update_dpsw(env); + return ret; +} + +float64 helper_itod(CPURXState *env, uint32_t t0) +{ + float64 ret; + + dp_begin(env); + ret =3D int32_to_float64((int32_t)t0, &env->dp_status); + update_dpsw(env); + return ret; +} + +float64 helper_utod(CPURXState *env, uint32_t t0) +{ + float64 ret; + + dp_begin(env); + ret =3D uint32_to_float64(t0, &env->dp_status); + update_dpsw(env); + return ret; +} + +float64 helper_ftod(CPURXState *env, float32 t0) +{ + float64 ret; + + dp_begin(env); + ret =3D float32_to_float64(t0, &env->dp_status); + update_dpsw(env); + return ret; +} + +/* + * mvtdc rs, DCRd. Not a plain register write: the DC* cause bits in DPSW + * clear on a written 0 and keep their value on a written 1, DFS is a + * read-only summary, DCMR holds only RES, and DEPC is read-only. + */ +void helper_mvtdc(CPURXState *env, uint32_t reg, uint32_t val) +{ + uint32_t old, dpsw; + + switch (reg) { + case RX_DCR_DPSW: + old =3D env->dcr[RX_DCR_DPSW]; + dpsw =3D val & RX_DPSW_WRITE_MASK; + dpsw =3D (dpsw & ~RX_DPSW_CAUSE_MASK) + | (old & val & RX_DPSW_CAUSE_MASK); + dpsw =3D FIELD_DP32(dpsw, DPSW, DFS, + FIELD_EX32(dpsw, DPSW, FLAGS) !=3D 0); + env->dcr[RX_DCR_DPSW] =3D dpsw; + break; + case RX_DCR_DCMR: + env->dcr[RX_DCR_DCMR] =3D val & RX_DCMR_WRITE_MASK; + break; + case RX_DCR_DECNT: + env->dcr[RX_DCR_DECNT] =3D val & (R_DECNT_EHM_MASK | R_DECNT_EHS_M= ASK); + break; + default: + /* DEPC is read-only. */ + break; + } +} + void helper_fcmp(CPURXState *env, float32 t0, float32 t1) { int st; @@ -180,6 +521,14 @@ uint32_t helper_ftoi(CPURXState *env, float32 t0) return ret; } =20 +uint32_t helper_ftou(CPURXState *env, float32 t0) +{ + uint32_t ret; + ret =3D float32_to_uint32_round_to_zero(t0, &env->fp_status); + update_fpsw(env, ret, GETPC()); + return ret; +} + uint32_t helper_round(CPURXState *env, float32 t0) { uint32_t ret; @@ -196,6 +545,14 @@ float32 helper_itof(CPURXState *env, uint32_t t0) return ret; } =20 +float32 helper_utof(CPURXState *env, uint32_t t0) +{ + float32 ret; + ret =3D uint32_to_float32(t0, &env->fp_status); + update_fpsw(env, ret, GETPC()); + return ret; +} + /* string operations */ void helper_scmpu(CPURXState *env) { diff --git a/target/rx/translate.c b/target/rx/translate.c index 132d495710..0958bdcf50 100644 --- a/target/rx/translate.c +++ b/target/rx/translate.c @@ -37,11 +37,23 @@ typedef struct DisasContext { CPURXState *env; uint32_t pc; uint32_t tb_flags; + RXISAVersion isa_version; } DisasContext; =20 +/* + * Instructions introduced after RXv1 are only valid on CPU models that + * implement the corresponding revision. Returning false from a trans_ + * handler makes decode() fail, which raises an illegal instruction + * exception exactly as an unknown encoding would. + */ +static bool require_isa(DisasContext *ctx, RXISAVersion version) +{ + return ctx->isa_version >=3D version; +} + typedef struct DisasCompare { - TCGv_i32 value; - TCGv_i32 temp; + TCGv value; + TCGv temp; TCGCond cond; } DisasCompare; =20 @@ -49,7 +61,7 @@ const char *rx_crname(uint8_t cr) { static const char *cr_names[] =3D { "psw", "pc", "usp", "fpsw", "", "", "", "", - "bpsw", "bpc", "isp", "fintv", "intb", "", "", "" + "bpsw", "bpc", "isp", "fintv", "intb", "extb", "", "" }; if (cr >=3D ARRAY_SIZE(cr_names)) { return "illegal"; @@ -63,20 +75,18 @@ const char *rx_crname(uint8_t cr) #define DISAS_EXIT DISAS_TARGET_2 =20 /* global register indexes */ -static TCGv_i32 cpu_regs[16]; -static TCGv_i32 cpu_psw_o, cpu_psw_s, cpu_psw_z, cpu_psw_c; -static TCGv_i32 cpu_psw_i, cpu_psw_pm, cpu_psw_u, cpu_psw_ipl; -static TCGv_i32 cpu_usp, cpu_fpsw, cpu_bpsw, cpu_bpc, cpu_isp; -static TCGv_i32 cpu_fintv, cpu_intb, cpu_pc; +static TCGv cpu_regs[16]; +static TCGv cpu_psw_o, cpu_psw_s, cpu_psw_z, cpu_psw_c; +static TCGv cpu_psw_i, cpu_psw_pm, cpu_psw_u, cpu_psw_ipl; +static TCGv cpu_usp, cpu_fpsw, cpu_bpsw, cpu_bpc, cpu_isp; +static TCGv cpu_fintv, cpu_intb, cpu_extb, cpu_pc; static TCGv_i64 cpu_acc; +/* RXv3 DPFPU state */ +static TCGv_i64 cpu_dregs[NUM_DREGS]; +static TCGv cpu_dcregs[NUM_DCREGS]; =20 #define cpu_sp cpu_regs[0] =20 -static inline MemOp mo_endian(DisasContext *dc) -{ - return MO_LE; -} - /* decoder helper */ static uint32_t decode_load_bytes(DisasContext *ctx, uint32_t insn, int i, int n) @@ -90,9 +100,8 @@ static uint32_t decode_load_bytes(DisasContext *ctx, uin= t32_t insn, =20 static uint32_t li(DisasContext *ctx, int sz) { - vaddr addr; + target_ulong addr; uint32_t tmp; - MemOp endian =3D mo_endian(ctx); CPURXState *env =3D ctx->env; addr =3D ctx->base.pc_next; =20 @@ -102,16 +111,16 @@ static uint32_t li(DisasContext *ctx, int sz) return (int8_t)translator_ldub(env, &ctx->base, addr); case 2: ctx->base.pc_next +=3D 2; - return (int16_t) translator_lduw_end(env, &ctx->base, addr, endian= ); + return (int16_t)translator_lduw_end(env, &ctx->base, addr, MO_LE); case 3: ctx->base.pc_next +=3D 3; tmp =3D (int8_t)translator_ldub(env, &ctx->base, addr + 2); tmp <<=3D 16; - tmp |=3D translator_lduw_end(env, &ctx->base, addr, endian); + tmp |=3D translator_lduw_end(env, &ctx->base, addr, MO_LE); return tmp; case 0: ctx->base.pc_next +=3D 4; - return translator_ldl_end(env, &ctx->base, addr, endian); + return translator_ldl_end(env, &ctx->base, addr, MO_LE); default: g_assert_not_reached(); } @@ -151,14 +160,23 @@ void rx_cpu_dump_state(CPUState *cs, FILE *f, int fla= gs) i, env->regs[i], i + 1, env->regs[i + 1], i + 2, env->regs[i + 2], i + 3, env->regs[i + 3]); } + if (RX_CPU_GET_CLASS(cs)->isa_version >=3D RX_ISA_V3) { + for (i =3D 0; i < NUM_DREGS; i +=3D 2) { + qemu_fprintf(f, "dr%d=3D0x%016" PRIx64 " dr%d=3D0x%016" PRIx64= "\n", + i, env->dr[i], i + 1, env->dr[i + 1]); + } + qemu_fprintf(f, "dpsw=3D0x%08x dcmr=3D0x%08x decnt=3D0x%08x depc= =3D0x%08x\n", + env->dcr[RX_DCR_DPSW], env->dcr[RX_DCR_DCMR], + env->dcr[RX_DCR_DECNT], env->dcr[RX_DCR_DEPC]); + } } =20 -static void gen_goto_tb(DisasContext *dc, unsigned tb_slot_idx, vaddr dest) +static void gen_goto_tb(DisasContext *dc, int n, target_ulong dest) { if (translator_use_goto_tb(&dc->base, dest)) { - tcg_gen_goto_tb(tb_slot_idx); + tcg_gen_goto_tb(n); tcg_gen_movi_i32(cpu_pc, dest); - tcg_gen_exit_tb(dc->base.tb, tb_slot_idx); + tcg_gen_exit_tb(dc->base.tb, n); } else { tcg_gen_movi_i32(cpu_pc, dest); tcg_gen_lookup_and_goto_ptr(); @@ -167,34 +185,34 @@ static void gen_goto_tb(DisasContext *dc, unsigned tb= _slot_idx, vaddr dest) } =20 /* generic load wrapper */ -static void rx_gen_ld(DisasContext *ctx, MemOp size, TCGv_i32 reg, TCGv_i3= 2 mem) +static inline void rx_gen_ld(unsigned int size, TCGv reg, TCGv mem) { - tcg_gen_qemu_ld_i32(reg, mem, 0, size | MO_SIGN | mo_endian(ctx)); + tcg_gen_qemu_ld_i32(reg, mem, 0, size | MO_SIGN | MO_LE); } =20 /* unsigned load wrapper */ -static void rx_gen_ldu(DisasContext *ctx, MemOp size, TCGv_i32 reg, TCGv_i= 32 mem) +static inline void rx_gen_ldu(unsigned int size, TCGv reg, TCGv mem) { - tcg_gen_qemu_ld_i32(reg, mem, 0, size | mo_endian(ctx)); + tcg_gen_qemu_ld_i32(reg, mem, 0, size | MO_LE); } =20 /* generic store wrapper */ -static void rx_gen_st(DisasContext *ctx, MemOp size, TCGv_i32 reg, TCGv_i3= 2 mem) +static inline void rx_gen_st(unsigned int size, TCGv reg, TCGv mem) { - tcg_gen_qemu_st_i32(reg, mem, 0, size | mo_endian(ctx)); + tcg_gen_qemu_st_i32(reg, mem, 0, size | MO_LE); } =20 /* [ri, rb] */ -static void rx_gen_regindex(DisasContext *ctx, TCGv_i32 mem, - int size, int ri, int rb) +static inline void rx_gen_regindex(DisasContext *ctx, TCGv mem, + int size, int ri, int rb) { tcg_gen_shli_i32(mem, cpu_regs[ri], size); tcg_gen_add_i32(mem, mem, cpu_regs[rb]); } =20 /* dsp[reg] */ -static TCGv_i32 rx_index_addr(DisasContext *ctx, TCGv_i32 mem, - int ld, int size, int reg) +static inline TCGv rx_index_addr(DisasContext *ctx, TCGv mem, + int ld, int size, int reg) { uint32_t dsp; =20 @@ -208,7 +226,7 @@ static TCGv_i32 rx_index_addr(DisasContext *ctx, TCGv_i= 32 mem, return mem; case 2: dsp =3D translator_lduw_end(ctx->env, &ctx->base, ctx->base.pc_nex= t, - mo_endian(ctx)) << size; + MO_LE) << size; tcg_gen_addi_i32(mem, cpu_regs[reg], dsp); ctx->base.pc_next +=3D 2; return mem; @@ -225,15 +243,15 @@ static inline MemOp mi_to_mop(unsigned mi) } =20 /* load source operand */ -static TCGv_i32 rx_load_source(DisasContext *ctx, TCGv_i32 mem, - int ld, int mi, int rs) +static inline TCGv rx_load_source(DisasContext *ctx, TCGv mem, + int ld, int mi, int rs) { - TCGv_i32 addr; + TCGv addr; MemOp mop; if (ld < 3) { mop =3D mi_to_mop(mi); addr =3D rx_index_addr(ctx, mem, ld, mop & MO_SIZE, rs); - tcg_gen_qemu_ld_i32(mem, addr, 0, mop | mo_endian(ctx)); + tcg_gen_qemu_ld_i32(mem, addr, 0, mop | MO_LE); return mem; } else { return cpu_regs[rs]; @@ -322,7 +340,7 @@ static void psw_cond(DisasCompare *dc, uint32_t cond) } } =20 -static void move_from_cr(DisasContext *ctx, TCGv_i32 ret, int cr, uint32_t= pc) +static void move_from_cr(DisasContext *ctx, TCGv ret, int cr, uint32_t pc) { switch (cr) { case 0: /* PSW */ @@ -360,6 +378,9 @@ static void move_from_cr(DisasContext *ctx, TCGv_i32 re= t, int cr, uint32_t pc) case 12: /* INTB */ tcg_gen_mov_i32(ret, cpu_intb); break; + case 13: /* EXTB */ + tcg_gen_mov_i32(ret, cpu_extb); + break; default: qemu_log_mask(LOG_GUEST_ERROR, "Unimplement control register %d", = cr); /* Unimplement registers return 0 */ @@ -368,7 +389,7 @@ static void move_from_cr(DisasContext *ctx, TCGv_i32 re= t, int cr, uint32_t pc) } } =20 -static void move_to_cr(DisasContext *ctx, TCGv_i32 val, int cr) +static void move_to_cr(DisasContext *ctx, TCGv val, int cr) { if (cr >=3D 8 && !is_privileged(ctx, 0)) { /* Some control registers can only be written in privileged mode. = */ @@ -414,6 +435,9 @@ static void move_to_cr(DisasContext *ctx, TCGv_i32 val,= int cr) case 12: /* INTB */ tcg_gen_mov_i32(cpu_intb, val); break; + case 13: /* EXTB */ + tcg_gen_mov_i32(cpu_extb, val); + break; default: qemu_log_mask(LOG_GUEST_ERROR, "Unimplement control register %d", cr); @@ -421,35 +445,35 @@ static void move_to_cr(DisasContext *ctx, TCGv_i32 va= l, int cr) } } =20 -static void push(DisasContext *ctx, TCGv_i32 val) +static void push(TCGv val) { tcg_gen_subi_i32(cpu_sp, cpu_sp, 4); - rx_gen_st(ctx, MO_32, val, cpu_sp); + rx_gen_st(MO_32, val, cpu_sp); } =20 -static void pop(DisasContext *ctx, TCGv_i32 ret) +static void pop(TCGv ret) { - rx_gen_ld(ctx, MO_32, ret, cpu_sp); + rx_gen_ld(MO_32, ret, cpu_sp); tcg_gen_addi_i32(cpu_sp, cpu_sp, 4); } =20 /* mov. rs,dsp5[rd] */ static bool trans_MOV_rm(DisasContext *ctx, arg_MOV_rm *a) { - TCGv_i32 mem; - mem =3D tcg_temp_new_i32(); + TCGv mem; + mem =3D tcg_temp_new(); tcg_gen_addi_i32(mem, cpu_regs[a->rd], a->dsp << a->sz); - rx_gen_st(ctx, a->sz, cpu_regs[a->rs], mem); + rx_gen_st(a->sz, cpu_regs[a->rs], mem); return true; } =20 /* mov. dsp5[rs],rd */ static bool trans_MOV_mr(DisasContext *ctx, arg_MOV_mr *a) { - TCGv_i32 mem; - mem =3D tcg_temp_new_i32(); + TCGv mem; + mem =3D tcg_temp_new(); tcg_gen_addi_i32(mem, cpu_regs[a->rs], a->dsp << a->sz); - rx_gen_ld(ctx, a->sz, cpu_regs[a->rd], mem); + rx_gen_ld(a->sz, cpu_regs[a->rd], mem); return true; } =20 @@ -466,31 +490,31 @@ static bool trans_MOV_ir(DisasContext *ctx, arg_MOV_i= r *a) /* mov. #imm, dsp[rd] */ static bool trans_MOV_im(DisasContext *ctx, arg_MOV_im *a) { - TCGv_i32 imm, mem; + TCGv imm, mem; imm =3D tcg_constant_i32(a->imm); - mem =3D tcg_temp_new_i32(); + mem =3D tcg_temp_new(); tcg_gen_addi_i32(mem, cpu_regs[a->rd], a->dsp << a->sz); - rx_gen_st(ctx, a->sz, imm, mem); + rx_gen_st(a->sz, imm, mem); return true; } =20 /* mov. [ri,rb],rd */ static bool trans_MOV_ar(DisasContext *ctx, arg_MOV_ar *a) { - TCGv_i32 mem; - mem =3D tcg_temp_new_i32(); + TCGv mem; + mem =3D tcg_temp_new(); rx_gen_regindex(ctx, mem, a->sz, a->ri, a->rb); - rx_gen_ld(ctx, a->sz, cpu_regs[a->rd], mem); + rx_gen_ld(a->sz, cpu_regs[a->rd], mem); return true; } =20 /* mov. rd,[ri,rb] */ static bool trans_MOV_ra(DisasContext *ctx, arg_MOV_ra *a) { - TCGv_i32 mem; - mem =3D tcg_temp_new_i32(); + TCGv mem; + mem =3D tcg_temp_new(); rx_gen_regindex(ctx, mem, a->sz, a->ri, a->rb); - rx_gen_st(ctx, a->sz, cpu_regs[a->rs], mem); + rx_gen_st(a->sz, cpu_regs[a->rs], mem); return true; } =20 @@ -500,7 +524,7 @@ static bool trans_MOV_ra(DisasContext *ctx, arg_MOV_ra = *a) /* mov. rs,rd */ static bool trans_MOV_mm(DisasContext *ctx, arg_MOV_mm *a) { - TCGv_i32 tmp, mem, addr; + TCGv tmp, mem, addr; =20 if (a->lds =3D=3D 3 && a->ldd =3D=3D 3) { /* mov. rs,rd */ @@ -508,22 +532,22 @@ static bool trans_MOV_mm(DisasContext *ctx, arg_MOV_m= m *a) return true; } =20 - mem =3D tcg_temp_new_i32(); + mem =3D tcg_temp_new(); if (a->lds =3D=3D 3) { /* mov. rs,dsp[rd] */ addr =3D rx_index_addr(ctx, mem, a->ldd, a->sz, a->rs); - rx_gen_st(ctx, a->sz, cpu_regs[a->rd], addr); + rx_gen_st(a->sz, cpu_regs[a->rd], addr); } else if (a->ldd =3D=3D 3) { /* mov. dsp[rs],rd */ addr =3D rx_index_addr(ctx, mem, a->lds, a->sz, a->rs); - rx_gen_ld(ctx, a->sz, cpu_regs[a->rd], addr); + rx_gen_ld(a->sz, cpu_regs[a->rd], addr); } else { /* mov. dsp[rs],dsp[rd] */ - tmp =3D tcg_temp_new_i32(); + tmp =3D tcg_temp_new(); addr =3D rx_index_addr(ctx, mem, a->lds, a->sz, a->rs); - rx_gen_ld(ctx, a->sz, tmp, addr); + rx_gen_ld(a->sz, tmp, addr); addr =3D rx_index_addr(ctx, mem, a->ldd, a->sz, a->rd); - rx_gen_st(ctx, a->sz, tmp, addr); + rx_gen_st(a->sz, tmp, addr); } return true; } @@ -532,13 +556,13 @@ static bool trans_MOV_mm(DisasContext *ctx, arg_MOV_m= m *a) /* mov. rs,[-rd] */ static bool trans_MOV_rp(DisasContext *ctx, arg_MOV_rp *a) { - TCGv_i32 val; - val =3D tcg_temp_new_i32(); + TCGv val; + val =3D tcg_temp_new(); tcg_gen_mov_i32(val, cpu_regs[a->rs]); if (a->ad =3D=3D 1) { tcg_gen_subi_i32(cpu_regs[a->rd], cpu_regs[a->rd], 1 << a->sz); } - rx_gen_st(ctx, a->sz, val, cpu_regs[a->rd]); + rx_gen_st(a->sz, val, cpu_regs[a->rd]); if (a->ad =3D=3D 0) { tcg_gen_addi_i32(cpu_regs[a->rd], cpu_regs[a->rd], 1 << a->sz); } @@ -549,12 +573,12 @@ static bool trans_MOV_rp(DisasContext *ctx, arg_MOV_r= p *a) /* mov. [-rd],rs */ static bool trans_MOV_pr(DisasContext *ctx, arg_MOV_pr *a) { - TCGv_i32 val; - val =3D tcg_temp_new_i32(); + TCGv val; + val =3D tcg_temp_new(); if (a->ad =3D=3D 1) { tcg_gen_subi_i32(cpu_regs[a->rd], cpu_regs[a->rd], 1 << a->sz); } - rx_gen_ld(ctx, a->sz, val, cpu_regs[a->rd]); + rx_gen_ld(a->sz, val, cpu_regs[a->rd]); if (a->ad =3D=3D 0) { tcg_gen_addi_i32(cpu_regs[a->rd], cpu_regs[a->rd], 1 << a->sz); } @@ -566,10 +590,10 @@ static bool trans_MOV_pr(DisasContext *ctx, arg_MOV_p= r *a) /* movu. dsp[rs],rd */ static bool trans_MOVU_mr(DisasContext *ctx, arg_MOVU_mr *a) { - TCGv_i32 mem; - mem =3D tcg_temp_new_i32(); + TCGv mem; + mem =3D tcg_temp_new(); tcg_gen_addi_i32(mem, cpu_regs[a->rs], a->dsp << a->sz); - rx_gen_ldu(ctx, a->sz, cpu_regs[a->rd], mem); + rx_gen_ldu(a->sz, cpu_regs[a->rd], mem); return true; } =20 @@ -583,10 +607,10 @@ static bool trans_MOVU_rr(DisasContext *ctx, arg_MOVU= _rr *a) /* movu. [ri,rb],rd */ static bool trans_MOVU_ar(DisasContext *ctx, arg_MOVU_ar *a) { - TCGv_i32 mem; - mem =3D tcg_temp_new_i32(); + TCGv mem; + mem =3D tcg_temp_new(); rx_gen_regindex(ctx, mem, a->sz, a->ri, a->rb); - rx_gen_ldu(ctx, a->sz, cpu_regs[a->rd], mem); + rx_gen_ldu(a->sz, cpu_regs[a->rd], mem); return true; } =20 @@ -594,12 +618,12 @@ static bool trans_MOVU_ar(DisasContext *ctx, arg_MOVU= _ar *a) /* mov. [-rd],rs */ static bool trans_MOVU_pr(DisasContext *ctx, arg_MOVU_pr *a) { - TCGv_i32 val; - val =3D tcg_temp_new_i32(); + TCGv val; + val =3D tcg_temp_new(); if (a->ad =3D=3D 1) { tcg_gen_subi_i32(cpu_regs[a->rd], cpu_regs[a->rd], 1 << a->sz); } - rx_gen_ldu(ctx, a->sz, val, cpu_regs[a->rd]); + rx_gen_ldu(a->sz, val, cpu_regs[a->rd]); if (a->ad =3D=3D 0) { tcg_gen_addi_i32(cpu_regs[a->rd], cpu_regs[a->rd], 1 << a->sz); } @@ -624,9 +648,9 @@ static bool trans_POP(DisasContext *ctx, arg_POP *a) /* popc cr */ static bool trans_POPC(DisasContext *ctx, arg_POPC *a) { - TCGv_i32 val; - val =3D tcg_temp_new_i32(); - pop(ctx, val); + TCGv val; + val =3D tcg_temp_new(); + pop(val); move_to_cr(ctx, val, a->cr); return true; } @@ -641,7 +665,7 @@ static bool trans_POPM(DisasContext *ctx, arg_POPM *a) } r =3D a->rd; while (r <=3D a->rd2 && r < 16) { - pop(ctx, cpu_regs[r++]); + pop(cpu_regs[r++]); } return true; } @@ -650,34 +674,34 @@ static bool trans_POPM(DisasContext *ctx, arg_POPM *a) /* push. rs */ static bool trans_PUSH_r(DisasContext *ctx, arg_PUSH_r *a) { - TCGv_i32 val; - val =3D tcg_temp_new_i32(); + TCGv val; + val =3D tcg_temp_new(); tcg_gen_mov_i32(val, cpu_regs[a->rs]); tcg_gen_subi_i32(cpu_sp, cpu_sp, 4); - rx_gen_st(ctx, a->sz, val, cpu_sp); + rx_gen_st(a->sz, val, cpu_sp); return true; } =20 /* push. dsp[rs] */ static bool trans_PUSH_m(DisasContext *ctx, arg_PUSH_m *a) { - TCGv_i32 mem, val, addr; - mem =3D tcg_temp_new_i32(); - val =3D tcg_temp_new_i32(); + TCGv mem, val, addr; + mem =3D tcg_temp_new(); + val =3D tcg_temp_new(); addr =3D rx_index_addr(ctx, mem, a->ld, a->sz, a->rs); - rx_gen_ld(ctx, a->sz, val, addr); + rx_gen_ld(a->sz, val, addr); tcg_gen_subi_i32(cpu_sp, cpu_sp, 4); - rx_gen_st(ctx, a->sz, val, cpu_sp); + rx_gen_st(a->sz, val, cpu_sp); return true; } =20 /* pushc rx */ static bool trans_PUSHC(DisasContext *ctx, arg_PUSHC *a) { - TCGv_i32 val; - val =3D tcg_temp_new_i32(); + TCGv val; + val =3D tcg_temp_new(); move_from_cr(ctx, val, a->cr, ctx->pc); - push(ctx, val); + push(val); return true; } =20 @@ -692,7 +716,7 @@ static bool trans_PUSHM(DisasContext *ctx, arg_PUSHM *a) } r =3D a->rs2; while (r >=3D a->rs && r >=3D 0) { - push(ctx, cpu_regs[r--]); + push(cpu_regs[r--]); } return true; } @@ -700,8 +724,8 @@ static bool trans_PUSHM(DisasContext *ctx, arg_PUSHM *a) /* xchg rs,rd */ static bool trans_XCHG_rr(DisasContext *ctx, arg_XCHG_rr *a) { - TCGv_i32 tmp; - tmp =3D tcg_temp_new_i32(); + TCGv tmp; + tmp =3D tcg_temp_new(); tcg_gen_mov_i32(tmp, cpu_regs[a->rs]); tcg_gen_mov_i32(cpu_regs[a->rs], cpu_regs[a->rd]); tcg_gen_mov_i32(cpu_regs[a->rd], tmp); @@ -711,8 +735,8 @@ static bool trans_XCHG_rr(DisasContext *ctx, arg_XCHG_r= r *a) /* xchg dsp[rs].,rd */ static bool trans_XCHG_mr(DisasContext *ctx, arg_XCHG_mr *a) { - TCGv_i32 mem, addr; - mem =3D tcg_temp_new_i32(); + TCGv mem, addr; + mem =3D tcg_temp_new(); switch (a->mi) { case 0: /* dsp[rs].b */ case 1: /* dsp[rs].w */ @@ -731,10 +755,10 @@ static bool trans_XCHG_mr(DisasContext *ctx, arg_XCHG= _mr *a) return true; } =20 -static void stcond(TCGCond cond, int rd, int imm) +static inline void stcond(TCGCond cond, int rd, int imm) { - TCGv_i32 z; - TCGv_i32 _imm; + TCGv z; + TCGv _imm; z =3D tcg_constant_i32(0); _imm =3D tcg_constant_i32(imm); tcg_gen_movcond_i32(cond, cpu_regs[rd], cpu_psw_z, z, @@ -744,6 +768,9 @@ static void stcond(TCGCond cond, int rd, int imm) /* stz #imm,rd */ static bool trans_STZ(DisasContext *ctx, arg_STZ *a) { + if (!require_isa(ctx, RX_ISA_V2)) { + return false; + } stcond(TCG_COND_EQ, a->rd, a->imm); return true; } @@ -751,6 +778,9 @@ static bool trans_STZ(DisasContext *ctx, arg_STZ *a) /* stnz #imm,rd */ static bool trans_STNZ(DisasContext *ctx, arg_STNZ *a) { + if (!require_isa(ctx, RX_ISA_V2)) { + return false; + } stcond(TCG_COND_NE, a->rd, a->imm); return true; } @@ -760,15 +790,15 @@ static bool trans_STNZ(DisasContext *ctx, arg_STNZ *a) static bool trans_SCCnd(DisasContext *ctx, arg_SCCnd *a) { DisasCompare dc; - TCGv_i32 val, mem, addr; - dc.temp =3D tcg_temp_new_i32(); + TCGv val, mem, addr; + dc.temp =3D tcg_temp_new(); psw_cond(&dc, a->cd); if (a->ld < 3) { - val =3D tcg_temp_new_i32(); - mem =3D tcg_temp_new_i32(); + val =3D tcg_temp_new(); + mem =3D tcg_temp_new(); tcg_gen_setcondi_i32(dc.cond, val, dc.value, 0); addr =3D rx_index_addr(ctx, mem, a->sz, a->ld, a->rd); - rx_gen_st(ctx, a->sz, val, addr); + rx_gen_st(a->sz, val, addr); } else { tcg_gen_setcondi_i32(dc.cond, cpu_regs[a->rd], dc.value, 0); } @@ -779,7 +809,7 @@ static bool trans_SCCnd(DisasContext *ctx, arg_SCCnd *a) static bool trans_RTSD_i(DisasContext *ctx, arg_RTSD_i *a) { tcg_gen_addi_i32(cpu_sp, cpu_sp, a->imm << 2); - pop(ctx, cpu_pc); + pop(cpu_pc); ctx->base.is_jmp =3D DISAS_JUMP; return true; } @@ -799,42 +829,42 @@ static bool trans_RTSD_irr(DisasContext *ctx, arg_RTS= D_irr *a) tcg_gen_addi_i32(cpu_sp, cpu_sp, adj << 2); dst =3D a->rd; while (dst <=3D a->rd2 && dst < 16) { - pop(ctx, cpu_regs[dst++]); + pop(cpu_regs[dst++]); } - pop(ctx, cpu_pc); + pop(cpu_pc); ctx->base.is_jmp =3D DISAS_JUMP; return true; } =20 -typedef void (*op2fn)(TCGv_i32 ret, TCGv_i32 arg1); -typedef void (*op3fn)(TCGv_i32 ret, TCGv_i32 arg1, TCGv_i32 arg2); +typedef void (*op2fn)(TCGv ret, TCGv arg1); +typedef void (*op3fn)(TCGv ret, TCGv arg1, TCGv arg2); =20 -static void rx_gen_op_rr(op2fn opr, int dst, int src) +static inline void rx_gen_op_rr(op2fn opr, int dst, int src) { opr(cpu_regs[dst], cpu_regs[src]); } =20 -static void rx_gen_op_rrr(op3fn opr, int dst, int src, int src2) +static inline void rx_gen_op_rrr(op3fn opr, int dst, int src, int src2) { opr(cpu_regs[dst], cpu_regs[src], cpu_regs[src2]); } =20 -static void rx_gen_op_irr(op3fn opr, int dst, int src, uint32_t src2) +static inline void rx_gen_op_irr(op3fn opr, int dst, int src, uint32_t src= 2) { - TCGv_i32 imm =3D tcg_constant_i32(src2); + TCGv imm =3D tcg_constant_i32(src2); opr(cpu_regs[dst], cpu_regs[src], imm); } =20 -static void rx_gen_op_mr(op3fn opr, DisasContext *ctx, - int dst, int src, int ld, int mi) +static inline void rx_gen_op_mr(op3fn opr, DisasContext *ctx, + int dst, int src, int ld, int mi) { - TCGv_i32 val, mem; - mem =3D tcg_temp_new_i32(); + TCGv val, mem; + mem =3D tcg_temp_new(); val =3D rx_load_source(ctx, mem, ld, mi, src); opr(cpu_regs[dst], cpu_regs[dst], val); } =20 -static void rx_and(TCGv_i32 ret, TCGv_i32 arg1, TCGv_i32 arg2) +static void rx_and(TCGv ret, TCGv arg1, TCGv arg2) { tcg_gen_and_i32(cpu_psw_s, arg1, arg2); tcg_gen_mov_i32(cpu_psw_z, cpu_psw_s); @@ -864,7 +894,7 @@ static bool trans_AND_rrr(DisasContext *ctx, arg_AND_rr= r *a) return true; } =20 -static void rx_or(TCGv_i32 ret, TCGv_i32 arg1, TCGv_i32 arg2) +static void rx_or(TCGv ret, TCGv arg1, TCGv arg2) { tcg_gen_or_i32(cpu_psw_s, arg1, arg2); tcg_gen_mov_i32(cpu_psw_z, cpu_psw_s); @@ -894,7 +924,7 @@ static bool trans_OR_rrr(DisasContext *ctx, arg_OR_rrr = *a) return true; } =20 -static void rx_xor(TCGv_i32 ret, TCGv_i32 arg1, TCGv_i32 arg2) +static void rx_xor(TCGv ret, TCGv arg1, TCGv arg2) { tcg_gen_xor_i32(cpu_psw_s, arg1, arg2); tcg_gen_mov_i32(cpu_psw_z, cpu_psw_s); @@ -916,7 +946,7 @@ static bool trans_XOR_mr(DisasContext *ctx, arg_XOR_mr = *a) return true; } =20 -static void rx_tst(TCGv_i32 ret, TCGv_i32 arg1, TCGv_i32 arg2) +static void rx_tst(TCGv ret, TCGv arg1, TCGv arg2) { tcg_gen_and_i32(cpu_psw_s, arg1, arg2); tcg_gen_mov_i32(cpu_psw_z, cpu_psw_s); @@ -937,7 +967,7 @@ static bool trans_TST_mr(DisasContext *ctx, arg_TST_mr = *a) return true; } =20 -static void rx_not(TCGv_i32 ret, TCGv_i32 arg1) +static void rx_not(TCGv ret, TCGv arg1) { tcg_gen_not_i32(ret, arg1); tcg_gen_mov_i32(cpu_psw_z, ret); @@ -952,7 +982,7 @@ static bool trans_NOT_rr(DisasContext *ctx, arg_NOT_rr = *a) return true; } =20 -static void rx_neg(TCGv_i32 ret, TCGv_i32 arg1) +static void rx_neg(TCGv ret, TCGv arg1) { tcg_gen_setcondi_i32(TCG_COND_EQ, cpu_psw_o, arg1, 0x80000000); tcg_gen_neg_i32(ret, arg1); @@ -971,9 +1001,9 @@ static bool trans_NEG_rr(DisasContext *ctx, arg_NEG_rr= *a) } =20 /* ret =3D arg1 + arg2 + psw_c */ -static void rx_adc(TCGv_i32 ret, TCGv_i32 arg1, TCGv_i32 arg2) +static void rx_adc(TCGv ret, TCGv arg1, TCGv arg2) { - TCGv_i32 z =3D tcg_constant_i32(0); + TCGv z =3D tcg_constant_i32(0); tcg_gen_add2_i32(cpu_psw_s, cpu_psw_c, arg1, z, cpu_psw_c, z); tcg_gen_add2_i32(cpu_psw_s, cpu_psw_c, cpu_psw_s, cpu_psw_c, arg2, z); tcg_gen_xor_i32(cpu_psw_o, cpu_psw_s, arg1); @@ -1009,9 +1039,9 @@ static bool trans_ADC_mr(DisasContext *ctx, arg_ADC_m= r *a) } =20 /* ret =3D arg1 + arg2 */ -static void rx_add(TCGv_i32 ret, TCGv_i32 arg1, TCGv_i32 arg2) +static void rx_add(TCGv ret, TCGv arg1, TCGv arg2) { - TCGv_i32 z =3D tcg_constant_i32(0); + TCGv z =3D tcg_constant_i32(0); tcg_gen_add2_i32(cpu_psw_s, cpu_psw_c, arg1, z, arg2, z); tcg_gen_xor_i32(cpu_psw_o, cpu_psw_s, arg1); tcg_gen_xor_i32(cpu_psw_z, arg1, arg2); @@ -1044,7 +1074,7 @@ static bool trans_ADD_rrr(DisasContext *ctx, arg_ADD_= rrr *a) } =20 /* ret =3D arg1 - arg2 */ -static void rx_sub(TCGv_i32 ret, TCGv_i32 arg1, TCGv_i32 arg2) +static void rx_sub(TCGv ret, TCGv arg1, TCGv arg2) { tcg_gen_sub_i32(cpu_psw_s, arg1, arg2); tcg_gen_setcond_i32(TCG_COND_GEU, cpu_psw_c, arg1, arg2); @@ -1058,17 +1088,17 @@ static void rx_sub(TCGv_i32 ret, TCGv_i32 arg1, TCG= v_i32 arg2) } } =20 -static void rx_cmp(TCGv_i32 dummy, TCGv_i32 arg1, TCGv_i32 arg2) +static void rx_cmp(TCGv dummy, TCGv arg1, TCGv arg2) { rx_sub(NULL, arg1, arg2); } =20 /* ret =3D arg1 - arg2 - !psw_c */ /* -> ret =3D arg1 + ~arg2 + psw_c */ -static void rx_sbb(TCGv_i32 ret, TCGv_i32 arg1, TCGv_i32 arg2) +static void rx_sbb(TCGv ret, TCGv arg1, TCGv arg2) { - TCGv_i32 temp; - temp =3D tcg_temp_new_i32(); + TCGv temp; + temp =3D tcg_temp_new(); tcg_gen_not_i32(temp, arg2); rx_adc(ret, arg1, temp); } @@ -1194,7 +1224,7 @@ static bool trans_MUL_rrr(DisasContext *ctx, arg_MUL_= rrr *a) /* emul #imm, rd */ static bool trans_EMUL_ir(DisasContext *ctx, arg_EMUL_ir *a) { - TCGv_i32 imm =3D tcg_constant_i32(a->imm); + TCGv imm =3D tcg_constant_i32(a->imm); if (a->rd > 14) { qemu_log_mask(LOG_GUEST_ERROR, "rd too large %d", a->rd); } @@ -1207,11 +1237,11 @@ static bool trans_EMUL_ir(DisasContext *ctx, arg_EM= UL_ir *a) /* emul dsp[rs], rd */ static bool trans_EMUL_mr(DisasContext *ctx, arg_EMUL_mr *a) { - TCGv_i32 val, mem; + TCGv val, mem; if (a->rd > 14) { qemu_log_mask(LOG_GUEST_ERROR, "rd too large %d", a->rd); } - mem =3D tcg_temp_new_i32(); + mem =3D tcg_temp_new(); val =3D rx_load_source(ctx, mem, a->ld, a->mi, a->rs); tcg_gen_muls2_i32(cpu_regs[a->rd], cpu_regs[(a->rd + 1) & 15], cpu_regs[a->rd], val); @@ -1221,7 +1251,7 @@ static bool trans_EMUL_mr(DisasContext *ctx, arg_EMUL= _mr *a) /* emulu #imm, rd */ static bool trans_EMULU_ir(DisasContext *ctx, arg_EMULU_ir *a) { - TCGv_i32 imm =3D tcg_constant_i32(a->imm); + TCGv imm =3D tcg_constant_i32(a->imm); if (a->rd > 14) { qemu_log_mask(LOG_GUEST_ERROR, "rd too large %d", a->rd); } @@ -1234,23 +1264,23 @@ static bool trans_EMULU_ir(DisasContext *ctx, arg_E= MULU_ir *a) /* emulu dsp[rs], rd */ static bool trans_EMULU_mr(DisasContext *ctx, arg_EMULU_mr *a) { - TCGv_i32 val, mem; + TCGv val, mem; if (a->rd > 14) { qemu_log_mask(LOG_GUEST_ERROR, "rd too large %d", a->rd); } - mem =3D tcg_temp_new_i32(); + mem =3D tcg_temp_new(); val =3D rx_load_source(ctx, mem, a->ld, a->mi, a->rs); tcg_gen_mulu2_i32(cpu_regs[a->rd], cpu_regs[(a->rd + 1) & 15], cpu_regs[a->rd], val); return true; } =20 -static void rx_div(TCGv_i32 ret, TCGv_i32 arg1, TCGv_i32 arg2) +static void rx_div(TCGv ret, TCGv arg1, TCGv arg2) { gen_helper_div(ret, tcg_env, arg1, arg2); } =20 -static void rx_divu(TCGv_i32 ret, TCGv_i32 arg1, TCGv_i32 arg2) +static void rx_divu(TCGv ret, TCGv arg1, TCGv arg2) { gen_helper_divu(ret, tcg_env, arg1, arg2); } @@ -1290,8 +1320,8 @@ static bool trans_DIVU_mr(DisasContext *ctx, arg_DIVU= _mr *a) /* shll #imm:5, rs2, rd */ static bool trans_SHLL_irr(DisasContext *ctx, arg_SHLL_irr *a) { - TCGv_i32 tmp; - tmp =3D tcg_temp_new_i32(); + TCGv tmp; + tmp =3D tcg_temp_new(); if (a->imm) { tcg_gen_sari_i32(cpu_psw_c, cpu_regs[a->rs2], 32 - a->imm); tcg_gen_shli_i32(cpu_regs[a->rd], cpu_regs[a->rs2], a->imm); @@ -1313,14 +1343,14 @@ static bool trans_SHLL_irr(DisasContext *ctx, arg_S= HLL_irr *a) static bool trans_SHLL_rr(DisasContext *ctx, arg_SHLL_rr *a) { TCGLabel *noshift, *done; - TCGv_i32 count, tmp; + TCGv count, tmp; =20 noshift =3D gen_new_label(); done =3D gen_new_label(); /* if (cpu_regs[a->rs]) { */ tcg_gen_brcondi_i32(TCG_COND_EQ, cpu_regs[a->rs], 0, noshift); - count =3D tcg_temp_new_i32(); - tmp =3D tcg_temp_new_i32(); + count =3D tcg_temp_new(); + tmp =3D tcg_temp_new(); tcg_gen_andi_i32(tmp, cpu_regs[a->rs], 31); tcg_gen_sub_i32(count, tcg_constant_i32(32), tmp); tcg_gen_sar_i32(cpu_psw_c, cpu_regs[a->rd], count); @@ -1341,10 +1371,10 @@ static bool trans_SHLL_rr(DisasContext *ctx, arg_SH= LL_rr *a) return true; } =20 -static void shiftr_imm(uint32_t rd, uint32_t rs, uint32_t imm, - unsigned int alith) +static inline void shiftr_imm(uint32_t rd, uint32_t rs, uint32_t imm, + unsigned int alith) { - static void (* const gen_sXri[])(TCGv_i32 ret, TCGv_i32 arg1, int arg2= ) =3D { + static void (* const gen_sXri[])(TCGv ret, TCGv arg1, int arg2) =3D { tcg_gen_shri_i32, tcg_gen_sari_i32, }; tcg_debug_assert(alith < 2); @@ -1361,21 +1391,20 @@ static void shiftr_imm(uint32_t rd, uint32_t rs, ui= nt32_t imm, tcg_gen_mov_i32(cpu_psw_s, cpu_regs[rd]); } =20 -static void shiftr_reg(uint32_t rd, uint32_t rs, unsigned int alith) +static inline void shiftr_reg(uint32_t rd, uint32_t rs, unsigned int alith) { TCGLabel *noshift, *done; - TCGv_i32 count; - static void (* const gen_sXri[])(TCGv_i32 ret, TCGv_i32 arg1, int arg2= ) =3D { + TCGv count; + static void (* const gen_sXri[])(TCGv ret, TCGv arg1, int arg2) =3D { tcg_gen_shri_i32, tcg_gen_sari_i32, }; - static void (* const gen_sXr[])(TCGv_i32 ret, - TCGv_i32 arg1, TCGv_i32 arg2) =3D { + static void (* const gen_sXr[])(TCGv ret, TCGv arg1, TCGv arg2) =3D { tcg_gen_shr_i32, tcg_gen_sar_i32, }; tcg_debug_assert(alith < 2); noshift =3D gen_new_label(); done =3D gen_new_label(); - count =3D tcg_temp_new_i32(); + count =3D tcg_temp_new(); /* if (cpu_regs[rs]) { */ tcg_gen_brcondi_i32(TCG_COND_EQ, cpu_regs[rs], 0, noshift); tcg_gen_andi_i32(count, cpu_regs[rs], 31); @@ -1427,8 +1456,8 @@ static bool trans_SHLR_rr(DisasContext *ctx, arg_SHLR= _rr *a) /* rolc rd */ static bool trans_ROLC(DisasContext *ctx, arg_ROLC *a) { - TCGv_i32 tmp; - tmp =3D tcg_temp_new_i32(); + TCGv tmp; + tmp =3D tcg_temp_new(); tcg_gen_shri_i32(tmp, cpu_regs[a->rd], 31); tcg_gen_shli_i32(cpu_regs[a->rd], cpu_regs[a->rd], 1); tcg_gen_or_i32(cpu_regs[a->rd], cpu_regs[a->rd], cpu_psw_c); @@ -1441,8 +1470,8 @@ static bool trans_ROLC(DisasContext *ctx, arg_ROLC *a) /* rorc rd */ static bool trans_RORC(DisasContext *ctx, arg_RORC *a) { - TCGv_i32 tmp; - tmp =3D tcg_temp_new_i32(); + TCGv tmp; + tmp =3D tcg_temp_new(); tcg_gen_andi_i32(tmp, cpu_regs[a->rd], 0x00000001); tcg_gen_shri_i32(cpu_regs[a->rd], cpu_regs[a->rd], 1); tcg_gen_shli_i32(cpu_psw_c, cpu_psw_c, 31); @@ -1455,7 +1484,7 @@ static bool trans_RORC(DisasContext *ctx, arg_RORC *a) =20 enum {ROTR =3D 0, ROTL =3D 1}; enum {ROT_IMM =3D 0, ROT_REG =3D 1}; -static void rx_rot(int ir, int dir, int rd, int src) +static inline void rx_rot(int ir, int dir, int rd, int src) { switch (dir) { case ROTL: @@ -1517,8 +1546,8 @@ static bool trans_REVL(DisasContext *ctx, arg_REVL *a) /* revw rs, rd */ static bool trans_REVW(DisasContext *ctx, arg_REVW *a) { - TCGv_i32 tmp; - tmp =3D tcg_temp_new_i32(); + TCGv tmp; + tmp =3D tcg_temp_new(); tcg_gen_andi_i32(tmp, cpu_regs[a->rs], 0x00ff00ff); tcg_gen_shli_i32(tmp, tmp, 8); tcg_gen_shri_i32(cpu_regs[a->rd], cpu_regs[a->rs], 8); @@ -1535,7 +1564,7 @@ static void rx_bcnd_main(DisasContext *ctx, int cd, i= nt dst) =20 switch (cd) { case 0 ... 13: - dc.temp =3D tcg_temp_new_i32(); + dc.temp =3D tcg_temp_new(); psw_cond(&dc, cd); t =3D gen_new_label(); done =3D gen_new_label(); @@ -1590,10 +1619,10 @@ static bool trans_BRA_l(DisasContext *ctx, arg_BRA_= l *a) return true; } =20 -static void rx_save_pc(DisasContext *ctx) +static inline void rx_save_pc(DisasContext *ctx) { - TCGv_i32 pc =3D tcg_constant_i32(ctx->base.pc_next); - push(ctx, pc); + TCGv pc =3D tcg_constant_i32(ctx->base.pc_next); + push(pc); } =20 /* jmp rs */ @@ -1634,7 +1663,7 @@ static bool trans_BSR_l(DisasContext *ctx, arg_BSR_l = *a) /* rts */ static bool trans_RTS(DisasContext *ctx, arg_RTS *a) { - pop(ctx, cpu_pc); + pop(cpu_pc); ctx->base.is_jmp =3D DISAS_JUMP; return true; } @@ -1675,7 +1704,7 @@ static bool trans_SMOVB(DisasContext *ctx, arg_SMOVB = *a) =20 #define STRING(op) \ do { \ - TCGv_i32 size =3D tcg_constant_i32(a->sz); \ + TCGv size =3D tcg_constant_i32(a->sz); \ gen_helper_##op(tcg_env, size); \ } while (0) =20 @@ -1803,10 +1832,80 @@ static bool trans_MVTACLO(DisasContext *ctx, arg_MV= TACLO *a) return true; } =20 +/* RXv2 accumulator moves used by interrupt context save/restore. */ +static bool rx_acc_move(DisasContext *ctx, int reg, int acc, + int part, bool write) +{ + TCGv_i64 value; + int offset; + + if (!require_isa(ctx, RX_ISA_V2)) { + return false; + } + if (part =3D=3D 2) { + offset =3D offsetof(CPURXState, acc_guard) + acc * sizeof(uint32_t= ); + if (write) { + TCGv_i32 guard =3D tcg_temp_new_i32(); + tcg_gen_ext8s_i32(guard, cpu_regs[reg]); + tcg_gen_st_i32(guard, tcg_env, offset); + } else { + tcg_gen_ld_i32(cpu_regs[reg], tcg_env, offset); + } + return true; + } + value =3D acc ? tcg_temp_new_i64() : cpu_acc; + if (acc) { + tcg_gen_ld_i64(value, tcg_env, offsetof(CPURXState, acc1)); + } + if (write) { + TCGv_i64 src =3D tcg_temp_new_i64(); + tcg_gen_extu_i32_i64(src, cpu_regs[reg]); + tcg_gen_deposit_i64(value, value, src, part * 32, 32); + if (acc) { + tcg_gen_st_i64(value, tcg_env, offsetof(CPURXState, acc1)); + } + } else if (part) { + tcg_gen_extrh_i64_i32(cpu_regs[reg], value); + } else { + tcg_gen_extrl_i64_i32(cpu_regs[reg], value); + } + return true; +} + +static bool trans_MVTACHI_A1(DisasContext *ctx, arg_MVTACHI_A1 *a) +{ + return rx_acc_move(ctx, a->r, 1, 1, true); +} + +static bool trans_MVTACLO_A1(DisasContext *ctx, arg_MVTACLO_A1 *a) +{ + return rx_acc_move(ctx, a->r, 1, 0, true); +} + +static bool trans_MVTACGU(DisasContext *ctx, arg_MVTACGU *a) +{ + return rx_acc_move(ctx, a->r, a->a, 2, true); +} + +static bool trans_MVFACHI_A1(DisasContext *ctx, arg_MVFACHI_A1 *a) +{ + return rx_acc_move(ctx, a->r, 1, 1, false); +} + +static bool trans_MVFACLO(DisasContext *ctx, arg_MVFACLO *a) +{ + return rx_acc_move(ctx, a->r, a->a, 0, false); +} + +static bool trans_MVFACGU(DisasContext *ctx, arg_MVFACGU *a) +{ + return rx_acc_move(ctx, a->r, a->a, 2, false); +} + /* racw #imm */ static bool trans_RACW(DisasContext *ctx, arg_RACW *a) { - TCGv_i32 imm =3D tcg_constant_i32(a->imm + 1); + TCGv imm =3D tcg_constant_i32(a->imm + 1); gen_helper_racw(tcg_env, imm); return true; } @@ -1814,8 +1913,8 @@ static bool trans_RACW(DisasContext *ctx, arg_RACW *a) /* sat rd */ static bool trans_SAT(DisasContext *ctx, arg_SAT *a) { - TCGv_i32 tmp, z; - tmp =3D tcg_temp_new_i32(); + TCGv tmp, z; + tmp =3D tcg_temp_new(); z =3D tcg_constant_i32(0); /* S =3D=3D 1 -> 0xffffffff / S =3D=3D 0 -> 0x00000000 */ tcg_gen_sari_i32(tmp, cpu_psw_s, 31); @@ -1838,7 +1937,7 @@ static bool trans_SATR(DisasContext *ctx, arg_SATR *a) static bool cat3(trans_, name, _ir)(DisasContext *ctx, \ cat3(arg_, name, _ir) * a) \ { \ - TCGv_i32 imm =3D tcg_constant_i32(li(ctx, 0)); \ + TCGv imm =3D tcg_constant_i32(li(ctx, 0)); \ gen_helper_##op(cpu_regs[a->rd], tcg_env, \ cpu_regs[a->rd], imm); \ return true; \ @@ -1846,19 +1945,22 @@ static bool trans_SATR(DisasContext *ctx, arg_SATR = *a) static bool cat3(trans_, name, _mr)(DisasContext *ctx, \ cat3(arg_, name, _mr) * a) \ { \ - TCGv_i32 val, mem; \ - mem =3D tcg_temp_new_i32(); \ + TCGv val, mem; \ + mem =3D tcg_temp_new(); \ val =3D rx_load_source(ctx, mem, a->ld, MO_32, a->rs); \ gen_helper_##op(cpu_regs[a->rd], tcg_env, \ cpu_regs[a->rd], val); \ return true; \ } =20 -#define FCONVOP(name, op) \ +#define FCONVOP(name, op, isa) \ static bool trans_##name(DisasContext *ctx, arg_##name * a) \ { \ - TCGv_i32 val, mem; \ - mem =3D tcg_temp_new_i32(); \ + TCGv val, mem; \ + if (!require_isa(ctx, isa)) { \ + return false; \ + } \ + mem =3D tcg_temp_new(); \ val =3D rx_load_source(ctx, mem, a->ld, MO_32, a->rs); \ gen_helper_##op(cpu_regs[a->rd], tcg_env, val); \ return true; \ @@ -1872,7 +1974,7 @@ FOP(FDIV, fdiv) /* fcmp #imm, rd */ static bool trans_FCMP_ir(DisasContext *ctx, arg_FCMP_ir * a) { - TCGv_i32 imm =3D tcg_constant_i32(li(ctx, 0)); + TCGv imm =3D tcg_constant_i32(li(ctx, 0)); gen_helper_fcmp(tcg_env, cpu_regs[a->rd], imm); return true; } @@ -1881,84 +1983,702 @@ static bool trans_FCMP_ir(DisasContext *ctx, arg_F= CMP_ir * a) /* fcmp rs, rd */ static bool trans_FCMP_mr(DisasContext *ctx, arg_FCMP_mr *a) { - TCGv_i32 val, mem; - mem =3D tcg_temp_new_i32(); + TCGv val, mem; + mem =3D tcg_temp_new(); val =3D rx_load_source(ctx, mem, a->ld, MO_32, a->rs); gen_helper_fcmp(tcg_env, cpu_regs[a->rd], val); return true; } =20 -FCONVOP(FTOI, ftoi) -FCONVOP(ROUND, round) +FCONVOP(FTOI, ftoi, RX_ISA_V1) +FCONVOP(FTOU, ftou, RX_ISA_V2) +FCONVOP(ROUND, round, RX_ISA_V1) =20 /* itof rs, rd */ /* itof dsp[rs], rd */ static bool trans_ITOF(DisasContext *ctx, arg_ITOF * a) { - TCGv_i32 val, mem; - mem =3D tcg_temp_new_i32(); + TCGv val, mem; + mem =3D tcg_temp_new(); val =3D rx_load_source(ctx, mem, a->ld, a->mi, a->rs); gen_helper_itof(cpu_regs[a->rd], tcg_env, val); return true; } =20 -static void rx_bsetm(DisasContext *ctx, TCGv_i32 mem, TCGv_i32 mask) +/* utof dsp[rs], rd (RXv2) */ +static bool trans_UTOF(DisasContext *ctx, arg_UTOF * a) +{ + TCGv val, mem; + if (!require_isa(ctx, RX_ISA_V2)) { + return false; + } + mem =3D tcg_temp_new(); + val =3D rx_load_source(ctx, mem, a->ld, a->mi, a->rs); + gen_helper_utof(cpu_regs[a->rd], tcg_env, val); + return true; +} + +/* + * RXv3 DPFPU register access. DR0-DR15 are dedicated 64-bit registers, + * architecturally distinct from the general purpose registers; DRHn and + * DRLn name the high and low 32-bit halves of DRn. + */ +static TCGv_i64 dr_read(int n) +{ + return cpu_dregs[n]; +} + +static void dr_write(int n, TCGv_i64 val) +{ + tcg_gen_mov_i64(cpu_dregs[n], val); +} + +/* Write val into the high or low 32-bit half of DRn, preserving the other= . */ +static void drh_write(int n, TCGv val) +{ + TCGv_i64 t =3D tcg_temp_new_i64(); + tcg_gen_extu_i32_i64(t, val); + tcg_gen_deposit_i64(cpu_dregs[n], cpu_dregs[n], t, 32, 32); +} + +static void drl_write(int n, TCGv val) +{ + TCGv_i64 t =3D tcg_temp_new_i64(); + tcg_gen_extu_i32_i64(t, val); + tcg_gen_deposit_i64(cpu_dregs[n], cpu_dregs[n], t, 0, 32); +} + +/* dadd DRdrs1, DRdrd, DRdrs2 (DRdrd =3D DRdrs1 + DRdrs2) */ +static bool trans_DADD(DisasContext *ctx, arg_DADD *a) +{ + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + TCGv_i64 s1 =3D dr_read(a->drs1); + TCGv_i64 s2 =3D dr_read(a->drs2); + TCGv_i64 d =3D tcg_temp_new_i64(); + gen_helper_dadd(d, tcg_env, s1, s2); + dr_write(a->drd, d); + return true; +} + +static bool trans_DSUB(DisasContext *ctx, arg_DSUB *a) { - TCGv_i32 val; - val =3D tcg_temp_new_i32(); - rx_gen_ld(ctx, MO_8, val, mem); + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + TCGv_i64 s1 =3D dr_read(a->drs1); + TCGv_i64 s2 =3D dr_read(a->drs2); + TCGv_i64 d =3D tcg_temp_new_i64(); + gen_helper_dsub(d, tcg_env, s1, s2); + dr_write(a->drd, d); + return true; +} + +static bool trans_DMUL(DisasContext *ctx, arg_DMUL *a) +{ + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + TCGv_i64 s1 =3D dr_read(a->drs1); + TCGv_i64 s2 =3D dr_read(a->drs2); + TCGv_i64 d =3D tcg_temp_new_i64(); + gen_helper_dmul(d, tcg_env, s1, s2); + dr_write(a->drd, d); + return true; +} + +static bool trans_DDIV(DisasContext *ctx, arg_DDIV *a) +{ + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + TCGv_i64 s1 =3D dr_read(a->drs1); + TCGv_i64 s2 =3D dr_read(a->drs2); + TCGv_i64 d =3D tcg_temp_new_i64(); + gen_helper_ddiv(d, tcg_env, s1, s2); + dr_write(a->drd, d); + return true; +} + +static bool trans_DCMP(DisasContext *ctx, arg_DCMP *a) +{ + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + gen_helper_dcmp(tcg_env, tcg_constant_i32(a->cd), + cpu_dregs[a->drs1], cpu_dregs[a->drs2]); + return true; +} + +static bool trans_DMOV(DisasContext *ctx, arg_DMOV *a) +{ + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + TCGv_i64 s =3D dr_read(a->drs); + dr_write(a->drd, s); + return true; +} + +static bool trans_DABS(DisasContext *ctx, arg_DABS *a) +{ + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + TCGv_i64 s =3D dr_read(a->drs); + TCGv_i64 d =3D tcg_temp_new_i64(); + gen_helper_dabs(d, tcg_env, s); + dr_write(a->drd, d); + return true; +} + +static bool trans_DNEG(DisasContext *ctx, arg_DNEG *a) +{ + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + TCGv_i64 s =3D dr_read(a->drs); + TCGv_i64 d =3D tcg_temp_new_i64(); + gen_helper_dneg(d, tcg_env, s); + dr_write(a->drd, d); + return true; +} + +static bool trans_DSQRT(DisasContext *ctx, arg_DSQRT *a) +{ + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + TCGv_i64 s =3D dr_read(a->drs); + TCGv_i64 d =3D tcg_temp_new_i64(); + gen_helper_dsqrt(d, tcg_env, s); + dr_write(a->drd, d); + return true; +} + +static bool trans_DROUND(DisasContext *ctx, arg_DROUND *a) +{ + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + TCGv_i64 s =3D dr_read(a->drs); + TCGv_i64 d =3D tcg_temp_new_i64(); + gen_helper_dround(d, tcg_env, s); + dr_write(a->drd, d); + return true; +} + +/* + * dtoi / dtou / dtof convert within the DPFPU: the 32-bit result is writt= en + * to the low half of the destination DR register, from where DMOV.L moves= it + * out to a GPR. The high half is cleared. + */ +static void dr_write_low32(int n, TCGv val) +{ + TCGv_i64 t =3D tcg_temp_new_i64(); + tcg_gen_extu_i32_i64(t, val); + tcg_gen_mov_i64(cpu_dregs[n], t); +} + +/* dtoi DRdrs, DRdrd -- convert double to signed int32 */ +static bool trans_DTOI(DisasContext *ctx, arg_DTOI *a) +{ + TCGv res; + + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + res =3D tcg_temp_new(); + gen_helper_dtoi(res, tcg_env, dr_read(a->drs)); + dr_write_low32(a->drd, res); + return true; +} + +/* dtou DRdrs, DRdrd -- convert double to unsigned int32 */ +static bool trans_DTOU(DisasContext *ctx, arg_DTOU *a) +{ + TCGv res; + + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + res =3D tcg_temp_new(); + gen_helper_dtou(res, tcg_env, dr_read(a->drs)); + dr_write_low32(a->drd, res); + return true; +} + +/* dtof DRdrs, DRdrd -- convert double to float32 */ +static bool trans_DTOF(DisasContext *ctx, arg_DTOF *a) +{ + TCGv res; + + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + res =3D tcg_temp_new(); + gen_helper_dtof(res, tcg_env, dr_read(a->drs)); + dr_write_low32(a->drd, res); + return true; +} + +/* itod rs, DRdrd -- convert signed int32 to double */ +static bool trans_ITOD(DisasContext *ctx, arg_ITOD *a) +{ + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + TCGv_i64 d =3D tcg_temp_new_i64(); + gen_helper_itod(d, tcg_env, cpu_regs[a->rs]); + dr_write(a->drd, d); + return true; +} + +/* utod rs, DRdrd -- convert unsigned int32 to double */ +static bool trans_UTOD(DisasContext *ctx, arg_UTOD *a) +{ + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + TCGv_i64 d =3D tcg_temp_new_i64(); + gen_helper_utod(d, tcg_env, cpu_regs[a->rs]); + dr_write(a->drd, d); + return true; +} + +/* ftod rs, DRdrd -- convert float32 to double */ +static bool trans_FTOD(DisasContext *ctx, arg_FTOD *a) +{ + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + TCGv_i64 d =3D tcg_temp_new_i64(); + gen_helper_ftod(d, tcg_env, cpu_regs[a->rs]); + dr_write(a->drd, d); + return true; +} + +/* + * dpushm.d DRn-DRm / dpopm.d DRn-DRm. + * The encoded rnum field holds (last - first), so the range covers + * rnum + 1 registers. Pushes run from the highest register down so that + * the lowest-numbered register ends up at the lowest address. + */ +static bool trans_DPUSHM(DisasContext *ctx, arg_DPUSHM *a) +{ + int i; + + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + for (i =3D a->drd + a->rnum; i >=3D a->drd; i--) { + TCGv_i32 hi =3D tcg_temp_new_i32(); + TCGv_i32 lo =3D tcg_temp_new_i32(); + tcg_gen_extr_i64_i32(lo, hi, cpu_dregs[i & (NUM_DREGS - 1)]); + tcg_gen_subi_i32(cpu_sp, cpu_sp, 4); + rx_gen_st(MO_32, hi, cpu_sp); + tcg_gen_subi_i32(cpu_sp, cpu_sp, 4); + rx_gen_st(MO_32, lo, cpu_sp); + } + return true; +} + +static bool trans_DPOPM(DisasContext *ctx, arg_DPOPM *a) +{ + int i; + + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + for (i =3D a->drd; i <=3D a->drd + a->rnum; i++) { + TCGv_i64 dr =3D tcg_temp_new_i64(); + TCGv_i32 hi =3D tcg_temp_new_i32(); + TCGv_i32 lo =3D tcg_temp_new_i32(); + rx_gen_ld(MO_32, lo, cpu_sp); + tcg_gen_addi_i32(cpu_sp, cpu_sp, 4); + rx_gen_ld(MO_32, hi, cpu_sp); + tcg_gen_addi_i32(cpu_sp, cpu_sp, 4); + tcg_gen_concat_i32_i64(dr, lo, hi); + tcg_gen_mov_i64(cpu_dregs[i & (NUM_DREGS - 1)], dr); + } + return true; +} + +/* dpushm.l DCRn-DCRm / dpopm.l DCRn-DCRm (DPFPU control registers) */ +static bool trans_DPUSHM_l(DisasContext *ctx, arg_DPUSHM_l *a) +{ + int i; + + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + for (i =3D a->dcrd + a->rnum; i >=3D a->dcrd; i--) { + tcg_gen_subi_i32(cpu_sp, cpu_sp, 4); + rx_gen_st(MO_32, cpu_dcregs[i & (NUM_DCREGS - 1)], cpu_sp); + } + return true; +} + +static bool trans_DPOPM_l(DisasContext *ctx, arg_DPOPM_l *a) +{ + int i; + + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + for (i =3D a->dcrd; i <=3D a->dcrd + a->rnum; i++) { + rx_gen_ld(MO_32, cpu_dcregs[i & (NUM_DCREGS - 1)], cpu_sp); + tcg_gen_addi_i32(cpu_sp, cpu_sp, 4); + } + return true; +} + +/* dmov.l rs, DRLd -- write the low half of DRd, preserving the high half= */ +static bool trans_DMOV_rdrl(DisasContext *ctx, arg_DMOV_rdrl *a) +{ + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + drl_write(a->drd, cpu_regs[a->rs]); + return true; +} + +/* dmov.l rs, DRHd -- write the high half of DRd, preserving the low half = */ +static bool trans_DMOV_rdrh(DisasContext *ctx, arg_DMOV_rdrh *a) +{ + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + drh_write(a->drd, cpu_regs[a->rs]); + return true; +} + +/* + * dmov.d rs, DRHd -- write the high half of DRd and clear the low half. + * This is the single-instruction way to materialise a double whose low + * word is zero, which covers the common literals (1.0, 0.5, 2.0, ...). + */ +static bool trans_DMOVD_rdrh(DisasContext *ctx, arg_DMOVD_rdrh *a) +{ + TCGv_i64 t; + + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + t =3D tcg_temp_new_i64(); + tcg_gen_extu_i32_i64(t, cpu_regs[a->rs]); + tcg_gen_shli_i64(cpu_dregs[a->drd], t, 32); + return true; +} + +/* dmov.l DRLs, rd */ +static bool trans_DMOV_drlr(DisasContext *ctx, arg_DMOV_drlr *a) +{ + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + tcg_gen_extrl_i64_i32(cpu_regs[a->rd], cpu_dregs[a->drs]); + return true; +} + +/* dmov.l DRHs, rd */ +static bool trans_DMOV_drhr(DisasContext *ctx, arg_DMOV_drhr *a) +{ + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + tcg_gen_extrh_i64_i32(cpu_regs[a->rd], cpu_dregs[a->drs]); + return true; +} + +/* dmov.l #imm32, DRLd */ +static bool trans_DMOVL_irl(DisasContext *ctx, arg_DMOVL_irl *a) +{ + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + drl_write(a->drd, tcg_constant_i32(li(ctx, 0))); + return true; +} + +/* dmov.l #imm32, DRHd */ +static bool trans_DMOVL_irh(DisasContext *ctx, arg_DMOVL_irh *a) +{ + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + drh_write(a->drd, tcg_constant_i32(li(ctx, 0))); + return true; +} + +/* dmov.d #imm32, DRHd -- set the high half, clear the low half */ +static bool trans_DMOVD_irh(DisasContext *ctx, arg_DMOVD_irh *a) +{ + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + tcg_gen_movi_i64(cpu_dregs[a->drd], (uint64_t)li(ctx, 0) << 32); + return true; +} + +/* + * The DMOV.D memory forms name their DR operand in a post-byte that follo= ws + * the displacement, so it can only be read once rx_index_addr() has consu= med + * the displacement bytes. + * + * Per the RXv3 ISA manual the .D displacement is scaled by 4, not by the + * 8-byte transfer size: dsp:8 reaches 1020 (255 * 4) and dsp:16 reaches + * 262140 (65535 * 4). Note that binutils gas disagrees, scaling by 8 and + * requiring 8-byte alignment; the manual is taken as authoritative here. + */ +#define DMOV_DSP_SHIFT 2 +static int dmov_postbyte_dr(DisasContext *ctx) +{ + uint8_t b =3D translator_ldub(ctx->env, &ctx->base, ctx->base.pc_next); + ctx->base.pc_next +=3D 1; + return b >> 4; +} + +/* dmov.d DRs, dsp[rd] */ +static bool trans_DMOV_mst(DisasContext *ctx, arg_DMOV_mst *a) +{ + TCGv mem, addr; + int drs; + + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + mem =3D tcg_temp_new(); + addr =3D rx_index_addr(ctx, mem, a->ld, DMOV_DSP_SHIFT, a->rd); + drs =3D dmov_postbyte_dr(ctx); + tcg_gen_qemu_st_i64(cpu_dregs[drs], addr, 0, MO_LEUQ); + return true; +} + +/* dmov.d dsp[rs], DRd */ +static bool trans_DMOV_mld(DisasContext *ctx, arg_DMOV_mld *a) +{ + TCGv mem, addr; + int drd; + + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + mem =3D tcg_temp_new(); + addr =3D rx_index_addr(ctx, mem, a->ld, DMOV_DSP_SHIFT, a->rs); + drd =3D dmov_postbyte_dr(ctx); + tcg_gen_qemu_ld_i64(cpu_dregs[drd], addr, 0, MO_LEUQ); + return true; +} + +/* mvfdc DCRs, rd */ +static bool trans_MVFDC(DisasContext *ctx, arg_MVFDC *a) +{ + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + tcg_gen_mov_i32(cpu_regs[a->rd], cpu_dcregs[a->dcrs]); + return true; +} + +/* mvtdc rs, DCRd -- see helper_mvtdc for the per-register write rules */ +static bool trans_MVTDC(DisasContext *ctx, arg_MVTDC *a) +{ + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + gen_helper_mvtdc(tcg_env, tcg_constant_i32(a->dcrd), cpu_regs[a->rs]); + return true; +} + +/* + * mvfdr -- Z =3D DCMR.RES. Moves the result of the last DCMP into the PSW= Z + * flag so it can be branched on; no other flag and no DPSW bit changes. + * + * psw_z holds the inverse of the architectural Z flag (Z is set when psw_z + * reads zero), so the RES bit is inverted on the way in. + */ +static bool trans_MVFDR(DisasContext *ctx, arg_MVFDR *a) +{ + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + tcg_gen_extract_i32(cpu_psw_z, cpu_dcregs[RX_DCR_DCMR], + RX_DCMR_RES_BIT, 1); + tcg_gen_xori_i32(cpu_psw_z, cpu_psw_z, 1); + return true; +} + +/* + * bfmov / bfmovz. The 16-bit field descriptor that follows the opcode pac= ks + * bits 4:0 (dlsb - slsb), as a signed 5-bit value + * bits 9:5 dlsb + * bits 14:10 dlsb + width + * BFMOV leaves the bits outside the destination field untouched; BFMOVZ + * clears them. + */ +static bool bfmov_common(DisasContext *ctx, int rd, int rs, bool zero) +{ + unsigned bf, dlsb, msb, width; + int delta, slsb; + TCGv tmp; + + bf =3D translator_lduw_end(ctx->env, &ctx->base, ctx->base.pc_next, MO= _LE); + ctx->base.pc_next +=3D 2; + + dlsb =3D (bf >> 5) & 0x1f; + msb =3D (bf >> 10) & 0x1f; + delta =3D bf & 0x1f; + if (delta >=3D 0x10) { + delta -=3D 0x20; + } + slsb =3D dlsb - delta; + + if (msb <=3D dlsb || slsb < 0 || slsb > 31) { + /* Empty or out-of-range field: architecturally undefined. */ + return false; + } + width =3D msb - dlsb; + if (slsb + width > 32) { + width =3D 32 - slsb; + } + + tmp =3D tcg_temp_new(); + tcg_gen_extract_i32(tmp, cpu_regs[rs], slsb, width); + if (zero) { + tcg_gen_shli_i32(cpu_regs[rd], tmp, dlsb); + } else { + tcg_gen_deposit_i32(cpu_regs[rd], cpu_regs[rd], tmp, dlsb, width); + } + return true; +} + +static bool trans_BFMOV(DisasContext *ctx, arg_BFMOV *a) +{ + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + return bfmov_common(ctx, a->rd, a->rs, false); +} + +static bool trans_BFMOVZ(DisasContext *ctx, arg_BFMOVZ *a) +{ + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + return bfmov_common(ctx, a->rd, a->rs, true); +} + +/* + * save / rstr -- the RXv3 register bank save function. A bank holds R1-R1= 5, + * the USP, the FPSW and the accumulator; the banks are internal CPU state + * that nothing but these two instructions can reach, so no memory traffic + * is involved. + */ +static bool trans_SAVE_i(DisasContext *ctx, arg_SAVE_i *a) +{ + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + gen_helper_save(tcg_env, tcg_constant_i32(a->imm)); + return true; +} + +static bool trans_SAVE_r(DisasContext *ctx, arg_SAVE_r *a) +{ + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + gen_helper_save(tcg_env, cpu_regs[a->rs]); + return true; +} + +static bool trans_RSTR_i(DisasContext *ctx, arg_RSTR_i *a) +{ + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + gen_helper_rstr(tcg_env, tcg_constant_i32(a->imm)); + return true; +} + +static bool trans_RSTR_r(DisasContext *ctx, arg_RSTR_r *a) +{ + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + gen_helper_rstr(tcg_env, cpu_regs[a->rs]); + return true; +} + +/* xor rs, rs2, rd */ +static bool trans_XOR_rrr(DisasContext *ctx, arg_XOR_rrr *a) +{ + if (!require_isa(ctx, RX_ISA_V3)) { + return false; + } + rx_gen_op_rrr(rx_xor, a->rd, a->rs, a->rs2); + return true; +} + +static void rx_bsetm(TCGv mem, TCGv mask) +{ + TCGv val; + val =3D tcg_temp_new(); + rx_gen_ld(MO_8, val, mem); tcg_gen_or_i32(val, val, mask); - rx_gen_st(ctx, MO_8, val, mem); + rx_gen_st(MO_8, val, mem); } =20 -static void rx_bclrm(DisasContext *ctx, TCGv_i32 mem, TCGv_i32 mask) +static void rx_bclrm(TCGv mem, TCGv mask) { - TCGv_i32 val; - val =3D tcg_temp_new_i32(); - rx_gen_ld(ctx, MO_8, val, mem); + TCGv val; + val =3D tcg_temp_new(); + rx_gen_ld(MO_8, val, mem); tcg_gen_andc_i32(val, val, mask); - rx_gen_st(ctx, MO_8, val, mem); + rx_gen_st(MO_8, val, mem); } =20 -static void rx_btstm(DisasContext *ctx, TCGv_i32 mem, TCGv_i32 mask) +static void rx_btstm(TCGv mem, TCGv mask) { - TCGv_i32 val; - val =3D tcg_temp_new_i32(); - rx_gen_ld(ctx, MO_8, val, mem); + TCGv val; + val =3D tcg_temp_new(); + rx_gen_ld(MO_8, val, mem); tcg_gen_and_i32(val, val, mask); tcg_gen_setcondi_i32(TCG_COND_NE, cpu_psw_c, val, 0); tcg_gen_mov_i32(cpu_psw_z, cpu_psw_c); } =20 -static void rx_bnotm(DisasContext *ctx, TCGv_i32 mem, TCGv_i32 mask) +static void rx_bnotm(TCGv mem, TCGv mask) { - TCGv_i32 val; - val =3D tcg_temp_new_i32(); - rx_gen_ld(ctx, MO_8, val, mem); + TCGv val; + val =3D tcg_temp_new(); + rx_gen_ld(MO_8, val, mem); tcg_gen_xor_i32(val, val, mask); - rx_gen_st(ctx, MO_8, val, mem); + rx_gen_st(MO_8, val, mem); } =20 -static void rx_bsetr(DisasContext *ctx, TCGv_i32 reg, TCGv_i32 mask) +static void rx_bsetr(TCGv reg, TCGv mask) { tcg_gen_or_i32(reg, reg, mask); } =20 -static void rx_bclrr(DisasContext *ctx, TCGv_i32 reg, TCGv_i32 mask) +static void rx_bclrr(TCGv reg, TCGv mask) { tcg_gen_andc_i32(reg, reg, mask); } =20 -static void rx_btstr(DisasContext *ctx, TCGv_i32 reg, TCGv_i32 mask) +static inline void rx_btstr(TCGv reg, TCGv mask) { - TCGv_i32 t0; - t0 =3D tcg_temp_new_i32(); + TCGv t0; + t0 =3D tcg_temp_new(); tcg_gen_and_i32(t0, reg, mask); tcg_gen_setcondi_i32(TCG_COND_NE, cpu_psw_c, t0, 0); tcg_gen_mov_i32(cpu_psw_z, cpu_psw_c); } =20 -static void rx_bnotr(DisasContext *ctx, TCGv_i32 reg, TCGv_i32 mask) +static inline void rx_bnotr(TCGv reg, TCGv mask) { tcg_gen_xor_i32(reg, reg, mask); } @@ -1967,43 +2687,43 @@ static void rx_bnotr(DisasContext *ctx, TCGv_i32 re= g, TCGv_i32 mask) static bool cat3(trans_, name, _im)(DisasContext *ctx, \ cat3(arg_, name, _im) * a) \ { \ - TCGv_i32 mask, mem, addr; \ - mem =3D tcg_temp_new_i32(); \ + TCGv mask, mem, addr; \ + mem =3D tcg_temp_new(); \ mask =3D tcg_constant_i32(1 << a->imm); \ addr =3D rx_index_addr(ctx, mem, a->ld, MO_8, a->rs); \ - cat3(rx_, op, m)(ctx, addr, mask); \ + cat3(rx_, op, m)(addr, mask); \ return true; \ } \ static bool cat3(trans_, name, _ir)(DisasContext *ctx, \ cat3(arg_, name, _ir) * a) \ { \ - TCGv_i32 mask; \ + TCGv mask; \ mask =3D tcg_constant_i32(1 << a->imm); \ - cat3(rx_, op, r)(ctx, cpu_regs[a->rd], mask); \ + cat3(rx_, op, r)(cpu_regs[a->rd], mask); \ return true; \ } \ static bool cat3(trans_, name, _rr)(DisasContext *ctx, \ cat3(arg_, name, _rr) * a) \ { \ - TCGv_i32 mask, b; \ - mask =3D tcg_temp_new_i32(); \ - b =3D tcg_temp_new_i32(); \ + TCGv mask, b; \ + mask =3D tcg_temp_new(); \ + b =3D tcg_temp_new(); \ tcg_gen_andi_i32(b, cpu_regs[a->rs], 31); \ tcg_gen_shl_i32(mask, tcg_constant_i32(1), b); \ - cat3(rx_, op, r)(ctx, cpu_regs[a->rd], mask); \ + cat3(rx_, op, r)(cpu_regs[a->rd], mask); \ return true; \ } \ static bool cat3(trans_, name, _rm)(DisasContext *ctx, \ cat3(arg_, name, _rm) * a) \ { \ - TCGv_i32 mask, mem, addr, b; \ - mask =3D tcg_temp_new_i32(); \ - b =3D tcg_temp_new_i32(); \ + TCGv mask, mem, addr, b; \ + mask =3D tcg_temp_new(); \ + b =3D tcg_temp_new(); \ tcg_gen_andi_i32(b, cpu_regs[a->rd], 7); \ tcg_gen_shl_i32(mask, tcg_constant_i32(1), b); \ - mem =3D tcg_temp_new_i32(); \ + mem =3D tcg_temp_new(); \ addr =3D rx_index_addr(ctx, mem, a->ld, MO_8, a->rs); \ - cat3(rx_, op, m)(ctx, addr, mask); \ + cat3(rx_, op, m)(addr, mask); \ return true; \ } =20 @@ -2012,12 +2732,12 @@ BITOP(BCLR, bclr) BITOP(BTST, btst) BITOP(BNOT, bnot) =20 -static void bmcnd_op(TCGv_i32 val, TCGCond cond, int pos) +static inline void bmcnd_op(TCGv val, TCGCond cond, int pos) { - TCGv_i32 bit; + TCGv bit; DisasCompare dc; - dc.temp =3D tcg_temp_new_i32(); - bit =3D tcg_temp_new_i32(); + dc.temp =3D tcg_temp_new(); + bit =3D tcg_temp_new(); psw_cond(&dc, cond); tcg_gen_andi_i32(val, val, ~(1 << pos)); tcg_gen_setcondi_i32(dc.cond, bit, dc.value, 0); @@ -2027,13 +2747,13 @@ static void bmcnd_op(TCGv_i32 val, TCGCond cond, in= t pos) /* bmcnd #imm, dsp[rd] */ static bool trans_BMCnd_im(DisasContext *ctx, arg_BMCnd_im *a) { - TCGv_i32 val, mem, addr; - val =3D tcg_temp_new_i32(); - mem =3D tcg_temp_new_i32(); + TCGv val, mem, addr; + val =3D tcg_temp_new(); + mem =3D tcg_temp_new(); addr =3D rx_index_addr(ctx, mem, a->ld, MO_8, a->rd); - rx_gen_ld(ctx, MO_8, val, addr); + rx_gen_ld(MO_8, val, addr); bmcnd_op(val, a->cd, a->imm); - rx_gen_st(ctx, MO_8, val, addr); + rx_gen_st(MO_8, val, addr); return true; } =20 @@ -2053,7 +2773,7 @@ enum { PSW_U =3D 9, }; =20 -static void clrsetpsw(DisasContext *ctx, int cb, int val) +static inline void clrsetpsw(DisasContext *ctx, int cb, int val) { if (cb < 8) { switch (cb) { @@ -2121,7 +2841,7 @@ static bool trans_MVTIPL(DisasContext *ctx, arg_MVTIP= L *a) /* mvtc #imm, rd */ static bool trans_MVTC_i(DisasContext *ctx, arg_MVTC_i *a) { - TCGv_i32 imm; + TCGv imm; =20 imm =3D tcg_constant_i32(a->imm); move_to_cr(ctx, imm, a->cr); @@ -2145,9 +2865,9 @@ static bool trans_MVFC(DisasContext *ctx, arg_MVFC *a) /* rtfi */ static bool trans_RTFI(DisasContext *ctx, arg_RTFI *a) { - TCGv_i32 psw; + TCGv psw; if (is_privileged(ctx, 1)) { - psw =3D tcg_temp_new_i32(); + psw =3D tcg_temp_new(); tcg_gen_mov_i32(cpu_pc, cpu_bpc); tcg_gen_mov_i32(psw, cpu_bpsw); gen_helper_set_psw_rte(tcg_env, psw); @@ -2159,11 +2879,11 @@ static bool trans_RTFI(DisasContext *ctx, arg_RTFI = *a) /* rte */ static bool trans_RTE(DisasContext *ctx, arg_RTE *a) { - TCGv_i32 psw; + TCGv psw; if (is_privileged(ctx, 1)) { - psw =3D tcg_temp_new_i32(); - pop(ctx, cpu_pc); - pop(ctx, psw); + psw =3D tcg_temp_new(); + pop(cpu_pc); + pop(psw); gen_helper_set_psw_rte(tcg_env, psw); ctx->base.is_jmp =3D DISAS_EXIT; } @@ -2182,7 +2902,7 @@ static bool trans_BRK(DisasContext *ctx, arg_BRK *a) /* int #imm */ static bool trans_INT(DisasContext *ctx, arg_INT *a) { - TCGv_i32 vec; + TCGv vec; =20 tcg_debug_assert(a->imm < 0x100); vec =3D tcg_constant_i32(a->imm); @@ -2207,6 +2927,7 @@ static void rx_tr_init_disas_context(DisasContextBase= *dcbase, CPUState *cs) DisasContext *ctx =3D container_of(dcbase, DisasContext, base); ctx->env =3D cpu_env(cs); ctx->tb_flags =3D ctx->base.tb->flags; + ctx->isa_version =3D RX_CPU_GET_CLASS(cs)->isa_version; } =20 static void rx_tr_tb_start(DisasContextBase *dcbase, CPUState *cs) @@ -2271,7 +2992,7 @@ void rx_translate_code(CPUState *cs, TranslationBlock= *tb, DisasContext dc; =20 translator_loop(cs, tb, max_insns, pc, host_pc, &rx_tr_ops, &dc.base, - TCG_TYPE_VA); + TCG_TYPE_I32); } =20 #define ALLOC_REGISTER(sym, name) \ @@ -2307,6 +3028,22 @@ void rx_translate_init(void) ALLOC_REGISTER(isp, "ISP"); ALLOC_REGISTER(fintv, "FINTV"); ALLOC_REGISTER(intb, "INTB"); + ALLOC_REGISTER(extb, "EXTB"); cpu_acc =3D tcg_global_mem_new_i64(tcg_env, offsetof(CPURXState, acc), "ACC"); + + for (i =3D 0; i < NUM_DREGS; i++) { + g_autofree char *name =3D g_strdup_printf("DR%d", i); + cpu_dregs[i] =3D tcg_global_mem_new_i64(tcg_env, + offsetof(CPURXState, dr[i]), + name); + } + for (i =3D 0; i < NUM_DCREGS; i++) { + static const char * const dcrnames[NUM_DCREGS] =3D { + "DPSW", "DCMR", "DECNT", "DEPC" + }; + cpu_dcregs[i] =3D tcg_global_mem_new_i32(tcg_env, + offsetof(CPURXState, dcr[i]= ), + dcrnames[i]); + } } --=20 2.53.0 From nobody Sat Sep 26 20:51:35 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; 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 Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1789329027773320.3181919253409; Sun, 13 Sep 2026 12:50:27 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x5qCy-0005Kz-C6; Sun, 13 Sep 2026 15:49:49 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x5oqK-00014i-Lr; Sun, 13 Sep 2026 14:22:22 -0400 Received: from www675.your-server.de ([85.10.213.182]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x5oqD-0003bf-SL; Sun, 13 Sep 2026 14:22:19 -0400 Received: from sslproxy07.your-server.de ([78.47.199.104]) by www675.your-server.de with esmtpsa (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.96.2) (envelope-from ) id 1x5oq5-000LWj-20; Sun, 13 Sep 2026 20:22:05 +0200 Received: from localhost ([127.0.0.1]) by sslproxy07.your-server.de with esmtpsa (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1x5oq5-000PM1-14; Sun, 13 Sep 2026 20:22:05 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sayyadumar.com; s=default2511; h=Cc:To:In-Reply-To:References:Message-Id: Content-Transfer-Encoding:Content-Type:MIME-Version:Subject:Date:From:Sender: Reply-To:Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender :Resent-To:Resent-Cc:Resent-Message-ID; bh=6ZoAdvpTuXr4K4f7AgGo2JY75YUEJSQMCbiYugcux6g=; b=PZUMQmPieQjB3oejm3DIW3Ku// aOdNpZk+/QzfZq4Buibjpw/YPU6+YHHx+kVz8FHT6itOx3GC4hZXB/GdUCTy5yY5+j9DCkqaXfGB9 PhqjUzD6h2e3zAdCQ5smg6J4sWhGvR9Bj4LO6/ptQ4N88+7MZpurdXd0jNmFjA2ZygrEQh48mDchp qmSpuV32rHsa/VQWv3nNH91YwgHdm7Woq3ElThM+OOzeRwLYjVaiK38nN0LMivaM8pIGtfDXSVAOJ o/CU+9bMju5bgiFAMSl3Izs+TAlFbJSBaFaxOb852UiCQCE+AY8Ri+nm/KAcCvlJZVursyUr5t3G5 9bpmx0Uw==; From: Sayyad Mahammad Umar Date: Sun, 13 Sep 2026 20:21:06 +0200 Subject: [PATCH 2/2] hw/rx: add common RX peripherals MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260913-umar-rx-series-v1-2-5a5841a5d1e9@sayyadumar.com> References: <20260913-umar-rx-series-v1-0-5a5841a5d1e9@sayyadumar.com> In-Reply-To: <20260913-umar-rx-series-v1-0-5a5841a5d1e9@sayyadumar.com> To: qemu-devel@nongnu.org Cc: Yoshinori Sato , Alistair Francis , Peter Maydell , Jason Wang , qemu-arm@nongnu.org, Sayyad Mahammad Umar X-Mailer: b4 0.16.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1789323724; l=188919; i=contact@sayyadumar.com; s=qemu; h=from:subject:message-id; bh=704bB4yRlSvwWkt3R4nmRrMS9MVPnIyyyfoqmXQyO5g=; b=QSOt9t/Yg9iQbM2VWA4okdX78P2Hwe+xiF8UGgVaJdLWz9jM3zJWY1+MlE8AkaZjG1NPuSNne 6btTGUfewqgDQwoEHZIa8RrDq3GHn+0nD0ysbsLiJqxaMQ607VJ0dg3 X-Developer-Key: i=contact@sayyadumar.com; a=ed25519; pk=CsAjTLRCB7+pnzmj8902JER4gL8CTzNQFxlMZTl4wDk= X-Virus-Scanned: Clear (ClamAV 1.4.3/28122/Sun Sep 13 08:26:25 2026) 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=85.10.213.182; envelope-from=contact@sayyadumar.com; helo=www675.your-server.de X-Spam_score_int: -16 X-Spam_score: -1.7 X-Spam_bar: - X-Spam_report: (-1.7 / 5.0 requ) BAYES_00=-1.9, DKIM_INVALID=0.1, DKIM_SIGNED=0.1, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=no autolearn_force=no X-Spam_action: no action X-Mailman-Approved-At: Sun, 13 Sep 2026 15:49:41 -0400 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-ZM-MESSAGEID: 1789329033100158500 Signed-off-by: Sayyad Mahammad Umar --- MAINTAINERS | 20 + hw/adc/renesas_s12ad.c | 324 +++++++++++ hw/dma/renesas_rx_dmac.c | 317 +++++++++++ hw/dma/renesas_rx_dtc.c | 169 ++++++ hw/gpio/renesas_rx_gpio.c | 256 +++++++++ hw/misc/renesas_rx_crc.c | 219 ++++++++ hw/misc/renesas_rx_fcu.c | 985 ++++++++++++++++++++++++++++++= ++++ hw/misc/renesas_rx_romcache.c | 139 +++++ hw/misc/rx65n_sysclk.c | 190 +++++++ hw/net/renesas_etherc.c | 955 ++++++++++++++++++++++++++++++= ++ hw/rtc/renesas_rx_rtc.c | 330 ++++++++++++ hw/ssi/renesas_rspi.c | 319 +++++++++++ hw/timer/renesas_mtu3.c | 509 ++++++++++++++++++ hw/watchdog/renesas_rx_wdt.c | 193 +++++++ include/hw/adc/renesas_s12ad.h | 61 +++ include/hw/dma/renesas_rx_dmac.h | 57 ++ include/hw/dma/renesas_rx_dtc.h | 42 ++ include/hw/gpio/renesas_rx_gpio.h | 64 +++ include/hw/misc/renesas_rx_crc.h | 30 ++ include/hw/misc/renesas_rx_fcu.h | 191 +++++++ include/hw/misc/renesas_rx_romcache.h | 30 ++ include/hw/misc/rx65n_sysclk.h | 37 ++ include/hw/net/renesas_etherc.h | 109 ++++ include/hw/rtc/renesas_rx_rtc.h | 52 ++ include/hw/ssi/renesas_rspi.h | 61 +++ include/hw/timer/renesas_mtu3.h | 73 +++ include/hw/watchdog/renesas_rx_wdt.h | 48 ++ 27 files changed, 5780 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 7183babd6a..8cd5fb62c7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3034,10 +3034,30 @@ F: include/hw/timer/renesas_*.h Renesas RX peripherals R: Yoshinori Sato S: Orphan +F: hw/adc/renesas_s12ad.c +F: hw/dma/renesas_rx_*.c +F: hw/gpio/renesas_rx_gpio.c F: hw/intc/rx_icu.c +F: hw/misc/renesas_rx_*.c +F: hw/misc/rx65n_sysclk.c +F: hw/net/renesas_etherc.c +F: hw/rtc/renesas_rx_rtc.c F: hw/rx/ +F: hw/ssi/renesas_rspi.c +F: hw/timer/renesas_mtu3.c +F: hw/watchdog/renesas_rx_wdt.c +F: include/hw/adc/renesas_s12ad.h +F: include/hw/dma/renesas_rx_*.h +F: include/hw/gpio/renesas_rx_gpio.h F: include/hw/intc/rx_icu.h +F: include/hw/misc/renesas_rx_*.h +F: include/hw/misc/rx65n_sysclk.h +F: include/hw/net/renesas_etherc.h +F: include/hw/rtc/renesas_rx_rtc.h F: include/hw/rx/ +F: include/hw/ssi/renesas_rspi.h +F: include/hw/timer/renesas_mtu3.h +F: include/hw/watchdog/renesas_rx_wdt.h =20 CAN bus subsystem and hardware M: Pavel Pisa diff --git a/hw/adc/renesas_s12ad.c b/hw/adc/renesas_s12ad.c new file mode 100644 index 0000000000..e01d9ececd --- /dev/null +++ b/hw/adc/renesas_s12ad.c @@ -0,0 +1,324 @@ +/* + * Renesas RX65N 12-bit Successive Approximation A/D Converter (S12AD) + * + * Datasheet: RX65N Group, RX651 Group User's Manual: Hardware + * (Rev.1.00 R01UH0590EJ0100), Section 40 + * + * Copyright (c) 2026 Umar, Sayyad Mahammad + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "hw/core/irq.h" +#include "hw/core/registerfields.h" +#include "hw/core/qdev-properties.h" +#include "hw/adc/renesas_s12ad.h" +#include "migration/vmstate.h" + +/* + * Register offsets from device base (0x00089000). + * Reference: RX65N Group Hardware Manual Table 40.1 + */ +REG16(ADCSR, 0x00) + FIELD(ADCSR, DBLANS, 0, 5) /* double trigger channel */ + FIELD(ADCSR, GBADIE, 6, 1) /* group B scan end interrupt enable */ + FIELD(ADCSR, DBLE, 7, 1) /* double trigger enable */ + FIELD(ADCSR, EXTRG, 8, 1) /* trigger select (0=3Dsync, 1=3Dasync) */ + FIELD(ADCSR, TRGE, 9, 1) /* trigger start enable */ + FIELD(ADCSR, ADHSC, 10, 1) /* high-speed conversion */ + FIELD(ADCSR, ADIE, 12, 1) /* group A end interrupt enable */ + FIELD(ADCSR, ADCS, 13, 2) /* scan mode */ + FIELD(ADCSR, ADST, 15, 1) /* start conversion (W1 to start, hw clear= s) */ + +REG16(ADANSA0, 0x04) /* channel select 0 (AN000-AN015) */ +REG16(ADANSA1, 0x06) /* channel select 1 (AN016-AN020) */ +REG16(ADADS0, 0x08) /* average enable 0 */ +REG16(ADADS1, 0x0A) /* average enable 1 */ +REG8(ADADC, 0x0C) /* average count */ +REG16(ADCER, 0x0E) /* control extended */ +REG16(ADSTRGR, 0x10) /* start trigger select */ +REG16(ADEXICR, 0x12) /* extended input */ +REG16(ADANSB0, 0x14) /* group B channel select 0 */ +REG16(ADANSB1, 0x16) /* group B channel select 1 */ +REG16(ADDBLDR, 0x18) /* double-buffer result */ +REG16(ADRD, 0x1E) /* temperature sensor / internal Vref result */ +/* ADDR0=E2=80=93ADDR15 at offsets 0x20=E2=80=930x3E (16-bit each) */ +#define A_ADDR_BASE 0x20 +#define A_ADDR_END 0x3E /* inclusive */ +#define A_ADSAMPR 0x63 +#define A_ADSAM 0x6e + +/* + * Simulated conversion time: ~1 =C2=B5s (typical for 12-bit at 60 MHz PCL= K). + * Real hardware takes ~1=E2=80=9317 =C2=B5s depending on mode and clock. + */ +#define CONV_TIME_NS 1000 + +/* Fixed mid-scale return value for all channels: 0x0800 =3D 2048 (12-bit)= */ +#define DUMMY_RESULT 0x0800 + +static void s12ad_conv_done(void *opaque) +{ + RX65NS12ADState *s =3D opaque; + + /* Clear ADST, set results */ + s->adcsr =3D FIELD_DP16(s->adcsr, ADCSR, ADST, 0); + + /* Fill result registers with mid-scale dummy value */ + for (int i =3D 0; i < S12AD_NR_CHANNELS; i++) { + if (s->adansa0 & (1u << i)) { + s->addr[i] =3D DUMMY_RESULT << 4; /* left-justified by default= */ + } + } + s->adrd =3D DUMMY_RESULT << 4; + + /* Fire end-of-scan interrupt if enabled */ + if (FIELD_EX16(s->adcsr, ADCSR, ADIE)) { + qemu_irq_pulse(s->irq[S12AD_IRQ_S12ADI]); + } + if (FIELD_EX16(s->adcsr, ADCSR, GBADIE)) { + qemu_irq_pulse(s->irq[S12AD_IRQ_GBADI]); + } +} + +static uint64_t s12ad_read(void *opaque, hwaddr addr, unsigned size) +{ + RX65NS12ADState *s =3D opaque; + + if (addr >=3D 0x70 && addr + size <=3D 0xf0) { + uint8_t *p =3D &s->ext_regs[addr - 0x70]; + + return size =3D=3D 1 ? *p : lduw_le_p(p); + } + + if (addr >=3D A_ADDR_BASE && addr <=3D A_ADDR_END) { + int ch =3D (addr - A_ADDR_BASE) / 2; + return (ch < S12AD_NR_CHANNELS) ? s->addr[ch] : UINT64_MAX; + } + + switch (addr) { + case A_ADCSR: + return s->adcsr; + case A_ADANSA0: + return s->adansa0; + case A_ADANSA1: + return s->adansa1; + case A_ADADS0: + return s->adads0; + case A_ADADS1: + return s->adads1; + case A_ADADC: + return s->adadc; + case A_ADCER: + return s->adcer; + case A_ADSTRGR: + return s->adstrgr; + case A_ADEXICR: + return s->adexicr; + case A_ADANSB0: + return s->adansb0; + case A_ADANSB1: + return s->adansb1; + case A_ADDBLDR: + return 0; + case 0x1a: /* ADTSDR */ + case 0x1c: /* ADOCDR */ + return DUMMY_RESULT << 4; + case A_ADRD: + return s->adrd; + case A_ADSAMPR: + return s->adsampr =3D=3D 3 ? 1 : 0; + case A_ADSAM: + return s->adsampr =3D=3D 3 ? s->adsam : 0; + default: + qemu_log_mask(LOG_UNIMP, "renesas_s12ad: read 0x%" HWADDR_PRIX + " not implemented\n", + addr); + return UINT64_MAX; + } +} + +static void s12ad_write(void *opaque, hwaddr addr, uint64_t val, unsigned = size) +{ + RX65NS12ADState *s =3D opaque; + + if (addr >=3D 0x70 && addr + size <=3D 0xf0) { + uint8_t *p =3D &s->ext_regs[addr - 0x70]; + + if (size =3D=3D 1) { + *p =3D val; + } else { + stw_le_p(p, val); + } + return; + } + + switch (addr) { + case A_ADCSR: + s->adcsr =3D val & ~FIELD_DP16(0, ADCSR, ADST, 1); /* keep ADST fo= r now */ + s->adcsr |=3D (val & FIELD_DP16(0, ADCSR, ADST, 1)); + if (FIELD_EX16(val, ADCSR, ADST)) { + /* Software-triggered start: schedule conversion completion */ + timer_mod(&s->conv_timer, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + CONV_TIME_NS= ); + } + break; + case A_ADANSA0: + s->adansa0 =3D val; + break; + case A_ADANSA1: + s->adansa1 =3D val; + break; + case A_ADADS0: + s->adads0 =3D val; + break; + case A_ADADS1: + s->adads1 =3D val; + break; + case A_ADADC: + s->adadc =3D val; + break; + case A_ADCER: + s->adcer =3D val; + break; + case A_ADSTRGR: + s->adstrgr =3D val; + break; + case A_ADEXICR: + s->adexicr =3D val; + break; + case A_ADANSB0: + s->adansb0 =3D val; + break; + case A_ADANSB1: + s->adansb1 =3D val; + break; + case A_ADSAMPR: + s->adsampr =3D val & 0x3; + break; + case A_ADSAM: + if (s->adsampr =3D=3D 3) { + s->adsam =3D val & 0x20; + } + break; + default: + qemu_log_mask(LOG_UNIMP, "renesas_s12ad: write 0x%" HWADDR_PRIX + " not implemented\n", + addr); + break; + } +} + +static const MemoryRegionOps s12ad_ops =3D { + .read =3D s12ad_read, + .write =3D s12ad_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .impl =3D { + .min_access_size =3D 1, + .max_access_size =3D 2, + }, + .valid =3D { + .min_access_size =3D 1, + .max_access_size =3D 2, + }, +}; + +static void s12ad_reset(DeviceState *dev) +{ + RX65NS12ADState *s =3D RENESAS_S12AD(dev); + int i; + + s->adcsr =3D 0x0000; + s->adansa0 =3D 0x0000; + s->adansa1 =3D 0x0000; + s->adads0 =3D 0x0000; + s->adads1 =3D 0x0000; + s->adadc =3D 0x00; + s->adcer =3D 0x0000; + s->adstrgr =3D 0x0000; + s->adexicr =3D 0x0000; + s->adansb0 =3D 0x0000; + s->adansb1 =3D 0x0000; + s->adrd =3D 0x0000; + s->adsam =3D 0x0000; + s->adsampr =3D 0x00; + memset(s->ext_regs, 0, sizeof(s->ext_regs)); + for (i =3D 0; i < S12AD_NR_CHANNELS; i++) { + s->addr[i] =3D 0x0000; + } + timer_del(&s->conv_timer); +} + +static void s12ad_init(Object *obj) +{ + SysBusDevice *d =3D SYS_BUS_DEVICE(obj); + RX65NS12ADState *s =3D RENESAS_S12AD(obj); + int i; + + /* Includes the conversion-time registers at offsets 0x63 and 0x6e. */ + memory_region_init_io(&s->memory, obj, &s12ad_ops, s, + "renesas-s12ad", 0xf0); + sysbus_init_mmio(d, &s->memory); + + for (i =3D 0; i < S12AD_NR_IRQ; i++) { + sysbus_init_irq(d, &s->irq[i]); + } + timer_init_ns(&s->conv_timer, QEMU_CLOCK_VIRTUAL, s12ad_conv_done, s); +} + +static const VMStateDescription vmstate_s12ad =3D { + .name =3D "renesas-s12ad", + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (const VMStateField[]) { + VMSTATE_UINT16(adcsr, RX65NS12ADState), + VMSTATE_UINT16(adansa0, RX65NS12ADState), + VMSTATE_UINT16(adansa1, RX65NS12ADState), + VMSTATE_UINT16(adads0, RX65NS12ADState), + VMSTATE_UINT16(adads1, RX65NS12ADState), + VMSTATE_UINT8(adadc, RX65NS12ADState), + VMSTATE_UINT16(adcer, RX65NS12ADState), + VMSTATE_UINT16(adstrgr, RX65NS12ADState), + VMSTATE_UINT16(adexicr, RX65NS12ADState), + VMSTATE_UINT16(adansb0, RX65NS12ADState), + VMSTATE_UINT16(adansb1, RX65NS12ADState), + VMSTATE_UINT16(adrd, RX65NS12ADState), + VMSTATE_UINT16(adsam, RX65NS12ADState), + VMSTATE_UINT8(adsampr, RX65NS12ADState), + VMSTATE_UINT8_ARRAY(ext_regs, RX65NS12ADState, 0x80), + VMSTATE_UINT16_ARRAY(addr, RX65NS12ADState, S12AD_NR_CHANNELS), + VMSTATE_TIMER(conv_timer, RX65NS12ADState), + VMSTATE_END_OF_LIST() + } +}; + +static const Property s12ad_properties[] =3D { + DEFINE_PROP_UINT64("input-freq", RX65NS12ADState, input_freq, 0), +}; + +static void s12ad_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(klass); + + dc->vmsd =3D &vmstate_s12ad; + device_class_set_legacy_reset(dc, s12ad_reset); + device_class_set_props(dc, s12ad_properties); +} + +static const TypeInfo s12ad_info =3D { + .name =3D TYPE_RENESAS_S12AD, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RX65NS12ADState), + .instance_init =3D s12ad_init, + .class_init =3D s12ad_class_init, +}; + +static void s12ad_register_types(void) +{ + type_register_static(&s12ad_info); +} + +type_init(s12ad_register_types) diff --git a/hw/dma/renesas_rx_dmac.c b/hw/dma/renesas_rx_dmac.c new file mode 100644 index 0000000000..11e44e104c --- /dev/null +++ b/hw/dma/renesas_rx_dmac.c @@ -0,0 +1,317 @@ +/* + * Renesas RX DMA Controller (DMAC) + * + * Datasheet: RX65N Group, RX651 Group User's Manual: Hardware + * (Rev.1.00 R01UH0590EJ0100), section 17 (DMAC) + * + * Copyright (c) 2026 Umar, Sayyad Mahammad + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * + * Models the eight DMAC channels. Hardware (peripheral-triggered) activat= ion is + * not modelled; software-requested transfers (DMREQ.SWREQ) are performed + * synchronously, which covers memory-to-memory use and driver self-tests.= The + * source/destination address increment modes and 8/16/32-bit transfer uni= t are + * honoured. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "hw/dma/renesas_rx_dmac.h" +#include "hw/core/irq.h" +#include "hw/core/qdev-properties.h" +#include "migration/vmstate.h" +#include "system/address-spaces.h" + +/* Per-channel register offsets. */ +#define R_DMSAR 0x00 /* 32-bit */ +#define R_DMDAR 0x04 /* 32-bit */ +#define R_DMCRA 0x08 /* 32-bit */ +#define R_DMCRB 0x0C /* 16-bit */ +#define R_DMTMD 0x10 /* 16-bit */ +#define R_DMINT 0x13 /* 8-bit */ +#define R_DMAMD 0x14 /* 16-bit */ +#define R_DMOFR 0x18 /* 32-bit */ +#define R_DMCNT 0x1C /* 8-bit */ +#define R_DMREQ 0x1D /* 8-bit */ +#define R_DMSTS 0x1E /* 8-bit */ + +/* Common registers. */ +#define R_DMAST 0x200 /* 8-bit */ + +/* DMTMD: SZ field (bits 9:8) =3D transfer unit. */ +#define DMTMD_SZ(v) (((v) >> 8) & 3) +/* DMAMD: SM (bits 9:8) source mode, DM (bits 13:12) destination mode. */ +#define DMAMD_SM(v) (((v) >> 8) & 3) +#define DMAMD_DM(v) (((v) >> 12) & 3) +#define ADDR_MODE_FIXED 0 +#define ADDR_MODE_OFF 1 /* offset addition (not modelled, treated fixe= d) */ +#define ADDR_MODE_INC 2 +#define ADDR_MODE_DEC 3 + +#define DMCNT_DTE (1u << 0) /* transfer enable */ +#define DMREQ_SWREQ (1u << 0) /* software request */ +#define DMREQ_CLRS (1u << 4) +#define DMSTS_ACT (1u << 7) +#define DMSTS_DTIF (1u << 1) /* transfer-end interrupt flag */ + +static unsigned dmac_unit_bytes(uint16_t dmtmd) +{ + switch (DMTMD_SZ(dmtmd)) { + case 0: + return 1; + case 1: + return 2; + default: return 4; + } +} + +static void dmac_step_addr(uint32_t *addr, unsigned mode, unsigned bytes) +{ + if (mode =3D=3D ADDR_MODE_INC) { + *addr +=3D bytes; + } else if (mode =3D=3D ADDR_MODE_DEC) { + *addr -=3D bytes; + } +} + +/* Perform a software-requested transfer on a channel. */ +static void dmac_do_transfer(RenesasRxDmacState *s, int n) +{ + RxDmacChannel *c =3D &s->ch[n]; + unsigned bytes =3D dmac_unit_bytes(c->dmtmd); + unsigned smode =3D DMAMD_SM(c->dmamd); + unsigned dmode =3D DMAMD_DM(c->dmamd); + uint16_t count =3D c->dmcra & 0xffff; + uint8_t buf[4]; + + if (!(s->dmast & 1) || !(c->dmcnt & DMCNT_DTE)) { + return; + } + + /* A zero count means 65536 transfers on hardware; clamp it for QEMU. = */ + if (count =3D=3D 0) { + count =3D 1; + } + + while (count--) { + address_space_read(s->as, c->dmsar, MEMTXATTRS_UNSPECIFIED, buf, b= ytes); + address_space_write(s->as, c->dmdar, MEMTXATTRS_UNSPECIFIED, + buf, bytes); + dmac_step_addr(&c->dmsar, smode, bytes); + dmac_step_addr(&c->dmdar, dmode, bytes); + } + + /* Transfer complete: clear enable, flag status, raise interrupt. */ + c->dmcra &=3D 0xffff0000; + c->dmcnt &=3D ~DMCNT_DTE; + c->dmsts =3D (c->dmsts & ~DMSTS_ACT) | DMSTS_DTIF; + if (c->dmint & 1) { + qemu_irq_pulse(s->irq[n]); + } +} + +static uint64_t dmac_read(void *opaque, hwaddr offset, unsigned size) +{ + RenesasRxDmacState *s =3D opaque; + RxDmacChannel *c; + unsigned reg; + + if (offset =3D=3D R_DMAST) { + return s->dmast; + } + if (offset >=3D RX_DMAC_NR_CH * RX_DMAC_CH_SIZE) { + return 0; + } + + c =3D &s->ch[offset / RX_DMAC_CH_SIZE]; + reg =3D offset % RX_DMAC_CH_SIZE; + switch (reg) { + case R_DMSAR: + return c->dmsar; + case R_DMDAR: + return c->dmdar; + case R_DMCRA: + return c->dmcra; + case R_DMCRB: + return c->dmcrb; + case R_DMTMD: + return c->dmtmd; + case R_DMINT: + return c->dmint; + case R_DMAMD: + return c->dmamd; + case R_DMOFR: + return c->dmofr; + case R_DMCNT: + return c->dmcnt; + case R_DMREQ: + return c->dmreq; + case R_DMSTS: + return c->dmsts; + default: return 0; + } +} + +static void dmac_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + RenesasRxDmacState *s =3D opaque; + RxDmacChannel *c; + unsigned reg, n; + + if (offset =3D=3D R_DMAST) { + s->dmast =3D value; + return; + } + if (offset >=3D RX_DMAC_NR_CH * RX_DMAC_CH_SIZE) { + qemu_log_mask(LOG_UNIMP, "renesas-rx-dmac: write unimplemented 0x%" + HWADDR_PRIx "\n", offset); + return; + } + + n =3D offset / RX_DMAC_CH_SIZE; + c =3D &s->ch[n]; + reg =3D offset % RX_DMAC_CH_SIZE; + switch (reg) { + case R_DMSAR: + c->dmsar =3D value; + break; + case R_DMDAR: + c->dmdar =3D value; + break; + case R_DMCRA: + c->dmcra =3D value; + break; + case R_DMCRB: + c->dmcrb =3D value; + break; + case R_DMTMD: + c->dmtmd =3D value; + break; + case R_DMINT: + c->dmint =3D value; + break; + case R_DMAMD: + c->dmamd =3D value; + break; + case R_DMOFR: + c->dmofr =3D value; + break; + case R_DMCNT: + c->dmcnt =3D value; + break; + case R_DMREQ: + c->dmreq =3D value; + if (value & DMREQ_SWREQ) { + dmac_do_transfer(s, n); + c->dmreq &=3D ~DMREQ_SWREQ; + } + break; + case R_DMSTS: + /* DTIF/ACT are status flags cleared by writing the register. */ + c->dmsts =3D value; + break; + default: + qemu_log_mask(LOG_UNIMP, "renesas-rx-dmac: write unimplemented 0x%" + HWADDR_PRIx "\n", offset); + break; + } +} + +static const MemoryRegionOps dmac_ops =3D { + .read =3D dmac_read, + .write =3D dmac_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .impl =3D { .min_access_size =3D 1, .max_access_size =3D 4 }, + .valid =3D { .min_access_size =3D 1, .max_access_size =3D 4 }, +}; + +static void rx_dmac_reset(DeviceState *dev) +{ + RenesasRxDmacState *s =3D RENESAS_RX_DMAC(dev); + + memset(s->ch, 0, sizeof(s->ch)); + s->dmast =3D 0; +} + +static void rx_dmac_realize(DeviceState *dev, Error **errp) +{ + RenesasRxDmacState *s =3D RENESAS_RX_DMAC(dev); + SysBusDevice *sbd =3D SYS_BUS_DEVICE(dev); + + if (s->dma_mr =3D=3D NULL) { + s->dma_mr =3D get_system_memory(); + } + s->as =3D g_new0(AddressSpace, 1); + address_space_init(s->as, s->dma_mr, "renesas-rx-dmac"); + + memory_region_init_io(&s->mr, OBJECT(s), &dmac_ops, s, + "renesas-rx-dmac", RX_DMAC_REGS_SIZE); + sysbus_init_mmio(sbd, &s->mr); + for (int i =3D 0; i < RX_DMAC_NR_CH; i++) { + sysbus_init_irq(sbd, &s->irq[i]); + } +} + +static const Property rx_dmac_properties[] =3D { + DEFINE_PROP_LINK("dma-memory", RenesasRxDmacState, dma_mr, + TYPE_MEMORY_REGION, MemoryRegion *), +}; + +static const VMStateDescription vmstate_rx_dmac_ch =3D { + .name =3D "renesas-rx-dmac-channel", + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (const VMStateField[]) { + VMSTATE_UINT32(dmsar, RxDmacChannel), + VMSTATE_UINT32(dmdar, RxDmacChannel), + VMSTATE_UINT32(dmcra, RxDmacChannel), + VMSTATE_UINT16(dmcrb, RxDmacChannel), + VMSTATE_UINT16(dmtmd, RxDmacChannel), + VMSTATE_UINT8(dmint, RxDmacChannel), + VMSTATE_UINT16(dmamd, RxDmacChannel), + VMSTATE_UINT32(dmofr, RxDmacChannel), + VMSTATE_UINT8(dmcnt, RxDmacChannel), + VMSTATE_UINT8(dmreq, RxDmacChannel), + VMSTATE_UINT8(dmsts, RxDmacChannel), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_rx_dmac =3D { + .name =3D "renesas-rx-dmac", + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (const VMStateField[]) { + VMSTATE_STRUCT_ARRAY(ch, RenesasRxDmacState, RX_DMAC_NR_CH, 1, + vmstate_rx_dmac_ch, RxDmacChannel), + VMSTATE_UINT8(dmast, RenesasRxDmacState), + VMSTATE_END_OF_LIST() + } +}; + +static void rx_dmac_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(klass); + + dc->realize =3D rx_dmac_realize; + dc->vmsd =3D &vmstate_rx_dmac; + device_class_set_props(dc, rx_dmac_properties); + device_class_set_legacy_reset(dc, rx_dmac_reset); +} + +static const TypeInfo rx_dmac_info =3D { + .name =3D TYPE_RENESAS_RX_DMAC, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RenesasRxDmacState), + .class_init =3D rx_dmac_class_init, +}; + +static void rx_dmac_register_types(void) +{ + type_register_static(&rx_dmac_info); +} + +type_init(rx_dmac_register_types) diff --git a/hw/dma/renesas_rx_dtc.c b/hw/dma/renesas_rx_dtc.c new file mode 100644 index 0000000000..10ac87be9a --- /dev/null +++ b/hw/dma/renesas_rx_dtc.c @@ -0,0 +1,169 @@ +/* + * Renesas RX Data Transfer Controller (DTC) + * + * Datasheet: RX65N Group, RX651 Group User's Manual: Hardware + * (Rev.1.00 R01UH0590EJ0100), section 18 (DTC) + * + * Copyright (c) 2026 Umar, Sayyad Mahammad + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * + * Register-level model of the DTC. The DTC fetches RAM-resident transfer + * descriptors when activated by a peripheral interrupt; that activation p= ath is + * not modelled, so the control/status registers behave as storage and the + * module always reports idle. This lets FIT/RTOS drivers configure and en= able + * the DTC (and fall back to interrupt-driven transfers) without faulting. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "hw/dma/renesas_rx_dtc.h" +#include "hw/core/qdev-properties.h" +#include "migration/vmstate.h" + +#define R_DTCCR 0x00 /* 8-bit */ +#define R_DTCVBR 0x04 /* 32-bit */ +#define R_DTCADMOD 0x08 /* 8-bit */ +#define R_DTCST 0x0C /* 8-bit */ +#define R_DTCSTS 0x0E /* 16-bit */ +#define R_DTCIBR 0x10 /* 32-bit */ +#define R_DTCOR 0x14 /* 8-bit */ +#define R_DTCEXBR 0x18 /* 16-bit */ + +static uint64_t dtc_read(void *opaque, hwaddr offset, unsigned size) +{ + RenesasRxDtcState *s =3D opaque; + + switch (offset) { + case R_DTCCR: + return s->dtccr; + case R_DTCVBR: + return s->dtcvbr; + case R_DTCADMOD: + return s->dtcadmod; + case R_DTCST: + return s->dtcst; + case R_DTCSTS: + /*ACT=3D0: never busy */ + return s->dtcsts; + case R_DTCIBR: + return s->dtcibr; + case R_DTCOR: + return s->dtcor; + case R_DTCEXBR: + return s->dtcexbr; + default: + qemu_log_mask(LOG_UNIMP, "renesas-rx-dtc: read unimplemented 0x%" + HWADDR_PRIx "\n", offset); + return 0; + } +} + +static void dtc_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + RenesasRxDtcState *s =3D opaque; + + switch (offset) { + case R_DTCCR: + s->dtccr =3D value; + break; + case R_DTCVBR: + s->dtcvbr =3D value; + break; + case R_DTCADMOD: + s->dtcadmod =3D value; + break; + case R_DTCST: + s->dtcst =3D value; + break; + case R_DTCIBR: + s->dtcibr =3D value; + break; + case R_DTCOR: + s->dtcor =3D value; + break; + case R_DTCEXBR: + s->dtcexbr =3D value; + break; + case R_DTCSTS: + /*status, read-only */ + break; + default: + qemu_log_mask(LOG_UNIMP, "renesas-rx-dtc: write unimplemented 0x%" + HWADDR_PRIx "\n", offset); + break; + } +} + +static const MemoryRegionOps dtc_ops =3D { + .read =3D dtc_read, + .write =3D dtc_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .impl =3D { .min_access_size =3D 1, .max_access_size =3D 4 }, + .valid =3D { .min_access_size =3D 1, .max_access_size =3D 4 }, +}; + +static void rx_dtc_reset(DeviceState *dev) +{ + RenesasRxDtcState *s =3D RENESAS_RX_DTC(dev); + + s->dtccr =3D 0x08; /* RRS =3D 1 out of reset */ + s->dtcvbr =3D 0; + s->dtcadmod =3D 0; + s->dtcst =3D 0; + s->dtcsts =3D 0; + s->dtcibr =3D 0; + s->dtcor =3D 0; + s->dtcexbr =3D 0; +} + +static void rx_dtc_init(Object *obj) +{ + RenesasRxDtcState *s =3D RENESAS_RX_DTC(obj); + + memory_region_init_io(&s->mr, obj, &dtc_ops, s, + "renesas-rx-dtc", RX_DTC_REGS_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mr); +} + +static const VMStateDescription vmstate_rx_dtc =3D { + .name =3D "renesas-rx-dtc", + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (const VMStateField[]) { + VMSTATE_UINT8(dtccr, RenesasRxDtcState), + VMSTATE_UINT32(dtcvbr, RenesasRxDtcState), + VMSTATE_UINT8(dtcadmod, RenesasRxDtcState), + VMSTATE_UINT8(dtcst, RenesasRxDtcState), + VMSTATE_UINT16(dtcsts, RenesasRxDtcState), + VMSTATE_UINT32(dtcibr, RenesasRxDtcState), + VMSTATE_UINT8(dtcor, RenesasRxDtcState), + VMSTATE_UINT16(dtcexbr, RenesasRxDtcState), + VMSTATE_END_OF_LIST() + } +}; + +static void rx_dtc_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(klass); + + dc->vmsd =3D &vmstate_rx_dtc; + device_class_set_legacy_reset(dc, rx_dtc_reset); +} + +static const TypeInfo rx_dtc_info =3D { + .name =3D TYPE_RENESAS_RX_DTC, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RenesasRxDtcState), + .instance_init =3D rx_dtc_init, + .class_init =3D rx_dtc_class_init, +}; + +static void rx_dtc_register_types(void) +{ + type_register_static(&rx_dtc_info); +} + +type_init(rx_dtc_register_types) diff --git a/hw/gpio/renesas_rx_gpio.c b/hw/gpio/renesas_rx_gpio.c new file mode 100644 index 0000000000..3a40d88948 --- /dev/null +++ b/hw/gpio/renesas_rx_gpio.c @@ -0,0 +1,256 @@ +/* + * Renesas RX I/O Ports (GPIO) and Multi-Function Pin Controller (MPC) + * + * Datasheet: RX65N Group, RX651 Group User's Manual: Hardware + * (Rev.1.00 R01UH0590EJ0100), sections 20 (I/O Ports) and 22 (= MPC) + * + * Copyright (c) 2026 Umar, Sayyad Mahammad + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * + * Each 8-bit I/O port exposes a direction register (PDR), an output data + * register (PODR) and an input data register (PIDR). Output pins drive the + * corresponding "gpio-out" line (used by board LEDs); input pins read bac= k the + * level driven on the "gpio-in" line (used by board switches). The MPC PFS + * registers select alternate pin functions and behave as plain storage. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "hw/gpio/renesas_rx_gpio.h" +#include "hw/core/irq.h" +#include "hw/core/qdev-properties.h" +#include "migration/vmstate.h" + +/* Per-port register offsets within the I/O port block (index =3D port num= ber). */ +#define R_PDR 0x000 /* 1 byte per port */ +#define R_PODR 0x020 +#define R_PIDR 0x040 +#define R_PMR 0x060 +#define R_ODR 0x080 /* 2 bytes per port */ +#define R_PCR 0x0C0 +#define R_DSCR 0x0E0 +#define R_END 0x100 + +/* MPC write-protect register, relative to the MPC block base (0x0008C100)= . */ +#define R_PWPR 0x01F /* 0x0008C11F */ + +/* Compute the input-data value seen for a port. */ +static uint8_t port_pidr(RenesasRxGpioState *s, unsigned p) +{ + uint8_t value; + + /* Output pins read back their output latch; input pins read the pin. = */ + value =3D (s->podr[p] & s->pdr[p]) | (s->input[p] & ~s->pdr[p]); + return value; +} + +/* Drive the per-pin output lines for a port from PDR/PODR. */ +static void port_update_out(RenesasRxGpioState *s, unsigned p) +{ + for (unsigned b =3D 0; b < 8; b++) { + int level =3D 0; + if (s->pdr[p] & (1 << b)) { /* output pin */ + level =3D (s->podr[p] >> b) & 1; + } + qemu_set_irq(s->out[p * 8 + b], level); + } +} + +static uint64_t gpio_port_read(void *opaque, hwaddr offset, unsigned size) +{ + RenesasRxGpioState *s =3D opaque; + unsigned p =3D offset & 0x1f; + + switch (offset & ~0x1f) { + case R_PDR: + return s->pdr[p]; + case R_PODR: + return s->podr[p]; + case R_PIDR: + return port_pidr(s, p); + case R_PMR: + return s->pmr[p]; + case R_PCR: + return s->pcr[p]; + case R_DSCR: + return s->dscr[p]; + default: + if (offset >=3D R_ODR && offset < R_PCR) { + return s->odr[offset - R_ODR]; + } + qemu_log_mask(LOG_UNIMP, "renesas-rx-gpio: read unimplemented 0x%" + HWADDR_PRIx "\n", offset); + return 0; + } +} + +static void gpio_port_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + RenesasRxGpioState *s =3D opaque; + unsigned p =3D offset & 0x1f; + + switch (offset & ~0x1f) { + case R_PDR: + s->pdr[p] =3D value; + port_update_out(s, p); + break; + case R_PODR: + s->podr[p] =3D value; + port_update_out(s, p); + break; + case R_PMR: + s->pmr[p] =3D value; + break; + case R_PCR: + s->pcr[p] =3D value; + break; + case R_DSCR: + s->dscr[p] =3D value; + break; + case R_PIDR: + /* Input data register is read-only. */ + break; + default: + if (offset >=3D R_ODR && offset < R_PCR) { + s->odr[offset - R_ODR] =3D value; + } else { + qemu_log_mask(LOG_UNIMP, "renesas-rx-gpio: write unimplemented= 0x%" + HWADDR_PRIx "\n", offset); + } + break; + } +} + +static const MemoryRegionOps gpio_port_ops =3D { + .read =3D gpio_port_read, + .write =3D gpio_port_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .impl =3D { .min_access_size =3D 1, .max_access_size =3D 1 }, + .valid =3D { .min_access_size =3D 1, .max_access_size =3D 1 }, +}; + +static uint64_t gpio_mpc_read(void *opaque, hwaddr offset, unsigned size) +{ + RenesasRxGpioState *s =3D opaque; + uint64_t val =3D 0; + + for (unsigned i =3D 0; i < size && offset + i < RX_GPIO_MPC_SIZE; i++)= { + val |=3D (uint64_t)s->mpc[offset + i] << (8 * i); + } + return val; +} + +static void gpio_mpc_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + RenesasRxGpioState *s =3D opaque; + + /* + * PFS registers are locked unless PWPR.PFSWE is set (and PWPR.B0WI cl= ear). + * Storing the PFS selection is otherwise sufficient: QEMU peripherals= are + * wired unconditionally, so pin muxing has no functional effect here. + */ + for (unsigned i =3D 0; i < size && offset + i < RX_GPIO_MPC_SIZE; i++)= { + s->mpc[offset + i] =3D (value >> (8 * i)) & 0xff; + } +} + +static const MemoryRegionOps gpio_mpc_ops =3D { + .read =3D gpio_mpc_read, + .write =3D gpio_mpc_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .impl =3D { .min_access_size =3D 1, .max_access_size =3D 1 }, + .valid =3D { .min_access_size =3D 1, .max_access_size =3D 1 }, +}; + +/* External input line handler: pin =3D port * 8 + bit. */ +static void gpio_set_input(void *opaque, int pin, int level) +{ + RenesasRxGpioState *s =3D opaque; + unsigned p =3D pin / 8, b =3D pin % 8; + + if (level) { + s->input[p] |=3D (1 << b); + } else { + s->input[p] &=3D ~(1 << b); + } +} + +static void rx_gpio_reset(DeviceState *dev) +{ + RenesasRxGpioState *s =3D RENESAS_RX_GPIO(dev); + + memset(s->pdr, 0, sizeof(s->pdr)); + memset(s->podr, 0, sizeof(s->podr)); + memset(s->pmr, 0, sizeof(s->pmr)); + memset(s->odr, 0, sizeof(s->odr)); + memset(s->pcr, 0, sizeof(s->pcr)); + memset(s->dscr, 0, sizeof(s->dscr)); + memset(s->mpc, 0, sizeof(s->mpc)); + /* PWPR resets with B0WI set (PFS write disabled). */ + s->mpc[R_PWPR] =3D 0x80; + + for (unsigned p =3D 0; p < RX_GPIO_NR_PORTS; p++) { + port_update_out(s, p); + } +} + +static void rx_gpio_init(Object *obj) +{ + RenesasRxGpioState *s =3D RENESAS_RX_GPIO(obj); + SysBusDevice *sbd =3D SYS_BUS_DEVICE(obj); + + memory_region_init_io(&s->port_mr, obj, &gpio_port_ops, s, + "renesas-rx-gpio.port", RX_GPIO_PORT_SIZE); + memory_region_init_io(&s->mpc_mr, obj, &gpio_mpc_ops, s, + "renesas-rx-gpio.mpc", RX_GPIO_MPC_SIZE); + sysbus_init_mmio(sbd, &s->port_mr); + sysbus_init_mmio(sbd, &s->mpc_mr); + + qdev_init_gpio_out_named(DEVICE(obj), s->out, "gpio-out", RX_GPIO_NR_P= INS); + qdev_init_gpio_in_named(DEVICE(obj), gpio_set_input, "gpio-in", + RX_GPIO_NR_PINS); +} + +static const VMStateDescription vmstate_rx_gpio =3D { + .name =3D "renesas-rx-gpio", + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (const VMStateField[]) { + VMSTATE_UINT8_ARRAY(pdr, RenesasRxGpioState, RX_GPIO_NR_PORTS), + VMSTATE_UINT8_ARRAY(podr, RenesasRxGpioState, RX_GPIO_NR_PORTS), + VMSTATE_UINT8_ARRAY(pmr, RenesasRxGpioState, RX_GPIO_NR_PORTS), + VMSTATE_UINT8_ARRAY(odr, RenesasRxGpioState, RX_GPIO_NR_PORTS * 2), + VMSTATE_UINT8_ARRAY(pcr, RenesasRxGpioState, RX_GPIO_NR_PORTS), + VMSTATE_UINT8_ARRAY(dscr, RenesasRxGpioState, RX_GPIO_NR_PORTS), + VMSTATE_UINT8_ARRAY(input, RenesasRxGpioState, RX_GPIO_NR_PORTS), + VMSTATE_UINT8_ARRAY(mpc, RenesasRxGpioState, RX_GPIO_MPC_SIZE), + VMSTATE_END_OF_LIST() + } +}; + +static void rx_gpio_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(klass); + + dc->vmsd =3D &vmstate_rx_gpio; + device_class_set_legacy_reset(dc, rx_gpio_reset); +} + +static const TypeInfo rx_gpio_info =3D { + .name =3D TYPE_RENESAS_RX_GPIO, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RenesasRxGpioState), + .instance_init =3D rx_gpio_init, + .class_init =3D rx_gpio_class_init, +}; + +static void rx_gpio_register_types(void) +{ + type_register_static(&rx_gpio_info); +} + +type_init(rx_gpio_register_types) diff --git a/hw/misc/renesas_rx_crc.c b/hw/misc/renesas_rx_crc.c new file mode 100644 index 0000000000..f06ac4a64e --- /dev/null +++ b/hw/misc/renesas_rx_crc.c @@ -0,0 +1,219 @@ +/* + * Renesas RX CRC Calculator (CRCA) + * + * Datasheet: RX65N Group, RX651 Group User's Manual: Hardware + * (Rev.2.40 R01UH0590EJ0240), section 42 + * + * Copyright (c) 2026 Umar, Sayyad Mahammad + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Writing to CRCDIR runs the selected polynomial over the data and leaves= the + * result in CRCDOR, which doubles as the initial value: it is zero out of + * reset, DORCLR puts it back, and firmware wanting a different seed write= s it + * directly. There is no final XOR, so the familiar CRC-32 of a message is + * obtained by seeding CRCDOR with all ones and inverting the result. + * + * CRCCR.LMS picks the bit order. MSB first is the plain shift-left form; = LSB + * first is the reflected form, computed with the reversed polynomial. The= two + * worked examples in section 42.3 pin this down: with the CCITT polynomia= l and + * a cleared CRCDOR, feeding 0xf0 gives 0xef1f MSB first and 0xf78f LSB fi= rst. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/bitops.h" +#include "hw/misc/renesas_rx_crc.h" +#include "hw/core/registerfields.h" +#include "hw/core/qdev-properties.h" +#include "migration/vmstate.h" + +REG8(CRCCR, 0) + FIELD(CRCCR, GPS, 0, 3) + FIELD(CRCCR, LMS, 6, 1) + FIELD(CRCCR, DORCLR, 7, 1) + +#define R_CRCDIR 0x04 +#define R_CRCDOR 0x08 + +/* CRCCR is read/write apart from the write-only DORCLR bit. */ +#define CRCCR_RW_MASK (R_CRCCR_GPS_MASK | R_CRCCR_LMS_MASK) + +/* + * Generating polynomials selected by CRCCR.GPS, without the implicit top + * term. Entries 0, 6 and 7 select "no calculation". + */ +static const struct { + uint32_t poly; + uint8_t width; +} crc_gps[8] =3D { + [1] =3D { 0x07, 8 }, /* X8 + X2 + X + 1 = */ + [2] =3D { 0x8005, 16 }, /* X16 + X15 + X2 + 1 = */ + [3] =3D { 0x1021, 16 }, /* X16 + X12 + X5 + 1 = */ + [4] =3D { 0x04c11db7, 32 }, /* X32 + X26 + X23 + ... + X2 + X + 1 = */ + [5] =3D { 0x1edc6f41, 32 }, /* X32 + X28 + X27 + ... + X8 + X6 + 1 = */ +}; + +static uint32_t bitreverse(uint32_t value, unsigned width) +{ + return revbit32(value) >> (32 - width); +} + +/* Run nbits of data through the selected polynomial. */ +static void crc_feed(RenesasRxCrcState *s, uint32_t data, unsigned nbits) +{ + unsigned gps =3D FIELD_EX8(s->crccr, CRCCR, GPS); + uint32_t poly =3D crc_gps[gps].poly; + unsigned width =3D crc_gps[gps].width; + uint32_t mask, crc; + + if (width =3D=3D 0) { + return; /* GPS selects no calculation */ + } + if (nbits > width) { + qemu_log_mask(LOG_GUEST_ERROR, + "renesas-rx-crc: %u-bit write to CRCDIR with a %u-bi= t " + "polynomial selected\n", nbits, width); + nbits =3D width; + } + + mask =3D width =3D=3D 32 ? UINT32_MAX : (1u << width) - 1; + crc =3D s->crcdor & mask; + + if (FIELD_EX8(s->crccr, CRCCR, LMS)) { + /* MSB first: shift up, align the data with the top of the registe= r. */ + crc ^=3D (data << (width - nbits)) & mask; + while (nbits--) { + crc =3D (crc & (mask ^ (mask >> 1))) ? ((crc << 1) ^ poly) & m= ask + : (crc << 1) & mask; + } + } else { + /* LSB first: the reflected form, so shift down with a reversed po= ly. */ + uint32_t rpoly =3D bitreverse(poly, width); + + crc ^=3D data & mask; + while (nbits--) { + crc =3D (crc & 1) ? (crc >> 1) ^ rpoly : crc >> 1; + } + } + + s->crcdor =3D crc; +} + +static uint64_t crc_read(void *opaque, hwaddr offset, unsigned size) +{ + RenesasRxCrcState *s =3D opaque; + + switch (offset) { + case A_CRCCR: + /* DORCLR always reads back as 0. */ + return s->crccr & CRCCR_RW_MASK; + case R_CRCDIR: + /* + * CRCDIR is documented readable, but the hardware keeps no separa= te + * copy of the data written to it, so nothing meaningful comes bac= k. + */ + return 0; + case R_CRCDOR: + if (size >=3D 4) { + return s->crcdor; + } + return s->crcdor & ((1u << (size * 8)) - 1); + default: + qemu_log_mask(LOG_GUEST_ERROR, + "renesas-rx-crc: read from unmapped offset 0x%" + HWADDR_PRIx "\n", offset); + return 0; + } +} + +static void crc_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + RenesasRxCrcState *s =3D opaque; + + switch (offset) { + case A_CRCCR: + s->crccr =3D value & CRCCR_RW_MASK; + if (FIELD_EX8(value, CRCCR, DORCLR)) { + s->crcdor =3D 0; + } + break; + case R_CRCDIR: + crc_feed(s, value, size * 8); + break; + case R_CRCDOR: + /* Writable so firmware can seed the calculation. */ + if (size >=3D 4) { + s->crcdor =3D value; + } else { + uint32_t m =3D (1u << (size * 8)) - 1; + s->crcdor =3D (s->crcdor & ~m) | (value & m); + } + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "renesas-rx-crc: write to unmapped offset 0x%" + HWADDR_PRIx "\n", offset); + break; + } +} + +static const MemoryRegionOps crc_ops =3D { + .read =3D crc_read, + .write =3D crc_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .impl =3D { .min_access_size =3D 1, .max_access_size =3D 4 }, + .valid =3D { .min_access_size =3D 1, .max_access_size =3D 4 }, +}; + +static void rx_crc_reset(DeviceState *dev) +{ + RenesasRxCrcState *s =3D RENESAS_RX_CRC(dev); + + s->crccr =3D 0; + s->crcdor =3D 0; +} + +static void rx_crc_init(Object *obj) +{ + RenesasRxCrcState *s =3D RENESAS_RX_CRC(obj); + + memory_region_init_io(&s->memory, obj, &crc_ops, s, + "renesas-rx-crc", RX_CRC_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->memory); +} + +static const VMStateDescription vmstate_rx_crc =3D { + .name =3D "renesas-rx-crc", + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (const VMStateField[]) { + VMSTATE_UINT8(crccr, RenesasRxCrcState), + VMSTATE_UINT32(crcdor, RenesasRxCrcState), + VMSTATE_END_OF_LIST() + } +}; + +static void rx_crc_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(klass); + + dc->vmsd =3D &vmstate_rx_crc; + device_class_set_legacy_reset(dc, rx_crc_reset); +} + +static const TypeInfo rx_crc_info =3D { + .name =3D TYPE_RENESAS_RX_CRC, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RenesasRxCrcState), + .instance_init =3D rx_crc_init, + .class_init =3D rx_crc_class_init, +}; + +static void rx_crc_register_types(void) +{ + type_register_static(&rx_crc_info); +} + +type_init(rx_crc_register_types) diff --git a/hw/misc/renesas_rx_fcu.c b/hw/misc/renesas_rx_fcu.c new file mode 100644 index 0000000000..309abdb0f2 --- /dev/null +++ b/hw/misc/renesas_rx_fcu.c @@ -0,0 +1,985 @@ +/* + * Renesas RX Flash Control Unit (FCU) with FACI command interface + * + * Datasheet: RX65N Group, RX651 Group User's Manual: Hardware + * (Rev.1.00 R01UH0590EJ0100), section 6 (Flash Memory) + * + * Copyright (c) 2026 Umar, Sayyad Mahammad + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * + * The RX65N programs and erases its on-chip code and data flash through t= he + * Flash Application Command Interface (FACI). Firmware first enters P/E m= ode + * for the target array via FENTRYR, then issues command sequences by writ= ing + * command bytes and data words to the destination address inside the flash + * array itself, polling FSTATR.FRDY for completion. + * + * Both flash arrays are modelled as ROM-device memory regions: ordinary r= eads + * and instruction fetches hit the backing RAM directly (fast, and firmwar= e is + * still loaded by the QEMU ROM loader, which bypasses these write callbac= ks), + * while guest writes are routed here and interpreted as FACI commands. QE= MU + * models no real program/erase timing, so every command completes + * synchronously and FRDY always reads ready. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/bswap.h" +#include "hw/misc/renesas_rx_fcu.h" +#include "hw/core/irq.h" +#include "hw/core/qdev-properties.h" +#include "hw/core/qdev-properties-system.h" +#include "system/block-backend.h" +#include "hw/block/block.h" +#include "qemu/error-report.h" +#include "qemu/units.h" +#include "migration/vmstate.h" +#include "qapi/error.h" + +/* FACI control register offsets from the register block base (0x007FE000)= . */ +#define R_FASTAT 0x010 /* Flash Access Status (8-bit) */ +#define R_FAEINT 0x014 /* Flash Access Err Int Enable (8-bit) */ +#define R_FRDYIE 0x018 /* Flash Ready Int Enable (8-bit) */ +#define R_FSADDR 0x030 /* Flash Processing Start Address (32-bit) */ +#define R_FEADDR 0x034 /* Flash Processing End Address (32-bit) */ +#define R_FSTATR 0x080 /* Flash Status (32-bit) */ +#define R_FENTRYR 0x084 /* Flash P/E Mode Entry (16-bit) */ +#define R_FPROTR 0x088 /* Flash Protection (16-bit) */ +#define R_FSUACR 0x08C /* Startup Area Control (16-bit) */ +#define R_FCMDR 0x0A0 /* FACI Command (16-bit) */ +#define R_FPESTAT 0x0C0 /* P/E Error Status (16-bit) */ +#define R_FBCCNT 0x0D0 /* Blank Check Control (16-bit) */ +#define R_FBCSTAT 0x0D4 /* Blank Check Status (8-bit) */ +#define R_FPSADDR 0x0D8 /* Programmed/Erased Start Addr (32-bit) */ +#define R_FCPSR 0x0E0 /* Clear/Processing Switch (16-bit) */ +#define R_FPCKAR 0x0E4 /* Processing Clock Notification (16-bit) */ + +/* FSTATR bit definitions. */ +#define FSTATR_FRDY (1u << 6) /* Flash ready */ +#define FSTATR_PRGERR (1u << 13) /* Programming error */ +#define FSTATR_ERSERR (1u << 14) /* Erasure error */ +#define FSTATR_ILGLERR (1u << 15) /* Illegal command error */ +#define FSTATR_OTERR (1u << 16) /* Other error */ +#define FSTATR_ERRORS (FSTATR_PRGERR | FSTATR_ERSERR | \ + FSTATR_ILGLERR | FSTATR_OTERR) + +/* FASTAT bit definitions. */ +#define FASTAT_CMDLK (1u << 4) /* Command lock */ + +/* FENTRYR: key code in the upper byte, target bits in the lower byte. */ +#define FENTRYR_KEY 0xAA00 +#define FENTRYR_CODE 0x0001 /* code flash P/E mode */ +#define FENTRYR_DATA 0x0080 /* data flash P/E mode */ + +/* FBCSTAT: BCST =3D 0 means the checked range is blank (all 0xFF). */ +#define FBCSTAT_BCST (1u << 0) + +/* FACI command codes. */ +#define FACI_CMD_PROGRAM 0xE8 +#define FACI_CMD_BLOCK_ERASE 0x20 +#define FACI_CMD_BLANK_CHECK 0x71 +#define FACI_CMD_CONFIG_SET 0x40 +#define FACI_CMD_CLEAR_STAT 0x50 +#define FACI_CMD_FORCED_STOP 0xB3 +#define FACI_CMD_CONFIRM 0xD0 + +/* Erase block granularity (HW manual section 6). */ +#define CFLASH_ERASE_BLOCK 0x8000 /* 32 KiB code flash block */ +#define CFLASH_SMALL_BLOCK 0x2000 /* 8 KiB block, blocks 0 to 7 */ +#define CFLASH_SMALL_REGION 0x10000 /* the eight small blocks span 64 = KiB */ +#define CFLASH_MIN_DUAL_SIZE (1536 * KiB) +#define DFLASH_ERASE_BLOCK 0x40 /* 64-byte data flash block */ + +static bool target_in_pe_mode(RenesasRxFcuState *s, RxFcuTarget t) +{ + return t =3D=3D RX_FCU_CFLASH ? (s->fentryr & FENTRYR_CODE) + : (s->fentryr & FENTRYR_DATA); +} + +static uint8_t *target_storage(RenesasRxFcuState *s, RxFcuTarget t) +{ + return t =3D=3D RX_FCU_CFLASH ? s->cflash_ptr : s->dflash_ptr; +} + +static uint32_t target_size(RenesasRxFcuState *s, RxFcuTarget t) +{ + return t =3D=3D RX_FCU_CFLASH ? s->cflash_size : s->dflash_size; +} + +/* Translate an absolute CPU flash address to an array-relative offset. */ +static bool addr_to_offset(RenesasRxFcuState *s, RxFcuTarget t, + uint32_t addr, uint32_t *off) +{ + uint32_t base =3D t =3D=3D RX_FCU_CFLASH ? s->cflash_base : s->dflash_= base; + + if (t =3D=3D RX_FCU_CFLASH && s->dual_mode) { + uint32_t bank_size =3D s->cflash_size / 2; + uint32_t lower_base =3D s->cflash_base - s->dual_bank_gap_size; + uint32_t upper_base =3D s->cflash_base + bank_size; + uint32_t bank; + + if (addr >=3D lower_base && addr - lower_base < bank_size) { + bank =3D 0; + *off =3D addr - lower_base; + } else if (addr >=3D upper_base && addr - upper_base < bank_size) { + bank =3D 1; + *off =3D addr - upper_base; + } else { + return false; + } + if (s->bank_swapped) { + bank ^=3D 1; + } + *off +=3D bank * bank_size; + return true; + } + + if (addr < base || addr - base >=3D target_size(s, t)) { + return false; + } + *off =3D addr - base; + return true; +} + +/* + * Code flash erase blocks are not uniform: the top 64 KB of each bank (of= the + * whole array in linear mode) is eight 8 KB blocks, and everything below = is + * 32 KB blocks. Return the block containing a physical offset. + */ +static void cflash_erase_block(RenesasRxFcuState *s, uint32_t off, + uint32_t *start, uint32_t *len) +{ + uint32_t unit =3D s->dual_mode ? s->cflash_size / 2 : s->cflash_size; + uint32_t within =3D off % unit; + uint32_t base =3D off - within; + + if (within >=3D unit - CFLASH_SMALL_REGION) { + *len =3D CFLASH_SMALL_BLOCK; + } else { + *len =3D CFLASH_ERASE_BLOCK; + } + *start =3D base + (within & ~(*len - 1)); +} + +/* + * FCMDR pairs the command just received (CMDR, low byte) with the one bef= ore + * it (PCMDR, high byte). A two-byte sequence such as block erase therefore + * ends up reading 0x20d0: 0x20 shifted up by the 0xd0 confirm. The confirm + * that terminates a programming sequence is not recorded, which is why the + * manual lists programming as leaving CMDR at 0xe8. + */ +static void faci_set_cmdr(RenesasRxFcuState *s, uint8_t cmd) +{ + s->fcmdr =3D ((s->fcmdr & 0xff) << 8) | cmd; +} + +/* Finish a command: report ready and pulse the ready interrupt if enabled= . */ +static void faci_complete(RenesasRxFcuState *s) +{ + s->cmd_state =3D RX_FCU_ST_READY; + s->fstatr |=3D FSTATR_FRDY; + if (s->frdyie & 1) { + qemu_irq_pulse(s->frdyi); + } +} + +/* Flag an illegal command: lock the sequencer and raise the error interru= pt. */ +static void faci_illegal(RenesasRxFcuState *s) +{ + s->cmd_state =3D RX_FCU_ST_READY; + s->fstatr |=3D FSTATR_ILGLERR | FSTATR_FRDY; + s->fastat |=3D FASTAT_CMDLK; + if (s->faeint) { + qemu_set_irq(s->fiferr, 1); + } +} + +/* Write the option-setting memory back to its image, if one is attached. = */ +static void fcu_ofsm_sync(RenesasRxFcuState *s) +{ + if (!s->ofsm_blk || s->ofsm_ro) { + return; + } + if (blk_pwrite(s->ofsm_blk, 0, RX_FCU_OFSM_SIZE, s->ofsm_ptr, 0) < 0) { + error_report("renesas-rx-fcu: could not write the OFSM image"); + } +} + +/* + * Write a modified range of an array back to its block backend, if one is + * attached. Offsets are widened to sector boundaries the way pflash does. + */ +static void fcu_flash_sync(RenesasRxFcuState *s, RxFcuTarget t, + uint32_t off, uint32_t len) +{ + BlockBackend *blk =3D t =3D=3D RX_FCU_CFLASH ? s->cflash_blk : s->dfla= sh_blk; + bool ro =3D t =3D=3D RX_FCU_CFLASH ? s->cflash_ro : s->dflash_ro; + uint32_t size =3D target_size(s, t); + uint64_t start, end; + int ret; + + if (!blk || ro) { + return; + } + start =3D QEMU_ALIGN_DOWN(off, BDRV_SECTOR_SIZE); + end =3D QEMU_ALIGN_UP((uint64_t)off + len, BDRV_SECTOR_SIZE); + if (end > size) { + end =3D size; + } + if (start >=3D end) { + return; + } + ret =3D blk_pwrite(blk, start, end - start, + target_storage(s, t) + start, 0); + if (ret < 0) { + error_report("renesas-rx-fcu: could not write flash image: %s", + strerror(-ret)); + } +} + +static void faci_block_erase(RenesasRxFcuState *s, RxFcuTarget t, uint32_t= off) +{ + uint32_t size =3D target_size(s, t); + uint32_t block, start; + + if (t =3D=3D RX_FCU_CFLASH) { + cflash_erase_block(s, off, &start, &block); + } else { + block =3D DFLASH_ERASE_BLOCK; + start =3D off & ~(block - 1); + } + + if (start >=3D size) { + faci_illegal(s); + return; + } + if (start + block > size) { + block =3D size - start; + } + memset(target_storage(s, t) + start, 0xff, block); + fcu_flash_sync(s, t, start, block); + s->fpsaddr =3D (t =3D=3D RX_FCU_CFLASH ? s->cflash_base : s->dflash_ba= se) + start; + faci_complete(s); +} + +static void faci_blank_check(RenesasRxFcuState *s, RxFcuTarget t) +{ + uint32_t start, end; + const uint8_t *p; + + if (!addr_to_offset(s, t, s->fsaddr, &start) || + !addr_to_offset(s, t, s->feaddr, &end) || end < start) { + faci_illegal(s); + return; + } + + p =3D target_storage(s, t); + s->fbcstat =3D 0; /* assume blank */ + for (uint32_t i =3D start; i <=3D end; i++) { + if (p[i] !=3D 0xff) { + s->fbcstat =3D FBCSTAT_BCST; /* not blank */ + break; + } + } + faci_complete(s); +} + +/* + * Interpret a guest write to a flash array address while that array is in= P/E + * mode. Implements the FACI program / block-erase / blank-check sequences. + */ +static void faci_command(RenesasRxFcuState *s, RxFcuTarget t, + uint32_t off, uint64_t value, unsigned size) +{ + uint8_t cmd =3D value & 0xff; + + switch (s->cmd_state) { + case RX_FCU_ST_READY: + faci_set_cmdr(s, cmd); + switch (cmd) { + case FACI_CMD_PROGRAM: + s->cmd_target =3D t; + s->prog_off =3D off; + s->cmd_state =3D RX_FCU_ST_PROGRAM_COUNT; + break; + case FACI_CMD_BLOCK_ERASE: + s->cmd_target =3D t; + s->prog_off =3D off; + s->cmd_state =3D RX_FCU_ST_ERASE; + break; + case FACI_CMD_BLANK_CHECK: + s->cmd_target =3D t; + s->cmd_state =3D RX_FCU_ST_BLANKCHECK; + break; + case FACI_CMD_CONFIG_SET: + /* + * Configuration set writes one 16-byte row of the option-sett= ing + * memory. FSADDR selects the row: its low byte is the offset + * within the OFSM, so 0x00ff5d20 reaches BANKSEL at +0x20. + */ + s->cfg_off =3D s->fsaddr & (RX_FCU_OFSM_SIZE - 1) & ~0xfu; + s->cmd_state =3D RX_FCU_ST_CONFIG_COUNT; + break; + case FACI_CMD_CLEAR_STAT: + s->fstatr =3D (s->fstatr & ~FSTATR_ERRORS) | FSTATR_FRDY; + s->fastat &=3D ~FASTAT_CMDLK; + qemu_set_irq(s->fiferr, 0); + break; + case FACI_CMD_FORCED_STOP: + s->fstatr =3D (s->fstatr & ~FSTATR_ERRORS) | FSTATR_FRDY; + s->fastat &=3D ~FASTAT_CMDLK; + s->cmd_state =3D RX_FCU_ST_READY; + qemu_set_irq(s->fiferr, 0); + break; + default: + faci_illegal(s); + break; + } + break; + + case RX_FCU_ST_CONFIG_COUNT: + /* Always eight 16-bit words, i.e. the 16-byte row. */ + s->prog_words =3D value & 0xff; + s->cmd_state =3D RX_FCU_ST_CONFIG_DATA; + break; + + case RX_FCU_ST_CONFIG_DATA: + if (s->prog_words > 0) { + if (s->cfg_off + 2 <=3D RX_FCU_OFSM_SIZE) { + /* Like the arrays, programming can only clear bits. */ + uint16_t cur =3D lduw_le_p(s->ofsm_ptr + s->cfg_off); + stw_le_p(s->ofsm_ptr + s->cfg_off, cur & (uint16_t)value); + } + s->cfg_off +=3D 2; + s->prog_words--; + } else if (cmd =3D=3D FACI_CMD_CONFIRM) { + fcu_ofsm_sync(s); + faci_complete(s); + } else { + faci_illegal(s); + } + break; + + case RX_FCU_ST_PROGRAM_COUNT: + /* Number of 16-bit data words that follow. */ + s->prog_words =3D value & 0xff; + s->cmd_state =3D RX_FCU_ST_PROGRAM_DATA; + break; + + case RX_FCU_ST_PROGRAM_DATA: + if (s->prog_words > 0) { + uint8_t *p =3D target_storage(s, s->cmd_target); + uint32_t sz =3D target_size(s, s->cmd_target); + if (s->prog_off + 2 <=3D sz) { + /* Flash programming only clears bits; emulate it with AND= . */ + uint16_t cur =3D lduw_le_p(p + s->prog_off); + stw_le_p(p + s->prog_off, cur & (uint16_t)value); + fcu_flash_sync(s, s->cmd_target, s->prog_off, 2); + } + s->prog_off +=3D 2; + s->prog_words--; + } else if (cmd =3D=3D FACI_CMD_CONFIRM) { + faci_complete(s); + } else { + faci_illegal(s); + } + break; + + case RX_FCU_ST_ERASE: + if (cmd =3D=3D FACI_CMD_CONFIRM) { + faci_set_cmdr(s, cmd); + faci_block_erase(s, s->cmd_target, s->prog_off); + } else { + faci_illegal(s); + } + break; + + case RX_FCU_ST_BLANKCHECK: + if (cmd =3D=3D FACI_CMD_CONFIRM) { + faci_set_cmdr(s, cmd); + faci_blank_check(s, s->cmd_target); + } else { + faci_illegal(s); + } + break; + } +} + +static uint64_t faci_flash_read(void *opaque, hwaddr offset, unsigned size) +{ + RxFcuFlash *f =3D opaque; + const uint8_t *p =3D target_storage(f->fcu, f->target); + + /* + * This callback is only reached while the array is in P/E mode (romd = is + * disabled); outside P/E mode reads hit the backing RAM directly. Ret= urn + * the stored contents so reads remain coherent in either mode. + */ + switch (size) { + case 1: + return p[offset]; + case 2: + return lduw_le_p(p + offset); + default: + return ldl_le_p(p + offset); + } +} + +static void faci_flash_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + RxFcuFlash *f =3D opaque; + + qemu_log_mask(LOG_GUEST_ERROR, + "renesas-rx-fcu: write to the %s flash array @0x%" + HWADDR_PRIx "; FACI commands are issued to 0x%08x\n", + f->target =3D=3D RX_FCU_CFLASH ? "code" : "data", offset, + RX_FCU_FACI_ISSUE_BASE); +} + +/* + * The FACI command-issuing area. Every FACI command byte and every data w= ord + * is written here, four bytes at 0x007E0000, rather than to the flash arr= ay; + * the destination inside the array comes from FSADDR. The array itself is + * not writable, so a write there is a guest error rather than a command. + */ +static void faci_issue_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + RenesasRxFcuState *s =3D opaque; + RxFcuTarget t; + uint32_t off; + + if (target_in_pe_mode(s, RX_FCU_CFLASH)) { + t =3D RX_FCU_CFLASH; + } else if (target_in_pe_mode(s, RX_FCU_DFLASH)) { + t =3D RX_FCU_DFLASH; + } else { + qemu_log_mask(LOG_GUEST_ERROR, + "renesas-rx-fcu: FACI command 0x%02x issued while " + "neither array is in P/E mode\n", (uint8_t)value); + return; + } + + /* + * Only the command that opens a sequence needs an address; the count, + * data words and confirmation that follow continue where it left off. + */ + if (s->cmd_state =3D=3D RX_FCU_ST_READY) { + if (!addr_to_offset(s, t, s->fsaddr, &off)) { + faci_illegal(s); + return; + } + } else { + off =3D s->prog_off; + } + faci_command(s, t, off, value, size); +} + +static uint64_t faci_issue_read(void *opaque, hwaddr offset, unsigned size) +{ + /* The command-issuing area is write-only; reads are undefined. */ + return 0; +} + +static const MemoryRegionOps faci_issue_ops =3D { + .read =3D faci_issue_read, + .write =3D faci_issue_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .valid =3D { + .min_access_size =3D 1, + .max_access_size =3D 4, + }, +}; + +static const MemoryRegionOps faci_flash_ops =3D { + .read =3D faci_flash_read, + .write =3D faci_flash_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .valid =3D { + .min_access_size =3D 1, + .max_access_size =3D 4, + }, +}; + +/* Update P/E mode entry and toggle the rom_device read path accordingly. = */ +static void fcu_set_fentryr(RenesasRxFcuState *s, uint16_t value) +{ + bool was_code =3D s->fentryr & FENTRYR_CODE; + bool was_data =3D s->fentryr & FENTRYR_DATA; + bool code, data; + + if ((value & 0xff00) !=3D FENTRYR_KEY) { + qemu_log_mask(LOG_GUEST_ERROR, + "renesas-rx-fcu: FENTRYR write without key (0x%04x)\= n", + value); + return; + } + + code =3D value & FENTRYR_CODE; + data =3D value & FENTRYR_DATA; + s->fentryr =3D (code ? FENTRYR_CODE : 0) | (data ? FENTRYR_DATA : 0); + + if (code !=3D was_code) { + memory_region_rom_device_set_romd(&s->cflash_mr, !code); + } + if (data !=3D was_data) { + memory_region_rom_device_set_romd(&s->dflash_mr, !data); + } + if (!code && !data) { + /* Leaving P/E mode resets the command sequencer. */ + s->cmd_state =3D RX_FCU_ST_READY; + } +} + +static uint64_t fcu_regs_read(void *opaque, hwaddr offset, unsigned size) +{ + RenesasRxFcuState *s =3D opaque; + uint64_t val; + + switch (offset) { + case R_FASTAT: + val =3D s->fastat; + break; + case R_FAEINT: + val =3D s->faeint; + break; + case R_FRDYIE: + val =3D s->frdyie; + break; + case R_FSADDR: + val =3D s->fsaddr; + break; + case R_FEADDR: + val =3D s->feaddr; + break; + case R_FSTATR: + val =3D s->fstatr; + break; + case R_FENTRYR: + val =3D s->fentryr; + break; + case R_FPROTR: + val =3D s->fprotr; + break; + case R_FSUACR: + val =3D s->fsuacr; + break; + case R_FCMDR: + val =3D s->fcmdr; + break; + case R_FPESTAT: + val =3D s->fpestat; + break; + case R_FBCCNT: + val =3D s->fbccnt; + break; + case R_FBCSTAT: + val =3D s->fbcstat; + break; + case R_FPSADDR: + val =3D s->fpsaddr; + break; + case R_FCPSR: + val =3D s->fcpsr; + break; + case R_FPCKAR: + val =3D s->fpckar; + break; + default: + qemu_log_mask(LOG_UNIMP, + "renesas-rx-fcu: read from unimplemented reg 0x%" + HWADDR_PRIx "\n", offset); + return 0; + } + + if (size < 4) { + val &=3D (1ull << (8 * size)) - 1; + } + return val; +} + +static void fcu_regs_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + RenesasRxFcuState *s =3D opaque; + + switch (offset) { + case R_FASTAT: + s->fastat =3D value; + break; + case R_FAEINT: + s->faeint =3D value; + break; + case R_FRDYIE: + s->frdyie =3D value; + break; + case R_FSADDR: + s->fsaddr =3D value; + break; + case R_FEADDR: + s->feaddr =3D value; + break; + case R_FENTRYR: + fcu_set_fentryr(s, value); + break; + case R_FPROTR: + s->fprotr =3D value; + break; + case R_FSUACR: + s->fsuacr =3D value; + break; + case R_FBCCNT: + s->fbccnt =3D value; + break; + case R_FCPSR: + s->fcpsr =3D value; + break; + case R_FPCKAR: + s->fpckar =3D value; + break; + case R_FSTATR: + case R_FBCSTAT: + case R_FCMDR: + case R_FPESTAT: + case R_FPSADDR: + /* Read-only status registers; ignore writes. */ + break; + default: + qemu_log_mask(LOG_UNIMP, + "renesas-rx-fcu: write to unimplemented reg 0x%" + HWADDR_PRIx " =3D 0x%" PRIx64 "\n", offset, value); + break; + } +} + +static const MemoryRegionOps fcu_regs_ops =3D { + .read =3D fcu_regs_read, + .write =3D fcu_regs_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .impl =3D { + .min_access_size =3D 1, + .max_access_size =3D 4, + }, + .valid =3D { + .min_access_size =3D 1, + .max_access_size =3D 4, + }, +}; + +/* + * Latch the code flash bank layout from the option-setting memory. The + * hardware samples MDE.BANKMD and BANKSEL.BANKSWP at reset, so a mode or + * bank switch only takes effect on the next reset, which is what makes the + * "program the other bank then reboot into it" update flow work. + * + * BANKSWP =3D 111b maps bank 0, the one holding the vectors, into the upp= er + * half, which is the layout a linearly programmed image already has, so it + * needs no aliasing. 000b swaps the halves. + */ +void rx_fcu_update_bank_map(RenesasRxFcuState *s) +{ + uint32_t mde, banksel; + bool dual, swapped; + + mde =3D ldl_le_p(s->ofsm_ptr + RX_OFSM_MDE); + banksel =3D ldl_le_p(s->ofsm_ptr + RX_OFSM_BANKSEL); + + dual =3D (mde & RX_BANKMD_MASK) =3D=3D RX_BANKMD_DUAL; + swapped =3D (banksel & RX_BANKSWP_MASK) =3D=3D RX_BANKSWP_SWAPPED; + + /* Dual mode is only available on parts with at least 1.5 MiB of flash= . */ + if (dual && s->cflash_size < CFLASH_MIN_DUAL_SIZE) { + qemu_log_mask(LOG_UNIMP, + "renesas-rx-fcu: dual mode requested on a %u KiB cod= e " + "flash is not modelled; staying linear\n", + s->cflash_size / 1024); + dual =3D false; + } + + s->dual_mode =3D dual; + s->bank_swapped =3D dual && swapped; + + memory_region_transaction_begin(); + memory_region_set_enabled(&s->cflash_linear, !s->dual_mode); + memory_region_set_enabled(&s->cflash_bank[0], s->dual_mode); + memory_region_set_enabled(&s->cflash_bank[1], s->dual_mode); + if (s->dual_mode) { + uint32_t bank_size =3D s->cflash_size / 2; + + memory_region_set_alias_offset(&s->cflash_bank[0], + s->bank_swapped ? bank_size : 0); + memory_region_set_alias_offset(&s->cflash_bank[1], + s->bank_swapped ? 0 : bank_size); + } + memory_region_transaction_commit(); +} + +static void rx_fcu_reset(DeviceState *dev) +{ + RenesasRxFcuState *s =3D RENESAS_RX_FCU(dev); + + /* The bank layout is sampled from the OFSM at every reset. */ + rx_fcu_update_bank_map(s); + + /* Exit P/E mode and restore direct (romd) flash reads. */ + memory_region_rom_device_set_romd(&s->cflash_mr, true); + memory_region_rom_device_set_romd(&s->dflash_mr, true); + + s->fentryr =3D 0; + s->fstatr =3D FSTATR_FRDY; + s->fastat =3D 0; + s->frdyie =3D 0; + s->faeint =3D 0; + s->fsaddr =3D 0; + s->feaddr =3D 0; + s->fpsaddr =3D 0; + s->fbcstat =3D 0; + s->fbccnt =3D 0; + s->fcpsr =3D 0; + s->fpckar =3D 0; + s->fprotr =3D 0; + s->fsuacr =3D 0; + s->fpestat =3D 0; + s->fcmdr =3D 0; + s->cmd_state =3D RX_FCU_ST_READY; + s->prog_off =3D 0; + s->prog_words =3D 0; + + qemu_set_irq(s->frdyi, 0); + qemu_set_irq(s->fiferr, 0); +} + +/* + * Attach one array to its block backend: take the write permission if the + * image allows it, then load the image over the erased contents. A backend + * whose size does not match the array is rejected rather than silently + * truncated. + */ +static bool fcu_attach_blk(RenesasRxFcuState *s, DeviceState *dev, + RxFcuTarget t, Error **errp) +{ + BlockBackend *blk =3D t =3D=3D RX_FCU_CFLASH ? s->cflash_blk : s->dfla= sh_blk; + MemoryRegion *mr =3D t =3D=3D RX_FCU_CFLASH ? &s->cflash_mr : &s->dfla= sh_mr; + uint32_t size =3D target_size(s, t); + uint64_t perm; + bool ro; + + if (!blk) { + return true; + } + + ro =3D !blk_supports_write_perm(blk); + if (t =3D=3D RX_FCU_CFLASH) { + s->cflash_ro =3D ro; + } else { + s->dflash_ro =3D ro; + } + + perm =3D BLK_PERM_CONSISTENT_READ | (ro ? 0 : BLK_PERM_WRITE); + if (blk_set_perm(blk, perm, BLK_PERM_ALL, errp) < 0) { + return false; + } + + /* The block helper skips zero extents, so its destination must be zer= o. */ + memset(target_storage(s, t), 0, size); + if (!blk_check_size_and_read_all(blk, dev, target_storage(s, t), + size, errp)) { + vmstate_unregister_ram(mr, dev); + return false; + } + return true; +} + +static void rx_fcu_realize(DeviceState *dev, Error **errp) +{ + RenesasRxFcuState *s =3D RENESAS_RX_FCU(dev); + SysBusDevice *sbd =3D SYS_BUS_DEVICE(dev); + + if (s->cflash_size =3D=3D 0 || s->dflash_size =3D=3D 0) { + error_setg(errp, "code-flash-size and data-flash-size must be set"= ); + return; + } + if (s->dual_bank_gap_size > s->cflash_base) { + error_setg(errp, "dual-bank-gap-size exceeds code-flash-base"); + return; + } + + s->cflash_ctx.fcu =3D s; + s->cflash_ctx.target =3D RX_FCU_CFLASH; + s->dflash_ctx.fcu =3D s; + s->dflash_ctx.target =3D RX_FCU_DFLASH; + + memory_region_init_io(&s->regs_mr, OBJECT(s), &fcu_regs_ops, s, + "renesas-rx-fcu.regs", RX_FCU_REGS_SIZE); + memory_region_init_io(&s->faci_mr, OBJECT(s), &faci_issue_ops, s, + "renesas-rx-fcu.faci", RX_FCU_FACI_ISSUE_SIZE); + + if (!memory_region_init_rom_device(&s->cflash_mr, OBJECT(s), + &faci_flash_ops, &s->cflash_ctx, + "renesas-rx-fcu.cflash", + s->cflash_size, errp)) { + return; + } + if (!memory_region_init_rom_device(&s->dflash_mr, OBJECT(s), + &faci_flash_ops, &s->dflash_ctx, + "renesas-rx-fcu.dflash", + s->dflash_size, errp)) { + return; + } + /* + * The option-setting memory is read-only to ordinary accesses. It is + * changed through the FACI configuration-set command, so a plain ROM = is + * the right shape for the CPU-visible mapping. + */ + if (!memory_region_init_rom(&s->ofsm_mr, OBJECT(s), + "renesas-rx-fcu.ofsm", + RX_FCU_OFSM_SIZE, errp)) { + return; + } + + /* + * The code flash reaches the bus through a container holding either o= ne + * alias over the whole array (linear mode) or one per bank (dual mode= ). + */ + memory_region_init(&s->cflash_container, OBJECT(s), + "renesas-rx-fcu.cflash-map", + s->cflash_size + s->dual_bank_gap_size); + memory_region_init_alias(&s->cflash_linear, OBJECT(s), + "renesas-rx-fcu.cflash-linear", + &s->cflash_mr, 0, s->cflash_size); + memory_region_add_subregion(&s->cflash_container, + s->dual_bank_gap_size, &s->cflash_linear); + + for (int i =3D 0; i < 2; i++) { + g_autofree char *name =3D + g_strdup_printf("renesas-rx-fcu.cflash-bank%d", i); + memory_region_init_alias(&s->cflash_bank[i], OBJECT(s), name, + &s->cflash_mr, 0, s->cflash_size / 2); + memory_region_add_subregion(&s->cflash_container, + (hwaddr)i * (s->cflash_size / 2 + + s->dual_bank_gap_size), + &s->cflash_bank[i]); + memory_region_set_enabled(&s->cflash_bank[i], false); + } + + s->cflash_ptr =3D memory_region_get_ram_ptr(&s->cflash_mr); + s->dflash_ptr =3D memory_region_get_ram_ptr(&s->dflash_mr); + s->ofsm_ptr =3D memory_region_get_ram_ptr(&s->ofsm_mr); + + /* + * Erased flash reads as all ones, but the backing memory starts zeroe= d. + * Fill both arrays here rather than at reset: realize runs before the + * ROM loader writes the firmware image into the code flash, and flash + * is non-volatile, so anything the guest programmed must survive a + * later system reset. + */ + memset(s->cflash_ptr, 0xff, s->cflash_size); + memset(s->dflash_ptr, 0xff, s->dflash_size); + memset(s->ofsm_ptr, 0xff, RX_FCU_OFSM_SIZE); + + if (!fcu_attach_blk(s, dev, RX_FCU_CFLASH, errp) || + !fcu_attach_blk(s, dev, RX_FCU_DFLASH, errp)) { + return; + } + if (s->ofsm_blk) { + uint64_t perm; + int64_t len; + + s->ofsm_ro =3D !blk_supports_write_perm(s->ofsm_blk); + perm =3D BLK_PERM_CONSISTENT_READ | (s->ofsm_ro ? 0 : BLK_PERM_WRI= TE); + if (blk_set_perm(s->ofsm_blk, perm, BLK_PERM_ALL, errp) < 0) { + return; + } + /* + * The OFSM is smaller than a block sector, so an exact size match= is + * not something an image file can offer. Require enough bytes and + * read only the ones the region holds. + */ + len =3D blk_getlength(s->ofsm_blk); + if (len < RX_FCU_OFSM_SIZE) { + error_setg(errp, "ofsm-drive is %" PRId64 + " bytes, need at least %d", + len, RX_FCU_OFSM_SIZE); + return; + } + if (blk_pread(s->ofsm_blk, 0, RX_FCU_OFSM_SIZE, s->ofsm_ptr, 0) < = 0) { + error_setg(errp, "could not read ofsm-drive"); + return; + } + } + + sysbus_init_mmio(sbd, &s->regs_mr); + sysbus_init_mmio(sbd, &s->cflash_container); + sysbus_init_mmio(sbd, &s->dflash_mr); + sysbus_init_mmio(sbd, &s->ofsm_mr); + sysbus_init_mmio(sbd, &s->faci_mr); + sysbus_init_irq(sbd, &s->frdyi); + sysbus_init_irq(sbd, &s->fiferr); +} + +static const Property rx_fcu_properties[] =3D { + DEFINE_PROP_DRIVE("code-flash-drive", RenesasRxFcuState, cflash_blk), + DEFINE_PROP_DRIVE("data-flash-drive", RenesasRxFcuState, dflash_blk), + DEFINE_PROP_DRIVE("ofsm-drive", RenesasRxFcuState, ofsm_blk), + DEFINE_PROP_UINT32("code-flash-size", RenesasRxFcuState, cflash_size, = 0), + DEFINE_PROP_UINT32("data-flash-size", RenesasRxFcuState, dflash_size, = 0), + DEFINE_PROP_UINT32("code-flash-base", RenesasRxFcuState, cflash_base, = 0), + DEFINE_PROP_UINT32("data-flash-base", RenesasRxFcuState, dflash_base, = 0), + DEFINE_PROP_UINT32("dual-bank-gap-size", RenesasRxFcuState, + dual_bank_gap_size, 0), +}; + +static const VMStateDescription vmstate_rx_fcu =3D { + .name =3D "renesas-rx-fcu", + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (const VMStateField[]) { + VMSTATE_UINT16(fentryr, RenesasRxFcuState), + VMSTATE_UINT32(fstatr, RenesasRxFcuState), + VMSTATE_UINT8(fastat, RenesasRxFcuState), + VMSTATE_UINT8(frdyie, RenesasRxFcuState), + VMSTATE_UINT8(faeint, RenesasRxFcuState), + VMSTATE_UINT32(fsaddr, RenesasRxFcuState), + VMSTATE_UINT32(fpsaddr, RenesasRxFcuState), + VMSTATE_UINT32(feaddr, RenesasRxFcuState), + VMSTATE_UINT8(fbcstat, RenesasRxFcuState), + VMSTATE_UINT16(fbccnt, RenesasRxFcuState), + VMSTATE_UINT16(fcpsr, RenesasRxFcuState), + VMSTATE_UINT16(fpckar, RenesasRxFcuState), + VMSTATE_UINT16(fprotr, RenesasRxFcuState), + VMSTATE_UINT16(fsuacr, RenesasRxFcuState), + VMSTATE_UINT16(fpestat, RenesasRxFcuState), + VMSTATE_UINT16(fcmdr, RenesasRxFcuState), + VMSTATE_INT32(cmd_state, RenesasRxFcuState), + VMSTATE_INT32(cmd_target, RenesasRxFcuState), + VMSTATE_UINT32(prog_off, RenesasRxFcuState), + VMSTATE_UINT32(prog_words, RenesasRxFcuState), + VMSTATE_END_OF_LIST() + } +}; + +static void rx_fcu_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(klass); + + dc->realize =3D rx_fcu_realize; + dc->vmsd =3D &vmstate_rx_fcu; + device_class_set_props(dc, rx_fcu_properties); + device_class_set_legacy_reset(dc, rx_fcu_reset); +} + +static const TypeInfo rx_fcu_info =3D { + .name =3D TYPE_RENESAS_RX_FCU, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RenesasRxFcuState), + .class_init =3D rx_fcu_class_init, +}; + +static void rx_fcu_register_types(void) +{ + type_register_static(&rx_fcu_info); +} + +type_init(rx_fcu_register_types) diff --git a/hw/misc/renesas_rx_romcache.c b/hw/misc/renesas_rx_romcache.c new file mode 100644 index 0000000000..22797d569d --- /dev/null +++ b/hw/misc/renesas_rx_romcache.c @@ -0,0 +1,139 @@ +/* + * Renesas RX ROM cache control registers + * + * Datasheet: RX65N Group, RX651 Group User's Manual: Hardware + * (Rev.2.40 R01UH0590EJ0240) + * + * Copyright (c) 2026 Umar, Sayyad Mahammad + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * The ROM cache speeds up code flash reads and is architecturally invisib= le + * apart from these two registers. QEMU has no such cache, so there is not= hing + * to model beyond them -- but they do have to exist, because start-up code + * routinely enables the cache and then spins until the enable reads back: + * + * FLASH.ROMCE.WORD =3D 0x0001; + * while (FLASH.ROMCE.WORD !=3D 0x0001) { + * } + * + * With no device here that loop never terminates, and the machine hangs + * before anything else in the firmware runs. + * + * ROMCE holds the enable bit and reads back what was written. ROMCIV star= ts + * an invalidation, which completes instantly here, so the bit reads back = as + * zero the way it does on hardware once invalidation has finished. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "hw/misc/renesas_rx_romcache.h" +#include "migration/vmstate.h" + +#define R_ROMCE 0x00 /* 16-bit ROM cache enable */ +#define R_ROMCIV 0x04 /* 16-bit ROM cache invalidate */ +#define R_ROMWT 0x1c /* 8-bit ROM wait-cycle setting */ + +#define ROMCE_ROMCEN 0x0001 + +static uint64_t romcache_read(void *opaque, hwaddr offset, unsigned size) +{ + RenesasRxRomCacheState *s =3D opaque; + + switch (offset) { + case R_ROMCE: + return s->romce; + case R_ROMCIV: + /* Invalidation is instantaneous, so it always reads as complete. = */ + return 0; + case R_ROMWT: + return s->romwt; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "renesas-rx-romcache: read from unmapped offset 0x%" + HWADDR_PRIx "\n", offset); + return 0; + } +} + +static void romcache_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + RenesasRxRomCacheState *s =3D opaque; + + switch (offset) { + case R_ROMCE: + s->romce =3D value & ROMCE_ROMCEN; + break; + case R_ROMCIV: + /* Nothing to invalidate; the write is accepted and completes. */ + break; + case R_ROMWT: + s->romwt =3D value & 0x3; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "renesas-rx-romcache: write to unmapped offset 0x%" + HWADDR_PRIx "\n", offset); + break; + } +} + +static const MemoryRegionOps romcache_ops =3D { + .read =3D romcache_read, + .write =3D romcache_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .impl =3D { .min_access_size =3D 1, .max_access_size =3D 4 }, + .valid =3D { .min_access_size =3D 1, .max_access_size =3D 4 }, +}; + +static void romcache_reset(DeviceState *dev) +{ + RenesasRxRomCacheState *s =3D RENESAS_RX_ROMCACHE(dev); + + s->romce =3D 0; + s->romwt =3D 0; +} + +static void romcache_init(Object *obj) +{ + RenesasRxRomCacheState *s =3D RENESAS_RX_ROMCACHE(obj); + + memory_region_init_io(&s->memory, obj, &romcache_ops, s, + "renesas-rx-romcache", RX_ROMCACHE_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->memory); +} + +static const VMStateDescription vmstate_romcache =3D { + .name =3D "renesas-rx-romcache", + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (const VMStateField[]) { + VMSTATE_UINT16(romce, RenesasRxRomCacheState), + VMSTATE_UINT8(romwt, RenesasRxRomCacheState), + VMSTATE_END_OF_LIST() + } +}; + +static void romcache_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(klass); + + dc->vmsd =3D &vmstate_romcache; + device_class_set_legacy_reset(dc, romcache_reset); +} + +static const TypeInfo romcache_info =3D { + .name =3D TYPE_RENESAS_RX_ROMCACHE, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RenesasRxRomCacheState), + .instance_init =3D romcache_init, + .class_init =3D romcache_class_init, +}; + +static void romcache_register_types(void) +{ + type_register_static(&romcache_info); +} + +type_init(romcache_register_types) diff --git a/hw/misc/rx65n_sysclk.c b/hw/misc/rx65n_sysclk.c new file mode 100644 index 0000000000..8f21ddd860 --- /dev/null +++ b/hw/misc/rx65n_sysclk.c @@ -0,0 +1,190 @@ +/* + * RX65N Clock Generation Circuit (SYSTEM) + * + * Datasheet: RX65N Group, RX651 Group User's Manual: Hardware + * (Rev.1.00 R01UH0590EJ0100), section 9 + * + * Copyright (c) 2026 Umar, Sayyad Mahammad + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * + * The RX65N clock generation circuit configures the oscillators, PLL and + * clock dividers. QEMU does not model real clock timing, so most registers + * behave as plain read/write storage. The crucial exception is the + * Oscillation Stabilization Flag Register (OSCOVFSR): startup code busy-w= aits + * on it until every selected clock source reports "stable", so it is forc= ed + * to report all sources stabilized. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "hw/misc/rx65n_sysclk.h" +#include "migration/vmstate.h" + +/* Register offsets from the SYSTEM base (0x00080000). */ +#define R_PLLCR2 0x2A /* PLL control register 2 (8-bi= t) */ +#define R_MOSCCR 0x32 /* Main clock oscillator control reg (8-bi= t) */ +#define R_SOSCCR 0x33 /* Sub-clock oscillator control reg (8-bi= t) */ +#define R_HOCOCR 0x36 /* High-speed on-chip osc control reg (8-bi= t) */ +#define R_OSCOVFSR 0x3C /* Oscillation stabilization flag reg (8-bi= t) */ + +/* OSCOVFSR flag bits. */ +#define OSCOVFSR_MOOVF (1 << 0) /* Main clock oscillator */ +#define OSCOVFSR_SOOVF (1 << 1) /* Sub-clock oscillator */ +#define OSCOVFSR_PLOVF (1 << 2) /* PLL */ +#define OSCOVFSR_HCOVF (1 << 3) /* High-speed on-chip oscillator */ +#define OSCOVFSR_ILCOVF (1 << 4) /* IWDT low-speed on-chip osc */ +#define OSCOVFSR_PPLOVF (1 << 5) /* PLL2 (RX72M, high-speed periph) */ + +/* + * Compute the Oscillation Stabilization Flag Register from the oscillator + * control registers. Each flag reads 1 once its oscillator is enabled (its + * stop bit is clear) and 0 while it is stopped. QEMU has no real oscillat= or + * timing, so stabilization is reported as immediate. PLLCR2.PLLEN uses the + * same "0 =3D operating" polarity as the stop bits. + */ +static uint8_t oscovfsr_value(RX65NSysClkState *s) +{ + uint8_t v =3D 0; + + if (!(s->regs[R_MOSCCR] & 1)) { + v |=3D OSCOVFSR_MOOVF; + } + if (!(s->regs[R_SOSCCR] & 1)) { + v |=3D OSCOVFSR_SOOVF; + } + if (!(s->regs[R_PLLCR2] & 1)) { + v |=3D OSCOVFSR_PLOVF; + } + if (!(s->regs[R_HOCOCR] & 1)) { + v |=3D OSCOVFSR_HCOVF; + } + /* + * The IWDT low-speed on-chip oscillator and (on the RX72M) PLL2 have = no + * stop bit modelled here; report them stabilized so firmware that sel= ects + * them does not busy-wait forever. QEMU has no real oscillator timing. + */ + v |=3D OSCOVFSR_ILCOVF | OSCOVFSR_PPLOVF; + return v; +} + +static uint64_t rx65n_sysclk_read(void *opaque, hwaddr offset, unsigned si= ze) +{ + RX65NSysClkState *s =3D opaque; + uint64_t val =3D 0; + unsigned i; + + if (offset + size > RX65N_SYSCLK_SIZE) { + qemu_log_mask(LOG_GUEST_ERROR, + "rx65n_sysclk: read out of range 0x%" HWADDR_PRIX "\= n", + offset); + return 0; + } + + for (i =3D 0; i < size; i++) { + val |=3D (uint64_t)s->regs[offset + i] << (8 * i); + } + + /* OSCOVFSR is read-only and derived from the oscillator controls. */ + if (offset <=3D R_OSCOVFSR && offset + size > R_OSCOVFSR) { + unsigned b =3D R_OSCOVFSR - offset; + val &=3D ~((uint64_t)0xff << (8 * b)); + val |=3D (uint64_t)oscovfsr_value(s) << (8 * b); + } + + return val; +} + +static void rx65n_sysclk_write(void *opaque, hwaddr offset, uint64_t val, + unsigned size) +{ + RX65NSysClkState *s =3D opaque; + unsigned i; + + if (offset + size > RX65N_SYSCLK_SIZE) { + qemu_log_mask(LOG_GUEST_ERROR, + "rx65n_sysclk: write out of range 0x%" HWADDR_PRIX "= \n", + offset); + return; + } + + for (i =3D 0; i < size; i++) { + s->regs[offset + i] =3D (val >> (8 * i)) & 0xff; + } +} + +static const MemoryRegionOps rx65n_sysclk_ops =3D { + .read =3D rx65n_sysclk_read, + .write =3D rx65n_sysclk_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .impl =3D { + .min_access_size =3D 1, + .max_access_size =3D 4, + }, + .valid =3D { + .min_access_size =3D 1, + .max_access_size =3D 4, + }, +}; + +static void rx65n_sysclk_reset(DeviceState *dev) +{ + RX65NSysClkState *s =3D RX65N_SYSCLK(dev); + + memset(s->regs, 0, sizeof(s->regs)); + + /* + * Reset values for the oscillator controls (HW manual section 9). The + * main and sub-clock oscillators and the PLL are stopped out of reset; + * the high-speed on-chip oscillator (HOCO) is left running so its + * stabilization flag reads ready for firmware that selects it directl= y. + */ + s->regs[R_MOSCCR] =3D 0x01; /* MOSTP =3D 1: main oscillator stopped = */ + s->regs[R_SOSCCR] =3D 0x01; /* SOSTP =3D 1: sub-clock stopped = */ + s->regs[R_PLLCR2] =3D 0x01; /* PLLEN =3D 1: PLL stopped = */ + s->regs[R_HOCOCR] =3D 0x00; /* HCSTP =3D 0: HOCO running = */ +} + +static void rx65n_sysclk_init(Object *obj) +{ + SysBusDevice *d =3D SYS_BUS_DEVICE(obj); + RX65NSysClkState *s =3D RX65N_SYSCLK(obj); + + memory_region_init_io(&s->memory, obj, &rx65n_sysclk_ops, s, + "rx65n-sysclk", RX65N_SYSCLK_SIZE); + sysbus_init_mmio(d, &s->memory); +} + +static const VMStateDescription vmstate_rx65n_sysclk =3D { + .name =3D "rx65n-sysclk", + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (const VMStateField[]) { + VMSTATE_UINT8_ARRAY(regs, RX65NSysClkState, RX65N_SYSCLK_SIZE), + VMSTATE_END_OF_LIST() + } +}; + +static void rx65n_sysclk_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(klass); + + dc->vmsd =3D &vmstate_rx65n_sysclk; + device_class_set_legacy_reset(dc, rx65n_sysclk_reset); +} + +static const TypeInfo rx65n_sysclk_info =3D { + .name =3D TYPE_RX65N_SYSCLK, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RX65NSysClkState), + .instance_init =3D rx65n_sysclk_init, + .class_init =3D rx65n_sysclk_class_init, +}; + +static void rx65n_sysclk_register_types(void) +{ + type_register_static(&rx65n_sysclk_info); +} + +type_init(rx65n_sysclk_register_types) diff --git a/hw/net/renesas_etherc.c b/hw/net/renesas_etherc.c new file mode 100644 index 0000000000..2a95469ab2 --- /dev/null +++ b/hw/net/renesas_etherc.c @@ -0,0 +1,955 @@ +/* + * Renesas RX65N Ethernet Controller (ETHERC) + Ethernet DMA Controller (E= DMAC) + * + * Datasheet: RX65N Group, RX651 Group User's Manual: Hardware + * (Rev.1.00 R01UH0590EJ0100), Sections 32=E2=80=9333 + * + * Copyright (c) 2026 Umar, Sayyad Mahammad + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "hw/core/irq.h" +#include "hw/net/renesas_etherc.h" +#include "hw/core/qdev-properties.h" +#include "hw/core/qdev-properties-system.h" +#include "migration/vmstate.h" +#include "net/net.h" +#include "system/dma.h" + +/* + * Register offsets within one channel's 0x200-byte MMIO window. + * EDMAC occupies offsets 0x000-0x0ff; ETHERC occupies 0x100-0x1ff. + * RX65N/RX72M/RX72N channel 0 starts at 0x000c0000. RX72M/RX72N + * channel 1 starts at 0x000c0200 and must use a separate device instance. + * References: RX65N hardware manual sections 34/35; RX72M/RX72N 36/38. + */ +/* ETHERC */ +#define R_ECMR 0x100 +#define R_RFLR 0x108 +#define R_ECSR 0x110 +#define R_ECSIPR 0x118 +#define R_PIR 0x120 +#define R_PSR 0x128 +#define R_RDMLR 0x140 +#define R_IPGR 0x150 +#define R_APR 0x154 +#define R_MPR 0x158 +#define R_RFCF 0x160 +#define R_TPAUSER 0x164 +#define R_TPAUSECR 0x168 +#define R_BCFRR 0x16C +#define R_MAHR 0x1C0 +#define R_MALR 0x1C8 +#define R_TROCR 0x1D0 +#define R_CDCR 0x1D4 +#define R_LCCR 0x1D8 +#define R_CNDCR 0x1DC +#define R_CEFCR 0x1E4 +#define R_FRECR 0x1E8 +#define R_TSFRCR 0x1EC +#define R_TLFRCR 0x1F0 +#define R_RFCR 0x1F4 +#define R_MAFCR 0x1F8 +/* EDMAC */ +#define R_EDMR 0x000 +#define R_EDTRR 0x008 +#define R_EDRRR 0x010 +#define R_TDLAR 0x018 +#define R_RDLAR 0x020 +#define R_EESR 0x028 +#define R_EESIPR 0x030 +#define R_TRSCER 0x038 +#define R_RMFCR 0x040 +#define R_TFTR 0x048 +#define R_FDR 0x050 +#define R_RMCR 0x058 +#define R_TFUCR 0x064 +#define R_RFOCR 0x068 +#define R_IOSR 0x06C +#define R_FCFTR 0x070 +#define R_RPADIR 0x078 +#define R_TRIMD 0x07C +#define R_RBWAR 0x0C8 +#define R_RDFAR 0x0CC +#define R_TBRAR 0x0D4 +#define R_TDFAR 0x0D8 + +/* ECMR bits */ +#define ECMR_PRM (1u << 0) /* promiscuous */ +#define ECMR_DM (1u << 1) /* full duplex */ +#define ECMR_TE (1u << 5) /* transmit enable */ +#define ECMR_RE (1u << 6) /* receive enable */ + +/* PIR bits */ +#define PIR_MDC (1u << 0) +#define PIR_MDO (1u << 2) +#define PIR_MMD (1u << 1) /* direction: 1=3DSoC drives MDO */ +#define PIR_MDI (1u << 3) /* MDI input (read-only) */ + +/* EESR / EESIPR bits */ +#define EESR_CERF (1u << 0) +#define EESR_RMAF (1u << 7) +#define EESR_RFOF (1u << 21) +#define EESR_RDE (1u << 22) +#define EESR_FR (1u << 23) +#define EESR_TFUF (1u << 25) +#define EESR_TDE (1u << 26) +#define EESR_TC (1u << 27) +#define EESR_TWB (1u << 31) + +/* TX descriptor word 0 (TD0) bits */ +#define TD0_TACT (1u << 31) +#define TD0_TDLE (1u << 30) +#define TD0_TFP1 (1u << 29) +#define TD0_TFP0 (1u << 28) +#define TD0_TFE (1u << 27) +#define TD0_TWBI (1u << 26) +#define TD0_TBL 0x0000FFFFu + +/* RX descriptor word 0 (RD0) bits */ +#define RD0_RACT (1u << 31) +#define RD0_RDLE (1u << 30) +#define RD0_RFP1 (1u << 29) +#define RD0_RFP0 (1u << 28) +#define RD0_RBL 0x0000FFFFu + +/* Descriptor size (EDMR.DL =3D 00 =E2=86=92 4 words =3D 16 bytes) */ +#define DESC_SIZE 16 + +/* + * PHY simulation + * Emulates a generic 100BASE-TX PHY at addresses 0 and 1, always link-up. + */ +static uint16_t mdio_phy_read(RX65NEthercState *s, int phy, int reg) +{ + /* Accept both PHY station addresses used by RX firmware variants. */ + if (phy !=3D 0 && phy !=3D 1) { + return 0xFFFF; + } + switch (reg) { + case 0: + /*Basic Control: FD, 100Mbps */ + return 0x3100; + case 1: + /*Basic Status: link up, AN complete */ + return 0x786D; + case 2: + /*PHY ID 1 */ + return 0x0022; + case 3: + /*PHY ID 2 */ + return 0x1550; + case 4: + /*ANAR */ + return 0x05E1; + case 5: + /*ANLPAR */ + return 0x45E1; + default: return s->mdio.phy_regs[reg < 32 ? reg : 0]; + } +} + +static void mdio_phy_write(RX65NEthercState *s, int phy, int reg, uint16_t= val) +{ + if ((phy =3D=3D 0 || phy =3D=3D 1) && reg < 32) { + s->mdio.phy_regs[reg] =3D val; + } +} + +/* + * MDIO bit-bang state machine. + * + * Invoked on every PIR write that has a MDC rising edge. + * Accumulates the MDIO frame header (SOF + OP + PHYADDR + REGADDR =3D 13 + * master bits after the preamble-terminating 0), decodes read/write, + * and then either: schedules slave-driven MDI bits for reads, or + * accumulates the TA + DATA bits for writes. + * + * MDI is updated in pir immediately so the firmware can read it back. + */ +static void mdio_do_rising(RX65NEthercState *s, bool mdo, bool mden) +{ + MDIOState *m =3D &s->mdio; + + /* ---- Slave output phase (read response) ---- */ + if (m->out_cnt >=3D 0) { + /* + * out_cnt 0 =E2=86=92 TA[1]: floating pull-up (MDI=3D1) + * out_cnt 1 =E2=86=92 TA[0]: slave drives 0 (MDI=3D0) + * out_cnt 2=E2=80=9317 =E2=86=92 DATA[15:0] MSB-first + */ + if (m->out_cnt =3D=3D 0) { + m->mdi =3D true; + } else if (m->out_cnt =3D=3D 1) { + m->mdi =3D false; + } else { + int bit =3D 17 - m->out_cnt; /* 15 down to 0 */ + m->mdi =3D (m->read_data >> bit) & 1; + } + m->out_cnt++; + if (m->out_cnt >=3D 18) { + m->out_cnt =3D -1; + m->in_cnt =3D -1; + m->preamble =3D 0; + } + return; + } + + if (!mden) { + return; /* master not driving; ignore (should be in output phas= e) */ + } + + /* ---- Preamble phase ---- */ + if (m->in_cnt =3D=3D -1) { + if (mdo) { + m->preamble++; + } else if (m->preamble >=3D 32) { + /* SOF[0] =3D 0 detected, start header accumulation */ + m->in_cnt =3D 0; + m->sr =3D 0; + m->write_cnt =3D -1; + } else { + m->preamble =3D 0; + } + return; + } + + /* ---- Header accumulation (13 bits after SOF[0]) ---- */ + if (m->in_cnt < 13) { + m->sr =3D (m->sr << 1) | (mdo ? 1 : 0); + m->in_cnt++; + + if (m->in_cnt =3D=3D 13) { + /* + * sr[12] =3D SOF[1] + * sr[11:10] =3D OP (10=3Dread, 01=3Dwrite) + * sr[9:5] =3D PHYADDR + * sr[4:0] =3D REGADDR + */ + int sof1 =3D (m->sr >> 12) & 1; + int op =3D (m->sr >> 10) & 3; + int paddr =3D (m->sr >> 5) & 0x1F; + int raddr =3D m->sr & 0x1F; + + if (sof1 !=3D 1) { + m->in_cnt =3D -1; + m->preamble =3D 0; + return; + } + if (op =3D=3D 2) { + m->is_read =3D true; + m->read_data =3D mdio_phy_read(s, paddr, raddr); + /* Slave output starts on the next rising edge. */ + m->out_cnt =3D 0; + } else if (op =3D=3D 1) { + m->is_read =3D false; + m->write_paddr =3D paddr; + m->write_raddr =3D raddr; + m->write_data =3D 0; + m->write_cnt =3D 0; + } + } + return; + } + + /* ---- Write: TA (2) + DATA (16) =3D 18 master bits ---- */ + if (!m->is_read && m->write_cnt >=3D 0 && m->write_cnt < 18) { + if (m->write_cnt >=3D 2) { + m->write_data =3D (m->write_data << 1) | (mdo ? 1 : 0); + } + m->write_cnt++; + if (m->write_cnt =3D=3D 18) { + mdio_phy_write(s, m->write_paddr, m->write_raddr, m->write_dat= a); + m->in_cnt =3D -1; + m->write_cnt =3D -1; + m->preamble =3D 0; + } + } +} + +static void etherc_pir_write(RX65NEthercState *s, uint32_t val) +{ + bool old_mdc =3D !!(s->pir & PIR_MDC); + bool new_mdc =3D !!(val & PIR_MDC); + bool new_mden =3D !!(val & PIR_MMD); + bool new_mdo =3D !!(val & PIR_MDO); + + /* Update writable bits, keep MDI */ + s->pir =3D (val & ~PIR_MDI) | (s->pir & PIR_MDI); + + if (!old_mdc && new_mdc) { + /* Rising edge: run state machine and update MDI */ + mdio_do_rising(s, new_mdo, new_mden); + s->pir =3D (s->pir & ~PIR_MDI) | (s->mdio.mdi ? PIR_MDI : 0); + } +} + +/* + * Interrupt management + */ +static void etherc_update_irq(RX65NEthercState *s) +{ + qemu_set_irq(s->irq[ETHERC_IRQ_EINT], !!(s->eesr & s->eesipr)); +} + +/* + * TX path: walk descriptor ring, send frames, update EESR. + */ +static void etherc_tx_run(RX65NEthercState *s) +{ + bool did_tx =3D false; + + for (;;) { + uint32_t td0, td1; + dma_memory_read(&address_space_memory, s->tx_cur, + &td0, 4, MEMTXATTRS_UNSPECIFIED); + dma_memory_read(&address_space_memory, s->tx_cur + 4, + &td1, 4, MEMTXATTRS_UNSPECIFIED); + td0 =3D le32_to_cpu(td0); + td1 =3D le32_to_cpu(td1); + + if (!(td0 & TD0_TACT)) { + /* CPU owns descriptor: TX descriptor list exhausted */ + s->eesr |=3D EESR_TDE; + break; + } + + uint32_t buf_addr =3D td1; + uint32_t buf_len =3D td0 & TD0_TBL; + + if (buf_len > 0 && (s->ecmr & ECMR_TE)) { + uint8_t *buf =3D g_malloc(buf_len); + dma_memory_read(&address_space_memory, buf_addr, + buf, buf_len, MEMTXATTRS_UNSPECIFIED); + qemu_send_packet(qemu_get_queue(s->nic), buf, buf_len); + g_free(buf); + } + + /* Return descriptor to CPU: clear TACT, mark SOF+EOF */ + td0 &=3D ~TD0_TACT; + td0 |=3D TD0_TFP1 | TD0_TFP0; + uint32_t w =3D cpu_to_le32(td0); + dma_memory_write(&address_space_memory, s->tx_cur, + &w, 4, MEMTXATTRS_UNSPECIFIED); + + s->eesr |=3D EESR_TC; + if (td0 & TD0_TWBI) { + s->eesr |=3D EESR_TWB; + } + did_tx =3D true; + + if (td0 & TD0_TDLE) { + s->tx_cur =3D s->tdlar; + } else { + s->tx_cur +=3D DESC_SIZE; + } + } + + if (did_tx) { + s->edtrr &=3D ~0x1u; + etherc_update_irq(s); + } +} + +/* + * RX path: called by QEMU net layer on packet arrival. + */ +static bool etherc_can_receive(NetClientState *nc) +{ + RX65NEthercState *s =3D qemu_get_nic_opaque(nc); + return !!(s->edrrr & 0x1) && !!(s->ecmr & ECMR_RE); +} + +static ssize_t etherc_receive(NetClientState *nc, const uint8_t *buf, + size_t len) +{ + RX65NEthercState *s =3D qemu_get_nic_opaque(nc); + + if (!(s->edrrr & 0x1) || !(s->ecmr & ECMR_RE)) { + return -1; + } + + /* Walk descriptor ring to find an EDMAC-owned (RACT=3D1) entry */ + uint32_t start =3D s->rx_cur; + for (;;) { + uint32_t rd0, rd1; + dma_memory_read(&address_space_memory, s->rx_cur, + &rd0, 4, MEMTXATTRS_UNSPECIFIED); + dma_memory_read(&address_space_memory, s->rx_cur + 4, + &rd1, 4, MEMTXATTRS_UNSPECIFIED); + rd0 =3D le32_to_cpu(rd0); + rd1 =3D le32_to_cpu(rd1); + + if (rd0 & RD0_RACT) { + break; /* found a free descriptor */ + } + + /* Descriptor is CPU-owned: advance */ + bool last =3D !!(rd0 & RD0_RDLE); + s->rx_cur =3D last ? s->rdlar : s->rx_cur + DESC_SIZE; + + if (s->rx_cur =3D=3D start) { + /* Wrapped all the way around: no free descriptors */ + s->eesr |=3D EESR_RDE; + s->rmfcr +=3D 1; + etherc_update_irq(s); + return -1; + } + } + + /* Read the free descriptor */ + uint32_t rd0, rd1; + dma_memory_read(&address_space_memory, s->rx_cur, + &rd0, 4, MEMTXATTRS_UNSPECIFIED); + dma_memory_read(&address_space_memory, s->rx_cur + 4, + &rd1, 4, MEMTXATTRS_UNSPECIFIED); + rd0 =3D le32_to_cpu(rd0); + rd1 =3D le32_to_cpu(rd1); + + uint32_t buf_addr =3D rd1; + uint32_t buf_len =3D rd0 & RD0_RBL; + size_t copy_len =3D MIN(len, buf_len); + + dma_memory_write(&address_space_memory, buf_addr, + buf, copy_len, MEMTXATTRS_UNSPECIFIED); + + /* Write back descriptor: clear RACT, mark SOF+EOF, write RFL in RD2 */ + rd0 &=3D ~RD0_RACT; + rd0 |=3D RD0_RFP1 | RD0_RFP0; + + uint32_t w0 =3D cpu_to_le32(rd0); + uint32_t w2 =3D cpu_to_le32((uint32_t)copy_len << 16); + dma_memory_write(&address_space_memory, s->rx_cur, + &w0, 4, MEMTXATTRS_UNSPECIFIED); + dma_memory_write(&address_space_memory, s->rx_cur + 8, + &w2, 4, MEMTXATTRS_UNSPECIFIED); + + s->eesr |=3D EESR_FR; + if (buf[0] & 0x01) { + s->eesr |=3D EESR_RMAF; + } + + if (rd0 & RD0_RDLE) { + s->rx_cur =3D s->rdlar; + } else { + s->rx_cur +=3D DESC_SIZE; + } + + etherc_update_irq(s); + return (ssize_t)len; +} + +/* + * Filtering: honour promiscuous mode and MAC address. + */ +static bool etherc_accept_frame(RX65NEthercState *s, const uint8_t *buf) +{ + uint8_t mac[6]; + + if (s->ecmr & ECMR_PRM) { + return true; + } + /* Build our 6-byte MAC from MAHR/MALR */ + mac[0] =3D (s->mahr >> 24) & 0xFF; + mac[1] =3D (s->mahr >> 16) & 0xFF; + mac[2] =3D (s->mahr >> 8) & 0xFF; + mac[3] =3D s->mahr & 0xFF; + mac[4] =3D (s->malr >> 24) & 0xFF; + mac[5] =3D (s->malr >> 16) & 0xFF; + + if (buf[0] & 0x01) { + return true; /* multicast / broadcast */ + } + return memcmp(buf, mac, 6) =3D=3D 0; +} + +static ssize_t etherc_receive_filter(NetClientState *nc, + const uint8_t *buf, size_t len) +{ + RX65NEthercState *s =3D qemu_get_nic_opaque(nc); + if (!etherc_accept_frame(s, buf)) { + return len; /* silently discard */ + } + return etherc_receive(nc, buf, len); +} + +/* + * EDMAC software reset + */ +static void etherc_sw_reset(RX65NEthercState *s) +{ + s->eesr =3D 0; + s->edtrr =3D 0; + s->edrrr =3D 0; + s->tx_cur =3D s->tdlar; + s->rx_cur =3D s->rdlar; + etherc_update_irq(s); +} + +/* + * MMIO read + */ +static uint64_t etherc_read(void *opaque, hwaddr addr, unsigned size) +{ + RX65NEthercState *s =3D opaque; + + switch (addr) { + /* ETHERC */ + case R_ECMR: + return s->ecmr; + case R_RFLR: + return s->rflr; + case R_ECSR: + return s->ecsr; + case R_ECSIPR: + return s->ecsipr; + case R_PIR: + return s->pir; + case R_PSR: + /*link up */ + return 0x0001; + case R_RDMLR: + return s->rdmlr; + case R_IPGR: + return s->ipgr; + case R_APR: + return s->apr; + case R_MPR: + return s->mpr; + case R_RFCF: + return 0; + case R_TPAUSER: + return s->tpauser; + case R_TPAUSECR:return 0; + case R_BCFRR: + return s->bcfrr; + case R_MAHR: + return s->mahr; + case R_MALR: + return s->malr; + case R_TROCR: + return s->trocr; + case R_CDCR: + return s->cdcr; + case R_LCCR: + return s->lccr; + case R_CNDCR: + return s->cndcr; + case R_CEFCR: + return s->cefcr; + case R_FRECR: + return s->frecr; + case R_TSFRCR: + return s->tsfrcr; + case R_TLFRCR: + return s->tlfrcr; + case R_RFCR: + return s->rfcr; + case R_MAFCR: + return s->mafcr; + /* EDMAC */ + case R_EDMR: + return s->edmr; + case R_EDTRR: + return s->edtrr; + case R_EDRRR: + return s->edrrr; + case R_TDLAR: + return s->tdlar; + case R_RDLAR: + return s->rdlar; + case R_EESR: + return s->eesr; + case R_EESIPR: + return s->eesipr; + case R_TRSCER: + return s->trscer; + case R_RMFCR: + return s->rmfcr; + case R_TFTR: + return s->tftr; + case R_FDR: + return s->fdr; + case R_RMCR: + return s->rmcr; + case R_TFUCR: + return s->tfucr; + case R_RFOCR: + return s->rfocr; + case R_FCFTR: + return s->fcftr; + case R_RPADIR: + return s->rpadir; + case R_TRIMD: + return s->trimd; + case R_RBWAR: + return s->rx_cur; + case R_RDFAR: + return s->rx_cur; + case R_TBRAR: + return s->tx_cur; + case R_TDFAR: + return s->tx_cur; + default: + qemu_log_mask(LOG_UNIMP, + "renesas_etherc: read 0x%" HWADDR_PRIX " unimplement= ed\n", + addr); + return UINT64_MAX; + } +} + +/* + * MMIO write + */ +static void etherc_write(void *opaque, hwaddr addr, uint64_t val, unsigned= size) +{ + RX65NEthercState *s =3D opaque; + + switch (addr) { + /* ETHERC */ + case R_ECMR: + s->ecmr =3D val; + break; + case R_RFLR: + s->rflr =3D val; + break; + case R_ECSR: + /*W0C */ + s->ecsr &=3D ~val; + break; + case R_ECSIPR: + s->ecsipr =3D val; + break; + case R_PIR: + etherc_pir_write(s, val); + break; + case R_RDMLR: + s->rdmlr =3D val; + break; + case R_IPGR: + s->ipgr =3D val; + break; + case R_APR: + s->apr =3D val; + break; + case R_MPR: + s->mpr =3D val; + break; + case R_TPAUSER: + s->tpauser =3D val; + break; + case R_BCFRR: + s->bcfrr =3D val; + break; + case R_MAHR: + s->mahr =3D val; + break; + case R_MALR: + s->malr =3D val; + break; + case R_TROCR: + /*write clears counter */ + s->trocr =3D 0; + break; + case R_CDCR: + s->cdcr =3D 0; + break; + case R_LCCR: + s->lccr =3D 0; + break; + case R_CNDCR: + s->cndcr =3D 0; + break; + case R_CEFCR: + s->cefcr =3D 0; + break; + case R_FRECR: + s->frecr =3D 0; + break; + case R_TSFRCR: + s->tsfrcr =3D 0; + break; + case R_TLFRCR: + s->tlfrcr =3D 0; + break; + case R_RFCR: + s->rfcr =3D 0; + break; + case R_MAFCR: + s->mafcr =3D 0; + break; + + /* EDMAC */ + case R_EDMR: + s->edmr =3D val; + if (val & 0x1) { /* SWR: software reset */ + etherc_sw_reset(s); + s->edmr &=3D ~0x1u; + } + break; + case R_EDTRR: + s->edtrr =3D val; + if (val & 0x1) { + s->tx_cur =3D s->tdlar; + etherc_tx_run(s); + } + break; + case R_EDRRR: + s->edrrr =3D val; + if (val & 0x1) { + s->rx_cur =3D s->rdlar; + qemu_flush_queued_packets(qemu_get_queue(s->nic)); + } + break; + case R_TDLAR: + s->tdlar =3D val; + s->tx_cur =3D val; + break; + case R_RDLAR: + s->rdlar =3D val; + s->rx_cur =3D val; + break; + case R_EESR: + /*W0C */ + s->eesr &=3D ~val; + etherc_update_irq(s); + break; + case R_EESIPR: + s->eesipr =3D val; + etherc_update_irq(s); + break; + case R_TRSCER: + s->trscer =3D val; + break; + case R_RMFCR: + s->rmfcr =3D 0; + break; + case R_TFTR: + s->tftr =3D val; + break; + case R_FDR: + s->fdr =3D val; + break; + case R_RMCR: + s->rmcr =3D val; + break; + case R_TFUCR: + s->tfucr =3D 0; + break; + case R_RFOCR: + s->rfocr =3D 0; + break; + case R_FCFTR: + s->fcftr =3D val; + break; + case R_RPADIR: + s->rpadir =3D val; + break; + case R_TRIMD: + s->trimd =3D val; + break; + default: + qemu_log_mask(LOG_UNIMP, "renesas_etherc: write 0x%" HWADDR_PRIX + " unimplemented\n", + addr); + break; + } +} + +static const MemoryRegionOps etherc_ops =3D { + .read =3D etherc_read, + .write =3D etherc_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .impl =3D { .min_access_size =3D 4, .max_access_size =3D 4 }, + .valid =3D { .min_access_size =3D 4, .max_access_size =3D 4 }, +}; + +/* + * NIC callbacks + */ +static NetClientInfo etherc_net_info =3D { + .type =3D NET_CLIENT_DRIVER_NIC, + .size =3D sizeof(NICState), + .can_receive =3D etherc_can_receive, + .receive =3D etherc_receive_filter, +}; + +/* + * Device lifecycle + */ +static void etherc_reset(DeviceState *dev) +{ + RX65NEthercState *s =3D RENESAS_ETHERC(dev); + + s->ecmr =3D 0; + s->rflr =3D 0x05EE; /* 1518 bytes max frame length (reset value) = */ + s->ecsr =3D 0; + s->ecsipr =3D 0; + s->pir =3D 0; + s->rdmlr =3D 0; + s->ipgr =3D 0x0014; + s->apr =3D 0x0000FFFF; + s->mpr =3D 0; + s->tpauser =3D 0; + s->bcfrr =3D 0; + s->mahr =3D 0; + s->malr =3D 0; + s->trocr =3D 0; s->cdcr =3D 0; s->lccr =3D 0; s->cndcr =3D 0; + s->cefcr =3D 0; s->frecr =3D 0; s->tsfrcr =3D 0; s->tlfrcr =3D 0; + s->rfcr =3D 0; s->mafcr =3D 0; + + s->edmr =3D 0; + s->edtrr =3D 0; + s->edrrr =3D 0; + s->tdlar =3D 0; + s->rdlar =3D 0; + s->eesr =3D 0; + s->eesipr =3D 0; + s->trscer =3D 0; + s->rmfcr =3D 0; + s->tftr =3D 0; + s->fdr =3D 0x0707; + s->rmcr =3D 0; + s->tfucr =3D 0; s->rfocr =3D 0; + s->fcftr =3D 0; + s->rpadir =3D 0; + s->trimd =3D 0; + + s->tx_cur =3D 0; + s->rx_cur =3D 0; + + memset(&s->mdio, 0, sizeof(s->mdio)); + s->mdio.in_cnt =3D -1; + s->mdio.out_cnt =3D -1; + s->mdio.write_cnt =3D -1; + + etherc_update_irq(s); +} + +static void etherc_init(Object *obj) +{ + SysBusDevice *d =3D SYS_BUS_DEVICE(obj); + RX65NEthercState *s =3D RENESAS_ETHERC(obj); + + memory_region_init_io(&s->memory, obj, ðerc_ops, s, + "renesas-etherc", ETHERC_MMIO_SIZE); + sysbus_init_mmio(d, &s->memory); + + sysbus_init_irq(d, &s->irq[ETHERC_IRQ_EINT]); +} + +static void etherc_realize(DeviceState *dev, Error **errp) +{ + RX65NEthercState *s =3D RENESAS_ETHERC(dev); + + qemu_macaddr_default_if_unset(&s->conf.macaddr); + s->nic =3D qemu_new_nic(ðerc_net_info, &s->conf, + object_get_typename(OBJECT(dev)), + dev->id, &dev->mem_reentrancy_guard, s); + qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a); + + /* Pre-load MAC address registers from NICConf */ + const uint8_t *m =3D s->conf.macaddr.a; + s->mahr =3D ((uint32_t)m[0] << 24) | ((uint32_t)m[1] << 16) | + ((uint32_t)m[2] << 8) | m[3]; + s->malr =3D ((uint32_t)m[4] << 24) | ((uint32_t)m[5] << 16); +} + +/* + * VMState + */ +static const VMStateDescription vmstate_mdio =3D { + .name =3D "renesas-etherc-mdio", + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (const VMStateField[]) { + VMSTATE_INT32(preamble, MDIOState), + VMSTATE_INT32(in_cnt, MDIOState), + VMSTATE_UINT32(sr, MDIOState), + VMSTATE_BOOL(is_read, MDIOState), + VMSTATE_INT32(write_cnt, MDIOState), + VMSTATE_INT32(write_paddr, MDIOState), + VMSTATE_INT32(write_raddr, MDIOState), + VMSTATE_UINT16(write_data, MDIOState), + VMSTATE_UINT16(read_data, MDIOState), + VMSTATE_INT32(out_cnt, MDIOState), + VMSTATE_BOOL(mdi, MDIOState), + VMSTATE_UINT16_ARRAY(phy_regs, MDIOState, 32), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_etherc =3D { + .name =3D "renesas-etherc", + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (const VMStateField[]) { + VMSTATE_UINT32(ecmr, RX65NEthercState), + VMSTATE_UINT32(rflr, RX65NEthercState), + VMSTATE_UINT32(ecsr, RX65NEthercState), + VMSTATE_UINT32(ecsipr, RX65NEthercState), + VMSTATE_UINT32(pir, RX65NEthercState), + VMSTATE_UINT32(rdmlr, RX65NEthercState), + VMSTATE_UINT32(ipgr, RX65NEthercState), + VMSTATE_UINT32(apr, RX65NEthercState), + VMSTATE_UINT32(mpr, RX65NEthercState), + VMSTATE_UINT32(tpauser, RX65NEthercState), + VMSTATE_UINT32(bcfrr, RX65NEthercState), + VMSTATE_UINT32(mahr, RX65NEthercState), + VMSTATE_UINT32(malr, RX65NEthercState), + VMSTATE_UINT32(edmr, RX65NEthercState), + VMSTATE_UINT32(edtrr, RX65NEthercState), + VMSTATE_UINT32(edrrr, RX65NEthercState), + VMSTATE_UINT32(tdlar, RX65NEthercState), + VMSTATE_UINT32(rdlar, RX65NEthercState), + VMSTATE_UINT32(eesr, RX65NEthercState), + VMSTATE_UINT32(eesipr, RX65NEthercState), + VMSTATE_UINT32(trscer, RX65NEthercState), + VMSTATE_UINT32(rmfcr, RX65NEthercState), + VMSTATE_UINT32(tftr, RX65NEthercState), + VMSTATE_UINT32(fdr, RX65NEthercState), + VMSTATE_UINT32(rmcr, RX65NEthercState), + VMSTATE_UINT32(fcftr, RX65NEthercState), + VMSTATE_UINT32(rpadir, RX65NEthercState), + VMSTATE_UINT32(trimd, RX65NEthercState), + VMSTATE_UINT32(tx_cur, RX65NEthercState), + VMSTATE_UINT32(rx_cur, RX65NEthercState), + VMSTATE_STRUCT(mdio, RX65NEthercState, 1, vmstate_mdio, MDIOState), + VMSTATE_END_OF_LIST() + } +}; + +static const Property etherc_properties[] =3D { + DEFINE_NIC_PROPERTIES(RX65NEthercState, conf), +}; + +static void etherc_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(klass); + + dc->realize =3D etherc_realize; + dc->vmsd =3D &vmstate_etherc; + device_class_set_legacy_reset(dc, etherc_reset); + device_class_set_props(dc, etherc_properties); + set_bit(DEVICE_CATEGORY_NETWORK, dc->categories); +} + +static const TypeInfo etherc_info =3D { + .name =3D TYPE_RENESAS_ETHERC, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RX65NEthercState), + .instance_init =3D etherc_init, + .class_init =3D etherc_class_init, +}; + +static void etherc_register_types(void) +{ + type_register_static(ðerc_info); +} + +type_init(etherc_register_types) diff --git a/hw/rtc/renesas_rx_rtc.c b/hw/rtc/renesas_rx_rtc.c new file mode 100644 index 0000000000..69cfa19d81 --- /dev/null +++ b/hw/rtc/renesas_rx_rtc.c @@ -0,0 +1,330 @@ +/* + * Renesas RX Realtime Clock (RTC) + * + * Datasheet: RX65N Group, RX651 Group User's Manual: Hardware + * (Rev.1.00 R01UH0590EJ0100), section 26 (RTC) + * + * Copyright (c) 2026 Umar, Sayyad Mahammad + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * + * Calendar-count-mode model. The time/date registers are BCD-encoded and = the + * counter advances once per second (driven by a host timer) while RCR2.ST= ART is + * set. Seeded from host wall-clock time at reset. Periodic/alarm/carry + * interrupts are not generated. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/timer.h" +#include "qemu/bcd.h" +#include "hw/rtc/renesas_rx_rtc.h" +#include "migration/vmstate.h" +#include "system/rtc.h" +#include "system/system.h" + +/* Register offsets from the RTC base (0x0008C400), calendar count mode. */ +#define R_R64CNT 0x00 /* 8-bit 64 Hz counter (read-only) */ +#define R_RSECCNT 0x02 /* 8-bit seconds (BCD) */ +#define R_RMINCNT 0x04 /* 8-bit minutes (BCD) */ +#define R_RHRCNT 0x06 /* 8-bit hours (BCD) */ +#define R_RWKCNT 0x08 /* 8-bit day of week */ +#define R_RDAYCNT 0x0A /* 8-bit day (BCD) */ +#define R_RMONCNT 0x0C /* 8-bit month (BCD) */ +#define R_RYRCNT 0x0E /* 16-bit year (BCD) */ +#define R_RSECAR 0x10 +#define R_RMINAR 0x12 +#define R_RHRAR 0x14 +#define R_RWKAR 0x16 +#define R_RDAYAR 0x18 +#define R_RMONAR 0x1A +#define R_RYRAR 0x1C +#define R_RCR1 0x22 /* 8-bit control 1 */ +#define R_RCR2 0x24 /* 8-bit control 2 */ +#define R_RCR3 0x26 /* 8-bit control 3 */ +#define R_RCR4 0x28 /* 8-bit control 4 */ + +#define RCR2_START (1u << 0) +#define RCR2_RESET (1u << 1) + +static int days_in_month(int mon, int year) +{ + static const int d[] =3D { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30,= 31 }; + if (mon =3D=3D 2 && ((year % 4 =3D=3D 0 && year % 100 !=3D 0) || year = % 400 =3D=3D 0)) { + return 29; + } + return d[(mon - 1) % 12]; +} + +static void rtc_seed_from_host(RenesasRxRtcState *s) +{ + struct tm now; + + qemu_get_timedate(&now, 0); + s->sec =3D now.tm_sec; + s->min =3D now.tm_min; + s->hour =3D now.tm_hour; + s->wday =3D now.tm_wday; + s->mday =3D now.tm_mday; + s->mon =3D now.tm_mon + 1; + s->year =3D now.tm_year + 1900; +} + +static void rtc_advance_second(RenesasRxRtcState *s) +{ + if (++s->sec < 60) { + return; + } + s->sec =3D 0; + if (++s->min < 60) { + return; + } + s->min =3D 0; + if (++s->hour < 24) { + return; + } + s->hour =3D 0; + s->wday =3D (s->wday + 1) % 7; + if (++s->mday <=3D days_in_month(s->mon, s->year)) { + return; + } + s->mday =3D 1; + if (++s->mon <=3D 12) { + return; + } + s->mon =3D 1; + s->year++; +} + +static void rtc_timer_tick(void *opaque) +{ + RenesasRxRtcState *s =3D opaque; + + if (!(s->rcr2 & RCR2_START)) { + return; + } + rtc_advance_second(s); + timer_mod(s->timer, + qemu_clock_get_ms(rtc_clock) + 1000); +} + +static void rtc_arm_timer(RenesasRxRtcState *s) +{ + if (s->rcr2 & RCR2_START) { + timer_mod(s->timer, qemu_clock_get_ms(rtc_clock) + 1000); + } else { + timer_del(s->timer); + } +} + +static uint64_t rtc_read(void *opaque, hwaddr offset, unsigned size) +{ + RenesasRxRtcState *s =3D opaque; + + switch (offset) { + case R_R64CNT: + return 0; + case R_RSECCNT: + return to_bcd(s->sec); + case R_RMINCNT: + return to_bcd(s->min); + case R_RHRCNT: + return to_bcd(s->hour); + case R_RWKCNT: + return s->wday; + case R_RDAYCNT: + return to_bcd(s->mday); + case R_RMONCNT: + return to_bcd(s->mon); + case R_RYRCNT: + return to_bcd(s->year % 100) | (to_bcd(s->year / 100) << 8); + case R_RSECAR: + return s->secar; + case R_RMINAR: + return s->minar; + case R_RHRAR: + return s->hrar; + case R_RWKAR: + return s->wkar; + case R_RDAYAR: + return s->dayar; + case R_RMONAR: + return s->monar; + case R_RYRAR: + return s->yrar; + case R_RCR1: + return s->rcr1; + case R_RCR2: + return s->rcr2; + case R_RCR3: + return s->rcr3; + case R_RCR4: + return s->rcr4; + default: + qemu_log_mask(LOG_UNIMP, "renesas-rx-rtc: read unimplemented 0x%" + HWADDR_PRIx "\n", offset); + return 0; + } +} + +static void rtc_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + RenesasRxRtcState *s =3D opaque; + + switch (offset) { + case R_RSECCNT: + s->sec =3D from_bcd(value & 0x7f); + break; + case R_RMINCNT: + s->min =3D from_bcd(value & 0x7f); + break; + case R_RHRCNT: + s->hour =3D from_bcd(value & 0x3f); + break; + case R_RWKCNT: + s->wday =3D value & 7; + break; + case R_RDAYCNT: + s->mday =3D from_bcd(value & 0x3f); + break; + case R_RMONCNT: + s->mon =3D from_bcd(value & 0x1f); + break; + case R_RYRCNT: + s->year =3D 2000 + from_bcd(value & 0xff); + break; + case R_RSECAR: + s->secar =3D value; + break; + case R_RMINAR: + s->minar =3D value; + break; + case R_RHRAR: + s->hrar =3D value; + break; + case R_RWKAR: + s->wkar =3D value; + break; + case R_RDAYAR: + s->dayar =3D value; + break; + case R_RMONAR: + s->monar =3D value; + break; + case R_RYRAR: + s->yrar =3D value; + break; + case R_RCR1: + s->rcr1 =3D value; + break; + case R_RCR2: + if (value & RCR2_RESET) { + s->sec =3D s->min =3D s->hour =3D 0; + } + s->rcr2 =3D value & ~RCR2_RESET; + rtc_arm_timer(s); + break; + case R_RCR3: + s->rcr3 =3D value; + break; + case R_RCR4: + s->rcr4 =3D value; + break; + default: + qemu_log_mask(LOG_UNIMP, "renesas-rx-rtc: write unimplemented 0x%" + HWADDR_PRIx "\n", offset); + break; + } +} + +static const MemoryRegionOps rtc_ops =3D { + .read =3D rtc_read, + .write =3D rtc_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .impl =3D { .min_access_size =3D 1, .max_access_size =3D 2 }, + .valid =3D { .min_access_size =3D 1, .max_access_size =3D 2 }, +}; + +static void rx_rtc_reset(DeviceState *dev) +{ + RenesasRxRtcState *s =3D RENESAS_RX_RTC(dev); + + rtc_seed_from_host(s); + s->secar =3D s->minar =3D s->hrar =3D s->wkar =3D s->dayar =3D s->mona= r =3D 0; + s->yrar =3D 0; + s->rcr1 =3D 0; + s->rcr2 =3D 0; + s->rcr3 =3D 0; + s->rcr4 =3D 0; + timer_del(s->timer); +} + +static void rx_rtc_realize(DeviceState *dev, Error **errp) +{ + RenesasRxRtcState *s =3D RENESAS_RX_RTC(dev); + + s->timer =3D timer_new_ms(rtc_clock, rtc_timer_tick, s); +} + +static void rx_rtc_init(Object *obj) +{ + RenesasRxRtcState *s =3D RENESAS_RX_RTC(obj); + + memory_region_init_io(&s->mr, obj, &rtc_ops, s, + "renesas-rx-rtc", RX_RTC_REGS_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mr); +} + +static const VMStateDescription vmstate_rx_rtc =3D { + .name =3D "renesas-rx-rtc", + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (const VMStateField[]) { + VMSTATE_INT32(sec, RenesasRxRtcState), + VMSTATE_INT32(min, RenesasRxRtcState), + VMSTATE_INT32(hour, RenesasRxRtcState), + VMSTATE_INT32(wday, RenesasRxRtcState), + VMSTATE_INT32(mday, RenesasRxRtcState), + VMSTATE_INT32(mon, RenesasRxRtcState), + VMSTATE_INT32(year, RenesasRxRtcState), + VMSTATE_UINT8(secar, RenesasRxRtcState), + VMSTATE_UINT8(minar, RenesasRxRtcState), + VMSTATE_UINT8(hrar, RenesasRxRtcState), + VMSTATE_UINT8(wkar, RenesasRxRtcState), + VMSTATE_UINT8(dayar, RenesasRxRtcState), + VMSTATE_UINT8(monar, RenesasRxRtcState), + VMSTATE_UINT16(yrar, RenesasRxRtcState), + VMSTATE_UINT8(rcr1, RenesasRxRtcState), + VMSTATE_UINT8(rcr2, RenesasRxRtcState), + VMSTATE_UINT8(rcr3, RenesasRxRtcState), + VMSTATE_UINT8(rcr4, RenesasRxRtcState), + VMSTATE_TIMER_PTR(timer, RenesasRxRtcState), + VMSTATE_END_OF_LIST() + } +}; + +static void rx_rtc_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(klass); + + dc->realize =3D rx_rtc_realize; + dc->vmsd =3D &vmstate_rx_rtc; + device_class_set_legacy_reset(dc, rx_rtc_reset); +} + +static const TypeInfo rx_rtc_info =3D { + .name =3D TYPE_RENESAS_RX_RTC, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RenesasRxRtcState), + .instance_init =3D rx_rtc_init, + .class_init =3D rx_rtc_class_init, +}; + +static void rx_rtc_register_types(void) +{ + type_register_static(&rx_rtc_info); +} + +type_init(rx_rtc_register_types) diff --git a/hw/ssi/renesas_rspi.c b/hw/ssi/renesas_rspi.c new file mode 100644 index 0000000000..ad5d8ca278 --- /dev/null +++ b/hw/ssi/renesas_rspi.c @@ -0,0 +1,319 @@ +/* + * Renesas RX65N Serial Peripheral Interface (RSPI) + * + * Datasheet: RX65N Group, RX651 Group User's Manual: Hardware + * (Rev.1.00 R01UH0590EJ0100), Section 30 + * + * Copyright (c) 2026 Umar, Sayyad Mahammad + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "hw/core/irq.h" +#include "hw/core/registerfields.h" +#include "hw/core/qdev-properties.h" +#include "hw/ssi/renesas_rspi.h" +#include "migration/vmstate.h" + +/* + * Register offsets from device base. + * Reference: RX65N Group Hardware Manual Table 30.1 + */ +REG8(SPCR, 0x00) + FIELD(SPCR, SPMS, 0, 1) /* SPI mode: 0=3D4-wire, 1=3D3-wire */ + FIELD(SPCR, TXONLY, 1, 1) /* transmit-only mode */ + FIELD(SPCR, MODFEN, 2, 1) /* mode fault detect enable */ + FIELD(SPCR, MSTR, 3, 1) /* master/slave: 1=3Dmaster */ + FIELD(SPCR, SPEIE, 4, 1) /* error interrupt enable */ + FIELD(SPCR, SPTIE, 5, 1) /* transmit interrupt enable */ + FIELD(SPCR, SPE, 6, 1) /* SPI function enable */ + FIELD(SPCR, SPRIE, 7, 1) /* receive interrupt enable */ + +REG8(SSLP, 0x01) /* SSL polarity */ +REG8(SPPCR, 0x02) /* pin control */ + +REG8(SPSR, 0x03) + FIELD(SPSR, OVRF, 0, 1) /* overrun flag */ + FIELD(SPSR, IDLNF, 1, 1) /* idle flag */ + FIELD(SPSR, MODF, 2, 1) /* mode fault flag */ + FIELD(SPSR, PERF, 3, 1) /* parity error flag */ + FIELD(SPSR, UDRF, 4, 1) /* underrun flag */ + FIELD(SPSR, SPTEF, 5, 1) /* transmit buffer empty */ + FIELD(SPSR, SPRFF, 6, 1) /* receive FIFO full (not implemented here) = */ + FIELD(SPSR, SPRF, 7, 1) /* receive buffer full */ + +REG32(SPDR, 0x04) /* data register (also 16-bit and 8-bit access) */ +REG8(SPSCR, 0x08) /* sequence control */ +REG8(SPSSR, 0x09) /* sequence status */ +REG8(SPBR, 0x0A) /* bit rate */ +REG8(SPDCR, 0x0B) + FIELD(SPDCR, SPFC, 0, 2) /* frames per access */ + FIELD(SPDCR, SPRDTD, 4, 1) /* receive/transmit data select */ + FIELD(SPDCR, SPLW, 5, 1) /* access width (1=3Dlong word) */ + FIELD(SPDCR, SPBYT, 6, 1) /* byte access */ + +REG8(SPCKD, 0x0C) /* clock delay */ +REG8(SSLND, 0x0D) /* SSL negate delay */ +REG8(SPND, 0x0E) /* next-access delay */ +REG8(SPCR2, 0x0F) /* control 2 */ +/* SPCMD0=E2=80=93SPCMD7 at offsets 0x10=E2=80=930x1E (16-bit each) */ +#define A_SPCMD_BASE 0x10 +#define A_SPCMD_END 0x1E +REG8(SPDCR2, 0x20) /* data control 2 */ + +static void rspi_do_transfer(RX65NRSPIState *s) +{ + uint32_t rx; + + /* Only transfer in master mode with SPI enabled */ + if (!FIELD_EX8(s->spcr, SPCR, MSTR) || + !FIELD_EX8(s->spcr, SPCR, SPE)) { + return; + } + + rx =3D ssi_transfer(s->ssi, s->spdr); + s->spdr =3D rx; + + /* Mark receive buffer full, transmit buffer empty */ + s->spsr =3D FIELD_DP8(s->spsr, SPSR, SPRF, 1); + s->spsr =3D FIELD_DP8(s->spsr, SPSR, SPTEF, 1); + + /* Fire receive interrupt */ + if (FIELD_EX8(s->spcr, SPCR, SPRIE)) { + qemu_irq_pulse(s->irq[RSPI_IRQ_SPRI]); + } +} + +static uint64_t rspi_read(void *opaque, hwaddr addr, unsigned size) +{ + RX65NRSPIState *s =3D opaque; + uint64_t ret; + + if (addr >=3D A_SPCMD_BASE && addr <=3D A_SPCMD_END) { + int idx =3D (addr - A_SPCMD_BASE) / 2; + return s->spcmd[idx]; + } + + switch (addr) { + case A_SPCR: + return s->spcr; + case A_SSLP: + return s->sslp; + case A_SPPCR: + return s->sppcr; + case A_SPSR: + ret =3D s->spsr; + /* Reading SPSR clears SPRF and OVRF */ + s->spsr =3D FIELD_DP8(s->spsr, SPSR, SPRF, 0); + s->spsr =3D FIELD_DP8(s->spsr, SPSR, OVRF, 0); + return ret; + case A_SPDR: + s->spsr =3D FIELD_DP8(s->spsr, SPSR, SPRF, 0); + /* Raise transmit interrupt now that the buffer is free again */ + if (FIELD_EX8(s->spcr, SPCR, SPTIE)) { + qemu_irq_pulse(s->irq[RSPI_IRQ_SPTI]); + } + return s->spdr; + case A_SPSCR: + return s->spscr; + case A_SPSSR: + return s->spssr; + case A_SPBR: + return s->spbr; + case A_SPDCR: + return s->spdcr; + case A_SPCKD: + return s->spckd; + case A_SSLND: + return s->sslnd; + case A_SPND: + return s->spnd; + case A_SPCR2: + return s->spcr2; + case A_SPDCR2: + return s->spdcr2; + default: + qemu_log_mask(LOG_UNIMP, + "renesas_rspi: read 0x%" HWADDR_PRIX " not implement= ed\n", + addr); + return UINT64_MAX; + } +} + +static void rspi_write(void *opaque, hwaddr addr, uint64_t val, unsigned s= ize) +{ + RX65NRSPIState *s =3D opaque; + + if (addr >=3D A_SPCMD_BASE && addr <=3D A_SPCMD_END) { + int idx =3D (addr - A_SPCMD_BASE) / 2; + s->spcmd[idx] =3D val; + return; + } + + switch (addr) { + case A_SPCR: + s->spcr =3D val; + /* Raise SPTI when SPE+SPTIE are both set (buffer initially empty)= */ + if (FIELD_EX8(s->spcr, SPCR, SPE) && + FIELD_EX8(s->spcr, SPCR, SPTIE) && + FIELD_EX8(s->spsr, SPSR, SPTEF)) { + qemu_irq_pulse(s->irq[RSPI_IRQ_SPTI]); + } + break; + case A_SSLP: + s->sslp =3D val; + break; + case A_SPPCR: + s->sppcr =3D val; + break; + case A_SPSR: + /* W0C for error/status bits */ + s->spsr &=3D val | 0xe0; /* keep SPTEF, SPRFF, SPRF read-only */ + break; + case A_SPDR: + s->spdr =3D val; + s->spsr =3D FIELD_DP8(s->spsr, SPSR, SPTEF, 0); + rspi_do_transfer(s); + break; + case A_SPSCR: + s->spscr =3D val; + break; + case A_SPBR: + s->spbr =3D val; + break; + case A_SPDCR: + s->spdcr =3D val; + break; + case A_SPCKD: + s->spckd =3D val; + break; + case A_SSLND: + s->sslnd =3D val; + break; + case A_SPND: + s->spnd =3D val; + break; + case A_SPCR2: + s->spcr2 =3D val; + break; + case A_SPDCR2: + s->spdcr2 =3D val; + break; + default: + qemu_log_mask(LOG_UNIMP, "renesas_rspi: write 0x%" HWADDR_PRIX + " not implemented\n", + addr); + break; + } +} + +static const MemoryRegionOps rspi_ops =3D { + .read =3D rspi_read, + .write =3D rspi_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .impl =3D { + .min_access_size =3D 1, + .max_access_size =3D 4, + }, + .valid =3D { + .min_access_size =3D 1, + .max_access_size =3D 4, + }, +}; + +static void rspi_reset(DeviceState *dev) +{ + RX65NRSPIState *s =3D RENESAS_RSPI(dev); + int i; + + s->spcr =3D 0x00; + s->sslp =3D 0x00; + s->sppcr =3D 0x00; + s->spsr =3D 0x20; /* SPTEF=3D1: transmit buffer initially empty */ + s->spdr =3D 0x00000000; + s->spscr =3D 0x00; + s->spssr =3D 0x00; + s->spbr =3D 0xff; + s->spdcr =3D 0x20; /* SPLW=3D1: long-word access default */ + s->spckd =3D 0x00; + s->sslnd =3D 0x00; + s->spnd =3D 0x00; + s->spcr2 =3D 0x00; + s->spdcr2 =3D 0x00; + for (i =3D 0; i < 8; i++) { + s->spcmd[i] =3D 0x0700; /* default: 8-bit, CPOL=3D0, CPHA=3D0 */ + } +} + +static void rspi_init(Object *obj) +{ + SysBusDevice *d =3D SYS_BUS_DEVICE(obj); + RX65NRSPIState *s =3D RENESAS_RSPI(obj); + int i; + + /* 0x24 bytes covers SPCR through SPDCR2 */ + memory_region_init_io(&s->memory, obj, &rspi_ops, s, + "renesas-rspi", 0x24); + sysbus_init_mmio(d, &s->memory); + + for (i =3D 0; i < RSPI_NR_IRQ; i++) { + sysbus_init_irq(d, &s->irq[i]); + } + + s->ssi =3D ssi_create_bus(DEVICE(obj), "ssi"); +} + +static const VMStateDescription vmstate_rspi =3D { + .name =3D "renesas-rspi", + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (const VMStateField[]) { + VMSTATE_UINT8(spcr, RX65NRSPIState), + VMSTATE_UINT8(sslp, RX65NRSPIState), + VMSTATE_UINT8(sppcr, RX65NRSPIState), + VMSTATE_UINT8(spsr, RX65NRSPIState), + VMSTATE_UINT32(spdr, RX65NRSPIState), + VMSTATE_UINT8(spscr, RX65NRSPIState), + VMSTATE_UINT8(spssr, RX65NRSPIState), + VMSTATE_UINT8(spbr, RX65NRSPIState), + VMSTATE_UINT8(spdcr, RX65NRSPIState), + VMSTATE_UINT8(spckd, RX65NRSPIState), + VMSTATE_UINT8(sslnd, RX65NRSPIState), + VMSTATE_UINT8(spnd, RX65NRSPIState), + VMSTATE_UINT8(spcr2, RX65NRSPIState), + VMSTATE_UINT16_ARRAY(spcmd, RX65NRSPIState, 8), + VMSTATE_UINT8(spdcr2, RX65NRSPIState), + VMSTATE_END_OF_LIST() + } +}; + +static const Property rspi_properties[] =3D { + DEFINE_PROP_UINT64("input-freq", RX65NRSPIState, input_freq, 0), +}; + +static void rspi_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(klass); + + dc->vmsd =3D &vmstate_rspi; + device_class_set_legacy_reset(dc, rspi_reset); + device_class_set_props(dc, rspi_properties); +} + +static const TypeInfo rspi_info =3D { + .name =3D TYPE_RENESAS_RSPI, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RX65NRSPIState), + .instance_init =3D rspi_init, + .class_init =3D rspi_class_init, +}; + +static void rspi_register_types(void) +{ + type_register_static(&rspi_info); +} + +type_init(rspi_register_types) diff --git a/hw/timer/renesas_mtu3.c b/hw/timer/renesas_mtu3.c new file mode 100644 index 0000000000..7dd20843ed --- /dev/null +++ b/hw/timer/renesas_mtu3.c @@ -0,0 +1,509 @@ +/* + * Renesas RX65N Multi-Function Timer Pulse Unit 3 (MTU3) + * + * Models MTU channels 3 and 4 (the pair sharing base address 0x000C1200). + * + * Datasheet: RX65N Group, RX651 Group User's Manual: Hardware + * (Rev.1.00 R01UH0590EJ0100), Section 17 + * + * Copyright (c) 2026 Umar, Sayyad Mahammad + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "hw/core/irq.h" +#include "hw/core/registerfields.h" +#include "hw/core/qdev-properties.h" +#include "hw/timer/renesas_mtu3.h" +#include "migration/vmstate.h" + +/* + * Register offsets from device base (0x000C1200). + * MTU3 and MTU4 channel registers are interleaved at the base. + * ch[0] =3D MTU channel 3, ch[1] =3D MTU channel 4. + * + * Reference: RX65N Group Hardware Manual Table 17.1 + */ +#define R_TCR_CH3 0x00 /* 8-bit */ +#define R_TCR_CH4 0x01 /* 8-bit */ +#define R_TMDR1_CH3 0x02 /* 8-bit */ +#define R_TMDR1_CH4 0x03 /* 8-bit */ +#define R_TIORH_CH3 0x04 /* 8-bit */ +#define R_TIORL_CH3 0x05 /* 8-bit */ +#define R_TIORH_CH4 0x06 /* 8-bit */ +#define R_TIORL_CH4 0x07 /* 8-bit */ +#define R_TIER_CH3 0x08 /* 8-bit */ +#define R_TIER_CH4 0x09 /* 8-bit */ +#define R_TCNT_CH3 0x10 /* 16-bit */ +#define R_TCNT_CH4 0x12 /* 16-bit */ +#define R_TGRA_CH3 0x18 /* 16-bit */ +#define R_TGRB_CH3 0x1A /* 16-bit */ +#define R_TGRA_CH4 0x1C /* 16-bit */ +#define R_TGRB_CH4 0x1E /* 16-bit */ +#define R_TGRC_CH3 0x24 /* 16-bit */ +#define R_TGRD_CH3 0x26 /* 16-bit */ +#define R_TGRC_CH4 0x28 /* 16-bit */ +#define R_TGRD_CH4 0x2A /* 16-bit */ +#define R_TSR_CH3 0x2C /* 8-bit */ +#define R_TSR_CH4 0x2D /* 8-bit */ +#define R_TSTRA 0x80 /* 8-bit: Timer Start Register A */ + +/* TCR register fields */ +#define TCR_TPCS_MASK 0x07 /* bits [2:0]: prescale select */ +#define TCR_CCLR_SHIFT 3 +#define TCR_CCLR_MASK (0x07 << 3) +#define TCR_CCLR_NONE 0 +#define TCR_CCLR_TGRA 1 /* clear TCNT on TGRA match */ +#define TCR_CCLR_TGRB 2 /* clear TCNT on TGRB match */ + +/* TIER register bits */ +#define TIER_TGIEA (1 << 0) /* TGRA interrupt enable */ +#define TIER_TGIEB (1 << 1) /* TGRB interrupt enable */ +#define TIER_TGIEC (1 << 2) /* TGRC interrupt enable */ +#define TIER_TGIED (1 << 3) /* TGRD interrupt enable */ +#define TIER_TCIEV (1 << 4) /* overflow interrupt enable */ + +/* TSR register bits */ +#define TSR_TGFA (1 << 0) /* TGRA compare match flag */ +#define TSR_TGFB (1 << 1) /* TGRB compare match flag */ +#define TSR_TGFC (1 << 2) /* TGRC compare match flag */ +#define TSR_TGFD (1 << 3) /* TGRD compare match flag */ +#define TSR_TCFV (1 << 4) /* overflow flag */ + +/* TSTRA register bits */ +#define TSTR_STR3 (1 << 6) /* MTU channel 3 start */ +#define TSTR_STR4 (1 << 7) /* MTU channel 4 start */ + +/* Clock prescaler: index =3D TCR.TPCS[2:0] */ +static const uint16_t prescaler_table[8] =3D {1, 4, 16, 64, 256, 1024, 1, = 1}; + +static bool chan_is_running(RXMTU3State *s, int ci) +{ + return !!(s->tstr & (ci =3D=3D 0 ? TSTR_STR3 : TSTR_STR4)); +} + +static uint16_t chan_read_tcnt(RXMTU3State *s, int ci) +{ + struct RXMTU3ChanState *ch =3D &s->ch[ci]; + int64_t now, delta; + uint16_t prescaler; + int64_t elapsed; + + if (!chan_is_running(s, ci)) { + return ch->tcnt; + } + + prescaler =3D prescaler_table[ch->tcr & TCR_TPCS_MASK]; + if (prescaler =3D=3D 0) { + return ch->tcnt; + } + + now =3D qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + delta =3D now - ch->tick; + if (delta <=3D 0) { + return ch->tcnt; + } + + ch->div_round +=3D delta; + elapsed =3D ch->div_round / ((int64_t)prescaler * NANOSECONDS_PER_SECO= ND + / s->input_freq); + ch->div_round %=3D ((int64_t)prescaler * NANOSECONDS_PER_SECOND + / s->input_freq); + ch->tick =3D now; + ch->tcnt =3D (uint16_t)(ch->tcnt + elapsed); + return ch->tcnt; +} + +static void chan_mod_timer(RXMTU3State *s, int ci) +{ + struct RXMTU3ChanState *ch =3D &s->ch[ci]; + uint16_t prescaler; + uint32_t diff; + int64_t next_ns; + + if (!chan_is_running(s, ci)) { + timer_del(&ch->timer); + return; + } + + prescaler =3D prescaler_table[ch->tcr & TCR_TPCS_MASK]; + if (prescaler =3D=3D 0) { + timer_del(&ch->timer); + return; + } + + /* + * Schedule at the nearer of: TGRA compare match or 16-bit overflow. + * TGRB/C/D are not separately scheduled here; the event handler checks + * all flags once the timer fires. + */ + uint16_t tcnt =3D chan_read_tcnt(s, ci); + uint32_t to_tgra =3D (ch->tgra >=3D tcnt) ? (uint32_t)(ch->tgra - tcnt) + : (uint32_t)(0x10000u - tcnt); + uint32_t to_ovf =3D (uint32_t)(0x10000u - tcnt); + diff =3D MIN(to_tgra, to_ovf); + if (diff =3D=3D 0) { + diff =3D 0x10000u; + } + + next_ns =3D (int64_t)diff * prescaler + * (NANOSECONDS_PER_SECOND / s->input_freq); + timer_mod(&ch->timer, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + next_ns); +} + +static void chan_timer_event(RXMTU3State *s, int ci) +{ + struct RXMTU3ChanState *ch =3D &s->ch[ci]; + int base =3D ci * MTU3_CH_NR_IRQ; + uint16_t tcnt =3D chan_read_tcnt(s, ci); + uint8_t cclr =3D (ch->tcr & TCR_CCLR_MASK) >> TCR_CCLR_SHIFT; + + /* TGRA compare match */ + if (tcnt >=3D ch->tgra) { + ch->tsr |=3D TSR_TGFA; + if (ch->tier & TIER_TGIEA) { + qemu_irq_pulse(s->irq[base + MTU3_IRQ_TGIA]); + } + if (cclr =3D=3D TCR_CCLR_TGRA) { + ch->tcnt =3D tcnt - ch->tgra; + ch->tick =3D qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + ch->div_round =3D 0; + chan_mod_timer(s, ci); + return; + } + } + + /* TGRB compare match */ + if (tcnt >=3D ch->tgrb && ch->tgrb !=3D 0xffff) { + ch->tsr |=3D TSR_TGFB; + if (ch->tier & TIER_TGIEB) { + qemu_irq_pulse(s->irq[base + MTU3_IRQ_TGIB]); + } + if (cclr =3D=3D TCR_CCLR_TGRB) { + ch->tcnt =3D tcnt - ch->tgrb; + ch->tick =3D qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + ch->div_round =3D 0; + chan_mod_timer(s, ci); + return; + } + } + + /* Overflow (TCNT wraps from 0xFFFF) */ + if (tcnt =3D=3D 0 || (tcnt >=3D ch->tgra && ch->tgra =3D=3D 0xffff)) { + ch->tsr |=3D TSR_TCFV; + if (ch->tier & TIER_TCIEV) { + qemu_irq_pulse(s->irq[base + MTU3_IRQ_TCIV]); + } + } + + chan_mod_timer(s, ci); +} + +static void chan0_timer_event(void *opaque) +{ + chan_timer_event(opaque, 0); +} + +static void chan1_timer_event(void *opaque) +{ + chan_timer_event(opaque, 1); +} + +static uint64_t mtu3_read(void *opaque, hwaddr addr, unsigned size) +{ + RXMTU3State *s =3D opaque; + + switch (addr) { + case R_TCR_CH3: + return s->ch[0].tcr; + case R_TCR_CH4: + return s->ch[1].tcr; + case R_TMDR1_CH3: + return s->ch[0].tmdr1; + case R_TMDR1_CH4: + return s->ch[1].tmdr1; + case R_TIORH_CH3: + return s->ch[0].tiorh; + case R_TIORL_CH3: + return s->ch[0].tiorl; + case R_TIORH_CH4: + return s->ch[1].tiorh; + case R_TIORL_CH4: + return s->ch[1].tiorl; + case R_TIER_CH3: + return s->ch[0].tier; + case R_TIER_CH4: + return s->ch[1].tier; + case R_TCNT_CH3: + return chan_read_tcnt(s, 0); + case R_TCNT_CH4: + return chan_read_tcnt(s, 1); + case R_TGRA_CH3: + return s->ch[0].tgra; + case R_TGRB_CH3: + return s->ch[0].tgrb; + case R_TGRA_CH4: + return s->ch[1].tgra; + case R_TGRB_CH4: + return s->ch[1].tgrb; + case R_TGRC_CH3: + return s->ch[0].tgrc; + case R_TGRD_CH3: + return s->ch[0].tgrd; + case R_TGRC_CH4: + return s->ch[1].tgrc; + case R_TGRD_CH4: + return s->ch[1].tgrd; + case R_TSR_CH3: + return s->ch[0].tsr; + case R_TSR_CH4: + return s->ch[1].tsr; + case R_TSTRA: + return s->tstr; + default: + qemu_log_mask(LOG_UNIMP, + "renesas_mtu3: read 0x%" HWADDR_PRIX " not implement= ed\n", + addr); + return UINT64_MAX; + } +} + +static void mtu3_write(void *opaque, hwaddr addr, uint64_t val, unsigned s= ize) +{ + RXMTU3State *s =3D opaque; + int ci =3D -1; + + switch (addr) { + case R_TCR_CH3: + s->ch[0].tcr =3D val; + chan_mod_timer(s, 0); + break; + case R_TCR_CH4: + s->ch[1].tcr =3D val; + chan_mod_timer(s, 1); + break; + case R_TMDR1_CH3: + s->ch[0].tmdr1 =3D val; + break; + case R_TMDR1_CH4: + s->ch[1].tmdr1 =3D val; + break; + case R_TIORH_CH3: + s->ch[0].tiorh =3D val; + break; + case R_TIORL_CH3: + s->ch[0].tiorl =3D val; + break; + case R_TIORH_CH4: + s->ch[1].tiorh =3D val; + break; + case R_TIORL_CH4: + s->ch[1].tiorl =3D val; + break; + case R_TIER_CH3: + s->ch[0].tier =3D val; + break; + case R_TIER_CH4: + s->ch[1].tier =3D val; + break; + case R_TCNT_CH3: + s->ch[0].tcnt =3D val; + s->ch[0].tick =3D qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + s->ch[0].div_round =3D 0; + chan_mod_timer(s, 0); + break; + case R_TCNT_CH4: + s->ch[1].tcnt =3D val; + s->ch[1].tick =3D qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + s->ch[1].div_round =3D 0; + chan_mod_timer(s, 1); + break; + case R_TGRA_CH3: + ci =3D 0; + s->ch[0].tgra =3D val; + break; + case R_TGRB_CH3: + ci =3D 0; + s->ch[0].tgrb =3D val; + break; + case R_TGRA_CH4: + ci =3D 1; + s->ch[1].tgra =3D val; + break; + case R_TGRB_CH4: + ci =3D 1; + s->ch[1].tgrb =3D val; + break; + case R_TGRC_CH3: + s->ch[0].tgrc =3D val; + break; + case R_TGRD_CH3: + s->ch[0].tgrd =3D val; + break; + case R_TGRC_CH4: + s->ch[1].tgrc =3D val; + break; + case R_TGRD_CH4: + s->ch[1].tgrd =3D val; + break; + case R_TSR_CH3: + /* W0C: clear flags that are written as 0 */ + s->ch[0].tsr &=3D val; + break; + case R_TSR_CH4: + s->ch[1].tsr &=3D val; + break; + case R_TSTRA: { + uint8_t prev =3D s->tstr; + s->tstr =3D val & (TSTR_STR3 | TSTR_STR4); + for (int i =3D 0; i < MTU3_NR_CHAN; i++) { + uint8_t bit =3D (i =3D=3D 0) ? TSTR_STR3 : TSTR_STR4; + if ((s->tstr & bit) && !(prev & bit)) { + /* Channel just started: latch tick */ + s->ch[i].tick =3D qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + s->ch[i].div_round =3D 0; + chan_mod_timer(s, i); + } else if (!(s->tstr & bit) && (prev & bit)) { + /* Channel stopped: snapshot counter */ + chan_read_tcnt(s, i); + timer_del(&s->ch[i].timer); + } + } + break; + } + default: + qemu_log_mask(LOG_UNIMP, "renesas_mtu3: write 0x%" HWADDR_PRIX + " not implemented\n", + addr); + break; + } + + /* Re-arm timer if a TGR register was updated while channel is running= */ + if (ci >=3D 0 && chan_is_running(s, ci)) { + chan_mod_timer(s, ci); + } +} + +static const MemoryRegionOps mtu3_ops =3D { + .read =3D mtu3_read, + .write =3D mtu3_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .impl =3D { + .min_access_size =3D 1, + .max_access_size =3D 2, + }, + .valid =3D { + .min_access_size =3D 1, + .max_access_size =3D 2, + }, +}; + +static void mtu3_reset(DeviceState *dev) +{ + RXMTU3State *s =3D RXMTU3(dev); + int i; + + s->tstr =3D 0x00; + for (i =3D 0; i < MTU3_NR_CHAN; i++) { + s->ch[i].tcr =3D 0x00; + s->ch[i].tmdr1 =3D 0x00; + s->ch[i].tiorh =3D 0x00; + s->ch[i].tiorl =3D 0x00; + s->ch[i].tier =3D 0x00; + s->ch[i].tsr =3D 0xc0; /* reset value per HW manual */ + s->ch[i].tcnt =3D 0x0000; + s->ch[i].tgra =3D 0xffff; + s->ch[i].tgrb =3D 0xffff; + s->ch[i].tgrc =3D 0xffff; + s->ch[i].tgrd =3D 0xffff; + s->ch[i].tick =3D qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + s->ch[i].div_round =3D 0; + timer_del(&s->ch[i].timer); + } +} + +static void mtu3_init(Object *obj) +{ + SysBusDevice *d =3D SYS_BUS_DEVICE(obj); + RXMTU3State *s =3D RXMTU3(obj); + int i; + + /* 256-byte MMIO covers the interleaved MTU3/MTU4 registers + TSTR */ + memory_region_init_io(&s->memory, obj, &mtu3_ops, s, + "renesas-mtu3", 0x100); + sysbus_init_mmio(d, &s->memory); + + for (i =3D 0; i < MTU3_NR_IRQ; i++) { + sysbus_init_irq(d, &s->irq[i]); + } + + timer_init_ns(&s->ch[0].timer, QEMU_CLOCK_VIRTUAL, chan0_timer_event, = s); + timer_init_ns(&s->ch[1].timer, QEMU_CLOCK_VIRTUAL, chan1_timer_event, = s); +} + +static const VMStateDescription vmstate_mtu3_chan =3D { + .name =3D "renesas-mtu3-chan", + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (const VMStateField[]) { + VMSTATE_UINT8(tcr, struct RXMTU3ChanState), + VMSTATE_UINT8(tmdr1, struct RXMTU3ChanState), + VMSTATE_UINT8(tiorh, struct RXMTU3ChanState), + VMSTATE_UINT8(tiorl, struct RXMTU3ChanState), + VMSTATE_UINT8(tier, struct RXMTU3ChanState), + VMSTATE_UINT8(tsr, struct RXMTU3ChanState), + VMSTATE_UINT16(tcnt, struct RXMTU3ChanState), + VMSTATE_UINT16(tgra, struct RXMTU3ChanState), + VMSTATE_UINT16(tgrb, struct RXMTU3ChanState), + VMSTATE_UINT16(tgrc, struct RXMTU3ChanState), + VMSTATE_UINT16(tgrd, struct RXMTU3ChanState), + VMSTATE_INT64(tick, struct RXMTU3ChanState), + VMSTATE_INT64(div_round, struct RXMTU3ChanState), + VMSTATE_TIMER(timer, struct RXMTU3ChanState), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_mtu3 =3D { + .name =3D "renesas-mtu3", + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (const VMStateField[]) { + VMSTATE_UINT8(tstr, RXMTU3State), + VMSTATE_STRUCT_ARRAY(ch, RXMTU3State, MTU3_NR_CHAN, 1, + vmstate_mtu3_chan, struct RXMTU3ChanState), + VMSTATE_END_OF_LIST() + } +}; + +static const Property mtu3_properties[] =3D { + DEFINE_PROP_UINT64("input-freq", RXMTU3State, input_freq, 0), +}; + +static void mtu3_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(klass); + + dc->vmsd =3D &vmstate_mtu3; + device_class_set_legacy_reset(dc, mtu3_reset); + device_class_set_props(dc, mtu3_properties); +} + +static const TypeInfo mtu3_info =3D { + .name =3D TYPE_RENESAS_MTU3, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RXMTU3State), + .instance_init =3D mtu3_init, + .class_init =3D mtu3_class_init, +}; + +static void mtu3_register_types(void) +{ + type_register_static(&mtu3_info); +} + +type_init(mtu3_register_types) diff --git a/hw/watchdog/renesas_rx_wdt.c b/hw/watchdog/renesas_rx_wdt.c new file mode 100644 index 0000000000..f95374b139 --- /dev/null +++ b/hw/watchdog/renesas_rx_wdt.c @@ -0,0 +1,193 @@ +/* + * Renesas RX Watchdog Timers (WDT and IWDT) + * + * Datasheet: RX65N Group, RX651 Group User's Manual: Hardware + * (Rev.1.00 R01UH0590EJ0100), sections 28 (WDT) and 29 (IWDT) + * + * Copyright (c) 2026 Umar, Sayyad Mahammad + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * + * The watchdogs accept their refresh sequences (write 0x00 then 0xFF to t= he + * refresh register) and present a ready, non-underflowed status. QEMU mod= els no + * real timeout, so a missed refresh never resets the guest; this keeps fi= rmware + * that arms the watchdog during init running normally. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "hw/watchdog/renesas_rx_wdt.h" +#include "migration/vmstate.h" + +/* WDT register offsets from 0x00088020. */ +#define R_WDTRR 0x00 /* 8-bit refresh */ +#define R_WDTCR 0x02 /* 16-bit control */ +#define R_WDTSR 0x04 /* 16-bit status */ +#define R_WDTRCR 0x06 /* 8-bit reset control */ + +/* IWDT register offsets (block at 0x00088030, i.e. +0x10). */ +#define R_IWDTRR 0x10 /* 8-bit refresh */ +#define R_IWDTCR 0x12 /* 16-bit control */ +#define R_IWDTSR 0x14 /* 16-bit status */ +#define R_IWDTRCR 0x16 /* 8-bit reset control */ +#define R_IWDTCSTPR 0x18 /* 8-bit count-stop */ + +/* The counter value lives in the status register's low 14 bits. */ +#define WDT_COUNT_RELOAD 0x3fff + +static void wdt_refresh(uint8_t *phase, uint16_t *sr, uint64_t value) +{ + /* A 0x00 followed by 0xFF reloads the down-counter to its top value. = */ + if (*phase =3D=3D 0 && value =3D=3D 0x00) { + *phase =3D 1; + } else if (*phase =3D=3D 1 && value =3D=3D 0xff) { + *phase =3D 0; + *sr =3D (*sr & ~WDT_COUNT_RELOAD) | WDT_COUNT_RELOAD; + } else { + *phase =3D 0; + } +} + +static uint64_t wdt_read(void *opaque, hwaddr offset, unsigned size) +{ + RenesasRxWdtState *s =3D opaque; + + switch (offset) { + case R_WDTRR: + return 0xff; + case R_WDTCR: + return s->wdtcr; + case R_WDTSR: + return s->wdtsr; + case R_WDTRCR: + return s->wdtrcr; + case R_IWDTRR: + return 0xff; + case R_IWDTCR: + return s->iwdtcr; + case R_IWDTSR: + return s->iwdtsr; + case R_IWDTRCR: + return s->iwdtrcr; + case R_IWDTCSTPR: + return s->iwdtcstpr; + default: + qemu_log_mask(LOG_UNIMP, "renesas-rx-wdt: read unimplemented 0x%" + HWADDR_PRIx "\n", offset); + return 0; + } +} + +static void wdt_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + RenesasRxWdtState *s =3D opaque; + + switch (offset) { + case R_WDTRR: + wdt_refresh(&s->wdt_rr_phase, &s->wdtsr, value); + break; + case R_WDTCR: + s->wdtcr =3D value; + break; + case R_WDTSR: + s->wdtsr =3D value; + break; + case R_WDTRCR: + s->wdtrcr =3D value; + break; + case R_IWDTRR: + wdt_refresh(&s->iwdt_rr_phase, &s->iwdtsr, value); + break; + case R_IWDTCR: + s->iwdtcr =3D value; + break; + case R_IWDTSR: + s->iwdtsr =3D value; + break; + case R_IWDTRCR: + s->iwdtrcr =3D value; + break; + case R_IWDTCSTPR: + s->iwdtcstpr =3D value; + break; + default: + qemu_log_mask(LOG_UNIMP, "renesas-rx-wdt: write unimplemented 0x%" + HWADDR_PRIx "\n", offset); + break; + } +} + +static const MemoryRegionOps wdt_ops =3D { + .read =3D wdt_read, + .write =3D wdt_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .impl =3D { .min_access_size =3D 1, .max_access_size =3D 2 }, + .valid =3D { .min_access_size =3D 1, .max_access_size =3D 2 }, +}; + +static void rx_wdt_reset(DeviceState *dev) +{ + RenesasRxWdtState *s =3D RENESAS_RX_WDT(dev); + + s->wdtcr =3D 0x33f3; + s->wdtsr =3D 0; + s->wdtrcr =3D 0x80; + s->wdt_rr_phase =3D 0; + s->iwdtcr =3D 0; + s->iwdtsr =3D 0; + s->iwdtrcr =3D 0x80; + s->iwdtcstpr =3D 0; + s->iwdt_rr_phase =3D 0; +} + +static void rx_wdt_init(Object *obj) +{ + RenesasRxWdtState *s =3D RENESAS_RX_WDT(obj); + + memory_region_init_io(&s->mr, obj, &wdt_ops, s, + "renesas-rx-wdt", RX_WDT_REGS_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mr); +} + +static const VMStateDescription vmstate_rx_wdt =3D { + .name =3D "renesas-rx-wdt", + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (const VMStateField[]) { + VMSTATE_UINT16(wdtcr, RenesasRxWdtState), + VMSTATE_UINT16(wdtsr, RenesasRxWdtState), + VMSTATE_UINT8(wdtrcr, RenesasRxWdtState), + VMSTATE_UINT8(wdt_rr_phase, RenesasRxWdtState), + VMSTATE_UINT16(iwdtcr, RenesasRxWdtState), + VMSTATE_UINT16(iwdtsr, RenesasRxWdtState), + VMSTATE_UINT8(iwdtrcr, RenesasRxWdtState), + VMSTATE_UINT8(iwdtcstpr, RenesasRxWdtState), + VMSTATE_UINT8(iwdt_rr_phase, RenesasRxWdtState), + VMSTATE_END_OF_LIST() + } +}; + +static void rx_wdt_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(klass); + + dc->vmsd =3D &vmstate_rx_wdt; + device_class_set_legacy_reset(dc, rx_wdt_reset); +} + +static const TypeInfo rx_wdt_info =3D { + .name =3D TYPE_RENESAS_RX_WDT, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RenesasRxWdtState), + .instance_init =3D rx_wdt_init, + .class_init =3D rx_wdt_class_init, +}; + +static void rx_wdt_register_types(void) +{ + type_register_static(&rx_wdt_info); +} + +type_init(rx_wdt_register_types) diff --git a/include/hw/adc/renesas_s12ad.h b/include/hw/adc/renesas_s12ad.h new file mode 100644 index 0000000000..2f85e0660f --- /dev/null +++ b/include/hw/adc/renesas_s12ad.h @@ -0,0 +1,61 @@ +/* + * Renesas RX65N 12-bit Successive Approximation A/D Converter (S12AD) + * + * Datasheet: RX65N Group, RX651 Group User's Manual: Hardware + * (Rev.1.00 R01UH0590EJ0100), Section 40 + * + * Copyright (c) 2026 Umar, Sayyad Mahammad + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_ADC_RENESAS_S12AD_H +#define HW_ADC_RENESAS_S12AD_H + +#include "qemu/timer.h" +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_RENESAS_S12AD "renesas-s12ad" +typedef struct RX65NS12ADState RX65NS12ADState; +DECLARE_INSTANCE_CHECKER(RX65NS12ADState, RENESAS_S12AD, TYPE_RENESAS_S12A= D) + +#define S12AD_NR_CHANNELS 16 /* AN000=E2=80=93AN015 */ + +enum { + S12AD_IRQ_S12ADI =3D 0, /* end of scan (group A) */ + S12AD_IRQ_GBADI =3D 1, /* end of scan (group B) */ + S12AD_NR_IRQ =3D 2, +}; + +struct RX65NS12ADState { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + MemoryRegion memory; + uint64_t input_freq; + + /* Registers */ + uint16_t adcsr; /* A/D Control/Status */ + uint16_t adansa0; /* Channel select 0 (AN000=E2=80=93AN015) */ + uint16_t adansa1; /* Channel select 1 (AN016=E2=80=93AN020) */ + uint16_t adads0; /* Average enable 0 */ + uint16_t adads1; /* Average enable 1 */ + uint8_t adadc; /* Average count */ + uint16_t adcer; /* Control extended */ + uint16_t adstrgr; /* Start trigger select */ + uint16_t adexicr; /* Extended input */ + uint16_t adansb0; /* Group B channel select 0 */ + uint16_t adansb1; /* Group B channel select 1 */ + uint16_t addr[S12AD_NR_CHANNELS]; /* Result registers AN000=E2=80=93AN= 015 */ + uint16_t adrd; /* Temperature sensor / internal ref result */ + uint16_t adsam; /* Conversion time setting */ + uint8_t adsampr; /* ADSAM write protection */ + uint8_t ext_regs[0x80]; /* Extended configuration, offsets 0x70-0xef */ + + qemu_irq irq[S12AD_NR_IRQ]; + QEMUTimer conv_timer; /* models conversion delay */ +}; + +#endif /* HW_ADC_RENESAS_S12AD_H */ diff --git a/include/hw/dma/renesas_rx_dmac.h b/include/hw/dma/renesas_rx_d= mac.h new file mode 100644 index 0000000000..47242a31b6 --- /dev/null +++ b/include/hw/dma/renesas_rx_dmac.h @@ -0,0 +1,57 @@ +/* + * Renesas RX DMA Controller (DMAC) and Data Transfer Controller (DTC) + * + * Datasheet: RX65N Group, RX651 Group User's Manual: Hardware + * (Rev.1.00 R01UH0590EJ0100), sections 17 (DMAC) and 18 (DTC) + * + * Copyright (c) 2026 Umar, Sayyad Mahammad + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + */ + +#ifndef HW_DMA_RENESAS_RX_DMAC_H +#define HW_DMA_RENESAS_RX_DMAC_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_RENESAS_RX_DMAC "renesas-rx-dmac" +typedef struct RenesasRxDmacState RenesasRxDmacState; +DECLARE_INSTANCE_CHECKER(RenesasRxDmacState, RENESAS_RX_DMAC, + TYPE_RENESAS_RX_DMAC) + +#define RX_DMAC_NR_CH 8 +#define RX_DMAC_CH_SIZE 0x40 /* per-channel register spacing */ +#define RX_DMAC_REGS_SIZE 0x300 /* channels + common registers */ + +typedef struct RxDmacChannel { + uint32_t dmsar; /* source address */ + uint32_t dmdar; /* destination address */ + uint32_t dmcra; /* transfer count (A: lower, B: upper) */ + uint16_t dmcrb; /* block transfer count */ + uint16_t dmtmd; /* transfer mode */ + uint8_t dmint; /* interrupt setting */ + uint16_t dmamd; /* address mode */ + uint32_t dmofr; /* offset */ + uint8_t dmcnt; /* transfer enable */ + uint8_t dmreq; /* software/refresh request */ + uint8_t dmsts; /* status */ +} RxDmacChannel; + +struct RenesasRxDmacState { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + MemoryRegion mr; + AddressSpace *as; + MemoryRegion *dma_mr; + + RxDmacChannel ch[RX_DMAC_NR_CH]; + uint8_t dmast; /* module activation */ + + qemu_irq irq[RX_DMAC_NR_CH]; +}; + +#endif /* HW_DMA_RENESAS_RX_DMAC_H */ diff --git a/include/hw/dma/renesas_rx_dtc.h b/include/hw/dma/renesas_rx_dt= c.h new file mode 100644 index 0000000000..aa02bf5d66 --- /dev/null +++ b/include/hw/dma/renesas_rx_dtc.h @@ -0,0 +1,42 @@ +/* + * Renesas RX Data Transfer Controller (DTC) + * + * Datasheet: RX65N Group, RX651 Group User's Manual: Hardware + * (Rev.1.00 R01UH0590EJ0100), section 18 (DTC) + * + * Copyright (c) 2026 Umar, Sayyad Mahammad + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + */ + +#ifndef HW_DMA_RENESAS_RX_DTC_H +#define HW_DMA_RENESAS_RX_DTC_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_RENESAS_RX_DTC "renesas-rx-dtc" +typedef struct RenesasRxDtcState RenesasRxDtcState; +DECLARE_INSTANCE_CHECKER(RenesasRxDtcState, RENESAS_RX_DTC, TYPE_RENESAS_R= X_DTC) + +#define RX_DTC_REGS_SIZE 0x20 + +struct RenesasRxDtcState { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + MemoryRegion mr; + + uint8_t dtccr; /* control */ + uint32_t dtcvbr; /* vector base address */ + uint8_t dtcadmod; /* address mode */ + uint8_t dtcst; /* module start */ + uint16_t dtcsts; /* status */ + uint32_t dtcibr; /* index table base */ + uint8_t dtcor; /* operation */ + uint16_t dtcexbr; /* extended repeat-area base */ +}; + +#endif /* HW_DMA_RENESAS_RX_DTC_H */ diff --git a/include/hw/gpio/renesas_rx_gpio.h b/include/hw/gpio/renesas_rx= _gpio.h new file mode 100644 index 0000000000..24784d7d1a --- /dev/null +++ b/include/hw/gpio/renesas_rx_gpio.h @@ -0,0 +1,64 @@ +/* + * Renesas RX I/O Ports (GPIO) and Multi-Function Pin Controller (MPC) + * + * Datasheet: RX65N Group, RX651 Group User's Manual: Hardware + * (Rev.1.00 R01UH0590EJ0100), sections 20 (I/O Ports) and 22 (= MPC) + * + * Copyright (c) 2026 Umar, Sayyad Mahammad + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + */ + +#ifndef HW_GPIO_RENESAS_RX_GPIO_H +#define HW_GPIO_RENESAS_RX_GPIO_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_RENESAS_RX_GPIO "renesas-rx-gpio" +typedef struct RenesasRxGpioState RenesasRxGpioState; +DECLARE_INSTANCE_CHECKER(RenesasRxGpioState, RENESAS_RX_GPIO, + TYPE_RENESAS_RX_GPIO) + +/* Number of 8-bit ports modelled (PORT0..PORT1F register indices). */ +#define RX_GPIO_NR_PORTS 0x20 +#define RX_GPIO_NR_PINS (RX_GPIO_NR_PORTS * 8) + +/* MMIO regions. */ +enum { + RX_GPIO_MMIO_PORT =3D 0, /* I/O port register block (0x0008C000) */ + RX_GPIO_MMIO_MPC, /* MPC pin-function block (0x0008C100) */ + RX_GPIO_NR_MMIO, +}; + +#define RX_GPIO_PORT_SIZE 0x100 +#define RX_GPIO_MPC_SIZE 0x200 + +struct RenesasRxGpioState { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + MemoryRegion port_mr; + MemoryRegion mpc_mr; + + /* Per-port registers. */ + uint8_t pdr[RX_GPIO_NR_PORTS]; /* direction: 1 =3D output = */ + uint8_t podr[RX_GPIO_NR_PORTS]; /* output data */ + uint8_t pmr[RX_GPIO_NR_PORTS]; /* mode: 1 =3D peripheral = */ + uint8_t odr[RX_GPIO_NR_PORTS * 2]; /* open-drain control (2/port) */ + uint8_t pcr[RX_GPIO_NR_PORTS]; /* pull-up control */ + uint8_t dscr[RX_GPIO_NR_PORTS]; /* drive capacity */ + + /* External input level driven onto each port by board wiring. */ + uint8_t input[RX_GPIO_NR_PORTS]; + + /* MPC PFS storage and write-protect register. */ + uint8_t mpc[RX_GPIO_MPC_SIZE]; + + /* Output lines, one per pin (port * 8 + bit). */ + qemu_irq out[RX_GPIO_NR_PINS]; +}; + +#endif /* HW_GPIO_RENESAS_RX_GPIO_H */ diff --git a/include/hw/misc/renesas_rx_crc.h b/include/hw/misc/renesas_rx_= crc.h new file mode 100644 index 0000000000..1c41b6bd4b --- /dev/null +++ b/include/hw/misc/renesas_rx_crc.h @@ -0,0 +1,30 @@ +/* + * Renesas RX CRC Calculator (CRCA) + * + * Copyright (c) 2026 Umar, Sayyad Mahammad + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_RENESAS_RX_CRC_H +#define HW_MISC_RENESAS_RX_CRC_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_RENESAS_RX_CRC "renesas-rx-crc" +OBJECT_DECLARE_SIMPLE_TYPE(RenesasRxCrcState, RENESAS_RX_CRC) + +/* CRCCR, CRCDIR and CRCDOR occupy 0x8280, 0x8284 and 0x8288. */ +#define RX_CRC_SIZE 0x10 + +struct RenesasRxCrcState { + SysBusDevice parent_obj; + + MemoryRegion memory; + + uint8_t crccr; /* control: GPS[2:0], LMS, DORCLR */ + uint32_t crcdor; /* running result, also the initial value */ +}; + +#endif /* HW_MISC_RENESAS_RX_CRC_H */ diff --git a/include/hw/misc/renesas_rx_fcu.h b/include/hw/misc/renesas_rx_= fcu.h new file mode 100644 index 0000000000..5bc580f51e --- /dev/null +++ b/include/hw/misc/renesas_rx_fcu.h @@ -0,0 +1,191 @@ +/* + * Renesas RX Flash Control Unit (FCU) with FACI command interface + * + * Datasheet: RX65N Group, RX651 Group User's Manual: Hardware + * (Rev.1.00 R01UH0590EJ0100), section 6 (Flash Memory) + * + * Copyright (c) 2026 Umar, Sayyad Mahammad + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + */ + +#ifndef HW_MISC_RENESAS_RX_FCU_H +#define HW_MISC_RENESAS_RX_FCU_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_RENESAS_RX_FCU "renesas-rx-fcu" +#include "system/block-backend.h" + +typedef struct RenesasRxFcuState RenesasRxFcuState; +DECLARE_INSTANCE_CHECKER(RenesasRxFcuState, RENESAS_RX_FCU, TYPE_RENESAS_R= X_FCU) + +/* The FACI control register block (0x007FE000) covers the first 4 KiB. */ +#define RX_FCU_REGS_SIZE 0x1000 + +/* + * FACI command-issuing area: every command byte and data word is written + * here, and the destination inside the flash comes from FSADDR. + */ +#define RX_FCU_FACI_ISSUE_BASE 0x007E0000 +#define RX_FCU_FACI_ISSUE_SIZE 4 + +/* + * Option-setting memory (OFSM). It sits apart from the code flash array a= nd + * holds, among other things, the two registers that select the code flash + * bank layout. + */ +#define RX_FCU_OFSM_SIZE 0x80 +#define RX_OFSM_MDE 0x00 /* Endian Select Register (BANKMD) */ +#define RX_OFSM_BANKSEL 0x20 /* Bank Select Register (BANKSWP) */ + +/* + * MDE.BANKMD[2:0] picks the code flash layout: 111b is linear mode, 000b = is + * dual mode. A blank part reads 0xffffffff, hence linear. + */ +#define RX_BANKMD_MASK 0x7 +#define RX_BANKMD_DUAL 0x0 +#define RX_BANKMD_LINEAR 0x7 + +/* + * BANKSEL.BANKSWP[2:0] picks which bank is mapped where in dual mode. 111b + * puts bank 0 in the upper half, which is where the vectors live, so it is + * the layout a linearly programmed image already has; 000b swaps the two. + */ +#define RX_BANKSWP_MASK 0x7 +#define RX_BANKSWP_NORMAL 0x7 +#define RX_BANKSWP_SWAPPED 0x0 + +/* sysbus MMIO regions exposed by the device. */ +enum { + RX_FCU_MMIO_REGS =3D 0, /* FACI control/status registers */ + RX_FCU_MMIO_CFLASH, /* code flash array (rom_device) */ + RX_FCU_MMIO_DFLASH, /* data flash array (rom_device) */ + RX_FCU_MMIO_OFSM, /* option-setting memory */ + RX_FCU_MMIO_FACI, /* FACI command-issuing area */ + RX_FCU_NR_MMIO, +}; + +/* sysbus IRQ lines. */ +enum { + RX_FCU_IRQ_FRDYI =3D 0, /* flash ready interrupt */ + RX_FCU_IRQ_FIFERR, /* flash access error */ + RX_FCU_NR_IRQ, +}; + +/* Identifies which flash array a FACI command targets. */ +typedef enum { + RX_FCU_CFLASH =3D 0, + RX_FCU_DFLASH =3D 1, +} RxFcuTarget; + +/* + * Per-array context handed to the flash MemoryRegion ops so a single set = of + * callbacks can serve both the code and data flash arrays. + */ +typedef struct RxFcuFlash { + RenesasRxFcuState *fcu; + RxFcuTarget target; +} RxFcuFlash; + +/* FACI command sequencer state. */ +typedef enum { + RX_FCU_ST_READY =3D 0, + RX_FCU_ST_PROGRAM_COUNT, /* awaiting the data-word count */ + RX_FCU_ST_PROGRAM_DATA, /* receiving data words, then 0xD0 */ + RX_FCU_ST_ERASE, /* awaiting the 0xD0 confirmation */ + RX_FCU_ST_BLANKCHECK, /* awaiting the 0xD0 confirmation */ + RX_FCU_ST_CONFIG_COUNT, /* awaiting the config data-word count */ + RX_FCU_ST_CONFIG_DATA, /* receiving config words, then 0xD0 */ +} RxFcuCmdState; + +struct RenesasRxFcuState { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + MemoryRegion regs_mr; + MemoryRegion cflash_mr; /* backing store, reached through the alia= ses */ + MemoryRegion dflash_mr; + MemoryRegion ofsm_mr; + MemoryRegion faci_mr; + + /* + * The code flash is exposed through a container so the two dual-mode + * banks can be mapped in either order. In linear mode the container + * holds one alias spanning the whole array; in dual mode it holds one + * alias per half, placed according to BANKSWP. The 1.5 MiB parts have= a + * gap between the two bank windows. + */ + MemoryRegion cflash_container; + MemoryRegion cflash_linear; + MemoryRegion cflash_bank[2]; + + RxFcuFlash cflash_ctx; + RxFcuFlash dflash_ctx; + + qemu_irq frdyi; + qemu_irq fiferr; + + /* Geometry (set via properties by the MCU). */ + uint32_t cflash_size; + uint32_t dflash_size; + uint32_t cflash_base; /* absolute CPU address of code flash */ + uint32_t dflash_base; /* absolute CPU address of data flash */ + uint32_t dual_bank_gap_size; + + /* Backing storage pointers (into the rom_device RAM). */ + uint8_t *cflash_ptr; + uint8_t *dflash_ptr; + uint8_t *ofsm_ptr; + + /* Code flash bank layout latched at reset from the OFSM. */ + uint32_t cfg_off; /* OFSM offset for a configuration set */ + bool dual_mode; + bool bank_swapped; + + /* + * Optional block backends. When present the array is loaded from the + * image at realize and every program or erase is written back, so fla= sh + * contents survive across runs the way real flash does. + */ + BlockBackend *cflash_blk; + BlockBackend *dflash_blk; + BlockBackend *ofsm_blk; + bool cflash_ro; + bool dflash_ro; + bool ofsm_ro; + + /* FACI registers. */ + uint16_t fentryr; /* P/E mode entry (read-back form) */ + uint32_t fstatr; /* flash status */ + uint8_t fastat; /* flash access status */ + uint8_t frdyie; /* flash ready interrupt enable */ + uint8_t faeint; /* flash access error interrupt enable */ + uint32_t fsaddr; /* processing start address */ + uint32_t fpsaddr; /* programmed/erased start address */ + uint32_t feaddr; /* processing end address */ + uint8_t fbcstat; /* blank check status */ + uint16_t fbccnt; /* blank check control */ + uint16_t fcpsr; /* clear/processing switch */ + uint16_t fpckar; /* clock notification */ + uint16_t fprotr; /* protection */ + uint16_t fsuacr; /* startup area control */ + uint16_t fpestat; /* P/E error status */ + uint16_t fcmdr; /* last FACI command (read-only) */ + + /* + * Command sequencer (RxFcuCmdState / RxFcuTarget, stored as int32 for + * migration). + */ + int32_t cmd_state; + int32_t cmd_target; /* array of the in-progress command */ + uint32_t prog_off; /* next program offset within array */ + uint32_t prog_words; /* remaining 16-bit words to program */ +}; + +void rx_fcu_update_bank_map(RenesasRxFcuState *s); + +#endif /* HW_MISC_RENESAS_RX_FCU_H */ diff --git a/include/hw/misc/renesas_rx_romcache.h b/include/hw/misc/renesa= s_rx_romcache.h new file mode 100644 index 0000000000..2c3ddeb4bb --- /dev/null +++ b/include/hw/misc/renesas_rx_romcache.h @@ -0,0 +1,30 @@ +/* + * Renesas RX ROM cache control registers + * + * Copyright (c) 2026 Umar, Sayyad Mahammad + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_RENESAS_RX_ROMCACHE_H +#define HW_MISC_RENESAS_RX_ROMCACHE_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_RENESAS_RX_ROMCACHE "renesas-rx-romcache" +OBJECT_DECLARE_SIMPLE_TYPE(RenesasRxRomCacheState, RENESAS_RX_ROMCACHE) + +/* Also covers the adjacent system ROMWT register at +0x01c. */ +#define RX_ROMCACHE_SIZE 0x20 + +struct RenesasRxRomCacheState { + SysBusDevice parent_obj; + + MemoryRegion memory; + + uint16_t romce; /* ROM cache enable */ + uint8_t romwt; /* ROM wait cycles */ +}; + +#endif /* HW_MISC_RENESAS_RX_ROMCACHE_H */ diff --git a/include/hw/misc/rx65n_sysclk.h b/include/hw/misc/rx65n_sysclk.h new file mode 100644 index 0000000000..5733e9a126 --- /dev/null +++ b/include/hw/misc/rx65n_sysclk.h @@ -0,0 +1,37 @@ +/* + * RX65N Clock Generation Circuit (SYSTEM) registers + * + * Datasheet: RX65N Group, RX651 Group User's Manual: Hardware + * (Rev.1.00 R01UH0590EJ0100), section 9 + * + * Copyright (c) 2026 Umar, Sayyad Mahammad + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + */ + +#ifndef HW_MISC_RX65N_SYSCLK_H +#define HW_MISC_RX65N_SYSCLK_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_RX65N_SYSCLK "rx65n-sysclk" +typedef struct RX65NSysClkState RX65NSysClkState; +DECLARE_INSTANCE_CHECKER(RX65NSysClkState, RX65N_SYSCLK, TYPE_RX65N_SYSCLK) + +/* The clock-generation register block occupies 0x80000-0x80FFF. */ +#define RX65N_SYSCLK_SIZE 0x1000 + +struct RX65NSysClkState { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + MemoryRegion memory; + + /* Backing storage for the register block. */ + uint8_t regs[RX65N_SYSCLK_SIZE]; +}; + +#endif /* HW_MISC_RX65N_SYSCLK_H */ diff --git a/include/hw/net/renesas_etherc.h b/include/hw/net/renesas_ether= c.h new file mode 100644 index 0000000000..c35a7b3f6e --- /dev/null +++ b/include/hw/net/renesas_etherc.h @@ -0,0 +1,109 @@ +/* + * Renesas RX65N Ethernet Controller (ETHERC) + Ethernet DMA Controller (E= DMAC) + * + * Datasheet: RX65N Group, RX651 Group User's Manual: Hardware + * (Rev.1.00 R01UH0590EJ0100), Sections 32=E2=80=9333 + * + * Copyright (c) 2026 Umar, Sayyad Mahammad + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_NET_RENESAS_ETHERC_H +#define HW_NET_RENESAS_ETHERC_H + +#include "hw/core/sysbus.h" +#include "net/net.h" +#include "qom/object.h" + +#define TYPE_RENESAS_ETHERC "renesas-etherc" +typedef struct RX65NEthercState RX65NEthercState; +DECLARE_INSTANCE_CHECKER(RX65NEthercState, RENESAS_ETHERC, TYPE_RENESAS_ET= HERC) + +/* MMIO covers ETHERC (0x000=E2=80=930x1FF) + EDMAC (0x200=E2=80=930x3FF) = */ +#define ETHERC_MMIO_SIZE 0x200 + +enum { + ETHERC_IRQ_EINT =3D 0, /* EDMAC interrupt (vector 32) */ + ETHERC_NR_IRQ =3D 1, +}; + +/* MDIO bit-bang state machine */ +typedef struct { + int preamble; /* consecutive MDO=3D1 count */ + int in_cnt; /* master bits received (-1 =3D idle) */ + uint32_t sr; /* shift register for header bits */ + bool is_read; /* current transaction is a read */ + int write_cnt; /* write data/TA bits accumulated */ + int write_paddr; + int write_raddr; + uint16_t write_data; + uint16_t read_data; /* latched PHY register value */ + int out_cnt; /* slave output bit counter (-1 =3D idle) */ + bool mdi; /* current MDI output level */ + uint16_t phy_regs[32]; /* shared register bank for PHY addresses 0 an= d 1 */ +} MDIOState; + +struct RX65NEthercState { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + MemoryRegion memory; + NICState *nic; + NICConf conf; + + qemu_irq irq[ETHERC_NR_IRQ]; + + /* ETHERC registers (offsets 0x000=E2=80=930x0FF) */ + uint32_t ecmr; + uint32_t rflr; + uint32_t ecsr; + uint32_t ecsipr; + uint32_t pir; + uint32_t rdmlr; + uint32_t ipgr; + uint32_t apr; + uint32_t mpr; + uint32_t tpauser; + uint32_t bcfrr; + uint32_t mahr; + uint32_t malr; + uint32_t trocr; + uint32_t cdcr; + uint32_t lccr; + uint32_t cndcr; + uint32_t cefcr; + uint32_t frecr; + uint32_t tsfrcr; + uint32_t tlfrcr; + uint32_t rfcr; + uint32_t mafcr; + + /* EDMAC registers (offsets 0x200=E2=80=930x2FF) */ + uint32_t edmr; + uint32_t edtrr; + uint32_t edrrr; + uint32_t tdlar; + uint32_t rdlar; + uint32_t eesr; + uint32_t eesipr; + uint32_t trscer; + uint32_t rmfcr; + uint32_t tftr; + uint32_t fdr; + uint32_t rmcr; + uint32_t tfucr; + uint32_t rfocr; + uint32_t fcftr; + uint32_t rpadir; + uint32_t trimd; + + /* DMA ring state */ + uint32_t tx_cur; + uint32_t rx_cur; + + MDIOState mdio; +}; + +#endif /* HW_NET_RENESAS_ETHERC_H */ diff --git a/include/hw/rtc/renesas_rx_rtc.h b/include/hw/rtc/renesas_rx_rt= c.h new file mode 100644 index 0000000000..762d8cea51 --- /dev/null +++ b/include/hw/rtc/renesas_rx_rtc.h @@ -0,0 +1,52 @@ +/* + * Renesas RX Realtime Clock (RTC) + * + * Datasheet: RX65N Group, RX651 Group User's Manual: Hardware + * (Rev.1.00 R01UH0590EJ0100), section 26 (RTC) + * + * Copyright (c) 2026 Umar, Sayyad Mahammad + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + */ + +#ifndef HW_RTC_RENESAS_RX_RTC_H +#define HW_RTC_RENESAS_RX_RTC_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_RENESAS_RX_RTC "renesas-rx-rtc" +typedef struct RenesasRxRtcState RenesasRxRtcState; +DECLARE_INSTANCE_CHECKER(RenesasRxRtcState, RENESAS_RX_RTC, TYPE_RENESAS_R= X_RTC) + +#define RX_RTC_REGS_SIZE 0x40 + +struct RenesasRxRtcState { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + MemoryRegion mr; + QEMUTimer *timer; + + /* Current time held as binary fields (converted to/from BCD on access= ). */ + int sec; + int min; + int hour; + int wday; /* 0 =3D Sunday */ + int mday; /* 1..31 */ + int mon; /* 1..12 */ + int year; /* full year, e.g. 2024 */ + + /* Alarm match registers (raw BCD as written). */ + uint8_t secar, minar, hrar, wkar, dayar, monar; + uint16_t yrar; + + uint8_t rcr1; + uint8_t rcr2; + uint8_t rcr3; + uint8_t rcr4; +}; + +#endif /* HW_RTC_RENESAS_RX_RTC_H */ diff --git a/include/hw/ssi/renesas_rspi.h b/include/hw/ssi/renesas_rspi.h new file mode 100644 index 0000000000..b6f5bf8b0b --- /dev/null +++ b/include/hw/ssi/renesas_rspi.h @@ -0,0 +1,61 @@ +/* + * Renesas RX65N Serial Peripheral Interface (RSPI) + * + * Datasheet: RX65N Group, RX651 Group User's Manual: Hardware + * (Rev.1.00 R01UH0590EJ0100), Section 30 + * + * Copyright (c) 2026 Umar, Sayyad Mahammad + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_SSI_RENESAS_RSPI_H +#define HW_SSI_RENESAS_RSPI_H + +#include "hw/core/sysbus.h" +#include "hw/ssi/ssi.h" +#include "qom/object.h" + +#define TYPE_RENESAS_RSPI "renesas-rspi" +typedef struct RX65NRSPIState RX65NRSPIState; +DECLARE_INSTANCE_CHECKER(RX65NRSPIState, RENESAS_RSPI, TYPE_RENESAS_RSPI) + +enum { + RSPI_IRQ_SPEI =3D 0, /* error */ + RSPI_IRQ_SPRI =3D 1, /* receive buffer full */ + RSPI_IRQ_SPTI =3D 2, /* transmit buffer empty */ + RSPI_IRQ_SPII =3D 3, /* idle */ + RSPI_NR_IRQ =3D 4, +}; + +struct RX65NRSPIState { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + MemoryRegion memory; + uint64_t input_freq; + SSIBus *ssi; + + /* Registers */ + uint8_t spcr; /* Control */ + uint8_t sslp; /* SSL polarity */ + uint8_t sppcr; /* Pin control */ + uint8_t spsr; /* Status */ + /* Data is double-buffered in hardware; this model uses one buffer. */ + uint32_t spdr; + uint8_t spscr; /* Sequence control */ + uint8_t spssr; /* Sequence status */ + uint8_t spbr; /* Bit rate */ + uint8_t spdcr; /* Data control */ + uint8_t spckd; /* Clock delay */ + uint8_t sslnd; /* SSL negate delay */ + uint8_t spnd; /* Next-access delay */ + uint8_t spcr2; /* Control 2 */ + uint16_t spcmd[8]; /* Command registers 0=E2=80=937 */ + uint8_t spdcr2; /* Data control 2 */ + + qemu_irq irq[RSPI_NR_IRQ]; +}; + +#endif /* HW_SSI_RENESAS_RSPI_H */ diff --git a/include/hw/timer/renesas_mtu3.h b/include/hw/timer/renesas_mtu= 3.h new file mode 100644 index 0000000000..8d1b82aec5 --- /dev/null +++ b/include/hw/timer/renesas_mtu3.h @@ -0,0 +1,73 @@ +/* + * Renesas RX65N Multi-Function Timer Pulse Unit 3 (MTU3) + * + * Datasheet: RX65N Group, RX651 Group User's Manual: Hardware + * (Rev.1.00 R01UH0590EJ0100), Section 17 + * + * Copyright (c) 2026 Umar, Sayyad Mahammad + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_TIMER_RENESAS_MTU3_H +#define HW_TIMER_RENESAS_MTU3_H + +#include "qemu/timer.h" +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_RENESAS_MTU3 "renesas-mtu3" +typedef struct RXMTU3State RXMTU3State; +DECLARE_INSTANCE_CHECKER(RXMTU3State, RXMTU3, TYPE_RENESAS_MTU3) + +/* + * This device models MTU3 channels 3 and 4 of the RX65N MTU3 module, + * which share base address 0x000C1200 with an interleaved register layout. + * ch[0] =3D MTU channel 3, ch[1] =3D MTU channel 4. + */ +enum { + MTU3_NR_CHAN =3D 2, + MTU3_CH_NR_IRQ =3D 5, /* TGIA, TGIB, TGIC, TGID, TCIV per channel */ + MTU3_NR_IRQ =3D MTU3_CH_NR_IRQ * MTU3_NR_CHAN /* =3D 10 */ +}; + +/* IRQ index within the per-channel group */ +enum { + MTU3_IRQ_TGIA =3D 0, + MTU3_IRQ_TGIB =3D 1, + MTU3_IRQ_TGIC =3D 2, + MTU3_IRQ_TGID =3D 3, + MTU3_IRQ_TCIV =3D 4, +}; + +struct RXMTU3ChanState { + uint8_t tcr; + uint8_t tmdr1; + uint8_t tiorh; + uint8_t tiorl; + uint8_t tier; + uint8_t tsr; + uint16_t tcnt; + uint16_t tgra; + uint16_t tgrb; + uint16_t tgrc; + uint16_t tgrd; + int64_t tick; /* virtual-clock time when tcnt was last latched = */ + int64_t div_round; /* fractional cycles accumulator */ + QEMUTimer timer; +}; + +struct RXMTU3State { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + MemoryRegion memory; + uint64_t input_freq; + + uint8_t tstr; /* Timer Start Register A (offset 0x80) */ + struct RXMTU3ChanState ch[MTU3_NR_CHAN]; + qemu_irq irq[MTU3_NR_IRQ]; +}; + +#endif /* HW_TIMER_RENESAS_MTU3_H */ diff --git a/include/hw/watchdog/renesas_rx_wdt.h b/include/hw/watchdog/ren= esas_rx_wdt.h new file mode 100644 index 0000000000..d232f13582 --- /dev/null +++ b/include/hw/watchdog/renesas_rx_wdt.h @@ -0,0 +1,48 @@ +/* + * Renesas RX Watchdog Timers (WDT and IWDT) + * + * Datasheet: RX65N Group, RX651 Group User's Manual: Hardware + * (Rev.1.00 R01UH0590EJ0100), sections 28 (WDT) and 29 (IWDT) + * + * Copyright (c) 2026 Umar, Sayyad Mahammad + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + */ + +#ifndef HW_WATCHDOG_RENESAS_RX_WDT_H +#define HW_WATCHDOG_RENESAS_RX_WDT_H + +#include "hw/core/boards.h" +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_RENESAS_RX_WDT "renesas-rx-wdt" +typedef struct RenesasRxWdtState RenesasRxWdtState; +DECLARE_INSTANCE_CHECKER(RenesasRxWdtState, RENESAS_RX_WDT, TYPE_RENESAS_R= X_WDT) + +/* One block covering both WDT (0x88020) and IWDT (0x88030). */ +#define RX_WDT_REGS_SIZE 0x20 + +struct RenesasRxWdtState { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + MemoryRegion mr; + + /* WDT registers. */ + uint16_t wdtcr; + uint16_t wdtsr; + uint8_t wdtrcr; + uint8_t wdt_rr_phase; /* tracks the 0x00 -> 0xFF refresh sequence */ + + /* IWDT registers. */ + uint16_t iwdtcr; + uint16_t iwdtsr; + uint8_t iwdtrcr; + uint8_t iwdtcstpr; + uint8_t iwdt_rr_phase; +}; + +#endif /* HW_WATCHDOG_RENESAS_RX_WDT_H */ --=20 2.53.0