#include "hw/virtio/virtio-md-pci.h"
#include "hw/virtio/virtio-iommu.h"
#include "hw/char/pl011.h"
+#include "hw/core/cpu.h"
#include "hw/cxl/cxl.h"
#include "hw/cxl/cxl_host.h"
#include "qemu/guest-random.h"
arm_feature(env, ARM_FEATURE_EL2) && cpu_isar_feature(aa64_vh, cpu);
}
+void set_cpu_cache(CPUCoreCaches *cpu_cache, enum CacheType cache_type,
+ int cache_level, bool is_i_cache0)
+{
+ int bank_index = ((cache_level - 1) * 2) | is_i_cache0;
+ ARMCPU *armcpu = ARM_CPU(qemu_get_cpu(0));
+ bool ccidx = cpu_isar_feature(any_ccidx, armcpu);
+
+ if (ccidx) {
+ *cpu_cache = (CPUCoreCaches){
+ .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 {
+ *cpu_cache = (CPUCoreCaches){
+ .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,
+ };
+ }
+ cpu_cache->type = cache_type;
+ cpu_cache->level = cache_level;
+ cpu_cache->size = cpu_cache->associativity *
+ cpu_cache->sets *
+ cpu_cache->linesize;
+
+ return;
+}
+
+unsigned int virt_get_caches(const VirtMachineState *vms, CPUCoreCaches *caches)
+{
+ int num_cache = 0;
+ ARMCPU *armcpu = ARM_CPU(qemu_get_cpu(0)); /* assume homogeneous CPUs */
+ ARMISARegisters *isar = &armcpu->isar;
+ uint32_t clidr = GET_IDREG(isar, CLIDR);
+
+ for (int cache_level = 1; cache_level <= CLIDR_CTYPE_MAX_CACHE_LEVEL;
+ cache_level++) {
+ uint8_t ctype =
+ (clidr >> (3 * (cache_level - 1))) & CLIDR_CTYPE_MAX_CACHE_LEVEL;
+
+ if (ctype == CLIDR_CTYPE_NO_CACHE) {
+ /*
+ * If a "No cache" cache type is found it means no manageable caches
+ * exist at further-out levels of hierarchy, so ignore them.
+ */
+ break;
+ } else if (ctype == CLIDR_CTYPE_SEPARATE_I_D_CACHES) {
+ /*
+ * Create separate D and I caches. D-cache is stored first.
+ */
+ enum CacheType cache_type;
+ for (cache_type = DATA_CACHE; cache_type <= INSTRUCTION_CACHE;
+ cache_type++) {
+ set_cpu_cache(&caches[num_cache++], cache_type, cache_level,
+ cache_type == INSTRUCTION_CACHE ? true : false);
+ }
+ } else if (ctype == CLIDR_CTYPE_UNIFIED_CACHE) {
+ set_cpu_cache(&caches[num_cache++], UNIFIED_CACHE, cache_level,
+ false);
+ } else if (ctype == CLIDR_CTYPE_D_CACHE) {
+ set_cpu_cache(&caches[num_cache++], DATA_CACHE, cache_level, false);
+ } else if (ctype == CLIDR_CTYPE_I_CACHE) {
+ set_cpu_cache(&caches[num_cache++], INSTRUCTION_CACHE, cache_level,
+ true);
+ } else {
+ error_setg(&error_abort, "Unrecognized cache type");
+ return 0;
+ }
+ }
+
+ return num_cache;
+}
+
static void create_fdt(VirtMachineState *vms)
{
MachineState *ms = MACHINE(vms);
}
}
+static void add_cache_node(void *fdt, char *nodepath, CPUCoreCaches cache,
+ uint32_t *next_level)
+{
+ /* 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, CPUCoreCaches* 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;
+
+ for (int level = top_level; level >= bottom_level; level--) {
+ for (int i = 0; i < cache_cnt; i++) {
+ char *nodepath;
+
+ 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, CPUCoreCaches 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 bool partial_cache_description(const MachineState *ms, int num_caches)
+{
+ assert(num_caches - 1 < CACHE_LEVEL_AND_TYPE__MAX);
+ enum CpuTopologyLevel topo_level;
+ enum CacheLevelAndType cache_level;
+
+ for (cache_level = 0; cache_level < num_caches; cache_level++) {
+ topo_level = machine_get_cache_topo_level(ms, cache_level);
+ if (topo_level == CPU_TOPOLOGY_LEVEL_DEFAULT) {
+ /* No topology level described for this cache level. */
+ return true;
+ }
+ }
+
+ return false;
+}
+
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;
+ uint32_t cluster_offset = 0;
+ uint32_t core_offset = 0;
+ int last_socket = -1;
+ int last_cluster = -1;
+ int last_core = -1;
+ int top_node = 3;
+ int top_cluster = 3;
+ int top_core = 3;
+ int bottom_node = 3;
+ int bottom_cluster = 3;
+ int bottom_core = 3;
+ unsigned int num_cache;
+ CPUCoreCaches caches[CPU_MAX_CACHES];
+ bool cache_created = false;
+ bool cache_at_topo_level;
+
+ num_cache = virt_get_caches(vms, caches);
+
+ if (mc->smp_props.has_caches &&
+ partial_cache_description(ms, num_cache)) {
+ error_setg(&error_fatal, "Missing cache description");
+ return;
+ }
/*
* See Linux Documentation/devicetree/bindings/arm/cpus.yaml
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");
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_CACHE) {
+ prefix = "i-cache";
+ } else if (caches[i].type == DATA_CACHE) {
+ prefix = "d-cache";
+ } else if (caches[i].type == UNIFIED_CACHE) {
+ 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;
+ cache_at_topo_level =
+ machine_find_lowest_level_cache_at_topo_level(ms,
+ &bottom_node,
+ CPU_TOPOLOGY_LEVEL_SOCKET);
+ if (cache_at_topo_level) {
+ if (bottom_node == 1 && !virt_is_acpi_enabled(vms))
+ error_setg(
+ &error_fatal,
+ "Cannot share L1 at socket_id %d."
+ "DT limitation 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;
+ cache_at_topo_level =
+ machine_find_lowest_level_cache_at_topo_level(ms,
+ &bottom_cluster,
+ CPU_TOPOLOGY_LEVEL_CLUSTER);
+ if (cache_at_topo_level) {
+ cache_created = add_cpu_cache_hierarchy(ms->fdt, caches,
+ num_cache,
+ top_cluster,
+ bottom_cluster, cpu,
+ &cluster_offset);
+ if (bottom_cluster == 1 && !virt_is_acpi_enabled(vms)) {
+ error_setg(&error_fatal,
+ "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;
+ cache_at_topo_level =
+ machine_find_lowest_level_cache_at_topo_level(ms,
+ &bottom_core,
+ CPU_TOPOLOGY_LEVEL_CORE);
+ if (cache_at_topo_level) {
+ if (bottom_core == 1 && top_core > 1) {
+ bottom_core++;
+ cache_created = add_cpu_cache_hierarchy(ms->fdt,
+ caches,
+ num_cache,
+ top_core,
+ bottom_core, cpu,
+ &core_offset);
+
+ if (!cache_created) {
+ error_setg(&error_fatal,
+ "Core: No caches at levels %d-%d",
+ top_core, bottom_core);
+ return;
+ }
+ }
+ }
+
+ last_core = core_id;
+ }
+
+ next_level = core_offset;
+ qemu_fdt_setprop_cell(ms->fdt, nodename, "next-level-cache",
+ next_level);
+
g_free(nodename);
}
}
-bool virt_is_acpi_enabled(VirtMachineState *vms)
+bool virt_is_acpi_enabled(const VirtMachineState *vms)
{
if (vms->acpi == ON_OFF_AUTO_OFF) {
return false;
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 */