The Pedigree Project 0.1
PciFunctionState.h
1/* Copyright (c) 2026, Pedigree Developers. SPDX-License-Identifier: ISC */
2#ifndef PEDIGREE_PCI_FUNCTION_STATE_H
3#define PEDIGREE_PCI_FUNCTION_STATE_H
4#include "pedigree/kernel/compiler.h"
5#include "pedigree/kernel/processor/types.h"
6
7namespace PciFunctionState {
8struct State {
9 uint16_t command = 0;
10 uint32_t bars[6] = {};
11 uint8_t barCount = 6;
12 uint8_t interruptLine = 0, interruptPin = 0;
13 uint8_t pm = 0, msi = 0, msix = 0;
14};
15
16struct MsixTable {
17 uint8_t bar = 0, pbaBar = 0;
18 uint32_t offset = 0, pbaOffset = 0;
19 uint16_t vectors = 0;
20};
21
22template <class Config>
23bool writeVerified16(Config& config, uint16_t offset, uint16_t value) {
24 uint16_t actual = 0;
25 return config.write16(offset, value) && config.read16(offset, actual) && actual == value;
26}
27
28template <class Config>
29bool writeVerified32(Config& config, uint16_t offset, uint32_t value) {
30 uint32_t actual = 0;
31 return config.write32(offset, value) && config.read32(offset, actual) && actual == value;
32}
33
34// virtualFunction is supplied by the PF owner; raw VF Vendor/Device IDs may
35// read FFFF:FFFF. Header and capability checks still establish configuration.
36template <class Config>
37bool inspect(Config& config, State& result, bool requireLegacyInterrupt = true,
38 bool virtualFunction = false) {
39 State state;
40 uint32_t identity = 0;
41 uint16_t status = 0;
42 uint8_t header = 0;
43 if (!config.read32(0, identity) ||
44 (((identity & 0xffff) == 0xffff || !(identity & 0xffff)) &&
45 !(virtualFunction && identity == 0xffffffffU)) ||
46 !config.read8(14, header) || (header & 0x7f) > (virtualFunction ? 0 : 1) ||
47 !config.read16(4, state.command) || !config.read16(6, status) ||
48 !config.read8(0x3c, state.interruptLine) || !config.read8(0x3d, state.interruptPin)) {
49 return false;
50 }
51 state.barCount = (header & 0x7f) == 1 ? 2 : 6;
52 if (state.interruptPin > 4 || (requireLegacyInterrupt && !state.interruptPin)) {
53 return false;
54 }
55 // PC drivers use the programmable inputs of the legacy PIC. A valid line
56 // is a routing claim, not proof of delivery on either platform.
57 if (requireLegacyInterrupt &&
58 (state.interruptLine >= 16 || !(0xdef8U & (1U << state.interruptLine)))) {
59 return false;
60 }
61 for (unsigned i = 0; i < state.barCount; ++i) {
62 if (!config.read32(0x10 + 4 * i, state.bars[i])) {
63 return false;
64 }
65 }
66 uint8_t cap = 0;
67 if ((status & 0x10) && !config.read8(0x34, cap))
68 return false;
69 uint64_t occupied = 0;
70 while (cap) {
71 if (cap < 0x40 || (cap & 3))
72 return false;
73 uint8_t type = 0, next = 0;
74 uint16_t control = 0;
75 if (!config.read8(cap, type) || !config.read8(cap + 1, next) ||
76 !config.read16(cap + 2, control))
77 return false;
78 unsigned bytes = 4;
79 if (type == 1) {
80 if (state.pm)
81 return false;
82 state.pm = cap;
83 bytes = 8;
84 } else if (type == 5) {
85 if (state.msi)
86 return false;
87 state.msi = cap;
88 bytes = (control & 0x80) ? 14 : 10;
89 if (control & 0x100)
90 bytes += 10;
91 } else if (type == 0x11) {
92 if (state.msix)
93 return false;
94 state.msix = cap;
95 bytes = 12;
96 }
97 if (cap + bytes > 256)
98 return false;
99 for (unsigned slot = cap / 4; slot < (cap + bytes + 3) / 4; ++slot) {
100 if (occupied & (1ULL << slot))
101 return false;
102 occupied |= 1ULL << slot;
103 }
104 if (type == 1) {
105 uint16_t pmcsr = 0;
106 if (!config.read16(cap + 4, pmcsr) || (pmcsr & 3))
107 return false;
108 }
109 cap = next;
110 }
111 if (!requireLegacyInterrupt && !state.interruptPin && !state.msi && !state.msix)
112 return false;
113 result = state;
114 return true;
115}
116
117template <class Config>
118bool msixTable(Config& config, const State& state, MsixTable& result) {
119 if (!state.msix || (state.barCount != 2 && state.barCount != 6)) {
120 return false;
121 }
122 uint16_t control = 0;
123 uint32_t table = 0, pba = 0;
124 if (!config.read16(state.msix + 2, control) || !config.read32(state.msix + 4, table) ||
125 !config.read32(state.msix + 8, pba) || (table & 7U) >= state.barCount ||
126 (pba & 7U) >= state.barCount) {
127 return false;
128 }
129 const uint16_t vectors = (control & 0x7ffU) + 1;
130 const uint8_t bar = table & 7U, pbaBar = pba & 7U;
131 const uint64_t tableEnd = uint64_t(table & ~7U) + uint64_t(vectors) * 16;
132 const uint64_t pbaEnd = uint64_t(pba & ~7U) + uint64_t((vectors + 63) / 64) * 8;
133 if (tableEnd > 0x100000000ULL || pbaEnd > 0x100000000ULL ||
134 (bar == pbaBar && (table & ~7U) < pbaEnd && (pba & ~7U) < tableEnd))
135 return false;
136 result = {bar, pbaBar, table & ~7U, pba & ~7U, vectors};
137 return true;
138}
139
140template <class Config>
141bool enableMsi(Config& config, const State& state, uint64_t address, uint16_t data) {
142 if (!state.msi || (address & 3U))
143 return false;
144 uint16_t control = 0, command = 0, oldData = 0, msixControl = 0;
145 uint32_t oldLow = 0, oldHigh = 0, mask = 0;
146 if (!config.read16(state.msi + 2, control) || !config.read16(4, command) ||
147 !config.read32(state.msi + 4, oldLow) || (control & 1U) ||
148 (state.msix && (!config.read16(state.msix + 2, msixControl) || (msixControl & 0x8000U))))
149 return false;
150 const bool wide = control & 0x80U;
151 if (!wide && (address >> 32))
152 return false;
153 const uint16_t dataOffset = state.msi + (wide ? 12 : 8);
154 const uint16_t maskOffset = state.msi + (wide ? 16 : 12);
155 if ((wide && !config.read32(state.msi + 8, oldHigh)) || !config.read16(dataOffset, oldData) ||
156 ((control & 0x100U) && !config.read32(maskOffset, mask)))
157 return false;
158
159 const bool programmed =
160 writeVerified32(config, state.msi + 4, uint32_t(address)) &&
161 (!wide || writeVerified32(config, state.msi + 8, uint32_t(address >> 32))) &&
162 writeVerified16(config, dataOffset, data) &&
163 (!(control & 0x100U) || writeVerified32(config, maskOffset, mask & ~1U)) &&
164 writeVerified16(config, 4, command | 0x400U) &&
165 writeVerified16(config, state.msi + 2, (control & ~0x70U) | 1U);
166 if (programmed)
167 return true;
168
169 // Keep the source disabled while restoring the registers after a partial write.
170 config.write16(state.msi + 2, control & ~1U);
171 config.write32(state.msi + 4, oldLow);
172 if (wide)
173 config.write32(state.msi + 8, oldHigh);
174 config.write16(dataOffset, oldData);
175 if (control & 0x100U)
176 config.write32(maskOffset, mask);
177 config.write16(4, command);
178 return false;
179}
180
181template <class Config>
182bool disableMsi(Config& config, const State& state) {
183 if (!state.msi)
184 return false;
185 uint16_t control = 0;
186 if (!config.read16(state.msi + 2, control))
187 return false;
188 if (!(control & 1U))
189 return true;
190 bool masked = true;
191 if (control & 0x100U) {
192 uint32_t mask = 0;
193 const uint16_t offset = state.msi + ((control & 0x80U) ? 16 : 12);
194 masked = config.read32(offset, mask) && writeVerified32(config, offset, mask | 1U);
195 }
196 const bool disabled = writeVerified16(config, state.msi + 2, control & ~1U);
197 return masked && disabled;
198}
199
200template <class Config, class Io>
201bool enableMsixVectors(Config& config, const State& state, Io& table, uint32_t offset,
202 uint64_t address, const uint32_t* data, size_t count,
203 const uint64_t* addresses = nullptr) {
204 constexpr size_t MaxVectors = 64;
205 if (!state.msix || !data || !count || count > MaxVectors ||
206 uint64_t(offset) + count * 16 > table.size())
207 return false;
208 for (size_t i = 0; i < count; ++i) {
209 if ((addresses ? addresses[i] : address) & 3U) {
210 return false;
211 }
212 }
213 uint16_t control = 0, command = 0, msiControl = 0;
214 if (!config.read16(state.msix + 2, control) || !config.read16(4, command) ||
215 (control & 0x8000U) || count > (control & 0x7ffU) + 1 ||
216 (state.msi && (!config.read16(state.msi + 2, msiControl) || (msiControl & 1U))))
217 return false;
218 uint32_t previous[MaxVectors][4];
219 for (size_t i = 0; i < count; ++i) {
220 const uint32_t entry = offset + i * 16;
221 for (size_t word = 0; word < 4; ++word)
222 previous[i][word] = table.read32(entry + word * 4);
223 }
224 if (!writeVerified16(config, state.msix + 2, control | 0x4000U)) {
225 config.write16(state.msix + 2, control);
226 return false;
227 }
228 bool programmed = true;
229 for (size_t i = 0; i < count; ++i) {
230 const uint32_t entry = offset + i * 16;
231 table.write32(previous[i][3] | 1U, entry + 12);
232 const uint64_t destination = addresses ? addresses[i] : address;
233 table.write32(uint32_t(destination), entry);
234 table.write32(uint32_t(destination >> 32), entry + 4);
235 table.write32(data[i], entry + 8);
236 }
237 FENCE();
238 for (size_t i = 0; i < count; ++i) {
239 const uint32_t entry = offset + i * 16;
240 const uint64_t destination = addresses ? addresses[i] : address;
241 programmed = programmed && (table.read32(entry + 12) & 1U) &&
242 table.read32(entry) == uint32_t(destination) &&
243 table.read32(entry + 4) == uint32_t(destination >> 32) &&
244 table.read32(entry + 8) == data[i];
245 }
246 if (programmed)
247 programmed = writeVerified16(config, 4, command | 0x400U) &&
248 writeVerified16(config, state.msix + 2, control | 0xc000U);
249 if (programmed) {
250 for (size_t i = 0; i < count; ++i)
251 table.write32(previous[i][3] & ~1U, offset + i * 16 + 12);
252 FENCE();
253 for (size_t i = 0; i < count; ++i)
254 programmed = programmed && !(table.read32(offset + i * 16 + 12) & 1U);
255 if (programmed && writeVerified16(config, state.msix + 2, (control | 0x8000U) & ~0x4000U))
256 return true;
257 }
258
259 // Do not rewrite table entries until the function has stopped emitting them.
260 if (!writeVerified16(config, state.msix + 2, (control | 0x4000U) & ~0x8000U))
261 return false;
262 for (size_t i = 0; i < count; ++i) {
263 const uint32_t entry = offset + i * 16;
264 table.write32(previous[i][3] | 1U, entry + 12);
265 for (size_t word = 0; word < 3; ++word)
266 table.write32(previous[i][word], entry + word * 4);
267 table.write32(previous[i][3], entry + 12);
268 }
269 FENCE();
270 config.write16(state.msix + 2, control);
271 config.write16(4, command);
272 return false;
273}
274
275template <class Config, class Io>
276bool enableMsix(Config& config, const State& state, Io& table, uint32_t offset, uint64_t address,
277 uint32_t data) {
278 return enableMsixVectors(config, state, table, offset, address, &data, 1);
279}
280
281template <class Config, class Io>
282bool setMsixVectorMask(Config& config, const State& state, Io& table, uint32_t offset, size_t index,
283 bool masked) {
284 uint16_t control = 0;
285 if (!state.msix || !config.read16(state.msix + 2, control) || index >= (control & 0x7ffU) + 1 ||
286 uint64_t(offset) + (index + 1) * 16 > table.size() || (!masked && !(control & 0x8000U)))
287 return false;
288 const uint32_t entryControl = table.read32(offset + index * 16 + 12);
289 const uint32_t desired = masked ? entryControl | 1U : entryControl & ~1U;
290 table.write32(desired, offset + index * 16 + 12);
291 FENCE();
292 return table.read32(offset + index * 16 + 12) == desired;
293}
294
295template <class Config, class Io>
296bool disableMsix(Config& config, const State& state, Io& table, uint32_t offset) {
297 if (!state.msix || uint64_t(offset) + 16 > table.size())
298 return false;
299 uint16_t control = 0;
300 if (!config.read16(state.msix + 2, control))
301 return false;
302 if (!writeVerified16(config, state.msix + 2, control | 0x4000U))
303 return false;
304 const uint32_t entryControl = table.read32(offset + 12);
305 table.write32(entryControl | 1U, offset + 12);
306 FENCE();
307 return (table.read32(offset + 12) & 1U) &&
308 writeVerified16(config, state.msix + 2, (control | 0x4000U) & ~0x8000U);
309}
310
311template <class Config>
312bool disableMessageInterrupts(Config& config, const State& state) {
313 const uint8_t capabilities[] = {state.msi, state.msix};
314 for (uint8_t cap : capabilities) {
315 if (!cap)
316 continue;
317 uint16_t control = 0, actual = 0;
318 if (!config.read16(cap + 2, control))
319 return false;
320 const uint16_t desired = cap == state.msi ? control & ~1U : (control | 0x4000U) & ~0x8000U;
321 if (!config.write16(cap + 2, desired) || !config.read16(cap + 2, actual) || actual != desired)
322 return false;
323 }
324 return true;
325}
326
327template <class Config>
328bool resourcesUnchanged(Config& config, const State& state, bool virtualFunction = false) {
329 if (state.barCount != 2 && state.barCount != 6) {
330 return false;
331 }
332 uint32_t identity = 0;
333 uint8_t header = 0;
334 if (!config.read32(0, identity) ||
335 (((identity & 0xffff) == 0xffff || !(identity & 0xffff)) &&
336 !(virtualFunction && identity == 0xffffffffU)) ||
337 !config.read8(14, header) || (header & 0x7f) > (virtualFunction ? 0 : 1) ||
338 state.barCount != ((header & 0x7f) == 1 ? 2 : 6)) {
339 return false;
340 }
341 for (unsigned i = 0; i < state.barCount; ++i) {
342 uint32_t bar = 0;
343 if (!config.read32(0x10 + 4 * i, bar) || bar != state.bars[i])
344 return false;
345 }
346 uint8_t line = 0, pin = 0;
347 return config.read8(0x3c, line) && config.read8(0x3d, pin) && line == state.interruptLine &&
348 pin == state.interruptPin;
349}
350} // namespace PciFunctionState
351#endif