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