1 // Copyright (c) 2024 Apple Inc. All rights reserved. 2 3 #include "sched_test_harness/sched_policy_darwintest.h" 4 #include "sched_test_harness/sched_edge_harness.h" 5 6 T_GLOBAL_META(T_META_NAMESPACE("xnu.scheduler"), 7 T_META_RADAR_COMPONENT_NAME("xnu"), 8 T_META_RADAR_COMPONENT_VERSION("scheduler"), 9 T_META_RUN_CONCURRENTLY(true), 10 T_META_OWNER("emily_peterson")); 11 12 SCHED_POLICY_T_DECL(migration_cluster_bound, 13 "Verify that cluster-bound threads always choose the bound " 14 "cluster except when its derecommended") 15 { 16 int ret; 17 init_migration_harness(dual_die); 18 struct thread_group *tg = create_tg(0); 19 test_thread_t threads[dual_die.num_psets]; 20 int idle_load = 0; 21 int low_load = 100000; 22 int high_load = 10000000; 23 for (int i = 0; i < dual_die.num_psets; i++) { 24 threads[i] = create_thread(TH_BUCKET_SHARE_DF, tg, root_bucket_to_highest_pri[TH_BUCKET_SHARE_DF]); 25 set_thread_cluster_bound(threads[i], i); 26 set_pset_load_avg(i, TH_BUCKET_SHARE_DF, low_load); 27 } 28 for (int i = 0; i < dual_die.num_psets; i++) { 29 set_current_processor(cluster_id_to_cpu_id(i)); 30 for (int j = 0; j < dual_die.num_psets; j++) { 31 /* Add extra load to the bound cluster, so we're definitely not just idle short-circuiting */ 32 set_pset_load_avg(j, TH_BUCKET_SHARE_DF, high_load); 33 ret = choose_pset_for_thread_expect(threads[j], j); 34 T_QUIET; T_EXPECT_TRUE(ret, "Expecting the bound cluster"); 35 set_pset_load_avg(j, TH_BUCKET_SHARE_DF, low_load); 36 } 37 } 38 SCHED_POLICY_PASS("Cluster bound chooses bound cluster"); 39 /* Derecommend the bound cluster */ 40 for (int i = 0; i < dual_die.num_psets; i++) { 41 set_pset_derecommended(i); 42 int replacement_pset = -1; 43 for (int j = 0; j < dual_die.num_psets; j++) { 44 /* Find the first homogenous cluster and mark it as idle so we choose it */ 45 if ((i != j) && (dual_die.psets[i].cpu_type == dual_die.psets[j].cpu_type)) { 46 replacement_pset = j; 47 set_pset_load_avg(replacement_pset, TH_BUCKET_SHARE_DF, idle_load); 48 break; 49 } 50 } 51 ret = choose_pset_for_thread_expect(threads[i], replacement_pset); 52 T_QUIET; T_EXPECT_TRUE(ret, "Expecting the idle pset when the bound cluster is derecommended"); 53 /* Restore pset conditions */ 54 set_pset_recommended(i); 55 set_pset_load_avg(replacement_pset, TH_BUCKET_SHARE_DF, low_load); 56 } 57 SCHED_POLICY_PASS("Cluster binding is soft"); 58 } 59 60 SCHED_POLICY_T_DECL(migration_should_yield, 61 "Verify that we only yield if there's a \"good enough\" thread elsewhere " 62 "to switch to") 63 { 64 int ret; 65 init_migration_harness(basic_amp); 66 struct thread_group *tg = create_tg(0); 67 test_thread_t background = create_thread(TH_BUCKET_SHARE_BG, tg, root_bucket_to_highest_pri[TH_BUCKET_SHARE_BG]); 68 test_thread_t yielder = create_thread(TH_BUCKET_SHARE_DF, tg, root_bucket_to_highest_pri[TH_BUCKET_SHARE_DF]); 69 cpu_set_thread_current(0, yielder); 70 ret = cpu_check_should_yield(0, false); 71 T_QUIET; T_EXPECT_TRUE(ret, "No thread present to yield to"); 72 enqueue_thread(cluster_target(0), background); 73 ret = cpu_check_should_yield(0, true); 74 T_QUIET; T_EXPECT_TRUE(ret, "Should yield to a low priority thread on the current runqueue"); 75 SCHED_POLICY_PASS("Basic yield behavior on single pset"); 76 77 ret = dequeue_thread_expect(cluster_target(0), background); 78 T_QUIET; T_EXPECT_TRUE(ret, "Only background thread in runqueue"); 79 cpu_set_thread_current(0, yielder); /* Reset current thread */ 80 enqueue_thread(cluster_target(1), background); 81 ret = cpu_check_should_yield(0, true); 82 T_QUIET; T_EXPECT_TRUE(ret, "Should yield in order to steal thread"); 83 ret = dequeue_thread_expect(cluster_target(1), background); 84 T_QUIET; T_EXPECT_TRUE(ret, "Only background thread in runqueue"); 85 cpu_set_thread_current(cluster_id_to_cpu_id(1), background); 86 ret = cpu_check_should_yield(cluster_id_to_cpu_id(1), false); 87 T_QUIET; T_EXPECT_TRUE(ret, "Should not yield in order to rebalance (presumed) native thread"); 88 SCHED_POLICY_PASS("Thread yields in order to steal from other psets"); 89 } 90 91 SCHED_POLICY_T_DECL(migration_ipi_policy, 92 "Verify we send the right type of IPI in different cross-core preemption scenarios") 93 { 94 int ret; 95 init_migration_harness(dual_die); 96 struct thread_group *tg = create_tg(0); 97 thread_t thread = create_thread(TH_BUCKET_SHARE_DF, tg, root_bucket_to_highest_pri[TH_BUCKET_SHARE_DF]); 98 int dst_pcore = 3; 99 int src_pcore = 0; 100 101 set_current_processor(src_pcore); 102 cpu_send_ipi_for_thread(dst_pcore, thread, TEST_IPI_EVENT_PREEMPT); 103 ret = ipi_expect(dst_pcore, TEST_IPI_IDLE); 104 T_QUIET; T_EXPECT_TRUE(ret, "Idle CPU"); 105 106 thread_t core_busy = create_thread(TH_BUCKET_SHARE_DF, tg, root_bucket_to_highest_pri[TH_BUCKET_SHARE_DF]); 107 cpu_set_thread_current(dst_pcore, core_busy); 108 set_current_processor(src_pcore); 109 cpu_send_ipi_for_thread(dst_pcore, thread, TEST_IPI_EVENT_PREEMPT); 110 ret = ipi_expect(dst_pcore, TEST_IPI_IMMEDIATE); 111 T_QUIET; T_EXPECT_TRUE(ret, "Should immediate IPI to preempt on P-core"); 112 SCHED_POLICY_PASS("Immediate IPIs to preempt P-cores"); 113 114 int dst_ecore = 13; 115 int ecluster_id = 5; 116 set_tg_sched_bucket_preferred_pset(tg, TH_BUCKET_SHARE_DF, ecluster_id); 117 set_current_processor(src_pcore); 118 cpu_send_ipi_for_thread(dst_ecore, thread, TEST_IPI_EVENT_PREEMPT); 119 ret = ipi_expect(dst_ecore, TEST_IPI_IDLE); 120 T_QUIET; T_EXPECT_TRUE(ret, "Idle CPU"); 121 122 cpu_set_thread_current(dst_ecore, core_busy); 123 set_current_processor(src_pcore); 124 cpu_send_ipi_for_thread(dst_ecore, thread, TEST_IPI_EVENT_PREEMPT); 125 ret = ipi_expect(dst_ecore, TEST_IPI_IMMEDIATE); 126 T_QUIET; T_EXPECT_TRUE(ret, "Should immediate IPI to preempt for E->E"); 127 SCHED_POLICY_PASS("Immediate IPIs to cluster homogeneous with preferred"); 128 } 129 130 SCHED_POLICY_T_DECL(migration_max_parallelism, 131 "Verify we report expected values for recommended width of parallel workloads") 132 { 133 int ret; 134 init_migration_harness(dual_die); 135 uint32_t num_pclusters = 4; 136 uint32_t num_pcores = 4 * num_pclusters; 137 uint32_t num_eclusters = 2; 138 uint32_t num_ecores = 2 * num_eclusters; 139 for (thread_qos_t qos = THREAD_QOS_UNSPECIFIED; qos < THREAD_QOS_LAST; qos++) { 140 for (int shared_rsrc = 0; shared_rsrc < 2; shared_rsrc++) { 141 for (int rt = 0; rt < 2; rt++) { 142 uint64_t options = 0; 143 uint32_t expected_width = 0; 144 if (shared_rsrc) { 145 options |= QOS_PARALLELISM_CLUSTER_SHARED_RESOURCE; 146 } 147 if (rt) { 148 options |= QOS_PARALLELISM_REALTIME; 149 /* Recommend P-width */ 150 expected_width = shared_rsrc ? num_pclusters : num_pcores; 151 } else if (qos == THREAD_QOS_BACKGROUND || qos == THREAD_QOS_MAINTENANCE) { 152 /* Recommend E-width */ 153 expected_width = shared_rsrc ? num_eclusters : num_ecores; 154 } else { 155 /* Recommend full width */ 156 expected_width = shared_rsrc ? (num_eclusters + num_pclusters) : (num_pcores + num_ecores); 157 } 158 ret = max_parallelism_expect(qos, options, expected_width); 159 T_QUIET; T_EXPECT_TRUE(ret, "Unexpected width for QoS %d shared_rsrc %d RT %d", 160 qos, shared_rsrc, rt); 161 } 162 } 163 } 164 SCHED_POLICY_PASS("Correct recommended parallel width for all configurations"); 165 } 166