/* * Copyright (c) 2025 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. The rights granted to you under the License * may not be used to create, or enable the creation or redistribution of, * unlawful or unlicensed copies of an Apple operating system, or to * circumvent, violate, or enable the circumvention or violation of, any * terms of an Apple operating system software license agreement. * * Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ */ #include #include #include #include #include #include #include #include /* for the sysctl tests */ #if HAS_MTE #include /* * This test verifies that the KMA_COMPRESSOR flag translates to tag storage * pages getting allocated when `vm_mte_tag_storage_for_compressor` is on. */ static int vm_mte_test_tag_storage_for_compressor(int64_t in, int64_t *out) { kern_return_t kr = KERN_FAILURE; kma_flags_t flags = KMA_NOFAIL | KMA_COMPRESSOR; vm_size_t page_count = 0; vm_page_t page = NULL; vm_page_t page_list = NULL; uint64_t cpu_free_claimed_count = 0; uint32_t correct_alloc_count = 0; bool expect_tag_storage = (bool)in; int ret = 0; *out = 1; disable_preemption(); cpu_free_claimed_count = counter_load(&vm_cpu_free_claimed_count); enable_preemption(); if (cpu_free_claimed_count) { page_count = MIN(cpu_free_claimed_count, VMP_FREE_BATCH_SIZE); printf("%s: page count: %lu\n", __func__, page_count); } else { printf("%s: no pages on CPU free claimed queue. Skipping...\n", __func__); *out = 1; return 0; } kr = vm_page_alloc_list(page_count, flags, &page_list); if (kr != KERN_SUCCESS) { printf("%s: vm_page_alloc_list failed, kr=%d\n", __func__, kr); *out = 0; return ENOMEM; } /* * We can't guarantee that _all_ pages will be tag storage (if we want that), * so if at least one page is tag storage when we want it to be, we will be * happy. * * If we are not expecting tag storage, _no_ page can be tag storage. */ _vm_page_list_foreach(page, page_list) { if (expect_tag_storage == vm_page_is_tag_storage(page)) { correct_alloc_count++; } } if ((expect_tag_storage && !correct_alloc_count) || (!expect_tag_storage && (correct_alloc_count != page_count))) { printf("%s: %lu tag storage pages were allocated.\n", __func__, expect_tag_storage ? correct_alloc_count : (page_count - correct_alloc_count)); *out = 0; ret = ENOMEM; goto done; } done: vm_page_free_list(page_list, FALSE); return ret; } SYSCTL_TEST_REGISTER(vm_mte_tag_storage_for_compressor, vm_mte_test_tag_storage_for_compressor); /* * This test verifies that if VM_MEMORY_STACK is specified in the mte_ts_vmtag * boot-arg, we get a tag storage page back. */ static int vm_mte_test_tag_storage_for_vm_tag(int64_t in, int64_t *out) { kern_return_t kr = KERN_FAILURE; mach_vm_offset_ut addr_u; mach_vm_address_t addr; uint64_t cpu_free_claimed_count = 0; ppnum_t pnum = -1; vm_map_kernel_flags_t vmk_flags = VM_MAP_KERNEL_FLAGS_ANYWHERE(); int error = -1; int ret = 0; struct { uint32_t vm_tag; bool mte; bool expect_ts; } args; *out = 1; error = copyin(in, &args, sizeof(args)); if (error) { printf("%s: copyin from userspace failed, error: %d.\n", __func__, error); *out = 0; return 1; } vmk_flags.vmf_mte = args.mte; vmk_flags.vm_tag = args.vm_tag; disable_preemption(); cpu_free_claimed_count = counter_load(&vm_cpu_free_claimed_count); enable_preemption(); if (!cpu_free_claimed_count) { printf("%s: no pages on CPU free claimed queue.\n", __func__); *out = 1; return 0; } printf("%s: free claimed count: %llu\n", __func__, cpu_free_claimed_count); kr = mach_vm_allocate_kernel(current_map(), &addr_u, vm_sanitize_wrap_size(PAGE_SIZE), vmk_flags); if (kr != KERN_SUCCESS) { printf("%s: mach_vm_allocate_kernel failed with kr=%d.\n", __func__, kr); *out = 0; return 1; } /* Fault in the page */ addr = vm_sanitize_addr(current_map(), addr_u); kr = vm_fault( current_map(), addr, VM_PROT_READ | VM_PROT_WRITE, FALSE, /* change_wiring */ VM_KERN_MEMORY_NONE, THREAD_UNINT, NULL, 0); if (kr != KERN_SUCCESS) { printf("%s: vm_fault failed with kr=%d.\n", __func__, kr); *out = 0; ret = 1; goto done; } /* Check the physical page */ pnum = vm_map_get_phys_page(current_map(), (vm_offset_t)addr); if (vm_page_is_tag_storage(vm_page_find_canonical(pnum)) ^ args.expect_ts) { printf("%s: tag: %d, expected tag storage: %d, mte: %d, pnum: %d\n", __func__, args.vm_tag, args.expect_ts, args.mte, pnum); if (args.expect_ts) { /* We can't guarantee that we get a tag storage page, so pass anyway. */ goto done; } *out = 0; ret = 1; goto done; } done: mach_vm_deallocate(current_map(), addr, PAGE_SIZE); return ret; } SYSCTL_TEST_REGISTER(vm_mte_tag_storage_for_vm_tag, vm_mte_test_tag_storage_for_vm_tag); #endif /* HAS_MTE */