Commit f1f0c0cf authored by Alexandru Elisei's avatar Alexandru Elisei Committed by Marc Zyngier

KVM: arm64: Don't BUG_ON() if emulated register table is unsorted

To emulate a register access, KVM uses a table of registers sorted by
register encoding to speed up queries using binary search.

When Linux boots, KVM checks that the table is sorted and uses a BUG_ON()
statement to let the user know if it's not. The unfortunate side effect is
that an unsorted sysreg table brings down the whole kernel, not just KVM,
even though the rest of the kernel can function just fine without KVM. To
make matters worse, on machines which lack a serial console, the user is
left pondering why the machine is taking so long to boot.

Improve this situation by returning an error from kvm_arch_init() if the
sysreg tables are not in the correct order. The machine is still very much
usable for the user, with the exception of virtualization, who can now
easily determine what went wrong.

A minor typo has also been corrected in the check_sysreg_table() function.
Signed-off-by: default avatarAlexandru Elisei <alexandru.elisei@arm.com>
Signed-off-by: default avatarMarc Zyngier <maz@kernel.org>
Link: https://lore.kernel.org/r/20220428103405.70884-2-alexandru.elisei@arm.com
parent b2d229d4
...@@ -686,7 +686,7 @@ int kvm_handle_sys_reg(struct kvm_vcpu *vcpu); ...@@ -686,7 +686,7 @@ int kvm_handle_sys_reg(struct kvm_vcpu *vcpu);
void kvm_reset_sys_regs(struct kvm_vcpu *vcpu); void kvm_reset_sys_regs(struct kvm_vcpu *vcpu);
void kvm_sys_reg_table_init(void); int kvm_sys_reg_table_init(void);
/* MMIO helpers */ /* MMIO helpers */
void kvm_mmio_write_buf(void *buf, unsigned int len, unsigned long data); void kvm_mmio_write_buf(void *buf, unsigned int len, unsigned long data);
......
...@@ -1761,8 +1761,6 @@ static int init_subsystems(void) ...@@ -1761,8 +1761,6 @@ static int init_subsystems(void)
kvm_register_perf_callbacks(NULL); kvm_register_perf_callbacks(NULL);
kvm_sys_reg_table_init();
out: out:
if (err || !is_protected_kvm_enabled()) if (err || !is_protected_kvm_enabled())
on_each_cpu(_kvm_arch_hardware_disable, NULL, 1); on_each_cpu(_kvm_arch_hardware_disable, NULL, 1);
...@@ -2089,6 +2087,12 @@ int kvm_arch_init(void *opaque) ...@@ -2089,6 +2087,12 @@ int kvm_arch_init(void *opaque)
return -ENODEV; return -ENODEV;
} }
err = kvm_sys_reg_table_init();
if (err) {
kvm_info("Error initializing system register tables");
return err;
}
in_hyp_mode = is_kernel_in_hyp_mode(); in_hyp_mode = is_kernel_in_hyp_mode();
if (cpus_have_final_cap(ARM64_WORKAROUND_DEVICE_LOAD_ACQUIRE) || if (cpus_have_final_cap(ARM64_WORKAROUND_DEVICE_LOAD_ACQUIRE) ||
......
...@@ -2187,25 +2187,24 @@ static const struct sys_reg_desc cp15_64_regs[] = { ...@@ -2187,25 +2187,24 @@ static const struct sys_reg_desc cp15_64_regs[] = {
{ SYS_DESC(SYS_AARCH32_CNTP_CVAL), access_arch_timer }, { SYS_DESC(SYS_AARCH32_CNTP_CVAL), access_arch_timer },
}; };
static int check_sysreg_table(const struct sys_reg_desc *table, unsigned int n, static bool check_sysreg_table(const struct sys_reg_desc *table, unsigned int n,
bool is_32) bool is_32)
{ {
unsigned int i; unsigned int i;
for (i = 0; i < n; i++) { for (i = 0; i < n; i++) {
if (!is_32 && table[i].reg && !table[i].reset) { if (!is_32 && table[i].reg && !table[i].reset) {
kvm_err("sys_reg table %p entry %d has lacks reset\n", kvm_err("sys_reg table %p entry %d lacks reset\n", table, i);
table, i); return false;
return 1;
} }
if (i && cmp_sys_reg(&table[i-1], &table[i]) >= 0) { if (i && cmp_sys_reg(&table[i-1], &table[i]) >= 0) {
kvm_err("sys_reg table %p out of order (%d)\n", table, i - 1); kvm_err("sys_reg table %p out of order (%d)\n", table, i - 1);
return 1; return false;
} }
} }
return 0; return true;
} }
int kvm_handle_cp14_load_store(struct kvm_vcpu *vcpu) int kvm_handle_cp14_load_store(struct kvm_vcpu *vcpu)
...@@ -2860,18 +2859,22 @@ int kvm_arm_copy_sys_reg_indices(struct kvm_vcpu *vcpu, u64 __user *uindices) ...@@ -2860,18 +2859,22 @@ int kvm_arm_copy_sys_reg_indices(struct kvm_vcpu *vcpu, u64 __user *uindices)
return write_demux_regids(uindices); return write_demux_regids(uindices);
} }
void kvm_sys_reg_table_init(void) int kvm_sys_reg_table_init(void)
{ {
bool valid = true;
unsigned int i; unsigned int i;
struct sys_reg_desc clidr; struct sys_reg_desc clidr;
/* Make sure tables are unique and in order. */ /* Make sure tables are unique and in order. */
BUG_ON(check_sysreg_table(sys_reg_descs, ARRAY_SIZE(sys_reg_descs), false)); valid &= check_sysreg_table(sys_reg_descs, ARRAY_SIZE(sys_reg_descs), false);
BUG_ON(check_sysreg_table(cp14_regs, ARRAY_SIZE(cp14_regs), true)); valid &= check_sysreg_table(cp14_regs, ARRAY_SIZE(cp14_regs), true);
BUG_ON(check_sysreg_table(cp14_64_regs, ARRAY_SIZE(cp14_64_regs), true)); valid &= check_sysreg_table(cp14_64_regs, ARRAY_SIZE(cp14_64_regs), true);
BUG_ON(check_sysreg_table(cp15_regs, ARRAY_SIZE(cp15_regs), true)); valid &= check_sysreg_table(cp15_regs, ARRAY_SIZE(cp15_regs), true);
BUG_ON(check_sysreg_table(cp15_64_regs, ARRAY_SIZE(cp15_64_regs), true)); valid &= check_sysreg_table(cp15_64_regs, ARRAY_SIZE(cp15_64_regs), true);
BUG_ON(check_sysreg_table(invariant_sys_regs, ARRAY_SIZE(invariant_sys_regs), false)); valid &= check_sysreg_table(invariant_sys_regs, ARRAY_SIZE(invariant_sys_regs), false);
if (!valid)
return -EINVAL;
/* We abuse the reset function to overwrite the table itself. */ /* We abuse the reset function to overwrite the table itself. */
for (i = 0; i < ARRAY_SIZE(invariant_sys_regs); i++) for (i = 0; i < ARRAY_SIZE(invariant_sys_regs); i++)
...@@ -2894,4 +2897,6 @@ void kvm_sys_reg_table_init(void) ...@@ -2894,4 +2897,6 @@ void kvm_sys_reg_table_init(void)
break; break;
/* Clear all higher bits. */ /* Clear all higher bits. */
cache_levels &= (1 << (i*3))-1; cache_levels &= (1 << (i*3))-1;
return 0;
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment