/* * Copyright (c) 2023 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@ */ /* test that the header doesn't implicitly depend on others */ #include #include #include #include #include #include #include #include #include /* TODO: can this come from the right header? */ #define THREAD_GROUP_FLAGS_GAME_MODE 0x800 T_GLOBAL_META(T_META_NAMESPACE("xnu.scheduler"), T_META_RADAR_COMPONENT_NAME("xnu"), T_META_RADAR_COMPONENT_VERSION("scheduler"), T_META_OWNER("chimene"), T_META_RUN_CONCURRENTLY(false), T_META_TAG_VM_PREFERRED); static void check_game_mode(bool expected_mode) { int game_mode = getpriority(PRIO_DARWIN_GAME_MODE, 0); T_QUIET; T_ASSERT_POSIX_SUCCESS(game_mode, "getpriority(PRIO_DARWIN_GAME_MODE)"); T_LOG("pid %d: game mode is: %d", getpid(), game_mode); if (expected_mode) { T_QUIET; T_ASSERT_EQ(game_mode, PRIO_DARWIN_GAME_MODE_ON, "should be on"); } else { T_QUIET; T_ASSERT_EQ(game_mode, PRIO_DARWIN_GAME_MODE_OFF, "should be off"); } } T_DECL(entitled_game_mode, "game mode bit should be settable while entitled") { T_LOG("uid: %d", getuid()); check_game_mode(false); T_ASSERT_POSIX_SUCCESS(setpriority(PRIO_DARWIN_GAME_MODE, 0, PRIO_DARWIN_GAME_MODE_ON), "setpriority(PRIO_DARWIN_GAME_MODE, 0, PRIO_DARWIN_GAME_MODE_ON)"); check_game_mode(true); T_ASSERT_POSIX_SUCCESS(setpriority(PRIO_DARWIN_GAME_MODE, 0, PRIO_DARWIN_GAME_MODE_OFF), "setpriority(PRIO_DARWIN_GAME_MODE, 0, PRIO_DARWIN_GAME_MODE_OFF)"); check_game_mode(false); } T_DECL(entitled_game_mode_read_root, "game mode bit should be readable as root", T_META_ASROOT(true)) { T_LOG("uid: %d", getuid()); check_game_mode(false); } T_DECL(entitled_game_mode_read_notroot, "game mode bit should be readable as not root but entitled", T_META_ASROOT(false)) { T_LOG("uid: %d", getuid()); check_game_mode(false); } static struct coalinfo_debuginfo get_coal_debuginfo(uint64_t coal_id, char* prefix) { struct coalinfo_debuginfo coaldebuginfo = {}; int ret = coalition_info_debug_info(coal_id, &coaldebuginfo, sizeof(coaldebuginfo)); T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "coalition_info_debug_info"); T_LOG("coal(%s): %lld, game count %d, flags 0x%x (group 0x%llx, rec %d, focal: %d, nonfocal %d)", prefix, coal_id, coaldebuginfo.game_task_count, coaldebuginfo.thread_group_flags, coaldebuginfo.thread_group_id, coaldebuginfo.thread_group_recommendation, coaldebuginfo.focal_task_count, coaldebuginfo.nonfocal_task_count); return coaldebuginfo; } static void check_game_mode_count(uint32_t expected_count) { struct proc_pidcoalitioninfo idinfo = {}; int ret = proc_pidinfo(getpid(), PROC_PIDCOALITIONINFO, 0, &idinfo, sizeof(idinfo)); T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "proc_pidinfo(... PROC_PIDCOALITIONINFO ...)"); uint64_t res_id = idinfo.coalition_id[COALITION_TYPE_RESOURCE]; uint64_t jet_id = idinfo.coalition_id[COALITION_TYPE_JETSAM]; struct coalinfo_debuginfo coaldebuginfo_res = get_coal_debuginfo(res_id, "res"); struct coalinfo_debuginfo coaldebuginfo_jet = get_coal_debuginfo(jet_id, "jet"); if (expected_count) { // see COALITION_FOCAL_TASKS_ACCOUNTING for this difference #if TARGET_OS_OSX T_ASSERT_EQ(coaldebuginfo_res.game_task_count, expected_count, "should have game in res coalition"); T_QUIET; T_ASSERT_EQ(coaldebuginfo_jet.game_task_count, 0, "should not have game in jet coalition"); #else T_QUIET; T_ASSERT_EQ(coaldebuginfo_res.game_task_count, 0, "should not have game in res coalition"); T_ASSERT_EQ(coaldebuginfo_jet.game_task_count, expected_count, "should have game in jet coalition"); #endif T_ASSERT_BITS_SET(coaldebuginfo_jet.thread_group_flags, THREAD_GROUP_FLAGS_GAME_MODE, "should have game mode flag in jet coalition"); \ } else { #if TARGET_OS_OSX T_ASSERT_EQ(coaldebuginfo_res.game_task_count, 0, "should not have game in res coalition"); T_QUIET; T_ASSERT_EQ(coaldebuginfo_jet.game_task_count, 0, "should not have game in jet coalition"); #else T_QUIET; T_ASSERT_EQ(coaldebuginfo_res.game_task_count, 0, "should not have game in res coalition"); T_ASSERT_EQ(coaldebuginfo_jet.game_task_count, 0, "should not have game in jet coalition"); #endif T_ASSERT_BITS_NOTSET(coaldebuginfo_jet.thread_group_flags, THREAD_GROUP_FLAGS_GAME_MODE, "should not have game mode flag in jet coalition"); \ } T_QUIET; T_ASSERT_BITS_NOTSET(coaldebuginfo_res.thread_group_flags, THREAD_GROUP_FLAGS_GAME_MODE, "should never have game mode flag in res coalition"); \ } static void skip_if_unsupported(void) { int r; int supported = 0; size_t supported_size = sizeof(supported); r = sysctlbyname("kern.thread_groups_supported", &supported, &supported_size, NULL, 0); if (r < 0) { T_WITH_ERRNO; T_SKIP("could not find \"kern.thread_groups_supported\" sysctl"); } if (!supported) { T_SKIP("test was run even though kern.thread_groups_supported is not 1, see rdar://111297938"); } r = sysctlbyname("kern.development", &supported, &supported_size, NULL, 0); if (r < 0) { T_WITH_ERRNO; T_SKIP("could not find \"kern.development\" sysctl"); } if (!supported) { T_SKIP("test was run even though kern.development is not 1, see rdar://111297938"); } } T_DECL(entitled_game_mode_check_count, "game mode bit should affect coalition thread group flags", T_META_REQUIRES_SYSCTL_EQ("kern.development", 1), T_META_REQUIRES_SYSCTL_EQ("kern.thread_groups_supported", 1)) { T_LOG("uid: %d", getuid()); skip_if_unsupported(); check_game_mode(false); check_game_mode_count(0); T_ASSERT_POSIX_SUCCESS(setpriority(PRIO_DARWIN_GAME_MODE, 0, PRIO_DARWIN_GAME_MODE_ON), "setpriority(PRIO_DARWIN_GAME_MODE_ON)"); check_game_mode(true); check_game_mode_count(1); T_ASSERT_POSIX_SUCCESS(setpriority(PRIO_DARWIN_GAME_MODE, 0, PRIO_DARWIN_GAME_MODE_OFF), "setpriority(PRIO_DARWIN_GAME_MODE_OFF)"); check_game_mode(false); check_game_mode_count(0); } T_DECL(game_mode_child_exit, "game mode bit should disappear when child exits", T_META_REQUIRES_SYSCTL_EQ("kern.development", 1), T_META_REQUIRES_SYSCTL_EQ("kern.thread_groups_supported", 1)) { T_LOG("uid: %d", getuid()); skip_if_unsupported(); check_game_mode(false); check_game_mode_count(0); T_LOG("Spawning child"); pid_t child_pid = fork(); if (child_pid == 0) { /* child process */ check_game_mode(false); check_game_mode_count(0); T_ASSERT_POSIX_SUCCESS(setpriority(PRIO_DARWIN_GAME_MODE, 0, PRIO_DARWIN_GAME_MODE_ON), "setpriority(PRIO_DARWIN_GAME_MODE_ON)"); check_game_mode(true); check_game_mode_count(1); T_LOG("Exit pid %d with the game mode bit on", getpid()); exit(0); } else { T_ASSERT_POSIX_SUCCESS(child_pid, "fork, pid %d", child_pid); /* wait for child process to exit */ int exit_status = 0, signum = 0; T_ASSERT_TRUE(dt_waitpid(child_pid, &exit_status, &signum, 5), "wait for child (%d) complete", child_pid); T_QUIET; T_ASSERT_EQ(exit_status, 0, "dt_waitpid: exit_status"); T_QUIET; T_ASSERT_EQ(signum, 0, "dt_waitpid: signum"); } check_game_mode(false); check_game_mode_count(0); } T_DECL(game_mode_double_set_and_child_exit, "game mode bit on parent should stay when game mode child exits", T_META_REQUIRES_SYSCTL_EQ("kern.development", 1), T_META_REQUIRES_SYSCTL_EQ("kern.thread_groups_supported", 1)) { T_LOG("uid: %d", getuid()); skip_if_unsupported(); check_game_mode(false); check_game_mode_count(0); T_ASSERT_POSIX_SUCCESS(setpriority(PRIO_DARWIN_GAME_MODE, 0, PRIO_DARWIN_GAME_MODE_ON), "setpriority(PRIO_DARWIN_GAME_MODE_ON)"); check_game_mode(true); check_game_mode_count(1); pid_t child_pid = fork(); if (child_pid == 0) { /* child process */ check_game_mode(false); check_game_mode_count(1); T_ASSERT_POSIX_SUCCESS(setpriority(PRIO_DARWIN_GAME_MODE, 0, PRIO_DARWIN_GAME_MODE_ON), "setpriority(PRIO_DARWIN_GAME_MODE_ON)"); check_game_mode(true); check_game_mode_count(2); T_LOG("Exit pid %d with the game mode bit on", getpid()); exit(0); } else { T_ASSERT_POSIX_SUCCESS(child_pid, "fork, pid %d", child_pid); /* wait for child process to exit */ int exit_status = 0, signum = 0; T_ASSERT_TRUE(dt_waitpid(child_pid, &exit_status, &signum, 5), "wait for child (%d) complete", child_pid); T_QUIET; T_ASSERT_EQ(exit_status, 0, "dt_waitpid: exit_status"); T_QUIET; T_ASSERT_EQ(signum, 0, "dt_waitpid: signum"); } check_game_mode(true); check_game_mode_count(1); T_ASSERT_POSIX_SUCCESS(setpriority(PRIO_DARWIN_GAME_MODE, 0, PRIO_DARWIN_GAME_MODE_OFF), "setpriority(PRIO_DARWIN_GAME_MODE_OFF)"); check_game_mode(false); check_game_mode_count(0); } T_DECL(game_mode_double_set_and_child_unset, "game mode bit on parent should stay when game mode child unsets", T_META_REQUIRES_SYSCTL_EQ("kern.development", 1), T_META_REQUIRES_SYSCTL_EQ("kern.thread_groups_supported", 1)) { T_LOG("uid: %d", getuid()); skip_if_unsupported(); check_game_mode(false); check_game_mode_count(0); T_ASSERT_POSIX_SUCCESS(setpriority(PRIO_DARWIN_GAME_MODE, 0, PRIO_DARWIN_GAME_MODE_ON), "setpriority(PRIO_DARWIN_GAME_MODE_ON)"); check_game_mode(true); check_game_mode_count(1); pid_t child_pid = fork(); if (child_pid == 0) { /* child process */ check_game_mode(false); check_game_mode_count(1); T_ASSERT_POSIX_SUCCESS(setpriority(PRIO_DARWIN_GAME_MODE, 0, PRIO_DARWIN_GAME_MODE_ON), "setpriority(PRIO_DARWIN_GAME_MODE_ON)"); check_game_mode(true); check_game_mode_count(2); T_ASSERT_POSIX_SUCCESS(setpriority(PRIO_DARWIN_GAME_MODE, 0, PRIO_DARWIN_GAME_MODE_OFF), "setpriority(PRIO_DARWIN_GAME_MODE_OFF)"); check_game_mode(false); check_game_mode_count(1); T_LOG("Exit pid %d with the game mode bit off", getpid()); exit(0); } else { T_ASSERT_POSIX_SUCCESS(child_pid, "fork, pid %d", child_pid); /* wait for child process to exit */ int exit_status = 0, signum = 0; T_ASSERT_TRUE(dt_waitpid(child_pid, &exit_status, &signum, 5), "wait for child (%d) complete", child_pid); T_QUIET; T_ASSERT_EQ(exit_status, 0, "dt_waitpid: exit_status"); T_QUIET; T_ASSERT_EQ(signum, 0, "dt_waitpid: signum"); } check_game_mode(true); check_game_mode_count(1); T_ASSERT_POSIX_SUCCESS(setpriority(PRIO_DARWIN_GAME_MODE, 0, PRIO_DARWIN_GAME_MODE_OFF), "setpriority(PRIO_DARWIN_GAME_MODE_OFF)"); check_game_mode(false); check_game_mode_count(0); }