The Pedigree Project 0.1
XhciTransfer.cc
1/* Copyright (c) 2026, Pedigree Developers. SPDX-License-Identifier: ISC */
2#include "pedigree/kernel/LockGuard.h"
3#include "pedigree/kernel/Log.h"
4#include "pedigree/kernel/panic.h"
5#include "pedigree/kernel/process/TerminationDeferral.h"
6#include "pedigree/kernel/processor/VirtualAddressSpace.h"
7#include "pedigree/kernel/utilities/utility.h"
8
9#include "Xhci.h"
10using namespace XhciHw;
11uintptr_t Xhci::createTransaction(UsbEndpoint description) {
13 if (!m_Submissions.tryAcquire(admission) || description.nAddress >= 128 ||
14 !description.nMaxPacketSize || description.nEndpoint > 15)
15 return ~uintptr_t{0};
16 LockGuard<Mutex> configuration(m_ConfigurationLock);
17 uint8_t slot;
18 {
19 LockGuard<Mutex> lock(m_Lock);
20 slot = m_Online ? findSlot(description) : 0;
21 }
22 if (!slot)
23 return ~uintptr_t{0};
24 Endpoint* endpoint = ensureEndpoint(slot, description);
25 if (!endpoint)
26 return ~uintptr_t{0};
27 auto* transaction = new Transaction;
28 if (!allocate(transaction->bounce, MaxTransfer / PageBytes, false)) {
29 delete transaction;
30 return ~uintptr_t{0};
31 }
32 for (size_t i = 0; i < MaxTransfer / PageBytes; ++i) {
33 size_t flags = 0;
35 static_cast<uint8_t*>(transaction->bounce.virtualAddress()) + i * PageBytes,
36 transaction->pages[i], flags);
37 }
38 LockGuard<Mutex> lock(m_Lock);
39 for (size_t i = 0; m_Online && i < MaxTransactions; ++i) {
40 if (m_Transactions[i])
41 continue;
42 transaction->description = description;
43 transaction->endpoint = endpoint;
44 transaction->id = i;
45 transaction->slot = slot;
46 transaction->dci = description.nEndpoint ? description.nEndpoint * 2 + description.nIn : 1;
47 transaction->generation = m_Deliveries.nextGeneration();
48 m_Transactions[i] = transaction;
49 return i;
50 }
51 delete transaction;
52 return ~uintptr_t{0};
53}
54void Xhci::addTransferToTransaction(uintptr_t id, bool, UsbPid pid, uintptr_t buffer,
55 size_t bytes) {
57 if (!m_Submissions.tryAcquire(admission))
58 return;
59 LockGuard<Mutex> lock(m_Lock);
60 if (id >= MaxTransactions || !m_Transactions[id])
61 return;
62 auto& transaction = *m_Transactions[id];
63 if (transaction.submitted || transaction.bad)
64 return;
65 if (pid == UsbPidSetup) {
66 if (transaction.dci != 1 || transaction.setupPresent || bytes != 8 || !buffer) {
67 transaction.bad = true;
68 return;
69 }
70 MemoryCopy(&transaction.setup, reinterpret_cast<void*>(buffer), 8);
71 transaction.setupPresent = true;
72 transaction.input = transaction.setup & 0x80U;
73 return;
74 }
75 if (pid != UsbPidIn && pid != UsbPidOut) {
76 transaction.bad = true;
77 return;
78 }
79 if (!bytes && transaction.setupPresent) {
80 transaction.statusPresent = true;
81 if (bool(pid == UsbPidIn) != (!transaction.input || !(transaction.setup >> 48)))
82 transaction.bad = true;
83 return;
84 }
85 if (!buffer || !bytes || bytes > MaxTransfer - transaction.bytes || transaction.statusPresent ||
86 (transaction.client && buffer != transaction.client + transaction.bytes)) {
87 transaction.bad = true;
88 return;
89 }
90 if (!transaction.bytes) {
91 transaction.client = buffer;
92 if (!transaction.setupPresent)
93 transaction.input = pid == UsbPidIn;
94 }
95 if (transaction.input != (pid == UsbPidIn)) {
96 transaction.bad = true;
97 return;
98 }
99 if (!transaction.input)
100 MemoryCopy(static_cast<uint8_t*>(transaction.bounce.virtualAddress()) + transaction.bytes,
101 reinterpret_cast<void*>(buffer), bytes);
102 transaction.bytes += bytes;
103}
104bool Xhci::submitLocked(Transaction& transaction) {
105 Trb trbs[20]{};
106 size_t count = 0;
107 const bool control = transaction.dci == 1;
108 if (!m_Online || findSlot(transaction.description) != transaction.slot ||
109 transaction.endpoint->needsReset ||
110 (control && (!transaction.setupPresent || !transaction.statusPresent ||
111 transaction.bytes != (transaction.setup >> 48))))
112 return false;
113 if (control) {
114 transaction.trbTypes[count] = 2;
115 transaction.trbOffsets[count] = transaction.trbLengths[count] = 0;
116 trbs[count++] = {
117 transaction.setup, 8,
118 (2U << 10) | (1U << 6) | (transaction.bytes ? (transaction.input ? 3U : 2U) << 16 : 0)};
119 }
120 for (size_t offset = 0; offset < transaction.bytes;) {
121 const size_t remaining = transaction.bytes - offset;
122 const size_t bytes = remaining < PageBytes ? remaining : PageBytes;
123 const bool last = bytes == remaining;
124 const uint8_t type = control && !offset ? 3 : 1;
125 transaction.trbTypes[count] = type;
126 transaction.trbOffsets[count] = offset;
127 transaction.trbLengths[count] = bytes;
128 const size_t packets = (remaining - bytes + transaction.description.nMaxPacketSize - 1) /
129 transaction.description.nMaxPacketSize;
130 trbs[count++] = {transaction.pages[offset / PageBytes],
131 static_cast<uint32_t>(bytes | ((packets < 31 ? packets : 31) << 17)),
132 (uint32_t{type} << 10) |
133 (!last ? Chain
134 : !control ? Ioc
135 : 0) |
136 (transaction.input ? Isp : 0) |
137 (type == 3 && transaction.input ? 1U << 16 : 0)};
138 offset += bytes;
139 }
140 if (control) {
141 transaction.trbTypes[count] = 4;
142 transaction.trbOffsets[count] = transaction.trbLengths[count] = 0;
143 trbs[count++] = {0, 0,
144 (4U << 10) | Ioc | (!transaction.input || !transaction.bytes ? 1U << 16 : 0)};
145 }
146 if (!count)
147 return false;
148 const size_t wraps = transaction.endpoint->ring.wraps();
149 if (!transaction.endpoint->ring.enqueue(trbs, count, transaction.trbAddresses))
150 return false;
151 transaction.trbCount = count;
152 transaction.submitted = true;
153 transaction.shortSeen = false;
154 transaction.captured = false;
155 transaction.actual = 0;
156 if (!wraps && transaction.endpoint->ring.wraps()) {
157#if PEDIGREE_USB_SMOKE_TESTS
158 NOTICE("XHCI-SMOKE: transfer-ring-wrap");
159#endif
160 NOTICE("xHCI: transfer ring wrapped slot " << transaction.slot << " endpoint "
161 << transaction.dci);
162 }
163 write(m_Doorbells + transaction.slot * 4, transaction.dci);
164 (void)read(m_Op + 4);
165 return true;
166}
167bool Xhci::accept(uintptr_t id, void (*callback)(uintptr_t, ssize_t), uintptr_t parameter,
168 UsbInterruptInHandle* handle) {
169 LockGuard<Mutex> lock(m_Lock);
170 if (id >= MaxTransactions || !m_Transactions[id])
171 return false;
172 auto& transaction = *m_Transactions[id];
173 if (transaction.submitted)
174 return false;
175 if (transaction.bad || transaction.endpoint->active || !m_Online || !m_Transfers.tryEnter()) {
176 freeTransactionLocked(id);
177 return false;
178 }
179 transaction.callback = callback;
180 transaction.parameter = parameter;
181 transaction.periodic = handle != nullptr;
182 transaction.endpoint->active = &transaction;
183 if (!submitLocked(transaction)) {
184 transaction.endpoint->active = nullptr;
185 m_Transfers.leave();
186 freeTransactionLocked(id);
187 return false;
188 }
189 // The event lock prevents completion until the callback owner is published.
190 if (handle) {
191 if (!publishInterruptInHandle(*handle, {id, transaction.generation}, callback, parameter))
192 panic("xHCI: interrupt subscription publication failed");
193 } else
194 transaction.completion.arm(callback, parameter, transaction.generation);
195 return true;
196}
197bool Xhci::doAsync(uintptr_t id, void (*callback)(uintptr_t, ssize_t), uintptr_t parameter) {
198 OperationBarrier::Lease admission;
199 if (!m_Submissions.tryAcquire(admission))
200 return false;
201 return accept(id, callback, parameter, nullptr);
202}
203bool Xhci::addInterruptInHandler(UsbEndpoint description, uintptr_t buffer, uint16_t bytes,
204 void (*callback)(uintptr_t, ssize_t), UsbInterruptInHandle& handle,
205 uintptr_t parameter) {
206 OperationBarrier::Lease admission;
207 if (!m_Submissions.tryAcquire(admission) || handle || !callback || !bytes ||
208 description.nTransferType != 3 || !description.nIn || bytes > description.nMaxPacketSize)
209 return false;
210 const uintptr_t transaction = createTransaction(description);
211 if (transaction == ~uintptr_t{0})
212 return false;
213 addTransferToTransaction(transaction, false, UsbPidIn, buffer, bytes);
214 return accept(transaction, callback, parameter, &handle);
215}
216bool Xhci::decodeCompletion(Transaction& transaction, size_t index, uint8_t code, size_t residual,
217 bool& complete, size_t& actual) {
218 if (index >= transaction.trbCount || (code != 1 && code != 13))
219 return false;
220 // ED=0 residuals describe the referenced TRB, not the entire transfer.
221 if (code == 1 && residual)
222 return false;
223 if (code == 13 && !transaction.shortSeen) {
224 if (!transaction.input || !transaction.trbLengths[index] ||
225 residual > transaction.trbLengths[index])
226 return false;
227 transaction.actual = transaction.trbOffsets[index] + transaction.trbLengths[index] - residual;
228 transaction.shortSeen = true;
229 }
230 actual = transaction.shortSeen ? transaction.actual : transaction.bytes;
231 if (actual > transaction.bytes)
232 return false;
233 // A short ends a bulk/interrupt TD immediately. Control must still finish Status.
234 complete = transaction.dci == 1 ? transaction.trbTypes[index] == 4
235 : code == 13 || index + 1 == transaction.trbCount;
236 return true;
237}
238bool Xhci::completionRegressions() {
239 Transaction transaction;
240 transaction.input = true;
241 transaction.bytes = 8192;
242 transaction.dci = 3;
243 transaction.trbCount = 2;
244 transaction.trbTypes[0] = transaction.trbTypes[1] = 1;
245 transaction.trbLengths[0] = transaction.trbLengths[1] = 4096;
246 transaction.trbOffsets[1] = 4096;
247 bool complete = false;
248 size_t actual = 0;
249 if (!decodeCompletion(transaction, 0, 13, 3072, complete, actual) || !complete || actual != 1024)
250 return false;
251 transaction.shortSeen = false;
252 transaction.dci = 1;
253 transaction.trbCount = 3;
254 transaction.trbTypes[0] = 3;
255 transaction.trbTypes[2] = 4;
256 if (!decodeCompletion(transaction, 0, 13, 3072, complete, actual) || complete || actual != 1024 ||
257 !decodeCompletion(transaction, 2, 1, 0, complete, actual) || !complete || actual != 1024)
258 return false;
259 transaction.shortSeen = false;
260 if (decodeCompletion(transaction, 0, 1, 1, complete, actual) ||
261 decodeCompletion(transaction, 0, 13, 4097, complete, actual))
262 return false;
263#if PEDIGREE_USB_SMOKE_TESTS
264 NOTICE("XHCI-SMOKE: completion-contracts");
265#endif
266 return true;
267}
268bool Xhci::transferEventLocked(const Trb& event) {
269 const uint8_t slot = event.control >> 24, dci = (event.control >> 16) & 31U;
270 if (!slot || slot > m_SlotCount || !dci || !m_Slots[slot].endpoints[dci]) {
271 failLocked();
272 return false;
273 }
274 auto* endpoint = m_Slots[slot].endpoints[dci];
275 Transaction* transaction = endpoint->active;
276 if (!transaction || !transaction->submitted || transaction->cancelling)
277 return false;
278 size_t index = 0;
279 while (index < transaction->trbCount && transaction->trbAddresses[index] != event.parameter)
280 ++index;
281 const uint8_t code = event.status >> 24;
282 if (index == transaction->trbCount && (code == 1 || code == 13)) {
283 // Some controllers also report a skipped IOC tail after the earlier short event.
284 for (size_t i = 0; i < endpoint->retiredCount; ++i)
285 if (endpoint->retiredAddresses[i] == event.parameter)
286 return false;
287 }
288 if (index == transaction->trbCount || (event.control & 4U)) {
289 failLocked();
290 return false;
291 }
292 const bool stale = transaction->periodic && findSlot(transaction->description) != slot;
293 if (code == 1 || code == 13) {
294 bool complete = false;
295 size_t actual = 0;
296 if (!decodeCompletion(*transaction, index, code, event.status & 0xffffffU, complete, actual)) {
297 failLocked();
298 return false;
299 }
300 if (!complete)
301 return false;
302 if (!stale && transaction->input && actual)
303 MemoryCopy(reinterpret_cast<void*>(transaction->client), transaction->bounce.virtualAddress(),
304 actual);
305 if (!stale)
306 finishLocked(*transaction, actual + (transaction->dci == 1 ? 8 : 0), true);
307 } else {
308 // Halted/stopped endpoints cannot retain access to the completed TD's DMA.
309 const uint32_t state = outputContext(slot, dci)[0] & 7U;
310 if (state != 2 && state != 3) {
311 failLocked();
312 return false;
313 }
314 endpoint->needsReset = true;
315 if (!stale)
316 finishLocked(*transaction, code == 6 ? -Stall : -TransactionError, true);
317 }
318 if (stale) {
319 endpoint->ring.retire(transaction->trbCount);
320 transaction->submitted = false;
321 transaction->captured = true;
322 }
323 return !stale;
324}
325void Xhci::enqueueDeliveryLocked(UsbHcd::CallbackDeliveryQueue::Record* record) {
327 batch.pushBack(record);
328 m_Deliveries.publish(batch);
329 m_ReadyDeliveries.pushBack(record);
330 m_DeliveryReady.release();
331}
332void Xhci::finishLocked(Transaction& transaction, ssize_t result, bool natural) {
333 if (transaction.captured)
334 return;
335 transaction.captured = true;
336 transaction.endpoint->retiredCount = transaction.trbCount;
337 MemoryCopy(transaction.endpoint->retiredAddresses, transaction.trbAddresses,
338 transaction.trbCount * sizeof(transaction.trbAddresses[0]));
339 transaction.endpoint->ring.retire(transaction.trbCount);
340 transaction.submitted = false;
341 if (!transaction.periodic)
342 transaction.endpoint->active = nullptr;
343 if (transaction.periodic) {
344 auto* delivery = new Delivery{this, transaction.id, transaction.generation, result >= 0};
345 enqueueDeliveryLocked(
346 m_Deliveries.create({transaction.id, m_Deliveries.nextGeneration(), transaction.generation},
347 transaction.callback, transaction.parameter, result, afterDelivery,
348 delivery, destroyDelivery, delivery));
349 } else {
351 const bool claimed = natural ? transaction.completion.captureNatural(result) &&
352 transaction.completion.claimCaptured(claim)
353 : transaction.completion.claimForTeardown(result, claim);
354 if (!claimed)
355 return;
356 auto* delivery = new Delivery{this, transaction.id, transaction.generation, false};
357 enqueueDeliveryLocked(m_Deliveries.create({transaction.id, claim.generation}, claim.callback,
358 claim.parameter, claim.result, afterDelivery,
359 delivery, destroyDelivery, delivery));
360 }
361}
362void Xhci::afterDelivery(void* context) {
363 auto* delivery = static_cast<Delivery*>(context);
364 auto* controller = delivery->controller;
365 LockGuard<Mutex> lock(controller->m_Lock);
366 const uintptr_t id = delivery->transaction;
367 Transaction* transaction = controller->m_Transactions[id];
368 if (!transaction || transaction->generation != delivery->generation)
369 return;
370 if (!transaction->periodic) {
371 controller->freeTransactionLocked(id);
372 controller->m_Transfers.leave();
373 } else if (delivery->rearm && !transaction->cancelling && controller->m_Online &&
374 controller->findSlot(transaction->description) == transaction->slot) {
375 if (!controller->submitLocked(*transaction))
376 transaction->endpoint->needsReset = true;
377 }
378}
379void Xhci::destroyDelivery(void* context) {
380 delete static_cast<Delivery*>(context);
381}
382void Xhci::freeTransactionLocked(uintptr_t id) {
383 delete m_Transactions[id];
384 m_Transactions[id] = nullptr;
385}
386int Xhci::deliveryWorker(void* context) {
387 TerminationDeferral lifetime;
388 auto* controller = static_cast<Xhci*>(context);
389 for (;;) {
390 if (!controller->m_DeliveryReady.acquireForCompletion())
391 continue;
393 {
394 LockGuard<Mutex> lock(controller->m_Lock);
395 if (controller->m_ReadyDeliveries.count())
396 record = controller->m_ReadyDeliveries.popFront();
397 else if (controller->m_DeliveryStopping)
398 return 0;
399 }
400 if (record)
401 controller->m_Deliveries.deliver(record);
402 }
403}
Definition List.h:61
MUST_USE_RESULT bool tryAcquire(Lease &lease)
MUST_USE_RESULT bool tryEnter()
void release(size_t n=1)
Definition Semaphore.cc:546
void publish(List< Record * > &records)
MUST_USE_RESULT bool publishInterruptInHandle(UsbInterruptInHandle &handle, const UsbInterruptInToken &token, void(*callback)(uintptr_t, ssize_t), uintptr_t parameter)
Definition UsbHub.cc:206
virtual bool getMapping(void *virtualAddress, physical_uintptr_t &physicalAddress, size_t &flags)=0
static EXPORTED_PUBLIC VirtualAddressSpace & getKernelAddressSpace()
Definition Xhci.h:16
void addTransferToTransaction(uintptr_t transaction, bool toggle, UsbPid pid, uintptr_t buffer, size_t bytes) override
Adds a new transfer to an existent transaction.
bool doAsync(uintptr_t transaction, void(*callback)(uintptr_t, ssize_t)=nullptr, uintptr_t parameter=0) override
uintptr_t createTransaction(UsbEndpoint endpoint) override
Creates a new transaction with the given endpoint data.
bool addInterruptInHandler(UsbEndpoint endpoint, uintptr_t buffer, uint16_t bytes, void(*callback)(uintptr_t, ssize_t), UsbInterruptInHandle &handle, uintptr_t parameter=0) override
Adds an owned recurring interrupt-IN transaction.
void EXPORTED_PUBLIC panic(const char *msg) NORETURN
Definition panic.cc:117
void pushBack(const T &value)
Definition List.h:216