target-arm: initial coprocessor register framework
Initial infrastructure for data-driven registration of coprocessor register implementations. We still fall back to the old-style switch statements pending complete conversion of all existing registers. Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
parent
200bf596b9
commit
4b6a83fb0c
|
@ -58,6 +58,9 @@ typedef struct ARMCPU {
|
||||||
|
|
||||||
CPUARMState env;
|
CPUARMState env;
|
||||||
|
|
||||||
|
/* Coprocessor information */
|
||||||
|
GHashTable *cp_regs;
|
||||||
|
|
||||||
/* The instance init functions for implementation-specific subclasses
|
/* The instance init functions for implementation-specific subclasses
|
||||||
* set these fields to specify the implementation-dependent values of
|
* set these fields to specify the implementation-dependent values of
|
||||||
* various constant registers and reset values of non-constant
|
* various constant registers and reset values of non-constant
|
||||||
|
|
|
@ -24,6 +24,37 @@
|
||||||
#include "hw/loader.h"
|
#include "hw/loader.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static void cp_reg_reset(gpointer key, gpointer value, gpointer opaque)
|
||||||
|
{
|
||||||
|
/* Reset a single ARMCPRegInfo register */
|
||||||
|
ARMCPRegInfo *ri = value;
|
||||||
|
ARMCPU *cpu = opaque;
|
||||||
|
|
||||||
|
if (ri->type & ARM_CP_SPECIAL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ri->resetfn) {
|
||||||
|
ri->resetfn(&cpu->env, ri);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A zero offset is never possible as it would be regs[0]
|
||||||
|
* so we use it to indicate that reset is being handled elsewhere.
|
||||||
|
* This is basically only used for fields in non-core coprocessors
|
||||||
|
* (like the pxa2xx ones).
|
||||||
|
*/
|
||||||
|
if (!ri->fieldoffset) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ri->type & ARM_CP_64BIT) {
|
||||||
|
CPREG_FIELD64(&cpu->env, ri) = ri->resetvalue;
|
||||||
|
} else {
|
||||||
|
CPREG_FIELD32(&cpu->env, ri) = ri->resetvalue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* CPUClass::reset() */
|
/* CPUClass::reset() */
|
||||||
static void arm_cpu_reset(CPUState *s)
|
static void arm_cpu_reset(CPUState *s)
|
||||||
{
|
{
|
||||||
|
@ -39,6 +70,7 @@ static void arm_cpu_reset(CPUState *s)
|
||||||
acc->parent_reset(s);
|
acc->parent_reset(s);
|
||||||
|
|
||||||
memset(env, 0, offsetof(CPUARMState, breakpoints));
|
memset(env, 0, offsetof(CPUARMState, breakpoints));
|
||||||
|
g_hash_table_foreach(cpu->cp_regs, cp_reg_reset, cpu);
|
||||||
env->cp15.c15_config_base_address = cpu->reset_cbar;
|
env->cp15.c15_config_base_address = cpu->reset_cbar;
|
||||||
env->cp15.c0_cpuid = cpu->midr;
|
env->cp15.c0_cpuid = cpu->midr;
|
||||||
env->vfp.xregs[ARM_VFP_FPSID] = cpu->reset_fpsid;
|
env->vfp.xregs[ARM_VFP_FPSID] = cpu->reset_fpsid;
|
||||||
|
@ -130,6 +162,14 @@ static void arm_cpu_initfn(Object *obj)
|
||||||
ARMCPU *cpu = ARM_CPU(obj);
|
ARMCPU *cpu = ARM_CPU(obj);
|
||||||
|
|
||||||
cpu_exec_init(&cpu->env);
|
cpu_exec_init(&cpu->env);
|
||||||
|
cpu->cp_regs = g_hash_table_new_full(g_int_hash, g_int_equal,
|
||||||
|
g_free, g_free);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void arm_cpu_finalizefn(Object *obj)
|
||||||
|
{
|
||||||
|
ARMCPU *cpu = ARM_CPU(obj);
|
||||||
|
g_hash_table_destroy(cpu->cp_regs);
|
||||||
}
|
}
|
||||||
|
|
||||||
void arm_cpu_realize(ARMCPU *cpu)
|
void arm_cpu_realize(ARMCPU *cpu)
|
||||||
|
@ -657,6 +697,7 @@ static const TypeInfo arm_cpu_type_info = {
|
||||||
.parent = TYPE_CPU,
|
.parent = TYPE_CPU,
|
||||||
.instance_size = sizeof(ARMCPU),
|
.instance_size = sizeof(ARMCPU),
|
||||||
.instance_init = arm_cpu_initfn,
|
.instance_init = arm_cpu_initfn,
|
||||||
|
.instance_finalize = arm_cpu_finalizefn,
|
||||||
.abstract = true,
|
.abstract = true,
|
||||||
.class_size = sizeof(ARMCPUClass),
|
.class_size = sizeof(ARMCPUClass),
|
||||||
.class_init = arm_cpu_class_init,
|
.class_init = arm_cpu_class_init,
|
||||||
|
|
201
target-arm/cpu.h
201
target-arm/cpu.h
|
@ -410,6 +410,207 @@ void cpu_arm_set_cp_io(CPUARMState *env, int cpnum,
|
||||||
ARMReadCPFunc *cp_read, ARMWriteCPFunc *cp_write,
|
ARMReadCPFunc *cp_read, ARMWriteCPFunc *cp_write,
|
||||||
void *opaque);
|
void *opaque);
|
||||||
|
|
||||||
|
/* Interface for defining coprocessor registers.
|
||||||
|
* Registers are defined in tables of arm_cp_reginfo structs
|
||||||
|
* which are passed to define_arm_cp_regs().
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* When looking up a coprocessor register we look for it
|
||||||
|
* via an integer which encodes all of:
|
||||||
|
* coprocessor number
|
||||||
|
* Crn, Crm, opc1, opc2 fields
|
||||||
|
* 32 or 64 bit register (ie is it accessed via MRC/MCR
|
||||||
|
* or via MRRC/MCRR?)
|
||||||
|
* We allow 4 bits for opc1 because MRRC/MCRR have a 4 bit field.
|
||||||
|
* (In this case crn and opc2 should be zero.)
|
||||||
|
*/
|
||||||
|
#define ENCODE_CP_REG(cp, is64, crn, crm, opc1, opc2) \
|
||||||
|
(((cp) << 16) | ((is64) << 15) | ((crn) << 11) | \
|
||||||
|
((crm) << 7) | ((opc1) << 3) | (opc2))
|
||||||
|
|
||||||
|
#define DECODE_CPREG_CRN(enc) (((enc) >> 7) & 0xf)
|
||||||
|
|
||||||
|
/* ARMCPRegInfo type field bits. If the SPECIAL bit is set this is a
|
||||||
|
* special-behaviour cp reg and bits [15..8] indicate what behaviour
|
||||||
|
* it has. Otherwise it is a simple cp reg, where CONST indicates that
|
||||||
|
* TCG can assume the value to be constant (ie load at translate time)
|
||||||
|
* and 64BIT indicates a 64 bit wide coprocessor register. SUPPRESS_TB_END
|
||||||
|
* indicates that the TB should not be ended after a write to this register
|
||||||
|
* (the default is that the TB ends after cp writes). OVERRIDE permits
|
||||||
|
* a register definition to override a previous definition for the
|
||||||
|
* same (cp, is64, crn, crm, opc1, opc2) tuple: either the new or the
|
||||||
|
* old must have the OVERRIDE bit set.
|
||||||
|
*/
|
||||||
|
#define ARM_CP_SPECIAL 1
|
||||||
|
#define ARM_CP_CONST 2
|
||||||
|
#define ARM_CP_64BIT 4
|
||||||
|
#define ARM_CP_SUPPRESS_TB_END 8
|
||||||
|
#define ARM_CP_OVERRIDE 16
|
||||||
|
#define ARM_CP_NOP (ARM_CP_SPECIAL | (1 << 8))
|
||||||
|
#define ARM_CP_WFI (ARM_CP_SPECIAL | (2 << 8))
|
||||||
|
#define ARM_LAST_SPECIAL ARM_CP_WFI
|
||||||
|
/* Used only as a terminator for ARMCPRegInfo lists */
|
||||||
|
#define ARM_CP_SENTINEL 0xffff
|
||||||
|
/* Mask of only the flag bits in a type field */
|
||||||
|
#define ARM_CP_FLAG_MASK 0x1f
|
||||||
|
|
||||||
|
/* Return true if cptype is a valid type field. This is used to try to
|
||||||
|
* catch errors where the sentinel has been accidentally left off the end
|
||||||
|
* of a list of registers.
|
||||||
|
*/
|
||||||
|
static inline bool cptype_valid(int cptype)
|
||||||
|
{
|
||||||
|
return ((cptype & ~ARM_CP_FLAG_MASK) == 0)
|
||||||
|
|| ((cptype & ARM_CP_SPECIAL) &&
|
||||||
|
(cptype <= ARM_LAST_SPECIAL));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Access rights:
|
||||||
|
* We define bits for Read and Write access for what rev C of the v7-AR ARM ARM
|
||||||
|
* defines as PL0 (user), PL1 (fiq/irq/svc/abt/und/sys, ie privileged), and
|
||||||
|
* PL2 (hyp). The other level which has Read and Write bits is Secure PL1
|
||||||
|
* (ie any of the privileged modes in Secure state, or Monitor mode).
|
||||||
|
* If a register is accessible in one privilege level it's always accessible
|
||||||
|
* in higher privilege levels too. Since "Secure PL1" also follows this rule
|
||||||
|
* (ie anything visible in PL2 is visible in S-PL1, some things are only
|
||||||
|
* visible in S-PL1) but "Secure PL1" is a bit of a mouthful, we bend the
|
||||||
|
* terminology a little and call this PL3.
|
||||||
|
*
|
||||||
|
* If access permissions for a register are more complex than can be
|
||||||
|
* described with these bits, then use a laxer set of restrictions, and
|
||||||
|
* do the more restrictive/complex check inside a helper function.
|
||||||
|
*/
|
||||||
|
#define PL3_R 0x80
|
||||||
|
#define PL3_W 0x40
|
||||||
|
#define PL2_R (0x20 | PL3_R)
|
||||||
|
#define PL2_W (0x10 | PL3_W)
|
||||||
|
#define PL1_R (0x08 | PL2_R)
|
||||||
|
#define PL1_W (0x04 | PL2_W)
|
||||||
|
#define PL0_R (0x02 | PL1_R)
|
||||||
|
#define PL0_W (0x01 | PL1_W)
|
||||||
|
|
||||||
|
#define PL3_RW (PL3_R | PL3_W)
|
||||||
|
#define PL2_RW (PL2_R | PL2_W)
|
||||||
|
#define PL1_RW (PL1_R | PL1_W)
|
||||||
|
#define PL0_RW (PL0_R | PL0_W)
|
||||||
|
|
||||||
|
static inline int arm_current_pl(CPUARMState *env)
|
||||||
|
{
|
||||||
|
if ((env->uncached_cpsr & 0x1f) == ARM_CPU_MODE_USR) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
/* We don't currently implement the Virtualization or TrustZone
|
||||||
|
* extensions, so PL2 and PL3 don't exist for us.
|
||||||
|
*/
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct ARMCPRegInfo ARMCPRegInfo;
|
||||||
|
|
||||||
|
/* Access functions for coprocessor registers. These should return
|
||||||
|
* 0 on success, or one of the EXCP_* constants if access should cause
|
||||||
|
* an exception (in which case *value is not written).
|
||||||
|
*/
|
||||||
|
typedef int CPReadFn(CPUARMState *env, const ARMCPRegInfo *opaque,
|
||||||
|
uint64_t *value);
|
||||||
|
typedef int CPWriteFn(CPUARMState *env, const ARMCPRegInfo *opaque,
|
||||||
|
uint64_t value);
|
||||||
|
/* Hook function for register reset */
|
||||||
|
typedef void CPResetFn(CPUARMState *env, const ARMCPRegInfo *opaque);
|
||||||
|
|
||||||
|
#define CP_ANY 0xff
|
||||||
|
|
||||||
|
/* Definition of an ARM coprocessor register */
|
||||||
|
struct ARMCPRegInfo {
|
||||||
|
/* Name of register (useful mainly for debugging, need not be unique) */
|
||||||
|
const char *name;
|
||||||
|
/* Location of register: coprocessor number and (crn,crm,opc1,opc2)
|
||||||
|
* tuple. Any of crm, opc1 and opc2 may be CP_ANY to indicate a
|
||||||
|
* 'wildcard' field -- any value of that field in the MRC/MCR insn
|
||||||
|
* will be decoded to this register. The register read and write
|
||||||
|
* callbacks will be passed an ARMCPRegInfo with the crn/crm/opc1/opc2
|
||||||
|
* used by the program, so it is possible to register a wildcard and
|
||||||
|
* then behave differently on read/write if necessary.
|
||||||
|
* For 64 bit registers, only crm and opc1 are relevant; crn and opc2
|
||||||
|
* must both be zero.
|
||||||
|
*/
|
||||||
|
uint8_t cp;
|
||||||
|
uint8_t crn;
|
||||||
|
uint8_t crm;
|
||||||
|
uint8_t opc1;
|
||||||
|
uint8_t opc2;
|
||||||
|
/* Register type: ARM_CP_* bits/values */
|
||||||
|
int type;
|
||||||
|
/* Access rights: PL*_[RW] */
|
||||||
|
int access;
|
||||||
|
/* The opaque pointer passed to define_arm_cp_regs_with_opaque() when
|
||||||
|
* this register was defined: can be used to hand data through to the
|
||||||
|
* register read/write functions, since they are passed the ARMCPRegInfo*.
|
||||||
|
*/
|
||||||
|
void *opaque;
|
||||||
|
/* Value of this register, if it is ARM_CP_CONST. Otherwise, if
|
||||||
|
* fieldoffset is non-zero, the reset value of the register.
|
||||||
|
*/
|
||||||
|
uint64_t resetvalue;
|
||||||
|
/* Offset of the field in CPUARMState for this register. This is not
|
||||||
|
* needed if either:
|
||||||
|
* 1. type is ARM_CP_CONST or one of the ARM_CP_SPECIALs
|
||||||
|
* 2. both readfn and writefn are specified
|
||||||
|
*/
|
||||||
|
ptrdiff_t fieldoffset; /* offsetof(CPUARMState, field) */
|
||||||
|
/* Function for handling reads of this register. If NULL, then reads
|
||||||
|
* will be done by loading from the offset into CPUARMState specified
|
||||||
|
* by fieldoffset.
|
||||||
|
*/
|
||||||
|
CPReadFn *readfn;
|
||||||
|
/* Function for handling writes of this register. If NULL, then writes
|
||||||
|
* will be done by writing to the offset into CPUARMState specified
|
||||||
|
* by fieldoffset.
|
||||||
|
*/
|
||||||
|
CPWriteFn *writefn;
|
||||||
|
/* Function for resetting the register. If NULL, then reset will be done
|
||||||
|
* by writing resetvalue to the field specified in fieldoffset. If
|
||||||
|
* fieldoffset is 0 then no reset will be done.
|
||||||
|
*/
|
||||||
|
CPResetFn *resetfn;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Macros which are lvalues for the field in CPUARMState for the
|
||||||
|
* ARMCPRegInfo *ri.
|
||||||
|
*/
|
||||||
|
#define CPREG_FIELD32(env, ri) \
|
||||||
|
(*(uint32_t *)((char *)(env) + (ri)->fieldoffset))
|
||||||
|
#define CPREG_FIELD64(env, ri) \
|
||||||
|
(*(uint64_t *)((char *)(env) + (ri)->fieldoffset))
|
||||||
|
|
||||||
|
#define REGINFO_SENTINEL { .type = ARM_CP_SENTINEL }
|
||||||
|
|
||||||
|
void define_arm_cp_regs_with_opaque(ARMCPU *cpu,
|
||||||
|
const ARMCPRegInfo *regs, void *opaque);
|
||||||
|
void define_one_arm_cp_reg_with_opaque(ARMCPU *cpu,
|
||||||
|
const ARMCPRegInfo *regs, void *opaque);
|
||||||
|
static inline void define_arm_cp_regs(ARMCPU *cpu, const ARMCPRegInfo *regs)
|
||||||
|
{
|
||||||
|
define_arm_cp_regs_with_opaque(cpu, regs, 0);
|
||||||
|
}
|
||||||
|
static inline void define_one_arm_cp_reg(ARMCPU *cpu, const ARMCPRegInfo *regs)
|
||||||
|
{
|
||||||
|
define_one_arm_cp_reg_with_opaque(cpu, regs, 0);
|
||||||
|
}
|
||||||
|
const ARMCPRegInfo *get_arm_cp_reginfo(ARMCPU *cpu, uint32_t encoded_cp);
|
||||||
|
|
||||||
|
/* CPWriteFn that can be used to implement writes-ignored behaviour */
|
||||||
|
int arm_cp_write_ignore(CPUARMState *env, const ARMCPRegInfo *ri,
|
||||||
|
uint64_t value);
|
||||||
|
/* CPReadFn that can be used for read-as-zero behaviour */
|
||||||
|
int arm_cp_read_zero(CPUARMState *env, const ARMCPRegInfo *ri, uint64_t *value);
|
||||||
|
|
||||||
|
static inline bool cp_access_ok(CPUARMState *env,
|
||||||
|
const ARMCPRegInfo *ri, int isread)
|
||||||
|
{
|
||||||
|
return (ri->access >> ((arm_current_pl(env) * 2) + isread)) & 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* Does the core conform to the the "MicroController" profile. e.g. Cortex-M3.
|
/* Does the core conform to the the "MicroController" profile. e.g. Cortex-M3.
|
||||||
Note the M in older cores (eg. ARM7TDMI) stands for Multiply. These are
|
Note the M in older cores (eg. ARM7TDMI) stands for Multiply. These are
|
||||||
conventional cores (ie. Application or Realtime profile). */
|
conventional cores (ie. Application or Realtime profile). */
|
||||||
|
|
|
@ -137,6 +137,107 @@ void arm_cpu_list(FILE *f, fprintf_function cpu_fprintf)
|
||||||
g_slist_free(list);
|
g_slist_free(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void define_one_arm_cp_reg_with_opaque(ARMCPU *cpu,
|
||||||
|
const ARMCPRegInfo *r, void *opaque)
|
||||||
|
{
|
||||||
|
/* Define implementations of coprocessor registers.
|
||||||
|
* We store these in a hashtable because typically
|
||||||
|
* there are less than 150 registers in a space which
|
||||||
|
* is 16*16*16*8*8 = 262144 in size.
|
||||||
|
* Wildcarding is supported for the crm, opc1 and opc2 fields.
|
||||||
|
* If a register is defined twice then the second definition is
|
||||||
|
* used, so this can be used to define some generic registers and
|
||||||
|
* then override them with implementation specific variations.
|
||||||
|
* At least one of the original and the second definition should
|
||||||
|
* include ARM_CP_OVERRIDE in its type bits -- this is just a guard
|
||||||
|
* against accidental use.
|
||||||
|
*/
|
||||||
|
int crm, opc1, opc2;
|
||||||
|
int crmmin = (r->crm == CP_ANY) ? 0 : r->crm;
|
||||||
|
int crmmax = (r->crm == CP_ANY) ? 15 : r->crm;
|
||||||
|
int opc1min = (r->opc1 == CP_ANY) ? 0 : r->opc1;
|
||||||
|
int opc1max = (r->opc1 == CP_ANY) ? 7 : r->opc1;
|
||||||
|
int opc2min = (r->opc2 == CP_ANY) ? 0 : r->opc2;
|
||||||
|
int opc2max = (r->opc2 == CP_ANY) ? 7 : r->opc2;
|
||||||
|
/* 64 bit registers have only CRm and Opc1 fields */
|
||||||
|
assert(!((r->type & ARM_CP_64BIT) && (r->opc2 || r->crn)));
|
||||||
|
/* Check that the register definition has enough info to handle
|
||||||
|
* reads and writes if they are permitted.
|
||||||
|
*/
|
||||||
|
if (!(r->type & (ARM_CP_SPECIAL|ARM_CP_CONST))) {
|
||||||
|
if (r->access & PL3_R) {
|
||||||
|
assert(r->fieldoffset || r->readfn);
|
||||||
|
}
|
||||||
|
if (r->access & PL3_W) {
|
||||||
|
assert(r->fieldoffset || r->writefn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Bad type field probably means missing sentinel at end of reg list */
|
||||||
|
assert(cptype_valid(r->type));
|
||||||
|
for (crm = crmmin; crm <= crmmax; crm++) {
|
||||||
|
for (opc1 = opc1min; opc1 <= opc1max; opc1++) {
|
||||||
|
for (opc2 = opc2min; opc2 <= opc2max; opc2++) {
|
||||||
|
uint32_t *key = g_new(uint32_t, 1);
|
||||||
|
ARMCPRegInfo *r2 = g_memdup(r, sizeof(ARMCPRegInfo));
|
||||||
|
int is64 = (r->type & ARM_CP_64BIT) ? 1 : 0;
|
||||||
|
*key = ENCODE_CP_REG(r->cp, is64, r->crn, crm, opc1, opc2);
|
||||||
|
r2->opaque = opaque;
|
||||||
|
/* Make sure reginfo passed to helpers for wildcarded regs
|
||||||
|
* has the correct crm/opc1/opc2 for this reg, not CP_ANY:
|
||||||
|
*/
|
||||||
|
r2->crm = crm;
|
||||||
|
r2->opc1 = opc1;
|
||||||
|
r2->opc2 = opc2;
|
||||||
|
/* Overriding of an existing definition must be explicitly
|
||||||
|
* requested.
|
||||||
|
*/
|
||||||
|
if (!(r->type & ARM_CP_OVERRIDE)) {
|
||||||
|
ARMCPRegInfo *oldreg;
|
||||||
|
oldreg = g_hash_table_lookup(cpu->cp_regs, key);
|
||||||
|
if (oldreg && !(oldreg->type & ARM_CP_OVERRIDE)) {
|
||||||
|
fprintf(stderr, "Register redefined: cp=%d %d bit "
|
||||||
|
"crn=%d crm=%d opc1=%d opc2=%d, "
|
||||||
|
"was %s, now %s\n", r2->cp, 32 + 32 * is64,
|
||||||
|
r2->crn, r2->crm, r2->opc1, r2->opc2,
|
||||||
|
oldreg->name, r2->name);
|
||||||
|
assert(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_hash_table_insert(cpu->cp_regs, key, r2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void define_arm_cp_regs_with_opaque(ARMCPU *cpu,
|
||||||
|
const ARMCPRegInfo *regs, void *opaque)
|
||||||
|
{
|
||||||
|
/* Define a whole list of registers */
|
||||||
|
const ARMCPRegInfo *r;
|
||||||
|
for (r = regs; r->type != ARM_CP_SENTINEL; r++) {
|
||||||
|
define_one_arm_cp_reg_with_opaque(cpu, r, opaque);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ARMCPRegInfo *get_arm_cp_reginfo(ARMCPU *cpu, uint32_t encoded_cp)
|
||||||
|
{
|
||||||
|
return g_hash_table_lookup(cpu->cp_regs, &encoded_cp);
|
||||||
|
}
|
||||||
|
|
||||||
|
int arm_cp_write_ignore(CPUARMState *env, const ARMCPRegInfo *ri,
|
||||||
|
uint64_t value)
|
||||||
|
{
|
||||||
|
/* Helper coprocessor write function for write-ignore registers */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int arm_cp_read_zero(CPUARMState *env, const ARMCPRegInfo *ri, uint64_t *value)
|
||||||
|
{
|
||||||
|
/* Helper coprocessor write function for read-as-zero registers */
|
||||||
|
*value = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int bad_mode_switch(CPUARMState *env, int mode)
|
static int bad_mode_switch(CPUARMState *env, int mode)
|
||||||
{
|
{
|
||||||
/* Return true if it is not valid for us to switch to
|
/* Return true if it is not valid for us to switch to
|
||||||
|
|
|
@ -65,6 +65,11 @@ DEF_HELPER_2(get_cp15, i32, env, i32)
|
||||||
DEF_HELPER_3(set_cp, void, env, i32, i32)
|
DEF_HELPER_3(set_cp, void, env, i32, i32)
|
||||||
DEF_HELPER_2(get_cp, i32, env, i32)
|
DEF_HELPER_2(get_cp, i32, env, i32)
|
||||||
|
|
||||||
|
DEF_HELPER_3(set_cp_reg, void, env, ptr, i32)
|
||||||
|
DEF_HELPER_2(get_cp_reg, i32, env, ptr)
|
||||||
|
DEF_HELPER_3(set_cp_reg64, void, env, ptr, i64)
|
||||||
|
DEF_HELPER_2(get_cp_reg64, i64, env, ptr)
|
||||||
|
|
||||||
DEF_HELPER_2(get_r13_banked, i32, env, i32)
|
DEF_HELPER_2(get_r13_banked, i32, env, i32)
|
||||||
DEF_HELPER_3(set_r13_banked, void, env, i32, i32)
|
DEF_HELPER_3(set_r13_banked, void, env, i32, i32)
|
||||||
|
|
||||||
|
|
|
@ -23,13 +23,11 @@
|
||||||
#define SIGNBIT (uint32_t)0x80000000
|
#define SIGNBIT (uint32_t)0x80000000
|
||||||
#define SIGNBIT64 ((uint64_t)1 << 63)
|
#define SIGNBIT64 ((uint64_t)1 << 63)
|
||||||
|
|
||||||
#if !defined(CONFIG_USER_ONLY)
|
|
||||||
static void raise_exception(int tt)
|
static void raise_exception(int tt)
|
||||||
{
|
{
|
||||||
env->exception_index = tt;
|
env->exception_index = tt;
|
||||||
cpu_loop_exit(env);
|
cpu_loop_exit(env);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
uint32_t HELPER(neon_tbl)(uint32_t ireg, uint32_t def,
|
uint32_t HELPER(neon_tbl)(uint32_t ireg, uint32_t def,
|
||||||
uint32_t rn, uint32_t maxindex)
|
uint32_t rn, uint32_t maxindex)
|
||||||
|
@ -287,6 +285,46 @@ void HELPER(set_user_reg)(uint32_t regno, uint32_t val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HELPER(set_cp_reg)(CPUARMState *env, void *rip, uint32_t value)
|
||||||
|
{
|
||||||
|
const ARMCPRegInfo *ri = rip;
|
||||||
|
int excp = ri->writefn(env, ri, value);
|
||||||
|
if (excp) {
|
||||||
|
raise_exception(excp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t HELPER(get_cp_reg)(CPUARMState *env, void *rip)
|
||||||
|
{
|
||||||
|
const ARMCPRegInfo *ri = rip;
|
||||||
|
uint64_t value;
|
||||||
|
int excp = ri->readfn(env, ri, &value);
|
||||||
|
if (excp) {
|
||||||
|
raise_exception(excp);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HELPER(set_cp_reg64)(CPUARMState *env, void *rip, uint64_t value)
|
||||||
|
{
|
||||||
|
const ARMCPRegInfo *ri = rip;
|
||||||
|
int excp = ri->writefn(env, ri, value);
|
||||||
|
if (excp) {
|
||||||
|
raise_exception(excp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t HELPER(get_cp_reg64)(CPUARMState *env, void *rip)
|
||||||
|
{
|
||||||
|
const ARMCPRegInfo *ri = rip;
|
||||||
|
uint64_t value;
|
||||||
|
int excp = ri->readfn(env, ri, &value);
|
||||||
|
if (excp) {
|
||||||
|
raise_exception(excp);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
/* ??? Flag setting arithmetic is awkward because we need to do comparisons.
|
/* ??? Flag setting arithmetic is awkward because we need to do comparisons.
|
||||||
The only way to do that in TCG is a conditional branch, which clobbers
|
The only way to do that in TCG is a conditional branch, which clobbers
|
||||||
all our temporaries. For now implement these as helper functions. */
|
all our temporaries. For now implement these as helper functions. */
|
||||||
|
|
|
@ -6479,13 +6479,16 @@ static int disas_cp14_write(CPUARMState * env, DisasContext *s, uint32_t insn)
|
||||||
|
|
||||||
static int disas_coproc_insn(CPUARMState * env, DisasContext *s, uint32_t insn)
|
static int disas_coproc_insn(CPUARMState * env, DisasContext *s, uint32_t insn)
|
||||||
{
|
{
|
||||||
int cpnum;
|
int cpnum, is64, crn, crm, opc1, opc2, isread, rt, rt2;
|
||||||
|
const ARMCPRegInfo *ri;
|
||||||
|
ARMCPU *cpu = arm_env_get_cpu(env);
|
||||||
|
|
||||||
cpnum = (insn >> 8) & 0xf;
|
cpnum = (insn >> 8) & 0xf;
|
||||||
if (arm_feature(env, ARM_FEATURE_XSCALE)
|
if (arm_feature(env, ARM_FEATURE_XSCALE)
|
||||||
&& ((env->cp15.c15_cpar ^ 0x3fff) & (1 << cpnum)))
|
&& ((env->cp15.c15_cpar ^ 0x3fff) & (1 << cpnum)))
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
|
/* First check for coprocessor space used for actual instructions */
|
||||||
switch (cpnum) {
|
switch (cpnum) {
|
||||||
case 0:
|
case 0:
|
||||||
case 1:
|
case 1:
|
||||||
|
@ -6498,6 +6501,157 @@ static int disas_coproc_insn(CPUARMState * env, DisasContext *s, uint32_t insn)
|
||||||
case 10:
|
case 10:
|
||||||
case 11:
|
case 11:
|
||||||
return disas_vfp_insn (env, s, insn);
|
return disas_vfp_insn (env, s, insn);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Otherwise treat as a generic register access */
|
||||||
|
is64 = (insn & (1 << 25)) == 0;
|
||||||
|
if (!is64 && ((insn & (1 << 4)) == 0)) {
|
||||||
|
/* cdp */
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
crm = insn & 0xf;
|
||||||
|
if (is64) {
|
||||||
|
crn = 0;
|
||||||
|
opc1 = (insn >> 4) & 0xf;
|
||||||
|
opc2 = 0;
|
||||||
|
rt2 = (insn >> 16) & 0xf;
|
||||||
|
} else {
|
||||||
|
crn = (insn >> 16) & 0xf;
|
||||||
|
opc1 = (insn >> 21) & 7;
|
||||||
|
opc2 = (insn >> 5) & 7;
|
||||||
|
rt2 = 0;
|
||||||
|
}
|
||||||
|
isread = (insn >> 20) & 1;
|
||||||
|
rt = (insn >> 12) & 0xf;
|
||||||
|
|
||||||
|
ri = get_arm_cp_reginfo(cpu,
|
||||||
|
ENCODE_CP_REG(cpnum, is64, crn, crm, opc1, opc2));
|
||||||
|
if (ri) {
|
||||||
|
/* Check access permissions */
|
||||||
|
if (!cp_access_ok(env, ri, isread)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Handle special cases first */
|
||||||
|
switch (ri->type & ~(ARM_CP_FLAG_MASK & ~ARM_CP_SPECIAL)) {
|
||||||
|
case ARM_CP_NOP:
|
||||||
|
return 0;
|
||||||
|
case ARM_CP_WFI:
|
||||||
|
if (isread) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
gen_set_pc_im(s->pc);
|
||||||
|
s->is_jmp = DISAS_WFI;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isread) {
|
||||||
|
/* Read */
|
||||||
|
if (is64) {
|
||||||
|
TCGv_i64 tmp64;
|
||||||
|
TCGv_i32 tmp;
|
||||||
|
if (ri->type & ARM_CP_CONST) {
|
||||||
|
tmp64 = tcg_const_i64(ri->resetvalue);
|
||||||
|
} else if (ri->readfn) {
|
||||||
|
TCGv_ptr tmpptr;
|
||||||
|
gen_set_pc_im(s->pc);
|
||||||
|
tmp64 = tcg_temp_new_i64();
|
||||||
|
tmpptr = tcg_const_ptr(ri);
|
||||||
|
gen_helper_get_cp_reg64(tmp64, cpu_env, tmpptr);
|
||||||
|
tcg_temp_free_ptr(tmpptr);
|
||||||
|
} else {
|
||||||
|
tmp64 = tcg_temp_new_i64();
|
||||||
|
tcg_gen_ld_i64(tmp64, cpu_env, ri->fieldoffset);
|
||||||
|
}
|
||||||
|
tmp = tcg_temp_new_i32();
|
||||||
|
tcg_gen_trunc_i64_i32(tmp, tmp64);
|
||||||
|
store_reg(s, rt, tmp);
|
||||||
|
tcg_gen_shri_i64(tmp64, tmp64, 32);
|
||||||
|
tcg_gen_trunc_i64_i32(tmp, tmp64);
|
||||||
|
store_reg(s, rt2, tmp);
|
||||||
|
} else {
|
||||||
|
TCGv tmp;
|
||||||
|
if (ri->type & ARM_CP_CONST) {
|
||||||
|
tmp = tcg_const_i32(ri->resetvalue);
|
||||||
|
} else if (ri->readfn) {
|
||||||
|
TCGv_ptr tmpptr;
|
||||||
|
gen_set_pc_im(s->pc);
|
||||||
|
tmp = tcg_temp_new_i32();
|
||||||
|
tmpptr = tcg_const_ptr(ri);
|
||||||
|
gen_helper_get_cp_reg(tmp, cpu_env, tmpptr);
|
||||||
|
tcg_temp_free_ptr(tmpptr);
|
||||||
|
} else {
|
||||||
|
tmp = load_cpu_offset(ri->fieldoffset);
|
||||||
|
}
|
||||||
|
if (rt == 15) {
|
||||||
|
/* Destination register of r15 for 32 bit loads sets
|
||||||
|
* the condition codes from the high 4 bits of the value
|
||||||
|
*/
|
||||||
|
gen_set_nzcv(tmp);
|
||||||
|
tcg_temp_free_i32(tmp);
|
||||||
|
} else {
|
||||||
|
store_reg(s, rt, tmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Write */
|
||||||
|
if (ri->type & ARM_CP_CONST) {
|
||||||
|
/* If not forbidden by access permissions, treat as WI */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is64) {
|
||||||
|
TCGv tmplo, tmphi;
|
||||||
|
TCGv_i64 tmp64 = tcg_temp_new_i64();
|
||||||
|
tmplo = load_reg(s, rt);
|
||||||
|
tmphi = load_reg(s, rt2);
|
||||||
|
tcg_gen_concat_i32_i64(tmp64, tmplo, tmphi);
|
||||||
|
tcg_temp_free_i32(tmplo);
|
||||||
|
tcg_temp_free_i32(tmphi);
|
||||||
|
if (ri->writefn) {
|
||||||
|
TCGv_ptr tmpptr = tcg_const_ptr(ri);
|
||||||
|
gen_set_pc_im(s->pc);
|
||||||
|
gen_helper_set_cp_reg64(cpu_env, tmpptr, tmp64);
|
||||||
|
tcg_temp_free_ptr(tmpptr);
|
||||||
|
} else {
|
||||||
|
tcg_gen_st_i64(tmp64, cpu_env, ri->fieldoffset);
|
||||||
|
}
|
||||||
|
tcg_temp_free_i64(tmp64);
|
||||||
|
} else {
|
||||||
|
if (ri->writefn) {
|
||||||
|
TCGv tmp;
|
||||||
|
TCGv_ptr tmpptr;
|
||||||
|
gen_set_pc_im(s->pc);
|
||||||
|
tmp = load_reg(s, rt);
|
||||||
|
tmpptr = tcg_const_ptr(ri);
|
||||||
|
gen_helper_set_cp_reg(cpu_env, tmpptr, tmp);
|
||||||
|
tcg_temp_free_ptr(tmpptr);
|
||||||
|
tcg_temp_free_i32(tmp);
|
||||||
|
} else {
|
||||||
|
TCGv tmp = load_reg(s, rt);
|
||||||
|
store_cpu_offset(tmp, ri->fieldoffset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* We default to ending the TB on a coprocessor register write,
|
||||||
|
* but allow this to be suppressed by the register definition
|
||||||
|
* (usually only necessary to work around guest bugs).
|
||||||
|
*/
|
||||||
|
if (!(ri->type & ARM_CP_SUPPRESS_TB_END)) {
|
||||||
|
gen_lookup_tb(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fallback code: handle coprocessor registers not yet converted
|
||||||
|
* to ARMCPRegInfo.
|
||||||
|
*/
|
||||||
|
switch (cpnum) {
|
||||||
case 14:
|
case 14:
|
||||||
/* Coprocessors 7-15 are architecturally reserved by ARM.
|
/* Coprocessors 7-15 are architecturally reserved by ARM.
|
||||||
Unfortunately Intel decided to ignore this. */
|
Unfortunately Intel decided to ignore this. */
|
||||||
|
|
Loading…
Reference in a new issue