[PATCH 0/4] hw/arm: add TLBI virtual domain support for the virt machine
Background ========== On ARM, TLB invalidation instructions (TLBI) traditionally broadcast to all PEs sharing a translation regime. In large KVM guests this becomes expensive: each invalidation reaches every vCPU even when the affected translations are only used by a subset of PEs, e.g. a sub-cluster running one workload group. The bigger the guest, the more of the invalidation traffic is wasted on unaffected PEs. The ARM architecture addresses this with TLBI domains: each PE can be programmed to belong to several (virtual) domains, and domain-targeted TLBI variants then invalidate only within the given domain instead of broadcasting. When the host KVM exposes virtual TLBI domains (vdomains), the guest can group its vCPUs and use domain-scoped TLBI to cut invalidation traffic to the relevant subset of PEs. Using this from a guest requires the VMM to do three things: let the user describe the guest's domain topology, program each vCPU's domain membership into KVM, and describe the topology to the guest so its kernel can discover and use it. The guest kernel detects the TLBID field in ID_AA64MMFR4_EL1 and parses an ACPI "TLBI" table to learn the per-PE domain membership. Design Overview =============== The series adds the following to the ARM virt machine: 1. KVM capability query helpers (kvm_arm_vtlbid_supported(), kvm_arm_get_max_vdomains()), together with the corresponding KVM uapi definitions (KVM_CAP_ARM_TLBIDOMAIN, KVM_ARM_GET_VDOMAIN_NUM, KVM_ARM_VCPU_SET_VDOMAIN) in linux-headers. 2. A -tlbidomain command-line option, parsed like the NUMA options into per-domain CPU bitmaps held in VirtMachineState. A vCPU's membership is computed on demand as a uint32 bitmask of domain IDs (virt_cpu_vdomain_bitmap()): -tlbidomain domain-id=0,cpus=0-7 -tlbidomain domain-id=1,cpus=0-3,cpus=5-7 -tlbidomain domain-id=2,cpus=4 Domain 0 is the broadcast domain and must cover all vCPUs; domain IDs must be contiguous from 0; the total number must not exceed what KVM supports. With TCG the topology is only reflected in the ACPI table (a warning is printed). 3. Per-vCPU KVM configuration at vCPU init (kvm_arm_vtlbid_init()): the vdomain bitmap is pushed to KVM via KVM_ARM_VCPU_SET_VDOMAIN, and the TLBID field of ID_AA64MMFR4_EL1 is registered in the sysreg properties table so the writable-idreg machinery exposes it to the guest when KVM reports the bits writable. All possible vCPUs are configured before the VM starts, so vCPU hot-plug needs no extra handling, and MMFR4 is read back from KVM so the TLBID bit survives hot-plug. 4. ACPI table generation: build_tlbi() emits the "TLBI" table with one PE Component subtable per vCPU listing the domain IDs it belongs to; the guest kernel's acpi_tlbi_init() parses it to build the TLBI domain topology. Example topology for a 8-vCPU guest: ---------------------------------------------------------------------- vCPU | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 ---------------------------------------------------------------------- domain 0 (broadcast) | x | x | x | x | x | x | x | x domain 1 | x | x | x | x | | x | x | x domain 2 | | | | | x | | | ---------------------------------------------------------------------- A TLBI issued by vCPU 5 with domain 1 only invalidates translations shared by the vCPUs of domain 1, instead of all 8 vCPUs. This series depends on the companion kernel series that adds KVM_CAP_ARM_TLBIDOMAIN support and on a guest kernel that parses the TLBI ACPI table. Tian Zheng (4): target/arm: add KVM TLBI domain capability query helpers hw/arm/virt: add -tlbidomain command-line option and parsing target/arm: configure KVM TLBI vdomain at vCPU init hw/arm/virt-acpi: generate TLBI ACPI table hw/arm/virt-acpi-build.c | 73 ++++++++++++- hw/arm/virt.c | 161 +++++++++++++++++++++++++++++ include/hw/arm/virt.h | 13 +++ linux-headers/linux/kvm.h | 15 +++ qapi/machine.json | 16 +++++ qemu-options.hx | 17 +++++ system/vl.c | 16 +++++ target/arm/cpu-sysreg-properties.c | 1 + target/arm/kvm64.c | 82 +++++++++++++++ target/arm/kvm_arm.h | 25 +++++ 10 files changed, 418 insertions(+), 1 deletion(-) -- 2.33.0
Add kvm_arm_vtlbid_supported() to check KVM_CAP_ARM_TLBIDOMAIN and kvm_arm_get_max_vdomains() to query the maximum number of TLBI domains supported by KVM via the KVM_ARM_GET_VDOMAIN_NUM ioctl. Add the corresponding KVM uapi definitions (KVM_CAP_ARM_TLBIDOMAIN and struct kvm_arm_get_vdomain) to linux-headers. These helpers are used by the subsequent -tlbidomain command-line parsing to validate the requested domain topology against KVM support. Signed-off-by: Tian Zheng <zhengtian10@huawei.com> --- linux-headers/linux/kvm.h | 8 ++++++++ target/arm/kvm64.c | 24 ++++++++++++++++++++++++ target/arm/kvm_arm.h | 25 +++++++++++++++++++++++++ 3 files changed, 57 insertions(+) diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h index 71ee851e6e..4d5792a95d 100644 --- a/linux-headers/linux/kvm.h +++ b/linux-headers/linux/kvm.h @@ -961,6 +961,7 @@ struct kvm_enable_cap { #define KVM_CAP_HYGON_COCO_EXT_CSV3_SP_MGR (1 << 4) #define KVM_CAP_ARM_HW_DIRTY_STATE_TRACK 502 +#define KVM_CAP_ARM_TLBIDOMAIN 503 #define KVM_CAP_ARM_HISI_IPIV 798 #define KVM_CAP_ARM_VIRT_MSI_BYPASS 799 @@ -1841,4 +1842,11 @@ struct kvm_create_guest_memfd { __u64 reserved[6]; }; +/* Available with KVM_CAP_ARM_TLBIDOMAIN */ +struct kvm_arm_get_vdomain { + __u8 max_vdomains; +}; + +#define KVM_ARM_GET_VDOMAIN_NUM _IOW(KVMIO, 0xd7, struct kvm_arm_get_vdomain) + #endif /* __LINUX_KVM_H */ diff --git a/target/arm/kvm64.c b/target/arm/kvm64.c index ad202de259..a1255956f3 100644 --- a/target/arm/kvm64.c +++ b/target/arm/kvm64.c @@ -782,6 +782,30 @@ bool kvm_arm_vtimer_status_supported(void) return kvm_vm_check_extension(kvm_state, KVM_CAP_ARM_HISI_PVTIMER_STATUS); } +bool kvm_arm_vtlbid_supported(void) +{ + return kvm_check_extension(kvm_state, KVM_CAP_ARM_TLBIDOMAIN); +} + +uint8_t kvm_arm_get_max_vdomains(void) +{ + struct kvm_arm_get_vdomain arg; + int ret; + + if (!kvm_arm_vtlbid_supported()) { + return 0; + } + + ret = kvm_vm_ioctl(kvm_state, KVM_ARM_GET_VDOMAIN_NUM, &arg); + if (ret < 0) { + error_report("Failed to get max vdomains from KVM: %s", + strerror(errno)); + return 0; + } + + return arg.max_vdomains; +} + QEMU_BUILD_BUG_ON(KVM_ARM64_SVE_VQ_MIN != 1); uint32_t kvm_arm_sve_get_vls(CPUState *cs) diff --git a/target/arm/kvm_arm.h b/target/arm/kvm_arm.h index 85b34533dd..645d1a8b45 100644 --- a/target/arm/kvm_arm.h +++ b/target/arm/kvm_arm.h @@ -329,6 +329,21 @@ void kvm_arm_steal_time_finalize(ARMCPU *cpu, Error **errp); */ bool kvm_arm_steal_time_supported(void); +/** + * kvm_arm_vtlbid_supported: + * + * Returns: true if KVM supports TLBI vdomain ioctl + * and false otherwise. + */ +bool kvm_arm_vtlbid_supported(void); + +/** + * kvm_arm_get_max_vdomains: + * + * Returns: max number of vdomains supported by KVM (0 if not supported). + */ +uint8_t kvm_arm_get_max_vdomains(void); + /** * kvm_arm_vtimer_status_finalize: * @cpu: ARMCPU for which to finalize kvm-vtimer-status @@ -570,6 +585,16 @@ static inline bool kvm_arm_vtimer_status_supported(void) return false; } +static inline bool kvm_arm_vtlbid_supported(void) +{ + return false; +} + +static inline uint8_t kvm_arm_get_max_vdomains(void) +{ + return 0; +} + static inline void kvm_arm_rme_init_guest_ram(hwaddr base, size_t size) { } -- 2.33.0
Add the -tlbidomain command-line option for the ARM virt machine, allowing users to define TLBI domain topology by mapping domain IDs to vCPU ranges. Supports multiple cpus= values per domain entry: -tlbidomain domain-id=0,cpus=0-7 -tlbidomain domain-id=1,cpus=0-3,cpus=5-7 -tlbidomain domain-id=2,cpus=4 Each cpus= value accepts a single CPU (N) or a range (N-M). Domain 0 must cover all vCPUs (broadcast domain), and domain IDs must be contiguous starting from 0. The option is parsed into per-domain CPU bitmaps (like NUMA's approach) held in VirtMachineState, simplifying both validation and later table generation. Per-vCPU domain membership is computed on demand via virt_cpu_vdomain_bitmap() and consumed by the KVM vdomain setup and the TLBI ACPI table generation. If KVM is enabled, the requested topology is validated against the number of vdomains KVM supports, queried via kvm_arm_get_max_vdomains(). Because the tlbidomain QemuOptsList is unconditionally registered at startup, qemu_find_opts_err("tlbidomain") always returns non-NULL; guard with QTAILQ_EMPTY (consistent with the accel option handling in vl.c) so VMs start normally when no -tlbidomain option is given, even on hosts without TLBI vdomain support. Signed-off-by: Tian Zheng <zhengtian10@huawei.com> Signed-off-by: Jinqian Yang <yangjinqian1@huawei.com> --- hw/arm/virt.c | 161 ++++++++++++++++++++++++++++++++++++++++++ include/hw/arm/virt.h | 13 ++++ qapi/machine.json | 16 +++++ qemu-options.hx | 17 +++++ system/vl.c | 16 +++++ 5 files changed, 223 insertions(+) diff --git a/hw/arm/virt.c b/hw/arm/virt.c index f061b1c069..72e31ec6a6 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -33,7 +33,9 @@ #include "qemu/datadir.h" #include "qemu/units.h" #include "qemu/option.h" +#include "qemu/config-file.h" #include "qemu/log.h" +#include "qemu/bitmap.h" #include "monitor/qdev.h" #include "hw/sysbus.h" #include "hw/arm/boot.h" @@ -91,6 +93,8 @@ #include "hw/char/pl011.h" #include "qemu/guest-random.h" #include "qapi/qmp/qdict.h" +#include "qapi/opts-visitor.h" +#include "qapi/qapi-visit-machine.h" #include "qemu/log.h" #ifdef CONFIG_UB #include "hw/ub/ub.h" @@ -304,6 +308,7 @@ static int virt_get_socket_id(const MachineState *ms, int cpu_index); static int virt_get_cluster_id(const MachineState *ms, int cpu_index); static int virt_get_core_id(const MachineState *ms, int cpu_index); static int virt_get_thread_id(const MachineState *ms, int cpu_index); +static void virt_parse_vtlbidomain(VirtMachineState *vms); static bool cpu_type_valid(const char *cpu) { @@ -2824,6 +2829,8 @@ static void machvirt_init(MachineState *machine) finalize_gic_version(vms); + virt_parse_vtlbidomain(vms); + possible_cpus = mc->possible_cpu_arch_ids(machine); /* @@ -3378,6 +3385,160 @@ static void virt_set_oem_table_id(Object *obj, const char *value, strncpy(vms->oem_table_id, value, 8); } +/* QemuOpts foreach callback: parse one -tlbidomain option entry */ +static int virt_parse_one_vtlbidomain(void *opaque, QemuOpts *opts, + Error **errp) +{ + VirtMachineState *vms = opaque; + TlbiVdomainConfig *d; + TlbiDomainOptions *tdo = NULL; + uint8_t vdomain_id; + uint16List *cpus; + int i; + Error *err = NULL; + Visitor *v = opts_visitor_new(opts); + + visit_type_TlbiDomainOptions(v, NULL, &tdo, &err); + visit_free(v); + if (err) { + error_report("tlbidomain: OptsVisitor parse failed: %s", + error_get_pretty(err)); + error_free(err); + return -1; + } + + if (vms->num_vdomains >= vms->max_vdomains) { + error_report("tlbidomain: too many vdomains, max %d", + vms->max_vdomains); + exit(1); + } + + if (!tdo->has_cpus) { + error_report("tlbidomain: missing cpus"); + exit(1); + } + if (!tdo->has_domain_id) { + error_report("tlbidomain: missing domain-id"); + exit(1); + } + vdomain_id = tdo->domain_id; + if (vdomain_id >= vms->max_vdomains) { + error_report("tlbidomain: invalid domain-id %u (max %d)", + vdomain_id, vms->max_vdomains - 1); + exit(1); + } + for (i = 0; i < vms->num_vdomains; i++) { + if (vms->vdomains[i].vdomain_id == vdomain_id) { + error_report("tlbidomain: duplicate domain-id %u", vdomain_id); + exit(1); + } + } + + d = &vms->vdomains[vms->num_vdomains]; + d->vdomain_id = vdomain_id; + d->cpu_bitmap = bitmap_new(MACHINE(vms)->smp.max_cpus); + + for (cpus = tdo->cpus; cpus; cpus = cpus->next) { + if (cpus->value >= MACHINE(vms)->smp.max_cpus) { + error_report("tlbidomain: cpu %u >= max_cpus %u", + cpus->value, MACHINE(vms)->smp.max_cpus); + exit(1); + } + bitmap_set(d->cpu_bitmap, cpus->value, 1); + } + if (bitmap_empty(d->cpu_bitmap, MACHINE(vms)->smp.max_cpus)) { + error_report("tlbidomain: vdomain %u has no cpus", vdomain_id); + exit(1); + } + + qapi_free_TlbiDomainOptions(tdo); + vms->num_vdomains++; + return 0; +} + +uint32_t virt_cpu_vdomain_bitmap(VirtMachineState *vms, int cpu_index) +{ + int i; + uint32_t bm = 0; + + for (i = 0; i < vms->num_vdomains; i++) { + if (test_bit(cpu_index, vms->vdomains[i].cpu_bitmap)) { + bm |= (1U << vms->vdomains[i].vdomain_id); + } + } + return bm; +} + +/* + * Parse all -tlbidomain options, validate constraints: + * - vdomain 0 (broadcast) must cover every vCPU + * - vdomain IDs must be contiguous starting from 0 + * - Total vdomains must not exceed KVM max_vdomains + */ +static void virt_parse_vtlbidomain(VirtMachineState *vms) +{ + QemuOptsList *olist = qemu_find_opts_err("tlbidomain", NULL); + MachineState *ms = MACHINE(vms); + unsigned int max_cpus = ms->smp.max_cpus; + uint8_t max_vdomains; + int i; + + if (!olist || QTAILQ_EMPTY(&olist->head)) { + vms->max_vdomains = 0; + vms->vdomains = NULL; + return; + } + + if (!kvm_enabled()) { + warn_report("tlbidomain: not running under KVM, " + "TLBI domain will only be reflected in ACPI table"); + max_vdomains = VDOMAIN_BITMAP_BITS; + } else { + max_vdomains = kvm_arm_get_max_vdomains(); + if (max_vdomains == 0) { + error_report("tlbidomain: KVM does not support TLBI vdomain"); + exit(1); + } + } + + vms->max_vdomains = max_vdomains; + vms->vdomains = g_new0(TlbiVdomainConfig, max_vdomains); + + if (qemu_opts_foreach(olist, virt_parse_one_vtlbidomain, + vms, NULL) < 0) { + exit(1); + } + + if (vms->num_vdomains == 0) { + return; + } + + /* Validate: vdomain 0 must be the broadcast vdomain (all vCPUs) */ + for (i = 0; i < vms->num_vdomains; i++) { + if (vms->vdomains[i].vdomain_id == 0) { + if (!bitmap_full(vms->vdomains[i].cpu_bitmap, max_cpus)) { + error_report("tlbidomain: vdomain 0 must cover all cpus [0-%u]", + max_cpus - 1); + exit(1); + } + break; + } + } + + /* Validate: vdomain IDs are contiguous [0 .. num_vdomains-1] */ + unsigned long *id_bm = bitmap_new(max_vdomains); + for (i = 0; i < vms->num_vdomains; i++) { + bitmap_set(id_bm, vms->vdomains[i].vdomain_id, 1); + } + if (bitmap_count_one(id_bm, max_vdomains) != vms->num_vdomains || + find_last_bit(id_bm, max_vdomains) != + (unsigned long)(vms->num_vdomains - 1)) { + error_report("tlbidomain: vdomain IDs must be contiguous from 0"); + exit(1); + } + g_free(id_bm); +} + static TargetImplCpu target_impl_cpus[MAX_TARGET_IMPL_CPUS]; static void virt_set_target_impl_cpus(Object *obj, const char *value, diff --git a/include/hw/arm/virt.h b/include/hw/arm/virt.h index 5dd77096cd..51ad5b0531 100644 --- a/include/hw/arm/virt.h +++ b/include/hw/arm/virt.h @@ -32,6 +32,7 @@ #include "exec/hwaddr.h" #include "qemu/notify.h" +#include "qemu/bitmap.h" #include "hw/boards.h" #include "hw/arm/boot.h" #include "hw/arm/bsa.h" @@ -217,6 +218,14 @@ typedef enum VirtGICType { #define VIRT_GIC_VERSION_3_MASK BIT(VIRT_GIC_VERSION_3) #define VIRT_GIC_VERSION_4_MASK BIT(VIRT_GIC_VERSION_4) +#define VDOMAIN_BITMAP_BITS 32 /* uint32_t vdomain_bitmap width */ + +/* TLBI vdomain: maps a vdomain ID to the set of vCPUs belonging to it */ +typedef struct TlbiVdomainConfig { + uint8_t vdomain_id; + unsigned long *cpu_bitmap; +} TlbiVdomainConfig; + struct VirtMachineClass { MachineClass parent; bool disallow_affinity_adjustment; @@ -310,6 +319,9 @@ struct VirtMachineState { char *kvm_type; NotifierList cpuhp_notifiers; Object *event_log; + TlbiVdomainConfig *vdomains; + int num_vdomains; + uint8_t max_vdomains; /* max vdomains from KVM (via ioctl) */ }; #define VIRT_ECAM_ID(high) (high ? VIRT_HIGH_PCIE_ECAM : VIRT_PCIE_ECAM) @@ -319,6 +331,7 @@ OBJECT_DECLARE_TYPE(VirtMachineState, VirtMachineClass, VIRT_MACHINE) void virt_acpi_setup(VirtMachineState *vms); bool virt_is_acpi_enabled(VirtMachineState *vms); +uint32_t virt_cpu_vdomain_bitmap(VirtMachineState *vms, int cpu_index); void virt_madt_cpu_entry(int uid, const CPUArchIdList *cpu_list, GArray *entry, bool force_enabled); bool cpu_l1_cache_unified(int cpu); diff --git a/qapi/machine.json b/qapi/machine.json index edb741aac7..a5033ab329 100644 --- a/qapi/machine.json +++ b/qapi/machine.json @@ -1969,6 +1969,22 @@ '*max-size': 'size', '*slots': 'uint64' } } +## +# @TlbiDomainOptions: +# +# Configure a TLBI vdomain. (for OptsVisitor) +# +# @domain-id: TLBI vdomain ID +# +# @cpus: VCPUs belonging to this vdomain +# +# Since: 8.2 +## +{ 'struct': 'TlbiDomainOptions', + 'data': { + '*domain-id': 'uint8', + '*cpus': ['uint16'] } } + ## # @dumpdtb: # diff --git a/qemu-options.hx b/qemu-options.hx index ec31c4d30a..105ff03bda 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -581,6 +581,23 @@ SRST -numa hmat-cache,node-id=1,size=10K,level=1,associativity=direct,policy=write-back,line=8 ERST +DEF("tlbidomain", HAS_ARG, QEMU_OPTION_tlbidomain, + "-tlbidomain domain-id=id,cpus=cpurange[,cpus=cpurange]\n", + QEMU_ARCH_ALL) +SRST +``-tlbidomain domain-id=id,cpus=firstcpu[-lastcpu][,cpus=firstcpu[-lastcpu]]`` + Define a TLBI domain and assign vCPUs to it. ``domain-id`` is + the domain identifier. ``cpus`` can be a single CPU index or a + range. Multiple ``cpus`` entries accumulate. Domain 0 must + cover all vCPUs. Domain IDs must be contiguous from 0. + + Example:: + + -tlbidomain domain-id=0,cpus=0-7 + -tlbidomain domain-id=1,cpus=0-3,cpus=5-7 + -tlbidomain domain-id=2,cpus=1,cpus=3,cpus=6 +ERST + DEF("add-fd", HAS_ARG, QEMU_OPTION_add_fd, "-add-fd fd=fd,set=set[,opaque=opaque]\n" " Add 'fd' to fd 'set'\n", QEMU_ARCH_ALL) diff --git a/system/vl.c b/system/vl.c index bcf5211e4c..bf3bc55dee 100644 --- a/system/vl.c +++ b/system/vl.c @@ -738,6 +738,14 @@ static void configure_blockdev(BlockdevOptionsQueue *bdo_queue, } +/* QemuOpts definition for -tlbidomain command-line option */ +static QemuOptsList qemu_tlbidomain_opts = { + .name = "tlbidomain", + .implied_opt_name = "domain-id", + .head = QTAILQ_HEAD_INITIALIZER(qemu_tlbidomain_opts.head), + .desc = { { 0 } } /* validated with OptsVisitor */ +}; + static QemuOptsList qemu_smp_opts = { .name = "smp-opts", .implied_opt_name = "cpus", @@ -2844,6 +2852,7 @@ void qemu_init(int argc, char **argv) qemu_add_opts(&qemu_msg_opts); qemu_add_opts(&qemu_name_opts); qemu_add_opts(&qemu_numa_opts); + qemu_add_opts(&qemu_tlbidomain_opts); qemu_add_opts(&qemu_icount_opts); qemu_add_opts(&qemu_semihosting_config_opts); qemu_add_opts(&qemu_fw_cfg_opts); @@ -2961,6 +2970,13 @@ void qemu_init(int argc, char **argv) exit(1); } break; + case QEMU_OPTION_tlbidomain: + opts = qemu_opts_parse_noisily(qemu_find_opts("tlbidomain"), + optarg, true); + if (!opts) { + exit(1); + } + break; case QEMU_OPTION_display: parse_display(optarg); break; -- 2.33.0
Register the TLBID field of ID_AA64MMFR4_EL1 in the sysreg properties table so the writable-idreg machinery exposes it as a property when KVM reports the bits writable. Add kvm_arm_vtlbid_init(), called from kvm_arch_init_vcpu(). When running on the virt machine with TLBI vdomains configured, it computes the vCPU's domain bitmap via virt_cpu_vdomain_bitmap() and sends the KVM_ARM_VCPU_SET_VDOMAIN ioctl, whose uapi is added to linux-headers. It is a no-op when no vdomains are configured (vms->num_vdomains == 0) or on non-virt machines. The ioctl is only sent before the VM starts running (!runstate_is_running()). All possible vCPUs, including currently unplugged ones, have their vdomain configured at startup, so there is no need to re-send it during vCPU hot-plug; the kernel rejects re-writing the ID register after the VM has run once, which otherwise hangs the VM. Finally, read ID_AA64MMFR4_EL1 back from KVM into isar so the TLBID bit survives vCPU hot-plug: a hot-plugged ARMCPU is recreated with MMFR4 reset to 0 (stage-0 feature detection does not read MMFR4), and kvm_arm_writable_idregs_to_cpreg_list() would otherwise overwrite the KVM-configured value with 0. Signed-off-by: Tian Zheng <zhengtian10@huawei.com> Signed-off-by: Jinqian Yang <yangjinqian1@huawei.com> --- linux-headers/linux/kvm.h | 7 ++++ target/arm/cpu-sysreg-properties.c | 1 + target/arm/kvm64.c | 58 ++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+) diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h index 4d5792a95d..968c202188 100644 --- a/linux-headers/linux/kvm.h +++ b/linux-headers/linux/kvm.h @@ -1849,4 +1849,11 @@ struct kvm_arm_get_vdomain { #define KVM_ARM_GET_VDOMAIN_NUM _IOW(KVMIO, 0xd7, struct kvm_arm_get_vdomain) +struct kvm_arm_set_vdomain { + __u32 vdomain_bitmap; + __u8 num_vdomains; +}; + +#define KVM_ARM_VCPU_SET_VDOMAIN _IOW(KVMIO, 0xd8, struct kvm_arm_set_vdomain) + #endif /* __LINUX_KVM_H */ diff --git a/target/arm/cpu-sysreg-properties.c b/target/arm/cpu-sysreg-properties.c index c8a43c9eb4..41bd5b13cb 100644 --- a/target/arm/cpu-sysreg-properties.c +++ b/target/arm/cpu-sysreg-properties.c @@ -561,6 +561,7 @@ void initialize_cpu_sysreg_properties(void) ARM64SysReg *ID_AA64MMFR4_EL1 = arm64_sysreg_get(ID_AA64MMFR4_EL1_IDX); ID_AA64MMFR4_EL1->name = "ID_AA64MMFR4_EL1"; arm64_sysreg_add_field(ID_AA64MMFR4_EL1, "SRMASK", 44, 47); + arm64_sysreg_add_field(ID_AA64MMFR4_EL1, "TLBID", 40, 43); arm64_sysreg_add_field(ID_AA64MMFR4_EL1, "E3DSE", 36, 39); arm64_sysreg_add_field(ID_AA64MMFR4_EL1, "RMEGDI", 28, 31); arm64_sysreg_add_field(ID_AA64MMFR4_EL1, "E2H0", 24, 27); diff --git a/target/arm/kvm64.c b/target/arm/kvm64.c index a1255956f3..fd43dfe1d6 100644 --- a/target/arm/kvm64.c +++ b/target/arm/kvm64.c @@ -33,6 +33,7 @@ #include "cpu-features.h" #include "hw/acpi/acpi.h" #include "hw/acpi/ghes.h" +#include "hw/arm/virt.h" #include "cpu-custom.h" static bool have_guest_debug; @@ -898,6 +899,57 @@ static int kvm_arm_set_pv_unhalt_bmap(ARMCPU *cpu) return kvm_set_one_reg(CPU(cpu), KVM_REG_ARM_VENDOR_HYP_BMAP, &bmap); } +static int kvm_arm_vtlbid_init(CPUState *cs) +{ + VirtMachineState *vms; + ARMCPU *cpu = ARM_CPU(cs); + uint32_t vdomain_bitmap; + struct kvm_arm_set_vdomain config; + int ret = 0; + + if (!kvm_arm_vtlbid_supported()) { + return ret; + } + + vms = VIRT_MACHINE(object_dynamic_cast(OBJECT(qdev_get_machine()), + TYPE_VIRT_MACHINE)); + if (!vms || vms->num_vdomains == 0) { + return ret; + } + + vdomain_bitmap = virt_cpu_vdomain_bitmap(vms, cs->cpu_index); + if (vdomain_bitmap == 0) { + return ret; + } + + if (!runstate_is_running()) { + config = (struct kvm_arm_set_vdomain){ + .vdomain_bitmap = vdomain_bitmap, + .num_vdomains = vms->num_vdomains, + }; + ret = kvm_vcpu_ioctl(cs, KVM_ARM_VCPU_SET_VDOMAIN, &config); + if (ret < 0) { + error_report("vCPU %d: Failed to set vdomain bitmap (0x%x) " + "with %d vdomains: %s", + cs->cpu_index, vdomain_bitmap, vms->num_vdomains, + strerror(-ret)); + return ret; + } + qemu_log("vCPU %d: Set vdomain_bitmap=0x%x num_vdomains=%d\n", + cs->cpu_index, vdomain_bitmap, vms->num_vdomains); + } + + ret = kvm_get_one_reg(cs, + idregs_sysreg_to_kvm_reg( + id_register_sysreg[ID_AA64MMFR4_EL1_IDX]), + &cpu->isar.idregs[ID_AA64MMFR4_EL1_IDX]); + if (ret) + error_report("vCPU %d: Failed to read ID_AA64MMFR4_EL1: %s", + cs->cpu_index, strerror(-ret)); + + return ret; +} + #define ARM_CPU_ID_MPIDR 3, 0, 0, 0, 5 #define ARM_CPU_ID_CLIDR 3, 1, 0, 0, 1 @@ -982,6 +1034,12 @@ int kvm_arch_init_vcpu(CPUState *cs) /* Don't abort, as pvspinlock is optional */ } } + + ret = kvm_arm_vtlbid_init(cs); + if (ret) { + return ret; + } + /* * KVM reports the exact PSCI version it is implementing via a * special sysreg. If it is present, use its contents to determine -- 2.33.0
Add build_tlbi() to generate the TLBI ACPI table (signature 'TLBI') for the ARM virt machine. The table contains PE Component subtables listing the domain IDs associated with each vCPU. Per-vCPU domain membership is derived from the parsed -tlbidomain config via virt_cpu_vdomain_bitmap(). The table is integrated into virt_acpi_build() when TLBI domain configs are present, and is parsed by the guest kernel's acpi_tlbi_init() to discover TLBI domain topology for targeted TLB invalidation. Signed-off-by: Tian Zheng <zhengtian10@huawei.com> --- hw/arm/virt-acpi-build.c | 73 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/hw/arm/virt-acpi-build.c b/hw/arm/virt-acpi-build.c index 6a0e54ce51..344909afd7 100644 --- a/hw/arm/virt-acpi-build.c +++ b/hw/arm/virt-acpi-build.c @@ -28,7 +28,6 @@ #include "qemu/osdep.h" #include "qapi/error.h" -#include "qemu/bitmap.h" #include "qemu/error-report.h" #include "trace.h" #include "hw/core/cpu.h" @@ -1239,6 +1238,73 @@ spcr_setup(GArray *table_data, BIOSLinker *linker, VirtMachineState *vms) NULL); } +/* + * ACPI Specification Version 6.nex + * Build the TLBI ACPI table (signature "TLBI"). + * + * Table layout (after standard ACPI header): + * [4 bytes] max_cpus — number of PE Component entries + * For each vCPU (PE Component subtable): + * [2] type = 1 (PE Component) + * [2] reserved + * [4] component_length (total bytes of this subtable, including domain IDs) + * [4] pe_id (vCPU index) + * [4] domain_count (number of domain IDs that follow) + * [4] domain_offset (byte offset from TLBI Header start to + * the domain ID array, or 0) + * [domain_count * 4] domain IDs + * + * Guest kernel parses this via acpi_tlbi_init() to learn TLBI vdomain topology. + */ +static void +build_tlbi(GArray *table_data, BIOSLinker *linker, VirtMachineState *vms) +{ + unsigned int max_cpus = MACHINE(vms)->smp.max_cpus; + int i, j; + AcpiTable table = { .sig = "TLBI", .rev = 1, .oem_id = vms->oem_id, + .oem_table_id = vms->oem_table_id }; + + acpi_table_begin(&table, table_data); + build_append_int_noprefix(table_data, max_cpus, 4); /* PE count */ + + for (i = 0; i < (int)max_cpus; i++) { + GArray *dom_ids = g_array_new(false, false, sizeof(uint32_t)); + uint32_t component_len, domain_offset; + uint32_t vdbm = virt_cpu_vdomain_bitmap(vms, i); + + for (j = 0; j < vms->num_vdomains; j++) { + if (vdbm & (1U << j)) { + uint32_t id = j; + g_array_append_val(dom_ids, id); + } + } + + component_len = 20 + dom_ids->len * sizeof(uint32_t); + domain_offset = dom_ids->len > 0 + ? (table_data->len - table.table_offset + 20) : 0; + + /* Type: PE Component */ + build_append_int_noprefix(table_data, 1, 2); + /* Reserved */ + build_append_int_noprefix(table_data, 0, 2); + /* Length */ + build_append_int_noprefix(table_data, component_len, 4); + /* PE ID (vCPU index) */ + build_append_int_noprefix(table_data, i, 4); + /* Domain count */ + build_append_int_noprefix(table_data, dom_ids->len, 4); + /* Domain offset */ + build_append_int_noprefix(table_data, domain_offset, 4); + for (j = 0; j < (int)dom_ids->len; j++) { + build_append_int_noprefix(table_data, + g_array_index(dom_ids, uint32_t, j), 4); + } + g_array_free(dom_ids, true); + } + + acpi_table_end(linker, &table); +} + /* * ACPI spec, Revision 5.1 * 5.2.16 System Resource Affinity Table (SRAT) @@ -1883,6 +1949,11 @@ void virt_acpi_build(VirtMachineState *vms, AcpiBuildTables *tables) build_iort(tables_blob, tables->linker, vms); } + if (vms->num_vdomains > 0) { + acpi_add_table(table_offsets, tables_blob); + build_tlbi(tables_blob, tables->linker, vms); + } + #ifdef CONFIG_TPM if (tpm_get_version(tpm_find()) == TPM_VERSION_2_0) { acpi_add_table(table_offsets, tables_blob); -- 2.33.0
participants (1)
-
Tian Zheng