blob: a605fccaa33a49dc230df7293e6a83aefbef070e [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <arch/x86/system_topology.h>
#include <algorithm>
#include <ktl/unique_ptr.h>
#include <lib/system-topology.h>
#include <lk/init.h>
#include <pow2.h>
#include <printf.h>
#include <trace.h>
#define LOCAL_TRACE 0
namespace {
using cpu_id::Topology;
// TODO(edcoyne): move to fbl::Vector::resize().
template <typename T>
zx_status_t GrowVector(size_t new_size, fbl::Vector<T>* vector, fbl::AllocChecker* checker) {
for (size_t i = vector->size(); i < new_size; i++) {
vector->push_back(T(), checker);
if (!checker->check()) {
return ZX_ERR_NO_MEMORY;
}
}
return ZX_OK;
}
class Core {
public:
explicit Core() {
node_.entity_type = ZBI_TOPOLOGY_ENTITY_PROCESSOR;
node_.parent_index = ZBI_TOPOLOGY_NO_PARENT;
node_.entity.processor.logical_id_count = 0;
node_.entity.processor.architecture = ZBI_TOPOLOGY_ARCH_X86;
node_.entity.processor.architecture_info.x86.apic_id_count = 0;
}
void SetPrimary(bool primary) {
node_.entity.processor.flags = (primary) ? ZBI_TOPOLOGY_PROCESSOR_PRIMARY : 0;
}
void AddThread(uint16_t logical_id, uint32_t apic_id) {
auto& processor = node_.entity.processor;
processor.logical_ids[processor.logical_id_count++] = logical_id;
processor.architecture_info.x86.apic_ids[processor.architecture_info.x86.apic_id_count++] =
apic_id;
}
void SetFlatParent(uint16_t parent_index) {
node_.parent_index = parent_index;
}
const zbi_topology_node_t& node() const { return node_; }
private:
zbi_topology_node_t node_;
};
class SharedCache {
public:
SharedCache() {
node_.entity_type = ZBI_TOPOLOGY_ENTITY_CACHE;
node_.parent_index = ZBI_TOPOLOGY_NO_PARENT;
}
zx_status_t GetCore(int index, Core** core, fbl::AllocChecker* checker) {
auto status = GrowVector(index + 1, &cores_, checker);
if (status != ZX_OK) {
return status;
}
if (!cores_[index]) {
cores_[index].reset(new (checker) Core());
if (!checker->check()) {
return ZX_ERR_NO_MEMORY;
}
}
*core = cores_[index].get();
return ZX_OK;
}
void SetFlatParent(uint16_t parent_index) {
node_.parent_index = parent_index;
}
zbi_topology_node_t& node() { return node_; }
fbl::Vector<ktl::unique_ptr<Core>>& cores() { return cores_; }
private:
zbi_topology_node_t node_;
fbl::Vector<ktl::unique_ptr<Core>> cores_;
};
class Die {
public:
Die() {
node_.entity_type = ZBI_TOPOLOGY_ENTITY_DIE;
node_.parent_index = ZBI_TOPOLOGY_NO_PARENT;
}
zx_status_t GetCache(int index, SharedCache** cache, fbl::AllocChecker* checker) {
auto status = GrowVector(index + 1, &caches_, checker);
if (status != ZX_OK) {
return status;
}
if (!caches_[index]) {
caches_[index].reset(new (checker) SharedCache());
if (!checker->check()) {
return ZX_ERR_NO_MEMORY;
}
}
*cache = caches_[index].get();
return ZX_OK;
}
zx_status_t GetCore(int index, Core** core, fbl::AllocChecker* checker) {
auto status = GrowVector(index + 1, &cores_, checker);
if (status != ZX_OK) {
return status;
}
if (!cores_[index]) {
cores_[index].reset(new (checker) Core());
if (!checker->check()) {
return ZX_ERR_NO_MEMORY;
}
}
*core = cores_[index].get();
return ZX_OK;
}
void SetFlatParent(uint16_t parent_index) {
node_.parent_index = parent_index;
}
zbi_topology_node_t& node() { return node_; }
fbl::Vector<ktl::unique_ptr<SharedCache>>& caches() { return caches_; }
fbl::Vector<ktl::unique_ptr<Core>>& cores() { return cores_; }
void SetNuma(const AcpiNumaDomain& numa) {
numa_ = {numa};
}
const std::optional<AcpiNumaDomain>& numa() const { return numa_; }
private:
zbi_topology_node_t node_;
fbl::Vector<ktl::unique_ptr<SharedCache>> caches_;
fbl::Vector<ktl::unique_ptr<Core>> cores_;
std::optional<AcpiNumaDomain> numa_;
};
class ApicDecoder {
public:
ApicDecoder(uint8_t smt, uint8_t core, uint8_t die, uint8_t cache)
: smt_bits_(smt), core_bits_(core), die_bits_(die), cache_shift_(cache) {}
static std::optional<ApicDecoder> From(const cpu_id::CpuId& cpuid) {
uint8_t smt_bits = 0, core_bits = 0, die_bits = 0, cache_shift = 0;
const auto topology = cpuid.ReadTopology();
const auto cache = topology.highest_level_cache();
cache_shift = cache.shift_width;
LTRACEF("Top cache level: %u shift: %u size: %lu\n",
cache.level, cache.shift_width, cache.size_bytes);
const auto levels_opt = topology.levels();
if (!levels_opt) {
return {};
}
const auto& levels = *levels_opt;
for (int i = 0; i < levels.level_count; i++) {
const auto& level = levels.levels[i];
switch (level.type) {
case Topology::LevelType::SMT:
smt_bits = level.id_bits;
break;
case Topology::LevelType::CORE:
core_bits = level.id_bits;
break;
case Topology::LevelType::DIE:
die_bits = level.id_bits;
break;
default:
break;
}
}
LTRACEF("smt_bits: %u core_bits: %u die_bits: %u cache_shift: %u \n",
smt_bits, core_bits, die_bits, cache_shift);
return {ApicDecoder(smt_bits, core_bits, die_bits, cache_shift)};
}
uint32_t smt_id(uint32_t apic_id) const {
return apic_id & ToMask(smt_bits_);
}
uint32_t core_id(uint32_t apic_id) const {
return (apic_id >> smt_bits_) & ToMask(core_bits_);
}
uint32_t die_id(uint32_t apic_id) const {
if (die_bits_ == 0) {
// The die (or package) is defined by Intel as being what is left over
// after all other level ids are extracted.
return apic_id >> (smt_bits_ + core_bits_);
} else {
// AMD can explicitly define a die.
return (apic_id >> (smt_bits_ + core_bits_)) & ToMask(die_bits_);
}
}
uint32_t cache_id(uint32_t apic_id) const {
return (cache_shift_ == 0) ? 0 : apic_id >> cache_shift_;
}
bool has_cache_info() const {
return cache_shift_ > 0;
}
private:
uint32_t ToMask(uint8_t width) const {
return valpow2(width) - 1;
}
const uint8_t smt_bits_;
const uint8_t core_bits_;
const uint8_t die_bits_;
const uint8_t cache_shift_;
};
zx_status_t GenerateTree(const cpu_id::CpuId& cpuid, const AcpiTables& acpi_tables,
const ApicDecoder& decoder, fbl::Vector<ktl::unique_ptr<Die>>* dies) {
uint32_t cpu_count = 0;
auto status = acpi_tables.cpu_count(&cpu_count);
if (status != ZX_OK) {
return status;
}
fbl::AllocChecker checker;
ktl::unique_ptr<uint32_t[]> apic_ids(new (&checker) uint32_t[cpu_count]);
if (!checker.check()) {
return ZX_ERR_NO_MEMORY;
}
uint32_t apic_ids_count = 0;
status = acpi_tables.cpu_apic_ids(apic_ids.get(), cpu_count, &apic_ids_count);
if (status != ZX_OK) {
return status;
}
DEBUG_ASSERT(apic_ids_count == cpu_count);
// APIC of this processor, we will ensure it has logical_id 0;
const uint32_t primary_apic_id = cpuid.ReadProcessorId().local_apic_id();
uint16_t next_logical_id = 1;
for (size_t i = 0; i < apic_ids_count; i++) {
const auto apic_id = apic_ids[i];
const bool is_primary = primary_apic_id == apic_id;
const uint32_t die_id = decoder.die_id(apic_id);
if (die_id >= dies->size()) {
GrowVector(die_id + 1, dies, &checker);
}
auto& die = (*dies)[die_id];
if (!die) {
die.reset(new (&checker) Die());
if (!checker.check()) {
return ZX_ERR_NO_MEMORY;
}
}
SharedCache* cache = nullptr;
if (decoder.has_cache_info()) {
status = die->GetCache(decoder.cache_id(apic_id), &cache, &checker);
if (status != ZX_OK) {
return status;
}
}
Core* core = nullptr;
status = (cache != nullptr)
? cache->GetCore(decoder.core_id(apic_id), &core, &checker)
: die->GetCore(decoder.core_id(apic_id), &core, &checker);
if (status != ZX_OK) {
return status;
}
const uint16_t logical_id = (is_primary) ? 0 : next_logical_id++;
core->SetPrimary(is_primary);
core->AddThread(logical_id, apic_id);
LTRACEF("apic: %X logical: %u die: %u cache: %u core: %u \n",
apic_id, logical_id, die_id, decoder.cache_id(apic_id), decoder.core_id(apic_id));
}
return ZX_OK;
}
zx_status_t AttachNumaInformation(const AcpiTables& acpi_tables, const ApicDecoder& decoder,
fbl::Vector<ktl::unique_ptr<Die>>* dies) {
return acpi_tables.VisitCpuNumaPairs(
[&decoder, dies](const AcpiNumaDomain& domain, uint32_t apic_id) {
auto& die = (*dies)[decoder.die_id(apic_id)];
if (die && !die->numa()) {
die->SetNuma(domain);
}
});
}
zbi_topology_node_t ToFlatNode(const AcpiNumaDomain& numa) {
zbi_topology_node_t flat;
flat.entity_type = ZBI_TOPOLOGY_ENTITY_NUMA_REGION;
flat.parent_index = ZBI_TOPOLOGY_NO_PARENT;
if (numa.memory_count > 0) {
const auto& mem = numa.memory[0];
flat.entity.numa_region.start_address = mem.base_address;
flat.entity.numa_region.end_address = mem.base_address + mem.length;
}
return flat;
}
zx_status_t FlattenTree(const fbl::Vector<ktl::unique_ptr<Die>>& dies,
fbl::Vector<zbi_topology_node_t>* flat) {
fbl::AllocChecker checker;
for (auto& die : dies) {
if (!die) {
continue;
}
if (die->numa()) {
const auto numa_flat_index = static_cast<uint16_t>(flat->size());
flat->push_back(ToFlatNode(*die->numa()), &checker);
if (!checker.check()) {
return ZX_ERR_NO_MEMORY;
}
die->SetFlatParent(numa_flat_index);
}
const auto die_flat_index = static_cast<uint16_t>(flat->size());
flat->push_back(die->node(), &checker);
if (!checker.check()) {
return ZX_ERR_NO_MEMORY;
}
auto insert_core = [&](const ktl::unique_ptr<Core>& core) {
if (!core) {
return ZX_OK;
}
flat->push_back(core->node(), &checker);
if (!checker.check()) {
return ZX_ERR_NO_MEMORY;
}
return ZX_OK;
};
for (auto& cache : die->caches()) {
if (!cache) {
continue;
}
cache->SetFlatParent(die_flat_index);
const auto cache_flat_index = static_cast<uint16_t>(flat->size());
flat->push_back(cache->node(), &checker);
if (!checker.check()) {
return ZX_ERR_NO_MEMORY;
}
// Add cores that are on a die with shared cache.
for (auto& core : cache->cores()) {
if (!core) {
continue;
}
core->SetFlatParent(cache_flat_index);
auto status = insert_core(core);
if (status != ZX_OK) {
return status;
}
}
}
// Add cores directly attached to die.
for (auto& core : die->cores()) {
if (!core) {
continue;
}
core->SetFlatParent(die_flat_index);
auto status = insert_core(core);
if (status != ZX_OK) {
return status;
}
}
}
return ZX_OK;
}
void SystemTopologyInit(uint32_t) {
const AcpiTableProvider table_provider;
fbl::Vector<zbi_topology_node_t> topology;
const auto generate_status = x86::GenerateFlatTopology(cpu_id::CpuId(),
AcpiTables(&table_provider),
&topology);
ASSERT_MSG(generate_status == ZX_OK, "Failed to generate topology!");
const auto init_status =
system_topology::Graph::InitializeSystemTopology(topology.get(), topology.size());
ASSERT_MSG(init_status == ZX_OK, "Failed to load system topology!");
}
} // namespace
namespace x86 {
zx_status_t GenerateFlatTopology(const cpu_id::CpuId& cpuid, const AcpiTables& acpi_tables,
fbl::Vector<zbi_topology_node_t>* topology) {
const auto decoder_opt = ApicDecoder::From(cpuid);
if (!decoder_opt) {
return ZX_ERR_INTERNAL;
}
const auto& decoder = *decoder_opt;
fbl::Vector<ktl::unique_ptr<Die>> dies;
auto status = GenerateTree(cpuid, acpi_tables, decoder, &dies);
if (status != ZX_OK) {
return status;
}
status = AttachNumaInformation(acpi_tables, decoder, &dies);
if (status == ZX_ERR_NOT_FOUND) {
// This is not a critical error. Systems, such as qemu, may not have the
// tables present to enumerate NUMA information.
LTRACEF("Unable to attach NUMA information, missing ACPI tables.\n");
} else if (status != ZX_OK) {
return status;
}
return FlattenTree(dies, topology);
}
} // namespace x86
LK_INIT_HOOK(system_topology_init, SystemTopologyInit, LK_INIT_LEVEL_VM + 2)