The Pedigree Project 0.1
EhciHandoff.h
1/* Copyright (c) 2026, Pedigree Developers. SPDX-License-Identifier: ISC */
2#ifndef EHCI_HANDOFF_H
3#define EHCI_HANDOFF_H
4
5#include <stddef.h>
6#include <stdint.h>
7
8#include "EhciLegacy.h"
9
10namespace EhciHandoff {
11
13enum class AcquireResult {
14 Acquired,
15 AlreadyUnowned,
16 InvalidCapability,
17 AccessFailure,
18 OwnershipTimeout,
19 OwnershipMismatch,
20 SmiDisableFailure,
21 AlreadyAttempted,
22};
23
25enum class ReleaseResult {
26 NotOwned,
27 Released,
28 FirmwareConfirmed,
29 AccessFailure,
30 NotQuiesced,
31};
32
47class State {
48 public:
49 static constexpr uint32_t BiosOwned = 1U << 16;
50 static constexpr uint32_t OsOwned = 1U << 24;
51 static constexpr uint8_t LegacyCapabilityId = 1;
52 static constexpr size_t DefaultPollLimit = 1000;
53
54 State()
55 : m_Offset(0),
56 m_SavedSmiEnables(0),
57 m_WasBiosOwned(false),
58 m_RequestWritten(false),
59 m_Acquired(false),
60 m_Disabled(false),
61 m_SmiMutationAttempted(false),
62 m_SnapshotValid(false) {}
63
71 template <typename Config>
72 AcquireResult acquire(uint16_t offset, Config& config, size_t pollLimit = DefaultPollLimit) {
73 if (m_SnapshotValid)
74 return AcquireResult::AlreadyAttempted;
75 reset();
76 if (!validOffset(offset) || !pollLimit)
77 return AcquireResult::InvalidCapability;
78
79 uint32_t legsup = 0;
80 uint32_t control = 0;
81 if (!config.read32(offset, legsup) || !config.read32(controlOffset(offset), control))
82 return AcquireResult::AccessFailure;
83 if ((legsup & 0xffU) != LegacyCapabilityId)
84 return AcquireResult::InvalidCapability;
85
86 m_Offset = offset;
87 m_SavedSmiEnables = control & EhciLegacy::SmiEnables;
88 m_WasBiosOwned = (legsup & BiosOwned) != 0;
89 m_SnapshotValid = true;
90
91 if (m_WasBiosOwned) {
92 // A posted config write may have reached hardware even when its access
93 // wrapper reports failure, so retain the request obligation before the
94 // write is attempted.
95 m_RequestWritten = true;
96 if (!config.write8(static_cast<uint16_t>(offset + 3), 1))
97 return AcquireResult::AccessFailure;
98
99 bool sawBiosClear = false;
100 for (size_t poll = 0; poll < pollLimit; ++poll) {
101 if (!config.read32(offset, legsup))
102 return AcquireResult::AccessFailure;
103 if (!(legsup & BiosOwned)) {
104 sawBiosClear = true;
105 if (!(legsup & OsOwned))
106 return AcquireResult::OwnershipMismatch;
107 break;
108 }
109 if (poll + 1 < pollLimit)
110 config.delay();
111 }
112 if (!sawBiosClear)
113 return AcquireResult::OwnershipTimeout;
114 }
115
116 m_SmiMutationAttempted = true;
117 if (!EhciLegacy::disableSmis(
118 offset,
119 [&config](uint16_t registerOffset, uint32_t& value) {
120 return config.read32(registerOffset, value);
121 },
122 [&config](uint16_t registerOffset, uint32_t value) {
123 return config.write32(registerOffset, value);
124 })) {
125 return AcquireResult::SmiDisableFailure;
126 }
127
128 m_Disabled = true;
129 m_Acquired = m_WasBiosOwned;
130 return m_WasBiosOwned ? AcquireResult::Acquired : AcquireResult::AlreadyUnowned;
131 }
132
142 template <typename Config>
143 ReleaseResult withdrawRequest(Config& config, size_t pollLimit = DefaultPollLimit) {
144 if (!m_SnapshotValid || m_Acquired || !m_WasBiosOwned ||
145 (!m_RequestWritten && !m_SmiMutationAttempted))
146 return ReleaseResult::NotOwned;
147 return releaseInternal(config, true, pollLimit);
148 }
149
157 template <typename Config>
158 ReleaseResult release(Config& config, bool callerQuiescedOrNeverTouched,
159 size_t pollLimit = DefaultPollLimit) {
160 if (!callerQuiescedOrNeverTouched)
161 return ReleaseResult::NotQuiesced;
162 return releaseInternal(config, false, pollLimit);
163 }
164
165 uint16_t offset() const {
166 return m_Offset;
167 }
168
169 uint32_t savedSmiEnables() const {
170 return m_SavedSmiEnables;
171 }
172
173 bool wasBiosOwned() const {
174 return m_WasBiosOwned;
175 }
176
177 bool requested() const {
178 return m_RequestWritten;
179 }
180
181 bool acquired() const {
182 return m_Acquired;
183 }
184
185 bool disabled() const {
186 return m_Disabled;
187 }
188
189 private:
190 static constexpr uint16_t ControlOffset = 4;
191
192 static bool validOffset(uint16_t offset) {
193 return offset >= 0x40 && offset <= 0xf8 && !(offset & 3);
194 }
195
196 static uint16_t controlOffset(uint16_t offset) {
197 return static_cast<uint16_t>(offset + ControlOffset);
198 }
199
200 void reset() {
201 m_Offset = 0;
202 m_SavedSmiEnables = 0;
203 m_WasBiosOwned = false;
204 m_RequestWritten = false;
205 m_Acquired = false;
206 m_Disabled = false;
207 m_SmiMutationAttempted = false;
208 m_SnapshotValid = false;
209 }
210
211 template <typename Config>
212 bool restoreSmis(Config& config) {
213 if (!m_SnapshotValid ||
214 !config.write32(controlOffset(m_Offset), EhciLegacy::AcknowledgeStatus | m_SavedSmiEnables))
215 return false;
216
217 uint32_t control = 0;
218 if (!config.read32(controlOffset(m_Offset), control))
219 return false;
220 return (control & EhciLegacy::SmiEnables) == m_SavedSmiEnables;
221 }
222
223 template <typename Config>
224 ReleaseResult releaseInternal(Config& config, bool allowUnacquired, size_t pollLimit) {
225 if (!m_SnapshotValid || !pollLimit)
226 return ReleaseResult::NotOwned;
227
228 if (!m_WasBiosOwned) {
229 // The BIOS-unowned shortcut never establishes an OS ownership lease;
230 // do not synthesize a return-to-firmware exchange on teardown.
231 reset();
232 return ReleaseResult::NotOwned;
233 }
234
235 if (!m_RequestWritten)
236 return ReleaseResult::NotOwned;
237
238 if (!m_Acquired && !allowUnacquired)
239 return ReleaseResult::NotOwned;
240
241 // A failed handoff can receive a late firmware acknowledgement after the
242 // bounded acquire poll. Firmware may clear its own SMI enables as part of
243 // that acknowledgement, so inspect live ownership before deciding whether
244 // the saved enables must be restored. If firmware still owns the device,
245 // leave its controls untouched. Once this helper has attempted an SMI
246 // mutation, restoration is required regardless of the live semaphore.
247 if (m_SmiMutationAttempted) {
248 if (!restoreSmis(config))
249 return ReleaseResult::AccessFailure;
250 } else {
251 uint32_t liveLegsup = 0;
252 if (!config.read32(m_Offset, liveLegsup))
253 return ReleaseResult::AccessFailure;
254 if (!(liveLegsup & BiosOwned) && !restoreSmis(config))
255 return ReleaseResult::AccessFailure;
256 }
257
258 if (!config.write8(static_cast<uint16_t>(m_Offset + 3), 0))
259 return ReleaseResult::AccessFailure;
260
261 uint32_t legsup = 0;
262 if (!config.read32(m_Offset, legsup))
263 return ReleaseResult::AccessFailure;
264 if (legsup & OsOwned)
265 return ReleaseResult::AccessFailure;
266
267 bool firmwareConfirmed = (legsup & BiosOwned) != 0;
268 for (size_t poll = 0; !firmwareConfirmed && poll < pollLimit; ++poll) {
269 if (!config.read32(m_Offset, legsup))
270 return ReleaseResult::AccessFailure;
271 firmwareConfirmed = (legsup & BiosOwned) != 0;
272 if (!firmwareConfirmed && poll + 1 < pollLimit)
273 config.delay();
274 }
275
276 reset();
277 return firmwareConfirmed ? ReleaseResult::FirmwareConfirmed : ReleaseResult::Released;
278 }
279
280 uint16_t m_Offset;
281 uint32_t m_SavedSmiEnables;
282 bool m_WasBiosOwned;
283 bool m_RequestWritten;
284 bool m_Acquired;
285 bool m_Disabled;
286 bool m_SmiMutationAttempted;
287 bool m_SnapshotValid;
288};
289
290} // namespace EhciHandoff
291
292#endif
ReleaseResult withdrawRequest(Config &config, size_t pollLimit=DefaultPollLimit)
AcquireResult acquire(uint16_t offset, Config &config, size_t pollLimit=DefaultPollLimit)
Definition EhciHandoff.h:72
ReleaseResult release(Config &config, bool callerQuiescedOrNeverTouched, size_t pollLimit=DefaultPollLimit)