| // Copyright 2019 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 "sysmem.h" |
| |
| #include <dirent.h> |
| #include <fcntl.h> |
| #include <fidl/fuchsia.io/cpp/fidl.h> |
| #include <fidl/fuchsia.kernel/cpp/fidl.h> |
| #include <fidl/fuchsia.sysmem/cpp/fidl.h> |
| #include <fidl/fuchsia.sysmem2/cpp/fidl.h> |
| #include <inttypes.h> |
| #include <lib/async-loop/loop.h> |
| #include <lib/async/dispatcher.h> |
| #include <lib/fidl/cpp/wire/arena.h> |
| #include <lib/sync/cpp/completion.h> |
| #include <lib/sysmem-version/sysmem-version.h> |
| #include <lib/zx/channel.h> |
| #include <lib/zx/event.h> |
| #include <stdio.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| #include <zircon/assert.h> |
| #include <zircon/errors.h> |
| #include <zircon/processargs.h> |
| #include <zircon/syscalls/iommu.h> |
| #include <zircon/threads.h> |
| |
| #include <algorithm> |
| #include <memory> |
| #include <string> |
| #include <thread> |
| |
| #include <bind/fuchsia/hardware/sysmem/cpp/bind.h> |
| #include <bind/fuchsia/sysmem/heap/cpp/bind.h> |
| #include <fbl/string_printf.h> |
| #include <sdk/lib/sys/cpp/service_directory.h> |
| |
| #include "lib/async/cpp/task.h" |
| #include "src/sysmem/metrics/metrics.cb.h" |
| #include "src/sysmem/server/allocator.h" |
| #include "src/sysmem/server/buffer_collection_token.h" |
| #include "src/sysmem/server/contiguous_pooled_memory_allocator.h" |
| #include "src/sysmem/server/external_memory_allocator.h" |
| #include "src/sysmem/server/macros.h" |
| #include "src/sysmem/server/utils.h" |
| #include "zircon/status.h" |
| |
| namespace sysmem_service { |
| |
| namespace { |
| |
| constexpr bool kLogAllCollectionsPeriodically = false; |
| constexpr zx::duration kLogAllCollectionsInterval = zx::sec(20); |
| |
| // These defaults only take effect if there is no |
| // fuchsia.hardware.sysmem/SYSMEM_METADATA_TYPE, and also neither of these |
| // kernel cmdline parameters set: driver.sysmem.contiguous_memory_size |
| // driver.sysmem.protected_memory_size |
| // |
| // Typically these defaults are overriden. |
| // |
| // By default there is no protected memory pool. |
| constexpr int64_t kDefaultProtectedMemorySize = 0; |
| // By default we pre-reserve 5% of physical memory for contiguous memory |
| // allocation via sysmem. |
| // |
| // This is enough to allow tests in sysmem_tests.cc to pass, and avoids relying |
| // on zx::vmo::create_contiguous() after early boot (by default), since it can |
| // fail if physical memory has gotten too fragmented. |
| constexpr int64_t kDefaultContiguousMemorySize = -5; |
| |
| constexpr char kSysmemConfigFilename[] = "/sysmem-config/config.sysmem_config_persistent_fidl"; |
| |
| // fbl::round_up() doesn't work on signed types. |
| template <typename T> |
| T AlignUp(T value, T divisor) { |
| return (value + divisor - 1) / divisor * divisor; |
| } |
| |
| // Helper function to build owned HeapProperties table with coherency domain support. |
| fuchsia_hardware_sysmem::HeapProperties BuildHeapPropertiesWithCoherencyDomainSupport( |
| bool cpu_supported, bool ram_supported, bool inaccessible_supported, bool need_clear, |
| bool need_flush) { |
| using fuchsia_hardware_sysmem::CoherencyDomainSupport; |
| using fuchsia_hardware_sysmem::HeapProperties; |
| |
| CoherencyDomainSupport coherency_domain_support; |
| coherency_domain_support.cpu_supported().emplace(cpu_supported); |
| coherency_domain_support.ram_supported().emplace(ram_supported); |
| coherency_domain_support.inaccessible_supported().emplace(inaccessible_supported); |
| |
| HeapProperties heap_properties; |
| heap_properties.coherency_domain_support().emplace(std::move(coherency_domain_support)); |
| heap_properties.need_clear().emplace(need_clear); |
| heap_properties.need_flush().emplace(need_flush); |
| return heap_properties; |
| } |
| |
| class SystemRamMemoryAllocator : public MemoryAllocator { |
| public: |
| explicit SystemRamMemoryAllocator(Owner* parent_device) |
| : MemoryAllocator(BuildHeapPropertiesWithCoherencyDomainSupport( |
| true /*cpu*/, true /*ram*/, true /*inaccessible*/, |
| // Zircon guarantees created VMO are filled with 0; sysmem doesn't |
| // need to clear it once again. There's little point in flushing a |
| // demand-backed VMO that's only virtually filled with 0. |
| /*need_clear=*/false, /*need_flush=*/false)) { |
| node_ = parent_device->heap_node()->CreateChild("SysmemRamMemoryAllocator"); |
| node_.CreateUint("id", id(), &properties_); |
| } |
| |
| zx_status_t Allocate(uint64_t raw_vmo_size, const fuchsia_sysmem2::SingleBufferSettings& settings, |
| std::optional<std::string> name, uint64_t buffer_collection_id, |
| uint32_t buffer_index, zx::vmo* parent_vmo) override { |
| ZX_DEBUG_ASSERT_MSG(raw_vmo_size % zx_system_get_page_size() == 0, "raw_vmo_size: 0x%" PRIx64, |
| raw_vmo_size); |
| ZX_DEBUG_ASSERT_MSG(*settings.buffer_settings()->raw_vmo_size() == raw_vmo_size, |
| "settings raw_vmo_size: %" PRIu64 " raw_vmo_size: %" PRIu64, |
| *settings.buffer_settings()->raw_vmo_size(), raw_vmo_size); |
| zx_status_t status = zx::vmo::create(raw_vmo_size, 0, parent_vmo); |
| if (status != ZX_OK) { |
| return status; |
| } |
| constexpr const char vmo_name[] = "Sysmem-core"; |
| parent_vmo->set_property(ZX_PROP_NAME, vmo_name, sizeof(vmo_name)); |
| return status; |
| } |
| |
| void Delete(zx::vmo parent_vmo) override { |
| // ~parent_vmo |
| } |
| // Since this allocator only allocates independent VMOs, it's fine to orphan those VMOs from the |
| // allocator since the VMOs independently track what pages they're using. So this allocator can |
| // always claim is_empty() true. |
| bool is_empty() override { return true; } |
| |
| private: |
| inspect::Node node_; |
| inspect::ValueList properties_; |
| }; |
| |
| class ContiguousSystemRamMemoryAllocator : public MemoryAllocator { |
| public: |
| explicit ContiguousSystemRamMemoryAllocator(Owner* parent_device) |
| : MemoryAllocator(BuildHeapPropertiesWithCoherencyDomainSupport( |
| /*cpu_supported=*/true, /*ram_supported=*/true, |
| /*inaccessible_supported=*/true, |
| // Zircon guarantees contagious VMO created are filled with 0; |
| // sysmem doesn't need to clear it once again. Unlike non-contiguous |
| // VMOs which haven't backed pages yet, contiguous VMOs have backed |
| // pages, and it's effective to flush the zeroes to RAM. Some current |
| // sysmem clients rely on contiguous allocations having their initial |
| // zero-fill already flushed to RAM (at least for the RAM coherency |
| // domain, this should probably remain true). |
| /*need_clear=*/false, /*need_flush=*/true)), |
| parent_device_(parent_device) { |
| node_ = parent_device_->heap_node()->CreateChild("ContiguousSystemRamMemoryAllocator"); |
| node_.CreateUint("id", id(), &properties_); |
| } |
| |
| zx_status_t Allocate(uint64_t raw_vmo_size, const fuchsia_sysmem2::SingleBufferSettings& settings, |
| std::optional<std::string> name, uint64_t buffer_collection_id, |
| uint32_t buffer_index, zx::vmo* parent_vmo) override { |
| ZX_DEBUG_ASSERT_MSG(raw_vmo_size % zx_system_get_page_size() == 0, "size: 0x%" PRIx64, |
| raw_vmo_size); |
| ZX_DEBUG_ASSERT_MSG(*settings.buffer_settings()->raw_vmo_size() == raw_vmo_size, |
| "settings raw_vmo_size: %" PRIu64 " raw_vmo_size: %" PRIu64, |
| *settings.buffer_settings()->raw_vmo_size(), raw_vmo_size); |
| zx::vmo result_parent_vmo; |
| // This code is unlikely to work after running for a while and physical |
| // memory is more fragmented than early during boot. The |
| // ContiguousPooledMemoryAllocator handles that case by keeping |
| // a separate pool of contiguous memory. |
| zx_status_t status = |
| zx::vmo::create_contiguous(parent_device_->bti(), raw_vmo_size, 0, &result_parent_vmo); |
| if (status != ZX_OK) { |
| LOG(ERROR, "zx::vmo::create_contiguous() failed - raw_vmo_size: %" PRIu64 " status: %d", |
| raw_vmo_size, status); |
| // sanitize to ZX_ERR_NO_MEMORY regardless of why. |
| status = ZX_ERR_NO_MEMORY; |
| return status; |
| } |
| constexpr const char vmo_name[] = "Sysmem-contig-core"; |
| result_parent_vmo.set_property(ZX_PROP_NAME, vmo_name, sizeof(vmo_name)); |
| *parent_vmo = std::move(result_parent_vmo); |
| return ZX_OK; |
| } |
| void Delete(zx::vmo parent_vmo) override { |
| // ~vmo |
| } |
| // Since this allocator only allocates independent VMOs, it's fine to orphan those VMOs from the |
| // allocator since the VMOs independently track what pages they're using. So this allocator can |
| // always claim is_empty() true. |
| bool is_empty() override { return true; } |
| |
| private: |
| Owner* const parent_device_; |
| inspect::Node node_; |
| inspect::ValueList properties_; |
| }; |
| |
| } // namespace |
| |
| Sysmem::Sysmem(async_dispatcher_t* client_dispatcher) |
| : client_dispatcher_(client_dispatcher), loop_(&kAsyncLoopConfigNeverAttachToThread) { |
| LOG(DEBUG, "Sysmem::Sysmem"); |
| std::lock_guard checker(client_checker_); |
| |
| zx_status_t start_thread_status = loop_.StartThread("sysmem-loop", &loop_thrd_); |
| ZX_ASSERT_MSG(start_thread_status == ZX_OK, "loop start_thread_status: %s", |
| zx_status_get_string(start_thread_status)); |
| RunSyncOnLoop([this] { loop_checker_.emplace(); }); |
| } |
| |
| Sysmem::~Sysmem() { |
| std::lock_guard checker(client_checker_); |
| Shutdown(); |
| LOG(DEBUG, "Finished Shutdown"); |
| } |
| |
| zx_status_t Sysmem::GetContiguousGuardParameters(const std::optional<sysmem_config::Config>& config, |
| uint64_t* guard_bytes_out, |
| bool* unused_pages_guarded, |
| int64_t* unused_guard_pattern_period_bytes, |
| zx::duration* unused_page_check_cycle_period, |
| bool* internal_guard_pages_out, |
| bool* crash_on_fail_out) { |
| const uint64_t kDefaultGuardBytes = zx_system_get_page_size(); |
| *guard_bytes_out = kDefaultGuardBytes; |
| *unused_page_check_cycle_period = |
| ContiguousPooledMemoryAllocator::kDefaultUnusedPageCheckCyclePeriod; |
| |
| if (!config.has_value()) { |
| *unused_pages_guarded = true; |
| *internal_guard_pages_out = false; |
| *crash_on_fail_out = false; |
| *unused_guard_pattern_period_bytes = -1; |
| return ZX_OK; |
| } |
| |
| *crash_on_fail_out = config->contiguous_guard_pages_fatal(); |
| *internal_guard_pages_out = config->contiguous_guard_pages_internal(); |
| *unused_pages_guarded = config->contiguous_guard_pages_unused(); |
| // if this value is <= 0 it'll be ignored and the default of 1/128 will stay in effect |
| *unused_guard_pattern_period_bytes = |
| config->contiguous_guard_pages_unused_fraction_denominator() * zx_system_get_page_size(); |
| |
| int64_t unused_page_check_cycle_seconds = config->contiguous_guard_pages_unused_cycle_seconds(); |
| if (unused_page_check_cycle_seconds > 0) { |
| LOG(INFO, "Overriding unused page check period to %ld seconds", |
| unused_page_check_cycle_seconds); |
| *unused_page_check_cycle_period = zx::sec(unused_page_check_cycle_seconds); |
| } |
| |
| int64_t guard_page_count = config->contiguous_guard_page_count(); |
| if (guard_page_count > 0) { |
| LOG(INFO, "Overriding guard page count to %ld", guard_page_count); |
| *guard_bytes_out = zx_system_get_page_size() * guard_page_count; |
| } |
| |
| return ZX_OK; |
| } |
| |
| void Sysmem::Shutdown() { |
| // disconnect existing connections from drivers |
| bindings_.RemoveAll(); |
| |
| // Try to ensure there are no outstanding VMOS before shutting down the loop. |
| PostTask([this]() mutable { |
| std::lock_guard checker(*loop_checker_); |
| |
| // stop serving outgoing_ to prevent new connections |
| outgoing_.reset(); |
| |
| // disconnect existing allocators |
| v1_allocators_.RemoveAll(); |
| v2_allocators_.RemoveAll(); |
| |
| if (snapshot_annotation_register_.has_value()) { |
| snapshot_annotation_register_->UnsetServiceDirectory(); |
| snapshot_annotation_register_.reset(); |
| } |
| |
| // Force-fail all LogicalBufferCollection(s) - this doesn't force them to all immediately |
| // delete, but does get them headed toward deleting. The Fail() call may or may not delete a |
| // LogicalBufferCollection synchronously, so copy the list before iterating. |
| // |
| // This disconnects all Node(s) (all BufferCollectionToken(s), BufferCollection(s), |
| // BufferCollectionTokenGroup(s)). |
| LogicalBufferCollections local_collections_list = logical_buffer_collections_; |
| for (auto& collection : logical_buffer_collections_) { |
| collection->Fail(); |
| } |
| |
| // Async notice when all still-existing LogicalBufferCollection(s) go away then shut down loop_. |
| // This requires sysmem clients to close their remaining sysmem VMO handles to proceed async. |
| // |
| // If we decide in future that we want to be able to stop/delete the Sysmem without requiring |
| // client VMOs to close first, we'll want to ensure that the securemem protocol and securemem |
| // drivers can avoid orphaning protected_memory_size, while also leaving buffers HW-protected |
| // until clients are done with the buffers - currently not a real issue since both sysmem and |
| // securemem are effectively start-only per boot (outside of unit tests). |
| waiting_for_unbind_ = true; |
| CheckForUnbind(); |
| }); |
| |
| // If a test is stuck waiting here, ensure that the test is dropping its sysmem VMO handles before |
| // ~Sysmem. |
| loop_.JoinThreads(); |
| loop_.Shutdown(); |
| |
| LOG(DEBUG, "Finished Shutdown"); |
| } |
| |
| void Sysmem::CheckForUnbind() { |
| std::lock_guard checker(*loop_checker_); |
| if (!waiting_for_unbind_) { |
| return; |
| } |
| if (!logical_buffer_collections().empty()) { |
| LOG(INFO, "Not unbinding because there are logical buffer collections count %ld", |
| logical_buffer_collections().size()); |
| return; |
| } |
| if (!!contiguous_system_ram_allocator_ && !contiguous_system_ram_allocator_->is_empty()) { |
| LOG(INFO, "Not unbinding because contiguous system ram allocator is not empty"); |
| return; |
| } |
| for (auto& [heap, allocator] : allocators_) { |
| if (!allocator->is_empty()) { |
| LOG(INFO, "Not unbinding because allocator %s is not empty", |
| heap.heap_type().value().c_str()); |
| |
| return; |
| } |
| } |
| |
| // This will cause the loop thread to exit and will allow ~Sysmem to continue. |
| loop_.Quit(); |
| } |
| |
| std::optional<SnapshotAnnotationRegister>& Sysmem::snapshot_annotation_register() { |
| return snapshot_annotation_register_; |
| } |
| |
| SysmemMetrics& Sysmem::metrics() { return metrics_; } |
| |
| protected_ranges::ProtectedRangesCoreControl& Sysmem::protected_ranges_core_control( |
| const fuchsia_sysmem2::Heap& heap) { |
| std::lock_guard checker(*loop_checker_); |
| auto iter = secure_mem_controls_.find(heap); |
| ZX_DEBUG_ASSERT(iter != secure_mem_controls_.end()); |
| return iter->second; |
| } |
| |
| bool Sysmem::SecureMemControl::IsDynamic() { return is_dynamic; } |
| |
| uint64_t Sysmem::SecureMemControl::GetRangeGranularity() { return range_granularity; } |
| |
| uint64_t Sysmem::SecureMemControl::MaxRangeCount() { return max_range_count; } |
| |
| bool Sysmem::SecureMemControl::HasModProtectedRange() { return has_mod_protected_range; } |
| |
| void Sysmem::SecureMemControl::AddProtectedRange(const protected_ranges::Range& range) { |
| std::lock_guard checker(*parent->loop_checker_); |
| ZX_DEBUG_ASSERT(parent->secure_mem_); |
| fuchsia_sysmem2::SecureHeapAndRange secure_heap_and_range; |
| secure_heap_and_range.heap() = heap; |
| fuchsia_sysmem2::SecureHeapRange secure_heap_range; |
| secure_heap_range.physical_address() = range.begin(); |
| secure_heap_range.size_bytes() = range.length(); |
| secure_heap_and_range.range() = std::move(secure_heap_range); |
| fuchsia_sysmem2::SecureMemAddSecureHeapPhysicalRangeRequest add_request; |
| add_request.heap_range() = std::move(secure_heap_and_range); |
| auto result = parent->secure_mem_->channel()->AddSecureHeapPhysicalRange(std::move(add_request)); |
| // If we lose the ability to control protected memory ranges ... reboot. |
| ZX_ASSERT_MSG(result.is_ok(), "AddSecureHeapPhysicalRange() failed: %s", |
| result.error_value().FormatDescription().c_str()); |
| } |
| |
| void Sysmem::SecureMemControl::DelProtectedRange(const protected_ranges::Range& range) { |
| std::lock_guard checker(*parent->loop_checker_); |
| ZX_DEBUG_ASSERT(parent->secure_mem_); |
| fuchsia_sysmem2::SecureHeapAndRange secure_heap_and_range; |
| secure_heap_and_range.heap() = heap; |
| fuchsia_sysmem2::SecureHeapRange secure_heap_range; |
| secure_heap_range.physical_address() = range.begin(); |
| secure_heap_range.size_bytes() = range.length(); |
| secure_heap_and_range.range() = std::move(secure_heap_range); |
| fuchsia_sysmem2::SecureMemDeleteSecureHeapPhysicalRangeRequest delete_request; |
| delete_request.heap_range() = std::move(secure_heap_and_range); |
| auto result = |
| parent->secure_mem_->channel()->DeleteSecureHeapPhysicalRange(std::move(delete_request)); |
| // If we lose the ability to control protected memory ranges ... reboot. |
| ZX_ASSERT_MSG(result.is_ok(), "DeleteSecureHeapPhysicalRange() failed: %s", |
| result.error_value().FormatDescription().c_str()); |
| } |
| |
| void Sysmem::SecureMemControl::ModProtectedRange(const protected_ranges::Range& old_range, |
| const protected_ranges::Range& new_range) { |
| if (new_range.end() != old_range.end() && new_range.begin() != old_range.begin()) { |
| LOG(INFO, |
| "new_range.end(): %" PRIx64 " old_range.end(): %" PRIx64 " new_range.begin(): %" PRIx64 |
| " old_range.begin(): %" PRIx64, |
| new_range.end(), old_range.end(), new_range.begin(), old_range.begin()); |
| ZX_PANIC("INVALID RANGE MODIFICATION"); |
| } |
| |
| std::lock_guard checker(*parent->loop_checker_); |
| ZX_DEBUG_ASSERT(parent->secure_mem_); |
| fuchsia_sysmem2::SecureHeapAndRangeModification modification; |
| modification.heap() = heap; |
| fuchsia_sysmem2::SecureHeapRange range_old; |
| range_old.physical_address() = old_range.begin(); |
| range_old.size_bytes() = old_range.length(); |
| fuchsia_sysmem2::SecureHeapRange range_new; |
| range_new.physical_address() = new_range.begin(); |
| range_new.size_bytes() = new_range.length(); |
| modification.old_range() = std::move(range_old); |
| modification.new_range() = std::move(range_new); |
| fuchsia_sysmem2::SecureMemModifySecureHeapPhysicalRangeRequest mod_request; |
| mod_request.range_modification() = std::move(modification); |
| auto result = |
| parent->secure_mem_->channel()->ModifySecureHeapPhysicalRange(std::move(mod_request)); |
| // If we lose the ability to control protected memory ranges ... reboot. |
| ZX_ASSERT_MSG(result.is_ok(), "ModifySecureHeapPhysicalRange() failed: %s", |
| result.error_value().FormatDescription().c_str()); |
| } |
| |
| void Sysmem::SecureMemControl::ZeroProtectedSubRange(bool is_covering_range_explicit, |
| const protected_ranges::Range& range) { |
| std::lock_guard checker(*parent->loop_checker_); |
| ZX_DEBUG_ASSERT(parent->secure_mem_); |
| fuchsia_sysmem2::SecureHeapAndRange secure_heap_and_range; |
| secure_heap_and_range.heap() = heap; |
| fuchsia_sysmem2::SecureHeapRange secure_heap_range; |
| secure_heap_range.physical_address() = range.begin(); |
| secure_heap_range.size_bytes() = range.length(); |
| secure_heap_and_range.range() = std::move(secure_heap_range); |
| fuchsia_sysmem2::SecureMemZeroSubRangeRequest zero_request; |
| zero_request.is_covering_range_explicit() = is_covering_range_explicit; |
| zero_request.heap_range() = std::move(secure_heap_and_range); |
| auto result = parent->secure_mem_->channel()->ZeroSubRange(std::move(zero_request)); |
| // If we lose the ability to control protected memory ranges ... reboot. |
| ZX_ASSERT_MSG(result.is_ok(), "ZeroSubRange() failed: %s", |
| result.error_value().FormatDescription().c_str()); |
| } |
| |
| zx::result<std::unique_ptr<Sysmem>> Sysmem::Create(async_dispatcher_t* client_dispatcher, |
| const Sysmem::CreateArgs& create_args) { |
| LOG(DEBUG, "Create()"); |
| auto device = std::make_unique<Sysmem>(client_dispatcher); |
| |
| std::lock_guard checker(device->client_checker_); |
| |
| auto init_result = device->Initialize(create_args); |
| if (!init_result.is_ok()) { |
| LOG(ERROR, "Initialize() failed: %s\n", init_result.status_string()); |
| return init_result.take_error(); |
| } |
| |
| return zx::ok(std::move(device)); |
| } |
| |
| zx::result<> Sysmem::Initialize(const CreateArgs& create_args) { |
| // Put everything under a node called "sysmem" because there's currently there's not a simple way |
| // to distinguish (using a selector) which driver inspect information is coming from. |
| sysmem_root_ = inspector_.inspector().GetRoot().CreateChild("sysmem"); |
| heaps_ = sysmem_root_.CreateChild("heaps"); |
| collections_node_ = sysmem_root_.CreateChild("collections"); |
| |
| // this is always true outside of unit tests |
| if (create_args.create_bti) { |
| auto create_bti_result = CreateBti(); |
| if (!create_bti_result.is_ok()) { |
| LOG(ERROR, "CreateBti() failed: %s", create_bti_result.status_string()); |
| return create_bti_result.take_error(); |
| } |
| bti_ = std::move(create_bti_result.value()); |
| ZX_DEBUG_ASSERT(bti_.is_valid()); |
| } else { |
| ZX_DEBUG_ASSERT(!bti_.is_valid()); |
| } |
| |
| auto config_from_file_result = GetConfigFromFile(); |
| if (!config_from_file_result.is_ok()) { |
| LOG(WARNING, "sysmem-config - GetConfigFromFile() failed: %s", |
| config_from_file_result.status_string()); |
| // fall back to default-initialized config |
| config_from_file_result = zx::ok(fuchsia_sysmem2::Config{}); |
| } |
| auto config_from_file = std::move(config_from_file_result.value()); |
| if (!config_from_file.format_costs().has_value()) { |
| LOG(WARNING, "sysmem-config - missing format_costs"); |
| config_from_file.format_costs().emplace(); |
| } else { |
| LOG(INFO, "sysmem-config - format_costs.size(): %zu", config_from_file.format_costs()->size()); |
| } |
| usage_pixel_format_cost_.emplace( |
| UsagePixelFormatCost(std::move(*config_from_file.format_costs()))); |
| |
| int64_t protected_memory_size = kDefaultProtectedMemorySize; |
| int64_t contiguous_memory_size = kDefaultContiguousMemorySize; |
| |
| if (!create_args.create_bti) { |
| protected_memory_size = 0; |
| contiguous_memory_size = 0; |
| } |
| |
| std::optional<sysmem_config::Config> maybe_config; |
| // this is always true outside of unit tests |
| if (create_args.expect_structured_config) { |
| sysmem_config::Config config = sysmem_config::Config::TakeFromStartupHandle(); |
| if (config.contiguous_memory_size() >= 0) { |
| contiguous_memory_size = config.contiguous_memory_size(); |
| } else if (config.contiguous_memory_size_percent() >= 0 && |
| config.contiguous_memory_size_percent() <= 99) { |
| // the negation is un-done below |
| contiguous_memory_size = -config.contiguous_memory_size_percent(); |
| } |
| if (config.protected_memory_size() >= 0) { |
| protected_memory_size = config.protected_memory_size(); |
| } else if (config.protected_memory_size_percent() >= 0 && |
| config.protected_memory_size_percent() <= 99) { |
| // the negation is un-done below |
| protected_memory_size = -config.protected_memory_size_percent(); |
| } |
| RunSyncOnLoop([this, &config] { |
| std::lock_guard thread_checker(*loop_checker_); |
| protected_ranges_disable_dynamic_ = config.protected_ranges_disable_dynamic(); |
| }); |
| maybe_config = std::move(config); |
| } |
| |
| // Negative values are interpreted as a percentage of physical RAM. |
| if (contiguous_memory_size < 0) { |
| contiguous_memory_size = -contiguous_memory_size; |
| ZX_DEBUG_ASSERT(contiguous_memory_size >= 1 && contiguous_memory_size <= 99); |
| contiguous_memory_size = zx_system_get_physmem() * contiguous_memory_size / 100; |
| } |
| if (protected_memory_size < 0) { |
| protected_memory_size = -protected_memory_size; |
| ZX_DEBUG_ASSERT(protected_memory_size >= 1 && protected_memory_size <= 99); |
| protected_memory_size = zx_system_get_physmem() * protected_memory_size / 100; |
| } |
| |
| constexpr int64_t kMinProtectedAlignment = 64 * 1024; |
| assert(kMinProtectedAlignment % zx_system_get_page_size() == 0); |
| contiguous_memory_size = |
| AlignUp(contiguous_memory_size, safe_cast<int64_t>(zx_system_get_page_size())); |
| protected_memory_size = AlignUp(protected_memory_size, kMinProtectedAlignment); |
| |
| auto heap = sysmem::MakeHeap(bind_fuchsia_sysmem_heap::HEAP_TYPE_SYSTEM_RAM, 0); |
| RunSyncOnLoop([this, &heap] { |
| std::lock_guard thread_checker(*loop_checker_); |
| allocators_[std::move(heap)] = std::make_unique<SystemRamMemoryAllocator>(this); |
| snapshot_annotation_register_.emplace(loop_dispatcher()); |
| auto service_directory = sys::ServiceDirectory::CreateFromNamespace(); |
| snapshot_annotation_register_->SetServiceDirectory(service_directory, loop_dispatcher()); |
| metrics_.metrics_buffer().SetServiceDirectory(service_directory); |
| metrics_.LogUnusedPageCheck(sysmem_metrics::UnusedPageCheckMetricDimensionEvent_Connectivity); |
| }); |
| |
| if (contiguous_memory_size) { |
| constexpr bool kIsAlwaysCpuAccessible = true; |
| constexpr bool kIsEverCpuAccessible = true; |
| constexpr bool kIsEverZirconAccessible = true; |
| constexpr bool kIsReady = true; |
| constexpr bool kCanBeTornDown = true; |
| auto heap = sysmem::MakeHeap(bind_fuchsia_sysmem_heap::HEAP_TYPE_SYSTEM_RAM, 0); |
| auto pooled_allocator = std::make_unique<ContiguousPooledMemoryAllocator>( |
| this, "SysmemContiguousPool", &heaps_, std::move(heap), contiguous_memory_size, |
| kIsAlwaysCpuAccessible, kIsEverCpuAccessible, kIsEverZirconAccessible, kIsReady, |
| kCanBeTornDown, loop_dispatcher()); |
| if (pooled_allocator->Init() != ZX_OK) { |
| LOG(ERROR, "Contiguous system ram allocator initialization failed"); |
| return zx::error(ZX_ERR_NO_MEMORY); |
| } |
| uint64_t guard_region_size; |
| bool unused_pages_guarded; |
| int64_t unused_guard_pattern_period_bytes; |
| zx::duration unused_page_check_cycle_period; |
| bool internal_guard_regions; |
| bool crash_on_guard; |
| if (GetContiguousGuardParameters(maybe_config, &guard_region_size, &unused_pages_guarded, |
| &unused_guard_pattern_period_bytes, |
| &unused_page_check_cycle_period, &internal_guard_regions, |
| &crash_on_guard) == ZX_OK) { |
| pooled_allocator->InitGuardRegion(guard_region_size, unused_pages_guarded, |
| unused_guard_pattern_period_bytes, |
| unused_page_check_cycle_period, internal_guard_regions, |
| crash_on_guard, loop_dispatcher()); |
| } |
| pooled_allocator->SetupUnusedPages(); |
| RunSyncOnLoop([this, &pooled_allocator] { |
| std::lock_guard thread_checker(*loop_checker_); |
| contiguous_system_ram_allocator_ = std::move(pooled_allocator); |
| }); |
| } else { |
| RunSyncOnLoop([this] { |
| std::lock_guard thread_checker(*loop_checker_); |
| contiguous_system_ram_allocator_ = std::make_unique<ContiguousSystemRamMemoryAllocator>(this); |
| }); |
| } |
| |
| // TODO: Separate protected memory allocator into separate driver or library |
| if (protected_memory_size > 0) { |
| constexpr bool kIsAlwaysCpuAccessible = false; |
| constexpr bool kIsEverCpuAccessible = true; |
| constexpr bool kIsEverZirconAccessible = true; |
| constexpr bool kIsReady = false; |
| // We have no way to tear down secure memory. |
| constexpr bool kCanBeTornDown = false; |
| // The heap is initially nullopt, but is set via set_heap before set_ready when we hear from |
| // the secmem driver. |
| auto protected_allocator = std::make_unique<ContiguousPooledMemoryAllocator>( |
| this, "SysmemProtectedPool", &heaps_, /*heap=*/std::nullopt, protected_memory_size, |
| kIsAlwaysCpuAccessible, kIsEverCpuAccessible, kIsEverZirconAccessible, kIsReady, |
| kCanBeTornDown, loop_dispatcher()); |
| // Request 64kB alignment because the hardware can only modify protections along 64kB |
| // boundaries. |
| zx_status_t status = protected_allocator->Init(16); |
| if (status != ZX_OK) { |
| LOG(ERROR, "Failed to init allocator for protected/secure (DRM) memory: %d", status); |
| return zx::error(status); |
| } |
| |
| pending_protected_allocator_ = std::move(protected_allocator); |
| } |
| |
| if (create_args.serve_outgoing) { |
| auto begin_serving_result = SyncCall([this] { |
| std::lock_guard lock(*loop_checker_); |
| return BeginServing(); |
| }); |
| if (!begin_serving_result.is_ok()) { |
| LOG(ERROR, "BeginServing() failed: %s", begin_serving_result.status_string()); |
| return begin_serving_result.take_error(); |
| } |
| } |
| |
| if constexpr (kLogAllCollectionsPeriodically) { |
| ZX_ASSERT(ZX_OK == |
| log_all_collections_.PostDelayed(loop_dispatcher(), kLogAllCollectionsInterval)); |
| } |
| |
| LOG(INFO, "sysmem finished initialization"); |
| |
| return zx::ok(); |
| } |
| |
| zx::result<zx::bti> Sysmem::CreateBti() { |
| auto iommu_client_result = component::Connect<fuchsia_kernel::IommuResource>(); |
| if (!iommu_client_result.is_ok()) { |
| LOG(ERROR, "component::Connect<fuchsia_kernel::IommuResource>() failed: %s", |
| iommu_client_result.status_string()); |
| return iommu_client_result.take_error(); |
| } |
| auto iommu_sync_client = fidl::SyncClient(std::move(iommu_client_result.value())); |
| auto iommu_result = iommu_sync_client->Get(); |
| if (!iommu_result.is_ok()) { |
| LOG(ERROR, "mmio_sync_client->Get() failed: %s", |
| iommu_result.error_value().FormatDescription().c_str()); |
| return zx::error(iommu_result.error_value().status()); |
| } |
| auto iommu_resource = std::move(iommu_result.value().resource()); |
| zx_iommu_desc_stub_t stub_iommu_desc{}; |
| zx::iommu iommu; |
| zx_status_t iommu_create_status = zx::iommu::create( |
| iommu_resource, ZX_IOMMU_TYPE_STUB, &stub_iommu_desc, sizeof(stub_iommu_desc), &iommu); |
| if (iommu_create_status != ZX_OK) { |
| LOG(ERROR, "zx::iommu::create() failed: %s", zx_status_get_string(iommu_create_status)); |
| return zx::error(iommu_create_status); |
| } |
| zx::bti bti; |
| zx_status_t bti_create_status = zx::bti::create(iommu, 0, /*bti_id=*/0, &bti); |
| if (bti_create_status != ZX_OK) { |
| LOG(ERROR, "zx::bti::create() failed: %s", zx_status_get_string(bti_create_status)); |
| return zx::error(bti_create_status); |
| } |
| return zx::ok(std::move(bti)); |
| } |
| |
| zx::result<> Sysmem::BeginServing() { |
| // While outgoing_ is on loop_dispatcher(), the fuchsia_hardware_sysmem::Sysmem protocol is served |
| // on client_dispatcher(). |
| outgoing_ = component::OutgoingDirectory(loop_dispatcher()); |
| |
| auto add_allocator1_result = outgoing_->AddUnmanagedProtocol<fuchsia_sysmem::Allocator>( |
| [this](fidl::ServerEnd<fuchsia_sysmem::Allocator> server_end) { |
| Allocator::CreateOwnedV1(std::move(server_end), this, v1_allocators()); |
| }); |
| if (!add_allocator1_result.is_ok()) { |
| LOG(ERROR, "AddUnmanagedProtocol<fuchsia_sysmem::Allocator>() failed: %s", |
| add_allocator1_result.status_string()); |
| return add_allocator1_result.take_error(); |
| } |
| |
| auto add_allocator2_result = outgoing_->AddUnmanagedProtocol<fuchsia_sysmem2::Allocator>( |
| [this](fidl::ServerEnd<fuchsia_sysmem2::Allocator> server_end) { |
| Allocator::CreateOwnedV2(std::move(server_end), this, v2_allocators()); |
| }); |
| if (!add_allocator2_result.is_ok()) { |
| LOG(ERROR, "AddUnmanagedProtocol<fuchsia_sysmem2::Allocator>() failed: %s", |
| add_allocator2_result.status_string()); |
| return add_allocator2_result.take_error(); |
| } |
| |
| auto add_sysmem_result = outgoing_->AddUnmanagedProtocol<fuchsia_hardware_sysmem::Sysmem>( |
| [this](fidl::ServerEnd<fuchsia_hardware_sysmem::Sysmem> server_end) { |
| PostTaskToClientDispatcher([this, server_end = std::move(server_end)]() mutable { |
| bindings_.AddBinding(client_dispatcher(), std::move(server_end), this, |
| fidl::kIgnoreBindingClosure); |
| }); |
| }); |
| if (!add_sysmem_result.is_ok()) { |
| LOG(ERROR, "AddUnmanagedProtocol<fuchsia_hardware_sysmem::Sysmem>() failed: %s", |
| add_sysmem_result.status_string()); |
| return add_sysmem_result.take_error(); |
| } |
| |
| auto serve_result = outgoing_->ServeFromStartupInfo(); |
| if (!serve_result.is_ok()) { |
| LOG(ERROR, "outgoing_->ServeFromStartupInfo() failed: %s", serve_result.status_string()); |
| return serve_result.take_error(); |
| } |
| return zx::ok(); |
| } |
| |
| zx_status_t Sysmem::RegisterHeapInternal( |
| fuchsia_sysmem2::Heap heap, fidl::ClientEnd<fuchsia_hardware_sysmem::Heap> heap_connection) { |
| class EventHandler : public fidl::WireAsyncEventHandler<fuchsia_hardware_sysmem::Heap> { |
| public: |
| void OnRegister( |
| ::fidl::WireEvent<::fuchsia_hardware_sysmem::Heap::OnRegister>* event) override { |
| auto properties = fidl::ToNatural(event->properties); |
| std::lock_guard checker(*device_->loop_checker_); |
| // A heap should not be registered twice. |
| ZX_DEBUG_ASSERT(heap_client_.is_valid()); |
| // This replaces any previously registered allocator for heap. This |
| // behavior is preferred as it avoids a potential race-condition during |
| // heap restart. |
| auto allocator = std::make_shared<ExternalMemoryAllocator>(device_, std::move(heap_client_), |
| std::move(properties)); |
| weak_associated_allocator_ = allocator; |
| device_->allocators_[heap_] = std::move(allocator); |
| } |
| |
| void on_fidl_error(fidl::UnbindInfo info) override { |
| if (!info.is_peer_closed()) { |
| LOG(ERROR, "Heap failed: %s\n", info.FormatDescription().c_str()); |
| } |
| } |
| |
| // Clean up heap allocator after |heap_client_| tears down, but only if the |
| // heap allocator for this |heap_| is still associated with this handler via |
| // |weak_associated_allocator_|. |
| ~EventHandler() override { |
| std::lock_guard checker(*device_->loop_checker_); |
| auto existing = device_->allocators_.find(heap_); |
| if (existing != device_->allocators_.end() && |
| existing->second == weak_associated_allocator_.lock()) |
| device_->allocators_.erase(heap_); |
| } |
| |
| static void Bind(Sysmem* device, fidl::ClientEnd<fuchsia_hardware_sysmem::Heap> heap_client_end, |
| fuchsia_sysmem2::Heap heap) { |
| auto event_handler = std::unique_ptr<EventHandler>(new EventHandler(device, heap)); |
| event_handler->heap_client_.Bind(std::move(heap_client_end), device->loop_dispatcher(), |
| std::move(event_handler)); |
| } |
| |
| private: |
| EventHandler(Sysmem* device, fuchsia_sysmem2::Heap heap) |
| : device_(device), heap_(std::move(heap)) {} |
| |
| Sysmem* const device_; |
| fidl::WireSharedClient<fuchsia_hardware_sysmem::Heap> heap_client_; |
| const fuchsia_sysmem2::Heap heap_; |
| std::weak_ptr<ExternalMemoryAllocator> weak_associated_allocator_; |
| }; |
| |
| PostTask([this, heap = std::move(heap), heap_connection = std::move(heap_connection)]() mutable { |
| std::lock_guard checker(*loop_checker_); |
| EventHandler::Bind(this, std::move(heap_connection), std::move(heap)); |
| }); |
| return ZX_OK; |
| } |
| |
| zx_status_t Sysmem::RegisterSecureMemInternal( |
| fidl::ClientEnd<fuchsia_sysmem2::SecureMem> secure_mem_connection) { |
| LOG(DEBUG, "sysmem RegisterSecureMem begin"); |
| |
| current_close_is_abort_ = std::make_shared<std::atomic_bool>(true); |
| |
| PostTask([this, secure_mem_connection = std::move(secure_mem_connection), |
| close_is_abort = current_close_is_abort_]() mutable { |
| std::lock_guard checker(*loop_checker_); |
| // This code must run asynchronously for two reasons: |
| // 1) It does synchronous IPCs to the secure mem device, so SysmemRegisterSecureMem must |
| // have return so the call from the secure mem device is unblocked. |
| // 2) It modifies member variables like |secure_mem_| and |heaps_| that should only be |
| // touched on |loop_|'s thread. |
| auto wait_for_close = std::make_unique<async::Wait>( |
| secure_mem_connection.channel().get(), ZX_CHANNEL_PEER_CLOSED, 0, |
| async::Wait::Handler([this, close_is_abort](async_dispatcher_t* loop_dispatcher, |
| async::Wait* wait, zx_status_t status, |
| const zx_packet_signal_t* signal) { |
| std::lock_guard checker(*loop_checker_); |
| if (*close_is_abort && secure_mem_) { |
| // The server end of this channel (the securemem driver) is the driver that |
| // listens for suspend(mexec) so that soft reboot can succeed. If that driver has |
| // failed, intentionally force a hard reboot here to get back to a known-good state. |
| // |
| // TODO(https://fxbug.dev/42180331): When there's any more direct/immediate way to |
| // intentionally trigger a hard reboot, switch to that (or just remove this TODO |
| // when sysmem terminating directly leads to a hard reboot). |
| ZX_PANIC( |
| "secure_mem_ connection unexpectedly lost; secure mem in unknown state; hard " |
| "reboot"); |
| } |
| })); |
| |
| // It is safe to call Begin() here before setting up secure_mem_ because handler will either |
| // run on current thread (loop_thrd_), or be run after the current task finishes while the |
| // loop is shutting down. |
| zx_status_t status = wait_for_close->Begin(loop_dispatcher()); |
| if (status != ZX_OK) { |
| LOG(ERROR, "Sysmem::RegisterSecureMem() failed wait_for_close->Begin()"); |
| return; |
| } |
| |
| secure_mem_ = std::make_unique<SecureMemConnection>(std::move(secure_mem_connection), |
| std::move(wait_for_close)); |
| |
| // Else we already ZX_PANIC()ed in wait_for_close. |
| ZX_DEBUG_ASSERT(secure_mem_); |
| |
| // At this point pending_protected_vmo_ will have the protected_memory_size VMO that was |
| // allocated during sysmem driver Start (if protected_memory_size != 0), and secure_allocators_ |
| // has no heaps yet. The pending_protected_vmo_ is not yet protected, but it is physically |
| // contiguous. We get the Heap identity and SecureMem properties and set up the heap in |
| // secure_allocators_. This location of this heap is determined by the VMO allocation, not by |
| // the TEE. |
| if (pending_protected_allocator_) { |
| auto dynamic_heaps_result = secure_mem_->channel()->GetDynamicSecureHeaps(); |
| if (!dynamic_heaps_result.is_ok()) { |
| LOG(WARNING, "GetDynamicSecureHeaps failed: %s", |
| dynamic_heaps_result.error_value().FormatDescription().c_str()); |
| return; |
| } |
| auto dynamic_heaps = std::move(dynamic_heaps_result.value()); |
| if (!dynamic_heaps.heaps().has_value() || dynamic_heaps.heaps()->empty()) { |
| LOG(WARNING, "protected_memory_size was set, but missing dynamic heap"); |
| return; |
| } |
| // heap field is required; fatal if not present |
| auto heap = std::move(dynamic_heaps.heaps()->at(0).heap().value()); |
| pending_protected_allocator_->set_heap(heap); |
| secure_allocators_[heap] = pending_protected_allocator_.get(); |
| allocators_[std::move(heap)] = std::move(pending_protected_allocator_); |
| } |
| |
| for (const auto& [heap, allocator] : secure_allocators_) { |
| uint64_t phys_base; |
| uint64_t size_bytes; |
| zx_status_t get_status = allocator->GetPhysicalMemoryInfo(&phys_base, &size_bytes); |
| if (get_status != ZX_OK) { |
| LOG(WARNING, "get_status != ZX_OK - get_status: %d", get_status); |
| return; |
| } |
| fuchsia_sysmem2::SecureHeapAndRange whole_heap; |
| whole_heap.heap() = heap; |
| fuchsia_sysmem2::SecureHeapRange range; |
| range.physical_address() = phys_base; |
| range.size_bytes() = size_bytes; |
| whole_heap.range() = std::move(range); |
| fuchsia_sysmem2::SecureMemGetPhysicalSecureHeapPropertiesRequest get_props_request; |
| get_props_request.entire_heap() = std::move(whole_heap); |
| auto get_properties_result = |
| secure_mem_->channel()->GetPhysicalSecureHeapProperties(std::move(get_props_request)); |
| if (!get_properties_result.is_ok()) { |
| if (get_properties_result.error_value().is_framework_error()) { |
| // For now this is fatal unless explicitly unregistered, since this case is very |
| // unexpected, and in this case rebooting is the most plausible way to get back to a |
| // working state anyway. |
| ZX_ASSERT(!*close_is_abort); |
| } |
| LOG(WARNING, "GetPhysicalSecureHeapProperties() failed: %s", |
| get_properties_result.error_value().FormatDescription().c_str()); |
| // Don't call set_ready() on secure_allocators_. Eg. this can happen if securemem TA is |
| // not found. |
| return; |
| } |
| // properties field is required; fatal if not present |
| const fuchsia_sysmem2::SecureHeapProperties properties = |
| std::move(get_properties_result->properties().value()); |
| ZX_ASSERT(properties.heap().has_value()); |
| ZX_ASSERT(properties.heap() == heap); |
| ZX_ASSERT(properties.dynamic_protection_ranges().has_value()); |
| ZX_ASSERT(properties.protected_range_granularity().has_value()); |
| ZX_ASSERT(properties.max_protected_range_count().has_value()); |
| ZX_ASSERT(properties.is_mod_protected_range_available().has_value()); |
| SecureMemControl control; |
| control.heap = heap; |
| control.parent = this; |
| control.is_dynamic = properties.dynamic_protection_ranges().value(); |
| control.max_range_count = properties.max_protected_range_count().value(); |
| control.range_granularity = properties.protected_range_granularity().value(); |
| control.has_mod_protected_range = properties.is_mod_protected_range_available().value(); |
| secure_mem_controls_.emplace(heap, std::move(control)); |
| } |
| |
| // Now we get the secure heaps that are configured via the TEE. |
| auto get_result = secure_mem_->channel()->GetPhysicalSecureHeaps(); |
| if (!get_result.is_ok()) { |
| if (get_result.error_value().is_framework_error()) { |
| // For now this is fatal unless explicitly unregistered, since this case is very |
| // unexpected, and in this case rebooting is the most plausible way to get back to a |
| // working state anyway. |
| ZX_ASSERT(!*close_is_abort); |
| } |
| LOG(WARNING, "GetPhysicalSecureHeaps() failed: %s", |
| get_result.error_value().FormatDescription().c_str()); |
| // Don't call set_ready() on secure_allocators_. Eg. this can happen if securemem TA is |
| // not found. |
| return; |
| } |
| // heaps field is required; fatal if not present |
| auto heaps = std::move(get_result->heaps().value()); |
| ZX_ASSERT(heaps.size() != 0); |
| for (const auto& heap : heaps) { |
| ZX_ASSERT(heap.heap().has_value()); |
| ZX_ASSERT(heap.ranges().has_value()); |
| // A tee-configured heap with multiple ranges can be specified by the protocol but is not |
| // currently supported by sysmem. |
| ZX_ASSERT(heap.ranges()->size() == 1); |
| // For now we assume that all TEE-configured heaps are protected full-time, and that they |
| // start fully protected. |
| constexpr bool kIsAlwaysCpuAccessible = false; |
| constexpr bool kIsEverCpuAccessible = false; |
| constexpr bool kIsEverZirconAccessible = false; |
| constexpr bool kIsReady = false; |
| constexpr bool kCanBeTornDown = true; |
| const fuchsia_sysmem2::SecureHeapRange& heap_range = heap.ranges()->at(0); |
| const fuchsia_sysmem2::Heap& which_heap = heap.heap().value(); |
| auto secure_allocator = std::make_unique<ContiguousPooledMemoryAllocator>( |
| this, "tee_secure", &heaps_, which_heap, heap_range.size_bytes().value(), |
| kIsAlwaysCpuAccessible, kIsEverCpuAccessible, kIsEverZirconAccessible, kIsReady, |
| kCanBeTornDown, loop_dispatcher()); |
| status = secure_allocator->InitPhysical(heap_range.physical_address().value()); |
| // A failing status is fatal for now. |
| ZX_ASSERT_MSG(status == ZX_OK, "%s", zx_status_get_string(status)); |
| LOG(DEBUG, |
| "created secure allocator: heap_type: %s heap_id: %" PRId64 " base: %016" PRIx64 |
| " size: %016" PRIx64, |
| which_heap.heap_type().value().c_str(), which_heap.id().value(), |
| heap_range.physical_address().value(), heap_range.size_bytes().value()); |
| |
| // The only usage of SecureMemControl for a TEE-configured heap is to do ZeroSubRange(), |
| // so field values of this SecureMemControl are somewhat degenerate (eg. VDEC). |
| SecureMemControl control; |
| control.heap = which_heap; |
| control.parent = this; |
| control.is_dynamic = false; |
| control.max_range_count = 0; |
| control.range_granularity = 0; |
| control.has_mod_protected_range = false; |
| secure_mem_controls_.emplace(which_heap, std::move(control)); |
| |
| ZX_ASSERT(secure_allocators_.find(which_heap) == secure_allocators_.end()); |
| secure_allocators_[which_heap] = secure_allocator.get(); |
| ZX_ASSERT(allocators_.find(which_heap) == allocators_.end()); |
| allocators_[std::move(which_heap)] = std::move(secure_allocator); |
| } |
| |
| for (const auto& [heap_type, allocator] : secure_allocators_) { |
| // The secure_mem_ connection is ready to protect ranges on demand to cover this heap's |
| // used ranges. There are no used ranges yet since the heap wasn't ready until now. |
| allocator->set_ready(); |
| } |
| |
| is_secure_mem_ready_ = true; |
| was_secure_mem_ready_ = true; |
| |
| // At least for now, we just call all the LogicalBufferCollection(s), regardless of which |
| // are waiting on secure mem (if any). The extra calls are required (by semantics of |
| // OnDependencyReady) to not be harmful from a correctness point of view. If any are waiting |
| // on secure mem, those can now proceed, and will do so using the current thread (the loop_ |
| // thread). |
| ForEachLogicalBufferCollection([](LogicalBufferCollection* logical_buffer_collection) { |
| logical_buffer_collection->OnDependencyReady(); |
| }); |
| |
| LOG(DEBUG, "sysmem RegisterSecureMem() done (async)"); |
| }); |
| return ZX_OK; |
| } |
| |
| // This call allows us to tell the difference between expected vs. unexpected close of the tee_ |
| // channel. |
| zx_status_t Sysmem::UnregisterSecureMemInternal() { |
| // By this point, the securemem driver's suspend(mexec) has already prepared for mexec. |
| // |
| // In this path, the server end of the channel hasn't closed yet, but will be closed shortly after |
| // return from UnregisterSecureMem(). |
| // |
| // We set a flag here so that a PEER_CLOSED of the channel won't cause the wait handler to crash. |
| *current_close_is_abort_ = false; |
| current_close_is_abort_.reset(); |
| PostTask([this]() { |
| std::lock_guard checker(*loop_checker_); |
| LOG(DEBUG, "begin UnregisterSecureMem()"); |
| secure_mem_.reset(); |
| for (const auto& [heap_type, allocator] : secure_allocators_) { |
| allocator->clear_ready(); |
| } |
| is_secure_mem_ready_ = false; |
| LOG(DEBUG, "end UnregisterSecureMem()"); |
| }); |
| return ZX_OK; |
| } |
| |
| const zx::bti& Sysmem::bti() { return bti_; } |
| |
| // Only use this in cases where we really can't use zx::vmo::create_contiguous() because we must |
| // specify a specific physical range. |
| zx::result<zx::vmo> Sysmem::CreatePhysicalVmo(uint64_t base, uint64_t size) { |
| // This isn't called much, so get the mmio resource each time rather than caching it. |
| zx::result resource_result = component::Connect<fuchsia_kernel::MmioResource>(); |
| if (resource_result.is_error()) { |
| LOG(ERROR, "Connect<fuchsia_kernel::MmioResource>() failed: %s", |
| resource_result.status_string()); |
| return resource_result.take_error(); |
| } |
| auto resource_protocol = fidl::SyncClient(std::move(resource_result).value()); |
| auto get_result = resource_protocol->Get(); |
| if (!get_result.is_ok()) { |
| LOG(ERROR, "resource->Get() failed: %s", get_result.error_value().FormatDescription().c_str()); |
| return zx::error(get_result.error_value().status()); |
| } |
| auto resource = std::move(std::move(get_result).value().resource()); |
| |
| zx::vmo result_vmo; |
| zx_status_t status = zx::vmo::create_physical(resource, base, size, &result_vmo); |
| if (status != ZX_OK) { |
| return zx::error(status); |
| } |
| return zx::ok(std::move(result_vmo)); |
| } |
| |
| void Sysmem::TrackToken(BufferCollectionToken* token) { |
| std::lock_guard checker(*loop_checker_); |
| ZX_DEBUG_ASSERT(token->has_server_koid()); |
| zx_koid_t server_koid = token->server_koid(); |
| ZX_DEBUG_ASSERT(server_koid != ZX_KOID_INVALID); |
| ZX_DEBUG_ASSERT(tokens_by_koid_.find(server_koid) == tokens_by_koid_.end()); |
| tokens_by_koid_.insert({server_koid, token}); |
| } |
| |
| void Sysmem::UntrackToken(BufferCollectionToken* token) { |
| std::lock_guard checker(*loop_checker_); |
| if (!token->has_server_koid()) { |
| // The caller is allowed to un-track a token that never saw |
| // OnServerKoid(). |
| return; |
| } |
| // This is intentionally idempotent, to allow un-tracking from |
| // BufferCollectionToken::CloseChannel() as well as from |
| // ~BufferCollectionToken(). |
| tokens_by_koid_.erase(token->server_koid()); |
| } |
| |
| bool Sysmem::TryRemoveKoidFromUnfoundTokenList(zx_koid_t token_server_koid) { |
| std::lock_guard checker(*loop_checker_); |
| // unfound_token_koids_ is limited to kMaxUnfoundTokenCount (and likely empty), so a loop over it |
| // should be efficient enough. |
| for (auto it = unfound_token_koids_.begin(); it != unfound_token_koids_.end(); ++it) { |
| if (*it == token_server_koid) { |
| unfound_token_koids_.erase(it); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| BufferCollectionToken* Sysmem::FindTokenByServerChannelKoid(zx_koid_t token_server_koid) { |
| std::lock_guard checker(*loop_checker_); |
| auto iter = tokens_by_koid_.find(token_server_koid); |
| if (iter == tokens_by_koid_.end()) { |
| if (token_server_koid != 0) { |
| unfound_token_koids_.push_back(token_server_koid); |
| constexpr uint32_t kMaxUnfoundTokenCount = 8; |
| while (unfound_token_koids_.size() > kMaxUnfoundTokenCount) { |
| unfound_token_koids_.pop_front(); |
| } |
| } |
| return nullptr; |
| } |
| return iter->second; |
| } |
| |
| Sysmem::FindLogicalBufferByVmoKoidResult Sysmem::FindLogicalBufferByVmoKoid(zx_koid_t vmo_koid) { |
| auto iter = vmo_koids_.find(vmo_koid); |
| if (iter == vmo_koids_.end()) { |
| return {nullptr, false}; |
| } |
| return iter->second; |
| } |
| |
| MemoryAllocator* Sysmem::GetAllocator(const fuchsia_sysmem2::BufferMemorySettings& settings) { |
| std::lock_guard checker(*loop_checker_); |
| if (*settings.heap()->heap_type() == bind_fuchsia_sysmem_heap::HEAP_TYPE_SYSTEM_RAM && |
| *settings.is_physically_contiguous()) { |
| return contiguous_system_ram_allocator_.get(); |
| } |
| |
| auto iter = allocators_.find(*settings.heap()); |
| if (iter == allocators_.end()) { |
| return nullptr; |
| } |
| return iter->second.get(); |
| } |
| |
| const fuchsia_hardware_sysmem::HeapProperties* Sysmem::GetHeapProperties( |
| const fuchsia_sysmem2::Heap& heap) const { |
| std::lock_guard checker(*loop_checker_); |
| auto iter = allocators_.find(heap); |
| if (iter == allocators_.end()) { |
| return nullptr; |
| } |
| return &iter->second->heap_properties(); |
| } |
| |
| void Sysmem::AddVmoKoid(zx_koid_t koid, bool is_weak, LogicalBuffer& logical_buffer) { |
| vmo_koids_.insert({koid, {&logical_buffer, is_weak}}); |
| } |
| |
| void Sysmem::RemoveVmoKoid(zx_koid_t koid) { |
| // May not be present if ~TrackedParentVmo called in error path prior to being fully set up. |
| vmo_koids_.erase(koid); |
| } |
| |
| void Sysmem::LogAllBufferCollections() { |
| std::lock_guard checker(*loop_checker_); |
| IndentTracker indent_tracker(0); |
| auto indent = indent_tracker.Current(); |
| LOG(INFO, "%*scollections.size: %" PRId64, indent.num_spaces(), "", |
| logical_buffer_collections_.size()); |
| // We sort by create time to make it easier to figure out which VMOs are likely leaks, especially |
| // when running with kLogAllCollectionsPeriodically true. |
| std::vector<LogicalBufferCollection*> sort_by_create_time; |
| ForEachLogicalBufferCollection( |
| [&sort_by_create_time](LogicalBufferCollection* logical_buffer_collection) { |
| sort_by_create_time.push_back(logical_buffer_collection); |
| }); |
| struct CompareByCreateTime { |
| bool operator()(const LogicalBufferCollection* a, const LogicalBufferCollection* b) { |
| return a->create_time_monotonic() < b->create_time_monotonic(); |
| } |
| }; |
| std::sort(sort_by_create_time.begin(), sort_by_create_time.end(), CompareByCreateTime{}); |
| for (auto* logical_buffer_collection : sort_by_create_time) { |
| logical_buffer_collection->LogSummary(indent_tracker); |
| // let output catch up so we don't drop log lines, hopefully; if there were a way to flush/wait |
| // the logger to avoid dropped lines, we could do that instead |
| zx::nanosleep(zx::deadline_after(zx::msec(20))); |
| }; |
| } |
| |
| void Sysmem::ForeachSecureHeap(fit::function<bool(const fuchsia_sysmem2::Heap&)> callback) { |
| std::lock_guard checker(*loop_checker_); |
| for (auto& allocator : secure_allocators_) { |
| bool keep_going = callback(allocator.first); |
| if (!keep_going) { |
| break; |
| } |
| } |
| } |
| |
| Sysmem::SecureMemConnection::SecureMemConnection( |
| fidl::ClientEnd<fuchsia_sysmem2::SecureMem> channel, |
| std::unique_ptr<async::Wait> wait_for_close) |
| : connection_(std::move(channel)), wait_for_close_(std::move(wait_for_close)) { |
| // nothing else to do here |
| } |
| |
| const fidl::SyncClient<fuchsia_sysmem2::SecureMem>& Sysmem::SecureMemConnection::channel() const { |
| ZX_DEBUG_ASSERT(connection_); |
| return connection_; |
| } |
| |
| void Sysmem::LogCollectionsTimer(async_dispatcher_t* loop_dispatcher, async::TaskBase* task, |
| zx_status_t status) { |
| std::lock_guard checker(*loop_checker_); |
| LogAllBufferCollections(); |
| ZX_ASSERT(kLogAllCollectionsPeriodically); |
| ZX_ASSERT(ZX_OK == log_all_collections_.PostDelayed(loop_dispatcher, kLogAllCollectionsInterval)); |
| } |
| |
| void Sysmem::RegisterHeap(RegisterHeapRequest& request, RegisterHeapCompleter::Sync& completer) { |
| std::lock_guard checker(client_checker_); |
| // TODO(b/316646315): Change RegisterHeap to specify fuchsia_sysmem2::Heap, and remove the |
| // conversion here. |
| auto v2_heap_type_result = |
| sysmem::V2CopyFromV1HeapType(static_cast<fuchsia_sysmem::HeapType>(request.heap())); |
| if (!v2_heap_type_result.is_ok()) { |
| LOG(WARNING, "V2CopyFromV1HeapType failed"); |
| completer.Close(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| auto& v2_heap_type = v2_heap_type_result.value(); |
| auto heap = sysmem::MakeHeap(std::move(v2_heap_type), 0); |
| zx_status_t status = RegisterHeapInternal(std::move(heap), std::move(request.heap_connection())); |
| if (status != ZX_OK) { |
| LOG(WARNING, "CommonSysmemRegisterHeap failed"); |
| completer.Close(status); |
| return; |
| } |
| } |
| |
| void Sysmem::RegisterSecureMem(RegisterSecureMemRequest& request, |
| RegisterSecureMemCompleter::Sync& completer) { |
| std::lock_guard checker(client_checker_); |
| zx_status_t status = RegisterSecureMemInternal(std::move(request.secure_mem_connection())); |
| if (status != ZX_OK) { |
| completer.Close(status); |
| return; |
| } |
| } |
| |
| void Sysmem::UnregisterSecureMem(UnregisterSecureMemCompleter::Sync& completer) { |
| std::lock_guard checker(client_checker_); |
| zx_status_t status = UnregisterSecureMemInternal(); |
| if (status == ZX_OK) { |
| completer.Reply(fit::ok()); |
| } else { |
| completer.Reply(fit::error(status)); |
| } |
| } |
| |
| zx::result<fuchsia_sysmem2::Config> Sysmem::GetConfigFromFile() { |
| int config_fd = open(kSysmemConfigFilename, O_RDONLY); |
| if (config_fd == -1) { |
| int local_errno = errno; |
| LOG(WARNING, "open(kSysmemConfigFilename) failed: %d", local_errno); |
| return zx::error(ZX_ERR_INTERNAL); |
| } |
| auto close_config_fd = fit::defer([&config_fd] { close(config_fd); }); |
| struct stat config_stat; |
| int fstat_result = fstat(config_fd, &config_stat); |
| if (fstat_result != 0) { |
| int local_errno = errno; |
| LOG(WARNING, "fstat(config_fd) failed: %d", local_errno); |
| return zx::error(ZX_ERR_INTERNAL); |
| } |
| off_t size = config_stat.st_size; |
| std::vector<uint8_t> config_bytes(size); |
| ssize_t read_result = read(config_fd, config_bytes.data(), config_bytes.size()); |
| if (read_result < 0) { |
| int local_errno = errno; |
| LOG(WARNING, "read(config_fd) failed: %d", local_errno); |
| return zx::error(ZX_ERR_INTERNAL); |
| } |
| if (static_cast<size_t>(read_result) != config_bytes.size()) { |
| LOG(WARNING, "read(config_fd) didn't read exact size"); |
| return zx::error(ZX_ERR_INTERNAL); |
| } |
| // Decode the FIDL struct |
| fit::result result = fidl::Unpersist<fuchsia_sysmem2::Config>(config_bytes); |
| ZX_ASSERT_MSG(result.is_ok(), "Could not decode fuchsia.sysmem2.Config FIDL structure"); |
| fuchsia_sysmem2::Config fidl_config = std::move(result.value()); |
| return zx::ok(fidl_config); |
| } |
| |
| } // namespace sysmem_service |