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"
10using namespace XhciHw;
13 if (!m_Submissions.
tryAcquire(admission) || description.nAddress >= 128 ||
14 !description.nMaxPacketSize || description.nEndpoint > 15)
20 slot = m_Online ? findSlot(description) : 0;
24 Endpoint* endpoint = ensureEndpoint(slot, description);
28 if (!allocate(transaction->bounce, MaxTransfer / PageBytes,
false)) {
32 for (
size_t i = 0; i < MaxTransfer / PageBytes; ++i) {
35 static_cast<uint8_t*
>(transaction->bounce.virtualAddress()) + i * PageBytes,
36 transaction->pages[i], flags);
39 for (
size_t i = 0; m_Online && i < MaxTransactions; ++i) {
40 if (m_Transactions[i])
42 transaction->description = description;
43 transaction->endpoint = endpoint;
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;
60 if (
id >= MaxTransactions || !m_Transactions[
id])
62 auto& transaction = *m_Transactions[id];
63 if (transaction.submitted || transaction.bad)
65 if (pid == UsbPidSetup) {
66 if (transaction.dci != 1 || transaction.setupPresent || bytes != 8 || !buffer) {
67 transaction.bad =
true;
70 MemoryCopy(&transaction.setup,
reinterpret_cast<void*
>(buffer), 8);
71 transaction.setupPresent =
true;
72 transaction.input = transaction.setup & 0x80U;
75 if (pid != UsbPidIn && pid != UsbPidOut) {
76 transaction.bad =
true;
79 if (!bytes && transaction.setupPresent) {
80 transaction.statusPresent =
true;
81 if (
bool(pid == UsbPidIn) != (!transaction.input || !(transaction.setup >> 48)))
82 transaction.bad =
true;
85 if (!buffer || !bytes || bytes > MaxTransfer - transaction.bytes || transaction.statusPresent ||
86 (transaction.client && buffer != transaction.client + transaction.bytes)) {
87 transaction.bad =
true;
90 if (!transaction.bytes) {
91 transaction.client = buffer;
92 if (!transaction.setupPresent)
93 transaction.input = pid == UsbPidIn;
95 if (transaction.input != (pid == UsbPidIn)) {
96 transaction.bad =
true;
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;
104bool Xhci::submitLocked(Transaction& transaction) {
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))))
114 transaction.trbTypes[count] = 2;
115 transaction.trbOffsets[count] = transaction.trbLengths[count] = 0;
117 transaction.setup, 8,
118 (2U << 10) | (1U << 6) | (transaction.bytes ? (transaction.input ? 3U : 2U) << 16 : 0)};
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) |
136 (transaction.input ? Isp : 0) |
137 (type == 3 && transaction.input ? 1U << 16 : 0)};
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)};
148 const size_t wraps = transaction.endpoint->ring.wraps();
149 if (!transaction.endpoint->ring.enqueue(trbs, count, transaction.trbAddresses))
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");
160 NOTICE(
"xHCI: transfer ring wrapped slot " << transaction.slot <<
" endpoint "
163 write(m_Doorbells + transaction.slot * 4, transaction.dci);
164 (void)read(m_Op + 4);
167bool Xhci::accept(uintptr_t
id,
void (*callback)(uintptr_t, ssize_t), uintptr_t parameter,
170 if (
id >= MaxTransactions || !m_Transactions[
id])
172 auto& transaction = *m_Transactions[id];
173 if (transaction.submitted)
175 if (transaction.bad || transaction.endpoint->active || !m_Online || !m_Transfers.
tryEnter()) {
176 freeTransactionLocked(
id);
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;
186 freeTransactionLocked(
id);
192 panic(
"xHCI: interrupt subscription publication failed");
194 transaction.completion.arm(callback, parameter, transaction.generation);
197bool Xhci::doAsync(uintptr_t
id,
void (*callback)(uintptr_t, ssize_t), uintptr_t parameter) {
201 return accept(
id, callback, parameter,
nullptr);
205 uintptr_t parameter) {
207 if (!m_Submissions.
tryAcquire(admission) || handle || !callback || !bytes ||
208 description.nTransferType != 3 || !description.nIn || bytes > description.nMaxPacketSize)
211 if (transaction == ~uintptr_t{0})
214 return accept(transaction, callback, parameter, &handle);
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))
221 if (code == 1 && residual)
223 if (code == 13 && !transaction.shortSeen) {
224 if (!transaction.input || !transaction.trbLengths[index] ||
225 residual > transaction.trbLengths[index])
227 transaction.actual = transaction.trbOffsets[index] + transaction.trbLengths[index] - residual;
228 transaction.shortSeen =
true;
230 actual = transaction.shortSeen ? transaction.actual : transaction.bytes;
231 if (actual > transaction.bytes)
234 complete = transaction.dci == 1 ? transaction.trbTypes[index] == 4
235 : code == 13 || index + 1 == transaction.trbCount;
238bool Xhci::completionRegressions() {
239 Transaction transaction;
240 transaction.input =
true;
241 transaction.bytes = 8192;
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;
249 if (!decodeCompletion(transaction, 0, 13, 3072, complete, actual) || !complete || actual != 1024)
251 transaction.shortSeen =
false;
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)
259 transaction.shortSeen =
false;
260 if (decodeCompletion(transaction, 0, 1, 1, complete, actual) ||
261 decodeCompletion(transaction, 0, 13, 4097, complete, actual))
263#if PEDIGREE_USB_SMOKE_TESTS
264 NOTICE(
"XHCI-SMOKE: completion-contracts");
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]) {
274 auto* endpoint = m_Slots[slot].endpoints[dci];
275 Transaction* transaction = endpoint->active;
276 if (!transaction || !transaction->submitted || transaction->cancelling)
279 while (index < transaction->trbCount && transaction->trbAddresses[index] != event.parameter)
281 const uint8_t code =
event.status >> 24;
282 if (index == transaction->trbCount && (code == 1 || code == 13)) {
284 for (
size_t i = 0; i < endpoint->retiredCount; ++i)
285 if (endpoint->retiredAddresses[i] == event.parameter)
288 if (index == transaction->trbCount || (event.control & 4U)) {
292 const bool stale = transaction->periodic && findSlot(transaction->description) != slot;
293 if (code == 1 || code == 13) {
294 bool complete =
false;
296 if (!decodeCompletion(*transaction, index, code, event.status & 0xffffffU, complete, actual)) {
302 if (!stale && transaction->input && actual)
303 MemoryCopy(
reinterpret_cast<void*
>(transaction->client), transaction->bounce.virtualAddress(),
306 finishLocked(*transaction, actual + (transaction->dci == 1 ? 8 : 0), true);
309 const uint32_t state = outputContext(slot, dci)[0] & 7U;
310 if (state != 2 && state != 3) {
314 endpoint->needsReset =
true;
316 finishLocked(*transaction, code == 6 ? -Stall : -TransactionError, true);
319 endpoint->ring.retire(transaction->trbCount);
320 transaction->submitted =
false;
321 transaction->captured =
true;
332void Xhci::finishLocked(Transaction& transaction, ssize_t result,
bool natural) {
333 if (transaction.captured)
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));
351 const bool claimed = natural ? transaction.completion.captureNatural(result) &&
352 transaction.completion.claimCaptured(claim)
353 : transaction.completion.claimForTeardown(result, claim);
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));
362void Xhci::afterDelivery(
void* context) {
363 auto* delivery =
static_cast<Delivery*
>(context);
364 auto* controller = delivery->controller;
366 const uintptr_t
id = delivery->transaction;
367 Transaction* transaction = controller->m_Transactions[id];
368 if (!transaction || transaction->generation != delivery->generation)
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;
379void Xhci::destroyDelivery(
void* context) {
380 delete static_cast<Delivery*
>(context);
382void Xhci::freeTransactionLocked(uintptr_t
id) {
383 delete m_Transactions[id];
384 m_Transactions[id] =
nullptr;
386int Xhci::deliveryWorker(
void* context) {
388 auto* controller =
static_cast<Xhci*
>(context);
390 if (!controller->m_DeliveryReady.acquireForCompletion())
395 if (controller->m_ReadyDeliveries.count())
396 record = controller->m_ReadyDeliveries.popFront();
397 else if (controller->m_DeliveryStopping)
401 controller->m_Deliveries.deliver(record);
MUST_USE_RESULT bool tryAcquire(Lease &lease)
MUST_USE_RESULT bool tryEnter()
void publish(List< Record * > &records)
MUST_USE_RESULT bool publishInterruptInHandle(UsbInterruptInHandle &handle, const UsbInterruptInToken &token, void(*callback)(uintptr_t, ssize_t), uintptr_t parameter)
virtual bool getMapping(void *virtualAddress, physical_uintptr_t &physicalAddress, size_t &flags)=0
static EXPORTED_PUBLIC VirtualAddressSpace & getKernelAddressSpace()
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
void pushBack(const T &value)