SatCat5
satcat5/interrupts.cc
1 // Copyright 2021-2025 The Aerospace Corporation.
3 // This file is a part of SatCat5, licensed under CERN-OHL-W v2 or later.
5 
6 #include <satcat5/cfgbus_timer.h>
7 #include <satcat5/interrupts.h>
8 #include <satcat5/list.h>
9 #include <satcat5/polling.h>
10 #include <satcat5/utils.h>
11 
12 namespace irq = satcat5::irq;
13 namespace util = satcat5::util;
14 
15 // Global trackers for max time spent in interrupt or lock mode,
16 // plus the estimated depth of the stack pointer.
17 #if SATCAT5_IRQ_STATS
21  static u8* stack_ref = 0;
22 #endif
23 
24 // Context indicators increment g_lock_count by a huge amount.
25 static const u32 USER_CONTEXT = 0x40000000u;
26 static const u32 INTERRUPT_CONTEXT = 0x80000000u;
27 
28 // Global variable indicates the current interrupt & lock nesting level.
29 // This is necessary because atomic_xx methods may be called even
30 // before the global irq_ctrl has been initialized, since the order
31 // of operations for global constructors is ill-defined across files,
32 // and atomic_xx is a side-effect of many other constructors.
33 static u32 g_lock_count = 0;
34 
35 // Global linked list of all interrupt handlers.
36 // (See note above. This is always initialized before any C++ constructor.)
37 static irq::Handler* g_irq_list = 0;
38 
39 // Global timer object for interrupt and lock statistics.
40 static util::TimeRef* g_timer = 0;
41 
42 // Global pointer to the active Controller object.
43 static irq::Controller* g_irq_ctrl = 0;
44 
45 // Forcibly unregister global event-handler objects.
47  bool ok = true;
48  if (g_lock_count) {g_lock_count = 0; ok = false;}
49  if (g_irq_list) {g_irq_list = 0; ok = false;}
50  if (g_timer) {g_timer = 0; ok = false;}
51  if (g_irq_ctrl) {g_irq_ctrl = 0; ok = false;}
52  return ok;
53 }
54 
55 #if SATCAT5_ALLOW_DELETION
56 irq::Controller::~Controller() {
57  // Clear all global state.
58  g_lock_count = 0;
59  g_irq_ctrl = 0;
60  g_irq_list = 0;
61  g_timer = 0;
62 }
63 #endif
64 
66  irq::AtomicLock lock("IRQ_HANDLER");
67 
68  // Sanity check so we don't do this twice...
69  if (g_lock_count < USER_CONTEXT) return;
70 
71  // Unregister every object on the global list.
72  irq::Handler* ptr = g_irq_list;
73  while (g_irq_ctrl && ptr) {
74  if (ptr->m_irq_idx >= 0) irq_unregister(ptr);
75  ptr = ptr->m_next;
76  }
77  g_irq_list = 0;
78 
79  // Return to the pre-init context.
80  g_lock_count -= USER_CONTEXT;
81 }
82 
84  // Register each of the interrupt handlers.
85  irq::Handler* irq = g_irq_list;
86  while (irq) {
87  irq_register(irq);
88  irq = util::ListCore::next(irq);
89  }
90 
91  // Set timekeeper clock if it hasn't been linked already.
92  satcat5::poll::timekeeper.suggest_clock(timer);
93 
94  // Linking timer now resolves a chicken-and-egg problem if timer
95  // depends on a ConfigBus that needs this InterruptController.
96  // Note the current stack frame as an estimate of the minimum depth.
97 #if SATCAT5_IRQ_STATS
98  g_timer = timer ? timer : SATCAT5_CLOCK;
99  stack_ref = (u8*)__builtin_frame_address(0);
100 #endif
101 
102  // Update internal state as we enter regular runtime.
103  g_irq_ctrl = this;
104  g_lock_count = USER_CONTEXT;
105 }
106 
108  return (g_lock_count >= USER_CONTEXT);
109 }
110 
112  return (g_lock_count >= INTERRUPT_CONTEXT);
113 }
114 
116  return (g_lock_count > USER_CONTEXT);
117 }
118 
120  // Default handler does nothing.
121 }
122 
123 // Note: This method must be static for compatibility with the usual
124 // callback signature of most legacy-C interrupt handlers.
126  satcat5::util::TimeVal tstart = {0, 0};
127  unsigned elapsed = 0;
128 
129  // While in interrupt mode, increment nested-lock count to
130  // prevent duplicate calls to hal_irq_pause().
131  g_lock_count += INTERRUPT_CONTEXT;
132 
133  // In rapid sequence:
134  // * Note start time (if enabled)
135  // * Call the event handler
136  // * Acknowledge interrupt
137  // * Note elapsed time
138  if (SATCAT5_IRQ_STATS) tstart = g_timer->now();
139  obj->irq_event();
140  g_irq_ctrl->irq_acknowledge(obj);
141  if (SATCAT5_IRQ_STATS) elapsed = tstart.elapsed_tick();
142 
143 #if SATCAT5_IRQ_STATS
144  // If enabled, update per-interrupt and global time statistics.
145  worst_irq.update(obj->m_label, elapsed);
146  if (elapsed > obj->m_max_irqtime)
147  obj->m_max_irqtime = elapsed;
148 
149  // Also update the estimated maximum stack-depth.
150  // Note: This assumes stack grows "downward" per common convention.
151  // If this is wrong, the estimate is useless but does no harm.
152  unsigned stack_now = (unsigned)(stack_ref - (u8*)__builtin_frame_address(0));
153  worst_stack.update("STACK", stack_now);
154 #endif
155 
156  // Restore original lock-count.
157  g_lock_count -= INTERRUPT_CONTEXT;
158 }
159 
161  init(timer);
162 }
163 
165  irq::Handler* irq = g_irq_list;
166  while (irq) {
167  service_one(irq);
168  irq = util::ListCore::next(irq);
169  }
170 }
171 
172 irq::Handler::Handler(const char* lbl, int irq)
173  : m_label(lbl)
174  , m_irq_idx(irq)
175  , m_max_irqtime(0)
176  , m_next(0)
177 {
178  irq::AtomicLock lock("IRQ_HANDLER");
179 
180  if (m_irq_idx >= 0) {
181  // Add this interrupt handler to the global list.
182  util::ListCore::add(g_irq_list, this);
183 
184  // Register now if init() has already been called.
185  // (Otherwise, registration is handled by that method.)
186  if (g_lock_count >= USER_CONTEXT)
187  g_irq_ctrl->irq_register(this);
188  }
189 }
190 
191 #if SATCAT5_ALLOW_DELETION
192 irq::Handler::~Handler() {
193  irq::AtomicLock lock("IRQ_HANDLER");
194 
195  // Ignore placeholder interrupts (see above)
196  if (m_irq_idx < 0) return;
197 
198  // If init() has been called, unregister this interrupt.
199  if (g_lock_count >= USER_CONTEXT)
200  g_irq_ctrl->irq_unregister(this);
201 
202  // Remove ourselves from the global linked list.
203  util::ListCore::remove(g_irq_list, this);
204 }
205 #endif
206 
207 irq::Adapter::Adapter(const char* lbl, int irq, satcat5::poll::OnDemand* obj)
208  : irq::Handler(lbl, irq)
209  , m_obj(obj)
210 {
211  // No other initialization required.
212 }
213 
214 #if SATCAT5_ALLOW_DELETION
215 irq::Adapter::~Adapter() {
216  // Parent has already performed all required cleanup.
217 }
218 #endif
219 
221  m_obj->request_poll();
222 }
223 
224 irq::Shared::Shared(const char* lbl, int irq)
225  : irq::Handler(lbl, irq)
226 {
227  // No other initialization required.
228 }
229 
230 #if SATCAT5_ALLOW_DELETION
231 irq::Shared::~Shared() {
232  // Parent has already performed all required cleanup.
233 }
234 #endif
235 
237  // Traverse the list, notifying each callback.
238  irq::Handler* item = m_list.head();
239  while (item) {
240  item->irq_event();
241  item = item->m_next;
242  }
243 }
244 
246  : m_lbl(lbl)
247  , m_tstart{0,0}
248  , m_held(1)
249 {
250  // Disable interrupts EXACTLY ONCE regardless of nesting.
251  if (g_lock_count++ == USER_CONTEXT) {
252  g_irq_ctrl->irq_pause();
253  }
254 
255  // Optionally start the stopwatch for this atomic operation.
256  if (SATCAT5_IRQ_STATS && g_timer)
257  m_tstart = g_timer->now();
258 }
259 
260 irq::AtomicLock::~AtomicLock() {
261  release();
262 }
263 
265  if (m_held) {
266  // Clear flag and update global statistics.
267  m_held = 0;
268 #if SATCAT5_IRQ_STATS
269  // Update stats if applicable.
270  if (g_timer) {
271  irq::worst_lock.update(m_lbl, m_tstart.elapsed_tick());
272  }
273 #endif
274  // Enable interrupts EXACTLY ONCE regardless of nesting.
275  if (--g_lock_count == USER_CONTEXT) {
276  g_irq_ctrl->irq_resume();
277  }
278  }
279 }
Driver for the ConfigBus timer.
void irq_event() override
Method called whenever an interrupt is triggered.
Automatic lock or mutex.
AtomicLock(const char *lbl)
Creating this object starts a critical section.
void release()
Optionally release this lock before the destructor is called.
Platform-agnostic interrupt controller.
virtual void irq_resume()=0
Disable hardware interrupts.
virtual void irq_register(satcat5::irq::Handler *obj)=0
Re-enable hardware interrupts.
static bool is_irq_or_locked()
Are we currently in a critical-section?
virtual void irq_pause()=0
Prevent preemption from "pause" until "resume".
void init(satcat5::util::TimeRef *timer=0)
Start the interrupt controller.
virtual void irq_unregister(satcat5::irq::Handler *obj)=0
Un-register the callback for an interrupt handler.
static bool is_initialized()
Has init() been called?
void stop()
Unregister ALL interrupt handlers.
static bool is_irq_context()
Are we currently servicing an interrupt?
virtual void irq_acknowledge(satcat5::irq::Handler *obj)
Post-handler acknowledgement, notification, and cleanup.
static void interrupt_static(satcat5::irq::Handler *obj)
Static interrupt service routine.
ControllerNull(satcat5::util::TimeRef *timer=0)
Constructor accepts an optional Timer pointer, if available.
void service_all()
User should call one of the "service" methods whenever a SatCat5-related interrupt occurs.
Parent object for receiving interrupt-handler callbacks.
Handler(const char *lbl, int irq)
Only children should create or destroy base class.
virtual void irq_event()=0
Method called whenever an interrupt is triggered.
const char *const m_label
Human-readable label, for debugging.
u32 m_max_irqtime
Statistics tracking for time consumed by this interrupt.
const int m_irq_idx
IRQ index for this interrupt handler.
void irq_event() override
Method called whenever an interrupt is triggered.
An "OnDemand" object is polled only on request.
Definition: polling.h:131
void suggest_clock(satcat5::util::TimeRef *timer)
Compare the provided reference to the current TimeRef, and keep whichever is "better" by an internal ...
Definition: polling.cc:257
static void remove(T *&list, T *item)
Remove the designated item from the list.
Definition: list.h:182
static void add(T *&list, T *item)
Add new item to front or back, whichever is simpler.
Definition: list.h:56
static T * next(const T *item)
Fetch pointer to the next item.
Definition: list.h:151
Running maximum with label tracking.
Definition: utils.h:389
void update(const char *lbl, u32 value)
Update stats if new value exceeds previous record.
Definition: utils.cc:222
The TimeRef API provides access to a monotonic time-counter.
Definition: timeref.h:142
TimeVal now()
Create a TimeVal object using the tick-count from raw().
Definition: timeref.cc:65
Templated functions for manipulating singly-linked lists.
Core event-processing loop for SatCat5 software.
Platform-agnostic API for interrupt management.
satcat5::util::RunningMax worst_irq
Statistics tracking for critical sections.
satcat5::util::RunningMax worst_stack
Statistics tracking for critical sections.
bool pre_test_reset()
Hard-reset of global variables at the start of each unit test.
satcat5::util::RunningMax worst_lock
Statistics tracking for critical sections.
Timestamp for measuring elapsed time.
Definition: timeref.h:48
unsigned elapsed_tick() const
Measure elapsed time in ticks.
Definition: timeref.cc:19
Miscellaneous mathematical utility functions.