// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright(c) 2022 Huawei Technologies Co., Ltd */ #include #include #include #include #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(); }