target/arm: Add VHE system register redirection and aliasing

Several of the EL1/0 registers are redirected to the EL2 version when in
EL2 and HCR_EL2.E2H is set. Many of these registers have side effects.
Link together the two ARMCPRegInfo structures after they have been
properly instantiated. Install common dispatch routines to all of the
relevant registers.

The same set of registers that are redirected also have additional
EL12/EL02 aliases created to access the original register that was
redirected.

Omit the generic timer registers from redirection here, because we'll
need multiple kinds of redirection from both EL0 and EL2.

Backports commit e2cce18f5c1d0d55328c585c8372cdb096bbf528 from qemu
This commit is contained in:
Richard Henderson 2020-03-21 15:42:02 -04:00 committed by Lioncash
parent ff720b7fd3
commit e41c51f6da
4 changed files with 211 additions and 34 deletions

View file

@ -1481,7 +1481,7 @@ static inline void g_hash_table_maybe_resize (GHashTable *hash_table)
* @value (and perhaps the new @key). If it is not found, create a
* new node.
*/
static void g_hash_table_insert_internal (GHashTable *hash_table,
static gboolean g_hash_table_insert_internal (GHashTable *hash_table,
gpointer key,
gpointer value,
gboolean keep_new_key)
@ -1491,8 +1491,8 @@ static void g_hash_table_insert_internal (GHashTable *hash_table,
guint key_hash;
guint old_hash;
if (hash_table == NULL) return;
if (hash_table->ref_count == 0) return;
if (hash_table == NULL) return false;
if (hash_table->ref_count == 0) return false;
node_index = g_hash_table_lookup_node_for_insertion (hash_table, key, &key_hash);
node = &hash_table->nodes [node_index];
@ -1517,6 +1517,7 @@ static void g_hash_table_insert_internal (GHashTable *hash_table,
hash_table->value_destroy_func (node->value);
node->value = value;
return false;
}
else
{
@ -1532,6 +1533,7 @@ static void g_hash_table_insert_internal (GHashTable *hash_table,
hash_table->noccupied++;
g_hash_table_maybe_resize (hash_table);
}
return true;
}
}
@ -1571,11 +1573,11 @@ g_hash_table_get_keys (GHashTable *hash_table)
* a @key_destroy_func when creating the #GHashTable, the passed key is freed
* using that function.
**/
void g_hash_table_insert (GHashTable *hash_table,
gboolean g_hash_table_insert (GHashTable *hash_table,
gpointer key,
gpointer value)
{
g_hash_table_insert_internal (hash_table, key, value, FALSE);
return g_hash_table_insert_internal (hash_table, key, value, FALSE);
}
/**

View file

@ -143,7 +143,7 @@ void g_hash_table_destroy(GHashTable *hash_table);
gpointer g_hash_table_find(GHashTable *hash_table, GHRFunc predicate, gpointer user_data);
void g_hash_table_foreach(GHashTable *hash_table, GHFunc func, gpointer user_data);
GList *g_hash_table_get_keys(GHashTable *hash_table);
void g_hash_table_insert(GHashTable *hash_table, gpointer key, gpointer value);
gboolean g_hash_table_insert(GHashTable *hash_table, gpointer key, gpointer value);
void g_hash_table_replace(GHashTable *hash_table, gpointer key, gpointer value);
gpointer g_hash_table_lookup(GHashTable *hash_table, gconstpointer key);
GHashTable *g_hash_table_new(GHashFunc hash_func, GEqualFunc key_equal_func);

View file

@ -2475,6 +2475,19 @@ struct ARMCPRegInfo {
* fieldoffset is 0 then no reset will be done.
*/
CPResetFn *resetfn;
/*
* "Original" writefn and readfn.
* For ARMv8.1-VHE register aliases, we overwrite the read/write
* accessor functions of various EL1/EL0 to perform the runtime
* check for which sysreg should actually be modified, and then
* forwards the operation. Before overwriting the accessors,
* the original function is copied here, so that accesses that
* really do go to the EL1/EL0 version proceed normally.
* (The corresponding EL2 register is linked via opaque.)
*/
CPReadFn *orig_readfn;
CPWriteFn *orig_writefn;
};
/* Macros which are lvalues for the field in CPUARMState for the

View file

@ -5152,6 +5152,158 @@ static const ARMCPRegInfo el3_cp_reginfo[] = {
REGINFO_SENTINEL
};
#ifndef CONFIG_USER_ONLY
/* Test if system register redirection is to occur in the current state. */
static bool redirect_for_e2h(CPUARMState *env)
{
return arm_current_el(env) == 2 && (arm_hcr_el2_eff(env) & HCR_E2H);
}
static uint64_t el2_e2h_read(CPUARMState *env, const ARMCPRegInfo *ri)
{
CPReadFn *readfn;
if (redirect_for_e2h(env)) {
/* Switch to the saved EL2 version of the register. */
ri = ri->opaque;
readfn = ri->readfn;
} else {
readfn = ri->orig_readfn;
}
if (readfn == NULL) {
readfn = raw_read;
}
return readfn(env, ri);
}
static void el2_e2h_write(CPUARMState *env, const ARMCPRegInfo *ri,
uint64_t value)
{
CPWriteFn *writefn;
if (redirect_for_e2h(env)) {
/* Switch to the saved EL2 version of the register. */
ri = ri->opaque;
writefn = ri->writefn;
} else {
writefn = ri->orig_writefn;
}
if (writefn == NULL) {
writefn = raw_write;
}
writefn(env, ri, value);
}
static void define_arm_vh_e2h_redirects_aliases(ARMCPU *cpu)
{
struct E2HAlias {
uint32_t src_key, dst_key, new_key;
const char *src_name, *dst_name, *new_name;
bool (*feature)(const ARMISARegisters *id);
};
#define K(op0, op1, crn, crm, op2) \
ENCODE_AA64_CP_REG(CP_REG_ARM64_SYSREG_CP, crn, crm, op0, op1, op2)
static const struct E2HAlias aliases[] = {
{ K(3, 0, 1, 0, 0), K(3, 4, 1, 0, 0), K(3, 5, 1, 0, 0),
"SCTLR", "SCTLR_EL2", "SCTLR_EL12" },
{ K(3, 0, 1, 0, 2), K(3, 4, 1, 1, 2), K(3, 5, 1, 0, 2),
"CPACR", "CPTR_EL2", "CPACR_EL12" },
{ K(3, 0, 2, 0, 0), K(3, 4, 2, 0, 0), K(3, 5, 2, 0, 0),
"TTBR0_EL1", "TTBR0_EL2", "TTBR0_EL12" },
{ K(3, 0, 2, 0, 1), K(3, 4, 2, 0, 1), K(3, 5, 2, 0, 1),
"TTBR1_EL1", "TTBR1_EL2", "TTBR1_EL12" },
{ K(3, 0, 2, 0, 2), K(3, 4, 2, 0, 2), K(3, 5, 2, 0, 2),
"TCR_EL1", "TCR_EL2", "TCR_EL12" },
{ K(3, 0, 4, 0, 0), K(3, 4, 4, 0, 0), K(3, 5, 4, 0, 0),
"SPSR_EL1", "SPSR_EL2", "SPSR_EL12" },
{ K(3, 0, 4, 0, 1), K(3, 4, 4, 0, 1), K(3, 5, 4, 0, 1),
"ELR_EL1", "ELR_EL2", "ELR_EL12" },
{ K(3, 0, 5, 1, 0), K(3, 4, 5, 1, 0), K(3, 5, 5, 1, 0),
"AFSR0_EL1", "AFSR0_EL2", "AFSR0_EL12" },
{ K(3, 0, 5, 1, 1), K(3, 4, 5, 1, 1), K(3, 5, 5, 1, 1),
"AFSR1_EL1", "AFSR1_EL2", "AFSR1_EL12" },
{ K(3, 0, 5, 2, 0), K(3, 4, 5, 2, 0), K(3, 5, 5, 2, 0),
"ESR_EL1", "ESR_EL2", "ESR_EL12" },
{ K(3, 0, 6, 0, 0), K(3, 4, 6, 0, 0), K(3, 5, 6, 0, 0),
"FAR_EL1", "FAR_EL2", "FAR_EL12" },
{ K(3, 0, 10, 2, 0), K(3, 4, 10, 2, 0), K(3, 5, 10, 2, 0),
"MAIR_EL1", "MAIR_EL2", "MAIR_EL12" },
{ K(3, 0, 10, 3, 0), K(3, 4, 10, 3, 0), K(3, 5, 10, 3, 0),
"AMAIR0", "AMAIR_EL2", "AMAIR_EL12" },
{ K(3, 0, 12, 0, 0), K(3, 4, 12, 0, 0), K(3, 5, 12, 0, 0),
"VBAR", "VBAR_EL2", "VBAR_EL12" },
{ K(3, 0, 13, 0, 1), K(3, 4, 13, 0, 1), K(3, 5, 13, 0, 1),
"CONTEXTIDR_EL1", "CONTEXTIDR_EL2", "CONTEXTIDR_EL12" },
{ K(3, 0, 14, 1, 0), K(3, 4, 14, 1, 0), K(3, 5, 14, 1, 0),
"CNTKCTL", "CNTHCTL_EL2", "CNTKCTL_EL12" },
/*
* Note that redirection of ZCR is mentioned in the description
* of ZCR_EL2, and aliasing in the description of ZCR_EL1, but
* not in the summary table.
*/
{ K(3, 0, 1, 2, 0), K(3, 4, 1, 2, 0), K(3, 5, 1, 2, 0),
"ZCR_EL1", "ZCR_EL2", "ZCR_EL12", isar_feature_aa64_sve },
/* TODO: ARMv8.2-SPE -- PMSCR_EL2 */
/* TODO: ARMv8.4-Trace -- TRFCR_EL2 */
};
#undef K
size_t i;
for (i = 0; i < ARRAY_SIZE(aliases); i++) {
const struct E2HAlias *a = &aliases[i];
ARMCPRegInfo *src_reg, *dst_reg;
if (a->feature && !a->feature(&cpu->isar)) {
continue;
}
src_reg = g_hash_table_lookup(cpu->cp_regs, &a->src_key);
dst_reg = g_hash_table_lookup(cpu->cp_regs, &a->dst_key);
g_assert(src_reg != NULL);
g_assert(dst_reg != NULL);
/* Cross-compare names to detect typos in the keys. */
g_assert(strcmp(src_reg->name, a->src_name) == 0);
g_assert(strcmp(dst_reg->name, a->dst_name) == 0);
/* None of the core system registers use opaque; we will. */
g_assert(src_reg->opaque == NULL);
/* Create alias before redirection so we dup the right data. */
if (a->new_key) {
ARMCPRegInfo *new_reg = g_memdup(src_reg, sizeof(ARMCPRegInfo));
uint32_t *new_key = g_memdup(&a->new_key, sizeof(uint32_t));
bool ok;
new_reg->name = a->new_name;
new_reg->type |= ARM_CP_ALIAS;
/* Remove PL1/PL0 access, leaving PL2/PL3 R/W in place. */
new_reg->access &= PL2_RW | PL3_RW;
ok = g_hash_table_insert(cpu->cp_regs, new_key, new_reg);
g_assert(ok);
}
src_reg->opaque = dst_reg;
src_reg->orig_readfn = src_reg->readfn ?: raw_read;
src_reg->orig_writefn = src_reg->writefn ?: raw_write;
if (!src_reg->raw_readfn) {
src_reg->raw_readfn = raw_read;
}
if (!src_reg->raw_writefn) {
src_reg->raw_writefn = raw_write;
}
src_reg->readfn = el2_e2h_read;
src_reg->writefn = el2_e2h_write;
}
}
#endif
static CPAccessResult ctr_el0_access(CPUARMState *env, const ARMCPRegInfo *ri,
bool isread)
{
@ -7063,6 +7215,16 @@ void register_cp_regs_for_features(ARMCPU *cpu)
: cpu_isar_feature(aa32_predinv, cpu)) {
define_arm_cp_regs(cpu, predinv_reginfo);
}
#ifndef CONFIG_USER_ONLY
/*
* Register redirections and aliases must be done last,
* after the registers from the other extensions have been defined.
*/
if (arm_feature(env, ARM_FEATURE_EL2) && cpu_isar_feature(aa64_vh, cpu)) {
define_arm_vh_e2h_redirects_aliases(cpu);
}
#endif
}
void arm_cpu_register_gdb_regs_for_features(ARMCPU *cpu)