The Pedigree Project 0.1
PciExpress.h
1/* Copyright (c) 2026, Pedigree Developers. SPDX-License-Identifier: ISC */
2#ifndef PEDIGREE_PCI_EXPRESS_H
3#define PEDIGREE_PCI_EXPRESS_H
4
5#include "pedigree/kernel/processor/types.h"
6
7namespace PciExpress {
8constexpr uint8_t CapabilityId = 0x10;
9
10enum class FindResult { Found, Absent, Unavailable, Malformed };
11enum class FlrResult { Complete, Unavailable, Unsupported, Busy, Unverified };
12
13enum class Type : uint8_t {
14 Endpoint = 0,
15 LegacyEndpoint = 1,
16 RootPort = 4,
17 UpstreamPort = 5,
18 DownstreamPort = 6,
19 PcieToPciBridge = 7,
20 PciToPcieBridge = 8,
21 IntegratedEndpoint = 9,
22 EventCollector = 10,
23};
24
25struct State {
26 uint8_t offset = 0;
27 uint8_t version = 0;
28 Type type = Type::Endpoint;
29 uint8_t interruptMessage = 0;
30 uint32_t deviceCapabilities = 0;
31 uint16_t deviceControl = 0;
32 uint16_t deviceStatus = 0;
33 uint32_t linkCapabilities = 0;
34 uint16_t linkControl = 0;
35 uint16_t linkStatus = 0;
36 uint32_t slotCapabilities = 0;
37 uint16_t slotControl = 0;
38 uint16_t slotStatus = 0;
39 bool hasLink = false;
40 bool slotImplemented = false;
41
42 bool supportsFlr() const {
43 return (type == Type::Endpoint || type == Type::LegacyEndpoint ||
44 type == Type::IntegratedEndpoint) &&
45 (deviceCapabilities & (1U << 28));
46 }
47 uint8_t linkSpeed() const {
48 return linkStatus & 0xfU;
49 }
50 uint8_t linkWidth() const {
51 return (linkStatus >> 4) & 0x3fU;
52 }
53 bool linkTraining() const {
54 return linkStatus & (1U << 11);
55 }
56};
57
58inline bool validType(uint8_t type) {
59 return type <= 1 || (type >= 4 && type <= 10);
60}
61
62template <class Config>
63FindResult find(Config& config, State& result) {
64 uint32_t identity = 0;
65 uint16_t status = 0;
66 uint8_t headerType = 0;
67 if (!config.read32(0, identity) || !config.read16(6, status) || !config.read8(0x0e, headerType)) {
68 return FindResult::Unavailable;
69 }
70 if (!(identity & 0xffffU) || (identity & 0xffffU) == 0xffffU || (headerType & 0x7fU) > 1) {
71 return FindResult::Unavailable;
72 }
73 if (!(status & 0x10U)) {
74 return FindResult::Absent;
75 }
76
77 uint8_t offset = 0;
78 if (!config.read8(0x34, offset)) {
79 return FindResult::Unavailable;
80 }
81 uint64_t occupied = 0;
82 bool found = false;
83 State candidate;
84 while (offset) {
85 if (offset < 0x40 || (offset & 3U)) {
86 return FindResult::Malformed;
87 }
88 uint8_t id = 0, next = 0;
89 if (!config.read8(offset, id) || !config.read8(offset + 1, next)) {
90 return FindResult::Unavailable;
91 }
92 if (!id || id == 0xffU || (next && (next < 0x40 || (next & 3U)))) {
93 return FindResult::Malformed;
94 }
95
96 unsigned bytes = 4;
97 if (id == 1) {
98 bytes = 8;
99 } else if (id == 5) {
100 uint16_t control = 0;
101 if (!config.read16(offset + 2, control)) {
102 return FindResult::Unavailable;
103 }
104 bytes = (control & 0x80U) ? 14 : 10;
105 if (control & 0x100U) {
106 bytes += 10;
107 }
108 } else if (id == 0x11) {
109 bytes = 12;
110 } else if (id == CapabilityId) {
111 if (found) {
112 return FindResult::Malformed;
113 }
114 uint16_t flags = 0;
115 if (!config.read16(offset + 2, flags)) {
116 return FindResult::Unavailable;
117 }
118 const uint8_t version = flags & 0xfU;
119 const uint8_t type = (flags >> 4) & 0xfU;
120 if ((version != 1 && version != 2) || !validType(type)) {
121 return FindResult::Malformed;
122 }
123 candidate.offset = offset;
124 candidate.version = version;
125 candidate.type = static_cast<Type>(type);
126 candidate.interruptMessage = (flags >> 9) & 0x1fU;
127 candidate.hasLink = type != 9 && type != 10;
128 candidate.slotImplemented = (flags & (1U << 8)) && (candidate.type == Type::RootPort ||
129 candidate.type == Type::DownstreamPort);
130 bytes = candidate.hasLink ? 0x14 : 0x0c;
131 if (candidate.type == Type::RootPort) {
132 bytes = 0x24;
133 } else if (candidate.slotImplemented) {
134 bytes = 0x1c;
135 }
136 }
137 if (unsigned(offset) + bytes > 0x100) {
138 return FindResult::Malformed;
139 }
140 for (unsigned slot = offset / 4; slot < (offset + bytes + 3) / 4; ++slot) {
141 const uint64_t bit = uint64_t{1} << slot;
142 if (occupied & bit) {
143 return FindResult::Malformed;
144 }
145 occupied |= bit;
146 }
147 if (id == CapabilityId) {
148 if (!config.read32(offset + 4, candidate.deviceCapabilities) ||
149 !config.read16(offset + 8, candidate.deviceControl) ||
150 !config.read16(offset + 0x0a, candidate.deviceStatus) ||
151 (candidate.hasLink && (!config.read32(offset + 0x0c, candidate.linkCapabilities) ||
152 !config.read16(offset + 0x10, candidate.linkControl) ||
153 !config.read16(offset + 0x12, candidate.linkStatus))) ||
154 (candidate.slotImplemented &&
155 (!config.read32(offset + 0x14, candidate.slotCapabilities) ||
156 !config.read16(offset + 0x18, candidate.slotControl) ||
157 !config.read16(offset + 0x1a, candidate.slotStatus)))) {
158 return FindResult::Unavailable;
159 }
160 found = true;
161 }
162 offset = next;
163 }
164 if (!found) {
165 return FindResult::Absent;
166 }
167 result = candidate;
168 return FindResult::Found;
169}
170
175template <class Config, class Wait>
176FlrResult resetFunction(Config& config, Wait wait) {
177 State state;
178 const FindResult found = find(config, state);
179 if (found != FindResult::Found) {
180 return found == FindResult::Absent ? FlrResult::Unsupported : FlrResult::Unavailable;
181 }
182 if (!state.supportsFlr()) {
183 return FlrResult::Unsupported;
184 }
185 uint32_t identity = 0;
186 uint16_t command = 0;
187 if (!config.read32(0, identity) || !config.read16(4, command)) {
188 return FlrResult::Unavailable;
189 }
190 if ((command & 4U) || (state.deviceStatus & (1U << 5))) {
191 return FlrResult::Busy;
192 }
193 // Device Control and the W1C Device Status share a DWORD; never write both.
194 const bool wrote = config.write16(state.offset + 8, state.deviceControl | 0x8000U);
195 // The callback must actually wait at least this long before returning.
196 // Even a reported write failure may have initiated reset on the device.
197 const bool waited = wait(100);
198 if (!wrote || !waited) {
199 return FlrResult::Unverified;
200 }
201 uint32_t actual = 0;
202 uint16_t afterCommand = 0;
203 if (!config.read32(0, actual) || actual != identity || !config.read16(4, afterCommand) ||
204 (afterCommand & 4U)) {
205 return FlrResult::Unverified;
206 }
207 return FlrResult::Complete;
208}
209} // namespace PciExpress
210
211#endif