The Pedigree Project 0.1
UsbHubDevice.cc
1/*
2 * Copyright (c) 2008-2014, Pedigree Developers
3 *
4 * Please see the CONTRIB file in the root of the source tree for a full
5 * list of contributors.
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20#include "UsbHubDevice.h"
21#include "pedigree/kernel/Log.h"
22#include "pedigree/kernel/machine/Machine.h"
23#include "pedigree/kernel/panic.h"
24#include "pedigree/kernel/time/Time.h"
25
26#include "modules/system/usb/UsbDevice.h"
27#include "modules/system/usb/UsbHub.h"
28
29namespace {
30constexpr size_t PortResetPollLimit = 100;
31constexpr uint8_t HubDescriptorType = 0x29;
32constexpr size_t PortDebouncePollLimit = 10;
33} // namespace
34
35UsbHubDevice::UsbHubDevice(UsbDevice* dev)
36 : UsbDevice(dev),
37 UsbHub(),
38 RequestQueue(MakeConstantString("USB hub")),
39 m_nPorts(0),
40 m_PowerGoodDelay(50),
41 m_StatusBuffer(nullptr),
42 m_StatusBytes(0),
43 m_StatusInterrupt(),
44 m_StatusChange() {
45 attachToUpstreamHub(m_pHub, {m_nRootPort, m_nRootPortGeneration});
46}
47
48UsbHubDevice::~UsbHubDevice() {
49 stopHotplug();
50 Machine::setShutdownDetail("USB hub: disconnecting children");
52 delete[] m_StatusBuffer;
53}
54
59
61 stopHotplug();
62}
63
65 stopHotplug();
66}
67
68void UsbHubDevice::stopHotplug() {
69 Machine::setShutdownDetail("USB hub: cancelling status interrupt");
70 if (!m_StatusInterrupt.reset()) {
71 panic("USB hub destroyed from a running interrupt callback");
72 }
73 Machine::setShutdownDetail("USB hub: stopping port-change notifications");
74 m_StatusChange.stopAfterQuiesce();
75 Machine::setShutdownDetail("USB hub: joining hotplug worker");
77 Machine::setShutdownDetail("USB hub: hotplug stopped");
78}
79
81 StartupActivity startup(this);
82 if (!startup) {
83 return;
84 }
85 uint8_t len = getDescriptorLength(HubDescriptorType, 0, UsbRequestType::Class);
86 void* pDesc = 0;
87 if (len >= sizeof(HubDescriptor::Descriptor)) {
88 pDesc = getDescriptor(HubDescriptorType, 0, len, UsbRequestType::Class);
89 if (!pDesc) {
90 return;
91 }
92 } else {
93 return;
94 }
95
96 HubDescriptor pDescriptor(pDesc);
97 DEBUG_LOG("USB: HUB: Found a hub with "
98 << Dec << pDescriptor.nPorts << Hex
99 << " ports and hubCharacteristics = " << pDescriptor.hubCharacteristics);
100 m_nPorts = pDescriptor.nPorts;
101 if (!m_nPorts || !m_pInterface) {
102 startup.failed();
103 return;
104 }
105 const size_t powerGoodDelay = static_cast<size_t>(pDescriptor.powerGoodDelay) * 2;
106 if (powerGoodDelay > m_PowerGoodDelay) {
107 m_PowerGoodDelay = powerGoodDelay;
108 }
109 for (size_t i = 0; i < m_nPorts; i++) {
110 // Grab this port's status
111 uint32_t portStatus = 0;
112 if (!getPortStatus(i, portStatus)) {
113 WARNING("USB: HUB: couldn't read port " << Dec << i << Hex);
114 startup.failed();
115 continue;
116 }
117
118 // Is power on?
119 if (!(portStatus & (1 << 8))) {
120 DEBUG_LOG("USB: HUB: Powering up port " << Dec << i << Hex << " [status = " << portStatus
121 << "]...");
122
123 // Power it on
124 if (!setPortFeature(i, PortPower)) {
125 WARNING("USB: HUB: couldn't power port " << Dec << i << Hex);
126 startup.failed();
127 continue;
128 }
129
130 // Delay while the power goes on
131 if (!Time::delay(m_PowerGoodDelay * Time::Multiplier::Millisecond)) {
132 startup.failed();
133 continue;
134 }
135
136 // Done.
137 if (!getPortStatus(i, portStatus)) {
138 startup.failed();
139 continue;
140 }
141
142 // If port power never went on, skip this port
143 if (!(portStatus & (1 << 8))) {
144 DEBUG_LOG("USB: HUB: Port " << Dec << i << Hex << " couldn't be powered up.");
145 startup.failed();
146 continue;
147 }
148
149 DEBUG_LOG("USB: HUB: Powered up port " << Dec << i << Hex << " [status = " << portStatus
150 << "]...");
151 }
152
153 if (!clearPortChanges(i, portStatus)) {
154 startup.failed();
155 continue;
156 }
157 if ((portStatus & (1 << PortConnection)) && !connectPort(i)) {
158 startup.failed();
159 }
160 }
161
162 Endpoint* statusEndpoint = nullptr;
163 for (size_t i = 0; i < m_pInterface->endpointList.count(); ++i) {
164 Endpoint* endpoint = m_pInterface->endpointList[i];
165 if (endpoint->bIn && endpoint->nTransferType == Endpoint::Interrupt) {
166 statusEndpoint = endpoint;
167 break;
168 }
169 }
170 m_StatusBytes = (m_nPorts + 8) / 8;
171 if (!statusEndpoint || statusEndpoint->nMaxPacketSize < m_StatusBytes) {
172 ERROR("USB: HUB: no usable status-change endpoint");
173 startup.failed();
174 return;
175 }
176 m_StatusBuffer = new uint8_t[m_StatusBytes]();
177 if (!m_StatusChange.configure(*this, 0)) {
178 startup.failed();
179 return;
180 }
182 if (getLifecycleState() != RequestQueue::LifecycleState::Accepting ||
183 !UsbDevice::addInterruptInHandler(statusEndpoint, reinterpret_cast<uintptr_t>(m_StatusBuffer),
184 m_StatusBytes, statusChanged, m_StatusInterrupt,
185 reinterpret_cast<uintptr_t>(this))) {
186 ERROR("USB: HUB: status-change submission failed");
187 startup.failed();
188 stopHotplug();
189 return;
190 }
191
192 m_UsbState = HasDriver;
193 NOTICE("USB: HUB: hotplug ready, address " << Dec << m_nAddress << ", ports " << m_nPorts << Hex);
194}
195
196void UsbHubDevice::statusChanged(uintptr_t parameter, ssize_t result) {
197 auto* hub = reinterpret_cast<UsbHubDevice*>(parameter);
198 if (result <= 0 || static_cast<size_t>(result) > hub->m_StatusBytes) {
199 return;
200 }
201 bool changed = false;
202 for (size_t i = 0; i < static_cast<size_t>(result); ++i) {
203 changed = changed || hub->m_StatusBuffer[i];
204 }
205 if (!changed) {
206 return;
207 }
208
209 // Control requests must run outside HCD callback delivery, where they can
210 // wait for completions. Port-change latches retain the bitmap's meaning.
211 const auto observation = hub->m_StatusChange.observe();
212 if (UsbHcd::PortChangeRequest::canAcknowledge(observation.result)) {
213 hub->m_StatusChange.acknowledge(observation.generation);
214 }
215}
216
217uint64_t UsbHubDevice::executeRequest(uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t,
218 uint64_t, uint64_t generation) {
219 UsbHcd::PortChangeRequest::Completion completion(m_StatusChange, generation);
220 if (!completion) {
221 return 0;
222 }
223
224 uint32_t hubStatus = 0;
225 if (controlRequest(static_cast<uint8_t>(UsbRequestDirection::In) | HubRequest,
226 UsbRequest::GetStatus, 0, 0, sizeof(hubStatus),
227 reinterpret_cast<uintptr_t>(&hubStatus))) {
228 for (size_t feature = HubLocalPower; feature <= HubOverCurrent; ++feature) {
229 if (hubStatus & (1U << (feature + 16))) {
230 if (!controlRequest(HubRequest, UsbRequest::ClearFeature, feature, 0)) {
231 WARNING("USB: HUB: couldn't clear hub change " << Dec << feature << Hex);
232 }
233 }
234 }
235 }
236 for (size_t port = 0; port < m_nPorts; ++port) {
237 if (getLifecycleState() != RequestQueue::LifecycleState::Accepting) {
238 break;
239 }
240 portChanged(port);
241 }
242 return 0;
243}
244
246 m_StatusChange.cancel(request.p8);
247}
248
249bool UsbHubDevice::clearPortChanges(size_t port, uint32_t status) {
250 bool cleared = true;
251 for (size_t feature = CPortConnection; feature <= CPortReset; ++feature) {
252 if (status & (1U << feature)) {
253 if (!clearPortFeature(port, static_cast<PortFeatureSelectors>(feature))) {
254 cleared = false;
255 }
256 }
257 }
258 return cleared;
259}
260
261bool UsbHubDevice::debouncePort(size_t port, uint32_t& status) {
262 for (size_t poll = 0; poll < PortDebouncePollLimit; ++poll) {
263 const auto state = getLifecycleState();
264 if (state == RequestQueue::LifecycleState::Stopping ||
265 state == RequestQueue::LifecycleState::Destroyed) {
266 return false;
267 }
268 if (!clearPortFeature(port, CPortConnection) ||
269 !Time::delay(100 * Time::Multiplier::Millisecond) || !getPortStatus(port, status)) {
270 return false;
271 }
272 if (!(status & (1 << PortConnection))) {
273 return false;
274 }
275 if (!(status & (1U << CPortConnection))) {
276 return true;
277 }
278 }
279 WARNING("USB: HUB: connection on port " << Dec << port << Hex << " did not settle");
280 return false;
281}
282
283bool UsbHubDevice::connectPort(size_t port) {
284 uint32_t status = 0;
285 if (!debouncePort(port, status)) {
286 return false;
287 }
288 return deviceConnected(port, FullSpeed);
289}
290
291bool UsbHubDevice::portSpeed(uint8_t port, UsbSpeed& speed) {
292 uint32_t status = 0;
293 if (!getPortStatus(port, status) || (status & 3) != 3) {
294 return false;
295 }
296 if (status & (1 << 10)) {
297 speed = HighSpeed;
298 } else if (status & (1 << PortLowSpeed)) {
299 speed = LowSpeed;
300 } else {
301 speed = FullSpeed;
302 }
303 return true;
304}
305
306void UsbHubDevice::portChanged(size_t port) {
307 uint32_t status = 0;
308 if (!getPortStatus(port, status) || !(status >> 16)) {
309 return;
310 }
311 if (!clearPortChanges(port, status)) {
312 WARNING("USB: HUB: couldn't clear changes on port " << Dec << port << Hex);
313 return;
314 }
315
316 const bool connectionChanged = status & (1U << CPortConnection);
317 const bool disabled = (status & (1U << CPortEnable)) && !(status & (1 << PortEnable));
318 const bool overCurrentChanged = status & (1U << CPortOverCurrent);
319 if (!connectionChanged && !disabled && !overCurrentChanged) {
320 return;
321 }
322
323 deviceDisconnected(port);
324 if (status & (1 << PortOverCurrent)) {
325 return;
326 }
327 if (!(status & (1 << PortPower))) {
328 if (!setPortFeature(port, PortPower) ||
329 !Time::delay(m_PowerGoodDelay * Time::Multiplier::Millisecond)) {
330 return;
331 }
332 if (!getPortStatus(port, status)) {
333 return;
334 }
335 }
336 if (!(status & (1 << PortConnection))) {
337 return;
338 }
339 if (!connectPort(port)) {
340 WARNING("USB: HUB: couldn't enumerate changed port " << Dec << port << Hex);
341 }
342}
343
344bool UsbHubDevice::portReset(uint8_t nPort, bool bErrorResponse) {
345 (void)bErrorResponse;
346 if (nPort >= m_nPorts)
347 return false;
348
349 // Reset the port
350 if (!setPortFeature(nPort, PortReset))
351 return false;
352
353 // Delay while the reset completes
354 if (!Time::delay(50 * Time::Multiplier::Millisecond))
355 return false;
356
357 // Wait for completion
358 uint32_t portStatus = 0;
359 size_t poll = 0;
360 for (; poll < PortResetPollLimit; ++poll) {
361 const auto state = getLifecycleState();
362 if (state == RequestQueue::LifecycleState::Stopping ||
363 state == RequestQueue::LifecycleState::Destroyed) {
364 return false;
365 }
366 if (!getPortStatus(nPort, portStatus))
367 return false;
368 if (!(portStatus & (1 << 4)))
369 break;
370 if (!Time::delay(Time::Multiplier::Millisecond))
371 return false;
372 }
373 if (poll == PortResetPollLimit) {
374 ERROR("USB: HUB: reset on port " << Dec << static_cast<size_t>(nPort) << Hex << " timed out");
375 return false;
376 }
377 if (!clearPortFeature(nPort, CPortReset))
378 return false;
379
380 // Port has been powered on and now reset, check to see if it's enabled and
381 // a device is connected
382 return ((portStatus & 0x3) == 0x3);
383}
384
385bool UsbHubDevice::setPortFeature(size_t port, PortFeatureSelectors feature) {
386 return controlRequest(HubPortRequest, UsbRequest::SetFeature, feature, (port + 1) & 0xFF, 0, 0);
387}
388
389bool UsbHubDevice::clearPortFeature(size_t port, PortFeatureSelectors feature) {
390 return controlRequest(HubPortRequest, UsbRequest::ClearFeature, feature, (port + 1) & 0xFF, 0, 0);
391}
392
393bool UsbHubDevice::getPortStatus(size_t port, uint32_t& status) {
394 status = 0;
395 return controlRequest(static_cast<uint8_t>(static_cast<uint8_t>(UsbRequestDirection::In) |
396 static_cast<uint8_t>(HubPortRequest)),
397 UsbRequest::GetStatus, 0, (port + 1) & 0xFF, sizeof(status),
398 reinterpret_cast<uintptr_t>(&status));
399}
400
401void UsbHubDevice::addTransferToTransaction(uintptr_t pTransaction, bool bToggle, UsbPid pid,
402 uintptr_t pBuffer, size_t nBytes) {
403 m_pHub->addTransferToTransaction(pTransaction, bToggle, pid, pBuffer, nBytes);
404}
405
407 if (endpointInfo.speed != HighSpeed && !endpointInfo.nHubAddress) {
408 if (m_Speed == HighSpeed) {
409 endpointInfo.nHubAddress = m_nAddress;
410 ++endpointInfo.nHubPort;
411 } else
412 endpointInfo.nHubPort = m_nPort;
413 }
414 return m_pHub->createTransaction(endpointInfo);
415}
416
417bool UsbHubDevice::doAsync(uintptr_t pTransaction, void (*pCallback)(uintptr_t, ssize_t),
418 uintptr_t pParam) {
419 return m_pHub->doAsync(pTransaction, pCallback, pParam);
420}
421
422void UsbHubDevice::cancelAsyncAndDrain(uintptr_t pTransaction,
423 void (*pCallback)(uintptr_t, ssize_t), uintptr_t pParam) {
424 m_pHub->cancelAsyncAndDrain(pTransaction, pCallback, pParam);
425}
426
427bool UsbHubDevice::addInterruptInHandler(UsbEndpoint endpointInfo, uintptr_t pBuffer,
428 uint16_t nBytes, void (*pCallback)(uintptr_t, ssize_t),
429 UsbInterruptInHandle& handle, uintptr_t pParam) {
430 if (endpointInfo.speed != HighSpeed && !endpointInfo.nHubAddress) {
431 if (m_Speed == HighSpeed) {
432 endpointInfo.nHubAddress = m_nAddress;
433 ++endpointInfo.nHubPort;
434 } else
435 endpointInfo.nHubPort = m_nPort;
436 }
437 // The upstream call publishes the handle directly against the root HCD.
438 return m_pHub->addInterruptInHandler(endpointInfo, pBuffer, nBytes, pCallback, handle, pParam);
439}
440
442 void (*callback)(uintptr_t, ssize_t),
443 uintptr_t parameter, bool producerAlreadyStopped) {
444 (void)token;
445 (void)callback;
446 (void)parameter;
447 (void)producerAlreadyStopped;
448 panic("downstream USB hub unexpectedly owned an interrupt-IN handle");
449 return false;
450}
static void setShutdownDetail(const char *detail)
Definition Machine.cc:38
LifecycleState getLifecycleState()
virtual void destroy()
virtual void initialise()
UsbHub * m_pHub
Parent USB hub.
Definition UsbDevice.h:323
uint8_t m_nAddress
The current address of the device.
Definition UsbDevice.h:297
Interface * m_pInterface
Interface in use.
Definition UsbDevice.h:320
bool controlRequest(uint8_t nRequestType, uint8_t nRequest, uint16_t nValue, uint16_t nIndex, uint16_t nLength=0, uintptr_t pBuffer=0, uint32_t timeout=5000)
Performs an USB control request.
Definition UsbDevice.cc:558
UsbState m_UsbState
The current state of the device.
Definition UsbDevice.h:311
UsbSpeed m_Speed
The speed at which the device operates.
Definition UsbDevice.h:308
uint8_t m_nPort
The number of the port on which the device is connected.
Definition UsbDevice.h:301
uint8_t getDescriptorLength(uint8_t nDescriptorType, uint8_t nDescriptorIndex, uint8_t requestType=0)
Gets a descriptor's length from the device.
Definition UsbDevice.cc:643
DeviceDescriptor * getDescriptor()
Returns the device descriptor of the device.
Definition UsbDevice.h:216
bool portSpeed(uint8_t port, UsbSpeed &speed) override
void prepareForDisconnection() override
bool getPortStatus(size_t port, uint32_t &status)
Top 16 bits of status hold the port-change flags.
virtual void cancelAsyncAndDrain(uintptr_t pTransaction, void(*pCallback)(uintptr_t, ssize_t), uintptr_t pParam)
MUST_USE_RESULT bool cancelInterruptInAndDrain(const UsbInterruptInToken &token, void(*callback)(uintptr_t, ssize_t), uintptr_t parameter, bool producerAlreadyStopped) override
void quiesceForRetirement() override
void prepareForDriverRetirement() override
virtual MUST_USE_RESULT bool doAsync(uintptr_t pTransaction, void(*pCallback)(uintptr_t, ssize_t)=0, uintptr_t pParam=0)
virtual bool portReset(uint8_t nPort, bool bErrorResponse=false)
Gets a UsbDevice from a given vendor:product pair.
virtual void addTransferToTransaction(uintptr_t pTransaction, bool bToggle, UsbPid pid, uintptr_t pBuffer, size_t nBytes)
Adds a new transfer to an existent transaction.
void cancelRequest(const Request &request) override
virtual void initialiseDriver()
Implemented by the driver class, initialises driver-specific stuff.
uint64_t executeRequest(uint64_t p1, uint64_t p2, uint64_t p3, uint64_t p4, uint64_t p5, uint64_t p6, uint64_t p7, uint64_t p8) override
virtual MUST_USE_RESULT bool addInterruptInHandler(UsbEndpoint endpointInfo, uintptr_t pBuffer, uint16_t nBytes, void(*pCallback)(uintptr_t, ssize_t), UsbInterruptInHandle &handle, uintptr_t pParam=0)
Adds an owned recurring interrupt-IN transaction.
virtual uintptr_t createTransaction(UsbEndpoint endpointInfo)
Creates a new transaction with the given endpoint data.
virtual void addTransferToTransaction(uintptr_t pTransaction, bool bToggle, UsbPid pid, uintptr_t pBuffer, size_t nBytes)=0
Adds a new transfer to an existent transaction.
virtual uintptr_t createTransaction(UsbEndpoint endpointInfo)=0
Creates a new transaction with the given endpoint data.
void retainDisconnectedAddressesUntilControllerTeardown()
Definition UsbHub.cc:242
virtual void cancelAsyncAndDrain(uintptr_t pTransaction, void(*pCallback)(uintptr_t, ssize_t), uintptr_t pParam)=0
bool deviceConnected(uint8_t nPort, UsbSpeed speed)
Called when a device is connected to a port on the hub.
Definition UsbHub.cc:388
void deviceDisconnected(uint8_t nPort)
Called when a device is disconnected from a port on the hub.
Definition UsbHub.cc:550
virtual MUST_USE_RESULT bool addInterruptInHandler(UsbEndpoint endpointInfo, uintptr_t pBuffer, uint16_t nBytes, void(*pCallback)(uintptr_t, ssize_t), UsbInterruptInHandle &handle, uintptr_t pParam=0)=0
Adds an owned recurring interrupt-IN transaction.
virtual MUST_USE_RESULT bool doAsync(uintptr_t pTransaction, void(*pCallback)(uintptr_t, ssize_t)=0, uintptr_t pParam=0)=0
void disconnectAllDevices()
Definition UsbHub.cc:217
MUST_USE_RESULT bool reset()
Definition UsbHub.cc:99
void EXPORTED_PUBLIC panic(const char *msg) NORETURN
Definition panic.cc:118
@ Dec
Definition Log.h:126
@ Hex
Definition Log.h:124