Files
nt-lite/code/drivers/bus/usb/usb_hub.c
T

1060 lines
40 KiB
C

// USB hub driver. This file owns hub probing, port status handling, and child-device enumeration.
// Root hubs and external hubs are represented by the same FDO abstraction. The
// xHCI root hub feeds port-change events directly into this driver, while an
// external hub feeds the same port state machine from its interrupt-IN change
// bitmap.
#include "usb.h"
DRIVER_OBJECT gUsbHubDriver;
static const IO_DEVICE_ID gUsbHubIds[] = {
{ IoBusUsb, { "USB\\ROOT_HUB", sizeof("USB\\ROOT_HUB") - 1 } },
{ IoBusUsb, { "USB\\CLASS_09", sizeof("USB\\CLASS_09") - 1 } },
};
static const IO_DRIVER_ID_TABLE gUsbHubIdTable = {
gUsbHubIds,
sizeof(gUsbHubIds) / sizeof(gUsbHubIds[0]),
};
#define USB_HUB_PORT_ENUMERATING 0xffu
#define USB_HUB_PORT_FAILED 0xfeu
// Implements the hub Slot Busy operation.
static bool UsbHubSlotBusy(uint8_t slot) {
return slot != 0 && slot != USB_HUB_PORT_ENUMERATING && slot != USB_HUB_PORT_FAILED;
}
// Implements the hub Root Port Connected operation.
static bool UsbHubRootPortConnected(XHCI_CONTROLLER* xhci, uint8_t port) {
if (!xhci || !xhci->Op || port == 0) return false;
uint32_t portsc = XhciRead32(xhci->Op, XhciPortReg(port));
return (portsc & XHCI_PORTSC_CCS) != 0;
}
// Implements the hub Clear Root Port Failure operation.
static void UsbHubClearRootPortFailure(USB_HUB_EXTENSION* hub, uint8_t port) {
if (!hub || port == 0 || port > USB_ROOT_HUB_MAX_PORTS) return;
hub->RootPortFailCount[port] = 0;
if (hub->RootPortSlot[port] == USB_HUB_PORT_FAILED)
hub->RootPortSlot[port] = 0;
}
// Implements the hub Mark Root Port Failed operation.
static void UsbHubMarkRootPortFailed(USB_HUB_EXTENSION* hub, uint8_t port) {
if (!hub || port == 0 || port > USB_ROOT_HUB_MAX_PORTS) return;
hub->RootPortSlot[port] = USB_HUB_PORT_FAILED;
hub->RootPortState[port] = UsbHubPortFailedConnected;
}
static KSTATUS UsbHubEnumerateRootPortWithRetries(USB_HUB_EXTENSION* hub,
XHCI_CONTROLLER* xhci,
uint8_t port,
uint8_t* outSlot) {
if (outSlot) *outSlot = 0;
if (!hub || !xhci || port == 0 || port > USB_ROOT_HUB_MAX_PORTS)
return STATUS_INVALID_PARAMETER;
KSTATUS last = STATUS_DEVICE_ERROR;
uint8_t maxAttempts = USB_ROOT_PORT_ENUM_RETRIES;
if (maxAttempts == 0) maxAttempts = 1;
for (uint8_t attempt = 1; attempt <= maxAttempts; attempt++) {
if (!UsbHubRootPortConnected(xhci, port))
return STATUS_NOT_FOUND;
uint8_t slot = 0;
last = XhciEnumeratePortEx(xhci, port, &slot);
if (KSUCCESS(last)) {
if (outSlot) *outSlot = slot;
hub->RootPortFailCount[port] = 0;
hub->RootPortState[port] = UsbHubPortChildPresent;
return STATUS_SUCCESS;
}
hub->RootPortFailCount[port] = attempt;
// On real Intel laptop controllers some internal full-speed devices
// respond with xHCI USB Transaction Error during Address Device while
// the port is still settling. Retry that boundedly while the port is
// held in ENUMERATING state so queued PSC events cannot start parallel
// enumerations. Descriptor/control timeouts are not retried here; they
// are usually dead/firmware-owned ports and retrying them stalls boot.
if (last != STATUS_DEVICE_ERROR || attempt == maxAttempts)
break;
DbgPrint("usb-hub: root port %u enumeration attempt %u failed st=0x%x; retrying\n",
port, attempt, last);
UsbDelayUs(xhci->Hal, 50000);
}
if (UsbHubRootPortConnected(xhci, port)) {
UsbHubMarkRootPortFailed(hub, port);
} else {
UsbHubClearRootPortFailure(hub, port);
hub->RootPortState[port] = UsbHubPortDisconnected;
}
return last;
}
// Implements the hub Pop Pending Root Port operation.
static bool UsbHubPopPendingRootPort(USB_HUB_EXTENSION* hub, uint8_t* outPort) {
if (!hub || !outPort) return false;
uintptr_t flags = KeAcquireSpinlock(&hub->PendingLock);
for (uint32_t word = 0; word < 8; word++) {
uint32_t bits = hub->PendingRootPorts[word];
if (!bits)
continue;
for (uint8_t bit = 0; bit < 32; bit++) {
if (!(bits & (1u << bit)))
continue;
hub->PendingRootPorts[word] &= ~(1u << bit);
KeReleaseSpinlock(&hub->PendingLock, flags);
*outPort = (uint8_t)(word * 32u + bit);
return true;
}
}
KeReleaseSpinlock(&hub->PendingLock, flags);
return false;
}
// Implements the hub Pop Pending External Port operation.
static bool UsbHubPopPendingExternalPort(USB_HUB_EXTENSION* hub, uint8_t* outPort) {
if (!hub || !outPort) return false;
uintptr_t flags = KeAcquireSpinlock(&hub->PendingLock);
uint32_t bits = hub->PendingHubPorts[0];
if (!bits) {
KeReleaseSpinlock(&hub->PendingLock, flags);
return false;
}
for (uint8_t port = 1; port <= hub->PortCount && port <= USB_EXTERNAL_HUB_MAX_PORTS; port++) {
if (!(bits & (1u << port)))
continue;
hub->PendingHubPorts[0] &= ~(1u << port);
KeReleaseSpinlock(&hub->PendingLock, flags);
*outPort = port;
return true;
}
hub->PendingHubPorts[0] = 0;
KeReleaseSpinlock(&hub->PendingLock, flags);
return false;
}
// Implements the hub Record Root Port Change operation.
static void UsbHubRecordRootPortChange(USB_HUB_EXTENSION* hub, uint8_t port) {
if (!hub || port == 0 || port > hub->PortCount)
return;
uint32_t word = (uint32_t)port / 32u;
uint32_t bit = (uint32_t)port & 31u;
uintptr_t flags = KeAcquireSpinlock(&hub->PendingLock);
hub->PendingRootPorts[word] |= (1u << bit);
KeReleaseSpinlock(&hub->PendingLock, flags);
if (hub->WorkerSemaphore)
KeSignalSemaphore(hub->WorkerSemaphore);
}
// Implements the hub Queue External Port Change operation.
void UsbHubQueueExternalPortChange(USB_HUB_EXTENSION* hub, uint8_t port) {
if (!hub || port == 0 || port > hub->PortCount || port > USB_EXTERNAL_HUB_MAX_PORTS)
return;
uintptr_t flags = KeAcquireSpinlock(&hub->PendingLock);
hub->PendingHubPorts[0] |= (1u << port);
KeReleaseSpinlock(&hub->PendingLock, flags);
if (hub->WorkerSemaphore)
KeSignalSemaphore(hub->WorkerSemaphore);
}
// Implements the hub Remove Slot Child operation.
static void UsbHubRemoveSlotChild(USB_HUB_EXTENSION* hub, uint8_t port, uint8_t slot) {
if (!hub || !hub->Hcd || slot == 0)
return;
XHCI_CONTROLLER* xhci = (XHCI_CONTROLLER*)hub->Hcd->ControllerContext;
if (!xhci || slot > XHCI_MAX_TRACKED_SLOTS)
return;
XHCI_DEVICE* xdev = &xhci->Devices[slot];
DbgPrint("usb-hub: %s port %u removing slot=%u\n",
hub->Kind == UsbHubKindRoot ? "root" : "hub", port, slot);
if (xdev->UsbPdo)
(void)UsbRemovePdoSubtree(xdev->UsbPdo, true);
if (xdev->Allocated)
(void)XhciDisableSlot(xhci, slot);
if (xdev->UsbDevice) {
xdev->UsbDevice->State = UsbDeviceRemoved;
UsbDereferenceDevice(xdev->UsbDevice);
}
memset(xdev, 0, sizeof(*xdev));
if (hub->Kind == UsbHubKindRoot && port <= USB_ROOT_HUB_MAX_PORTS) {
hub->RootPortSlot[port] = 0;
hub->RootPortState[port] = UsbHubPortDisconnected;
} else if (port <= USB_EXTERNAL_HUB_MAX_PORTS) {
hub->ChildSlot[port] = 0;
hub->ChildPortState[port] = UsbHubPortDisconnected;
}
}
// Implements the hub Remove Root Child operation.
static void UsbHubRemoveRootChild(USB_HUB_EXTENSION* hub, uint8_t port, uint8_t slot) {
UsbHubRemoveSlotChild(hub, port, slot);
}
static KSTATUS UsbHubControlIn(USB_HUB_EXTENSION* hub,
uint8_t requestType,
uint8_t request,
uint16_t value,
uint16_t index,
void* buffer,
PHYSICAL_ADDRESS bufferPhys,
uint16_t length) {
if (!hub || !hub->LowerDevice || !buffer || !bufferPhys || !length)
return STATUS_INVALID_PARAMETER;
USB_URB urb;
memset(&urb, 0, sizeof(urb));
urb.Function = UrbFunctionControlTransfer;
urb.Direction = UsbTransferIn;
urb.Device = hub->Device;
urb.Buffer = buffer;
urb.BufferPhys = bufferPhys;
urb.Length = length;
urb.Setup.bmRequestType = requestType;
urb.Setup.bRequest = request;
urb.Setup.wValue = value;
urb.Setup.wIndex = index;
urb.Setup.wLength = length;
return UsbSubmitUrbSync(hub->LowerDevice, &urb);
}
static KSTATUS UsbHubControlNoData(USB_HUB_EXTENSION* hub,
uint8_t requestType,
uint8_t request,
uint16_t value,
uint16_t index) {
if (!hub || !hub->LowerDevice)
return STATUS_INVALID_PARAMETER;
USB_URB urb;
memset(&urb, 0, sizeof(urb));
urb.Function = UrbFunctionControlTransfer;
urb.Direction = UsbTransferNoData;
urb.Device = hub->Device;
urb.Setup.bmRequestType = requestType;
urb.Setup.bRequest = request;
urb.Setup.wValue = value;
urb.Setup.wIndex = index;
urb.Setup.wLength = 0;
return UsbSubmitUrbSync(hub->LowerDevice, &urb);
}
// Implements the hub Read Descriptor operation.
static KSTATUS UsbHubReadDescriptor(USB_HUB_EXTENSION* hub, USB_HUB_DESCRIPTOR* outDesc) {
if (!hub || !outDesc || !hub->ChangeBitmap || !hub->ChangeBitmapPhys)
return STATUS_INVALID_PARAMETER;
memset(hub->ChangeBitmap, 0, USB_HUB_CHANGE_BITMAP_BYTES);
KSTATUS st = UsbHubControlIn(hub,
USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_DEVICE,
USB_REQ_GET_DESCRIPTOR,
(uint16_t)(USB_DESC_HUB << 8),
0,
hub->ChangeBitmap,
hub->ChangeBitmapPhys,
USB_HUB_CHANGE_BITMAP_BYTES);
if (!KSUCCESS(st))
return st;
memcpy(outDesc, hub->ChangeBitmap, sizeof(USB_HUB_DESCRIPTOR));
if (outDesc->bDescriptorType != USB_DESC_HUB || outDesc->bNbrPorts == 0)
return STATUS_DEVICE_ERROR;
return STATUS_SUCCESS;
}
// Implements the hub Get Port Status operation.
static KSTATUS UsbHubGetPortStatus(USB_HUB_EXTENSION* hub, uint8_t port, USB_HUB_PORT_STATUS* outStatus) {
if (!hub || !outStatus || port == 0)
return STATUS_INVALID_PARAMETER;
memset(hub->ChangeBitmap, 0, USB_HUB_CHANGE_BITMAP_BYTES);
KSTATUS st = UsbHubControlIn(hub,
USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_OTHER,
USB_REQ_GET_STATUS,
0,
port,
hub->ChangeBitmap,
hub->ChangeBitmapPhys,
sizeof(USB_HUB_PORT_STATUS));
if (!KSUCCESS(st))
return st;
memcpy(outStatus, hub->ChangeBitmap, sizeof(USB_HUB_PORT_STATUS));
return STATUS_SUCCESS;
}
// Implements the hub Set Port Feature operation.
static KSTATUS UsbHubSetPortFeature(USB_HUB_EXTENSION* hub, uint8_t port, uint16_t feature) {
return UsbHubControlNoData(hub,
USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_OTHER,
USB_REQ_SET_FEATURE,
feature,
port);
}
// Implements the hub Clear Port Feature operation.
static KSTATUS UsbHubClearPortFeature(USB_HUB_EXTENSION* hub, uint8_t port, uint16_t feature) {
return UsbHubControlNoData(hub,
USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_OTHER,
USB_REQ_CLEAR_FEATURE,
feature,
port);
}
// Implements the hub Speed From Port Status operation.
static uint8_t UsbHubSpeedFromPortStatus(uint16_t status) {
if (status & USB_HUB_PORT_STATUS_LOW_SPEED)
return 2u;
if (status & USB_HUB_PORT_STATUS_HIGH_SPEED)
return 3u;
return 1u;
}
// Implements the hub Reset External Port operation.
static KSTATUS UsbHubResetExternalPort(USB_HUB_EXTENSION* hub, uint8_t port, uint8_t* outSpeed) {
if (outSpeed) *outSpeed = 0;
if (!hub || port == 0)
return STATUS_INVALID_PARAMETER;
(void)UsbHubClearPortFeature(hub, port, USB_HUB_PORT_FEAT_C_CONNECTION);
(void)UsbHubClearPortFeature(hub, port, USB_HUB_PORT_FEAT_C_ENABLE);
(void)UsbHubClearPortFeature(hub, port, USB_HUB_PORT_FEAT_C_RESET);
KSTATUS st = UsbHubSetPortFeature(hub, port, USB_HUB_PORT_FEAT_RESET);
if (!KSUCCESS(st))
return st;
USB_HUB_PORT_STATUS ps;
memset(&ps, 0, sizeof(ps));
for (uint32_t i = 0; i < 500u; i++) {
UsbDelayUs(((XHCI_CONTROLLER*)hub->Hcd->ControllerContext)->Hal, 1000u);
st = UsbHubGetPortStatus(hub, port, &ps);
if (!KSUCCESS(st))
return st;
if (ps.Change & USB_HUB_PORT_CHANGE_RESET)
break;
}
(void)UsbHubClearPortFeature(hub, port, USB_HUB_PORT_FEAT_C_RESET);
(void)UsbHubClearPortFeature(hub, port, USB_HUB_PORT_FEAT_C_ENABLE);
st = UsbHubGetPortStatus(hub, port, &ps);
if (!KSUCCESS(st))
return st;
if (!(ps.Status & USB_HUB_PORT_STATUS_CONNECTION))
return STATUS_NOT_FOUND;
if (!(ps.Status & USB_HUB_PORT_STATUS_ENABLE))
return STATUS_DEVICE_ERROR;
if (outSpeed)
*outSpeed = UsbHubSpeedFromPortStatus(ps.Status);
return STATUS_SUCCESS;
}
// Implements the hub Handle External Port Change operation.
static void UsbHubHandleExternalPortChange(USB_HUB_EXTENSION* hub, uint8_t port) {
if (!hub || hub->Kind != UsbHubKindExternal || port == 0 || port > hub->PortCount || port > USB_EXTERNAL_HUB_MAX_PORTS)
return;
USB_HUB_PORT_STATUS ps;
memset(&ps, 0, sizeof(ps));
KSTATUS st = UsbHubGetPortStatus(hub, port, &ps);
if (!KSUCCESS(st)) {
DbgPrint("usb-hub: slot=%u port=%u GET_STATUS failed st=0x%x\n",
hub->Device ? hub->Device->SlotId : 0, port, st);
return;
}
if (ps.Change & USB_HUB_PORT_CHANGE_CONNECTION)
(void)UsbHubClearPortFeature(hub, port, USB_HUB_PORT_FEAT_C_CONNECTION);
if (ps.Change & USB_HUB_PORT_CHANGE_ENABLE)
(void)UsbHubClearPortFeature(hub, port, USB_HUB_PORT_FEAT_C_ENABLE);
if (ps.Change & USB_HUB_PORT_CHANGE_RESET)
(void)UsbHubClearPortFeature(hub, port, USB_HUB_PORT_FEAT_C_RESET);
uint8_t oldSlot = hub->ChildSlot[port];
if (!(ps.Status & USB_HUB_PORT_STATUS_CONNECTION)) {
if (UsbHubSlotBusy(oldSlot)) {
hub->ChildPortState[port] = UsbHubPortRemoving;
UsbHubRemoveSlotChild(hub, port, oldSlot);
} else {
hub->ChildPortState[port] = UsbHubPortDisconnected;
UsbTrace("usb-hub: slot=%u port=%u disconnected\n", hub->Device ? hub->Device->SlotId : 0, port);
}
return;
}
if (oldSlot == USB_HUB_PORT_ENUMERATING) {
UsbTrace("usb-hub: slot=%u port=%u change ignored; enumeration already in progress\n",
hub->Device ? hub->Device->SlotId : 0, port);
return;
}
if (UsbHubSlotBusy(oldSlot)) {
UsbTrace("usb-hub: slot=%u port=%u change ignored; child slot=%u still present\n",
hub->Device ? hub->Device->SlotId : 0, port, oldSlot);
return;
}
hub->ChildSlot[port] = USB_HUB_PORT_ENUMERATING;
hub->ChildPortState[port] = UsbHubPortConnectedDebounce;
UsbDelayUs(((XHCI_CONTROLLER*)hub->Hcd->ControllerContext)->Hal, 30000u);
uint8_t speed = 0;
hub->ChildPortState[port] = UsbHubPortResetting;
st = UsbHubResetExternalPort(hub, port, &speed);
if (!KSUCCESS(st)) {
hub->ChildSlot[port] = 0;
hub->ChildPortState[port] = UsbHubPortFailedConnected;
DbgPrint("usb-hub: slot=%u port=%u reset failed st=0x%x\n",
hub->Device ? hub->Device->SlotId : 0, port, st);
return;
}
hub->ChildPortState[port] = UsbHubPortEnumerating;
uint8_t slot = 0;
st = XhciEnumerateHubPortEx((XHCI_CONTROLLER*)hub->Hcd->ControllerContext, hub, port, speed, &slot);
if (KSUCCESS(st)) {
hub->ChildSlot[port] = slot;
hub->ChildPortState[port] = UsbHubPortChildPresent;
DbgPrint("usb-hub: slot=%u port=%u attached child-slot=%u speed=%s(%u)\n",
hub->Device ? hub->Device->SlotId : 0,
port,
slot,
XhciPortSpeedName(speed),
speed);
} else {
hub->ChildSlot[port] = 0;
hub->ChildPortState[port] = UsbHubPortFailedConnected;
DbgPrint("usb-hub: slot=%u port=%u enumeration failed st=0x%x\n",
hub->Device ? hub->Device->SlotId : 0, port, st);
}
}
// Implements the hub Handle Root Port Change operation.
static void UsbHubHandleRootPortChange(USB_HUB_EXTENSION* hub, uint8_t port) {
if (!hub || !hub->Hcd || port == 0 || port > hub->PortCount || port > USB_ROOT_HUB_MAX_PORTS)
return;
XHCI_CONTROLLER* xhci = (XHCI_CONTROLLER*)hub->Hcd->ControllerContext;
if (!xhci || !xhci->Op)
return;
uint32_t portsc = XhciRead32(xhci->Op, XhciPortReg(port));
uint32_t changes = portsc & XHCI_PORTSC_CHANGE_MASK;
bool connected = (portsc & XHCI_PORTSC_CCS) != 0;
uint8_t oldSlot = hub->RootPortSlot[port];
if (changes)
XhciClearPortChanges(xhci, port);
if (!connected) {
if (UsbHubSlotBusy(oldSlot)) {
hub->RootPortState[port] = UsbHubPortRemoving;
UsbHubRemoveRootChild(hub, port, oldSlot);
} else if (oldSlot == USB_HUB_PORT_ENUMERATING)
UsbTrace("usb-hub: root port %u disconnect while enumeration is already in progress\n", port);
else {
hub->RootPortState[port] = UsbHubPortDisconnected;
DbgPrint("usb-hub: root port %u disconnected\n", port);
}
UsbHubClearRootPortFailure(hub, port);
return;
}
if (oldSlot == USB_HUB_PORT_ENUMERATING) {
UsbTrace("usb-hub: root port %u change ignored; enumeration already in progress\n", port);
return;
}
if (oldSlot == USB_HUB_PORT_FAILED) {
if (changes & XHCI_PORTSC_CSC) {
UsbTrace("usb-hub: root port %u retrying after connection-change on failed port\n", port);
UsbHubClearRootPortFailure(hub, port);
oldSlot = 0;
} else {
UsbTrace("usb-hub: root port %u change ignored; previous enumeration failed and device is still connected\n", port);
return;
}
}
if (UsbHubSlotBusy(oldSlot)) {
UsbTrace("usb-hub: root port %u change ignored; slot=%u still present\n", port, oldSlot);
return;
}
hub->RootPortSlot[port] = USB_HUB_PORT_ENUMERATING;
hub->RootPortState[port] = UsbHubPortConnectedDebounce;
UsbDelayUs(xhci->Hal, 30000);
portsc = XhciRead32(xhci->Op, XhciPortReg(port));
if (!(portsc & XHCI_PORTSC_CCS)) {
UsbHubClearRootPortFailure(hub, port);
return;
}
hub->RootPortState[port] = UsbHubPortEnumerating;
uint8_t slot = 0;
KSTATUS st = UsbHubEnumerateRootPortWithRetries(hub, xhci, port, &slot);
if (KSUCCESS(st)) {
hub->RootPortSlot[port] = slot;
hub->RootPortState[port] = UsbHubPortChildPresent;
DbgPrint("usb-hub: root port %u attached slot=%u\n", port, slot);
} else {
DbgPrint("usb-hub: root port %u hotplug enumeration failed st=0x%x after %u attempt(s)\n",
port, st, hub->RootPortFailCount[port]);
}
}
static void UsbHubArmExternalInterrupt(USB_HUB_EXTENSION* hub);
static KSTATUS UsbHubDoRootPortScan(USB_HCD* hcd);
// Implements the hub Take Initial Root Scan operation.
static bool UsbHubTakeInitialRootScan(USB_HUB_EXTENSION* hub) {
if (!hub || hub->Kind != UsbHubKindRoot)
return false;
uintptr_t flags = KeAcquireSpinlock(&hub->PendingLock);
bool take = hub->InitialScanPending && !hub->InitialScanInProgress && !hub->InitialScanComplete;
if (take) {
hub->InitialScanPending = false;
hub->InitialScanInProgress = true;
}
KeReleaseSpinlock(&hub->PendingLock, flags);
return take;
}
// Implements the hub Worker operation.
static void UsbHubWorker(void* arg) {
USB_HUB_EXTENSION* hub = (USB_HUB_EXTENSION*)arg;
while (hub && hub->WorkerRunning) {
KSTATUS st = KeWaitSemaphore(hub->WorkerSemaphore);
if (!KSUCCESS(st))
continue;
if (hub->Kind == UsbHubKindRoot) {
if (UsbHubTakeInitialRootScan(hub))
(void)UsbHubDoRootPortScan(hub->Hcd);
uint8_t port = 0;
while (UsbHubPopPendingRootPort(hub, &port))
UsbHubHandleRootPortChange(hub, port);
} else {
uint8_t port = 0;
while (UsbHubPopPendingExternalPort(hub, &port))
UsbHubHandleExternalPortChange(hub, port);
if (hub->Started && !hub->Removed && !hub->SurpriseRemoved && !hub->ChangeUrbPending)
UsbHubArmExternalInterrupt(hub);
}
}
}
// Implements the hub Do Root Port Scan operation.
static KSTATUS UsbHubDoRootPortScan(USB_HCD* hcd) {
if (!hcd || !hcd->RootHubContext)
return STATUS_INVALID_PARAMETER;
USB_HUB_EXTENSION* hub = (USB_HUB_EXTENSION*)hcd->RootHubContext;
if (!hub->Started)
return STATUS_OBJECT_DEAD;
uint32_t attempted = 0;
uint32_t configured = 0;
XHCI_CONTROLLER* xhci = (XHCI_CONTROLLER*)hcd->ControllerContext;
if (!xhci || !xhci->Op)
return STATUS_INVALID_PARAMETER;
for (uint8_t port = 1; port <= hub->PortCount; port++) {
uint32_t portsc = XhciRead32(xhci->Op, XhciPortReg(port));
if (!(portsc & XHCI_PORTSC_CCS))
continue;
if (hub->RootPortSlot[port]) {
UsbTrace("usb-hub: root port %u initial scan skipped; state=%u\n",
port, hub->RootPortSlot[port]);
continue;
}
attempted++;
hub->RootPortSlot[port] = USB_HUB_PORT_ENUMERATING;
hub->RootPortState[port] = UsbHubPortEnumerating;
uint8_t slot = 0;
KSTATUS st = UsbHubEnumerateRootPortWithRetries(hub, xhci, port, &slot);
if (KSUCCESS(st)) {
hub->RootPortSlot[port] = slot;
hub->RootPortState[port] = UsbHubPortChildPresent;
configured++;
} else {
DbgPrint("usb-hub: root port %u enumeration failed st=0x%x after %u attempt(s)\n",
port, st, hub->RootPortFailCount[port]);
}
}
DbgPrint("usb-hub: root-port scan complete; attempted=%u configured=%u\n", attempted, configured);
hub->InitialScanComplete = true;
hub->InitialScanInProgress = false;
return STATUS_SUCCESS;
}
// Implements the hub Schedule Root Scan operation.
KSTATUS UsbHubScheduleRootScan(USB_HCD* hcd) {
if (!hcd || !hcd->RootHubContext)
return STATUS_INVALID_PARAMETER;
USB_HUB_EXTENSION* hub = (USB_HUB_EXTENSION*)hcd->RootHubContext;
if (hub->Kind != UsbHubKindRoot || !hub->Started)
return STATUS_OBJECT_DEAD;
uintptr_t flags = KeAcquireSpinlock(&hub->PendingLock);
if (!hub->InitialScanComplete && !hub->InitialScanInProgress)
hub->InitialScanPending = true;
KeReleaseSpinlock(&hub->PendingLock, flags);
if (hub->WorkerSemaphore)
KeSignalSemaphore(hub->WorkerSemaphore);
return STATUS_SUCCESS;
}
// Implements the hub Queue Root Port Change operation.
void UsbHubQueueRootPortChange(USB_HCD* hcd, uint8_t port) {
if (!hcd || !hcd->RootHubContext)
return;
USB_HUB_EXTENSION* hub = (USB_HUB_EXTENSION*)hcd->RootHubContext;
UsbHubRecordRootPortChange(hub, port);
}
// Implements the hub Scan External Ports operation.
static KSTATUS UsbHubScanExternalPorts(USB_HUB_EXTENSION* hub) {
if (!hub || hub->Kind != UsbHubKindExternal)
return STATUS_INVALID_PARAMETER;
uint32_t attempted = 0;
uint32_t configured = 0;
for (uint8_t port = 1; port <= hub->PortCount && port <= USB_EXTERNAL_HUB_MAX_PORTS; port++) {
USB_HUB_PORT_STATUS ps;
memset(&ps, 0, sizeof(ps));
KSTATUS st = UsbHubGetPortStatus(hub, port, &ps);
if (!KSUCCESS(st))
continue;
(void)UsbHubClearPortFeature(hub, port, USB_HUB_PORT_FEAT_C_CONNECTION);
(void)UsbHubClearPortFeature(hub, port, USB_HUB_PORT_FEAT_C_ENABLE);
(void)UsbHubClearPortFeature(hub, port, USB_HUB_PORT_FEAT_C_RESET);
if (!(ps.Status & USB_HUB_PORT_STATUS_CONNECTION))
continue;
attempted++;
UsbHubHandleExternalPortChange(hub, port);
if (UsbHubSlotBusy(hub->ChildSlot[port]))
configured++;
}
DbgPrint("usb-hub: external hub slot=%u scan complete; attempted=%u configured=%u\n",
hub->Device ? hub->Device->SlotId : 0, attempted, configured);
return STATUS_SUCCESS;
}
// Implements the hub Find Interrupt In operation.
static const USB_ENDPOINT_DESCRIPTOR* UsbHubFindInterruptIn(USB_PDO_EXTENSION* pdoExt) {
if (!pdoExt)
return (const USB_ENDPOINT_DESCRIPTOR*)0;
if (pdoExt->Kind == UsbPdoInterface) {
for (uint8_t i = 0; i < pdoExt->EndpointCount; i++) {
const USB_ENDPOINT_DESCRIPTOR* ep = &pdoExt->Endpoints[i];
if ((ep->bEndpointAddress & USB_ENDPOINT_DIR_IN) &&
((ep->bmAttributes & USB_ENDPOINT_ATTR_TYPE_MASK) == USB_ENDPOINT_TYPE_INTERRUPT))
return ep;
}
return (const USB_ENDPOINT_DESCRIPTOR*)0;
}
if (pdoExt->Kind == UsbPdoDevice && pdoExt->Device) {
XHCI_DEVICE* xdev = (XHCI_DEVICE*)pdoExt->Device->HcdDeviceContext;
if (!xdev)
return (const USB_ENDPOINT_DESCRIPTOR*)0;
for (uint8_t i = 0; i < xdev->InterfaceCount; i++) {
USB_TRACKED_INTERFACE* iface = &xdev->Interfaces[i];
if (!iface->Present || iface->Descriptor.bInterfaceClass != USB_CLASS_HUB)
continue;
for (uint8_t epn = 0; epn < iface->EndpointCount; epn++) {
const USB_ENDPOINT_DESCRIPTOR* ep = &iface->Endpoints[epn];
if ((ep->bEndpointAddress & USB_ENDPOINT_DIR_IN) &&
((ep->bmAttributes & USB_ENDPOINT_ATTR_TYPE_MASK) == USB_ENDPOINT_TYPE_INTERRUPT))
return ep;
}
}
}
return (const USB_ENDPOINT_DESCRIPTOR*)0;
}
// Implements the hub External Change Complete operation.
static void UsbHubExternalChangeComplete(USB_URB* urb, void* context) {
USB_HUB_EXTENSION* hub = (USB_HUB_EXTENSION*)context;
if (!hub || urb != &hub->ChangeUrb)
return;
hub->ChangeUrbPending = false;
if (!KSUCCESS(urb->Status) || hub->Removed || hub->SurpriseRemoved)
return;
uint32_t actual = urb->ActualLength;
if (actual > USB_HUB_CHANGE_BITMAP_BYTES)
actual = USB_HUB_CHANGE_BITMAP_BYTES;
for (uint8_t port = 1; port <= hub->PortCount && port <= USB_EXTERNAL_HUB_MAX_PORTS; port++) {
uint8_t byteIndex = port / 8u;
uint8_t bit = port & 7u;
if (byteIndex < actual && (hub->ChangeBitmap[byteIndex] & (1u << bit)))
UsbHubQueueExternalPortChange(hub, port);
}
}
// Implements the hub Arm External Interrupt operation.
static void UsbHubArmExternalInterrupt(USB_HUB_EXTENSION* hub) {
if (!hub || !hub->InterruptPipe || !hub->ChangeBitmap || hub->ChangeUrbPending)
return;
memset(hub->ChangeBitmap, 0, USB_HUB_CHANGE_BITMAP_BYTES);
memset(&hub->ChangeUrb, 0, sizeof(hub->ChangeUrb));
hub->ChangeUrb.Function = UrbFunctionInterruptTransfer;
hub->ChangeUrb.Direction = UsbTransferIn;
hub->ChangeUrb.Device = hub->Device;
hub->ChangeUrb.Pipe = hub->InterruptPipe;
hub->ChangeUrb.Buffer = hub->ChangeBitmap;
hub->ChangeUrb.BufferPhys = hub->ChangeBitmapPhys;
hub->ChangeUrb.Length = hub->InterruptMaxPacket;
if (hub->ChangeUrb.Length > USB_HUB_CHANGE_BITMAP_BYTES)
hub->ChangeUrb.Length = USB_HUB_CHANGE_BITMAP_BYTES;
hub->ChangeUrb.Complete = UsbHubExternalChangeComplete;
hub->ChangeUrb.CompleteContext = hub;
KSTATUS st = UsbSubmitUrbAsync(hub->LowerDevice, &hub->ChangeUrb);
if (st == STATUS_PENDING) {
hub->ChangeUrbPending = true;
} else if (!KSUCCESS(st)) {
DbgPrint("usb-hub: slot=%u interrupt arm failed st=0x%x\n",
hub->Device ? hub->Device->SlotId : 0, st);
}
}
// Implements the hub Start Root Device operation.
static KSTATUS UsbHubStartRootDevice(DEVICE_OBJECT* device, USB_HUB_EXTENSION* hub) {
hub->PortCount = hub->Hcd ? hub->Hcd->MaxRootPorts : 0;
if (hub->PortCount > USB_ROOT_HUB_MAX_PORTS)
hub->PortCount = USB_ROOT_HUB_MAX_PORTS;
KSTATUS st = KeSemaphoreCreate(0, 0x7fffffff, &hub->WorkerSemaphore);
if (!KSUCCESS(st))
return st;
hub->WorkerRunning = true;
st = KeCreateKernelThread((uintptr_t)UsbHubWorker,
(uintptr_t)hub,
PriorityNormal,
&hub->WorkerThread);
if (!KSUCCESS(st)) {
hub->WorkerRunning = false;
return st;
}
hub->Started = true;
device->PnpState = DevicePnpStarted;
if (hub->HubPdo)
hub->HubPdo->PnpState = DevicePnpStarted;
DbgPrint("usb-hub: root hub started ports=%u\n", hub->PortCount);
return STATUS_SUCCESS;
}
// Implements the hub Start External Device operation.
static KSTATUS UsbHubStartExternalDevice(DEVICE_OBJECT* device, USB_HUB_EXTENSION* hub) {
if (!hub || !hub->Device)
return STATUS_INVALID_PARAMETER;
hub->ChangeBitmap = (uint8_t*)UsbAllocateDmaBuffer(hub->Device, 1, &hub->ChangeBitmapPhys);
if (!hub->ChangeBitmap)
return STATUS_NO_MEMORY;
USB_HUB_DESCRIPTOR desc;
memset(&desc, 0, sizeof(desc));
KSTATUS st = UsbHubReadDescriptor(hub, &desc);
if (!KSUCCESS(st)) {
DbgPrint("usb-hub: slot=%u descriptor read failed st=0x%x\n", hub->Device->SlotId, st);
return st;
}
hub->PortCount = desc.bNbrPorts;
if (hub->PortCount > USB_EXTERNAL_HUB_MAX_PORTS) {
DbgPrint("usb-hub: slot=%u reports %u ports; limiting to %u\n",
hub->Device->SlotId, hub->PortCount, USB_EXTERNAL_HUB_MAX_PORTS);
hub->PortCount = USB_EXTERNAL_HUB_MAX_PORTS;
}
hub->PowerOnToPowerGoodMs = desc.bPwrOn2PwrGood ? (uint8_t)(desc.bPwrOn2PwrGood * 2u) : 100u;
for (uint8_t port = 1; port <= hub->PortCount; port++)
(void)UsbHubSetPortFeature(hub, port, USB_HUB_PORT_FEAT_POWER);
UsbDelayUs(((XHCI_CONTROLLER*)hub->Hcd->ControllerContext)->Hal,
(uint32_t)hub->PowerOnToPowerGoodMs * 1000u);
st = KeSemaphoreCreate(0, 0x7fffffff, &hub->WorkerSemaphore);
if (!KSUCCESS(st))
return st;
hub->WorkerRunning = true;
st = KeCreateKernelThread((uintptr_t)UsbHubWorker,
(uintptr_t)hub,
PriorityNormal,
&hub->WorkerThread);
if (!KSUCCESS(st)) {
hub->WorkerRunning = false;
return st;
}
st = UsbCreatePipe(hub->Device,
UsbPipeInterrupt,
hub->InterruptEndpointAddress,
hub->InterruptMaxPacket,
hub->InterruptInterval,
&hub->InterruptPipe);
if (!KSUCCESS(st))
return st;
hub->Started = true;
device->PnpState = DevicePnpStarted;
if (hub->HubPdo)
hub->HubPdo->PnpState = DevicePnpStarted;
DbgPrint("usb-hub: external hub started slot=%u ports=%u ep=%02x maxpkt=%u interval=%u\n",
hub->Device->SlotId,
hub->PortCount,
hub->InterruptEndpointAddress,
hub->InterruptMaxPacket,
hub->InterruptInterval);
(void)UsbHubScanExternalPorts(hub);
UsbHubArmExternalInterrupt(hub);
return STATUS_SUCCESS;
}
// Implements the hub Start Device operation.
static KSTATUS UsbHubStartDevice(DEVICE_OBJECT* device, USB_HUB_EXTENSION* hub) {
if (!device || !hub || hub->Started)
return STATUS_SUCCESS;
if (hub->Kind == UsbHubKindRoot)
return UsbHubStartRootDevice(device, hub);
return UsbHubStartExternalDevice(device, hub);
}
// Implements the hub Dispatch Pnp operation.
KSTATUS UsbHubDispatchPnp(DEVICE_OBJECT* device, IRP* irp) {
if (!device || !irp)
return STATUS_INVALID_PARAMETER;
IO_STACK_LOCATION* ios = IoGetCurrentIrpStackLocation(irp);
if (!ios || ios->MajorFunction != IRP_MJ_PNP)
return STATUS_INVALID_PARAMETER;
USB_HUB_EXTENSION* hub = (USB_HUB_EXTENSION*)device->DeviceExtension;
KSTATUS st = STATUS_SUCCESS;
switch ((IO_PNP_MINOR_FUNCTION)ios->MinorFunction) {
case IRP_MN_START_DEVICE:
st = UsbHubStartDevice(device, hub);
if (!KSUCCESS(st)) {
IoCompleteRequest(irp, st, 0);
return STATUS_PENDING;
}
return IoForwardIrpToLower(device, irp);
case IRP_MN_QUERY_REMOVE_DEVICE:
device->PnpState = DevicePnpRemovePending;
return IoForwardIrpToLower(device, irp);
case IRP_MN_CANCEL_REMOVE_DEVICE:
if (!hub || !hub->SurpriseRemoved)
device->PnpState = DevicePnpStarted;
return IoForwardIrpToLower(device, irp);
case IRP_MN_SURPRISE_REMOVAL:
device->PnpState = DevicePnpSurpriseRemoved;
if (hub) {
hub->SurpriseRemoved = true;
hub->WorkerRunning = false;
if (hub->ChangeUrbPending)
(void)UsbCancelUrb(&hub->ChangeUrb);
if (hub->WorkerSemaphore)
KeSignalSemaphore(hub->WorkerSemaphore);
// Remove downstream PDOs owned by this hub FDO. The explicit
// slot arrays are still used as the hardware lookup table; the
// child PDO tree is the authoritative software lifetime tree.
(void)UsbRemoveDeviceObjectChildren(device, true);
if (hub->Kind == UsbHubKindExternal) {
for (uint8_t port = 1; port <= hub->PortCount && port <= USB_EXTERNAL_HUB_MAX_PORTS; port++) {
if (UsbHubSlotBusy(hub->ChildSlot[port]))
UsbHubRemoveSlotChild(hub, port, hub->ChildSlot[port]);
}
} else if (hub->Kind == UsbHubKindRoot) {
for (uint8_t port = 1; port <= hub->PortCount && port <= USB_ROOT_HUB_MAX_PORTS; port++) {
if (UsbHubSlotBusy(hub->RootPortSlot[port]))
UsbHubRemoveRootChild(hub, port, hub->RootPortSlot[port]);
}
}
}
return IoForwardIrpToLower(device, irp);
case IRP_MN_REMOVE_DEVICE: {
device->PnpState = DevicePnpDeleted;
if (hub) {
hub->Removed = true;
hub->WorkerRunning = false;
if (hub->ChangeUrbPending)
(void)UsbCancelUrb(&hub->ChangeUrb);
(void)UsbRemoveDeviceObjectChildren(device, false);
if (hub->InterruptPipe) {
hub->InterruptPipe->Open = false;
UsbDereferencePipe(hub->InterruptPipe);
hub->InterruptPipe = (USB_PIPE*)0;
}
if (hub->WorkerSemaphore)
KeSignalSemaphore(hub->WorkerSemaphore);
if (hub->Kind == UsbHubKindRoot && hub->Hcd && hub->Hcd->RootHubContext == hub)
hub->Hcd->RootHubContext = (void*)0;
if (hub->RemoveLockReady) {
IoReleaseRemoveLockAndWait(&hub->RemoveLock, irp);
IoDestroyRemoveLock(&hub->RemoveLock);
hub->RemoveLockReady = false;
}
}
KSTATUS lower = hub && hub->LowerDevice ? IoForwardIrpToLower(device, irp) : STATUS_SUCCESS;
if (hub && hub->LowerDevice) {
IoDetachDevice(device);
hub->LowerDevice = (DEVICE_OBJECT*)0;
}
IoDeleteDevice(device);
if (lower == STATUS_NOT_SUPPORTED) {
IoCompleteRequest(irp, STATUS_SUCCESS, 0);
return STATUS_PENDING;
}
if (lower != STATUS_PENDING && !KSUCCESS(lower)) {
IoCompleteRequest(irp, lower, 0);
return STATUS_PENDING;
}
return lower;
}
default:
return IoForwardIrpToLower(device, irp);
}
}
// Implements the hub Add Device operation.
KSTATUS UsbHubAddDevice(DRIVER_OBJECT* driver, DEVICE_OBJECT* physicalDeviceObject) {
if (!driver || !physicalDeviceObject || physicalDeviceObject->Type != DeviceObjectPhysical)
return STATUS_INVALID_PARAMETER;
USB_PDO_EXTENSION* pdoExt = (USB_PDO_EXTENSION*)physicalDeviceObject->DeviceExtension;
if (!pdoExt)
return STATUS_INVALID_PARAMETER;
USB_HUB_KIND kind;
USB_DEVICE* usbDevice = (USB_DEVICE*)0;
const USB_ENDPOINT_DESCRIPTOR* intr = (const USB_ENDPOINT_DESCRIPTOR*)0;
if (pdoExt->Kind == UsbPdoRootHub && pdoExt->Hcd) {
kind = UsbHubKindRoot;
} else if (pdoExt->Kind == UsbPdoDevice && pdoExt->Device &&
pdoExt->Device->DeviceClass == USB_CLASS_HUB) {
kind = UsbHubKindExternal;
usbDevice = pdoExt->Device;
intr = UsbHubFindInterruptIn(pdoExt);
if (!intr)
return STATUS_NOT_FOUND;
} else if (pdoExt->Kind == UsbPdoInterface && pdoExt->Device &&
pdoExt->InterfaceDescriptor.bInterfaceClass == USB_CLASS_HUB) {
kind = UsbHubKindExternal;
usbDevice = pdoExt->Device;
intr = UsbHubFindInterruptIn(pdoExt);
if (!intr)
return STATUS_NOT_FOUND;
} else {
return STATUS_INVALID_PARAMETER;
}
DEVICE_OBJECT* fdo = (DEVICE_OBJECT*)0;
KSTATUS st = IoCreateDevice(driver, sizeof(USB_HUB_EXTENSION), &fdo);
if (!KSUCCESS(st))
return st;
(void)IoSetDeviceName(fdo, kind == UsbHubKindRoot ? SYS_SV_LITERAL("usb-root-hub-driver")
: SYS_SV_LITERAL("usb-hub"));
USB_HUB_EXTENSION* hub = (USB_HUB_EXTENSION*)fdo->DeviceExtension;
hub->Self = fdo;
hub->HubPdo = physicalDeviceObject;
hub->Kind = kind;
hub->Hcd = pdoExt->Hcd;
hub->Device = usbDevice;
if (kind == UsbHubKindExternal && hub->Device)
hub->Hcd = hub->Device->Hcd;
hub->PendingLock = (SPINLOCK)SPINLOCK_INIT;
if (intr) {
hub->InterruptEndpointAddress = intr->bEndpointAddress;
hub->InterruptMaxPacket = intr->wMaxPacketSize;
if (hub->InterruptMaxPacket == 0 || hub->InterruptMaxPacket > USB_HUB_CHANGE_BITMAP_BYTES)
hub->InterruptMaxPacket = USB_HUB_CHANGE_BITMAP_BYTES;
hub->InterruptInterval = intr->bInterval ? intr->bInterval : 12u;
if (pdoExt->Kind == UsbPdoInterface)
hub->InterfaceNumber = pdoExt->InterfaceDescriptor.bInterfaceNumber;
}
st = IoInitializeRemoveLock(&hub->RemoveLock);
if (!KSUCCESS(st)) {
IoDeleteDevice(fdo);
return st;
}
hub->RemoveLockReady = true;
hub->LowerDevice = IoAttachDeviceToDeviceStack(fdo, physicalDeviceObject);
if (!hub->LowerDevice) {
IoDestroyRemoveLock(&hub->RemoveLock);
hub->RemoveLockReady = false;
IoDeleteDevice(fdo);
return STATUS_FAILURE;
}
if (kind == UsbHubKindRoot) {
hub->Hcd->RootHubContext = hub;
DbgPrint("usb-hub: root hub FDO attached\n");
} else {
DbgPrint("usb-hub: external hub FDO attached slot=%u ep=%02x\n",
hub->Device ? hub->Device->SlotId : 0,
hub->InterruptEndpointAddress);
}
return STATUS_SUCCESS;
}
// Initializes hub Driver.
KSTATUS UsbInitializeHubDriver(void) {
memset(&gUsbHubDriver, 0, sizeof(gUsbHubDriver));
IoInitializeDriverObject(&gUsbHubDriver, SYS_SV_LITERAL("usb-hub"));
gUsbHubDriver.IdTable = &gUsbHubIdTable;
gUsbHubDriver.DriverAddDevice = UsbHubAddDevice;
gUsbHubDriver.MajorFunction[IRP_MJ_PNP] = UsbHubDispatchPnp;
return IoRegisterDriverObject(&gUsbHubDriver);
}