| #include "cpu.h" |
| #include "internals.h" |
| #include "exec/gdbstub.h" |
| #include "exec/helper-proto.h" |
| #include "qemu/host-utils.h" |
| #include "sysemu/arch_init.h" |
| #include "sysemu/sysemu.h" |
| #include "qemu/bitops.h" |
| #include "qemu/crc32c.h" |
| #include "exec/cpu_ldst.h" |
| #include "arm_ldst.h" |
| #include <zlib.h> /* For crc32 */ |
| |
| #ifndef CONFIG_USER_ONLY |
| static inline int get_phys_addr(CPUARMState *env, target_ulong address, |
| int access_type, int is_user, |
| hwaddr *phys_ptr, int *prot, |
| target_ulong *page_size); |
| |
| /* Definitions for the PMCCNTR and PMCR registers */ |
| #define PMCRD 0x8 |
| #define PMCRC 0x4 |
| #define PMCRE 0x1 |
| #endif |
| |
| static int vfp_gdb_get_reg(CPUARMState *env, uint8_t *buf, int reg) |
| { |
| int nregs; |
| |
| /* VFP data registers are always little-endian. */ |
| nregs = arm_feature(env, ARM_FEATURE_VFP3) ? 32 : 16; |
| if (reg < nregs) { |
| stfq_le_p(buf, env->vfp.regs[reg]); |
| return 8; |
| } |
| if (arm_feature(env, ARM_FEATURE_NEON)) { |
| /* Aliases for Q regs. */ |
| nregs += 16; |
| if (reg < nregs) { |
| stfq_le_p(buf, env->vfp.regs[(reg - 32) * 2]); |
| stfq_le_p(buf + 8, env->vfp.regs[(reg - 32) * 2 + 1]); |
| return 16; |
| } |
| } |
| switch (reg - nregs) { |
| case 0: stl_p(buf, env->vfp.xregs[ARM_VFP_FPSID]); return 4; |
| case 1: stl_p(buf, env->vfp.xregs[ARM_VFP_FPSCR]); return 4; |
| case 2: stl_p(buf, env->vfp.xregs[ARM_VFP_FPEXC]); return 4; |
| } |
| return 0; |
| } |
| |
| static int vfp_gdb_set_reg(CPUARMState *env, uint8_t *buf, int reg) |
| { |
| int nregs; |
| |
| nregs = arm_feature(env, ARM_FEATURE_VFP3) ? 32 : 16; |
| if (reg < nregs) { |
| env->vfp.regs[reg] = ldfq_le_p(buf); |
| return 8; |
| } |
| if (arm_feature(env, ARM_FEATURE_NEON)) { |
| nregs += 16; |
| if (reg < nregs) { |
| env->vfp.regs[(reg - 32) * 2] = ldfq_le_p(buf); |
| env->vfp.regs[(reg - 32) * 2 + 1] = ldfq_le_p(buf + 8); |
| return 16; |
| } |
| } |
| switch (reg - nregs) { |
| case 0: env->vfp.xregs[ARM_VFP_FPSID] = ldl_p(buf); return 4; |
| case 1: env->vfp.xregs[ARM_VFP_FPSCR] = ldl_p(buf); return 4; |
| case 2: env->vfp.xregs[ARM_VFP_FPEXC] = ldl_p(buf) & (1 << 30); return 4; |
| } |
| return 0; |
| } |
| |
| static int aarch64_fpu_gdb_get_reg(CPUARMState *env, uint8_t *buf, int reg) |
| { |
| switch (reg) { |
| case 0 ... 31: |
| /* 128 bit FP register */ |
| stfq_le_p(buf, env->vfp.regs[reg * 2]); |
| stfq_le_p(buf + 8, env->vfp.regs[reg * 2 + 1]); |
| return 16; |
| case 32: |
| /* FPSR */ |
| stl_p(buf, vfp_get_fpsr(env)); |
| return 4; |
| case 33: |
| /* FPCR */ |
| stl_p(buf, vfp_get_fpcr(env)); |
| return 4; |
| default: |
| return 0; |
| } |
| } |
| |
| static int aarch64_fpu_gdb_set_reg(CPUARMState *env, uint8_t *buf, int reg) |
| { |
| switch (reg) { |
| case 0 ... 31: |
| /* 128 bit FP register */ |
| env->vfp.regs[reg * 2] = ldfq_le_p(buf); |
| env->vfp.regs[reg * 2 + 1] = ldfq_le_p(buf + 8); |
| return 16; |
| case 32: |
| /* FPSR */ |
| vfp_set_fpsr(env, ldl_p(buf)); |
| return 4; |
| case 33: |
| /* FPCR */ |
| vfp_set_fpcr(env, ldl_p(buf)); |
| return 4; |
| default: |
| return 0; |
| } |
| } |
| |
| static uint64_t raw_read(CPUARMState *env, const ARMCPRegInfo *ri) |
| { |
| if (cpreg_field_is_64bit(ri)) { |
| return CPREG_FIELD64(env, ri); |
| } else { |
| return CPREG_FIELD32(env, ri); |
| } |
| } |
| |
| static void raw_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| if (cpreg_field_is_64bit(ri)) { |
| CPREG_FIELD64(env, ri) = value; |
| } else { |
| CPREG_FIELD32(env, ri) = value; |
| } |
| } |
| |
| static uint64_t read_raw_cp_reg(CPUARMState *env, const ARMCPRegInfo *ri) |
| { |
| /* Raw read of a coprocessor register (as needed for migration, etc). */ |
| if (ri->type & ARM_CP_CONST) { |
| return ri->resetvalue; |
| } else if (ri->raw_readfn) { |
| return ri->raw_readfn(env, ri); |
| } else if (ri->readfn) { |
| return ri->readfn(env, ri); |
| } else { |
| return raw_read(env, ri); |
| } |
| } |
| |
| static void write_raw_cp_reg(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t v) |
| { |
| /* Raw write of a coprocessor register (as needed for migration, etc). |
| * Note that constant registers are treated as write-ignored; the |
| * caller should check for success by whether a readback gives the |
| * value written. |
| */ |
| if (ri->type & ARM_CP_CONST) { |
| return; |
| } else if (ri->raw_writefn) { |
| ri->raw_writefn(env, ri, v); |
| } else if (ri->writefn) { |
| ri->writefn(env, ri, v); |
| } else { |
| raw_write(env, ri, v); |
| } |
| } |
| |
| bool write_cpustate_to_list(ARMCPU *cpu) |
| { |
| /* Write the coprocessor state from cpu->env to the (index,value) list. */ |
| int i; |
| bool ok = true; |
| |
| for (i = 0; i < cpu->cpreg_array_len; i++) { |
| uint32_t regidx = kvm_to_cpreg_id(cpu->cpreg_indexes[i]); |
| const ARMCPRegInfo *ri; |
| |
| ri = get_arm_cp_reginfo(cpu->cp_regs, regidx); |
| if (!ri) { |
| ok = false; |
| continue; |
| } |
| if (ri->type & ARM_CP_NO_MIGRATE) { |
| continue; |
| } |
| cpu->cpreg_values[i] = read_raw_cp_reg(&cpu->env, ri); |
| } |
| return ok; |
| } |
| |
| bool write_list_to_cpustate(ARMCPU *cpu) |
| { |
| int i; |
| bool ok = true; |
| |
| for (i = 0; i < cpu->cpreg_array_len; i++) { |
| uint32_t regidx = kvm_to_cpreg_id(cpu->cpreg_indexes[i]); |
| uint64_t v = cpu->cpreg_values[i]; |
| const ARMCPRegInfo *ri; |
| |
| ri = get_arm_cp_reginfo(cpu->cp_regs, regidx); |
| if (!ri) { |
| ok = false; |
| continue; |
| } |
| if (ri->type & ARM_CP_NO_MIGRATE) { |
| continue; |
| } |
| /* Write value and confirm it reads back as written |
| * (to catch read-only registers and partially read-only |
| * registers where the incoming migration value doesn't match) |
| */ |
| write_raw_cp_reg(&cpu->env, ri, v); |
| if (read_raw_cp_reg(&cpu->env, ri) != v) { |
| ok = false; |
| } |
| } |
| return ok; |
| } |
| |
| static void add_cpreg_to_list(gpointer key, gpointer opaque) |
| { |
| ARMCPU *cpu = opaque; |
| uint64_t regidx; |
| const ARMCPRegInfo *ri; |
| |
| regidx = *(uint32_t *)key; |
| ri = get_arm_cp_reginfo(cpu->cp_regs, regidx); |
| |
| if (!(ri->type & ARM_CP_NO_MIGRATE)) { |
| cpu->cpreg_indexes[cpu->cpreg_array_len] = cpreg_to_kvm_id(regidx); |
| /* The value array need not be initialized at this point */ |
| cpu->cpreg_array_len++; |
| } |
| } |
| |
| static void count_cpreg(gpointer key, gpointer opaque) |
| { |
| ARMCPU *cpu = opaque; |
| uint64_t regidx; |
| const ARMCPRegInfo *ri; |
| |
| regidx = *(uint32_t *)key; |
| ri = get_arm_cp_reginfo(cpu->cp_regs, regidx); |
| |
| if (!(ri->type & ARM_CP_NO_MIGRATE)) { |
| cpu->cpreg_array_len++; |
| } |
| } |
| |
| static gint cpreg_key_compare(gconstpointer a, gconstpointer b) |
| { |
| uint64_t aidx = cpreg_to_kvm_id(*(uint32_t *)a); |
| uint64_t bidx = cpreg_to_kvm_id(*(uint32_t *)b); |
| |
| if (aidx > bidx) { |
| return 1; |
| } |
| if (aidx < bidx) { |
| return -1; |
| } |
| return 0; |
| } |
| |
| static void cpreg_make_keylist(gpointer key, gpointer value, gpointer udata) |
| { |
| GList **plist = udata; |
| |
| *plist = g_list_prepend(*plist, key); |
| } |
| |
| void init_cpreg_list(ARMCPU *cpu) |
| { |
| /* Initialise the cpreg_tuples[] array based on the cp_regs hash. |
| * Note that we require cpreg_tuples[] to be sorted by key ID. |
| */ |
| GList *keys = NULL; |
| int arraylen; |
| |
| g_hash_table_foreach(cpu->cp_regs, cpreg_make_keylist, &keys); |
| |
| keys = g_list_sort(keys, cpreg_key_compare); |
| |
| cpu->cpreg_array_len = 0; |
| |
| g_list_foreach(keys, count_cpreg, cpu); |
| |
| arraylen = cpu->cpreg_array_len; |
| cpu->cpreg_indexes = g_new(uint64_t, arraylen); |
| cpu->cpreg_values = g_new(uint64_t, arraylen); |
| cpu->cpreg_vmstate_indexes = g_new(uint64_t, arraylen); |
| cpu->cpreg_vmstate_values = g_new(uint64_t, arraylen); |
| cpu->cpreg_vmstate_array_len = cpu->cpreg_array_len; |
| cpu->cpreg_array_len = 0; |
| |
| g_list_foreach(keys, add_cpreg_to_list, cpu); |
| |
| assert(cpu->cpreg_array_len == arraylen); |
| |
| g_list_free(keys); |
| } |
| |
| static void dacr_write(CPUARMState *env, const ARMCPRegInfo *ri, uint64_t value) |
| { |
| ARMCPU *cpu = arm_env_get_cpu(env); |
| |
| raw_write(env, ri, value); |
| tlb_flush(CPU(cpu), 1); /* Flush TLB as domain not tracked in TLB */ |
| } |
| |
| static void fcse_write(CPUARMState *env, const ARMCPRegInfo *ri, uint64_t value) |
| { |
| ARMCPU *cpu = arm_env_get_cpu(env); |
| |
| if (raw_read(env, ri) != value) { |
| /* Unlike real hardware the qemu TLB uses virtual addresses, |
| * not modified virtual addresses, so this causes a TLB flush. |
| */ |
| tlb_flush(CPU(cpu), 1); |
| raw_write(env, ri, value); |
| } |
| } |
| |
| static void contextidr_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| ARMCPU *cpu = arm_env_get_cpu(env); |
| |
| if (raw_read(env, ri) != value && !arm_feature(env, ARM_FEATURE_MPU) |
| && !extended_addresses_enabled(env)) { |
| /* For VMSA (when not using the LPAE long descriptor page table |
| * format) this register includes the ASID, so do a TLB flush. |
| * For PMSA it is purely a process ID and no action is needed. |
| */ |
| tlb_flush(CPU(cpu), 1); |
| } |
| raw_write(env, ri, value); |
| } |
| |
| static void tlbiall_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| /* Invalidate all (TLBIALL) */ |
| ARMCPU *cpu = arm_env_get_cpu(env); |
| |
| tlb_flush(CPU(cpu), 1); |
| } |
| |
| static void tlbimva_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| /* Invalidate single TLB entry by MVA and ASID (TLBIMVA) */ |
| ARMCPU *cpu = arm_env_get_cpu(env); |
| |
| tlb_flush_page(CPU(cpu), value & TARGET_PAGE_MASK); |
| } |
| |
| static void tlbiasid_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| /* Invalidate by ASID (TLBIASID) */ |
| ARMCPU *cpu = arm_env_get_cpu(env); |
| |
| tlb_flush(CPU(cpu), value == 0); |
| } |
| |
| static void tlbimvaa_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| /* Invalidate single entry by MVA, all ASIDs (TLBIMVAA) */ |
| ARMCPU *cpu = arm_env_get_cpu(env); |
| |
| tlb_flush_page(CPU(cpu), value & TARGET_PAGE_MASK); |
| } |
| |
| /* IS variants of TLB operations must affect all cores */ |
| static void tlbiall_is_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| CPUState *other_cs; |
| |
| CPU_FOREACH(other_cs) { |
| tlb_flush(other_cs, 1); |
| } |
| } |
| |
| static void tlbiasid_is_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| CPUState *other_cs; |
| |
| CPU_FOREACH(other_cs) { |
| tlb_flush(other_cs, value == 0); |
| } |
| } |
| |
| static void tlbimva_is_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| CPUState *other_cs; |
| |
| CPU_FOREACH(other_cs) { |
| tlb_flush_page(other_cs, value & TARGET_PAGE_MASK); |
| } |
| } |
| |
| static void tlbimvaa_is_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| CPUState *other_cs; |
| |
| CPU_FOREACH(other_cs) { |
| tlb_flush_page(other_cs, value & TARGET_PAGE_MASK); |
| } |
| } |
| |
| static const ARMCPRegInfo cp_reginfo[] = { |
| { .name = "FCSEIDR", .cp = 15, .crn = 13, .crm = 0, .opc1 = 0, .opc2 = 0, |
| .access = PL1_RW, .fieldoffset = offsetof(CPUARMState, cp15.c13_fcse), |
| .resetvalue = 0, .writefn = fcse_write, .raw_writefn = raw_write, }, |
| { .name = "CONTEXTIDR", .state = ARM_CP_STATE_BOTH, |
| .opc0 = 3, .opc1 = 0, .crn = 13, .crm = 0, .opc2 = 1, |
| .access = PL1_RW, |
| .fieldoffset = offsetof(CPUARMState, cp15.contextidr_el1), |
| .resetvalue = 0, .writefn = contextidr_write, .raw_writefn = raw_write, }, |
| REGINFO_SENTINEL |
| }; |
| |
| static const ARMCPRegInfo not_v8_cp_reginfo[] = { |
| /* NB: Some of these registers exist in v8 but with more precise |
| * definitions that don't use CP_ANY wildcards (mostly in v8_cp_reginfo[]). |
| */ |
| /* MMU Domain access control / MPU write buffer control */ |
| { .name = "DACR", .cp = 15, |
| .crn = 3, .crm = CP_ANY, .opc1 = CP_ANY, .opc2 = CP_ANY, |
| .access = PL1_RW, .fieldoffset = offsetof(CPUARMState, cp15.c3), |
| .resetvalue = 0, .writefn = dacr_write, .raw_writefn = raw_write, }, |
| /* ??? This covers not just the impdef TLB lockdown registers but also |
| * some v7VMSA registers relating to TEX remap, so it is overly broad. |
| */ |
| { .name = "TLB_LOCKDOWN", .cp = 15, .crn = 10, .crm = CP_ANY, |
| .opc1 = CP_ANY, .opc2 = CP_ANY, .access = PL1_RW, .type = ARM_CP_NOP }, |
| /* Cache maintenance ops; some of this space may be overridden later. */ |
| { .name = "CACHEMAINT", .cp = 15, .crn = 7, .crm = CP_ANY, |
| .opc1 = 0, .opc2 = CP_ANY, .access = PL1_W, |
| .type = ARM_CP_NOP | ARM_CP_OVERRIDE }, |
| REGINFO_SENTINEL |
| }; |
| |
| static const ARMCPRegInfo not_v6_cp_reginfo[] = { |
| /* Not all pre-v6 cores implemented this WFI, so this is slightly |
| * over-broad. |
| */ |
| { .name = "WFI_v5", .cp = 15, .crn = 7, .crm = 8, .opc1 = 0, .opc2 = 2, |
| .access = PL1_W, .type = ARM_CP_WFI }, |
| REGINFO_SENTINEL |
| }; |
| |
| static const ARMCPRegInfo not_v7_cp_reginfo[] = { |
| /* Standard v6 WFI (also used in some pre-v6 cores); not in v7 (which |
| * is UNPREDICTABLE; we choose to NOP as most implementations do). |
| */ |
| { .name = "WFI_v6", .cp = 15, .crn = 7, .crm = 0, .opc1 = 0, .opc2 = 4, |
| .access = PL1_W, .type = ARM_CP_WFI }, |
| /* L1 cache lockdown. Not architectural in v6 and earlier but in practice |
| * implemented in 926, 946, 1026, 1136, 1176 and 11MPCore. StrongARM and |
| * OMAPCP will override this space. |
| */ |
| { .name = "DLOCKDOWN", .cp = 15, .crn = 9, .crm = 0, .opc1 = 0, .opc2 = 0, |
| .access = PL1_RW, .fieldoffset = offsetof(CPUARMState, cp15.c9_data), |
| .resetvalue = 0 }, |
| { .name = "ILOCKDOWN", .cp = 15, .crn = 9, .crm = 0, .opc1 = 0, .opc2 = 1, |
| .access = PL1_RW, .fieldoffset = offsetof(CPUARMState, cp15.c9_insn), |
| .resetvalue = 0 }, |
| /* v6 doesn't have the cache ID registers but Linux reads them anyway */ |
| { .name = "DUMMY", .cp = 15, .crn = 0, .crm = 0, .opc1 = 1, .opc2 = CP_ANY, |
| .access = PL1_R, .type = ARM_CP_CONST | ARM_CP_NO_MIGRATE, |
| .resetvalue = 0 }, |
| /* We don't implement pre-v7 debug but most CPUs had at least a DBGDIDR; |
| * implementing it as RAZ means the "debug architecture version" bits |
| * will read as a reserved value, which should cause Linux to not try |
| * to use the debug hardware. |
| */ |
| { .name = "DBGDIDR", .cp = 14, .crn = 0, .crm = 0, .opc1 = 0, .opc2 = 0, |
| .access = PL0_R, .type = ARM_CP_CONST, .resetvalue = 0 }, |
| /* MMU TLB control. Note that the wildcarding means we cover not just |
| * the unified TLB ops but also the dside/iside/inner-shareable variants. |
| */ |
| { .name = "TLBIALL", .cp = 15, .crn = 8, .crm = CP_ANY, |
| .opc1 = CP_ANY, .opc2 = 0, .access = PL1_W, .writefn = tlbiall_write, |
| .type = ARM_CP_NO_MIGRATE }, |
| { .name = "TLBIMVA", .cp = 15, .crn = 8, .crm = CP_ANY, |
| .opc1 = CP_ANY, .opc2 = 1, .access = PL1_W, .writefn = tlbimva_write, |
| .type = ARM_CP_NO_MIGRATE }, |
| { .name = "TLBIASID", .cp = 15, .crn = 8, .crm = CP_ANY, |
| .opc1 = CP_ANY, .opc2 = 2, .access = PL1_W, .writefn = tlbiasid_write, |
| .type = ARM_CP_NO_MIGRATE }, |
| { .name = "TLBIMVAA", .cp = 15, .crn = 8, .crm = CP_ANY, |
| .opc1 = CP_ANY, .opc2 = 3, .access = PL1_W, .writefn = tlbimvaa_write, |
| .type = ARM_CP_NO_MIGRATE }, |
| REGINFO_SENTINEL |
| }; |
| |
| static void cpacr_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| uint32_t mask = 0; |
| |
| /* In ARMv8 most bits of CPACR_EL1 are RES0. */ |
| if (!arm_feature(env, ARM_FEATURE_V8)) { |
| /* ARMv7 defines bits for unimplemented coprocessors as RAZ/WI. |
| * ASEDIS [31] and D32DIS [30] are both UNK/SBZP without VFP. |
| * TRCDIS [28] is RAZ/WI since we do not implement a trace macrocell. |
| */ |
| if (arm_feature(env, ARM_FEATURE_VFP)) { |
| /* VFP coprocessor: cp10 & cp11 [23:20] */ |
| mask |= (1 << 31) | (1 << 30) | (0xf << 20); |
| |
| if (!arm_feature(env, ARM_FEATURE_NEON)) { |
| /* ASEDIS [31] bit is RAO/WI */ |
| value |= (1 << 31); |
| } |
| |
| /* VFPv3 and upwards with NEON implement 32 double precision |
| * registers (D0-D31). |
| */ |
| if (!arm_feature(env, ARM_FEATURE_NEON) || |
| !arm_feature(env, ARM_FEATURE_VFP3)) { |
| /* D32DIS [30] is RAO/WI if D16-31 are not implemented. */ |
| value |= (1 << 30); |
| } |
| } |
| value &= mask; |
| } |
| env->cp15.c1_coproc = value; |
| } |
| |
| static const ARMCPRegInfo v6_cp_reginfo[] = { |
| /* prefetch by MVA in v6, NOP in v7 */ |
| { .name = "MVA_prefetch", |
| .cp = 15, .crn = 7, .crm = 13, .opc1 = 0, .opc2 = 1, |
| .access = PL1_W, .type = ARM_CP_NOP }, |
| { .name = "ISB", .cp = 15, .crn = 7, .crm = 5, .opc1 = 0, .opc2 = 4, |
| .access = PL0_W, .type = ARM_CP_NOP }, |
| { .name = "DSB", .cp = 15, .crn = 7, .crm = 10, .opc1 = 0, .opc2 = 4, |
| .access = PL0_W, .type = ARM_CP_NOP }, |
| { .name = "DMB", .cp = 15, .crn = 7, .crm = 10, .opc1 = 0, .opc2 = 5, |
| .access = PL0_W, .type = ARM_CP_NOP }, |
| { .name = "IFAR", .cp = 15, .crn = 6, .crm = 0, .opc1 = 0, .opc2 = 2, |
| .access = PL1_RW, |
| .fieldoffset = offsetofhigh32(CPUARMState, cp15.far_el[1]), |
| .resetvalue = 0, }, |
| /* Watchpoint Fault Address Register : should actually only be present |
| * for 1136, 1176, 11MPCore. |
| */ |
| { .name = "WFAR", .cp = 15, .crn = 6, .crm = 0, .opc1 = 0, .opc2 = 1, |
| .access = PL1_RW, .type = ARM_CP_CONST, .resetvalue = 0, }, |
| { .name = "CPACR", .state = ARM_CP_STATE_BOTH, .opc0 = 3, |
| .crn = 1, .crm = 0, .opc1 = 0, .opc2 = 2, |
| .access = PL1_RW, .fieldoffset = offsetof(CPUARMState, cp15.c1_coproc), |
| .resetvalue = 0, .writefn = cpacr_write }, |
| REGINFO_SENTINEL |
| }; |
| |
| static CPAccessResult pmreg_access(CPUARMState *env, const ARMCPRegInfo *ri) |
| { |
| /* Performance monitor registers user accessibility is controlled |
| * by PMUSERENR. |
| */ |
| if (arm_current_el(env) == 0 && !env->cp15.c9_pmuserenr) { |
| return CP_ACCESS_TRAP; |
| } |
| return CP_ACCESS_OK; |
| } |
| |
| #ifndef CONFIG_USER_ONLY |
| |
| static inline bool arm_ccnt_enabled(CPUARMState *env) |
| { |
| /* This does not support checking PMCCFILTR_EL0 register */ |
| |
| if (!(env->cp15.c9_pmcr & PMCRE)) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void pmccntr_sync(CPUARMState *env) |
| { |
| uint64_t temp_ticks; |
| |
| temp_ticks = muldiv64(qemu_clock_get_us(QEMU_CLOCK_VIRTUAL), |
| get_ticks_per_sec(), 1000000); |
| |
| if (env->cp15.c9_pmcr & PMCRD) { |
| /* Increment once every 64 processor clock cycles */ |
| temp_ticks /= 64; |
| } |
| |
| if (arm_ccnt_enabled(env)) { |
| env->cp15.c15_ccnt = temp_ticks - env->cp15.c15_ccnt; |
| } |
| } |
| |
| static void pmcr_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| pmccntr_sync(env); |
| |
| if (value & PMCRC) { |
| /* The counter has been reset */ |
| env->cp15.c15_ccnt = 0; |
| } |
| |
| /* only the DP, X, D and E bits are writable */ |
| env->cp15.c9_pmcr &= ~0x39; |
| env->cp15.c9_pmcr |= (value & 0x39); |
| |
| pmccntr_sync(env); |
| } |
| |
| static uint64_t pmccntr_read(CPUARMState *env, const ARMCPRegInfo *ri) |
| { |
| uint64_t total_ticks; |
| |
| if (!arm_ccnt_enabled(env)) { |
| /* Counter is disabled, do not change value */ |
| return env->cp15.c15_ccnt; |
| } |
| |
| total_ticks = muldiv64(qemu_clock_get_us(QEMU_CLOCK_VIRTUAL), |
| get_ticks_per_sec(), 1000000); |
| |
| if (env->cp15.c9_pmcr & PMCRD) { |
| /* Increment once every 64 processor clock cycles */ |
| total_ticks /= 64; |
| } |
| return total_ticks - env->cp15.c15_ccnt; |
| } |
| |
| static void pmccntr_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| uint64_t total_ticks; |
| |
| if (!arm_ccnt_enabled(env)) { |
| /* Counter is disabled, set the absolute value */ |
| env->cp15.c15_ccnt = value; |
| return; |
| } |
| |
| total_ticks = muldiv64(qemu_clock_get_us(QEMU_CLOCK_VIRTUAL), |
| get_ticks_per_sec(), 1000000); |
| |
| if (env->cp15.c9_pmcr & PMCRD) { |
| /* Increment once every 64 processor clock cycles */ |
| total_ticks /= 64; |
| } |
| env->cp15.c15_ccnt = total_ticks - value; |
| } |
| |
| static void pmccntr_write32(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| uint64_t cur_val = pmccntr_read(env, NULL); |
| |
| pmccntr_write(env, ri, deposit64(cur_val, 0, 32, value)); |
| } |
| |
| #else /* CONFIG_USER_ONLY */ |
| |
| void pmccntr_sync(CPUARMState *env) |
| { |
| } |
| |
| #endif |
| |
| static void pmccfiltr_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| pmccntr_sync(env); |
| env->cp15.pmccfiltr_el0 = value & 0x7E000000; |
| pmccntr_sync(env); |
| } |
| |
| static void pmcntenset_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| value &= (1 << 31); |
| env->cp15.c9_pmcnten |= value; |
| } |
| |
| static void pmcntenclr_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| value &= (1 << 31); |
| env->cp15.c9_pmcnten &= ~value; |
| } |
| |
| static void pmovsr_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| env->cp15.c9_pmovsr &= ~value; |
| } |
| |
| static void pmxevtyper_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| env->cp15.c9_pmxevtyper = value & 0xff; |
| } |
| |
| static void pmuserenr_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| env->cp15.c9_pmuserenr = value & 1; |
| } |
| |
| static void pmintenset_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| /* We have no event counters so only the C bit can be changed */ |
| value &= (1 << 31); |
| env->cp15.c9_pminten |= value; |
| } |
| |
| static void pmintenclr_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| value &= (1 << 31); |
| env->cp15.c9_pminten &= ~value; |
| } |
| |
| static void vbar_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| /* Note that even though the AArch64 view of this register has bits |
| * [10:0] all RES0 we can only mask the bottom 5, to comply with the |
| * architectural requirements for bits which are RES0 only in some |
| * contexts. (ARMv8 would permit us to do no masking at all, but ARMv7 |
| * requires the bottom five bits to be RAZ/WI because they're UNK/SBZP.) |
| */ |
| raw_write(env, ri, value & ~0x1FULL); |
| } |
| |
| static void scr_write(CPUARMState *env, const ARMCPRegInfo *ri, uint64_t value) |
| { |
| /* We only mask off bits that are RES0 both for AArch64 and AArch32. |
| * For bits that vary between AArch32/64, code needs to check the |
| * current execution mode before directly using the feature bit. |
| */ |
| uint32_t valid_mask = SCR_AARCH64_MASK | SCR_AARCH32_MASK; |
| |
| if (!arm_feature(env, ARM_FEATURE_EL2)) { |
| valid_mask &= ~SCR_HCE; |
| |
| /* On ARMv7, SMD (or SCD as it is called in v7) is only |
| * supported if EL2 exists. The bit is UNK/SBZP when |
| * EL2 is unavailable. In QEMU ARMv7, we force it to always zero |
| * when EL2 is unavailable. |
| */ |
| if (arm_feature(env, ARM_FEATURE_V7)) { |
| valid_mask &= ~SCR_SMD; |
| } |
| } |
| |
| /* Clear all-context RES0 bits. */ |
| value &= valid_mask; |
| raw_write(env, ri, value); |
| } |
| |
| static uint64_t ccsidr_read(CPUARMState *env, const ARMCPRegInfo *ri) |
| { |
| ARMCPU *cpu = arm_env_get_cpu(env); |
| return cpu->ccsidr[env->cp15.c0_cssel]; |
| } |
| |
| static void csselr_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| raw_write(env, ri, value & 0xf); |
| } |
| |
| static uint64_t isr_read(CPUARMState *env, const ARMCPRegInfo *ri) |
| { |
| CPUState *cs = ENV_GET_CPU(env); |
| uint64_t ret = 0; |
| |
| if (cs->interrupt_request & CPU_INTERRUPT_HARD) { |
| ret |= CPSR_I; |
| } |
| if (cs->interrupt_request & CPU_INTERRUPT_FIQ) { |
| ret |= CPSR_F; |
| } |
| /* External aborts are not possible in QEMU so A bit is always clear */ |
| return ret; |
| } |
| |
| static const ARMCPRegInfo v7_cp_reginfo[] = { |
| /* the old v6 WFI, UNPREDICTABLE in v7 but we choose to NOP */ |
| { .name = "NOP", .cp = 15, .crn = 7, .crm = 0, .opc1 = 0, .opc2 = 4, |
| .access = PL1_W, .type = ARM_CP_NOP }, |
| /* Performance monitors are implementation defined in v7, |
| * but with an ARM recommended set of registers, which we |
| * follow (although we don't actually implement any counters) |
| * |
| * Performance registers fall into three categories: |
| * (a) always UNDEF in PL0, RW in PL1 (PMINTENSET, PMINTENCLR) |
| * (b) RO in PL0 (ie UNDEF on write), RW in PL1 (PMUSERENR) |
| * (c) UNDEF in PL0 if PMUSERENR.EN==0, otherwise accessible (all others) |
| * For the cases controlled by PMUSERENR we must set .access to PL0_RW |
| * or PL0_RO as appropriate and then check PMUSERENR in the helper fn. |
| */ |
| { .name = "PMCNTENSET", .cp = 15, .crn = 9, .crm = 12, .opc1 = 0, .opc2 = 1, |
| .access = PL0_RW, .type = ARM_CP_NO_MIGRATE, |
| .fieldoffset = offsetoflow32(CPUARMState, cp15.c9_pmcnten), |
| .writefn = pmcntenset_write, |
| .accessfn = pmreg_access, |
| .raw_writefn = raw_write }, |
| { .name = "PMCNTENSET_EL0", .state = ARM_CP_STATE_AA64, |
| .opc0 = 3, .opc1 = 3, .crn = 9, .crm = 12, .opc2 = 1, |
| .access = PL0_RW, .accessfn = pmreg_access, |
| .fieldoffset = offsetof(CPUARMState, cp15.c9_pmcnten), .resetvalue = 0, |
| .writefn = pmcntenset_write, .raw_writefn = raw_write }, |
| { .name = "PMCNTENCLR", .cp = 15, .crn = 9, .crm = 12, .opc1 = 0, .opc2 = 2, |
| .access = PL0_RW, |
| .fieldoffset = offsetoflow32(CPUARMState, cp15.c9_pmcnten), |
| .accessfn = pmreg_access, |
| .writefn = pmcntenclr_write, |
| .type = ARM_CP_NO_MIGRATE }, |
| { .name = "PMCNTENCLR_EL0", .state = ARM_CP_STATE_AA64, |
| .opc0 = 3, .opc1 = 3, .crn = 9, .crm = 12, .opc2 = 2, |
| .access = PL0_RW, .accessfn = pmreg_access, |
| .type = ARM_CP_NO_MIGRATE, |
| .fieldoffset = offsetof(CPUARMState, cp15.c9_pmcnten), |
| .writefn = pmcntenclr_write }, |
| { .name = "PMOVSR", .cp = 15, .crn = 9, .crm = 12, .opc1 = 0, .opc2 = 3, |
| .access = PL0_RW, .fieldoffset = offsetof(CPUARMState, cp15.c9_pmovsr), |
| .accessfn = pmreg_access, |
| .writefn = pmovsr_write, |
| .raw_writefn = raw_write }, |
| /* Unimplemented so WI. */ |
| { .name = "PMSWINC", .cp = 15, .crn = 9, .crm = 12, .opc1 = 0, .opc2 = 4, |
| .access = PL0_W, .accessfn = pmreg_access, .type = ARM_CP_NOP }, |
| /* Since we don't implement any events, writing to PMSELR is UNPREDICTABLE. |
| * We choose to RAZ/WI. |
| */ |
| { .name = "PMSELR", .cp = 15, .crn = 9, .crm = 12, .opc1 = 0, .opc2 = 5, |
| .access = PL0_RW, .type = ARM_CP_CONST, .resetvalue = 0, |
| .accessfn = pmreg_access }, |
| #ifndef CONFIG_USER_ONLY |
| { .name = "PMCCNTR", .cp = 15, .crn = 9, .crm = 13, .opc1 = 0, .opc2 = 0, |
| .access = PL0_RW, .resetvalue = 0, .type = ARM_CP_IO, |
| .readfn = pmccntr_read, .writefn = pmccntr_write32, |
| .accessfn = pmreg_access }, |
| { .name = "PMCCNTR_EL0", .state = ARM_CP_STATE_AA64, |
| .opc0 = 3, .opc1 = 3, .crn = 9, .crm = 13, .opc2 = 0, |
| .access = PL0_RW, .accessfn = pmreg_access, |
| .type = ARM_CP_IO, |
| .readfn = pmccntr_read, .writefn = pmccntr_write, }, |
| #endif |
| { .name = "PMCCFILTR_EL0", .state = ARM_CP_STATE_AA64, |
| .opc0 = 3, .opc1 = 3, .crn = 14, .crm = 15, .opc2 = 7, |
| .writefn = pmccfiltr_write, |
| .access = PL0_RW, .accessfn = pmreg_access, |
| .type = ARM_CP_IO, |
| .fieldoffset = offsetof(CPUARMState, cp15.pmccfiltr_el0), |
| .resetvalue = 0, }, |
| { .name = "PMXEVTYPER", .cp = 15, .crn = 9, .crm = 13, .opc1 = 0, .opc2 = 1, |
| .access = PL0_RW, |
| .fieldoffset = offsetof(CPUARMState, cp15.c9_pmxevtyper), |
| .accessfn = pmreg_access, .writefn = pmxevtyper_write, |
| .raw_writefn = raw_write }, |
| /* Unimplemented, RAZ/WI. */ |
| { .name = "PMXEVCNTR", .cp = 15, .crn = 9, .crm = 13, .opc1 = 0, .opc2 = 2, |
| .access = PL0_RW, .type = ARM_CP_CONST, .resetvalue = 0, |
| .accessfn = pmreg_access }, |
| { .name = "PMUSERENR", .cp = 15, .crn = 9, .crm = 14, .opc1 = 0, .opc2 = 0, |
| .access = PL0_R | PL1_RW, |
| .fieldoffset = offsetof(CPUARMState, cp15.c9_pmuserenr), |
| .resetvalue = 0, |
| .writefn = pmuserenr_write, .raw_writefn = raw_write }, |
| { .name = "PMINTENSET", .cp = 15, .crn = 9, .crm = 14, .opc1 = 0, .opc2 = 1, |
| .access = PL1_RW, |
| .fieldoffset = offsetof(CPUARMState, cp15.c9_pminten), |
| .resetvalue = 0, |
| .writefn = pmintenset_write, .raw_writefn = raw_write }, |
| { .name = "PMINTENCLR", .cp = 15, .crn = 9, .crm = 14, .opc1 = 0, .opc2 = 2, |
| .access = PL1_RW, .type = ARM_CP_NO_MIGRATE, |
| .fieldoffset = offsetof(CPUARMState, cp15.c9_pminten), |
| .resetvalue = 0, .writefn = pmintenclr_write, }, |
| { .name = "VBAR", .state = ARM_CP_STATE_BOTH, |
| .opc0 = 3, .crn = 12, .crm = 0, .opc1 = 0, .opc2 = 0, |
| .access = PL1_RW, .writefn = vbar_write, |
| .fieldoffset = offsetof(CPUARMState, cp15.vbar_el[1]), |
| .resetvalue = 0 }, |
| { .name = "SCR", .cp = 15, .crn = 1, .crm = 1, .opc1 = 0, .opc2 = 0, |
| .access = PL1_RW, .fieldoffset = offsetoflow32(CPUARMState, cp15.scr_el3), |
| .resetvalue = 0, .writefn = scr_write }, |
| { .name = "CCSIDR", .state = ARM_CP_STATE_BOTH, |
| .opc0 = 3, .crn = 0, .crm = 0, .opc1 = 1, .opc2 = 0, |
| .access = PL1_R, .readfn = ccsidr_read, .type = ARM_CP_NO_MIGRATE }, |
| { .name = "CSSELR", .state = ARM_CP_STATE_BOTH, |
| .opc0 = 3, .crn = 0, .crm = 0, .opc1 = 2, .opc2 = 0, |
| .access = PL1_RW, .fieldoffset = offsetof(CPUARMState, cp15.c0_cssel), |
| .writefn = csselr_write, .resetvalue = 0 }, |
| /* Auxiliary ID register: this actually has an IMPDEF value but for now |
| * just RAZ for all cores: |
| */ |
| { .name = "AIDR", .state = ARM_CP_STATE_BOTH, |
| .opc0 = 3, .opc1 = 1, .crn = 0, .crm = 0, .opc2 = 7, |
| .access = PL1_R, .type = ARM_CP_CONST, .resetvalue = 0 }, |
| /* Auxiliary fault status registers: these also are IMPDEF, and we |
| * choose to RAZ/WI for all cores. |
| */ |
| { .name = "AFSR0_EL1", .state = ARM_CP_STATE_BOTH, |
| .opc0 = 3, .opc1 = 0, .crn = 5, .crm = 1, .opc2 = 0, |
| .access = PL1_RW, .type = ARM_CP_CONST, .resetvalue = 0 }, |
| { .name = "AFSR1_EL1", .state = ARM_CP_STATE_BOTH, |
| .opc0 = 3, .opc1 = 0, .crn = 5, .crm = 1, .opc2 = 1, |
| .access = PL1_RW, .type = ARM_CP_CONST, .resetvalue = 0 }, |
| /* MAIR can just read-as-written because we don't implement caches |
| * and so don't need to care about memory attributes. |
| */ |
| { .name = "MAIR_EL1", .state = ARM_CP_STATE_AA64, |
| .opc0 = 3, .opc1 = 0, .crn = 10, .crm = 2, .opc2 = 0, |
| .access = PL1_RW, .fieldoffset = offsetof(CPUARMState, cp15.mair_el1), |
| .resetvalue = 0 }, |
| /* For non-long-descriptor page tables these are PRRR and NMRR; |
| * regardless they still act as reads-as-written for QEMU. |
| * The override is necessary because of the overly-broad TLB_LOCKDOWN |
| * definition. |
| */ |
| { .name = "MAIR0", .state = ARM_CP_STATE_AA32, .type = ARM_CP_OVERRIDE, |
| .cp = 15, .opc1 = 0, .crn = 10, .crm = 2, .opc2 = 0, .access = PL1_RW, |
| .fieldoffset = offsetoflow32(CPUARMState, cp15.mair_el1), |
| .resetfn = arm_cp_reset_ignore }, |
| { .name = "MAIR1", .state = ARM_CP_STATE_AA32, .type = ARM_CP_OVERRIDE, |
| .cp = 15, .opc1 = 0, .crn = 10, .crm = 2, .opc2 = 1, .access = PL1_RW, |
| .fieldoffset = offsetofhigh32(CPUARMState, cp15.mair_el1), |
| .resetfn = arm_cp_reset_ignore }, |
| { .name = "ISR_EL1", .state = ARM_CP_STATE_BOTH, |
| .opc0 = 3, .opc1 = 0, .crn = 12, .crm = 1, .opc2 = 0, |
| .type = ARM_CP_NO_MIGRATE, .access = PL1_R, .readfn = isr_read }, |
| /* 32 bit ITLB invalidates */ |
| { .name = "ITLBIALL", .cp = 15, .opc1 = 0, .crn = 8, .crm = 5, .opc2 = 0, |
| .type = ARM_CP_NO_MIGRATE, .access = PL1_W, .writefn = tlbiall_write }, |
| { .name = "ITLBIMVA", .cp = 15, .opc1 = 0, .crn = 8, .crm = 5, .opc2 = 1, |
| .type = ARM_CP_NO_MIGRATE, .access = PL1_W, .writefn = tlbimva_write }, |
| { .name = "ITLBIASID", .cp = 15, .opc1 = 0, .crn = 8, .crm = 5, .opc2 = 2, |
| .type = ARM_CP_NO_MIGRATE, .access = PL1_W, .writefn = tlbiasid_write }, |
| /* 32 bit DTLB invalidates */ |
| { .name = "DTLBIALL", .cp = 15, .opc1 = 0, .crn = 8, .crm = 6, .opc2 = 0, |
| .type = ARM_CP_NO_MIGRATE, .access = PL1_W, .writefn = tlbiall_write }, |
| { .name = "DTLBIMVA", .cp = 15, .opc1 = 0, .crn = 8, .crm = 6, .opc2 = 1, |
| .type = ARM_CP_NO_MIGRATE, .access = PL1_W, .writefn = tlbimva_write }, |
| { .name = "DTLBIASID", .cp = 15, .opc1 = 0, .crn = 8, .crm = 6, .opc2 = 2, |
| .type = ARM_CP_NO_MIGRATE, .access = PL1_W, .writefn = tlbiasid_write }, |
| /* 32 bit TLB invalidates */ |
| { .name = "TLBIALL", .cp = 15, .opc1 = 0, .crn = 8, .crm = 7, .opc2 = 0, |
| .type = ARM_CP_NO_MIGRATE, .access = PL1_W, .writefn = tlbiall_write }, |
| { .name = "TLBIMVA", .cp = 15, .opc1 = 0, .crn = 8, .crm = 7, .opc2 = 1, |
| .type = ARM_CP_NO_MIGRATE, .access = PL1_W, .writefn = tlbimva_write }, |
| { .name = "TLBIASID", .cp = 15, .opc1 = 0, .crn = 8, .crm = 7, .opc2 = 2, |
| .type = ARM_CP_NO_MIGRATE, .access = PL1_W, .writefn = tlbiasid_write }, |
| { .name = "TLBIMVAA", .cp = 15, .opc1 = 0, .crn = 8, .crm = 7, .opc2 = 3, |
| .type = ARM_CP_NO_MIGRATE, .access = PL1_W, .writefn = tlbimvaa_write }, |
| REGINFO_SENTINEL |
| }; |
| |
| static const ARMCPRegInfo v7mp_cp_reginfo[] = { |
| /* 32 bit TLB invalidates, Inner Shareable */ |
| { .name = "TLBIALLIS", .cp = 15, .opc1 = 0, .crn = 8, .crm = 3, .opc2 = 0, |
| .type = ARM_CP_NO_MIGRATE, .access = PL1_W, .writefn = tlbiall_is_write }, |
| { .name = "TLBIMVAIS", .cp = 15, .opc1 = 0, .crn = 8, .crm = 3, .opc2 = 1, |
| .type = ARM_CP_NO_MIGRATE, .access = PL1_W, .writefn = tlbimva_is_write }, |
| { .name = "TLBIASIDIS", .cp = 15, .opc1 = 0, .crn = 8, .crm = 3, .opc2 = 2, |
| .type = ARM_CP_NO_MIGRATE, .access = PL1_W, |
| .writefn = tlbiasid_is_write }, |
| { .name = "TLBIMVAAIS", .cp = 15, .opc1 = 0, .crn = 8, .crm = 3, .opc2 = 3, |
| .type = ARM_CP_NO_MIGRATE, .access = PL1_W, |
| .writefn = tlbimvaa_is_write }, |
| REGINFO_SENTINEL |
| }; |
| |
| static void teecr_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| value &= 1; |
| env->teecr = value; |
| } |
| |
| static CPAccessResult teehbr_access(CPUARMState *env, const ARMCPRegInfo *ri) |
| { |
| if (arm_current_el(env) == 0 && (env->teecr & 1)) { |
| return CP_ACCESS_TRAP; |
| } |
| return CP_ACCESS_OK; |
| } |
| |
| static const ARMCPRegInfo t2ee_cp_reginfo[] = { |
| { .name = "TEECR", .cp = 14, .crn = 0, .crm = 0, .opc1 = 6, .opc2 = 0, |
| .access = PL1_RW, .fieldoffset = offsetof(CPUARMState, teecr), |
| .resetvalue = 0, |
| .writefn = teecr_write }, |
| { .name = "TEEHBR", .cp = 14, .crn = 1, .crm = 0, .opc1 = 6, .opc2 = 0, |
| .access = PL0_RW, .fieldoffset = offsetof(CPUARMState, teehbr), |
| .accessfn = teehbr_access, .resetvalue = 0 }, |
| REGINFO_SENTINEL |
| }; |
| |
| static const ARMCPRegInfo v6k_cp_reginfo[] = { |
| { .name = "TPIDR_EL0", .state = ARM_CP_STATE_AA64, |
| .opc0 = 3, .opc1 = 3, .opc2 = 2, .crn = 13, .crm = 0, |
| .access = PL0_RW, |
| .fieldoffset = offsetof(CPUARMState, cp15.tpidr_el0), .resetvalue = 0 }, |
| { .name = "TPIDRURW", .cp = 15, .crn = 13, .crm = 0, .opc1 = 0, .opc2 = 2, |
| .access = PL0_RW, |
| .fieldoffset = offsetoflow32(CPUARMState, cp15.tpidr_el0), |
| .resetfn = arm_cp_reset_ignore }, |
| { .name = "TPIDRRO_EL0", .state = ARM_CP_STATE_AA64, |
| .opc0 = 3, .opc1 = 3, .opc2 = 3, .crn = 13, .crm = 0, |
| .access = PL0_R|PL1_W, |
| .fieldoffset = offsetof(CPUARMState, cp15.tpidrro_el0), .resetvalue = 0 }, |
| { .name = "TPIDRURO", .cp = 15, .crn = 13, .crm = 0, .opc1 = 0, .opc2 = 3, |
| .access = PL0_R|PL1_W, |
| .fieldoffset = offsetoflow32(CPUARMState, cp15.tpidrro_el0), |
| .resetfn = arm_cp_reset_ignore }, |
| { .name = "TPIDR_EL1", .state = ARM_CP_STATE_BOTH, |
| .opc0 = 3, .opc1 = 0, .opc2 = 4, .crn = 13, .crm = 0, |
| .access = PL1_RW, |
| .fieldoffset = offsetof(CPUARMState, cp15.tpidr_el1), .resetvalue = 0 }, |
| REGINFO_SENTINEL |
| }; |
| |
| #ifndef CONFIG_USER_ONLY |
| |
| static CPAccessResult gt_cntfrq_access(CPUARMState *env, const ARMCPRegInfo *ri) |
| { |
| /* CNTFRQ: not visible from PL0 if both PL0PCTEN and PL0VCTEN are zero */ |
| if (arm_current_el(env) == 0 && !extract32(env->cp15.c14_cntkctl, 0, 2)) { |
| return CP_ACCESS_TRAP; |
| } |
| return CP_ACCESS_OK; |
| } |
| |
| static CPAccessResult gt_counter_access(CPUARMState *env, int timeridx) |
| { |
| /* CNT[PV]CT: not visible from PL0 if ELO[PV]CTEN is zero */ |
| if (arm_current_el(env) == 0 && |
| !extract32(env->cp15.c14_cntkctl, timeridx, 1)) { |
| return CP_ACCESS_TRAP; |
| } |
| return CP_ACCESS_OK; |
| } |
| |
| static CPAccessResult gt_timer_access(CPUARMState *env, int timeridx) |
| { |
| /* CNT[PV]_CVAL, CNT[PV]_CTL, CNT[PV]_TVAL: not visible from PL0 if |
| * EL0[PV]TEN is zero. |
| */ |
| if (arm_current_el(env) == 0 && |
| !extract32(env->cp15.c14_cntkctl, 9 - timeridx, 1)) { |
| return CP_ACCESS_TRAP; |
| } |
| return CP_ACCESS_OK; |
| } |
| |
| static CPAccessResult gt_pct_access(CPUARMState *env, |
| const ARMCPRegInfo *ri) |
| { |
| return gt_counter_access(env, GTIMER_PHYS); |
| } |
| |
| static CPAccessResult gt_vct_access(CPUARMState *env, |
| const ARMCPRegInfo *ri) |
| { |
| return gt_counter_access(env, GTIMER_VIRT); |
| } |
| |
| static CPAccessResult gt_ptimer_access(CPUARMState *env, const ARMCPRegInfo *ri) |
| { |
| return gt_timer_access(env, GTIMER_PHYS); |
| } |
| |
| static CPAccessResult gt_vtimer_access(CPUARMState *env, const ARMCPRegInfo *ri) |
| { |
| return gt_timer_access(env, GTIMER_VIRT); |
| } |
| |
| static uint64_t gt_get_countervalue(CPUARMState *env) |
| { |
| return qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / GTIMER_SCALE; |
| } |
| |
| static void gt_recalc_timer(ARMCPU *cpu, int timeridx) |
| { |
| ARMGenericTimer *gt = &cpu->env.cp15.c14_timer[timeridx]; |
| |
| if (gt->ctl & 1) { |
| /* Timer enabled: calculate and set current ISTATUS, irq, and |
| * reset timer to when ISTATUS next has to change |
| */ |
| uint64_t count = gt_get_countervalue(&cpu->env); |
| /* Note that this must be unsigned 64 bit arithmetic: */ |
| int istatus = count >= gt->cval; |
| uint64_t nexttick; |
| |
| gt->ctl = deposit32(gt->ctl, 2, 1, istatus); |
| qemu_set_irq(cpu->gt_timer_outputs[timeridx], |
| (istatus && !(gt->ctl & 2))); |
| if (istatus) { |
| /* Next transition is when count rolls back over to zero */ |
| nexttick = UINT64_MAX; |
| } else { |
| /* Next transition is when we hit cval */ |
| nexttick = gt->cval; |
| } |
| /* Note that the desired next expiry time might be beyond the |
| * signed-64-bit range of a QEMUTimer -- in this case we just |
| * set the timer for as far in the future as possible. When the |
| * timer expires we will reset the timer for any remaining period. |
| */ |
| if (nexttick > INT64_MAX / GTIMER_SCALE) { |
| nexttick = INT64_MAX / GTIMER_SCALE; |
| } |
| timer_mod(cpu->gt_timer[timeridx], nexttick); |
| } else { |
| /* Timer disabled: ISTATUS and timer output always clear */ |
| gt->ctl &= ~4; |
| qemu_set_irq(cpu->gt_timer_outputs[timeridx], 0); |
| timer_del(cpu->gt_timer[timeridx]); |
| } |
| } |
| |
| static void gt_cnt_reset(CPUARMState *env, const ARMCPRegInfo *ri) |
| { |
| ARMCPU *cpu = arm_env_get_cpu(env); |
| int timeridx = ri->opc1 & 1; |
| |
| timer_del(cpu->gt_timer[timeridx]); |
| } |
| |
| static uint64_t gt_cnt_read(CPUARMState *env, const ARMCPRegInfo *ri) |
| { |
| return gt_get_countervalue(env); |
| } |
| |
| static void gt_cval_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| int timeridx = ri->opc1 & 1; |
| |
| env->cp15.c14_timer[timeridx].cval = value; |
| gt_recalc_timer(arm_env_get_cpu(env), timeridx); |
| } |
| |
| static uint64_t gt_tval_read(CPUARMState *env, const ARMCPRegInfo *ri) |
| { |
| int timeridx = ri->crm & 1; |
| |
| return (uint32_t)(env->cp15.c14_timer[timeridx].cval - |
| gt_get_countervalue(env)); |
| } |
| |
| static void gt_tval_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| int timeridx = ri->crm & 1; |
| |
| env->cp15.c14_timer[timeridx].cval = gt_get_countervalue(env) + |
| + sextract64(value, 0, 32); |
| gt_recalc_timer(arm_env_get_cpu(env), timeridx); |
| } |
| |
| static void gt_ctl_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| ARMCPU *cpu = arm_env_get_cpu(env); |
| int timeridx = ri->crm & 1; |
| uint32_t oldval = env->cp15.c14_timer[timeridx].ctl; |
| |
| env->cp15.c14_timer[timeridx].ctl = deposit64(oldval, 0, 2, value); |
| if ((oldval ^ value) & 1) { |
| /* Enable toggled */ |
| gt_recalc_timer(cpu, timeridx); |
| } else if ((oldval ^ value) & 2) { |
| /* IMASK toggled: don't need to recalculate, |
| * just set the interrupt line based on ISTATUS |
| */ |
| qemu_set_irq(cpu->gt_timer_outputs[timeridx], |
| (oldval & 4) && !(value & 2)); |
| } |
| } |
| |
| void arm_gt_ptimer_cb(void *opaque) |
| { |
| ARMCPU *cpu = opaque; |
| |
| gt_recalc_timer(cpu, GTIMER_PHYS); |
| } |
| |
| void arm_gt_vtimer_cb(void *opaque) |
| { |
| ARMCPU *cpu = opaque; |
| |
| gt_recalc_timer(cpu, GTIMER_VIRT); |
| } |
| |
| static const ARMCPRegInfo generic_timer_cp_reginfo[] = { |
| /* Note that CNTFRQ is purely reads-as-written for the benefit |
| * of software; writing it doesn't actually change the timer frequency. |
| * Our reset value matches the fixed frequency we implement the timer at. |
| */ |
| { .name = "CNTFRQ", .cp = 15, .crn = 14, .crm = 0, .opc1 = 0, .opc2 = 0, |
| .type = ARM_CP_NO_MIGRATE, |
| .access = PL1_RW | PL0_R, .accessfn = gt_cntfrq_access, |
| .fieldoffset = offsetoflow32(CPUARMState, cp15.c14_cntfrq), |
| .resetfn = arm_cp_reset_ignore, |
| }, |
| { .name = "CNTFRQ_EL0", .state = ARM_CP_STATE_AA64, |
| .opc0 = 3, .opc1 = 3, .crn = 14, .crm = 0, .opc2 = 0, |
| .access = PL1_RW | PL0_R, .accessfn = gt_cntfrq_access, |
| .fieldoffset = offsetof(CPUARMState, cp15.c14_cntfrq), |
| .resetvalue = (1000 * 1000 * 1000) / GTIMER_SCALE, |
| }, |
| /* overall control: mostly access permissions */ |
| { .name = "CNTKCTL", .state = ARM_CP_STATE_BOTH, |
| .opc0 = 3, .opc1 = 0, .crn = 14, .crm = 1, .opc2 = 0, |
| .access = PL1_RW, |
| .fieldoffset = offsetof(CPUARMState, cp15.c14_cntkctl), |
| .resetvalue = 0, |
| }, |
| /* per-timer control */ |
| { .name = "CNTP_CTL", .cp = 15, .crn = 14, .crm = 2, .opc1 = 0, .opc2 = 1, |
| .type = ARM_CP_IO | ARM_CP_NO_MIGRATE, .access = PL1_RW | PL0_R, |
| .accessfn = gt_ptimer_access, |
| .fieldoffset = offsetoflow32(CPUARMState, |
| cp15.c14_timer[GTIMER_PHYS].ctl), |
| .resetfn = arm_cp_reset_ignore, |
| .writefn = gt_ctl_write, .raw_writefn = raw_write, |
| }, |
| { .name = "CNTP_CTL_EL0", .state = ARM_CP_STATE_AA64, |
| .opc0 = 3, .opc1 = 3, .crn = 14, .crm = 2, .opc2 = 1, |
| .type = ARM_CP_IO, .access = PL1_RW | PL0_R, |
| .accessfn = gt_ptimer_access, |
| .fieldoffset = offsetof(CPUARMState, cp15.c14_timer[GTIMER_PHYS].ctl), |
| .resetvalue = 0, |
| .writefn = gt_ctl_write, .raw_writefn = raw_write, |
| }, |
| { .name = "CNTV_CTL", .cp = 15, .crn = 14, .crm = 3, .opc1 = 0, .opc2 = 1, |
| .type = ARM_CP_IO | ARM_CP_NO_MIGRATE, .access = PL1_RW | PL0_R, |
| .accessfn = gt_vtimer_access, |
| .fieldoffset = offsetoflow32(CPUARMState, |
| cp15.c14_timer[GTIMER_VIRT].ctl), |
| .resetfn = arm_cp_reset_ignore, |
| .writefn = gt_ctl_write, .raw_writefn = raw_write, |
| }, |
| { .name = "CNTV_CTL_EL0", .state = ARM_CP_STATE_AA64, |
| .opc0 = 3, .opc1 = 3, .crn = 14, .crm = 3, .opc2 = 1, |
| .type = ARM_CP_IO, .access = PL1_RW | PL0_R, |
| .accessfn = gt_vtimer_access, |
| .fieldoffset = offsetof(CPUARMState, cp15.c14_timer[GTIMER_VIRT].ctl), |
| .resetvalue = 0, |
| .writefn = gt_ctl_write, .raw_writefn = raw_write, |
| }, |
| /* TimerValue views: a 32 bit downcounting view of the underlying state */ |
| { .name = "CNTP_TVAL", .cp = 15, .crn = 14, .crm = 2, .opc1 = 0, .opc2 = 0, |
| .type = ARM_CP_NO_MIGRATE | ARM_CP_IO, .access = PL1_RW | PL0_R, |
| .accessfn = gt_ptimer_access, |
| .readfn = gt_tval_read, .writefn = gt_tval_write, |
| }, |
| { .name = "CNTP_TVAL_EL0", .state = ARM_CP_STATE_AA64, |
| .opc0 = 3, .opc1 = 3, .crn = 14, .crm = 2, .opc2 = 0, |
| .type = ARM_CP_NO_MIGRATE | ARM_CP_IO, .access = PL1_RW | PL0_R, |
| .readfn = gt_tval_read, .writefn = gt_tval_write, |
| }, |
| { .name = "CNTV_TVAL", .cp = 15, .crn = 14, .crm = 3, .opc1 = 0, .opc2 = 0, |
| .type = ARM_CP_NO_MIGRATE | ARM_CP_IO, .access = PL1_RW | PL0_R, |
| .accessfn = gt_vtimer_access, |
| .readfn = gt_tval_read, .writefn = gt_tval_write, |
| }, |
| { .name = "CNTV_TVAL_EL0", .state = ARM_CP_STATE_AA64, |
| .opc0 = 3, .opc1 = 3, .crn = 14, .crm = 3, .opc2 = 0, |
| .type = ARM_CP_NO_MIGRATE | ARM_CP_IO, .access = PL1_RW | PL0_R, |
| .readfn = gt_tval_read, .writefn = gt_tval_write, |
| }, |
| /* The counter itself */ |
| { .name = "CNTPCT", .cp = 15, .crm = 14, .opc1 = 0, |
| .access = PL0_R, .type = ARM_CP_64BIT | ARM_CP_NO_MIGRATE | ARM_CP_IO, |
| .accessfn = gt_pct_access, |
| .readfn = gt_cnt_read, .resetfn = arm_cp_reset_ignore, |
| }, |
| { .name = "CNTPCT_EL0", .state = ARM_CP_STATE_AA64, |
| .opc0 = 3, .opc1 = 3, .crn = 14, .crm = 0, .opc2 = 1, |
| .access = PL0_R, .type = ARM_CP_NO_MIGRATE | ARM_CP_IO, |
| .accessfn = gt_pct_access, |
| .readfn = gt_cnt_read, .resetfn = gt_cnt_reset, |
| }, |
| { .name = "CNTVCT", .cp = 15, .crm = 14, .opc1 = 1, |
| .access = PL0_R, .type = ARM_CP_64BIT | ARM_CP_NO_MIGRATE | ARM_CP_IO, |
| .accessfn = gt_vct_access, |
| .readfn = gt_cnt_read, .resetfn = arm_cp_reset_ignore, |
| }, |
| { .name = "CNTVCT_EL0", .state = ARM_CP_STATE_AA64, |
| .opc0 = 3, .opc1 = 3, .crn = 14, .crm = 0, .opc2 = 2, |
| .access = PL0_R, .type = ARM_CP_NO_MIGRATE | ARM_CP_IO, |
| .accessfn = gt_vct_access, |
| .readfn = gt_cnt_read, .resetfn = gt_cnt_reset, |
| }, |
| /* Comparison value, indicating when the timer goes off */ |
| { .name = "CNTP_CVAL", .cp = 15, .crm = 14, .opc1 = 2, |
| .access = PL1_RW | PL0_R, |
| .type = ARM_CP_64BIT | ARM_CP_IO | ARM_CP_NO_MIGRATE, |
| .fieldoffset = offsetof(CPUARMState, cp15.c14_timer[GTIMER_PHYS].cval), |
| .accessfn = gt_ptimer_access, .resetfn = arm_cp_reset_ignore, |
| .writefn = gt_cval_write, .raw_writefn = raw_write, |
| }, |
| { .name = "CNTP_CVAL_EL0", .state = ARM_CP_STATE_AA64, |
| .opc0 = 3, .opc1 = 3, .crn = 14, .crm = 2, .opc2 = 2, |
| .access = PL1_RW | PL0_R, |
| .type = ARM_CP_IO, |
| .fieldoffset = offsetof(CPUARMState, cp15.c14_timer[GTIMER_PHYS].cval), |
| .resetvalue = 0, .accessfn = gt_vtimer_access, |
| .writefn = gt_cval_write, .raw_writefn = raw_write, |
| }, |
| { .name = "CNTV_CVAL", .cp = 15, .crm = 14, .opc1 = 3, |
| .access = PL1_RW | PL0_R, |
| .type = ARM_CP_64BIT | ARM_CP_IO | ARM_CP_NO_MIGRATE, |
| .fieldoffset = offsetof(CPUARMState, cp15.c14_timer[GTIMER_VIRT].cval), |
| .accessfn = gt_vtimer_access, .resetfn = arm_cp_reset_ignore, |
| .writefn = gt_cval_write, .raw_writefn = raw_write, |
| }, |
| { .name = "CNTV_CVAL_EL0", .state = ARM_CP_STATE_AA64, |
| .opc0 = 3, .opc1 = 3, .crn = 14, .crm = 3, .opc2 = 2, |
| .access = PL1_RW | PL0_R, |
| .type = ARM_CP_IO, |
| .fieldoffset = offsetof(CPUARMState, cp15.c14_timer[GTIMER_VIRT].cval), |
| .resetvalue = 0, .accessfn = gt_vtimer_access, |
| .writefn = gt_cval_write, .raw_writefn = raw_write, |
| }, |
| REGINFO_SENTINEL |
| }; |
| |
| #else |
| /* In user-mode none of the generic timer registers are accessible, |
| * and their implementation depends on QEMU_CLOCK_VIRTUAL and qdev gpio outputs, |
| * so instead just don't register any of them. |
| */ |
| static const ARMCPRegInfo generic_timer_cp_reginfo[] = { |
| REGINFO_SENTINEL |
| }; |
| |
| #endif |
| |
| static void par_write(CPUARMState *env, const ARMCPRegInfo *ri, uint64_t value) |
| { |
| if (arm_feature(env, ARM_FEATURE_LPAE)) { |
| raw_write(env, ri, value); |
| } else if (arm_feature(env, ARM_FEATURE_V7)) { |
| raw_write(env, ri, value & 0xfffff6ff); |
| } else { |
| raw_write(env, ri, value & 0xfffff1ff); |
| } |
| } |
| |
| #ifndef CONFIG_USER_ONLY |
| /* get_phys_addr() isn't present for user-mode-only targets */ |
| |
| static CPAccessResult ats_access(CPUARMState *env, const ARMCPRegInfo *ri) |
| { |
| if (ri->opc2 & 4) { |
| /* Other states are only available with TrustZone; in |
| * a non-TZ implementation these registers don't exist |
| * at all, which is an Uncategorized trap. This underdecoding |
| * is safe because the reginfo is NO_MIGRATE. |
| */ |
| return CP_ACCESS_TRAP_UNCATEGORIZED; |
| } |
| return CP_ACCESS_OK; |
| } |
| |
| static void ats_write(CPUARMState *env, const ARMCPRegInfo *ri, uint64_t value) |
| { |
| hwaddr phys_addr; |
| target_ulong page_size; |
| int prot; |
| int ret, is_user = ri->opc2 & 2; |
| int access_type = ri->opc2 & 1; |
| |
| ret = get_phys_addr(env, value, access_type, is_user, |
| &phys_addr, &prot, &page_size); |
| if (extended_addresses_enabled(env)) { |
| /* ret is a DFSR/IFSR value for the long descriptor |
| * translation table format, but with WnR always clear. |
| * Convert it to a 64-bit PAR. |
| */ |
| uint64_t par64 = (1 << 11); /* LPAE bit always set */ |
| if (ret == 0) { |
| par64 |= phys_addr & ~0xfffULL; |
| /* We don't set the ATTR or SH fields in the PAR. */ |
| } else { |
| par64 |= 1; /* F */ |
| par64 |= (ret & 0x3f) << 1; /* FS */ |
| /* Note that S2WLK and FSTAGE are always zero, because we don't |
| * implement virtualization and therefore there can't be a stage 2 |
| * fault. |
| */ |
| } |
| env->cp15.par_el1 = par64; |
| } else { |
| /* ret is a DFSR/IFSR value for the short descriptor |
| * translation table format (with WnR always clear). |
| * Convert it to a 32-bit PAR. |
| */ |
| if (ret == 0) { |
| /* We do not set any attribute bits in the PAR */ |
| if (page_size == (1 << 24) |
| && arm_feature(env, ARM_FEATURE_V7)) { |
| env->cp15.par_el1 = (phys_addr & 0xff000000) | 1 << 1; |
| } else { |
| env->cp15.par_el1 = phys_addr & 0xfffff000; |
| } |
| } else { |
| env->cp15.par_el1 = ((ret & (1 << 10)) >> 5) | |
| ((ret & (1 << 12)) >> 6) | |
| ((ret & 0xf) << 1) | 1; |
| } |
| } |
| } |
| #endif |
| |
| static const ARMCPRegInfo vapa_cp_reginfo[] = { |
| { .name = "PAR", .cp = 15, .crn = 7, .crm = 4, .opc1 = 0, .opc2 = 0, |
| .access = PL1_RW, .resetvalue = 0, |
| .fieldoffset = offsetoflow32(CPUARMState, cp15.par_el1), |
| .writefn = par_write }, |
| #ifndef CONFIG_USER_ONLY |
| { .name = "ATS", .cp = 15, .crn = 7, .crm = 8, .opc1 = 0, .opc2 = CP_ANY, |
| .access = PL1_W, .accessfn = ats_access, |
| .writefn = ats_write, .type = ARM_CP_NO_MIGRATE }, |
| #endif |
| REGINFO_SENTINEL |
| }; |
| |
| /* Return basic MPU access permission bits. */ |
| static uint32_t simple_mpu_ap_bits(uint32_t val) |
| { |
| uint32_t ret; |
| uint32_t mask; |
| int i; |
| ret = 0; |
| mask = 3; |
| for (i = 0; i < 16; i += 2) { |
| ret |= (val >> i) & mask; |
| mask <<= 2; |
| } |
| return ret; |
| } |
| |
| /* Pad basic MPU access permission bits to extended format. */ |
| static uint32_t extended_mpu_ap_bits(uint32_t val) |
| { |
| uint32_t ret; |
| uint32_t mask; |
| int i; |
| ret = 0; |
| mask = 3; |
| for (i = 0; i < 16; i += 2) { |
| ret |= (val & mask) << i; |
| mask <<= 2; |
| } |
| return ret; |
| } |
| |
| static void pmsav5_data_ap_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| env->cp15.pmsav5_data_ap = extended_mpu_ap_bits(value); |
| } |
| |
| static uint64_t pmsav5_data_ap_read(CPUARMState *env, const ARMCPRegInfo *ri) |
| { |
| return simple_mpu_ap_bits(env->cp15.pmsav5_data_ap); |
| } |
| |
| static void pmsav5_insn_ap_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| env->cp15.pmsav5_insn_ap = extended_mpu_ap_bits(value); |
| } |
| |
| static uint64_t pmsav5_insn_ap_read(CPUARMState *env, const ARMCPRegInfo *ri) |
| { |
| return simple_mpu_ap_bits(env->cp15.pmsav5_insn_ap); |
| } |
| |
| static const ARMCPRegInfo pmsav5_cp_reginfo[] = { |
| { .name = "DATA_AP", .cp = 15, .crn = 5, .crm = 0, .opc1 = 0, .opc2 = 0, |
| .access = PL1_RW, .type = ARM_CP_NO_MIGRATE, |
| .fieldoffset = offsetof(CPUARMState, cp15.pmsav5_data_ap), |
| .resetvalue = 0, |
| .readfn = pmsav5_data_ap_read, .writefn = pmsav5_data_ap_write, }, |
| { .name = "INSN_AP", .cp = 15, .crn = 5, .crm = 0, .opc1 = 0, .opc2 = 1, |
| .access = PL1_RW, .type = ARM_CP_NO_MIGRATE, |
| .fieldoffset = offsetof(CPUARMState, cp15.pmsav5_insn_ap), |
| .resetvalue = 0, |
| .readfn = pmsav5_insn_ap_read, .writefn = pmsav5_insn_ap_write, }, |
| { .name = "DATA_EXT_AP", .cp = 15, .crn = 5, .crm = 0, .opc1 = 0, .opc2 = 2, |
| .access = PL1_RW, |
| .fieldoffset = offsetof(CPUARMState, cp15.pmsav5_data_ap), |
| .resetvalue = 0, }, |
| { .name = "INSN_EXT_AP", .cp = 15, .crn = 5, .crm = 0, .opc1 = 0, .opc2 = 3, |
| .access = PL1_RW, |
| .fieldoffset = offsetof(CPUARMState, cp15.pmsav5_insn_ap), |
| .resetvalue = 0, }, |
| { .name = "DCACHE_CFG", .cp = 15, .crn = 2, .crm = 0, .opc1 = 0, .opc2 = 0, |
| .access = PL1_RW, |
| .fieldoffset = offsetof(CPUARMState, cp15.c2_data), .resetvalue = 0, }, |
| { .name = "ICACHE_CFG", .cp = 15, .crn = 2, .crm = 0, .opc1 = 0, .opc2 = 1, |
| .access = PL1_RW, |
| .fieldoffset = offsetof(CPUARMState, cp15.c2_insn), .resetvalue = 0, }, |
| /* Protection region base and size registers */ |
| { .name = "946_PRBS0", .cp = 15, .crn = 6, .crm = 0, .opc1 = 0, |
| .opc2 = CP_ANY, .access = PL1_RW, .resetvalue = 0, |
| .fieldoffset = offsetof(CPUARMState, cp15.c6_region[0]) }, |
| { .name = "946_PRBS1", .cp = 15, .crn = 6, .crm = 1, .opc1 = 0, |
| .opc2 = CP_ANY, .access = PL1_RW, .resetvalue = 0, |
| .fieldoffset = offsetof(CPUARMState, cp15.c6_region[1]) }, |
| { .name = "946_PRBS2", .cp = 15, .crn = 6, .crm = 2, .opc1 = 0, |
| .opc2 = CP_ANY, .access = PL1_RW, .resetvalue = 0, |
| .fieldoffset = offsetof(CPUARMState, cp15.c6_region[2]) }, |
| { .name = "946_PRBS3", .cp = 15, .crn = 6, .crm = 3, .opc1 = 0, |
| .opc2 = CP_ANY, .access = PL1_RW, .resetvalue = 0, |
| .fieldoffset = offsetof(CPUARMState, cp15.c6_region[3]) }, |
| { .name = "946_PRBS4", .cp = 15, .crn = 6, .crm = 4, .opc1 = 0, |
| .opc2 = CP_ANY, .access = PL1_RW, .resetvalue = 0, |
| .fieldoffset = offsetof(CPUARMState, cp15.c6_region[4]) }, |
| { .name = "946_PRBS5", .cp = 15, .crn = 6, .crm = 5, .opc1 = 0, |
| .opc2 = CP_ANY, .access = PL1_RW, .resetvalue = 0, |
| .fieldoffset = offsetof(CPUARMState, cp15.c6_region[5]) }, |
| { .name = "946_PRBS6", .cp = 15, .crn = 6, .crm = 6, .opc1 = 0, |
| .opc2 = CP_ANY, .access = PL1_RW, .resetvalue = 0, |
| .fieldoffset = offsetof(CPUARMState, cp15.c6_region[6]) }, |
| { .name = "946_PRBS7", .cp = 15, .crn = 6, .crm = 7, .opc1 = 0, |
| .opc2 = CP_ANY, .access = PL1_RW, .resetvalue = 0, |
| .fieldoffset = offsetof(CPUARMState, cp15.c6_region[7]) }, |
| REGINFO_SENTINEL |
| }; |
| |
| static void vmsa_ttbcr_raw_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| int maskshift = extract32(value, 0, 3); |
| |
| if (!arm_feature(env, ARM_FEATURE_V8)) { |
| if (arm_feature(env, ARM_FEATURE_LPAE) && (value & TTBCR_EAE)) { |
| /* Pre ARMv8 bits [21:19], [15:14] and [6:3] are UNK/SBZP when |
| * using Long-desciptor translation table format */ |
| value &= ~((7 << 19) | (3 << 14) | (0xf << 3)); |
| } else if (arm_feature(env, ARM_FEATURE_EL3)) { |
| /* In an implementation that includes the Security Extensions |
| * TTBCR has additional fields PD0 [4] and PD1 [5] for |
| * Short-descriptor translation table format. |
| */ |
| value &= TTBCR_PD1 | TTBCR_PD0 | TTBCR_N; |
| } else { |
| value &= TTBCR_N; |
| } |
| } |
| |
| /* Note that we always calculate c2_mask and c2_base_mask, but |
| * they are only used for short-descriptor tables (ie if EAE is 0); |
| * for long-descriptor tables the TTBCR fields are used differently |
| * and the c2_mask and c2_base_mask values are meaningless. |
| */ |
| raw_write(env, ri, value); |
| env->cp15.c2_mask = ~(((uint32_t)0xffffffffu) >> maskshift); |
| env->cp15.c2_base_mask = ~((uint32_t)0x3fffu >> maskshift); |
| } |
| |
| static void vmsa_ttbcr_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| ARMCPU *cpu = arm_env_get_cpu(env); |
| |
| if (arm_feature(env, ARM_FEATURE_LPAE)) { |
| /* With LPAE the TTBCR could result in a change of ASID |
| * via the TTBCR.A1 bit, so do a TLB flush. |
| */ |
| tlb_flush(CPU(cpu), 1); |
| } |
| vmsa_ttbcr_raw_write(env, ri, value); |
| } |
| |
| static void vmsa_ttbcr_reset(CPUARMState *env, const ARMCPRegInfo *ri) |
| { |
| env->cp15.c2_base_mask = 0xffffc000u; |
| raw_write(env, ri, 0); |
| env->cp15.c2_mask = 0; |
| } |
| |
| static void vmsa_tcr_el1_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| ARMCPU *cpu = arm_env_get_cpu(env); |
| |
| /* For AArch64 the A1 bit could result in a change of ASID, so TLB flush. */ |
| tlb_flush(CPU(cpu), 1); |
| raw_write(env, ri, value); |
| } |
| |
| static void vmsa_ttbr_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| /* 64 bit accesses to the TTBRs can change the ASID and so we |
| * must flush the TLB. |
| */ |
| if (cpreg_field_is_64bit(ri)) { |
| ARMCPU *cpu = arm_env_get_cpu(env); |
| |
| tlb_flush(CPU(cpu), 1); |
| } |
| raw_write(env, ri, value); |
| } |
| |
| static const ARMCPRegInfo vmsa_cp_reginfo[] = { |
| { .name = "DFSR", .cp = 15, .crn = 5, .crm = 0, .opc1 = 0, .opc2 = 0, |
| .access = PL1_RW, .type = ARM_CP_NO_MIGRATE, |
| .fieldoffset = offsetoflow32(CPUARMState, cp15.esr_el[1]), |
| .resetfn = arm_cp_reset_ignore, }, |
| { .name = "IFSR", .cp = 15, .crn = 5, .crm = 0, .opc1 = 0, .opc2 = 1, |
| .access = PL1_RW, |
| .fieldoffset = offsetof(CPUARMState, cp15.ifsr_el2), .resetvalue = 0, }, |
| { .name = "ESR_EL1", .state = ARM_CP_STATE_AA64, |
| .opc0 = 3, .crn = 5, .crm = 2, .opc1 = 0, .opc2 = 0, |
| .access = PL1_RW, |
| .fieldoffset = offsetof(CPUARMState, cp15.esr_el[1]), .resetvalue = 0, }, |
| { .name = "TTBR0_EL1", .state = ARM_CP_STATE_BOTH, |
| .opc0 = 3, .crn = 2, .crm = 0, .opc1 = 0, .opc2 = 0, |
| .access = PL1_RW, .fieldoffset = offsetof(CPUARMState, cp15.ttbr0_el1), |
| .writefn = vmsa_ttbr_write, .resetvalue = 0 }, |
| { .name = "TTBR1_EL1", .state = ARM_CP_STATE_BOTH, |
| .opc0 = 3, .crn = 2, .crm = 0, .opc1 = 0, .opc2 = 1, |
| .access = PL1_RW, .fieldoffset = offsetof(CPUARMState, cp15.ttbr1_el1), |
| .writefn = vmsa_ttbr_write, .resetvalue = 0 }, |
| { .name = "TCR_EL1", .state = ARM_CP_STATE_AA64, |
| .opc0 = 3, .crn = 2, .crm = 0, .opc1 = 0, .opc2 = 2, |
| .access = PL1_RW, .writefn = vmsa_tcr_el1_write, |
| .resetfn = vmsa_ttbcr_reset, .raw_writefn = raw_write, |
| .fieldoffset = offsetof(CPUARMState, cp15.c2_control) }, |
| { .name = "TTBCR", .cp = 15, .crn = 2, .crm = 0, .opc1 = 0, .opc2 = 2, |
| .access = PL1_RW, .type = ARM_CP_NO_MIGRATE, .writefn = vmsa_ttbcr_write, |
| .resetfn = arm_cp_reset_ignore, .raw_writefn = vmsa_ttbcr_raw_write, |
| .fieldoffset = offsetoflow32(CPUARMState, cp15.c2_control) }, |
| /* 64-bit FAR; this entry also gives us the AArch32 DFAR */ |
| { .name = "FAR_EL1", .state = ARM_CP_STATE_BOTH, |
| .opc0 = 3, .crn = 6, .crm = 0, .opc1 = 0, .opc2 = 0, |
| .access = PL1_RW, .fieldoffset = offsetof(CPUARMState, cp15.far_el[1]), |
| .resetvalue = 0, }, |
| REGINFO_SENTINEL |
| }; |
| |
| static void omap_ticonfig_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| env->cp15.c15_ticonfig = value & 0xe7; |
| /* The OS_TYPE bit in this register changes the reported CPUID! */ |
| env->cp15.c0_cpuid = (value & (1 << 5)) ? |
| ARM_CPUID_TI915T : ARM_CPUID_TI925T; |
| } |
| |
| static void omap_threadid_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| env->cp15.c15_threadid = value & 0xffff; |
| } |
| |
| static void omap_wfi_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| /* Wait-for-interrupt (deprecated) */ |
| cpu_interrupt(CPU(arm_env_get_cpu(env)), CPU_INTERRUPT_HALT); |
| } |
| |
| static void omap_cachemaint_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| /* On OMAP there are registers indicating the max/min index of dcache lines |
| * containing a dirty line; cache flush operations have to reset these. |
| */ |
| env->cp15.c15_i_max = 0x000; |
| env->cp15.c15_i_min = 0xff0; |
| } |
| |
| static const ARMCPRegInfo omap_cp_reginfo[] = { |
| { .name = "DFSR", .cp = 15, .crn = 5, .crm = CP_ANY, |
| .opc1 = CP_ANY, .opc2 = CP_ANY, .access = PL1_RW, .type = ARM_CP_OVERRIDE, |
| .fieldoffset = offsetoflow32(CPUARMState, cp15.esr_el[1]), |
| .resetvalue = 0, }, |
| { .name = "", .cp = 15, .crn = 15, .crm = 0, .opc1 = 0, .opc2 = 0, |
| .access = PL1_RW, .type = ARM_CP_NOP }, |
| { .name = "TICONFIG", .cp = 15, .crn = 15, .crm = 1, .opc1 = 0, .opc2 = 0, |
| .access = PL1_RW, |
| .fieldoffset = offsetof(CPUARMState, cp15.c15_ticonfig), .resetvalue = 0, |
| .writefn = omap_ticonfig_write }, |
| { .name = "IMAX", .cp = 15, .crn = 15, .crm = 2, .opc1 = 0, .opc2 = 0, |
| .access = PL1_RW, |
| .fieldoffset = offsetof(CPUARMState, cp15.c15_i_max), .resetvalue = 0, }, |
| { .name = "IMIN", .cp = 15, .crn = 15, .crm = 3, .opc1 = 0, .opc2 = 0, |
| .access = PL1_RW, .resetvalue = 0xff0, |
| .fieldoffset = offsetof(CPUARMState, cp15.c15_i_min) }, |
| { .name = "THREADID", .cp = 15, .crn = 15, .crm = 4, .opc1 = 0, .opc2 = 0, |
| .access = PL1_RW, |
| .fieldoffset = offsetof(CPUARMState, cp15.c15_threadid), .resetvalue = 0, |
| .writefn = omap_threadid_write }, |
| { .name = "TI925T_STATUS", .cp = 15, .crn = 15, |
| .crm = 8, .opc1 = 0, .opc2 = 0, .access = PL1_RW, |
| .type = ARM_CP_NO_MIGRATE, |
| .readfn = arm_cp_read_zero, .writefn = omap_wfi_write, }, |
| /* TODO: Peripheral port remap register: |
| * On OMAP2 mcr p15, 0, rn, c15, c2, 4 sets up the interrupt controller |
| * base address at $rn & ~0xfff and map size of 0x200 << ($rn & 0xfff), |
| * when MMU is off. |
| */ |
| { .name = "OMAP_CACHEMAINT", .cp = 15, .crn = 7, .crm = CP_ANY, |
| .opc1 = 0, .opc2 = CP_ANY, .access = PL1_W, |
| .type = ARM_CP_OVERRIDE | ARM_CP_NO_MIGRATE, |
| .writefn = omap_cachemaint_write }, |
| { .name = "C9", .cp = 15, .crn = 9, |
| .crm = CP_ANY, .opc1 = CP_ANY, .opc2 = CP_ANY, .access = PL1_RW, |
| .type = ARM_CP_CONST | ARM_CP_OVERRIDE, .resetvalue = 0 }, |
| REGINFO_SENTINEL |
| }; |
| |
| static void xscale_cpar_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| env->cp15.c15_cpar = value & 0x3fff; |
| } |
| |
| static const ARMCPRegInfo xscale_cp_reginfo[] = { |
| { .name = "XSCALE_CPAR", |
| .cp = 15, .crn = 15, .crm = 1, .opc1 = 0, .opc2 = 0, .access = PL1_RW, |
| .fieldoffset = offsetof(CPUARMState, cp15.c15_cpar), .resetvalue = 0, |
| .writefn = xscale_cpar_write, }, |
| { .name = "XSCALE_AUXCR", |
| .cp = 15, .crn = 1, .crm = 0, .opc1 = 0, .opc2 = 1, .access = PL1_RW, |
| .fieldoffset = offsetof(CPUARMState, cp15.c1_xscaleauxcr), |
| .resetvalue = 0, }, |
| /* XScale specific cache-lockdown: since we have no cache we NOP these |
| * and hope the guest does not really rely on cache behaviour. |
| */ |
| { .name = "XSCALE_LOCK_ICACHE_LINE", |
| .cp = 15, .opc1 = 0, .crn = 9, .crm = 1, .opc2 = 0, |
| .access = PL1_W, .type = ARM_CP_NOP }, |
| { .name = "XSCALE_UNLOCK_ICACHE", |
| .cp = 15, .opc1 = 0, .crn = 9, .crm = 1, .opc2 = 1, |
| .access = PL1_W, .type = ARM_CP_NOP }, |
| { .name = "XSCALE_DCACHE_LOCK", |
| .cp = 15, .opc1 = 0, .crn = 9, .crm = 2, .opc2 = 0, |
| .access = PL1_RW, .type = ARM_CP_NOP }, |
| { .name = "XSCALE_UNLOCK_DCACHE", |
| .cp = 15, .opc1 = 0, .crn = 9, .crm = 2, .opc2 = 1, |
| .access = PL1_W, .type = ARM_CP_NOP }, |
| REGINFO_SENTINEL |
| }; |
| |
| static const ARMCPRegInfo dummy_c15_cp_reginfo[] = { |
| /* RAZ/WI the whole crn=15 space, when we don't have a more specific |
| * implementation of this implementation-defined space. |
| * Ideally this should eventually disappear in favour of actually |
| * implementing the correct behaviour for all cores. |
| */ |
| { .name = "C15_IMPDEF", .cp = 15, .crn = 15, |
| .crm = CP_ANY, .opc1 = CP_ANY, .opc2 = CP_ANY, |
| .access = PL1_RW, |
| .type = ARM_CP_CONST | ARM_CP_NO_MIGRATE | ARM_CP_OVERRIDE, |
| .resetvalue = 0 }, |
| REGINFO_SENTINEL |
| }; |
| |
| static const ARMCPRegInfo cache_dirty_status_cp_reginfo[] = { |
| /* Cache status: RAZ because we have no cache so it's always clean */ |
| { .name = "CDSR", .cp = 15, .crn = 7, .crm = 10, .opc1 = 0, .opc2 = 6, |
| .access = PL1_R, .type = ARM_CP_CONST | ARM_CP_NO_MIGRATE, |
| .resetvalue = 0 }, |
| REGINFO_SENTINEL |
| }; |
| |
| static const ARMCPRegInfo cache_block_ops_cp_reginfo[] = { |
| /* We never have a a block transfer operation in progress */ |
| { .name = "BXSR", .cp = 15, .crn = 7, .crm = 12, .opc1 = 0, .opc2 = 4, |
| .access = PL0_R, .type = ARM_CP_CONST | ARM_CP_NO_MIGRATE, |
| .resetvalue = 0 }, |
| /* The cache ops themselves: these all NOP for QEMU */ |
| { .name = "IICR", .cp = 15, .crm = 5, .opc1 = 0, |
| .access = PL1_W, .type = ARM_CP_NOP|ARM_CP_64BIT }, |
| { .name = "IDCR", .cp = 15, .crm = 6, .opc1 = 0, |
| .access = PL1_W, .type = ARM_CP_NOP|ARM_CP_64BIT }, |
| { .name = "CDCR", .cp = 15, .crm = 12, .opc1 = 0, |
| .access = PL0_W, .type = ARM_CP_NOP|ARM_CP_64BIT }, |
| { .name = "PIR", .cp = 15, .crm = 12, .opc1 = 1, |
| .access = PL0_W, .type = ARM_CP_NOP|ARM_CP_64BIT }, |
| { .name = "PDR", .cp = 15, .crm = 12, .opc1 = 2, |
| .access = PL0_W, .type = ARM_CP_NOP|ARM_CP_64BIT }, |
| { .name = "CIDCR", .cp = 15, .crm = 14, .opc1 = 0, |
| .access = PL1_W, .type = ARM_CP_NOP|ARM_CP_64BIT }, |
| REGINFO_SENTINEL |
| }; |
| |
| static const ARMCPRegInfo cache_test_clean_cp_reginfo[] = { |
| /* The cache test-and-clean instructions always return (1 << 30) |
| * to indicate that there are no dirty cache lines. |
| */ |
| { .name = "TC_DCACHE", .cp = 15, .crn = 7, .crm = 10, .opc1 = 0, .opc2 = 3, |
| .access = PL0_R, .type = ARM_CP_CONST | ARM_CP_NO_MIGRATE, |
| .resetvalue = (1 << 30) }, |
| { .name = "TCI_DCACHE", .cp = 15, .crn = 7, .crm = 14, .opc1 = 0, .opc2 = 3, |
| .access = PL0_R, .type = ARM_CP_CONST | ARM_CP_NO_MIGRATE, |
| .resetvalue = (1 << 30) }, |
| REGINFO_SENTINEL |
| }; |
| |
| static const ARMCPRegInfo strongarm_cp_reginfo[] = { |
| /* Ignore ReadBuffer accesses */ |
| { .name = "C9_READBUFFER", .cp = 15, .crn = 9, |
| .crm = CP_ANY, .opc1 = CP_ANY, .opc2 = CP_ANY, |
| .access = PL1_RW, .resetvalue = 0, |
| .type = ARM_CP_CONST | ARM_CP_OVERRIDE | ARM_CP_NO_MIGRATE }, |
| REGINFO_SENTINEL |
| }; |
| |
| static uint64_t mpidr_read(CPUARMState *env, const ARMCPRegInfo *ri) |
| { |
| CPUState *cs = CPU(arm_env_get_cpu(env)); |
| uint32_t mpidr = cs->cpu_index; |
| /* We don't support setting cluster ID ([8..11]) (known as Aff1 |
| * in later ARM ARM versions), or any of the higher affinity level fields, |
| * so these bits always RAZ. |
| */ |
| if (arm_feature(env, ARM_FEATURE_V7MP)) { |
| mpidr |= (1U << 31); |
| /* Cores which are uniprocessor (non-coherent) |
| * but still implement the MP extensions set |
| * bit 30. (For instance, A9UP.) However we do |
| * not currently model any of those cores. |
| */ |
| } |
| return mpidr; |
| } |
| |
| static const ARMCPRegInfo mpidr_cp_reginfo[] = { |
| { .name = "MPIDR", .state = ARM_CP_STATE_BOTH, |
| .opc0 = 3, .crn = 0, .crm = 0, .opc1 = 0, .opc2 = 5, |
| .access = PL1_R, .readfn = mpidr_read, .type = ARM_CP_NO_MIGRATE }, |
| REGINFO_SENTINEL |
| }; |
| |
| static const ARMCPRegInfo lpae_cp_reginfo[] = { |
| /* NOP AMAIR0/1: the override is because these clash with the rather |
| * broadly specified TLB_LOCKDOWN entry in the generic cp_reginfo. |
| */ |
| { .name = "AMAIR0", .state = ARM_CP_STATE_BOTH, |
| .opc0 = 3, .crn = 10, .crm = 3, .opc1 = 0, .opc2 = 0, |
| .access = PL1_RW, .type = ARM_CP_CONST | ARM_CP_OVERRIDE, |
| .resetvalue = 0 }, |
| /* AMAIR1 is mapped to AMAIR_EL1[63:32] */ |
| { .name = "AMAIR1", .cp = 15, .crn = 10, .crm = 3, .opc1 = 0, .opc2 = 1, |
| .access = PL1_RW, .type = ARM_CP_CONST | ARM_CP_OVERRIDE, |
| .resetvalue = 0 }, |
| { .name = "PAR", .cp = 15, .crm = 7, .opc1 = 0, |
| .access = PL1_RW, .type = ARM_CP_64BIT, |
| .fieldoffset = offsetof(CPUARMState, cp15.par_el1), .resetvalue = 0 }, |
| { .name = "TTBR0", .cp = 15, .crm = 2, .opc1 = 0, |
| .access = PL1_RW, .type = ARM_CP_64BIT | ARM_CP_NO_MIGRATE, |
| .fieldoffset = offsetof(CPUARMState, cp15.ttbr0_el1), |
| .writefn = vmsa_ttbr_write, .resetfn = arm_cp_reset_ignore }, |
| { .name = "TTBR1", .cp = 15, .crm = 2, .opc1 = 1, |
| .access = PL1_RW, .type = ARM_CP_64BIT | ARM_CP_NO_MIGRATE, |
| .fieldoffset = offsetof(CPUARMState, cp15.ttbr1_el1), |
| .writefn = vmsa_ttbr_write, .resetfn = arm_cp_reset_ignore }, |
| REGINFO_SENTINEL |
| }; |
| |
| static uint64_t aa64_fpcr_read(CPUARMState *env, const ARMCPRegInfo *ri) |
| { |
| return vfp_get_fpcr(env); |
| } |
| |
| static void aa64_fpcr_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| vfp_set_fpcr(env, value); |
| } |
| |
| static uint64_t aa64_fpsr_read(CPUARMState *env, const ARMCPRegInfo *ri) |
| { |
| return vfp_get_fpsr(env); |
| } |
| |
| static void aa64_fpsr_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| vfp_set_fpsr(env, value); |
| } |
| |
| static CPAccessResult aa64_daif_access(CPUARMState *env, const ARMCPRegInfo *ri) |
| { |
| if (arm_current_el(env) == 0 && !(env->cp15.c1_sys & SCTLR_UMA)) { |
| return CP_ACCESS_TRAP; |
| } |
| return CP_ACCESS_OK; |
| } |
| |
| static void aa64_daif_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| env->daif = value & PSTATE_DAIF; |
| } |
| |
| static CPAccessResult aa64_cacheop_access(CPUARMState *env, |
| const ARMCPRegInfo *ri) |
| { |
| /* Cache invalidate/clean: NOP, but EL0 must UNDEF unless |
| * SCTLR_EL1.UCI is set. |
| */ |
| if (arm_current_el(env) == 0 && !(env->cp15.c1_sys & SCTLR_UCI)) { |
| return CP_ACCESS_TRAP; |
| } |
| return CP_ACCESS_OK; |
| } |
| |
| /* See: D4.7.2 TLB maintenance requirements and the TLB maintenance instructions |
| * Page D4-1736 (DDI0487A.b) |
| */ |
| |
| static void tlbi_aa64_va_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| /* Invalidate by VA (AArch64 version) */ |
| ARMCPU *cpu = arm_env_get_cpu(env); |
| uint64_t pageaddr = sextract64(value << 12, 0, 56); |
| |
| tlb_flush_page(CPU(cpu), pageaddr); |
| } |
| |
| static void tlbi_aa64_vaa_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| /* Invalidate by VA, all ASIDs (AArch64 version) */ |
| ARMCPU *cpu = arm_env_get_cpu(env); |
| uint64_t pageaddr = sextract64(value << 12, 0, 56); |
| |
| tlb_flush_page(CPU(cpu), pageaddr); |
| } |
| |
| static void tlbi_aa64_asid_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| /* Invalidate by ASID (AArch64 version) */ |
| ARMCPU *cpu = arm_env_get_cpu(env); |
| int asid = extract64(value, 48, 16); |
| tlb_flush(CPU(cpu), asid == 0); |
| } |
| |
| static void tlbi_aa64_va_is_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| CPUState *other_cs; |
| uint64_t pageaddr = sextract64(value << 12, 0, 56); |
| |
| CPU_FOREACH(other_cs) { |
| tlb_flush_page(other_cs, pageaddr); |
| } |
| } |
| |
| static void tlbi_aa64_vaa_is_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| CPUState *other_cs; |
| uint64_t pageaddr = sextract64(value << 12, 0, 56); |
| |
| CPU_FOREACH(other_cs) { |
| tlb_flush_page(other_cs, pageaddr); |
| } |
| } |
| |
| static void tlbi_aa64_asid_is_write(CPUARMState *env, const ARMCPRegInfo *ri, |
| uint64_t value) |
| { |
| CPUState *other_cs; |
| int asid = extract64(value, 48, 16); |
| |
| CPU_FOREACH(other_cs) { |
| tlb_flush(other_cs, asid == 0); |
| } |
| } |
| |
| static CPAccessResult aa64_zva_access(CPUARMState *env, const ARMCPRegInfo *ri) |
| { |
| /* We don't implement EL2, so the only control on DC ZVA is the |
| * bit in the SCTLR which can prohibit access for EL0. |
| */ |
| if (arm_current_el(env) == 0 && !(env->cp15.c1_sys & SCTLR_DZE)) { |
| return CP_ACCESS_TRAP; |
| } |
| return CP_ACCESS_OK; |
| } |
| |
| static uint64_t aa64_dczid_read(CPUARMState *env, const ARMCPRegInfo *ri) |
| { |
| ARMCPU *cpu = arm_env_get_cpu(env); |
| int dzp_bit = 1 << 4; |
| |
| /* DZP indicates whether DC ZVA access is allowed */ |
| if (aa64_zva_access(env, NULL) == CP_ACCESS_OK) { |
| dzp_bit = 0; |
| } |
| return cpu->dcz_blocksize | dzp_bit; |
| } |
| |
| static CPAccessResult sp_el0_access(CPUARMState *env, const ARMCPRegInfo *ri) |
| { |
| if (!(env->pstate & PSTATE_SP)) { |
| /* Access to SP_EL0 is undefined if it's being used as |
| * the stack pointer. |
| */ |
| return CP_ACCESS_TRAP_UNCATEGORIZED; |
| } |
| return CP_ACCESS_OK; |
| } |
| |
| static uint64_t spsel_read(CPUARMState *env, const ARMCPRegInfo *ri) |
| { |
| return env->pstate & PSTATE_SP; |
| } |
| |
| static void spsel_write(CPUARMState *env, const ARMCPRegInfo *ri, uint64_t val) |
| { |
| update_spsel(env, val); |
| } |
| |
| static const ARMCPRegInfo v8_cp_reginfo[] = { |
| /* Minimal set of EL0-visible registers. This will need to be expanded |
| * significantly for system emulation of AArch64 CPUs. |
| */ |
| { .name = "NZCV", .state = ARM_CP_STATE_AA64, |
| .opc0 = 3, .opc1 = 3, .opc2 = 0, .crn = 4, .crm = 2, |
| .access = PL0_RW, .type = ARM_CP_NZCV }, |
| { .name = "DAIF", .state = ARM_CP_STATE_AA64, |
| .opc0 = 3, .opc1 = 3, .opc2 = 1, .crn = 4, .crm = 2, |
| .type = ARM_CP_NO_MIGRATE, |
| .access = PL0_RW, .accessfn = aa64_daif_access, |
| .fieldoffset = offsetof(CPUARMState, daif), |
| .writefn = aa64_daif_write, .resetfn = arm_cp_reset_ignore }, |
| { .name = "FPCR", .state = ARM_CP_STATE_AA64, |
| .opc0 = 3, .opc1 = 3, .opc2 = 0, .crn = 4, .crm = 4, |
| .access = PL0_RW, .readfn = aa64_fpcr_read, .writefn = aa64_fpcr_write }, |
| { .name = "FPSR", .state = ARM_CP_STATE_AA64, |
| .opc0 = 3, .opc1 = 3, .opc2 = 1, .crn = 4, .crm = 4, |
| .access = PL0_RW, .readfn = aa64_fpsr_read, .writefn = aa64_fpsr_write }, |
| { .name = "DCZID_EL0", .state = ARM_CP_STATE_AA64, |
| .opc0 = 3, .opc1 = 3, .opc2 = 7, .crn = 0, .crm = 0, |
| .access = PL0_R, .type = ARM_CP_NO_MIGRATE, |
| .readfn = aa64_dczid_read }, |
| { .name = "DC_ZVA", .state = ARM_CP_STATE_AA64, |
| .opc0 = 1, .opc1 = 3, .crn = 7, .crm = 4, .opc2 = 1, |
| .access = PL0_W, .type = ARM_CP_DC_ZVA, |
| #ifndef CONFIG_USER_ONLY |
| /* Avoid overhead of an access check that always passes in user-mode */ |
| .accessfn = aa64_zva_access, |
| #endif |
| }, |
| { .name = "CURRENTEL", .state = ARM_CP_STATE_AA64, |
| .opc0 = 3, .opc1 = 0, .opc2 = 2, .crn = 4, .crm = 2, |
| .access = PL1_R, .type = ARM_CP_CURRENTEL }, |
| /* Cache ops: all NOPs since we don't emulate caches */ |
| { .name = "IC_IALLUIS", .state = ARM_CP_STATE_AA64, |
| .opc0 = 1, .opc1 = 0, .crn = 7, .crm = 1, .opc2 = 0, |
| .access = PL1_W, .type = ARM_CP_NOP }, |
| { .name = "IC_IALLU", .state = ARM_CP_STATE_AA64, |
| .opc0 = 1, .opc1 = 0, .crn = 7, .crm = 5, .opc2 = 0, |
| .access = PL1_W, .type = ARM_CP_NOP }, |
| { .name = "IC_IVAU", .state = ARM_CP_STATE_AA64, |
| .opc0 = 1, .opc1 = 3, .crn = 7, .crm = 5, .opc2 = 1, |
| .access = PL0_W, .type = ARM_CP_NOP, |
| .accessfn = aa64_cacheop_access }, |
| { .name = "DC_IVAC", .state = ARM_CP_STATE_AA64, |
| .opc0 = 1, .opc1 = 0, .crn = 7, .crm = 6, .opc2 = 1, |
| .access = PL1_W, .type = ARM_CP_NOP }, |
| { .name = "DC_ISW", .state = ARM_CP_STATE_AA64, |
| .opc0 = 1, .opc1 = 0, .crn = 7, .crm = 6, .opc2 = 2, |
| .access = PL1_W, .type = ARM_CP_NOP }, |
| { .name = "DC_CVAC", .state = ARM_CP_STATE_AA64, |
| .opc0 = 1, .opc1 = 3, .crn = 7, .crm = 10, .opc2 = 1, |
| .access = PL0_W, .type = ARM_CP_NOP, |
| .accessfn = aa64_cacheop_access }, |
| { .name = "DC_CSW", .state = ARM_CP_STATE_AA64, |
| .opc0 = 1, .opc1 = 0, .crn = 7, .crm = 10, .opc2 = 2, |
| .access = PL1_W, .type = ARM_CP_NOP }, |
| { .name = "DC_CVAU", .state = ARM_CP_STATE_AA64, |
| .opc0 = 1, .opc1 = 3, .crn = 7, .crm = 11, .opc2 = 1, |
| .access = PL0_W, .type = ARM_CP_NOP, |
| .accessfn = aa64_cacheop_access }, |
| { .name = "DC_CIVAC", .state = ARM_CP_STATE_AA64, |
| .opc0 = 1, .opc1 = 3, .crn = 7, .crm = 14, .opc2 = 1, |
| .access = PL0_W, .type = ARM_CP_NOP, |
| .accessfn = aa64_cacheop_access }, |
| { .name = "DC_CISW", .state = ARM_CP_STATE_AA64, |
| .opc0 = 1, .opc1 = 0, .crn = 7, .crm = 14, .opc2 = 2, |
| .access = PL1_W, .type = ARM_CP_NOP }, |
| /* TLBI operations */ |
| { .name = "TLBI_VMALLE1IS", .state = ARM_CP_STATE_AA64, |
| .opc0 = 1, .opc1 = 0, .crn = 8, .crm = 3, .opc2 = 0, |
| .access = PL1_W, .type = ARM_CP_NO_MIGRATE, |
| .writefn = tlbiall_is_write }, |
| { .name = "TLBI_VAE1IS", .state = ARM_CP_STATE_AA64, |
| .opc0 = 1, .opc1 = 0, .crn = 8, .crm = 3, .opc2 = 1, |
| .access = PL1_W, .type = ARM_CP_NO_MIGRATE, |
| .writefn = tlbi_aa64_va_is_write }, |
| { .name = "TLBI_ASIDE1IS", .state = ARM_CP_STATE_AA64, |
| .opc0 = 1, .opc1 = 0, .crn = 8, .crm = 3, .opc2 = 2, |
| .access = PL1_W, .type = ARM_CP_NO_MIGRATE, |
| .writefn = tlbi_aa64_asid_is_write }, |
| { .name = "TLBI_VAAE1IS", .state = ARM_CP_STATE_AA64, |
| .opc0 = 1, .opc1 = 0, .crn = 8, .crm = 3, .opc2 = 3, |
| .access = PL1_W, .type = ARM_CP_NO_MIGRATE, |
| .writefn = tlbi_aa64_vaa_is_write }, |
| { .name = "TLBI_VALE1IS", .state = ARM_CP_STATE_AA64, |
| .opc0 = 1, .opc1 = 0, .crn = 8, .crm = 3, .opc2 = 5, |
| .access = PL1_W, .type = ARM_CP_NO_MIGRATE, |
| .writefn = tlbi_aa64_va_is_write }, |
| { .name = "TLBI_VAALE1IS", .state = ARM_CP_STATE_AA64, |
| .opc0 = 1, .opc1 = 0, .crn = 8, .crm = 3, .opc2 = 7, |
| .access = PL1_W, .type = ARM_CP_NO_MIGRATE, |
| .writefn = tlbi_aa64_vaa_is_write }, |
| { .name = "TLBI_VMALLE1", .state = ARM_CP_STATE_AA64, |
| .opc0 = 1, .opc1 = 0, .crn = 8, .crm = 7, .opc2 = 0, |
| .access = PL1_W, .type = ARM_CP_NO_MIGRATE, |
| .writefn = tlbiall_write }, |
| { .name = "TLBI_VAE1", .state = ARM_CP_STATE_AA64, |
| .opc0 = 1, .opc1 = 0, .crn = 8, .crm = 7, .opc2 = 1, |
| .access = PL1_W, .type = ARM_CP_NO_MIGRATE, |
| .writefn = tlbi_aa64_va_write }, |
| { .name = "TLBI_ASIDE1", .state = ARM_CP_STATE_AA64, |
| .opc0 = 1, .opc1 = 0, .crn = 8, .crm = 7, .opc2 = 2, |
| .access = PL1_W, .type = ARM_CP_NO_MIGRATE, |
| .writefn = tlbi_aa64_asid_write }, |
| { .name = "TLBI_VAAE1", .state = ARM_CP_STATE_AA64, |
| .opc0 = 1, .opc1 = 0, .crn = 8, .crm = 7, .opc2 = 3, |
| .access = PL1_W, .type = ARM_CP_NO_MIGRATE, |
| .writefn = tlbi_aa64_vaa_write }, |
| { .name = "TLBI_VALE1", .state = ARM_CP_STATE_AA64, |
| .opc0 = 1, .opc1 = 0, .crn = 8, .crm = 7, .opc2 = 5, |
| .access = PL1_W, .type = ARM_CP_NO_MIGRATE, |
| .writefn = tlbi_aa64_va_write }, |
| { .name = "TLBI_VAALE1", .state = ARM_CP_STATE_AA64, |
| .opc0 = 1, .opc1 = 0, .crn = 8, .crm = 7, .opc2 = 7, |
| .access = PL1_W, .type = ARM_CP_NO_MIGRATE, |
| .writefn = tlbi_aa64_vaa_write }, |
| #ifndef CONFIG_USER_ONLY |
| /* 64 bit address translation operations */ |
| { .name = "AT_S1E1R", .state = ARM_CP_STATE_AA64, |
| .opc0 = 1, .opc1 = 0, .crn = 7, .crm = 8, .opc2 = 0, |
| .access = PL1_W, .type = ARM_CP_NO_MIGRATE, .writefn = ats_write }, |
| { .name = "AT_S1E1W", .state = ARM_CP_STATE_AA64, |
| .opc0 = 1, .opc1 = 0, .crn = 7, .crm = 8, .opc2 = 1, |
| .access = PL1_W, .type = ARM_CP_NO_MIGRATE, .writefn = ats_write }, |
| { .name = "AT_S1E0R", .state = ARM_CP_STATE_AA64, |
| .opc0 = 1, .opc1 = 0, .crn = 7, .crm = 8, .opc2 = 2, |
| .access = PL1_W, .type = ARM_CP_NO_MIGRATE, .writefn = ats_write }, |
| { .name = "AT_S1E0W", .state = ARM_CP_STATE_AA64, |
| .opc0 = 1, .opc1 = 0, .crn = 7, .crm = 8, .opc2 = 3, |
| .access = PL1_W, .type = ARM_CP_NO_MIGRATE, .writefn = ats_write }, |
| #endif |
| /* TLB invalidate last level of translation table walk */ |
| { .name = "TLBIMVALIS", .cp = 15, .opc1 = 0, .crn = 8, .crm = 3, .opc2 = 5, |
| .type = ARM_CP_NO_MIGRATE, .access = PL1_W, .writefn = tlbimva_is_write }, |
| { .name = "TLBIMVAALIS", .cp = 15, .opc1 = 0, .crn = 8, .crm = 3, .opc2 = 7, |
| .type = ARM_CP_NO_MIGRATE, .access = PL1_W, |
| .writefn = tlbimvaa_is_write }, |
| { .name = "TLBIMVAL", .cp = 15, .opc1 = 0, .crn = 8, .crm = 7, .opc2 = 5, |
| .type = ARM_CP_NO_MIGRATE, .access = PL1_W, .writefn = tlbimva_write }, |
| { .name = "TLBIMVAAL", .cp = 15, .opc1 = 0, .crn = 8, .crm = 7, .opc2 = 7, |
| .type = ARM_CP_NO_MIGRATE, .access = PL1_W, .writefn = tlbimvaa_write }, |
| /* 32 bit cache operations */ |
| { .name = "ICIALLUIS", .cp = 15, .opc1 = 0, .crn = 7, .crm = 1, .opc2 = 0, |
| .type = ARM_CP_NOP, .access = PL1_W }, |
| { .name = "BPIALLUIS", .cp = 15, .opc1 = 0, .crn = 7, .crm = 1, .opc2 = 6, |
| .type = ARM_CP_NOP, .access = PL1_W }, |
| { .name = "ICIALLU", .cp = 15, .opc1 = 0, .crn = 7, .crm = 5, .opc2 = 0, |
| .type = ARM_CP_NOP, .access = PL1_W }, |
| { .name = "ICIMVAU", .cp = 15, .opc1 = 0, .crn = 7, .crm = 5, .opc2 = 1, |
| .type = ARM_CP_NOP, .access = PL1_W }, |
| { .name = "BPIALL", .cp = 15, .opc1 = 0, .crn = 7, .crm = 5, .opc2 = 6, |
| .type = ARM_CP_NOP, .access = PL1_W }, |
| { .name = "BPIMVA", .cp = 15, .opc1 = 0, .crn = 7, .crm = 5, .opc2 = 7, |
| .type = ARM_CP_NOP, .access = PL1_W }, |
| { .name = "DCIMVAC", .cp = 15, .opc1 = 0, .crn = 7, .crm = 6, .opc2 = 1, |
| .type = ARM_CP_NOP, .access = PL1_W }, |
| { .name = "DCISW", .cp = 15, .opc1 = 0, .crn = 7, .crm = 6, .opc2 = 2, |
| .type = ARM_CP_NOP, .access = PL1_W }, |
| { .name = "DCCMVAC", .cp = 15, .opc1 = 0, .crn = 7, .crm = 10, .opc2 = 1, |
| .type = ARM_CP_NOP, .access = PL1_W }, |
| { .name = "DCCSW", .cp = 15, .opc1 = 0, .crn = 7, .crm = 10, .opc2 = 2, |
| .type = ARM_CP_NOP, .access = PL1_W }, |
| { .name = "DCCMVAU", .cp = 15, .opc1 = 0, .crn = 7, .crm = 11, .opc2 = 1, |
| .type = ARM_CP_NOP, .access = PL1_W }, |
| { .name = "DCCIMVAC", .cp = 15, .opc1 = 0, .crn = 7, .crm = 14, .opc2 = 1, |
| .type = ARM_CP_NOP, .access = PL1_W }, |
| { .name = "DCCISW", .cp = 15, .opc1 = 0, .crn = 7, .crm = 14, .opc2 = 2, |
| .type = ARM_CP_NOP, .access = PL1_W }, |
| /* MMU Domain access control / MPU write buffer control */ |
| { .name = "DACR", .cp = 15, |
| .opc1 = 0, .crn = 3, .crm = 0, .opc2 = 0, |
| .access = PL1_RW, .fieldoffset = offsetof(CPUARMState, cp15.c3), |
| .resetvalue = 0, .writefn = dacr_write, .raw_writefn = raw_write, }, |
| { .name = "ELR_EL1", .state = ARM_CP_STATE_AA64, |
| .type = ARM_CP_NO_MIGRATE, |
| .opc0 = 3, .opc1 = 0, .crn = 4, .crm = 0, .opc2 = 1, |
| .access = PL1_RW, |
| .fieldoffset = offsetof(CPUARMState, elr_el[1]) }, |
| { .name = "SPSR_EL1", .state = ARM_CP_STATE_AA64, |
| .type = ARM_CP_NO_MIGRATE, |
| .opc0 = 3, .opc1 = 0, .crn = 4, .crm = 0, .opc2 = 0, |
| .access = PL1_RW, .fieldoffset = offsetof(CPUARMState, banked_spsr[0]) }, |
| /* We rely on the access checks not allowing the guest to write to the |
| * state field when SPSel indicates that it's being used as the stack |
| * pointer. |
| */ |
| { .name = "SP_EL0", .state = ARM_CP_STATE_AA64, |
| .opc0 = 3, .opc1 = 0, .crn = 4, .<
|