The Pedigree Project 0.1
IrqDiagnosticSnapshotStore.h
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#ifndef PEDIGREE_KERNEL_MACHINE_IRQDIAGNOSTICSNAPSHOTSTORE_H
9#define PEDIGREE_KERNEL_MACHINE_IRQDIAGNOSTICSNAPSHOTSTORE_H
10#include "pedigree/kernel/machine/IrqManager.h"
11
12#include <config.h>
13
14static_assert(__atomic_always_lock_free(sizeof(size_t), nullptr),
15 "IRQ diagnostic publication words must be lock-free");
16
25template <size_t LineCount>
27 public:
29 : m_Banks(),
30 m_Publications(),
31 m_BankClaims(),
32 m_WriterAdmissions(),
33 m_MissedPublications(),
34 m_Dirty() {
35 for (size_t line = 0; line < LineCount; ++line) {
36 for (size_t bank = 0; bank < BankCount; ++bank) {
37 m_Banks[line][bank].line = static_cast<uint8_t>(line);
38 }
39 m_Publications[line] = static_cast<size_t>(1) << BankBits;
40 }
41 }
42
44 IrqLineDiagnosticSnapshot* beginPublication(size_t line, size_t& targetBank) {
45 if (line >= LineCount) {
46 return nullptr;
47 }
48
49 size_t expectedAdmission = 0;
50 if (!__atomic_compare_exchange_n(&m_WriterAdmissions[line], &expectedAdmission,
51 static_cast<size_t>(1), false, __ATOMIC_ACQUIRE,
52 __ATOMIC_RELAXED)) {
53 missed(line);
54 return nullptr;
55 }
56
57 const size_t publication = __atomic_load_n(&m_Publications[line], __ATOMIC_ACQUIRE);
58 const size_t activeBank = publication & BankMask;
59 for (size_t offset = 1; offset < BankCount; ++offset) {
60 const size_t candidate = (activeBank + offset) % BankCount;
61 size_t expectedClaim = 0;
62 if (__atomic_compare_exchange_n(&m_BankClaims[line][candidate], &expectedClaim, WriterClaim,
63 false, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) {
64 targetBank = candidate;
65 return &m_Banks[line][candidate];
66 }
67 }
68
69 __atomic_store_n(&m_WriterAdmissions[line], static_cast<size_t>(0), __ATOMIC_RELEASE);
70 missed(line);
71 return nullptr;
72 }
73
75 void finishPublication(size_t line, size_t targetBank) {
76 if (line >= LineCount || targetBank >= BankCount) {
77 return;
78 }
79
80 const size_t current = __atomic_load_n(&m_Publications[line], __ATOMIC_RELAXED);
81 size_t generation = (current >> BankBits) + 1;
82 if (!generation) {
83 ++generation;
84 }
85
86 // Readers cannot select an inactive bank. Drop the private claim
87 // before the release-store which makes this bank active.
88 __atomic_store_n(&m_BankClaims[line][targetBank], static_cast<size_t>(0), __ATOMIC_RELEASE);
89 __atomic_store_n(&m_Publications[line], (generation << BankBits) | targetBank,
90 __ATOMIC_RELEASE);
91 __atomic_store_n(&m_WriterAdmissions[line], static_cast<size_t>(0), __ATOMIC_RELEASE);
92 }
93
95 bool snapshot(size_t line, IrqLineDiagnosticSnapshot& out) const {
96 if (line >= LineCount) {
97 return false;
98 }
99
100 for (size_t attempt = 0; attempt < SnapshotAttempts; ++attempt) {
101 const size_t publication = __atomic_load_n(&m_Publications[line], __ATOMIC_ACQUIRE);
102 const size_t bank = publication & BankMask;
103 if (bank >= BankCount) {
104 return false;
105 }
106
107 size_t claim = __atomic_load_n(&m_BankClaims[line][bank], __ATOMIC_ACQUIRE);
108 if (claim & WriterClaim) {
109 continue;
110 }
111 if (!__atomic_compare_exchange_n(&m_BankClaims[line][bank], &claim, claim + 1, false,
112 __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) {
113 continue;
114 }
115
116 if (__atomic_load_n(&m_Publications[line], __ATOMIC_ACQUIRE) != publication) {
117 __atomic_fetch_sub(&m_BankClaims[line][bank], static_cast<size_t>(1), __ATOMIC_RELEASE);
118 continue;
119 }
120
121 out = m_Banks[line][bank];
122 out.snapshotGeneration = publication >> BankBits;
123 __atomic_fetch_sub(&m_BankClaims[line][bank], static_cast<size_t>(1), __ATOMIC_RELEASE);
124 return true;
125 }
126 return false;
127 }
128
129#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
132 bool claimPublishedBankForTest(size_t line, size_t& bank) const {
133 if (line >= LineCount) {
134 return false;
135 }
136
137 const size_t publication = __atomic_load_n(&m_Publications[line], __ATOMIC_ACQUIRE);
138 bank = publication & BankMask;
139 if (bank >= BankCount) {
140 return false;
141 }
142
143 size_t claim = __atomic_load_n(&m_BankClaims[line][bank], __ATOMIC_ACQUIRE);
144 if ((claim & WriterClaim) ||
145 !__atomic_compare_exchange_n(&m_BankClaims[line][bank], &claim, claim + 1, false,
146 __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) {
147 return false;
148 }
149
150 if (__atomic_load_n(&m_Publications[line], __ATOMIC_ACQUIRE) != publication) {
151 __atomic_fetch_sub(&m_BankClaims[line][bank], static_cast<size_t>(1), __ATOMIC_RELEASE);
152 return false;
153 }
154 return true;
155 }
156
157 void releasePublishedBankForTest(size_t line, size_t bank) const {
158 if (line < LineCount && bank < BankCount) {
159 __atomic_fetch_sub(&m_BankClaims[line][bank], static_cast<size_t>(1), __ATOMIC_RELEASE);
160 }
161 }
162#endif
163
164 size_t missedPublications(size_t line) const {
165 return line < LineCount ? __atomic_load_n(&m_MissedPublications[line], __ATOMIC_RELAXED) : 0;
166 }
167
169 void recordMissedPublication(size_t line) {
170 if (line < LineCount) {
171 missed(line);
172 }
173 }
174
176 bool consumeDirty(size_t line) {
177 return line < LineCount &&
178 __atomic_exchange_n(&m_Dirty[line], static_cast<size_t>(0), __ATOMIC_ACQ_REL) != 0;
179 }
180
181 private:
182 static constexpr size_t BankCount = 3;
183 static constexpr size_t BankBits = 2;
184 static constexpr size_t BankMask = (1U << BankBits) - 1;
185 static constexpr size_t SnapshotAttempts = 4;
186 static constexpr size_t WriterClaim = static_cast<size_t>(1) << ((sizeof(size_t) * 8) - 1);
187
188 void missed(size_t line) {
189 __atomic_store_n(&m_Dirty[line], static_cast<size_t>(1), __ATOMIC_RELEASE);
190 __atomic_add_fetch(&m_MissedPublications[line], static_cast<size_t>(1), __ATOMIC_RELAXED);
191 }
192
193 IrqLineDiagnosticSnapshot m_Banks[LineCount][BankCount];
194 size_t m_Publications[LineCount];
195 mutable size_t m_BankClaims[LineCount][BankCount];
196 size_t m_WriterAdmissions[LineCount];
197 size_t m_MissedPublications[LineCount];
198 size_t m_Dirty[LineCount];
199};
200
201#endif
IrqLineDiagnosticSnapshot * beginPublication(size_t line, size_t &targetBank)
void finishPublication(size_t line, size_t targetBank)
bool snapshot(size_t line, IrqLineDiagnosticSnapshot &out) const