The Pedigree Project 0.1
IrqDiagnosticRenderer.cc
1/*
2 * Copyright (c) 2026, Pedigree Developers
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted.
6 */
7
8#include "pedigree/kernel/debugger/commands/IrqDiagnosticRenderer.h"
9
10namespace {
11constexpr uint16_t KnownMaskReasons = IrqMaskNoHandler | IrqMaskAdministrativelyDisabled |
12 IrqMaskAwaitingAcknowledgement |
13 IrqMaskAwaitingThreadedCompletion | IrqMaskMitigated |
14 IrqMaskShuttingDown | IrqMaskControllerContention;
15
16const char* yesNo(bool value) {
17 return value ? "yes" : "no";
18}
19
20const char* deliveryName(IrqDelivery delivery) {
21 switch (delivery) {
22 case IrqDelivery::None:
23 return "none";
24 case IrqDelivery::Hard:
25 return "hard";
26 case IrqDelivery::Threaded:
27 return "threaded";
28 case IrqDelivery::Mixed:
29 return "mixed";
30 }
31 return "unknown";
32}
33
34const char* triggerName(IrqTrigger trigger) {
35 switch (trigger) {
36 case IrqTrigger::Edge:
37 return "edge";
38 case IrqTrigger::Level:
39 return "level";
40 case IrqTrigger::Synthetic:
41 return "synthetic";
42 }
43 return "unknown";
44}
45
46const char* controllerAckName(IrqControllerAck ack) {
47 switch (ack) {
48 case IrqControllerAck::None:
49 return "none";
50 case IrqControllerAck::BeforeHardStage:
51 return "before-hard";
52 case IrqControllerAck::AfterHardStage:
53 return "after-hard";
54 }
55 return "unknown";
56}
57
58const char* lineReleaseName(IrqLineRelease release) {
59 switch (release) {
60 case IrqLineRelease::AfterHardStage:
61 return "after-hard";
62 case IrqLineRelease::AfterThreadedCompletion:
63 return "after-threaded";
64 }
65 return "unknown";
66}
67
68const char* workerDebugStateName(IrqWorkerDebugState state) {
69 switch (state) {
70 case IrqWorkerDebugState::Unavailable:
71 return "unavailable";
72 case IrqWorkerDebugState::None:
73 return "none";
74 case IrqWorkerDebugState::SemaphoreWait:
75 return "sem-wait";
76 case IrqWorkerDebugState::ConditionWait:
77 return "cond-wait";
78 case IrqWorkerDebugState::Joining:
79 return "joining";
80 case IrqWorkerDebugState::FutexWait:
81 return "futex-wait";
82 case IrqWorkerDebugState::EventWait:
83 return "event-wait";
84 case IrqWorkerDebugState::ProcessWait:
85 return "process-wait";
86 case IrqWorkerDebugState::CallbackDrain:
87 return "callback-drain";
88 }
89 return "unknown";
90}
91
92const char* workerWaitReasonName(IrqWorkerWaitReason reason) {
93 switch (reason) {
94 case IrqWorkerWaitReason::Unavailable:
95 return "unavailable";
96 case IrqWorkerWaitReason::Waiting:
97 return "waiting";
98 case IrqWorkerWaitReason::Signalled:
99 return "signalled";
100 case IrqWorkerWaitReason::Event:
101 return "event";
102 case IrqWorkerWaitReason::Unwinding:
103 return "unwinding";
104 case IrqWorkerWaitReason::Terminating:
105 return "terminating";
106 case IrqWorkerWaitReason::Spurious:
107 return "spurious";
108 }
109 return "unknown";
110}
111
112const char* controllerPromptStateName(IrqControllerPromptState state) {
113 switch (state) {
114 case IrqControllerPromptState::NotRequired:
115 return "none";
116 case IrqControllerPromptState::Submitted:
117 return "submitted";
118 case IrqControllerPromptState::Failed:
119 return "failed";
120 }
121 return "unknown";
122}
123
124void appendMaskReason(IrqDiagnosticString& line, bool& first, uint16_t reasons, uint16_t reason,
125 const char* name) {
126 if (!(reasons & reason)) {
127 return;
128 }
129
130 if (!first) {
131 line += ',';
132 }
133 line += name;
134 first = false;
135}
136
137void appendMaskReasons(IrqDiagnosticString& line, uint16_t reasons) {
138 if (reasons == IrqMaskNone) {
139 line += "none";
140 return;
141 }
142
143 bool first = true;
144 appendMaskReason(line, first, reasons, IrqMaskNoHandler, "no-handler");
145 appendMaskReason(line, first, reasons, IrqMaskAdministrativelyDisabled, "admin-disabled");
146 appendMaskReason(line, first, reasons, IrqMaskAwaitingAcknowledgement, "awaiting-ack");
147 appendMaskReason(line, first, reasons, IrqMaskAwaitingThreadedCompletion, "awaiting-threaded");
148 appendMaskReason(line, first, reasons, IrqMaskMitigated, "mitigated");
149 appendMaskReason(line, first, reasons, IrqMaskShuttingDown, "shutting-down");
150 appendMaskReason(line, first, reasons, IrqMaskControllerContention, "controller-contention");
151
152 const uint16_t unknown = reasons & ~KnownMaskReasons;
153 if (unknown) {
154 if (!first) {
155 line += ',';
156 }
157 line += "unknown:0x";
158 line.append(unknown, 16);
159 }
160}
161
162void appendHardDispatchState(IrqDiagnosticString& line, const IrqLineDiagnosticSnapshot& snapshot) {
163 line += " hard[pins=";
164 line.append(snapshot.activeHardDispatchCount);
165 line += " gen=";
166 if (!snapshot.activeHardDispatchCount) {
167 line += "none";
168 } else if (snapshot.activeHardDispatchCount == 1 && snapshot.activeHardDispatchGeneration) {
169 line.append(snapshot.activeHardDispatchGeneration);
170 } else {
171 line += "ambiguous";
172 }
173 line += ']';
174}
175
176void appendAge(IrqDiagnosticString& line, bool present, size_t observed, size_t started) {
177 if (!present || !started || observed < started) {
178 line += "none";
179 } else {
180 line.append(observed - started);
181 }
182}
183
184void appendWorkerState(IrqDiagnosticString& line, const IrqLineDiagnosticSnapshot& snapshot) {
185 line += "\n worker=0x";
186 line.append(snapshot.workerIdentity, 16);
187 line += " handler=";
188 if (!snapshot.activeThreadedDispatchCount) {
189 line += "none";
190 } else if (snapshot.activeThreadedDispatchCount == 1 && snapshot.activeThreadedHandlerIdentity) {
191 line += "0x";
192 line.append(snapshot.activeThreadedHandlerIdentity, 16);
193 } else {
194 line += "ambiguous";
195 }
196 line += " pins=";
197 line.append(snapshot.activeThreadedDispatchCount);
198 line += " cookie[pub=";
199 line.append(snapshot.publicationCookie);
200 line += " pending=";
201 line.append(snapshot.pendingCookie);
202 line += " active=";
203 line.append(snapshot.activeCookie);
204 line += " done=";
205 line.append(snapshot.completedCookie);
206 line += "] batches=";
207 line.append(snapshot.completedBatches);
208 line += " fail[publish=";
209 line.append(snapshot.publicationFailures);
210 line += " remove=";
211 line.append(snapshot.removalRejections);
212 line += " controller=";
213 line.append(snapshot.controllerContentions);
214 line += " diag=";
215 line.append(snapshot.diagnosticPublicationFailures);
216 line += "] prompt[controller attempts=";
217 line.append(snapshot.controllerPromptAttempts);
218 line += " failures=";
219 line.append(snapshot.controllerPromptFailures);
220 line += " dest=0x";
221 line.append(snapshot.controllerPromptDestination, 16);
222 line += " state=";
223 line += controllerPromptStateName(snapshot.controllerPromptState);
224 line += "] state[ack=";
225 line += yesNo(snapshot.acknowledgementPending);
226 line += " threaded=";
227 line += yesNo(snapshot.threadedPending);
228 line += " init=";
229 line += yesNo(snapshot.dispatcherInitialised);
230 line += " active=";
231 line += yesNo(snapshot.dispatcherActive);
232 line += " closed=";
233 line += yesNo(snapshot.dispatcherClosed);
234 line += " hard=";
235 line += yesNo(snapshot.hardStageActive);
236 line += "]\n";
237}
238
239void appendTimingState(IrqDiagnosticString& line, const IrqLineDiagnosticSnapshot& snapshot) {
240 line += " ns[pending=";
241 appendAge(line, snapshot.pendingCookie != 0, snapshot.observationTimestamp,
242 snapshot.pendingSinceTimestamp);
243 line += " active=";
244 appendAge(line, snapshot.activeCookie != 0, snapshot.observationTimestamp,
245 snapshot.activeCallbackStartedTimestamp);
246 line += " wake=";
247 line.append(snapshot.lastWakeLatency);
248 line += '/';
249 line.append(snapshot.maximumWakeLatency);
250 line += " runtime=";
251 line.append(snapshot.lastCallbackRuntime);
252 line += '/';
253 line.append(snapshot.maximumCallbackRuntime);
254 line += "]\n";
255}
256
257void appendWorkerDebugState(IrqDiagnosticString& line, const IrqLineDiagnosticSnapshot& snapshot) {
258 line += " debug[";
259 if (!snapshot.workerDiagnosticAvailable) {
260 line += "unavailable]\n";
261 return;
262 }
263
264 line += "state=";
265 line += workerDebugStateName(snapshot.workerDebugState);
266 line += " addr=0x";
267 line.append(snapshot.workerDebugAddress, 16);
268 if (!snapshot.workerWaitActive) {
269 line += " wait=none]\n";
270 return;
271 }
272
273 line += " waitq=0x";
274 line.append(snapshot.workerWaitQueue, 16);
275 line += " channel=0x";
276 line.append(snapshot.workerWaitChannelOwner, 16);
277 line += ":0x";
278 line.append(snapshot.workerWaitChannelValue, 16);
279 line += " reason=";
280 line += workerWaitReasonName(snapshot.workerWaitReason);
281 line += " level=";
282 line.append(snapshot.workerWaitStateLevel);
283 line += " queued=";
284 line += yesNo(snapshot.workerWaitQueued);
285 line += "]\n";
286}
287} // namespace
288
289void IrqDiagnosticRenderer::render(const IrqLineDiagnosticSnapshot& snapshot,
290 IrqDiagnosticString& line) {
291 line.clear();
292 line += "irq ";
293 line.append(snapshot.line);
294
295 if (!snapshot.snapshotGeneration) {
296 line += " SNAPSHOT MISS\n";
297 return;
298 } else {
299 line += " snap=";
300 line.append(snapshot.snapshotGeneration);
301 line += " cfg=";
302 line += yesNo(snapshot.configured);
303 line += " handlers=";
304 line.append(snapshot.handlerCount);
305 line += " delivery=";
306 line += deliveryName(snapshot.delivery);
307 line += " trigger=";
308 line += snapshot.configured ? triggerName(snapshot.trigger) : "n/a";
309 line += " ack=";
310 line += snapshot.configured ? controllerAckName(snapshot.controllerAck) : "n/a";
311 line += " release=";
312 line += snapshot.configured ? lineReleaseName(snapshot.lineRelease) : "n/a";
313 line += " req=";
314 line += snapshot.requestedEnabled ? "on" : "off";
315 line += " masked=";
316 line += yesNo(snapshot.effectiveMasked);
317 line += " mask=";
318 appendMaskReasons(line, snapshot.maskReasons);
319 }
320
321 line += " gen=";
322 line.append(snapshot.dispatchGeneration);
323 line += " acked=";
324 line.append(snapshot.acknowledgedGeneration);
325 line += " count[irq=";
326 line.append(snapshot.interruptCount);
327 line += " spur=";
328 line.append(snapshot.spuriousCount);
329 line += " unhandled=";
330 line.append(snapshot.unhandledCount);
331 line += ']';
332 appendHardDispatchState(line, snapshot);
333 appendWorkerState(line, snapshot);
334 appendTimingState(line, snapshot);
335 appendWorkerDebugState(line, snapshot);
336}