The Pedigree Project 0.1
XhciEvent.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/time/Time.h"
5#include "pedigree/kernel/utilities/utility.h"
6
7#include "Xhci.h"
8using namespace XhciHw;
9bool Xhci::collectEventsLocked(bool fromInterrupt) {
10 const bool credit = fromInterrupt && m_PolledInterrupt;
11 if (fromInterrupt)
12 m_PolledInterrupt = false;
13 if (!m_Online)
14 return credit;
15 const uint32_t status = read(m_Op + 4);
16 const uint32_t iman = read(m_Runtime + 0x20);
17 const bool cause = (status & 0x1cU) || (iman & 1U);
18 if (status & ((1U << 12) | 4U)) {
19 failLocked();
20 return true;
21 }
22 if (status & 0x18U)
23 write(m_Op + 4, status & 0x18U);
24 if (iman & 1U)
25 write(m_Runtime + 0x20, 3);
26 auto* entries = static_cast<volatile Trb*>(m_Events.virtualAddress());
27 bool consumed = false;
28 for (size_t count = 0; count < RingEntries; ++count) {
29 volatile auto& source = entries[m_EventHead];
30 const uint32_t control = source.control;
31 if (bool(control & Cycle) != m_EventCycle)
32 break;
33 FENCE();
34 Trb event{source.parameter, source.status, control};
35 if (++m_EventHead == RingEntries) {
36 m_EventHead = 0;
37 m_EventCycle = !m_EventCycle;
38 if (!m_EventWraps++) {
39 NOTICE("xHCI: event ring wrapped");
40#if PEDIGREE_USB_SMOKE_TESTS
41 NOTICE("XHCI-SMOKE: event-ring-wrap");
42#endif
43 }
44 }
45 consumed = true;
46 const uint32_t type = (control >> 10) & 63U;
47 if (type == 33) {
48 if (!m_CommandAddress || event.parameter != m_CommandAddress) {
49 failLocked();
50 break;
51 }
52 m_Commands.retire(1);
53 m_CommandAddress = 0;
54 m_CommandCode = event.status >> 24;
55 m_CommandSlot = control >> 24;
56 m_CommandDone.release();
57 } else if (type == 32) {
58 const bool valid = transferEventLocked(event);
59#if PEDIGREE_USB_SMOKE_TESTS
60 if (valid && fromInterrupt && m_Online && !m_ObservedInterruptCompletion) {
61 m_ObservedInterruptCompletion = true;
62 NOTICE("XHCI-SMOKE: interrupt-completion");
63 }
64#else
65 (void)valid;
66#endif
67 } else if (type == 34) {
68 const size_t port = (event.parameter >> 24) & 255U;
69 if (!port || port > m_PortCount) {
70 failLocked();
71 break;
72 }
73 const size_t reg = m_Op + 0x400 + (port - 1) * 16;
74 const uint32_t portStatus = read(reg);
75 m_Ports[port - 1].resetChanges |= portStatus & ((1U << 19) | (1U << 21));
76 if (portStatus & (1U << 17))
77 m_Ports[port - 1].changePending = true;
78 write(reg, (portStatus & 0x0e00c200U) | (portStatus & PortChanges));
79 (void)read(reg);
80 if ((portStatus & (1U << 17)) && !m_PortsClosing)
81 notifyPortLocked(port - 1);
82 } else if (type == 37) {
83 failLocked();
84 break;
85 }
86 if (!m_Online)
87 break;
88 }
89 if (m_Online && (consumed || cause)) {
90 if (!fromInterrupt)
91 m_PolledInterrupt = true;
92 FENCE();
93 write64(m_Runtime + 0x38, m_Events.physicalAddress() + m_EventHead * sizeof(Trb) + 8);
94 (void)read(m_Runtime + 0x38);
95 }
96 return cause || consumed || credit;
97}
99 LockGuard<Mutex> lock(m_Lock);
100 if (m_Stopping)
102 return collectEventsLocked(true) ? IrqDisposition::Handled : IrqDisposition::NotHandled;
103}
104void Xhci::notifyPortLocked(size_t port) {
105 if (m_PortsClosing)
106 return;
107 m_Ports[port].changePending = true;
109 return;
110 const auto observation = m_PortChanges[port].observe();
111 if (!UsbHcd::PortChangeRequest::canAcknowledge(observation.result)) {
112 ERROR("xHCI: root-port publication failed");
113 failLocked();
114 return;
115 }
116 m_PortChanges[port].acknowledge(observation.generation);
117 m_Ports[port].changePending = false;
118}
119size_t Xhci::currentRootPortGeneration(size_t port) const {
120 return port < m_PortCount ? m_PortChanges[port].observedGeneration() : 0;
121}
123 LockGuard<Mutex> lock(m_Lock);
124 if (port < m_PortCount && !m_PortsClosing)
125 notifyPortLocked(port);
126}
127bool Xhci::portReset(uint8_t port, bool errorResponse) {
128 if (port >= m_PortCount || !m_Online)
129 return false;
130 const size_t reg = m_Op + 0x400 + port * 16;
131 const bool warm = errorResponse && m_Ports[port].major == 3;
132 const uint32_t completion = warm ? 1U << 19 : 1U << 21;
133 {
134 LockGuard<Mutex> lock(m_Lock);
135 const uint32_t status = read(reg);
136 if (!(status & 1U))
137 return false;
138 m_Ports[port].resetChanges = 0;
139 write(reg, (status & 0x0e00c200U) | (status & completion) | (warm ? 1U << 31 : 1U << 4));
140 (void)read(reg);
141 }
142 const auto deadline = Time::getTicks() + Time::Multiplier::Second;
143 do {
144 {
145 LockGuard<Mutex> lock(m_Lock);
146 const uint32_t ready = read(reg);
147 if (!m_Online || !(ready & 1U))
148 return false;
149 // WPR reads as zero. Both reset forms drive PR until completion.
150 if (!(ready & (1U << 4)) && ((ready | m_Ports[port].resetChanges) & completion)) {
151 const uint8_t speed = (ready >> 10) & 15U;
152 // CSC belongs to the event path, including disconnects during reset.
153 write(reg, (ready & 0x0e00c200U) | (ready & ((1U << 19) | (1U << 21))));
154 (void)read(reg);
155 return (ready & 3U) == 3U && m_Ports[port].validSpeed[speed];
156 }
157 }
158 Time::delay(Time::Multiplier::Millisecond);
159 } while (Time::getTicks() < deadline);
160 return false;
161}
162uint64_t Xhci::executeRequest(uint64_t port, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t,
163 uint64_t, uint64_t generation) {
164 if (port >= m_PortCount)
165 return 0;
166 UsbHcd::PortChangeRequest::Completion completion(m_PortChanges[port], generation);
167 if (!completion || m_PortsClosing || !m_Online)
168 return 0;
169 uint32_t status = read(m_Op + 0x400 + port * 16);
170 if (!(status & 1U))
171 deviceDisconnected(port);
172 else {
173 // USB 2 port speed may be undefined until its first completed reset.
174 if (!m_Ports[port].validSpeed[(status >> 10) & 15U]) {
175 if (!portReset(port))
176 return 0;
177 status = read(m_Op + 0x400 + port * 16);
178 }
179 const uint8_t speed = (status >> 10) & 15U;
180 if (m_Ports[port].validSpeed[speed])
181 deviceConnected(port, m_Ports[port].speeds[speed]);
182 else
183 WARNING("xHCI: unsupported root-port speed ID " << speed);
184 }
185 return 0;
186}
187void Xhci::cancelRequest(const Request& request) {
188 if (request.p1 < m_PortCount)
189 m_PortChanges[request.p1].cancel(request.p8);
190}
void * virtualAddress() const
physical_uintptr_t physicalAddress() const
void release(size_t n=1)
Definition Semaphore.cc:546
bool deviceConnected(uint8_t nPort, UsbSpeed speed)
Called when a device is connected to a port on the hub.
Definition UsbHub.cc:387
void deviceDisconnected(uint8_t nPort)
Called when a device is disconnected from a port on the hub.
Definition UsbHub.cc:545
MUST_USE_RESULT bool deferConnectionChangeIfSuppressed(size_t port)
Definition UsbHub.cc:302
IrqDisposition irq(irq_id_t) override
Definition XhciEvent.cc:98
void replaySuppressedConnectionChange(size_t port) override
Definition XhciEvent.cc:122
bool portReset(uint8_t port, bool errorResponse=false) override
Gets a UsbDevice from a given vendor:product pair.
Definition XhciEvent.cc:127
uint64_t executeRequest(uint64_t port, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t generation) override
Definition XhciEvent.cc:162
void cancelRequest(const Request &request) override
Definition XhciEvent.cc:187
size_t currentRootPortGeneration(size_t port) const override
Definition XhciEvent.cc:119
IrqDisposition
Definition IrqHandler.h:31