| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <lib/zbi-format/cpu.h> |
| #include <lib/zbi-format/internal/deprecated-cpu.h> |
| #include <lib/zbitl/items/cpu-topology.h> |
| |
| #include <algorithm> |
| |
| namespace zbitl { |
| |
| fit::result<std::string_view, CpuTopologyTable> CpuTopologyTable::FromPayload( |
| uint32_t item_type, zbitl::ByteView payload) { |
| switch (item_type) { |
| case ZBI_TYPE_CPU_TOPOLOGY: { |
| if (payload.empty()) { |
| return fit::error("ZBI_TYPE_CPU_TOPOLOGY payload is empty"); |
| } |
| if (payload.size_bytes() % sizeof(zbi_topology_node_t) != 0) { |
| return fit::error("ZBI_TYPE_CPU_TOPOLOGY payload not a multiple of entry size"); |
| } |
| CpuTopologyTable result; |
| result.table_ = cpp20::span{ |
| reinterpret_cast<const zbi_topology_node_t*>(payload.data()), |
| payload.size_bytes() / sizeof(zbi_topology_node_t), |
| }; |
| return fit::ok(result); |
| } |
| case ZBI_TYPE_DEPRECATED_CPU_TOPOLOGY_V2: { |
| if (payload.empty()) { |
| return fit::error("ZBI_TYPE_DEPRECATED_CPU_TOPOLOGY_V2 payload is empty"); |
| } |
| if (payload.size_bytes() % sizeof(zbi_topology_node_v2_t) != 0) { |
| return fit::error( |
| "ZBI_TYPE_DEPRECATED_CPU_TOPOLOGY_V2 payload not a multiple of entry size"); |
| } |
| |
| CpuTopologyTable result; |
| result.table_ = cpp20::span{ |
| reinterpret_cast<const zbi_topology_node_v2_t*>(payload.data()), |
| payload.size_bytes() / sizeof(zbi_topology_node_v2_t), |
| }; |
| return fit::ok(result); |
| } |
| case ZBI_TYPE_DEPRECATED_CPU_TOPOLOGY_V1: |
| if (payload.size_bytes() >= sizeof(zbi_cpu_config_t)) { |
| auto conf = reinterpret_cast<const zbi_cpu_config_t*>(payload.data()); |
| const size_t conf_size = |
| sizeof(zbi_cpu_config_t) + (conf->cluster_count * sizeof(zbi_cpu_cluster_t)); |
| if (payload.size_bytes() < conf_size) { |
| return fit::error("ZBI_TYPE_DEPRECATED_CPU_TOPOLOGY_V1 too small for cluster count"); |
| } |
| CpuTopologyTable result; |
| result.table_ = conf; |
| return fit::ok(result); |
| } |
| return fit::error("ZBI_TYPE_DEPRECATED_CPU_TOPOLOGY_V1 too small for header"); |
| |
| default: |
| return fit::error("invalid ZBI item type for CpuTopologyTable"); |
| } |
| } |
| |
| // These functions are static in the private inner class rather than just being |
| // in an anonymous namespace local to this file just so that the public classes |
| // can have a friend declaration. |
| |
| struct CpuTopologyTable::Dispatch { |
| // Set up with the modern table format, just use the input as is. |
| |
| static size_t TableSize(cpp20::span<const zbi_topology_node_t> nodes) { return nodes.size(); } |
| |
| static iterator TableBegin(cpp20::span<const zbi_topology_node_t> nodes) { |
| iterator result; |
| result.it_ = nodes.begin(); |
| return result; |
| } |
| |
| static iterator TableEnd(cpp20::span<const zbi_topology_node_t> nodes) { |
| iterator result; |
| result.it_ = nodes.end(); |
| return result; |
| } |
| |
| static size_t TableSize(cpp20::span<const zbi_topology_node_v2_t> nodes) { return nodes.size(); } |
| |
| static iterator TableBegin(cpp20::span<const zbi_topology_node_v2_t> nodes) { |
| V2ConvertingIterator it; |
| it.v2_nodes_ = nodes; |
| it.idx_ = 0; |
| |
| iterator result; |
| result.it_ = it; |
| return result; |
| } |
| |
| static iterator TableEnd(cpp20::span<const zbi_topology_node_v2_t> nodes) { |
| V2ConvertingIterator it; |
| it.v2_nodes_ = nodes; |
| it.idx_ = nodes.size(); |
| |
| iterator result; |
| result.it_ = it; |
| return result; |
| } |
| |
| // Set up with the old table format, convert on the fly. |
| |
| static size_t TableSize(const zbi_cpu_config_t* config) { |
| size_t nodes = 0; |
| cpp20::span clusters(config->clusters, config->cluster_count); |
| for (const zbi_cpu_cluster_t& cluster : clusters) { |
| // There's a node for the cluster, then a node for each CPU. |
| nodes += 1 + cluster.cpu_count; |
| } |
| return nodes; |
| } |
| |
| static iterator TableBegin(const zbi_cpu_config_t* config) { |
| V1ConvertingIterator it; |
| if (config->cluster_count > 0) { |
| it.clusters_ = cpp20::span(config->clusters, config->cluster_count); |
| it.logical_id_ = 0; |
| } |
| iterator result; |
| result.it_ = it; |
| return result; |
| } |
| |
| static iterator TableEnd(const zbi_cpu_config_t* config) { |
| iterator result; |
| result.it_ = V1ConvertingIterator(); |
| return result; |
| } |
| |
| static void Advance(V1ConvertingIterator& it) { |
| ZX_ASSERT_MSG(it.logical_id_, "cannot increment default-constructed or end iterator"); |
| ++it.next_node_idx_; |
| |
| if (!it.cpu_idx_) { |
| // This is at the node for a cluster. Advance to its first CPU. |
| it.cpu_idx_ = 0; |
| return; |
| } |
| |
| const uint32_t cpu_count = it.clusters_[it.cluster_idx_].cpu_count; |
| if (const uint32_t cpu_idx = *it.cpu_idx_; cpu_idx < cpu_count) { |
| ++*it.logical_id_; |
| ++*it.cpu_idx_; |
| // If there are still CPUs to process, advance to the next one; else, |
| // fall through to advance to the next cluster. |
| if (cpu_idx < cpu_count - 1) { |
| return; |
| } |
| } |
| |
| // Advance to the next cluster, unless we have reached the end. |
| it.cluster_node_idx_ = it.next_node_idx_; |
| it.cpu_idx_ = std::nullopt; |
| if (++it.cluster_idx_ == it.clusters_.size()) { |
| it.logical_id_ = std::nullopt; |
| } |
| } |
| |
| static zbi_topology_node_t GetNode(const V1ConvertingIterator& it) { |
| ZX_ASSERT_MSG(it.logical_id_, "cannot dereference default-constructed or end iterator"); |
| |
| // First there's a node for the cluster itself. |
| if (!it.cpu_idx_) { |
| return zbi_topology_node_t{ |
| // We don't have this data so it is a guess that little cores are |
| // first. |
| .entity = |
| { |
| .discriminant = ZBI_TOPOLOGY_ENTITY_CLUSTER, |
| .cluster = {.performance_class = it.cluster_idx_}, |
| }, |
| .parent_index = ZBI_TOPOLOGY_NO_PARENT, |
| }; |
| } |
| |
| // Then there's a node for each CPU. |
| return zbi_topology_node_t{ |
| .entity = |
| { |
| .discriminant = ZBI_TOPOLOGY_ENTITY_PROCESSOR, |
| .processor = |
| { |
| .architecture_info = |
| { |
| .discriminant = ZBI_TOPOLOGY_ARCHITECTURE_INFO_ARM64, |
| .arm64 = |
| { |
| .cluster_1_id = it.cluster_idx_, |
| .cpu_id = *it.cpu_idx_, |
| .gic_id = *it.logical_id_, |
| }, |
| }, |
| .logical_ids = {*it.logical_id_}, |
| .logical_id_count = 1, |
| }, |
| }, |
| .parent_index = static_cast<uint16_t>(it.cluster_node_idx_), |
| }; |
| } |
| |
| static zbi_topology_node_t GetNode(const V2ConvertingIterator& it) { |
| ZX_ASSERT_MSG(it.idx_, "cannot dereference default-constructed iterator"); |
| const zbi_topology_node_v2_t& v2_node = it.v2_nodes_[*it.idx_]; |
| |
| zbi_topology_node_t node = {.parent_index = v2_node.parent_index}; |
| zbi_topology_entity_t& entity = node.entity; |
| auto& v2_entity = v2_node.entity; |
| switch (v2_node.entity_type) { |
| case ZBI_TOPOLOGY_ENTITY_V2_PROCESSOR: { |
| entity.discriminant = ZBI_TOPOLOGY_ENTITY_PROCESSOR; |
| entity.processor = zbi_topology_processor_t{ |
| .flags = v2_entity.processor.flags, |
| .logical_id_count = v2_entity.processor.logical_id_count, |
| |
| }; |
| zbi_topology_architecture_info_t& arch_info = entity.processor.architecture_info; |
| switch (v2_entity.processor.architecture) { |
| case ZBI_TOPOLOGY_ARCHITECTURE_V2_X64: |
| arch_info.discriminant = ZBI_TOPOLOGY_ARCHITECTURE_INFO_X64; |
| arch_info.x64 = v2_entity.processor.architecture_info.x64; |
| break; |
| case ZBI_TOPOLOGY_ARCHITECTURE_V2_ARM64: |
| arch_info.discriminant = ZBI_TOPOLOGY_ARCHITECTURE_INFO_ARM64; |
| arch_info.arm64 = v2_entity.processor.architecture_info.arm64; |
| break; |
| case ZBI_TOPOLOGY_ARCHITECTURE_V2_RISCV64: |
| arch_info.discriminant = ZBI_TOPOLOGY_ARCHITECTURE_INFO_RISCV64; |
| arch_info.riscv64 = v2_entity.processor.architecture_info.riscv64; |
| break; |
| } |
| memcpy(entity.processor.logical_ids, v2_entity.processor.logical_ids, |
| sizeof(entity.processor.logical_ids)); |
| break; |
| } |
| case ZBI_TOPOLOGY_ENTITY_V2_CLUSTER: |
| entity.discriminant = ZBI_TOPOLOGY_ENTITY_CLUSTER; |
| entity.cluster = v2_entity.cluster; |
| break; |
| case ZBI_TOPOLOGY_ENTITY_V2_CACHE: |
| entity.discriminant = ZBI_TOPOLOGY_ENTITY_CACHE; |
| entity.cache = v2_entity.cache; |
| break; |
| case ZBI_TOPOLOGY_ENTITY_V2_NUMA_REGION: |
| entity.discriminant = ZBI_TOPOLOGY_ENTITY_NUMA_REGION; |
| entity.numa_region = zbi_topology_numa_region_t{ |
| .start = v2_entity.numa_region.start_address, |
| .size = v2_entity.numa_region.end_address - v2_entity.numa_region.start_address, |
| }; |
| break; |
| } |
| return node; |
| } |
| }; |
| |
| size_t CpuTopologyTable::size() const { |
| return std::visit([](const auto& table) { return Dispatch::TableSize(table); }, table_); |
| } |
| |
| CpuTopologyTable::iterator CpuTopologyTable::begin() const { |
| return std::visit([](const auto& table) { return Dispatch::TableBegin(table); }, table_); |
| } |
| |
| CpuTopologyTable::iterator CpuTopologyTable::end() const { |
| return std::visit([](const auto& table) { return Dispatch::TableEnd(table); }, table_); |
| } |
| |
| CpuTopologyTable::V1ConvertingIterator& CpuTopologyTable::V1ConvertingIterator::operator++() { |
| Dispatch::Advance(*this); |
| return *this; |
| } |
| |
| zbi_topology_node_t CpuTopologyTable::V1ConvertingIterator::operator*() const { |
| return Dispatch::GetNode(*this); |
| } |
| |
| zbi_topology_node_t CpuTopologyTable::V2ConvertingIterator::operator*() const { |
| return Dispatch::GetNode(*this); |
| } |
| |
| } // namespace zbitl |