On Sun, 11 May 2025 14:20:56 -0400 "Michael S. Tsirkin" <m...@redhat.com> wrote:
> On Mon, Apr 28, 2025 at 12:07:50PM +0100, Alireza Sanaee wrote: > > Specify which layer (core/cluster/socket) caches found at in the CPU > > topology. Updating cache topology to device tree (spec v0.4). > > Example: > > > > Here, 2 sockets (packages), and 2 clusters, 4 cores and 2 threads > > created, in aggregate 2*2*4*2 logical cores. In the smp-cache > > object, cores will have l1d and l1i. However, extending this is > > not difficult). The clusters will share a unified l2 level cache, > > and finally sockets will share l3. In this patch, threads will > > share l1 caches by default, but this can be adjusted if case > > required. > > > > Currently only three levels of caches are supported. The patch > > does not allow partial declaration of caches. In another word, all > > caches must be defined or caches must be skipped. > > > > ./qemu-system-aarch64 \ > > -machine virt,\ > > smp-cache.0.cache=l1i,smp-cache.0.topology=core,\ > > smp-cache.1.cache=l1d,smp-cache.1.topology=core,\ > > smp-cache.2.cache=l2,smp-cache.2.topology=cluster,\ > > smp-cache.3.cache=l3,smp-cache.3.topology=socket\ > > -cpu max \ > > -m 2048 \ > > -smp sockets=2,clusters=2,cores=4,threads=1 \ > > -kernel ./Image.gz \ > > -append "console=ttyAMA0 root=/dev/ram rdinit=/init acpi=force" > > \ -initrd rootfs.cpio.gz \ > > -bios ./edk2-aarch64-code.fd \ > > -nographic > > > > For instance, following device tree will be generated for a scenario > > where we have 2 sockets, 2 clusters, 2 cores and 2 threads, in > > total 16 PEs. L1i and L1d are private to each thread, and l2 and l3 > > are shared at socket level as an example. > > > > Limitation: SMT cores cannot share L1 cache for now. This > > problem does not exist in PPTT tables. > > > > Signed-off-by: Alireza Sanaee <alireza.san...@huawei.com> > > Co-developed-by: Jonathan Cameron <jonathan.came...@huawei.com> > > Signed-off-by: Jonathan Cameron <jonathan.came...@huawei.com> > > --- > > hw/arm/virt.c | 343 > > ++++++++++++++++++++++++++++++++++++++++++ hw/cpu/core.c | > > 92 +++++++++++ include/hw/arm/virt.h | 4 + > > include/hw/cpu/core.h | 25 +++ > > 4 files changed, 464 insertions(+) > > > > diff --git a/hw/arm/virt.c b/hw/arm/virt.c > > index a96452f17a48..ece8203e9f0b 100644 > > --- a/hw/arm/virt.c > > +++ b/hw/arm/virt.c > > @@ -238,6 +238,132 @@ static const int a15irqmap[] = { > > [VIRT_PLATFORM_BUS] = 112, /* ...to 112 + > > PLATFORM_BUS_NUM_IRQS -1 */ }; > > > > +unsigned int virt_get_caches(const VirtMachineState *vms, > > + PPTTCPUCaches *caches) > > pass an array size so we can assert on OOB at least. I think OOB does not occur as we are using CPU_MAX_CACHES in the loop which is always 16 (related to the comment you also had later in the code). I can still pass a len if you think this looks dubious. > > > +{ > > + ARMCPU *armcpu = ARM_CPU(qemu_get_cpu(0)); /* assume > > homogeneous CPUs */ > > + bool ccidx = cpu_isar_feature(any_ccidx, armcpu); > > + unsigned int num_cache, i; > > + int level_instr = 1, level_data = 1; > > + > > + for (i = 0, num_cache = 0; i < CPU_MAX_CACHES; i++, > > num_cache++) { > > + int type = (armcpu->clidr >> (3 * i)) & 7; > > + int bank_index; > > + int level; > > + PPTTCPUCacheType cache_type; > > + > > + if (type == 0) { > > + break; > > + } > > + > > + switch (type) { > > + case 1: > > + cache_type = INSTRUCTION; > > + level = level_instr; > > + break; > > + case 2: > > + cache_type = DATA; > > + level = level_data; > > + break; > > + case 4: > > + cache_type = UNIFIED; > > + level = level_instr > level_data ? level_instr : > > level_data; > > + break; > > + case 3: /* Split - Do data first */ > > + cache_type = DATA; > > + level = level_data; > > + break; > > + default: > > + error_setg(&error_abort, "Unrecognized cache type"); > > + return 0; > > + } > > + /* > > + * ccsidr is indexed using both the level and whether it is > > + * an instruction cache. Unified caches use the same > > storage > > + * as data caches. > > + */ > > + bank_index = (i * 2) | ((type == 1) ? 1 : 0); > > + if (ccidx) { > > + caches[num_cache] = (PPTTCPUCaches) { > > + .type = cache_type, > > + .level = level, > > + .linesize = 1 << > > (FIELD_EX64(armcpu->ccsidr[bank_index], > > + CCSIDR_EL1, > > + CCIDX_LINESIZE) + 4), > > + .associativity = > > FIELD_EX64(armcpu->ccsidr[bank_index], > > + CCSIDR_EL1, > > + CCIDX_ASSOCIATIVITY) + > > 1, > > + .sets = FIELD_EX64(armcpu->ccsidr[bank_index], > > CCSIDR_EL1, > > + CCIDX_NUMSETS) + 1, > > + }; > > + } else { > > + caches[num_cache] = (PPTTCPUCaches) { > > + .type = cache_type, > > + .level = level, > > + .linesize = 1 << > > (FIELD_EX64(armcpu->ccsidr[bank_index], > > + CCSIDR_EL1, LINESIZE) > > + 4), > > + .associativity = > > FIELD_EX64(armcpu->ccsidr[bank_index], > > + CCSIDR_EL1, > > + ASSOCIATIVITY) + 1, > > + .sets = FIELD_EX64(armcpu->ccsidr[bank_index], > > CCSIDR_EL1, > > + NUMSETS) + 1, > > + }; > > + } > > + caches[num_cache].size = caches[num_cache].associativity * > > + caches[num_cache].sets * caches[num_cache].linesize; > > + > > + /* Break one 'split' entry up into two records */ > > + if (type == 3) { > > + num_cache++; > > + bank_index = (i * 2) | 1; > > + if (ccidx) { > > + /* Instruction cache: bottom bit set when reading > > banked reg */ > > + caches[num_cache] = (PPTTCPUCaches) { > > + .type = INSTRUCTION, > > + .level = level_instr, > > + .linesize = 1 << > > (FIELD_EX64(armcpu->ccsidr[bank_index], > > + CCSIDR_EL1, > > + CCIDX_LINESIZE) + > > 4), > > + .associativity = > > FIELD_EX64(armcpu->ccsidr[bank_index], > > + CCSIDR_EL1, > > + > > CCIDX_ASSOCIATIVITY) + 1, > > + .sets = FIELD_EX64(armcpu->ccsidr[bank_index], > > CCSIDR_EL1, > > + CCIDX_NUMSETS) + 1, > > + }; > > + } else { > > + caches[num_cache] = (PPTTCPUCaches) { > > + .type = INSTRUCTION, > > + .level = level_instr, > > + .linesize = 1 << > > (FIELD_EX64(armcpu->ccsidr[bank_index], > > + CCSIDR_EL1, > > LINESIZE) + 4), > > + .associativity = > > FIELD_EX64(armcpu->ccsidr[bank_index], > > + CCSIDR_EL1, > > + ASSOCIATIVITY) + 1, > > + .sets = FIELD_EX64(armcpu->ccsidr[bank_index], > > CCSIDR_EL1, > > + NUMSETS) + 1, > > + }; > > + } > > + caches[num_cache].size = > > caches[num_cache].associativity * > > + caches[num_cache].sets * > > caches[num_cache].linesize; > > + } > > + switch (type) { > > + case 1: > > + level_instr++; > > + break; > > + case 2: > > + level_data++; > > + break; > > + case 3: > > + case 4: > > + level_instr++; > > + level_data++; > > + break; > > + } > > + } > > + > > + return num_cache; > > +} > > + > > static void create_randomness(MachineState *ms, const char *node) > > { > > struct { > > @@ -421,13 +547,96 @@ static void fdt_add_timer_nodes(const > > VirtMachineState *vms) } > > } > > > > +static void add_cache_node(void *fdt, char * nodepath, > > PPTTCPUCaches cache, > > + uint32_t *next_level) { > > coding style violation thanks. gonna fix. > > > + /* Assume L2/3 are unified caches. */ > > + > > + uint32_t phandle; > > + > > + qemu_fdt_add_path(fdt, nodepath); > > + phandle = qemu_fdt_alloc_phandle(fdt); > > + qemu_fdt_setprop_cell(fdt, nodepath, "phandle", phandle); > > + qemu_fdt_setprop_cell(fdt, nodepath, "cache-level", > > cache.level); > > + qemu_fdt_setprop_cell(fdt, nodepath, "cache-size", cache.size); > > + qemu_fdt_setprop_cell(fdt, nodepath, "cache-block-size", > > cache.linesize); > > + qemu_fdt_setprop_cell(fdt, nodepath, "cache-sets", cache.sets); > > + qemu_fdt_setprop(fdt, nodepath, "cache-unified", NULL, 0); > > + qemu_fdt_setprop_string(fdt, nodepath, "compatible", "cache"); > > + if (cache.level != 3) { > > + /* top level cache doesn't have next-level-cache property > > */ > > + qemu_fdt_setprop_cell(fdt, nodepath, "next-level-cache", > > *next_level); > > + } > > + > > + *next_level = phandle; > > +} > > + > > +static bool add_cpu_cache_hierarchy(void *fdt, PPTTCPUCaches* > > cache, > > + uint32_t cache_cnt, > > + uint32_t top_level, > > + uint32_t bottom_level, > > + uint32_t cpu_id, > > + uint32_t *next_level) { > > + bool found_cache = false; > > + char *nodepath; > > + > > + for (int level = top_level; level >= bottom_level; level--) { > > + for (int i = 0; i < cache_cnt; i++) { > > + if (i != level) { > > + continue; > > + } > > + > > + nodepath = g_strdup_printf("/cpus/cpu@%d/l%d-cache", > > + cpu_id, level); > > + add_cache_node(fdt, nodepath, cache[i], next_level); > > + found_cache = true; > > + g_free(nodepath); > > + > > + } > > + } > > + > > + return found_cache; > > +} > > + > > +static void set_cache_properties(void *fdt, const char *nodename, > > + const char *prefix, PPTTCPUCaches > > cache) +{ > > + char prop_name[64]; > > + > > + snprintf(prop_name, sizeof(prop_name), "%s-block-size", > > prefix); > > + qemu_fdt_setprop_cell(fdt, nodename, prop_name, > > cache.linesize); + > > + snprintf(prop_name, sizeof(prop_name), "%s-size", prefix); > > + qemu_fdt_setprop_cell(fdt, nodename, prop_name, cache.size); > > + > > + snprintf(prop_name, sizeof(prop_name), "%s-sets", prefix); > > + qemu_fdt_setprop_cell(fdt, nodename, prop_name, cache.sets); > > +} > > + > > static void fdt_add_cpu_nodes(const VirtMachineState *vms) > > { > > int cpu; > > int addr_cells = 1; > > const MachineState *ms = MACHINE(vms); > > + const MachineClass *mc = MACHINE_GET_CLASS(ms); > > const VirtMachineClass *vmc = VIRT_MACHINE_GET_CLASS(vms); > > int smp_cpus = ms->smp.cpus; > > + int socket_id, cluster_id, core_id; > > + uint32_t next_level = 0; > > + uint32_t socket_offset = 0, cluster_offset = 0, core_offset = > > 0; > > + int last_socket = -1, last_cluster = -1, last_core = -1; > > + int top_node = 3, top_cluster = 3, top_core = 3; > > + int bottom_node = 3, bottom_cluster = 3, bottom_core = 3; > > do these one var at a time. sure, will fix. > > > + unsigned int num_cache; > > + PPTTCPUCaches caches[16]; > > why 16? The code assumes 3 layers of cache max, so if we say at l1 we got two 1 inst cache and 1 data cache, and then 1 unified for l2 and one unified at l3 then there are 4 instances of caches. But then things can grow, if each level has separate data and instruction then the value is higher. If CPUs end up having L4, and L5 then again higher ... I think I assumed 7 layer of caches, and then had 16 as max number of objects possible. The value can be lower, but then it is a choice. If we fall back to 3 layers max, then 3*2 = 6 max objects would be fair, but then any suggestions here? > > > + bool cache_created = false; > > + > > + num_cache = virt_get_caches(vms, caches); > > + > > + if (mc->smp_props.has_caches && > > + partial_cache_description(ms, caches, num_cache)) { > > + error_setg(&error_fatal, "Missing cache description"); > > + return; > > + } > > > > /* > > * See Linux Documentation/devicetree/bindings/arm/cpus.yaml > > @@ -456,9 +665,14 @@ static void fdt_add_cpu_nodes(const > > VirtMachineState *vms) qemu_fdt_setprop_cell(ms->fdt, "/cpus", > > "#size-cells", 0x0); > > for (cpu = smp_cpus - 1; cpu >= 0; cpu--) { > > + socket_id = cpu / (ms->smp.clusters * ms->smp.cores * > > ms->smp.threads); > > + cluster_id = cpu / (ms->smp.cores * ms->smp.threads) % > > ms->smp.clusters; > > + core_id = cpu / (ms->smp.threads) % ms->smp.cores; > > + > > char *nodename = g_strdup_printf("/cpus/cpu@%d", cpu); > > ARMCPU *armcpu = ARM_CPU(qemu_get_cpu(cpu)); > > CPUState *cs = CPU(armcpu); > > + const char *prefix = NULL; > > > > qemu_fdt_add_subnode(ms->fdt, nodename); > > qemu_fdt_setprop_string(ms->fdt, nodename, "device_type", > > "cpu"); @@ -488,6 +702,130 @@ static void fdt_add_cpu_nodes(const > > VirtMachineState *vms) qemu_fdt_alloc_phandle(ms->fdt)); > > } > > > > + if (!vmc->no_cpu_topology && num_cache) { > > + for (uint8_t i = 0; i < num_cache; i++) { > > + /* only level 1 in the CPU entry */ > > + if (caches[i].level > 1) { > > + continue; > > + } > > + > > + if (caches[i].type == INSTRUCTION) { > > + prefix = "i-cache"; > > + } else if (caches[i].type == DATA) { > > + prefix = "d-cache"; > > + } else if (caches[i].type == UNIFIED) { > > + error_setg(&error_fatal, > > + "Unified type is not implemented at > > level %d", > > + caches[i].level); > > + return; > > + } else { > > + error_setg(&error_fatal, "Undefined cache > > type"); > > + return; > > + } > > + > > + set_cache_properties(ms->fdt, nodename, prefix, > > caches[i]); > > + } > > + } > > + > > + if (socket_id != last_socket) { > > + bottom_node = top_node; > > + /* this assumes socket as the highest topological > > level */ > > + socket_offset = 0; > > + cluster_offset = 0; > > + if (cache_described_at(ms, CPU_TOPOLOGY_LEVEL_SOCKET) > > && > > + find_the_lowest_level_cache_defined_at_level(ms, > > + &bottom_node, > > + CPU_TOPOLOGY_LEVEL_SOCKET)) { > > + > > + if (bottom_node == 1) { > > + error_report( > > + "Cannot share L1 at socket_id %d. DT > > limiation on " > > + "sharing at cache level = 1", > > + socket_id); > > + } > > + > > + cache_created = add_cpu_cache_hierarchy(ms->fdt, > > caches, > > + num_cache, > > + top_node, > > + > > bottom_node, cpu, > > + > > &socket_offset); + > > + if (!cache_created) { > > + error_setg(&error_fatal, > > + "Socket: No caches at levels %d-%d", > > + top_node, bottom_node); > > + return; > > + } > > + > > + top_cluster = bottom_node - 1; > > + } > > + > > + last_socket = socket_id; > > + } > > + > > + if (cluster_id != last_cluster) { > > + bottom_cluster = top_cluster; > > + cluster_offset = socket_offset; > > + core_offset = 0; > > + if (cache_described_at(ms, CPU_TOPOLOGY_LEVEL_CLUSTER) > > && > > + find_the_lowest_level_cache_defined_at_level(ms, > > + &bottom_cluster, > > + CPU_TOPOLOGY_LEVEL_CLUSTER)) { > > + > > + cache_created = add_cpu_cache_hierarchy(ms->fdt, > > caches, > > + num_cache, > > + > > top_cluster, > > + > > bottom_cluster, cpu, > > + > > &cluster_offset); > > + if (bottom_cluster == 1) { > > + error_report( > > + "Cannot share L1 at socket_id %d, > > cluster_id %d. " > > + "DT limitation on sharing at cache level = > > 1.", > > + socket_id, cluster_id); > > + } > > + > > + if (!cache_created) { > > + error_setg(&error_fatal, > > + "Cluster: No caches at levels > > %d-%d", > > + top_cluster, bottom_cluster); > > + return; > > + } > > + > > + top_core = bottom_cluster - 1; > > + } else if (top_cluster == bottom_node - 1) { > > + top_core = bottom_node - 1; > > + } > > + > > + last_cluster = cluster_id; > > + } > > + > > + if (core_id != last_core) { > > + bottom_core = top_core; > > + core_offset = cluster_offset; > > + if (cache_described_at(ms, CPU_TOPOLOGY_LEVEL_CORE) && > > + find_the_lowest_level_cache_defined_at_level(ms, > > + &bottom_core, > > + CPU_TOPOLOGY_LEVEL_CORE)) { > > + > > + if (bottom_core == 1) { > > + bottom_core++; > > + } > > + > > + cache_created = add_cpu_cache_hierarchy(ms->fdt, > > + caches, > > + num_cache, > > + top_core, > > + > > bottom_core, cpu, > > + > > &core_offset); > > + } > > + > > + last_core = core_id; > > + } > > + > > + next_level = core_offset; > > + qemu_fdt_setprop_cell(ms->fdt, nodename, > > "next-level-cache", > > + next_level); > > + > > g_free(nodename); > > } > > > > @@ -3193,6 +3531,11 @@ static void > > virt_machine_class_init(ObjectClass *oc, void *data) hc->unplug = > > virt_machine_device_unplug_cb; mc->nvdimm_supported = true; > > mc->smp_props.clusters_supported = true; > > + /* Supported caches */ > > + mc->smp_props.cache_supported[CACHE_LEVEL_AND_TYPE_L1D] = true; > > + mc->smp_props.cache_supported[CACHE_LEVEL_AND_TYPE_L1I] = true; > > + mc->smp_props.cache_supported[CACHE_LEVEL_AND_TYPE_L2] = true; > > + mc->smp_props.cache_supported[CACHE_LEVEL_AND_TYPE_L3] = true; > > mc->auto_enable_numa_with_memhp = true; > > mc->auto_enable_numa_with_memdev = true; > > /* platform instead of architectural choice */ > > diff --git a/hw/cpu/core.c b/hw/cpu/core.c > > index 495a5c30ffe1..3adfc3ca0001 100644 > > --- a/hw/cpu/core.c > > +++ b/hw/cpu/core.c > > @@ -102,4 +102,96 @@ static void cpu_core_register_types(void) > > type_register_static(&cpu_core_type_info); > > } > > > > +bool cache_described_at(const MachineState *ms, CpuTopologyLevel > > level) +{ > > + if (machine_get_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L3) > > == level || > > + machine_get_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L2) > > == level || > > + machine_get_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L1I) > > == level || > > + machine_get_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L1D) > > == level) { > > + return true; > > + } > > + return false; > > +} > > + > > +int partial_cache_description(const MachineState *ms, > > PPTTCPUCaches *caches, > > + int num_caches) > > +{ > > + int level, c; > > + > > + for (level = 1; level < num_caches; level++) { > > + for (c = 0; c < num_caches; c++) { > > + if (caches[c].level != level) { > > + continue; > > + } > > + > > + switch (level) { > > + case 1: > > + /* > > + * L1 cache is assumed to have both L1I and L1D > > available. > > + * Technically both need to be checked. > > + */ > > + if (machine_get_cache_topo_level(ms, > > + > > CACHE_LEVEL_AND_TYPE_L1I) == > > + CPU_TOPOLOGY_LEVEL_DEFAULT) { > > + return level; > > + } > > + break; > > + case 2: > > + if (machine_get_cache_topo_level(ms, > > CACHE_LEVEL_AND_TYPE_L2) == > > + CPU_TOPOLOGY_LEVEL_DEFAULT) { > > + return level; > > + } > > + break; > > + case 3: > > + if (machine_get_cache_topo_level(ms, > > CACHE_LEVEL_AND_TYPE_L3) == > > + CPU_TOPOLOGY_LEVEL_DEFAULT) { > > + return level; > > + } > > + break; > > + } > > + } > > + } > > + > > + return 0; > > +} > > + > > +/* > > + * This function assumes l3 and l2 have unified cache and l1 is > > split l1d > > + * and l1i, and further prepares the lowest cache level for a > > topology > > + * level. The info will be fed to build_caches to create caches > > at the > > + * right level. > > + */ > > +bool find_the_lowest_level_cache_defined_at_level(const > > MachineState *ms, > > + int *level_found, > > + CpuTopologyLevel > > topo_level) { + > > + CpuTopologyLevel level; > > + > > + level = machine_get_cache_topo_level(ms, > > CACHE_LEVEL_AND_TYPE_L1I); > > + if (level == topo_level) { > > + *level_found = 1; > > + return true; > > + } > > + > > + level = machine_get_cache_topo_level(ms, > > CACHE_LEVEL_AND_TYPE_L1D); > > + if (level == topo_level) { > > + *level_found = 1; > > + return true; > > + } > > + > > + level = machine_get_cache_topo_level(ms, > > CACHE_LEVEL_AND_TYPE_L2); > > + if (level == topo_level) { > > + *level_found = 2; > > + return true; > > + } > > + > > + level = machine_get_cache_topo_level(ms, > > CACHE_LEVEL_AND_TYPE_L3); > > + if (level == topo_level) { > > + *level_found = 3; > > + return true; > > + } > > + > > + return false; > > +} > > + > > type_init(cpu_core_register_types) > > diff --git a/include/hw/arm/virt.h b/include/hw/arm/virt.h > > index c8e94e6aedc9..68ff99d6806d 100644 > > --- a/include/hw/arm/virt.h > > +++ b/include/hw/arm/virt.h > > @@ -39,6 +39,7 @@ > > #include "system/kvm.h" > > #include "hw/intc/arm_gicv3_common.h" > > #include "qom/object.h" > > +#include "hw/cpu/core.h" > > > > #define NUM_GICV2M_SPIS 64 > > #define NUM_VIRTIO_TRANSPORTS 32 > > @@ -50,6 +51,8 @@ > > /* GPIO pins */ > > #define GPIO_PIN_POWER_BUTTON 3 > > > > +#define CPU_MAX_CACHES 16 > > + > > enum { > > VIRT_FLASH, > > VIRT_MEM, > > @@ -189,6 +192,7 @@ OBJECT_DECLARE_TYPE(VirtMachineState, > > VirtMachineClass, VIRT_MACHINE) > > void virt_acpi_setup(VirtMachineState *vms); > > bool virt_is_acpi_enabled(VirtMachineState *vms); > > +unsigned int virt_get_caches(const VirtMachineState *vms, > > PPTTCPUCaches *caches); > > /* Return number of redistributors that fit in the specified > > region */ static uint32_t virt_redist_capacity(VirtMachineState > > *vms, int region) diff --git a/include/hw/cpu/core.h > > b/include/hw/cpu/core.h index 98ab91647eb2..a90b708b835b 100644 > > --- a/include/hw/cpu/core.h > > +++ b/include/hw/cpu/core.h > > @@ -25,6 +25,31 @@ struct CPUCore { > > int nr_threads; > > }; > > > > +typedef enum CPUCacheType { > > + DATA, > > + INSTRUCTION, > > + UNIFIED, > > +} PPTTCPUCacheType; > > Given specific values matter, you should specify them. > Also, please prefix names sensibly and consistently: > CPUCoreCacheType CPU_CORE_DATA and so on. > > > > > + > > +typedef struct PPTTCPUCaches { > > + PPTTCPUCacheType type; > > + uint32_t sets; > > + uint32_t size; > > + uint32_t level; > > + uint16_t linesize; > > + uint8_t attributes; /* write policy: 0x0 write back, 0x1 write > > through */ > > + uint8_t associativity; > > +} PPTTCPUCaches; > > + > > +int partial_cache_description(const MachineState *ms, > > PPTTCPUCaches *caches, > > + int num_caches); > > + > > +bool cache_described_at(const MachineState *ms, CpuTopologyLevel > > level); + > > +bool find_the_lowest_level_cache_defined_at_level(const > > MachineState *ms, > > + int *level_found, > > + CpuTopologyLevel > > topo_level); + > > /* Note: topology field names need to be kept in sync with > > * 'CpuInstanceProperties' */ > > same here, prefix everything with cpu_core CPUCore etc. > > > -- > > 2.34.1 > >