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

477 lines
19 KiB
C

// USB core helpers. This file owns common descriptor, string, and request utilities.
#include "usb.h"
KSTATUS XhciSetConfiguration(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev,
uint8_t configurationValue) {
USB_SETUP_PACKET setup;
setup.bmRequestType = USB_DIR_OUT;
setup.bRequest = USB_REQ_SET_CONFIGURATION;
setup.wValue = configurationValue;
setup.wIndex = 0;
setup.wLength = 0;
DbgPrint("usb-stage: slot=%u SET_CONFIGURATION begin value=%u\n",
dev->SlotId, configurationValue);
KSTATUS st = XhciControlNoData(xhci, dev, &setup);
if (!KSUCCESS(st)) {
DbgPrint("usb: SET_CONFIGURATION slot=%u value=%u failed st=0x%x\n",
dev->SlotId, configurationValue, st);
return st;
}
dev->ConfigurationValue = configurationValue;
dev->Configured = true;
DbgPrint("usb-stage: slot=%u SET_CONFIGURATION done value=%u\n",
dev->SlotId, configurationValue);
UsbTrace("usb: SET_CONFIGURATION slot=%u value=%u complete\n",
dev->SlotId, configurationValue);
return NTLSTATUS_SUCCESS;
}
// Returns tracked Interface By Number.
static USB_TRACKED_INTERFACE* UsbFindTrackedInterfaceByNumber(XHCI_DEVICE* dev, uint8_t interfaceNumber) {
if (!dev) return (USB_TRACKED_INTERFACE*)0;
for (uint8_t i = 0; i < dev->InterfaceCount && i < USB_MAX_TRACKED_INTERFACES; i++) {
if (dev->Interfaces[i].Present &&
dev->Interfaces[i].Descriptor.bInterfaceNumber == interfaceNumber)
return &dev->Interfaces[i];
}
return (USB_TRACKED_INTERFACE*)0;
}
void XhciLogConfigurationTree(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev,
const USB_CONFIGURATION_DESCRIPTOR* config,
uint16_t totalLength) {
(void)xhci;
memset(dev->Interfaces, 0, sizeof(dev->Interfaces));
dev->InterfaceCount = 0;
UsbTrace("usb: config slot=%u value=%u total=%u interfaces=%u attrs=%02x maxpower=%umA\n",
dev->SlotId,
config->bConfigurationValue,
totalLength,
config->bNumInterfaces,
config->bmAttributes,
(uint32_t)config->bMaxPower * 2u);
uint16_t off = config->bLength;
uint8_t currentInterface = 0xFFu;
uint8_t currentClass = 0;
uint8_t currentSubClass = 0;
uint8_t currentProtocol = 0;
bool hidCandidate = false;
bool massCandidate = false;
uint8_t hidInterruptIn = 0;
uint8_t massBulkIn = 0;
uint8_t massBulkOut = 0;
USB_TRACKED_INTERFACE* tracked = (USB_TRACKED_INTERFACE*)0;
while (off + 2u <= totalLength) {
const uint8_t* desc = dev->DescriptorBuffer + off;
uint8_t len = desc[0];
uint8_t type = desc[1];
if (len < 2 || off + len > totalLength) {
DbgPrint("usb: descriptor parse stopped slot=%u off=%u len=%u type=%u total=%u\n",
dev->SlotId, off, len, type, totalLength);
break;
}
if (type == USB_DESC_INTERFACE && len >= sizeof(USB_INTERFACE_DESCRIPTOR)) {
const USB_INTERFACE_DESCRIPTOR* iface = (const USB_INTERFACE_DESCRIPTOR*)desc;
currentInterface = iface->bInterfaceNumber;
currentClass = iface->bInterfaceClass;
currentSubClass = iface->bInterfaceSubClass;
currentProtocol = iface->bInterfaceProtocol;
hidCandidate = (currentClass == USB_CLASS_HID);
massCandidate = (currentClass == USB_CLASS_MASS_STORAGE &&
currentSubClass == USB_MASS_SUBCLASS_SCSI &&
currentProtocol == USB_MASS_PROTOCOL_BOT);
hidInterruptIn = 0;
massBulkIn = 0;
massBulkOut = 0;
tracked = (USB_TRACKED_INTERFACE*)0;
if (iface->bAlternateSetting == 0) {
USB_TRACKED_INTERFACE* existing = UsbFindTrackedInterfaceByNumber(dev, iface->bInterfaceNumber);
if (existing) {
tracked = existing;
memset(tracked->Endpoints, 0, sizeof(tracked->Endpoints));
tracked->EndpointCount = 0;
memcpy(&tracked->Descriptor, iface, sizeof(*iface));
UsbTrace("usb: slot=%u duplicate alt0 interface %u replaced existing PDO candidate\n",
dev->SlotId, iface->bInterfaceNumber);
} else if (dev->InterfaceCount < USB_MAX_TRACKED_INTERFACES) {
tracked = &dev->Interfaces[dev->InterfaceCount++];
memset(tracked, 0, sizeof(*tracked));
tracked->Present = true;
memcpy(&tracked->Descriptor, iface, sizeof(*iface));
} else {
DbgPrint("usb: slot=%u too many interfaces, interface %u alt=0 not published\n",
dev->SlotId, iface->bInterfaceNumber);
}
} else {
// Alternate settings are not independent USB interfaces. Real
// UVC cameras often expose one VideoStreaming interface with
// many alternate settings, and publishing each alternate setting
// as a separate PDO causes duplicate interface devices and can
// exhaust USB_MAX_TRACKED_INTERFACES. Track only alternate
// setting zero until a function driver explicitly selects a
// different setting.
tracked = (USB_TRACKED_INTERFACE*)0;
}
UsbTrace("usb: interface slot=%u if=%u alt=%u eps=%u class=%02x subclass=%02x proto=%02x (%s)%s\n",
dev->SlotId,
iface->bInterfaceNumber,
iface->bAlternateSetting,
iface->bNumEndpoints,
currentClass,
currentSubClass,
currentProtocol,
UsbInterfaceClassName(currentClass, currentSubClass, currentProtocol),
iface->bAlternateSetting ? " alt-not-published" : "");
} else if (type == USB_DESC_ENDPOINT && len >= sizeof(USB_ENDPOINT_DESCRIPTOR)) {
const USB_ENDPOINT_DESCRIPTOR* ep = (const USB_ENDPOINT_DESCRIPTOR*)desc;
uint8_t epType = ep->bmAttributes & USB_ENDPOINT_ATTR_TYPE_MASK;
uint8_t epNumber = ep->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
bool in = (ep->bEndpointAddress & USB_ENDPOINT_DIR_IN) != 0;
if (tracked && tracked->EndpointCount < USB_MAX_TRACKED_ENDPOINTS) {
memcpy(&tracked->Endpoints[tracked->EndpointCount++], ep, sizeof(*ep));
} else if (tracked) {
DbgPrint("usb: slot=%u if=%u too many endpoints, endpoint %02x not published\n",
dev->SlotId, currentInterface, ep->bEndpointAddress);
}
UsbTrace("usb: endpoint slot=%u if=%u addr=%02x ep=%u dir=%s type=%s maxpkt=%u interval=%u\n",
dev->SlotId,
currentInterface,
ep->bEndpointAddress,
epNumber,
UsbDirectionName(ep->bEndpointAddress),
UsbEndpointTypeName(epType),
ep->wMaxPacketSize,
ep->bInterval);
if (hidCandidate && in && epType == USB_ENDPOINT_TYPE_INTERRUPT && hidInterruptIn == 0) {
hidInterruptIn = ep->bEndpointAddress;
}
if (massCandidate && epType == USB_ENDPOINT_TYPE_BULK) {
if (in && massBulkIn == 0) massBulkIn = ep->bEndpointAddress;
if (!in && massBulkOut == 0) massBulkOut = ep->bEndpointAddress;
}
} else if (type == USB_DESC_HID) {
UsbTrace("usb: hid descriptor slot=%u if=%u len=%u\n",
dev->SlotId, currentInterface, len);
} else if (type == USB_DESC_CS_INTERFACE) {
UsbTrace("usb: class-specific interface descriptor slot=%u if=%u len=%u subtype=%u\n",
dev->SlotId, currentInterface, len, len >= 3 ? desc[2] : 0);
}
uint16_t next = (uint16_t)(off + len);
if (hidCandidate && hidInterruptIn &&
(next >= totalLength || (next + 1u < totalLength && dev->DescriptorBuffer[next + 1u] == USB_DESC_INTERFACE))) {
UsbTrace("usb: class candidate slot=%u if=%u hid interrupt-in=%02x subclass=%02x proto=%02x\n",
dev->SlotId,
currentInterface,
hidInterruptIn,
currentSubClass,
currentProtocol);
hidCandidate = false;
}
if (massCandidate && massBulkIn && massBulkOut &&
(next >= totalLength || (next + 1u < totalLength && dev->DescriptorBuffer[next + 1u] == USB_DESC_INTERFACE))) {
UsbTrace("usb: class candidate slot=%u if=%u mass-storage bulk-in=%02x bulk-out=%02x\n",
dev->SlotId,
currentInterface,
massBulkIn,
massBulkOut);
massCandidate = false;
}
off = next;
}
if (hidCandidate && hidInterruptIn) {
UsbTrace("usb: class candidate slot=%u if=%u hid interrupt-in=%02x subclass=%02x proto=%02x\n",
dev->SlotId,
currentInterface,
hidInterruptIn,
currentSubClass,
currentProtocol);
}
if (massCandidate && massBulkIn && massBulkOut) {
UsbTrace("usb: class candidate slot=%u if=%u mass-storage bulk-in=%02x bulk-out=%02x\n",
dev->SlotId,
currentInterface,
massBulkIn,
massBulkOut);
}
}
KSTATUS XhciDiscoverAndSetConfiguration(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev,
const USB_DEVICE_DESCRIPTOR* devDesc) {
if (devDesc->bNumConfigurations == 0) {
DbgPrint("usb: slot=%u has no configurations\n", dev->SlotId);
return NTLSTATUS_NOT_FOUND;
}
USB_CONFIGURATION_DESCRIPTOR config;
memset(&config, 0, sizeof(config));
uint16_t totalLength = 0;
DbgPrint("usb-stage: slot=%u GET_CONFIGURATION begin\n", dev->SlotId);
KSTATUS st = XhciGetConfigurationDescriptor(xhci, dev, 0, &config, &totalLength);
if (!KSUCCESS(st)) {
DbgPrint("usb: slot=%u configuration descriptor read failed st=0x%x\n",
dev->SlotId, st);
return st;
}
DbgPrint("usb-stage: slot=%u GET_CONFIGURATION done total=%u value=%u\n",
dev->SlotId, totalLength, config.bConfigurationValue);
XhciLogConfigurationTree(xhci, dev, &config, totalLength);
if (config.bConfigurationValue == 0) {
DbgPrint("usb: slot=%u configuration value 0 is invalid for SET_CONFIGURATION\n",
dev->SlotId);
return NTLSTATUS_DEVICE_ERROR;
}
st = XhciSetConfiguration(xhci, dev, config.bConfigurationValue);
if (!KSUCCESS(st)) return st;
if (!dev->UsbPdo) {
DbgPrint("usb-stage: slot=%u PDO creation begin\n", dev->SlotId);
st = UsbCreateDevicePdo(&xhci->Hcd, dev,
dev->ParentDeviceObject ? dev->ParentDeviceObject :
(xhci->Hcd.RootHubPdo ? xhci->Hcd.RootHubPdo : xhci->Hcd.ControllerDevice));
if (!KSUCCESS(st)) {
DbgPrint("usb: create device PDO slot=%u failed st=0x%x\n", dev->SlotId, st);
return st;
}
DbgPrint("usb-stage: slot=%u PDO creation done\n", dev->SlotId);
}
return NTLSTATUS_SUCCESS;
}
// Implements the rollback Enumerated Slot operation.
static void XhciRollbackEnumeratedSlot(XHCI_CONTROLLER* xhci, uint8_t slotId) {
if (!xhci || slotId == 0 || slotId > XHCI_MAX_TRACKED_SLOTS)
return;
XHCI_DEVICE* dev = &xhci->Devices[slotId];
if (dev->Allocated && xhci->Running)
(void)XhciDisableSlot(xhci, slotId);
if (dev->UsbPdo) {
(void)UsbRemovePdoSubtree(dev->UsbPdo, true);
dev->UsbPdo = (DEVICE_OBJECT*)0;
}
if (dev->UsbDevice) {
dev->UsbDevice->State = UsbDeviceRemoved;
UsbDereferenceDevice(dev->UsbDevice);
dev->UsbDevice = (USB_DEVICE*)0;
}
XhciReleaseDeviceDma(dev);
memset(dev, 0, sizeof(*dev));
}
static KSTATUS XhciEnumeratePathEx(XHCI_CONTROLLER* xhci,
uint8_t logicalPort,
uint8_t speed,
const USB_PORT_PATH* path,
DEVICE_OBJECT* parentDevice,
uint8_t* outSlotId) {
if (outSlotId) *outSlotId = 0;
if (!xhci || !path || path->RootPort == 0)
return NTLSTATUS_INVALID_PARAMETER;
DbgPrint("usb-stage: hcd=%02x:%02x.%u root=%u hub-slot=%u hub-port=%u enable-slot begin\n",
xhci->Bus, xhci->Dev, xhci->Fn, path->RootPort,
path->BehindHub ? path->ParentHubSlot : 0,
path->BehindHub ? path->ParentHubPort : 0);
uint8_t slotId = 0;
KSTATUS st = XhciEnableSlot(xhci, &slotId);
DbgPrint("usb-stage: hcd=%02x:%02x.%u enable-slot done st=0x%x slot=%u\n",
xhci->Bus, xhci->Dev, xhci->Fn, st, slotId);
if (!KSUCCESS(st)) return st;
XHCI_DEVICE* dev = &xhci->Devices[slotId];
memset(dev, 0, sizeof(*dev));
dev->Allocated = true;
dev->SlotId = slotId;
dev->Port = path->RootPort;
dev->Speed = speed;
dev->Path = *path;
dev->ParentDeviceObject = parentDevice;
st = XhciAllocateDeviceMemory(xhci, dev);
if (!KSUCCESS(st)) {
XhciRollbackEnumeratedSlot(xhci, slotId);
return st;
}
XhciBuildDefaultControlContext(xhci, dev);
DbgPrint("usb-stage: hcd=%02x:%02x.%u slot=%u address-device begin speed=%u route=%05x\n",
xhci->Bus, xhci->Dev, xhci->Fn, slotId, speed, path->RouteString);
st = XhciAddressDevice(xhci, dev);
DbgPrint("usb-stage: hcd=%02x:%02x.%u slot=%u address-device done st=0x%x\n",
xhci->Bus, xhci->Dev, xhci->Fn, slotId, st);
if (!KSUCCESS(st)) {
XhciRollbackEnumeratedSlot(xhci, slotId);
return st;
}
dev->Addressed = true;
USB_DEVICE_DESCRIPTOR desc;
memset(&desc, 0, sizeof(desc));
DbgPrint("usb-stage: hcd=%02x:%02x.%u slot=%u get-device-descriptor begin\n",
xhci->Bus, xhci->Dev, xhci->Fn, slotId);
st = XhciGetDeviceDescriptorTimeout(xhci, dev, &desc, USB_XHCI_ENUM_CONTROL_TIMEOUT_US);
DbgPrint("usb-stage: hcd=%02x:%02x.%u slot=%u get-device-descriptor done st=0x%x\n",
xhci->Bus, xhci->Dev, xhci->Fn, slotId, st);
if (!KSUCCESS(st)) {
DbgPrint("usb: port %u slot %u descriptor read failed st=0x%x\n",
logicalPort, slotId, st);
XhciRollbackEnumeratedSlot(xhci, slotId);
return st;
}
dev->MaxPacketSize0 = desc.bMaxPacketSize0;
dev->VendorId = desc.idVendor;
dev->ProductId = desc.idProduct;
dev->BcdUsb = desc.bcdUSB;
dev->BcdDevice = desc.bcdDevice;
dev->DeviceClass = desc.bDeviceClass;
dev->DeviceSubclass = desc.bDeviceSubClass;
dev->DeviceProtocol = desc.bDeviceProtocol;
if (path->BehindHub) {
DbgPrint("usb: device root-port=%u hub-slot=%u hub-port=%u slot=%u speed=%s(%u) vid=%04x pid=%04x class=%02x subclass=%02x proto=%02x mps0=%u configs=%u usb=%04x route=%05x\n",
path->RootPort,
path->ParentHubSlot,
path->ParentHubPort,
slotId,
XhciPortSpeedName(speed),
speed,
desc.idVendor,
desc.idProduct,
desc.bDeviceClass,
desc.bDeviceSubClass,
desc.bDeviceProtocol,
desc.bMaxPacketSize0,
desc.bNumConfigurations,
desc.bcdUSB,
path->RouteString);
} else {
DbgPrint("usb: device port=%u slot=%u speed=%s(%u) vid=%04x pid=%04x class=%02x subclass=%02x proto=%02x mps0=%u configs=%u usb=%04x\n",
logicalPort,
slotId,
XhciPortSpeedName(speed),
speed,
desc.idVendor,
desc.idProduct,
desc.bDeviceClass,
desc.bDeviceSubClass,
desc.bDeviceProtocol,
desc.bMaxPacketSize0,
desc.bNumConfigurations,
desc.bcdUSB);
}
st = XhciDiscoverAndSetConfiguration(xhci, dev, &desc);
if (!KSUCCESS(st)) {
XhciRollbackEnumeratedSlot(xhci, slotId);
return st;
}
if (outSlotId) *outSlotId = slotId;
return NTLSTATUS_SUCCESS;
}
// Implements the enumerate Port Ex operation.
KSTATUS XhciEnumeratePortEx(XHCI_CONTROLLER* xhci, uint8_t port, uint8_t* outSlotId) {
if (outSlotId) *outSlotId = 0;
uint8_t speed = 0;
KSTATUS st = XhciResetPort(xhci, port, &speed);
if (!KSUCCESS(st)) return st;
USB_PORT_PATH path;
memset(&path, 0, sizeof(path));
path.RootPort = port;
path.Depth = 0;
path.RouteString = 0;
path.BehindHub = false;
DEVICE_OBJECT* parent = xhci && xhci->Hcd.RootHubPdo ? xhci->Hcd.RootHubPdo : (DEVICE_OBJECT*)0;
return XhciEnumeratePathEx(xhci, port, speed, &path, parent, outSlotId);
}
KSTATUS XhciEnumerateHubPortEx(XHCI_CONTROLLER* xhci,
USB_HUB_EXTENSION* hub,
uint8_t hubPort,
uint8_t speed,
uint8_t* outSlotId) {
if (outSlotId) *outSlotId = 0;
if (!xhci || !hub || !hub->Device || !hub->Self || hubPort == 0)
return NTLSTATUS_INVALID_PARAMETER;
USB_DEVICE* parent = hub->Device;
if (parent->Path.Depth >= 5u) {
DbgPrint("usb-hub: hub slot=%u port=%u exceeds xHCI route depth\n",
parent->SlotId, hubPort);
return NTLSTATUS_NOT_SUPPORTED;
}
if (hubPort > 15u) {
DbgPrint("usb-hub: hub slot=%u port=%u cannot be represented in xHCI route string yet\n",
parent->SlotId, hubPort);
return NTLSTATUS_NOT_SUPPORTED;
}
USB_PORT_PATH path = parent->Path;
path.BehindHub = true;
path.ParentHubSlot = parent->SlotId;
path.ParentHubPort = hubPort;
path.RouteString |= ((uint32_t)hubPort << (path.Depth * 4u));
path.Depth++;
return XhciEnumeratePathEx(xhci, hubPort, speed, &path, hub->Self, outSlotId);
}
// Implements the enumerate Port operation.
KSTATUS XhciEnumeratePort(XHCI_CONTROLLER* xhci, uint8_t port) {
return XhciEnumeratePortEx(xhci, port, (uint8_t*)0);
}
// Implements the enumerate Root Ports operation.
void XhciEnumerateRootPorts(XHCI_CONTROLLER* xhci) {
uint32_t attempted = 0;
uint32_t configured = 0;
for (uint8_t port = 1; port <= xhci->MaxPorts; port++) {
uint32_t portsc = XhciRead32(xhci->Op, XhciPortReg(port));
if (!(portsc & XHCI_PORTSC_CCS))
continue;
attempted++;
uint8_t slot = 0;
KSTATUS st = XhciEnumeratePortEx(xhci, port, &slot);
if (KSUCCESS(st)) configured++;
else DbgPrint("usb: root-port %u enumeration failed st=0x%x\n", port, st);
}
DbgPrint("usb: xHCI root-port enumeration complete; attempted=%u configured=%u\n",
attempted, configured);
}