From nobody Mon Feb 9 20:10:23 2026 Delivered-To: importer@patchew.org Received-SPF: pass (zoho.com: domain of gnu.org designates 208.118.235.17 as permitted sender) client-ip=208.118.235.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zoho.com: domain of gnu.org designates 208.118.235.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org Return-Path: Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) by mx.zohomail.com with SMTPS id 1524701227101663.4159837403781; Wed, 25 Apr 2018 17:07:07 -0700 (PDT) Received: from localhost ([::1]:39653 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1fBURG-00048y-3S for importer@patchew.org; Wed, 25 Apr 2018 20:07:06 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:46841) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1fBU9g-0005p4-Pv for qemu-devel@nongnu.org; Wed, 25 Apr 2018 19:48:58 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1fBU9f-0005XT-1I for qemu-devel@nongnu.org; Wed, 25 Apr 2018 19:48:56 -0400 Received: from mail-pg0-x244.google.com ([2607:f8b0:400e:c05::244]:39880) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1fBU9e-0005X0-Om for qemu-devel@nongnu.org; Wed, 25 Apr 2018 19:48:54 -0400 Received: by mail-pg0-x244.google.com with SMTP id b9so14459639pgf.6 for ; Wed, 25 Apr 2018 16:48:54 -0700 (PDT) Received: from localhost.localdomain (122-58-167-38-fibre.bb.spark.co.nz. [122.58.167.38]) by smtp.gmail.com with ESMTPSA id e10sm29577549pfn.67.2018.04.25.16.48.50 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Wed, 25 Apr 2018 16:48:53 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sifive.com; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=867GFDAb8/me15iTegLfvf4TRg6qkh/zsi6aRnZ5Yis=; b=hscGp8t0zA3Rj62tx21VUkxCW1zwbn6KluipghS+7imdw4fCZNxN4zHF52J+RMDpV7 Lj67ffXk9wO16DCmTt55jXSDMegCtcXm78n3dMGTSJe6UHqgdICJ97/4nsEodsJVwRl4 7+jW+PpQZlVHC7UwrkKmeqHUuKskubd318PRufKEzKo1oc6WcRRW3GhCf3HmRX1gQFAc VYwDONw8MdqXy3vSxEecHqm4MmzAnedCYwiSzdCbLQSv9j1cBxVauFcFIB/YkSBboYeV bqj2Vz+3vtPCuPLUoKh3bTgPPyFq6uGkKQ1Q40YcsKcz5jMvCHyAh5Sraplplpq2tHww IHig== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=867GFDAb8/me15iTegLfvf4TRg6qkh/zsi6aRnZ5Yis=; b=t/R4X7Y0olzOet7ZdKAsXnziGW3+1utS3aT3s1icFkR7ol8kcyH8fBZ9fLNfO57u6p 9t69In7tiZFvbVHULJLDI5bbhRuRtWT5SdAH0cENN9rc8JZQuGXPhSvST/XRDUV9ompF f8WHcSvo3QDgvxesW6cXTnDGRZWqMgbOYUMBm1zBocXa4SZwchz6AHjvY/pLiol2+GPA ougURt+png1c9OVTm6w9G1CjEOHq2ijw1bQz0GVnh0AND6BcDENm+bR7HpoApNlGmk3m MxD+teexGW2bjU0/vvY6WGgZZn5yWQ3R49Oepr/uGT4cN6xiRI5ImARlNM/uCP1ZOcEQ RpXg== X-Gm-Message-State: ALQs6tCSEoGRyhJhlBwZZO3qsARNUGq2w3pgB14USYmsHL4TwpKFD1XI AX+4wiyQl0iIzXvh4HaqCFKgfxQ1WTs= X-Google-Smtp-Source: AB8JxZrq0fC23X7Q/GLI5PmAtXWSeLvHsuSVX4E5ids2uSJVGI4MWSRPKj+XD6gO/rwi9HD2EOuuXw== X-Received: by 2002:a17:902:74c6:: with SMTP id f6-v6mr7952584plt.7.1524700133718; Wed, 25 Apr 2018 16:48:53 -0700 (PDT) From: Michael Clark To: qemu-devel@nongnu.org Date: Thu, 26 Apr 2018 11:45:32 +1200 Message-Id: <1524699938-6764-30-git-send-email-mjc@sifive.com> X-Mailer: git-send-email 2.7.0 In-Reply-To: <1524699938-6764-1-git-send-email-mjc@sifive.com> References: <1524699938-6764-1-git-send-email-mjc@sifive.com> X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 2607:f8b0:400e:c05::244 Subject: [Qemu-devel] [PATCH v8 29/35] RISC-V: Implement existential predicates for CSRs X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Sagar Karandikar , Bastian Koppelmann , Palmer Dabbelt , Michael Clark , Alistair Francis , patches@groups.riscv.org Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail-DKIM: fail (Header signature does not verify) X-ZohoMail: RDKM_2 RSF_0 Z_629925259 SPT_0 Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" CSR predicate functions are added to the CSR table. mstatus.FS and counter enable checks are moved to predicate functions and two new predicates are added to check misa.S for s* CSRs and a new PMP CPU feature for pmp* CSRs. Processors that don't implement S-mode will trap on access to s* CSRs and processors that don't implement PMP will trap on accesses to pmp* CSRs. PMP checks are disabled in riscv_cpu_handle_mmu_fault when the PMP CPU feature is not present. Cc: Sagar Karandikar Cc: Bastian Koppelmann Cc: Palmer Dabbelt Cc: Alistair Francis Signed-off-by: Michael Clark Acked-by: Alistair Francis --- target/riscv/cpu.c | 6 ++ target/riscv/cpu.h | 5 +- target/riscv/cpu_helper.c | 3 +- target/riscv/csr.c | 172 ++++++++++++++++++++++++++----------------= ---- 4 files changed, 107 insertions(+), 79 deletions(-) diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index 4e5a56d..26741d0 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -124,6 +124,7 @@ static void rv32gcsu_priv1_09_1_cpu_init(Object *obj) set_versions(env, USER_VERSION_2_02_0, PRIV_VERSION_1_09_1); set_resetvec(env, DEFAULT_RSTVEC); set_feature(env, RISCV_FEATURE_MMU); + set_feature(env, RISCV_FEATURE_PMP); } =20 static void rv32gcsu_priv1_10_0_cpu_init(Object *obj) @@ -133,6 +134,7 @@ static void rv32gcsu_priv1_10_0_cpu_init(Object *obj) set_versions(env, USER_VERSION_2_02_0, PRIV_VERSION_1_10_0); set_resetvec(env, DEFAULT_RSTVEC); set_feature(env, RISCV_FEATURE_MMU); + set_feature(env, RISCV_FEATURE_PMP); } =20 static void rv32imacu_nommu_cpu_init(Object *obj) @@ -141,6 +143,7 @@ static void rv32imacu_nommu_cpu_init(Object *obj) set_misa(env, RV32 | RVI | RVM | RVA | RVC | RVU); set_versions(env, USER_VERSION_2_02_0, PRIV_VERSION_1_10_0); set_resetvec(env, DEFAULT_RSTVEC); + set_feature(env, RISCV_FEATURE_PMP); } =20 #elif defined(TARGET_RISCV64) @@ -152,6 +155,7 @@ static void rv64gcsu_priv1_09_1_cpu_init(Object *obj) set_versions(env, USER_VERSION_2_02_0, PRIV_VERSION_1_09_1); set_resetvec(env, DEFAULT_RSTVEC); set_feature(env, RISCV_FEATURE_MMU); + set_feature(env, RISCV_FEATURE_PMP); } =20 static void rv64gcsu_priv1_10_0_cpu_init(Object *obj) @@ -161,6 +165,7 @@ static void rv64gcsu_priv1_10_0_cpu_init(Object *obj) set_versions(env, USER_VERSION_2_02_0, PRIV_VERSION_1_10_0); set_resetvec(env, DEFAULT_RSTVEC); set_feature(env, RISCV_FEATURE_MMU); + set_feature(env, RISCV_FEATURE_PMP); } =20 static void rv64imacu_nommu_cpu_init(Object *obj) @@ -169,6 +174,7 @@ static void rv64imacu_nommu_cpu_init(Object *obj) set_misa(env, RV64 | RVI | RVM | RVA | RVC | RVU); set_versions(env, USER_VERSION_2_02_0, PRIV_VERSION_1_10_0); set_resetvec(env, DEFAULT_RSTVEC); + set_feature(env, RISCV_FEATURE_PMP); } =20 #endif diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h index dc79f7c..3fed92d 100644 --- a/target/riscv/cpu.h +++ b/target/riscv/cpu.h @@ -83,9 +83,10 @@ /* S extension denotes that Supervisor mode exists, however it is possible to have a core that support S mode but does not have an MMU and there is currently no bit in misa to indicate whether an MMU exists or not - so a cpu features bitfield is required */ + so a cpu features bitfield is required, likewise for optional PMP suppo= rt */ enum { - RISCV_FEATURE_MMU + RISCV_FEATURE_MMU, + RISCV_FEATURE_PMP }; =20 #define USER_VERSION_2_02_0 0x00020200 diff --git a/target/riscv/cpu_helper.c b/target/riscv/cpu_helper.c index 2937da0..5d33f7b 100644 --- a/target/riscv/cpu_helper.c +++ b/target/riscv/cpu_helper.c @@ -403,7 +403,8 @@ int riscv_cpu_handle_mmu_fault(CPUState *cs, vaddr addr= ess, int size, qemu_log_mask(CPU_LOG_MMU, "%s address=3D%" VADDR_PRIx " ret %d physical " TARGET_FMT_plx " prot %d\n", __func__, address, ret, pa, prot); - if (!pmp_hart_has_privs(env, pa, TARGET_PAGE_SIZE, 1 << rw)) { + if (riscv_feature(env, RISCV_FEATURE_PMP) && + !pmp_hart_has_privs(env, pa, TARGET_PAGE_SIZE, 1 << rw)) { ret =3D TRANSLATE_FAIL; } if (ret =3D=3D TRANSLATE_SUCCESS) { diff --git a/target/riscv/csr.c b/target/riscv/csr.c index c704545..ecf74a0 100644 --- a/target/riscv/csr.c +++ b/target/riscv/csr.c @@ -26,6 +26,7 @@ =20 /* Control and Status Register function table forward declaration */ =20 +typedef int (*riscv_csr_predicate_fn)(CPURISCVState *env, int csrno); typedef int (*riscv_csr_read_fn)(CPURISCVState *env, int csrno, target_ulong *ret_value); typedef int (*riscv_csr_write_fn)(CPURISCVState *env, int csrno, @@ -34,6 +35,7 @@ typedef int (*riscv_csr_op_fn)(CPURISCVState *env, int cs= rno, target_ulong *ret_value, target_ulong new_value, target_ulong write_ma= sk); =20 typedef struct { + riscv_csr_predicate_fn predicate; riscv_csr_read_fn read; riscv_csr_write_fn write; riscv_csr_op_fn op; @@ -42,6 +44,47 @@ typedef struct { static const riscv_csr_operations csr_ops[]; =20 =20 +/* Predicates */ + +static int fs(CPURISCVState *env, int csrno) +{ +#if !defined(CONFIG_USER_ONLY) + if (!(env->mstatus & MSTATUS_FS)) { + return -1; + } +#endif + return 0; +} + +static int ctr(CPURISCVState *env, int csrno) +{ +#if !defined(CONFIG_USER_ONLY) + target_ulong ctr_en =3D env->priv =3D=3D PRV_U ? env->scounteren : + env->priv =3D=3D PRV_S ? env->mcounteren : -1U; + if (!(ctr_en & (1 << (csrno & 31)))) { + return -1; + } +#endif + return 0; +} + +#if !defined(CONFIG_USER_ONLY) +static int any(CPURISCVState *env, int csrno) +{ + return 0; +} + +static int smode(CPURISCVState *env, int csrno) +{ + return -!riscv_has_ext(env, RVS); +} + +static int pmp(CPURISCVState *env, int csrno) +{ + return -!riscv_feature(env, RISCV_FEATURE_PMP); +} +#endif + /* User Floating-Point CSRs */ =20 static int read_fflags(CPURISCVState *env, int csrno, target_ulong *val) @@ -117,33 +160,8 @@ static int write_fcsr(CPURISCVState *env, int csrno, t= arget_ulong val) =20 /* User Timers and Counters */ =20 -static int counter_enabled(CPURISCVState *env, int csrno) -{ -#ifndef CONFIG_USER_ONLY - target_ulong ctr_en =3D env->priv =3D=3D PRV_U ? env->scounteren : - env->priv =3D=3D PRV_S ? env->mcounteren : -1U; -#else - target_ulong ctr_en =3D -1; -#endif - return (ctr_en >> (csrno & 31)) & 1; -} - -#if !defined(CONFIG_USER_ONLY) -static int read_zero_counter(CPURISCVState *env, int csrno, target_ulong *= val) -{ - if (!counter_enabled(env, csrno)) { - return -1; - } - *val =3D 0; - return 0; -} -#endif - static int read_instret(CPURISCVState *env, int csrno, target_ulong *val) { - if (!counter_enabled(env, csrno)) { - return -1; - } #if !defined(CONFIG_USER_ONLY) if (use_icount) { *val =3D cpu_get_icount(); @@ -159,9 +177,6 @@ static int read_instret(CPURISCVState *env, int csrno, = target_ulong *val) #if defined(TARGET_RISCV32) static int read_instreth(CPURISCVState *env, int csrno, target_ulong *val) { - if (!counter_enabled(env, csrno)) { - return -1; - } #if !defined(CONFIG_USER_ONLY) if (use_icount) { *val =3D cpu_get_icount() >> 32; @@ -726,6 +741,11 @@ int riscv_csrrw(CPURISCVState *env, int csrno, target_= ulong *ret_value, } #endif =20 + /* check predicate */ + if (!csr_ops[csrno].predicate || csr_ops[csrno].predicate(env, csrno) = < 0) { + return -1; + } + /* execute combined read/write operation if it exists */ if (csr_ops[csrno].op) { return csr_ops[csrno].op(env, csrno, ret_value, new_value, write_m= ask); @@ -765,89 +785,89 @@ int riscv_csrrw(CPURISCVState *env, int csrno, target= _ulong *ret_value, =20 static const riscv_csr_operations csr_ops[0xfff] =3D { /* User Floating-Point CSRs */ - [CSR_FFLAGS] =3D { read_fflags, write_fflags }, - [CSR_FRM] =3D { read_frm, write_frm }, - [CSR_FCSR] =3D { read_fcsr, write_fcsr }, + [CSR_FFLAGS] =3D { fs, read_fflags, write_fflags = }, + [CSR_FRM] =3D { fs, read_frm, write_frm = }, + [CSR_FCSR] =3D { fs, read_fcsr, write_fcsr = }, =20 /* User Timers and Counters */ - [CSR_CYCLE] =3D { read_instret }, - [CSR_INSTRET] =3D { read_instret }, + [CSR_CYCLE] =3D { ctr, read_instret = }, + [CSR_INSTRET] =3D { ctr, read_instret = }, #if defined(TARGET_RISCV32) - [CSR_CYCLEH] =3D { read_instreth }, - [CSR_INSTRETH] =3D { read_instreth }, + [CSR_CYCLEH] =3D { ctr, read_instreth = }, + [CSR_INSTRETH] =3D { ctr, read_instreth = }, #endif =20 /* User-level time CSRs are only available in linux-user * In privileged mode, the monitor emulates these CSRs */ #if defined(CONFIG_USER_ONLY) - [CSR_TIME] =3D { read_time }, + [CSR_TIME] =3D { ctr, read_time = }, #if defined(TARGET_RISCV32) - [CSR_TIMEH] =3D { read_timeh }, + [CSR_TIMEH] =3D { ctr, read_timeh = }, #endif #endif =20 #if !defined(CONFIG_USER_ONLY) /* Machine Timers and Counters */ - [CSR_MCYCLE] =3D { read_instret }, - [CSR_MINSTRET] =3D { read_instret }, + [CSR_MCYCLE] =3D { any, read_instret = }, + [CSR_MINSTRET] =3D { any, read_instret = }, #if defined(TARGET_RISCV32) - [CSR_MCYCLEH] =3D { read_instreth }, - [CSR_MINSTRETH] =3D { read_instreth }, + [CSR_MCYCLEH] =3D { any, read_instreth = }, + [CSR_MINSTRETH] =3D { any, read_instreth = }, #endif =20 /* Machine Information Registers */ - [CSR_MVENDORID] =3D { read_zero }, - [CSR_MARCHID] =3D { read_zero }, - [CSR_MIMPID] =3D { read_zero }, - [CSR_MHARTID] =3D { read_mhartid }, + [CSR_MVENDORID] =3D { any, read_zero = }, + [CSR_MARCHID] =3D { any, read_zero = }, + [CSR_MIMPID] =3D { any, read_zero = }, + [CSR_MHARTID] =3D { any, read_mhartid = }, =20 /* Machine Trap Setup */ - [CSR_MSTATUS] =3D { read_mstatus, write_mstatus }, - [CSR_MISA] =3D { read_misa }, - [CSR_MIDELEG] =3D { read_mideleg, write_mideleg }, - [CSR_MEDELEG] =3D { read_medeleg, write_medeleg }, - [CSR_MIE] =3D { read_mie, write_mie }, - [CSR_MTVEC] =3D { read_mtvec, write_mtvec }, - [CSR_MCOUNTEREN] =3D { read_mcounteren, write_mcounteren }, + [CSR_MSTATUS] =3D { any, read_mstatus, write_mstatus = }, + [CSR_MISA] =3D { any, read_misa = }, + [CSR_MIDELEG] =3D { any, read_mideleg, write_mideleg = }, + [CSR_MEDELEG] =3D { any, read_medeleg, write_medeleg = }, + [CSR_MIE] =3D { any, read_mie, write_mie = }, + [CSR_MTVEC] =3D { any, read_mtvec, write_mtvec = }, + [CSR_MCOUNTEREN] =3D { any, read_mcounteren, write_mcounter= en }, =20 /* Legacy Counter Setup (priv v1.9.1) */ - [CSR_MUCOUNTEREN] =3D { read_mucounteren, write_mucounteren }, - [CSR_MSCOUNTEREN] =3D { read_mscounteren, write_mscounteren }, + [CSR_MUCOUNTEREN] =3D { any, read_mucounteren, write_mucounte= ren }, + [CSR_MSCOUNTEREN] =3D { any, read_mscounteren, write_mscounte= ren }, =20 /* Machine Trap Handling */ - [CSR_MSCRATCH] =3D { read_mscratch, write_mscratch }, - [CSR_MEPC] =3D { read_mepc, write_mepc }, - [CSR_MCAUSE] =3D { read_mcause, write_mcause }, - [CSR_MBADADDR] =3D { read_mbadaddr, write_mbadaddr }, - [CSR_MIP] =3D { NULL, NULL, rmw_mip }, + [CSR_MSCRATCH] =3D { any, read_mscratch, write_mscratch= }, + [CSR_MEPC] =3D { any, read_mepc, write_mepc = }, + [CSR_MCAUSE] =3D { any, read_mcause, write_mcause = }, + [CSR_MBADADDR] =3D { any, read_mbadaddr, write_mbadaddr= }, + [CSR_MIP] =3D { any, NULL, NULL, rmw_mip = }, =20 /* Supervisor Trap Setup */ - [CSR_SSTATUS] =3D { read_sstatus, write_sstatus }, - [CSR_SIE] =3D { read_sie, write_sie }, - [CSR_STVEC] =3D { read_stvec, write_stvec }, - [CSR_SCOUNTEREN] =3D { read_scounteren, write_scounteren }, + [CSR_SSTATUS] =3D { smode, read_sstatus, write_sstatus= }, + [CSR_SIE] =3D { smode, read_sie, write_sie = }, + [CSR_STVEC] =3D { smode, read_stvec, write_stvec = }, + [CSR_SCOUNTEREN] =3D { smode, read_scounteren, write_scounte= ren }, =20 /* Supervisor Trap Handling */ - [CSR_SSCRATCH] =3D { read_sscratch, write_sscratch }, - [CSR_SEPC] =3D { read_sepc, write_sepc }, - [CSR_SCAUSE] =3D { read_scause, write_scause }, - [CSR_SBADADDR] =3D { read_sbadaddr, write_sbadaddr }, - [CSR_SIP] =3D { NULL, NULL, rmw_sip }, + [CSR_SSCRATCH] =3D { smode, read_sscratch, write_sscratc= h }, + [CSR_SEPC] =3D { smode, read_sepc, write_sepc = }, + [CSR_SCAUSE] =3D { smode, read_scause, write_scause = }, + [CSR_SBADADDR] =3D { smode, read_sbadaddr, write_sbadadd= r }, + [CSR_SIP] =3D { smode, NULL, NULL, rmw_sip = }, =20 /* Supervisor Protection and Translation */ - [CSR_SATP] =3D { read_satp, write_satp }, + [CSR_SATP] =3D { smode, read_satp, write_satp = }, =20 /* Physical Memory Protection */ - [CSR_PMPCFG0 ... CSR_PMPADDR9] =3D { read_pmpcfg, write_pmpcfg }, - [CSR_PMPADDR0 ... CSR_PMPADDR15] =3D { read_pmpaddr, write_pmpaddr }, + [CSR_PMPCFG0 ... CSR_PMPADDR9] =3D { pmp, read_pmpcfg, write_pmpc= fg }, + [CSR_PMPADDR0 ... CSR_PMPADDR15] =3D { pmp, read_pmpaddr, write_pmpa= ddr }, =20 /* Performance Counters */ - [CSR_HPMCOUNTER3 ... CSR_HPMCOUNTER31] =3D { read_zero_counter }, - [CSR_MHPMCOUNTER3 ... CSR_MHPMCOUNTER31] =3D { read_zero }, - [CSR_MHPMEVENT3 ... CSR_MHPMEVENT31] =3D { read_zero }, + [CSR_HPMCOUNTER3 ... CSR_HPMCOUNTER31] =3D { ctr, read_zero = }, + [CSR_MHPMCOUNTER3 ... CSR_MHPMCOUNTER31] =3D { any, read_zero = }, + [CSR_MHPMEVENT3 ... CSR_MHPMEVENT31] =3D { any, read_zero = }, #if defined(TARGET_RISCV32) - [CSR_HPMCOUNTER3H ... CSR_HPMCOUNTER31H] =3D { read_zero_counter }, - [CSR_MHPMCOUNTER3H ... CSR_MHPMCOUNTER31H] =3D { read_zero }, + [CSR_HPMCOUNTER3H ... CSR_HPMCOUNTER31H] =3D { ctr, read_zero = }, + [CSR_MHPMCOUNTER3H ... CSR_MHPMCOUNTER31H] =3D { any, read_zero = }, #endif #endif }; --=20 2.7.0