1 // © 2021 Qualcomm Innovation Center, Inc. All rights reserved.
2 //
3 // SPDX-License-Identifier: BSD-3-Clause
4 
5 // Preemption control functions for preemptible configurations.
6 
7 #include <assert.h>
8 #include <hyptypes.h>
9 
10 #include <compiler.h>
11 #include <irq.h>
12 #include <preempt.h>
13 #include <scheduler.h>
14 #include <trace.h>
15 #include <util.h>
16 
17 #include <events/preempt.h>
18 
19 #include <asm/interrupt.h>
20 
21 #include "event_handlers.h"
22 
23 static _Thread_local count_t preempt_disable_count;
24 
25 static const count_t preempt_count_mask =
26 	(count_t)util_bit((index_t)PREEMPT_BITS_COUNT_MAX + 1U) - 1U;
27 static const count_t preempt_count_max = preempt_count_mask;
28 
29 static_assert(PREEMPT_BITS_COUNT_MAX < PREEMPT_BITS_CPU_INIT,
30 	      "PREEMPT_BITS_CPU_INIT invalid");
31 static_assert(PREEMPT_BITS_COUNT_MAX < PREEMPT_BITS_IN_INTERRUPT,
32 	      "PREEMPT_BITS_IN_INTERRUPT invalid");
33 static_assert(PREEMPT_BITS_COUNT_MAX < PREEMPT_BITS_ABORT_KERNEL,
34 	      "PREEMPT_BITS_ABORT_KERNEL invalid");
35 
36 static const count_t preempt_cpu_init =
37 	(count_t)util_bit((index_t)PREEMPT_BITS_CPU_INIT);
38 static const count_t preempt_in_interrupt =
39 	(count_t)util_bit((index_t)PREEMPT_BITS_IN_INTERRUPT);
40 static const count_t preempt_abort_kernel =
41 	(count_t)util_bit((index_t)PREEMPT_BITS_ABORT_KERNEL);
42 
43 void
preempt_handle_boot_cpu_early_init(void)44 preempt_handle_boot_cpu_early_init(void) LOCK_IMPL
45 {
46 	// Prevent an accidental preempt-enable during the boot sequence.
47 	preempt_disable_count |= preempt_cpu_init;
48 }
49 
50 void
preempt_handle_boot_cpu_start(void)51 preempt_handle_boot_cpu_start(void) LOCK_IMPL
52 {
53 	// Boot has finished; allow preemption to be enabled.
54 	assert((preempt_disable_count & preempt_cpu_init) != 0U);
55 	preempt_disable_count &= ~preempt_cpu_init;
56 }
57 
58 void
preempt_handle_thread_start(void)59 preempt_handle_thread_start(void) LOCK_IMPL
60 {
61 	// Arrange for preemption to be enabled by the first
62 	// preempt_enable() call.
63 	//
64 	// Note that preempt_disable_count is briefly 0 in each newly
65 	// started thread even though preemption is always disabled
66 	// across context switches. To avoid problems we must ensure
67 	// that this setup is done as early as possible in new threads,
68 	// before anything that might call preempt_disable().
69 	preempt_disable_count = 1U;
70 }
71 
72 void
preempt_handle_thread_entry_from_user(thread_entry_reason_t reason)73 preempt_handle_thread_entry_from_user(thread_entry_reason_t reason)
74 {
75 	assert(preempt_disable_count == 1U);
76 
77 	if (reason == THREAD_ENTRY_REASON_INTERRUPT) {
78 		preempt_disable_count |= preempt_in_interrupt;
79 	}
80 
81 	preempt_enable();
82 #if defined(VERBOSE) && VERBOSE
83 	assert_preempt_enabled();
84 #endif
85 }
86 
87 void
preempt_handle_thread_exit_to_user(thread_entry_reason_t reason)88 preempt_handle_thread_exit_to_user(thread_entry_reason_t reason)
89 {
90 	assert_preempt_enabled();
91 	preempt_disable();
92 
93 	if (reason == THREAD_ENTRY_REASON_INTERRUPT) {
94 		preempt_disable_count &= ~preempt_in_interrupt;
95 	}
96 
97 	assert(preempt_disable_count == 1U);
98 }
99 
100 void
preempt_disable(void)101 preempt_disable(void) LOCK_IMPL
102 {
103 	assert((preempt_disable_count & preempt_count_mask) <
104 	       preempt_count_max);
105 	asm_interrupt_disable_acquire(&preempt_disable_count);
106 	preempt_disable_count++;
107 	assert_preempt_disabled();
108 }
109 
110 void
preempt_enable(void)111 preempt_enable(void) LOCK_IMPL
112 {
113 	assert((preempt_disable_count & preempt_count_mask) > 0U);
114 	preempt_disable_count--;
115 	if (preempt_disable_count == 0U) {
116 		asm_interrupt_enable_release(&preempt_disable_count);
117 	}
118 }
119 
120 bool
preempt_interrupt_dispatch(void)121 preempt_interrupt_dispatch(void) LOCK_IMPL
122 {
123 	preempt_disable_count |= preempt_in_interrupt;
124 
125 	if (!trigger_preempt_interrupt_event() && irq_interrupt_dispatch()) {
126 		if ((preempt_disable_count & preempt_count_mask) > 0U) {
127 			// Preemption is disabled; we are in some context that
128 			// needs to enable interrupts but can't permit a context
129 			// switch, e.g. the idle loop. Trigger a deferred
130 			// reschedule.
131 			scheduler_trigger();
132 		} else {
133 			(void)scheduler_schedule();
134 		}
135 	}
136 
137 	preempt_disable_count &= ~preempt_in_interrupt;
138 
139 	return false;
140 }
141 
142 void
preempt_disable_in_irq(void)143 preempt_disable_in_irq(void) LOCK_IMPL
144 {
145 	assert((preempt_disable_count & preempt_in_interrupt) != 0U);
146 }
147 
148 void
preempt_enable_in_irq(void)149 preempt_enable_in_irq(void) LOCK_IMPL
150 {
151 	assert((preempt_disable_count & preempt_in_interrupt) != 0U);
152 }
153 
154 bool
preempt_abort_dispatch(void)155 preempt_abort_dispatch(void) LOCK_IMPL
156 {
157 	preempt_disable_count |= preempt_in_interrupt;
158 
159 	bool ret = trigger_preempt_abort_event();
160 
161 	preempt_disable_count &= ~preempt_in_interrupt;
162 
163 	return ret;
164 }
165 
166 void
preempt_handle_scheduler_stop(void)167 preempt_handle_scheduler_stop(void) LOCK_IMPL
168 {
169 	count_t old_count = preempt_disable_count;
170 	asm_interrupt_disable_acquire(&preempt_disable_count);
171 
172 	// Set the abort bit and clear the current count, to avoid an unbounded
173 	// recursion in case preempt_disable() fails the count overflow
174 	// assertion and the abort path calls preempt_disable() again.
175 	preempt_disable_count = preempt_abort_kernel;
176 
177 	// Log the original preempt count.
178 	TRACE(DEBUG, INFO, "preempt: force disabled; previous count was {:#x}",
179 	      old_count);
180 }
181 
182 void
assert_preempt_disabled(void)183 assert_preempt_disabled(void)
184 {
185 	assert(preempt_disable_count != 0U);
186 }
187 
188 void
assert_preempt_enabled(void)189 assert_preempt_enabled(void)
190 {
191 	assert((preempt_disable_count & preempt_count_mask) == 0U);
192 }
193