514 lines
11 KiB
C
514 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright(c) 2022 Huawei Technologies Co., Ltd
|
|
*/
|
|
|
|
#include <linux/acpi.h>
|
|
#include <linux/of.h>
|
|
#include <linux/init.h>
|
|
#include <linux/kvm_host.h>
|
|
#include "hisi_virt.h"
|
|
|
|
static enum hisi_cpu_type cpu_type = UNKNOWN_HI_TYPE;
|
|
|
|
static bool dvmbm_enabled;
|
|
|
|
static const char * const hisi_cpu_type_str[] = {
|
|
"Hisi1612",
|
|
"Hisi1616",
|
|
"Hisi1620",
|
|
"HIP09",
|
|
"Unknown"
|
|
};
|
|
|
|
/* ACPI Hisi oem table id str */
|
|
static const char * const oem_str[] = {
|
|
"HIP06", /* Hisi 1612 */
|
|
"HIP07", /* Hisi 1616 */
|
|
"HIP08", /* Hisi 1620 */
|
|
"HIP09" /* HIP09 */
|
|
};
|
|
|
|
/*
|
|
* Probe Hisi CPU type form ACPI.
|
|
*/
|
|
static enum hisi_cpu_type acpi_get_hisi_cpu_type(void)
|
|
{
|
|
struct acpi_table_header *table;
|
|
acpi_status status;
|
|
int i, str_size = ARRAY_SIZE(oem_str);
|
|
|
|
/* Get oem table id from ACPI table header */
|
|
status = acpi_get_table(ACPI_SIG_DSDT, 0, &table);
|
|
if (ACPI_FAILURE(status)) {
|
|
pr_warn("Failed to get ACPI table: %s\n",
|
|
acpi_format_exception(status));
|
|
return UNKNOWN_HI_TYPE;
|
|
}
|
|
|
|
for (i = 0; i < str_size; ++i) {
|
|
if (!strncmp(oem_str[i], table->oem_table_id, 5))
|
|
return i;
|
|
}
|
|
|
|
return UNKNOWN_HI_TYPE;
|
|
}
|
|
|
|
/* of Hisi cpu model str */
|
|
static const char * const of_model_str[] = {
|
|
"Hi1612",
|
|
"Hi1616"
|
|
};
|
|
|
|
/*
|
|
* Probe Hisi CPU type from DT.
|
|
*/
|
|
static enum hisi_cpu_type of_get_hisi_cpu_type(void)
|
|
{
|
|
const char *model;
|
|
int ret, i, str_size = ARRAY_SIZE(of_model_str);
|
|
|
|
/*
|
|
* Note: There may not be a "model" node in FDT, which
|
|
* is provided by the vendor. In this case, we are not
|
|
* able to get CPU type information through this way.
|
|
*/
|
|
ret = of_property_read_string(of_root, "model", &model);
|
|
if (ret < 0) {
|
|
pr_warn("Failed to get Hisi cpu model by OF.\n");
|
|
return UNKNOWN_HI_TYPE;
|
|
}
|
|
|
|
for (i = 0; i < str_size; ++i) {
|
|
if (strstr(model, of_model_str[i]))
|
|
return i;
|
|
}
|
|
|
|
return UNKNOWN_HI_TYPE;
|
|
}
|
|
|
|
void probe_hisi_cpu_type(void)
|
|
{
|
|
if (!acpi_disabled)
|
|
cpu_type = acpi_get_hisi_cpu_type();
|
|
else
|
|
cpu_type = of_get_hisi_cpu_type();
|
|
|
|
kvm_info("detected: Hisi CPU type '%s'\n", hisi_cpu_type_str[cpu_type]);
|
|
}
|
|
|
|
/*
|
|
* We have the fantastic HHA ncsnp capability on Kunpeng 920,
|
|
* with which hypervisor doesn't need to perform a lot of cache
|
|
* maintenance like before (in case the guest has non-cacheable
|
|
* Stage-1 mappings).
|
|
*/
|
|
#define NCSNP_MMIO_BASE 0x20107E238
|
|
bool hisi_ncsnp_supported(void)
|
|
{
|
|
void __iomem *base;
|
|
unsigned int high;
|
|
bool supported = false;
|
|
|
|
if (cpu_type != HI_1620)
|
|
return supported;
|
|
|
|
base = ioremap(NCSNP_MMIO_BASE, 4);
|
|
if (!base) {
|
|
pr_warn("Unable to map MMIO region when probing ncsnp!\n");
|
|
return supported;
|
|
}
|
|
|
|
high = readl_relaxed(base) >> 28;
|
|
iounmap(base);
|
|
if (high != 0x1)
|
|
supported = true;
|
|
|
|
return supported;
|
|
}
|
|
|
|
static int __init early_dvmbm_enable(char *buf)
|
|
{
|
|
return strtobool(buf, &dvmbm_enabled);
|
|
}
|
|
early_param("kvm-arm.dvmbm_enabled", early_dvmbm_enable);
|
|
|
|
static void hardware_enable_dvmbm(void *data)
|
|
{
|
|
u64 val;
|
|
|
|
val = read_sysreg_s(SYS_LSUDVM_CTRL_EL2);
|
|
val |= LSUDVM_CTLR_EL2_MASK;
|
|
write_sysreg_s(val, SYS_LSUDVM_CTRL_EL2);
|
|
}
|
|
|
|
static void hardware_disable_dvmbm(void *data)
|
|
{
|
|
u64 val;
|
|
|
|
val = read_sysreg_s(SYS_LSUDVM_CTRL_EL2);
|
|
val &= ~LSUDVM_CTLR_EL2_MASK;
|
|
write_sysreg_s(val, SYS_LSUDVM_CTRL_EL2);
|
|
}
|
|
|
|
bool hisi_dvmbm_supported(void)
|
|
{
|
|
if (cpu_type != HI_IP09)
|
|
return false;
|
|
|
|
/* Determine whether DVMBM is supported by the hardware */
|
|
if (!(read_sysreg(aidr_el1) & AIDR_EL1_DVMBM_MASK))
|
|
return false;
|
|
|
|
/* User provided kernel command-line parameter */
|
|
if (!dvmbm_enabled || !is_kernel_in_hyp_mode()) {
|
|
on_each_cpu(hardware_disable_dvmbm, NULL, 1);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Enable TLBI Broadcast optimization by setting
|
|
* LSUDVM_CTRL_EL2's bit[0].
|
|
*/
|
|
on_each_cpu(hardware_enable_dvmbm, NULL, 1);
|
|
return true;
|
|
}
|
|
|
|
int kvm_sched_affinity_vcpu_init(struct kvm_vcpu *vcpu)
|
|
{
|
|
if (!kvm_dvmbm_support)
|
|
return 0;
|
|
|
|
if (!zalloc_cpumask_var(&vcpu->arch.sched_cpus, GFP_ATOMIC) ||
|
|
!zalloc_cpumask_var(&vcpu->arch.pre_sched_cpus, GFP_ATOMIC))
|
|
return -ENOMEM;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void kvm_sched_affinity_vcpu_destroy(struct kvm_vcpu *vcpu)
|
|
{
|
|
if (!kvm_dvmbm_support)
|
|
return;
|
|
|
|
free_cpumask_var(vcpu->arch.sched_cpus);
|
|
free_cpumask_var(vcpu->arch.pre_sched_cpus);
|
|
}
|
|
|
|
static void __kvm_write_lsudvmbm(struct kvm *kvm)
|
|
{
|
|
write_sysreg_s(kvm->arch.tlbi_dvmbm, SYS_LSUDVMBM_EL2);
|
|
}
|
|
|
|
static void kvm_write_lsudvmbm(struct kvm *kvm)
|
|
{
|
|
spin_lock(&kvm->arch.sched_lock);
|
|
__kvm_write_lsudvmbm(kvm);
|
|
spin_unlock(&kvm->arch.sched_lock);
|
|
}
|
|
|
|
static int kvm_dvmbm_get_dies_info(struct kvm *kvm, u64 *vm_aff3s, int size)
|
|
{
|
|
int num = 0, cpu;
|
|
|
|
for_each_cpu(cpu, kvm->arch.sched_cpus) {
|
|
bool found = false;
|
|
u64 aff3;
|
|
int i;
|
|
|
|
if (num >= size)
|
|
break;
|
|
|
|
aff3 = MPIDR_AFFINITY_LEVEL(cpu_logical_map(cpu), 3);
|
|
for (i = 0; i < num; i++) {
|
|
if (vm_aff3s[i] == aff3) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
vm_aff3s[num++] = aff3;
|
|
}
|
|
|
|
return num;
|
|
}
|
|
|
|
static u32 socket_num, die_num;
|
|
|
|
static u32 kvm_get_socket_num(void)
|
|
{
|
|
int socket_id[MAX_PG_CFG_SOCKETS], cpu;
|
|
u32 num = 0;
|
|
|
|
for_each_cpu(cpu, cpu_possible_mask) {
|
|
bool found = false;
|
|
u64 aff3, socket;
|
|
int i;
|
|
|
|
aff3 = MPIDR_AFFINITY_LEVEL(cpu_logical_map(cpu), 3);
|
|
/* aff3[7:3]: socket ID */
|
|
socket = (aff3 & SOCKET_ID_MASK) >> SOCKET_ID_SHIFT;
|
|
for (i = 0; i < num; i++) {
|
|
if (socket_id[i] == socket) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
socket_id[num++] = socket;
|
|
}
|
|
return num;
|
|
}
|
|
|
|
static u32 kvm_get_die_num(void)
|
|
{
|
|
int die_id[MAX_DIES_PER_SOCKET], cpu;
|
|
u32 num = 0;
|
|
|
|
for_each_cpu(cpu, cpu_possible_mask) {
|
|
bool found = false;
|
|
u64 aff3, die;
|
|
int i;
|
|
|
|
aff3 = MPIDR_AFFINITY_LEVEL(cpu_logical_map(cpu), 3);
|
|
/* aff3[2:0]: die ID */
|
|
die = aff3 & DIE_ID_MASK;
|
|
for (i = 0; i < num; i++) {
|
|
if (die_id[i] == die) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
die_id[num++] = die;
|
|
}
|
|
return num;
|
|
}
|
|
|
|
static u32 g_die_pg[MAX_PG_CFG_SOCKETS * MAX_DIES_PER_SOCKET][MAX_CLUSTERS_PER_DIE];
|
|
|
|
static void kvm_get_die_pg(unsigned long pg_cfg, int socket_id, int die_id)
|
|
{
|
|
u32 pg_num = 0, i, j;
|
|
u32 pg_flag[MAX_CLUSTERS_PER_DIE];
|
|
u32 die_tmp = socket_id * die_num + die_id;
|
|
|
|
for (i = 0; i < MAX_CLUSTERS_PER_DIE; i++) {
|
|
if (test_bit(i, &pg_cfg))
|
|
pg_num++;
|
|
g_die_pg[die_tmp][i] = i;
|
|
pg_flag[i] = 0;
|
|
}
|
|
|
|
for (i = 0; i < MAX_CLUSTERS_PER_DIE - pg_num; i++) {
|
|
if (test_bit(i, &pg_cfg)) {
|
|
for (j = 0; j < pg_num; j++) {
|
|
u32 cluster_bak = MAX_CLUSTERS_PER_DIE - pg_num + j;
|
|
|
|
if (!test_bit(cluster_bak, &pg_cfg) &&
|
|
!pg_flag[cluster_bak]) {
|
|
pg_flag[cluster_bak] = 1;
|
|
g_die_pg[die_tmp][i] = cluster_bak;
|
|
g_die_pg[die_tmp][cluster_bak] = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void kvm_update_vm_lsudvmbm(struct kvm *kvm)
|
|
{
|
|
u64 mpidr, aff3, aff2, aff1, phy_aff2;
|
|
u64 vm_aff3s[DVMBM_MAX_DIES];
|
|
u64 val;
|
|
int cpu, nr_dies;
|
|
u32 socket_id, die_id;
|
|
|
|
nr_dies = kvm_dvmbm_get_dies_info(kvm, vm_aff3s, DVMBM_MAX_DIES);
|
|
if (nr_dies > 2) {
|
|
val = DVMBM_RANGE_ALL_DIES << DVMBM_RANGE_SHIFT;
|
|
goto out_update;
|
|
}
|
|
|
|
if (nr_dies == 1) {
|
|
val = DVMBM_RANGE_ONE_DIE << DVMBM_RANGE_SHIFT |
|
|
vm_aff3s[0] << DVMBM_DIE1_SHIFT;
|
|
|
|
/* fulfill bits [52:0] */
|
|
for_each_cpu(cpu, kvm->arch.sched_cpus) {
|
|
mpidr = cpu_logical_map(cpu);
|
|
aff3 = MPIDR_AFFINITY_LEVEL(mpidr, 3);
|
|
aff2 = MPIDR_AFFINITY_LEVEL(mpidr, 2);
|
|
aff1 = MPIDR_AFFINITY_LEVEL(mpidr, 1);
|
|
socket_id = (aff3 & SOCKET_ID_MASK) >> SOCKET_ID_SHIFT;
|
|
die_id = (aff3 & DIE_ID_MASK) >> DIE_ID_SHIFT;
|
|
if (die_id == TOTEM_B_ID)
|
|
die_id = 0;
|
|
else
|
|
die_id = 1;
|
|
|
|
phy_aff2 = g_die_pg[socket_id * die_num + die_id][aff2];
|
|
val |= 1ULL << (phy_aff2 * 4 + aff1);
|
|
}
|
|
|
|
goto out_update;
|
|
}
|
|
|
|
/* nr_dies == 2 */
|
|
val = DVMBM_RANGE_TWO_DIES << DVMBM_RANGE_SHIFT |
|
|
DVMBM_GRAN_CLUSTER << DVMBM_GRAN_SHIFT |
|
|
vm_aff3s[0] << DVMBM_DIE1_SHIFT |
|
|
vm_aff3s[1] << DVMBM_DIE2_SHIFT;
|
|
|
|
/* and fulfill bits [43:0] */
|
|
for_each_cpu(cpu, kvm->arch.sched_cpus) {
|
|
mpidr = cpu_logical_map(cpu);
|
|
aff3 = MPIDR_AFFINITY_LEVEL(mpidr, 3);
|
|
aff2 = MPIDR_AFFINITY_LEVEL(mpidr, 2);
|
|
socket_id = (aff3 & SOCKET_ID_MASK) >> SOCKET_ID_SHIFT;
|
|
die_id = (aff3 & DIE_ID_MASK) >> DIE_ID_SHIFT;
|
|
if (die_id == TOTEM_B_ID)
|
|
die_id = 0;
|
|
else
|
|
die_id = 1;
|
|
|
|
if (aff3 == vm_aff3s[0]) {
|
|
phy_aff2 = g_die_pg[socket_id * die_num + die_id][aff2];
|
|
val |= 1ULL << (phy_aff2 + DVMBM_DIE1_CLUSTER_SHIFT);
|
|
} else {
|
|
phy_aff2 = g_die_pg[socket_id * die_num + die_id][aff2];
|
|
val |= 1ULL << (phy_aff2 + DVMBM_DIE2_CLUSTER_SHIFT);
|
|
}
|
|
}
|
|
|
|
out_update:
|
|
kvm->arch.tlbi_dvmbm = val;
|
|
}
|
|
|
|
void kvm_tlbi_dvmbm_vcpu_load(struct kvm_vcpu *vcpu)
|
|
{
|
|
struct kvm *kvm = vcpu->kvm;
|
|
struct kvm_vcpu *tmp;
|
|
cpumask_t mask;
|
|
unsigned long i;
|
|
|
|
/* Don't bother on old hardware */
|
|
if (!kvm_dvmbm_support)
|
|
return;
|
|
|
|
cpumask_copy(vcpu->arch.sched_cpus, current->cpus_ptr);
|
|
|
|
if (likely(cpumask_equal(vcpu->arch.sched_cpus,
|
|
vcpu->arch.pre_sched_cpus))) {
|
|
kvm_write_lsudvmbm(kvm);
|
|
return;
|
|
}
|
|
|
|
/* Re-calculate sched_cpus for this VM */
|
|
spin_lock(&kvm->arch.sched_lock);
|
|
|
|
cpumask_clear(&mask);
|
|
kvm_for_each_vcpu(i, tmp, kvm) {
|
|
/*
|
|
* We may get the stale sched_cpus if another thread
|
|
* is concurrently changing its affinity. It'll
|
|
* eventually go through vcpu_load() and we rely on
|
|
* the last sched_lock holder to make things correct.
|
|
*/
|
|
cpumask_or(&mask, &mask, tmp->arch.sched_cpus);
|
|
}
|
|
|
|
if (cpumask_equal(kvm->arch.sched_cpus, &mask))
|
|
goto out_unlock;
|
|
|
|
cpumask_copy(kvm->arch.sched_cpus, &mask);
|
|
|
|
kvm_flush_remote_tlbs(kvm);
|
|
|
|
/*
|
|
* Re-calculate LSUDVMBM_EL2 for this VM and kick all vcpus
|
|
* out to reload the LSUDVMBM configuration.
|
|
*/
|
|
kvm_update_vm_lsudvmbm(kvm);
|
|
kvm_make_all_cpus_request(kvm, KVM_REQ_RELOAD_TLBI_DVMBM);
|
|
|
|
out_unlock:
|
|
__kvm_write_lsudvmbm(kvm);
|
|
spin_unlock(&kvm->arch.sched_lock);
|
|
}
|
|
|
|
void kvm_tlbi_dvmbm_vcpu_put(struct kvm_vcpu *vcpu)
|
|
{
|
|
if (!kvm_dvmbm_support)
|
|
return;
|
|
|
|
cpumask_copy(vcpu->arch.pre_sched_cpus, vcpu->arch.sched_cpus);
|
|
}
|
|
|
|
void kvm_get_pg_cfg(void)
|
|
{
|
|
void __iomem *mn_base;
|
|
u32 i, j;
|
|
u32 pg_cfgs[MAX_PG_CFG_SOCKETS * MAX_DIES_PER_SOCKET];
|
|
u64 mn_phy_base;
|
|
u32 val;
|
|
|
|
socket_num = kvm_get_socket_num();
|
|
die_num = kvm_get_die_num();
|
|
|
|
for (i = 0; i < socket_num; i++) {
|
|
for (j = 0; j < die_num; j++) {
|
|
|
|
/*
|
|
* totem B means the first CPU DIE within a SOCKET,
|
|
* totem A means the second one.
|
|
*/
|
|
mn_phy_base = (j == 0) ? TB_MN_BASE : TA_MN_BASE;
|
|
mn_phy_base += CHIP_ADDR_OFFSET(i);
|
|
mn_phy_base += MN_ECO0_OFFSET;
|
|
|
|
mn_base = ioremap(mn_phy_base, 4);
|
|
if (!mn_base) {
|
|
kvm_info("MN base addr ioremap failed\n");
|
|
return;
|
|
}
|
|
val = readl_relaxed(mn_base);
|
|
pg_cfgs[j + i * die_num] = val & 0xff;
|
|
kvm_get_die_pg(pg_cfgs[j + i * die_num], i, j);
|
|
iounmap(mn_base);
|
|
}
|
|
}
|
|
}
|
|
|
|
int kvm_sched_affinity_vm_init(struct kvm *kvm)
|
|
{
|
|
if (!kvm_dvmbm_support)
|
|
return 0;
|
|
|
|
spin_lock_init(&kvm->arch.sched_lock);
|
|
if (!zalloc_cpumask_var(&kvm->arch.sched_cpus, GFP_ATOMIC))
|
|
return -ENOMEM;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void kvm_sched_affinity_vm_destroy(struct kvm *kvm)
|
|
{
|
|
if (!kvm_dvmbm_support)
|
|
return;
|
|
|
|
free_cpumask_var(kvm->arch.sched_cpus);
|
|
}
|
|
|
|
void kvm_hisi_reload_lsudvmbm(struct kvm *kvm)
|
|
{
|
|
if (WARN_ON_ONCE(!kvm_dvmbm_support))
|
|
return;
|
|
|
|
preempt_disable();
|
|
kvm_write_lsudvmbm(kvm);
|
|
preempt_enable();
|
|
}
|