| // Copyright 2022 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/zx/vmar.h> |
| #include <lib/zx/vmo.h> |
| |
| #include <fbl/string_printf.h> |
| #include <perftest/perftest.h> |
| |
| #include "assert.h" |
| |
| namespace { |
| |
| // Measures the time taken to perform zx_vmar_protect over multiple mappings inside a vmar. This is |
| // distinct from just causing there to be multiple protection regions inside a single mapping. The |
| // protection is performed on a subset of |protect_mappings| inside of |total_mappings| to evaluate |
| // the lookup and iteration of mappings in the vmar tree. If |toggle_protect| is true then the |
| // zx_vmar_protect calls will continuously alternate permissions, preventing any short circuiting. |
| // |
| // The mappings themselves will deliberately not get populated in the mmu so that this measures just |
| // the vmar hierarchy, and not the arch specific mmu code. |
| bool VmarMultiMappingsProtect(perftest::RepeatState* state, bool toggle_protect, |
| uint32_t total_mappings, uint32_t protect_mappings) { |
| // Create a VMAR to hold all the mappings. |
| const uint32_t page_size = zx_system_get_page_size(); |
| const size_t vmar_size = total_mappings * page_size; |
| zx::vmar vmar; |
| zx_vaddr_t addr = 0; |
| ASSERT_OK(zx::vmar::root_self()->allocate( |
| ZX_VM_CAN_MAP_SPECIFIC | ZX_VM_CAN_MAP_READ | ZX_VM_CAN_MAP_WRITE, 0, vmar_size, &vmar, |
| &addr)); |
| |
| // Create a VMO to map in that is twice as large as the VMAR so that every second page can be |
| // mapped. Mapping in every second page prevents mappings from being internally merged. |
| const size_t vmo_size = vmar_size * 2; |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(vmo_size, 0, &vmo)); |
| |
| // Map in every second page. |
| for (uint32_t i = 0; i < total_mappings; i++) { |
| zx_vaddr_t map_addr; |
| const size_t vmar_offset = i * page_size; |
| const uint64_t vmo_offset = i * 2 * page_size; |
| ASSERT_OK(vmar.map(ZX_VM_PERM_READ | ZX_VM_PERM_WRITE | ZX_VM_SPECIFIC, vmar_offset, vmo, |
| vmo_offset, page_size, &map_addr)); |
| } |
| |
| // Skew the protect address to be in the middle of the mapping so that some kind of interesting |
| // tree walk has to happen. Ideally we would use many different offsets, but this is a lot of |
| // additional permutations without much expected benefit. |
| const zx_vaddr_t protect_addr = addr + ((total_mappings - protect_mappings) / 2 * page_size); |
| const size_t protect_size = protect_mappings * page_size; |
| // For the case of |toggle_protect| we will alternate the write permissions of the mapping. |
| bool protect_write = true; |
| while (state->KeepRunning()) { |
| ASSERT_OK(vmar.protect(ZX_VM_PERM_READ | (protect_write ? ZX_VM_PERM_WRITE : 0), protect_addr, |
| protect_size)); |
| if (toggle_protect) { |
| protect_write = !protect_write; |
| } |
| } |
| vmar.destroy(); |
| return true; |
| } |
| |
| // Measures the time taken to decommit pages from a VMO via the VMAR mappings. |commit| controls |
| // whether pages are committed to the VMO, and hence whether the decommit step performs any true |
| // work, with |num_mappings| * |pages_per_mapping| being the total number of pages committed and |
| // decommitted. |
| // |
| // This is functionally equivalent to performing decommit directly on the VMO, however the VMAR |
| // lookup and walking adds overhead that we want to measure. |
| bool VmarDecommit(perftest::RepeatState* state, bool commit, uint32_t num_mappings, |
| uint32_t pages_per_mapping) { |
| // Create a VMAR to hold all the mappings. |
| const uint32_t page_size = zx_system_get_page_size(); |
| const size_t vmar_size = num_mappings * pages_per_mapping * page_size; |
| zx::vmar vmar; |
| zx_vaddr_t addr = 0; |
| ASSERT_OK(zx::vmar::root_self()->allocate( |
| ZX_VM_CAN_MAP_SPECIFIC | ZX_VM_CAN_MAP_READ | ZX_VM_CAN_MAP_WRITE, 0, vmar_size, &vmar, |
| &addr)); |
| |
| // Create a VMO to map in that is twice as large as the VMAR so that every second range can be |
| // mapped. Mapping in every second range prevents mappings from being internally merged. |
| const size_t vmo_size = vmar_size * 2; |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(vmo_size, 0, &vmo)); |
| |
| // Map in every second range. |
| const size_t mapping_size = pages_per_mapping * page_size; |
| for (uint32_t i = 0; i < num_mappings; i++) { |
| zx_vaddr_t map_addr; |
| const size_t vmar_offset = i * mapping_size; |
| const uint64_t vmo_offset = i * 2 * mapping_size; |
| ASSERT_OK(vmar.map(ZX_VM_PERM_READ | ZX_VM_PERM_WRITE | ZX_VM_SPECIFIC, vmar_offset, vmo, |
| vmo_offset, mapping_size, &map_addr)); |
| } |
| |
| if (commit) { |
| state->DeclareStep("Commit"); |
| } |
| state->DeclareStep("Decommit"); |
| |
| while (state->KeepRunning()) { |
| if (commit) { |
| ASSERT_OK(vmar.op_range(ZX_VMAR_OP_COMMIT, addr, vmar_size, nullptr, 0)); |
| state->NextStep(); |
| } |
| ASSERT_OK(vmar.op_range(ZX_VMAR_OP_DECOMMIT, addr, vmar_size, nullptr, 0)); |
| } |
| vmar.destroy(); |
| return true; |
| } |
| |
| void RegisterTests() { |
| for (unsigned total : {1, 16, 128}) { |
| for (unsigned protect : {1, 16, 128}) { |
| if (protect > total) { |
| continue; |
| } |
| for (bool toggle : {true, false}) { |
| auto protect_name = fbl::StringPrintf("Vmar/Protect%s/%uMappings/%uProtect", |
| toggle ? "Toggle" : "Same", total, protect); |
| perftest::RegisterTest(protect_name.c_str(), VmarMultiMappingsProtect, toggle, total, |
| protect); |
| } |
| } |
| } |
| for (unsigned pages : {1, 128, 1024}) { |
| for (unsigned mappings : {1, 4, 32}) { |
| for (bool uncommitted : {true, false}) { |
| auto decommit_name = |
| fbl::StringPrintf("Vmar/Decommit%s/%uMappings/%uPages", |
| uncommitted ? "Uncommitted" : "", mappings, pages * mappings); |
| perftest::RegisterTest(decommit_name.c_str(), VmarDecommit, uncommitted, mappings, pages); |
| } |
| } |
| } |
| } |
| PERFTEST_CTOR(RegisterTests) |
| |
| } // namespace |