// USB mass-storage driver. This file owns bulk-only transport and storage-device exposure. // The storage driver now binds as a USB function driver to an interface PDO // (USB\CLASS_08&SUBCLASS_06&PROT_50). It creates a disk FDO above that // interface stack and issues BOT transfers through USB URBs. The xHCI HCD // still backs the bulk URBs with the existing per-device rings; later stages // will make endpoint rings fully pipe-owned and asynchronous. #include #include #include #include "usb.h" DRIVER_OBJECT gUsbStorageDriver; static const IO_DEVICE_ID gUsbStorageIds[] = { { IoBusUsb, { "USB\\CLASS_08&SUBCLASS_06&PROT_50", sizeof("USB\\CLASS_08&SUBCLASS_06&PROT_50") - 1 } }, }; static const IO_DRIVER_ID_TABLE gUsbStorageIdTable = { gUsbStorageIds, sizeof(gUsbStorageIds) / sizeof(gUsbStorageIds[0]), }; // Implements the storage From Disk Device operation. static USB_STORAGE_EXTENSION* UsbStorageFromDiskDevice(DEVICE_OBJECT* device) { if (!device || !device->DeviceExtension) return (USB_STORAGE_EXTENSION*)0; USB_STORAGE_DISK_EXTENSION* diskExt = (USB_STORAGE_DISK_EXTENSION*)device->DeviceExtension; if (diskExt->Kind != UsbStorageExtensionDisk) return (USB_STORAGE_EXTENSION*)0; return diskExt->Storage; } // Implements the storage From Interface Device operation. static USB_STORAGE_EXTENSION* UsbStorageFromInterfaceDevice(DEVICE_OBJECT* device) { if (!device || !device->DeviceExtension) return (USB_STORAGE_EXTENSION*)0; USB_STORAGE_EXTENSION* ext = (USB_STORAGE_EXTENSION*)device->DeviceExtension; if (ext->Kind != UsbStorageExtensionInterface) return (USB_STORAGE_EXTENSION*)0; return ext; } // Implements the storage Find Bulk Endpoint operation. static const USB_ENDPOINT_DESCRIPTOR* UsbStorageFindBulkEndpoint(const USB_PDO_EXTENSION* pdoExt, bool wantIn) { if (!pdoExt) return (const USB_ENDPOINT_DESCRIPTOR*)0; for (uint8_t i = 0; i < pdoExt->EndpointCount; i++) { const USB_ENDPOINT_DESCRIPTOR* ep = &pdoExt->Endpoints[i]; uint8_t type = ep->bmAttributes & USB_ENDPOINT_ATTR_TYPE_MASK; bool in = (ep->bEndpointAddress & USB_ENDPOINT_DIR_IN) != 0; if (type == USB_ENDPOINT_TYPE_BULK && in == wantIn) return ep; } return (const USB_ENDPOINT_DESCRIPTOR*)0; } static KSTATUS UsbStorageSubmitBulk(USB_STORAGE_EXTENSION* storage, bool dataIn, uint64_t deviceAddress, uint32_t length, uint32_t* outActual) { if (!storage || !storage->LowerDevice || !storage->Device || !deviceAddress || length == 0) return NTLSTATUS_INVALID_PARAMETER; USB_PIPE* pipe = dataIn ? storage->BulkInPipe : storage->BulkOutPipe; if (!pipe || !pipe->Open) return NTLSTATUS_OBJECT_DEAD; USB_URB urb; memset(&urb, 0, sizeof(urb)); urb.Function = UrbFunctionBulkTransfer; urb.Direction = dataIn ? UsbTransferIn : UsbTransferOut; urb.Device = storage->Device; urb.Pipe = pipe; urb.Buffer = (void*)0; urb.BufferDeviceAddress = deviceAddress; urb.Length = length; KSTATUS st = UsbSubmitUrbSync(storage->LowerDevice, &urb); if (outActual) *outActual = KSUCCESS(st) ? urb.ActualLength : 0; return st; } KSTATUS UsbStorageBulkIn(USB_STORAGE_EXTENSION* storage, uint64_t deviceAddress, uint32_t length, uint32_t* outActual) { return UsbStorageSubmitBulk(storage, true, deviceAddress, length, outActual); } KSTATUS UsbStorageBulkOut(USB_STORAGE_EXTENSION* storage, uint64_t deviceAddress, uint32_t length, uint32_t* outActual) { return UsbStorageSubmitBulk(storage, false, deviceAddress, length, outActual); } typedef struct USB_STORAGE_SENSE_DATA { bool Valid; uint8_t ResponseCode; uint8_t Key; uint8_t Asc; uint8_t Ascq; } USB_STORAGE_SENSE_DATA; // Executes one BOT command without issuing follow-up commands. static KSTATUS UsbStorageBotCommandRaw(USB_STORAGE_EXTENSION* storage, const uint8_t* cdb, uint8_t cdbLen, void* data, uint64_t dataDeviceAddress, uint32_t dataLength, bool dataIn, uint8_t* outCswStatus, uint32_t* outResidue) { if (outCswStatus) *outCswStatus = 0xFFu; if (outResidue) *outResidue = 0; if (!storage || !storage->XhciDevice || !cdb || cdbLen == 0 || cdbLen > 16) return NTLSTATUS_INVALID_PARAMETER; if (dataLength && (!data || !dataDeviceAddress)) return NTLSTATUS_INVALID_PARAMETER; XHCI_DEVICE* dev = storage->XhciDevice; USB_MASS_CBW* cbw = (USB_MASS_CBW*)dev->DescriptorBuffer; memset(cbw, 0, sizeof(*cbw)); cbw->Signature = USB_MASS_BOT_CBW_SIGNATURE; cbw->Tag = ++storage->Tag; cbw->DataTransferLength = dataLength; cbw->Flags = dataIn ? USB_DIR_IN : USB_DIR_OUT; cbw->Lun = 0; cbw->CommandBlockLength = cdbLen; memcpy(cbw->CommandBlock, cdb, cdbLen); uint32_t actual = 0; KSTATUS st = UsbStorageBulkOut(storage, dev->DescriptorDeviceAddress, sizeof(*cbw), &actual); if (!KSUCCESS(st) || actual != sizeof(*cbw)) return KSUCCESS(st) ? NTLSTATUS_DEVICE_ERROR : st; if (dataLength) { actual = 0; st = dataIn ? UsbStorageBulkIn(storage, dataDeviceAddress, dataLength, &actual) : UsbStorageBulkOut(storage, dataDeviceAddress, dataLength, &actual); if (!KSUCCESS(st)) return st; } USB_MASS_CSW* csw = (USB_MASS_CSW*)dev->DescriptorBuffer; memset(csw, 0, sizeof(*csw)); actual = 0; st = UsbStorageBulkIn(storage, dev->DescriptorDeviceAddress, sizeof(*csw), &actual); if (!KSUCCESS(st) || actual != sizeof(*csw)) return KSUCCESS(st) ? NTLSTATUS_DEVICE_ERROR : st; if (csw->Signature != USB_MASS_BOT_CSW_SIGNATURE || csw->Tag != storage->Tag) { DbgPrint("usb-storage: invalid CSW slot=%u sig=%08x tag=%08x expected=%08x\n", dev->SlotId, csw->Signature, csw->Tag, storage->Tag); return NTLSTATUS_DEVICE_ERROR; } if (outCswStatus) *outCswStatus = csw->Status; if (outResidue) *outResidue = csw->DataResidue; return csw->Status == USB_MASS_BOT_CSW_PASSED ? NTLSTATUS_SUCCESS : NTLSTATUS_DEVICE_ERROR; } // Returns a readable SCSI sense-key name. static const char* UsbStorageSenseKeyName(uint8_t key) { switch (key) { case 0x00u: return "NO SENSE"; case 0x01u: return "RECOVERED ERROR"; case 0x02u: return "NOT READY"; case 0x03u: return "MEDIUM ERROR"; case 0x04u: return "HARDWARE ERROR"; case 0x05u: return "ILLEGAL REQUEST"; case 0x06u: return "UNIT ATTENTION"; case 0x07u: return "DATA PROTECT"; case 0x08u: return "BLANK CHECK"; case 0x0Bu: return "ABORTED COMMAND"; case 0x0Eu: return "MISCOMPARE"; default: return "UNKNOWN"; } } // Decodes fixed or descriptor-format REQUEST SENSE data. static void UsbStorageDecodeSense(const uint8_t* bytes, uint32_t length, USB_STORAGE_SENSE_DATA* sense) { if (!sense) return; memset(sense, 0, sizeof(*sense)); if (!bytes || length == 0) return; sense->ResponseCode = bytes[0] & 0x7Fu; if ((sense->ResponseCode == 0x70u || sense->ResponseCode == 0x71u) && length >= 14u) { sense->Key = bytes[2] & 0x0Fu; sense->Asc = bytes[12]; sense->Ascq = bytes[13]; sense->Valid = true; return; } if ((sense->ResponseCode == 0x72u || sense->ResponseCode == 0x73u) && length >= 4u) { sense->Key = bytes[1] & 0x0Fu; sense->Asc = bytes[2]; sense->Ascq = bytes[3]; sense->Valid = true; } } // Fetches and decodes sense data after a command failure. static KSTATUS UsbStorageFetchSense(USB_STORAGE_EXTENSION* storage, USB_STORAGE_SENSE_DATA* sense) { if (!storage || !storage->XhciDevice || !sense) return NTLSTATUS_INVALID_PARAMETER; XHCI_DEVICE* dev = storage->XhciDevice; uint8_t cdb[6] = { SCSI_REQUEST_SENSE, 0, 0, 0, 18, 0 }; memset(dev->MassBuffer, 0, 18); uint8_t cswStatus = 0xFFu; uint32_t residue = 0; KSTATUS st = UsbStorageBotCommandRaw(storage, cdb, sizeof(cdb), dev->MassBuffer, dev->MassBufferDeviceAddress, 18, true, &cswStatus, &residue); if (!KSUCCESS(st)) { DbgPrint("usb-storage: REQUEST SENSE failed slot=%u st=0x%x csw_status=%u residue=%u\n", dev->SlotId, st, cswStatus, residue); memset(sense, 0, sizeof(*sense)); return st; } uint32_t returned = residue < 18u ? 18u - residue : 0u; UsbStorageDecodeSense(dev->MassBuffer, returned, sense); return NTLSTATUS_SUCCESS; } // Executes one BOT command and reports SCSI sense data on command failure. static KSTATUS UsbStorageExecuteCommand(USB_STORAGE_EXTENSION* storage, const uint8_t* cdb, uint8_t cdbLen, void* data, uint64_t dataDeviceAddress, uint32_t dataLength, bool dataIn, USB_STORAGE_SENSE_DATA* outSense) { if (outSense) memset(outSense, 0, sizeof(*outSense)); uint8_t cswStatus = 0xFFu; uint32_t residue = 0; KSTATUS st = UsbStorageBotCommandRaw(storage, cdb, cdbLen, data, dataDeviceAddress, dataLength, dataIn, &cswStatus, &residue); if (KSUCCESS(st)) return st; XHCI_DEVICE* dev = storage ? storage->XhciDevice : (XHCI_DEVICE*)0; uint32_t slot = dev ? dev->SlotId : 0; if (cswStatus != USB_MASS_BOT_CSW_FAILED || !cdb || cdb[0] == SCSI_REQUEST_SENSE) { DbgPrint("usb-storage: command %02x failed slot=%u st=0x%x csw_status=%u residue=%u\n", cdb ? cdb[0] : 0, slot, st, cswStatus, residue); return st; } USB_STORAGE_SENSE_DATA sense; KSTATUS senseStatus = UsbStorageFetchSense(storage, &sense); if (KSUCCESS(senseStatus) && sense.Valid) { DbgPrint("usb-storage: command %02x failed slot=%u csw_status=%u residue=%u sense=%s key=%02x asc=%02x ascq=%02x\n", cdb[0], slot, cswStatus, residue, UsbStorageSenseKeyName(sense.Key), sense.Key, sense.Asc, sense.Ascq); if (outSense) *outSense = sense; } else { DbgPrint("usb-storage: command %02x failed slot=%u csw_status=%u residue=%u sense=unavailable\n", cdb[0], slot, cswStatus, residue); } return st; } // Executes one BOT command through the sense-reporting path. KSTATUS UsbStorageBotCommand(USB_STORAGE_EXTENSION* storage, const uint8_t* cdb, uint8_t cdbLen, void* data, uint64_t dataDeviceAddress, uint32_t dataLength, bool dataIn) { return UsbStorageExecuteCommand(storage, cdb, cdbLen, data, dataDeviceAddress, dataLength, dataIn, (USB_STORAGE_SENSE_DATA*)0); } // Implements the be32 operation. uint32_t UsbBe32(const uint8_t* p) { return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | (uint32_t)p[3]; } // Updates be32. void UsbStoreBe32(uint8_t* p, uint32_t v) { p[0] = (uint8_t)(v >> 24); p[1] = (uint8_t)(v >> 16); p[2] = (uint8_t)(v >> 8); p[3] = (uint8_t)v; } // Updates be16. void UsbStoreBe16(uint8_t* p, uint16_t v) { p[0] = (uint8_t)(v >> 8); p[1] = (uint8_t)v; } // Implements the storage Inquiry operation. KSTATUS UsbStorageInquiry(USB_STORAGE_EXTENSION* storage) { if (!storage || !storage->XhciDevice) return NTLSTATUS_INVALID_PARAMETER; XHCI_DEVICE* dev = storage->XhciDevice; uint8_t cdb[6] = { SCSI_INQUIRY, 0, 0, 0, 36, 0 }; memset(dev->MassBuffer, 0, 36); KSTATUS st = UsbStorageBotCommand(storage, cdb, sizeof(cdb), dev->MassBuffer, dev->MassBufferDeviceAddress, 36, true); if (!KSUCCESS(st)) return st; char vendor[9]; char product[17]; memcpy(vendor, dev->MassBuffer + 8, 8); vendor[8] = 0; memcpy(product, dev->MassBuffer + 16, 16); product[16] = 0; DbgPrint("usb-storage: INQUIRY slot=%u vendor='%s' product='%s'\n", dev->SlotId, vendor, product); return NTLSTATUS_SUCCESS; } // Implements the storage Test Unit Ready operation. KSTATUS UsbStorageTestUnitReady(USB_STORAGE_EXTENSION* storage) { if (!storage || !storage->XhciDevice) return NTLSTATUS_INVALID_PARAMETER; uint8_t cdb[6] = { SCSI_TEST_UNIT_READY, 0, 0, 0, 0, 0 }; KSTATUS last = NTLSTATUS_DEVICE_ERROR; XHCI_CONTROLLER* xhci = (XHCI_CONTROLLER*)storage->Device->Hcd->ControllerContext; for (uint32_t attempt = 0; attempt < 10; attempt++) { KSTATUS st = UsbStorageBotCommand(storage, cdb, sizeof(cdb), (void*)0, 0, 0, false); if (KSUCCESS(st)) return NTLSTATUS_SUCCESS; last = st; UsbDelayUs(10000u); } return last; } // Implements the storage Read Capacity operation. KSTATUS UsbStorageReadCapacity(USB_STORAGE_EXTENSION* storage) { if (!storage || !storage->XhciDevice) return NTLSTATUS_INVALID_PARAMETER; XHCI_DEVICE* dev = storage->XhciDevice; uint8_t cdb[10] = { SCSI_READ_CAPACITY_10, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; memset(dev->MassBuffer, 0, 8); KSTATUS st = UsbStorageBotCommand(storage, cdb, sizeof(cdb), dev->MassBuffer, dev->MassBufferDeviceAddress, 8, true); if (!KSUCCESS(st)) return st; uint32_t lastLba = UsbBe32(dev->MassBuffer + 0); uint32_t blockSize = UsbBe32(dev->MassBuffer + 4); if (blockSize == 0) return NTLSTATUS_DEVICE_ERROR; storage->BlockSize = blockSize; storage->BlockCount = (uint64_t)lastLba + 1ull; dev->MassBlockSize = blockSize; dev->MassBlockCount = storage->BlockCount; DbgPrint("usb-storage: capacity slot=%u blocks=%llu block_size=%u (%llu MB)\n", dev->SlotId, (unsigned long long)storage->BlockCount, storage->BlockSize, (unsigned long long)((storage->BlockCount * (uint64_t)storage->BlockSize) / (1024ull * 1024ull))); return NTLSTATUS_SUCCESS; } KSTATUS UsbStorageReadWriteSectors(USB_STORAGE_EXTENSION* storage, bool write, uint64_t lba, uint32_t sectors, void* buffer) { if (!storage || !storage->XhciDevice || !buffer || sectors == 0) return NTLSTATUS_INVALID_PARAMETER; if (storage->BlockSize != USB_STORAGE_SECTOR_SIZE) return NTLSTATUS_NOT_SUPPORTED; if (lba > 0xFFFFFFFFull || sectors > 0xFFFFu) return NTLSTATUS_INVALID_PARAMETER; XHCI_DEVICE* dev = storage->XhciDevice; uint32_t bytes = sectors * USB_STORAGE_SECTOR_SIZE; if (bytes > USB_STORAGE_MAX_BYTES) return NTLSTATUS_INVALID_PARAMETER; uint8_t cdb[10]; memset(cdb, 0, sizeof(cdb)); cdb[0] = write ? SCSI_WRITE_10 : SCSI_READ_10; UsbStoreBe32(&cdb[2], (uint32_t)lba); UsbStoreBe16(&cdb[7], (uint16_t)sectors); if (write) memcpy(dev->MassBuffer, buffer, bytes); KSTATUS st = UsbStorageBotCommand(storage, cdb, sizeof(cdb), dev->MassBuffer, dev->MassBufferDeviceAddress, bytes, !write); if (!KSUCCESS(st)) return st; if (!write) memcpy(buffer, dev->MassBuffer, bytes); return NTLSTATUS_SUCCESS; } // Returns whether sense data means SYNCHRONIZE CACHE is unsupported. static bool UsbStorageSenseRejectsSynchronizeCache(const USB_STORAGE_SENSE_DATA* sense) { if (!sense || !sense->Valid || sense->Key != 0x05u) return false; return sense->Asc == 0x20u || sense->Asc == 0x24u; } // Flushes device write caches through SCSI SYNCHRONIZE CACHE(10). static KSTATUS UsbStorageSynchronizeCache(USB_STORAGE_EXTENSION* storage) { if (!storage || !storage->XhciDevice) return NTLSTATUS_INVALID_PARAMETER; if (storage->FlushCapability == UsbStorageFlushUnsupported) return NTLSTATUS_SUCCESS; uint8_t cdb[10]; memset(cdb, 0, sizeof(cdb)); cdb[0] = SCSI_SYNCHRONIZE_CACHE_10; for (uint32_t attempt = 0; attempt < 2; attempt++) { USB_STORAGE_SENSE_DATA sense; KSTATUS st = UsbStorageExecuteCommand(storage, cdb, sizeof(cdb), (void*)0, 0, 0, false, &sense); if (KSUCCESS(st)) { storage->FlushCapability = UsbStorageFlushSupported; return NTLSTATUS_SUCCESS; } if (UsbStorageSenseRejectsSynchronizeCache(&sense)) { storage->FlushCapability = UsbStorageFlushUnsupported; DbgPrint("usb-storage: slot=%u does not support SYNCHRONIZE CACHE; flushes use BOT command-completion semantics only\n", storage->XhciDevice->SlotId); return NTLSTATUS_SUCCESS; } if (sense.Valid && sense.Key == 0x06u && attempt == 0) continue; return st; } return NTLSTATUS_DEVICE_ERROR; } // Implements the storage Acquire Io operation. void UsbStorageAcquireIo(USB_STORAGE_EXTENSION* storage) { if (!storage) return; XHCI_CONTROLLER* xhci = storage->Device ? (XHCI_CONTROLLER*)storage->Device->Hcd->ControllerContext : (XHCI_CONTROLLER*)0; while (NtlAtomicCompareExchange32(&storage->IoBusy, 1, 0) != 0) { if (xhci) UsbDelayUs(10); } } // Implements the storage Release Io operation. void UsbStorageReleaseIo(USB_STORAGE_EXTENSION* storage) { if (storage) NtlAtomicExchange32(&storage->IoBusy, 0); } // Implements the storage Dispatch Read Write operation. KSTATUS UsbStorageDispatchReadWrite(DEVICE_OBJECT* device, IRP* irp) { if (!device || !irp) return NTLSTATUS_INVALID_PARAMETER; USB_STORAGE_EXTENSION* storage = UsbStorageFromDiskDevice(device); if (!storage || !storage->RemoveLockReady) return NTLSTATUS_OBJECT_DEAD; KSTATUS st = IoAcquireRemoveLock(&storage->RemoveLock, irp); if (!KSUCCESS(st)) return st; IO_STACK_LOCATION* ios = IoGetCurrentIrpStackLocation(irp); if (!ios) { IoReleaseRemoveLock(&storage->RemoveLock, irp); return NTLSTATUS_INVALID_PARAMETER; } if (ios->MajorFunction != IRP_MJ_READ && ios->MajorFunction != IRP_MJ_WRITE) { IoReleaseRemoveLock(&storage->RemoveLock, irp); return NTLSTATUS_INVALID_PARAMETER; } if ((ios->Parameters.ReadWrite.ByteOffset & 511ull) || (ios->Parameters.ReadWrite.Length & 511u) || ios->Parameters.ReadWrite.Length == 0 || !irp->UserBuffer) { IoReleaseRemoveLock(&storage->RemoveLock, irp); return NTLSTATUS_INVALID_PARAMETER; } if (!storage->Started || storage->SurpriseRemoved || storage->Removed || storage->BlockSize != USB_STORAGE_SECTOR_SIZE) { IoReleaseRemoveLock(&storage->RemoveLock, irp); IoCompleteRequest(irp, NTLSTATUS_OBJECT_DEAD, 0); return NTLSTATUS_PENDING; } uint8_t* buffer = (uint8_t*)irp->UserBuffer; uint64_t lba = ios->Parameters.ReadWrite.ByteOffset >> 9; uint32_t remaining = ios->Parameters.ReadWrite.Length >> 9; uint32_t done = 0; bool write = (ios->MajorFunction == IRP_MJ_WRITE); uint32_t maxSectors = USB_STORAGE_MAX_BYTES / USB_STORAGE_SECTOR_SIZE; st = NTLSTATUS_SUCCESS; UsbStorageAcquireIo(storage); while (remaining) { uint32_t chunk = remaining; if (chunk > maxSectors) chunk = maxSectors; st = UsbStorageReadWriteSectors(storage, write, lba, chunk, buffer + (done * USB_STORAGE_SECTOR_SIZE)); if (!KSUCCESS(st)) break; lba += chunk; done += chunk; remaining -= chunk; } UsbStorageReleaseIo(storage); IoReleaseRemoveLock(&storage->RemoveLock, irp); IoCompleteRequest(irp, st, KSUCCESS(st) ? (size_t)done * USB_STORAGE_SECTOR_SIZE : 0); return NTLSTATUS_PENDING; } // Flushes one USB mass-storage disk synchronously. static KSTATUS UsbStorageDispatchFlush(DEVICE_OBJECT* device, IRP* irp) { if (!device || !irp) return NTLSTATUS_INVALID_PARAMETER; USB_STORAGE_EXTENSION* storage = UsbStorageFromDiskDevice(device); if (!storage || !storage->RemoveLockReady) return NTLSTATUS_OBJECT_DEAD; KSTATUS st = IoAcquireRemoveLock(&storage->RemoveLock, irp); if (!KSUCCESS(st)) return st; IO_STACK_LOCATION* ios = IoGetCurrentIrpStackLocation(irp); if (!ios || ios->MajorFunction != IRP_MJ_FLUSH_BUFFERS) { IoReleaseRemoveLock(&storage->RemoveLock, irp); return NTLSTATUS_INVALID_PARAMETER; } if (!storage->Started || storage->SurpriseRemoved || storage->Removed) { IoReleaseRemoveLock(&storage->RemoveLock, irp); IoCompleteRequest(irp, NTLSTATUS_OBJECT_DEAD, 0); return NTLSTATUS_PENDING; } UsbStorageAcquireIo(storage); st = UsbStorageSynchronizeCache(storage); UsbStorageReleaseIo(storage); IoReleaseRemoveLock(&storage->RemoveLock, irp); IoCompleteRequest(irp, st, 0); return NTLSTATUS_PENDING; } // Creates mass Storage Memory. KSTATUS XhciAllocateMassStorageMemory(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev) { if (dev->MassInRing && dev->MassOutRing && dev->MassBuffer) return NTLSTATUS_SUCCESS; dev->MassInRing = (XHCI_TRB*)UsbDmaAllocPages(xhci, 1, &dev->MassInRingDeviceAddress, &dev->MassInRingDma); dev->MassOutRing = (XHCI_TRB*)UsbDmaAllocPages(xhci, 1, &dev->MassOutRingDeviceAddress, &dev->MassOutRingDma); dev->MassBuffer = (uint8_t*)UsbDmaAllocPages(xhci, USB_STORAGE_DMA_PAGES, &dev->MassBufferDeviceAddress, &dev->MassBufferDma); if (!dev->MassInRing || !dev->MassOutRing || !dev->MassBuffer) { { IoDmaFreeCommonBuffer(dev->MassBufferDma); IoDmaFreeCommonBuffer(dev->MassOutRingDma); IoDmaFreeCommonBuffer(dev->MassInRingDma); } dev->MassBufferDma = (IO_DMA_BUFFER*)0; dev->MassOutRingDma = (IO_DMA_BUFFER*)0; dev->MassInRingDma = (IO_DMA_BUFFER*)0; dev->MassBuffer = (uint8_t*)0; dev->MassOutRing = (XHCI_TRB*)0; dev->MassInRing = (XHCI_TRB*)0; return NTLSTATUS_NO_MEMORY; } dev->MassInEnqueue = 0; dev->MassInCycle = 1; dev->MassOutEnqueue = 0; dev->MassOutCycle = 1; dev->MassTag = 0x1000u + dev->SlotId; XhciInitialiseTransferRing(dev->MassInRing, dev->MassInRingDeviceAddress, XHCI_BULK_RING_TRBS, dev->MassInCycle); XhciInitialiseTransferRing(dev->MassOutRing, dev->MassOutRingDeviceAddress, XHCI_BULK_RING_TRBS, dev->MassOutCycle); return NTLSTATUS_SUCCESS; } // Implements the configure Mass Storage Endpoints operation. KSTATUS XhciConfigureMassStorageEndpoints(XHCI_CONTROLLER* xhci, XHCI_DEVICE* dev) { if (!dev->MassCandidate) return NTLSTATUS_SUCCESS; KSTATUS st = XhciAllocateMassStorageMemory(xhci, dev); if (!KSUCCESS(st)) return st; uint8_t inDci = dev->MassBulkInEndpointId; uint8_t outDci = dev->MassBulkOutEndpointId; if (inDci < 2 || inDci > 31 || outDci < 2 || outDci > 31) return NTLSTATUS_INVALID_PARAMETER; memset(dev->InputContext, 0, PAGE_SIZE); uint32_t* icc = XhciInputControlContext(xhci, dev); uint32_t* slot = XhciInputSlotContext(xhci, dev); uint32_t* epIn = XhciInputEndpointContext(xhci, dev, inDci); uint32_t* epOut = XhciInputEndpointContext(xhci, dev, outDci); icc[0] = 0; icc[1] = (1u << 0) | (1u << inDci) | (1u << outDci); uint8_t maxDci = inDci > outDci ? inDci : outDci; slot[0] = XHCI_CONTEXT_ENTRIES(maxDci) | XHCI_SLOT_SPEED(dev->Speed); slot[1] = XHCI_SLOT_ROOT_PORT(dev->Port); slot[2] = 0; slot[3] = 0; uint16_t inMaxPacket = dev->MassBulkInMaxPacket & 0x07FFu; uint16_t outMaxPacket = dev->MassBulkOutMaxPacket & 0x07FFu; if (inMaxPacket == 0) inMaxPacket = 512; if (outMaxPacket == 0) outMaxPacket = 512; epIn[0] = 0; epIn[1] = XHCI_EP_CERR_3 | XHCI_EP_TYPE_BULK_IN | ((uint32_t)inMaxPacket << 16); epIn[2] = (uint32_t)(dev->MassInRingDeviceAddress | XHCI_EP_DCS); epIn[3] = (uint32_t)((dev->MassInRingDeviceAddress | XHCI_EP_DCS) >> 32); epIn[4] = inMaxPacket; epOut[0] = 0; epOut[1] = XHCI_EP_CERR_3 | XHCI_EP_TYPE_BULK_OUT | ((uint32_t)outMaxPacket << 16); epOut[2] = (uint32_t)(dev->MassOutRingDeviceAddress | XHCI_EP_DCS); epOut[3] = (uint32_t)((dev->MassOutRingDeviceAddress | XHCI_EP_DCS) >> 32); epOut[4] = outMaxPacket; XHCI_COMMAND_RESULT result; memset(&result, 0, sizeof(result)); st = XhciSubmitCommand( xhci, dev->InputContextDeviceAddress, 0, (XHCI_TRB_TYPE_CONFIGURE_ENDPOINT << XHCI_TRB_TYPE_SHIFT) | ((uint32_t)dev->SlotId << 24), &result); if (!KSUCCESS(st)) { DbgPrint("usb-storage: Configure Endpoint failed slot=%u in=%u out=%u st=0x%x cc=%u\n", dev->SlotId, inDci, outDci, st, result.CompletionCode); return st; } UsbTrace("usb-storage: bulk endpoints configured slot=%u if=%u in=%02x/dci=%u out=%02x/dci=%u\n", dev->SlotId, dev->MassInterface, dev->MassBulkInAddress, inDci, dev->MassBulkOutAddress, outDci); return NTLSTATUS_SUCCESS; } // Returns free Disk Index. uint8_t UsbFindFreeDiskIndex(void) { for (uint8_t i = 0; i < 8; i++) { if (!IoDiskDevicePresent(i)) return i; } return 0xFFu; } // Implements the storage Register Disk operation. KSTATUS UsbStorageRegisterDisk(USB_STORAGE_EXTENSION* storage) { if (!storage || storage->BlockSize != USB_STORAGE_SECTOR_SIZE || storage->BlockCount == 0) return NTLSTATUS_NOT_SUPPORTED; if (storage->DiskDevice) return NTLSTATUS_ALREADY_EXISTS; DEVICE_OBJECT* disk = (DEVICE_OBJECT*)0; KSTATUS st = IoCreateDevice(&gUsbStorageDriver, sizeof(USB_STORAGE_DISK_EXTENSION), &disk); if (!KSUCCESS(st)) return st; (void)IoSetDeviceName(disk, SYS_SV_LITERAL("usb-storage-disk")); USB_STORAGE_DISK_EXTENSION* diskExt = (USB_STORAGE_DISK_EXTENSION*)disk->DeviceExtension; diskExt->Kind = UsbStorageExtensionDisk; diskExt->Storage = storage; disk->u.Disk.SectorCount = storage->BlockCount; disk->u.Disk.SectorSize = USB_STORAGE_SECTOR_SIZE; disk->AlignmentRequirement = USB_STORAGE_SECTOR_SIZE; disk->Flags |= DEVICE_OBJECT_REMOVABLE; uint8_t index = UsbFindFreeDiskIndex(); if (index == 0xFFu) { IoDeleteDevice(disk); return NTLSTATUS_NO_MEMORY; } st = IoRegisterDiskDevice(index, disk); if (!KSUCCESS(st)) { IoDeleteDevice(disk); return st; } storage->DiskDevice = disk; if (storage->XhciDevice) storage->XhciDevice->MassDeviceObject = disk; DbgPrint("usb-storage: registered disk %u slot=%u sectors=%llu\n", index, storage->Device ? storage->Device->SlotId : 0, (unsigned long long)storage->BlockCount); return NTLSTATUS_SUCCESS; } // Implements the storage Start Device operation. static KSTATUS UsbStorageStartDevice(DEVICE_OBJECT* device, USB_STORAGE_EXTENSION* storage) { if (!device || !storage || !storage->InterfacePdo || !storage->Device) return NTLSTATUS_INVALID_PARAMETER; if (storage->Started) return NTLSTATUS_SUCCESS; if (storage->SurpriseRemoved || storage->Removed) return NTLSTATUS_OBJECT_DEAD; USB_PDO_EXTENSION* pdoExt = (USB_PDO_EXTENSION*)storage->InterfacePdo->DeviceExtension; if (!pdoExt || pdoExt->Kind != UsbPdoInterface) return NTLSTATUS_INVALID_PARAMETER; const USB_ENDPOINT_DESCRIPTOR* bulkIn = UsbStorageFindBulkEndpoint(pdoExt, true); const USB_ENDPOINT_DESCRIPTOR* bulkOut = UsbStorageFindBulkEndpoint(pdoExt, false); if (!bulkIn || !bulkOut) return NTLSTATUS_NOT_FOUND; XHCI_CONTROLLER* xhci = (XHCI_CONTROLLER*)storage->Device->Hcd->ControllerContext; XHCI_DEVICE* xdev = (XHCI_DEVICE*)storage->Device->HcdDeviceContext; if (!xhci || !xdev) return NTLSTATUS_INVALID_PARAMETER; storage->XhciDevice = xdev; storage->InterfaceNumber = pdoExt->InterfaceDescriptor.bInterfaceNumber; storage->BulkInAddress = bulkIn->bEndpointAddress; storage->BulkOutAddress = bulkOut->bEndpointAddress; storage->BulkInMaxPacket = bulkIn->wMaxPacketSize; storage->BulkOutMaxPacket = bulkOut->wMaxPacketSize; storage->Tag = 0x1000u + storage->Device->SlotId; storage->FlushCapability = UsbStorageFlushUnknown; xdev->MassCandidate = true; xdev->MassInterface = storage->InterfaceNumber; xdev->MassBulkInAddress = storage->BulkInAddress; xdev->MassBulkOutAddress = storage->BulkOutAddress; xdev->MassBulkInEndpointId = XhciEndpointIdFromAddress(storage->BulkInAddress); xdev->MassBulkOutEndpointId = XhciEndpointIdFromAddress(storage->BulkOutAddress); xdev->MassBulkInMaxPacket = storage->BulkInMaxPacket; xdev->MassBulkOutMaxPacket = storage->BulkOutMaxPacket; KSTATUS st = XhciConfigureMassStorageEndpoints(xhci, xdev); if (!KSUCCESS(st)) goto fail; st = UsbCreatePipe(storage->Device, UsbPipeBulk, storage->BulkInAddress, storage->BulkInMaxPacket, bulkIn->bInterval, &storage->BulkInPipe); if (!KSUCCESS(st)) goto fail; st = UsbCreatePipe(storage->Device, UsbPipeBulk, storage->BulkOutAddress, storage->BulkOutMaxPacket, bulkOut->bInterval, &storage->BulkOutPipe); if (!KSUCCESS(st)) goto fail; st = UsbStorageInquiry(storage); if (!KSUCCESS(st)) goto fail; st = UsbStorageTestUnitReady(storage); if (!KSUCCESS(st)) { DbgPrint("usb-storage: TEST UNIT READY failed slot=%u st=0x%x\n", storage->Device->SlotId, st); goto fail; } st = UsbStorageReadCapacity(storage); if (!KSUCCESS(st)) goto fail; if (storage->BlockSize != USB_STORAGE_SECTOR_SIZE) { DbgPrint("usb-storage: unsupported block size slot=%u size=%u\n", storage->Device->SlotId, storage->BlockSize); st = NTLSTATUS_NOT_SUPPORTED; goto fail; } // The disk namespace publishes an arrival notification synchronously from // IoRegisterDiskDevice(). Mark the storage FDO usable before publishing the // disk so filesystem hotplug callbacks can read sector 0 immediately. xdev->MassActive = true; storage->Started = true; device->PnpState = DevicePnpStarted; if (storage->InterfacePdo) storage->InterfacePdo->PnpState = DevicePnpStarted; st = UsbStorageRegisterDisk(storage); if (!KSUCCESS(st)) { storage->Started = false; device->PnpState = DevicePnpNotStarted; if (storage->InterfacePdo) storage->InterfacePdo->PnpState = DevicePnpNotStarted; goto fail; } DbgPrint("usb-storage: FDO bound slot=%u if=%u disk-ready\n", storage->Device->SlotId, storage->InterfaceNumber); return NTLSTATUS_SUCCESS; fail: xdev->MassCandidate = false; xdev->MassActive = false; if (storage->BulkInPipe) { UsbDereferencePipe(storage->BulkInPipe); storage->BulkInPipe = (USB_PIPE*)0; } if (storage->BulkOutPipe) { UsbDereferencePipe(storage->BulkOutPipe); storage->BulkOutPipe = (USB_PIPE*)0; } DbgPrint("usb-storage: start failed slot=%u if=%u st=0x%x\n", storage->Device ? storage->Device->SlotId : 0, storage->InterfaceNumber, st); return st; } // Implements the storage Disk Pnp operation. static KSTATUS UsbStorageDiskPnp(DEVICE_OBJECT* device, IRP* irp, USB_STORAGE_EXTENSION* storage, IO_PNP_MINOR_FUNCTION minor) { if (!device || !irp || !storage) return NTLSTATUS_INVALID_PARAMETER; switch (minor) { case IRP_MN_START_DEVICE: device->PnpState = DevicePnpStarted; IoCompleteRequest(irp, NTLSTATUS_SUCCESS, 0); return NTLSTATUS_PENDING; case IRP_MN_QUERY_REMOVE_DEVICE: device->PnpState = DevicePnpRemovePending; IoCompleteRequest(irp, NTLSTATUS_SUCCESS, 0); return NTLSTATUS_PENDING; case IRP_MN_CANCEL_REMOVE_DEVICE: device->PnpState = DevicePnpStarted; IoCompleteRequest(irp, NTLSTATUS_SUCCESS, 0); return NTLSTATUS_PENDING; case IRP_MN_SURPRISE_REMOVAL: storage->SurpriseRemoved = true; if (storage->XhciDevice) { storage->XhciDevice->MassSurpriseRemoved = true; storage->XhciDevice->MassActive = false; } device->PnpState = DevicePnpSurpriseRemoved; IoCompleteRequest(irp, NTLSTATUS_SUCCESS, 0); return NTLSTATUS_PENDING; case IRP_MN_REMOVE_DEVICE: storage->Removed = true; if (storage->XhciDevice) { storage->XhciDevice->MassRemoved = true; storage->XhciDevice->MassActive = false; if (storage->XhciDevice->MassDeviceObject == device) storage->XhciDevice->MassDeviceObject = (DEVICE_OBJECT*)0; } device->PnpState = DevicePnpDeleted; if (storage->DiskDevice == device) storage->DiskDevice = (DEVICE_OBJECT*)0; IoDeleteDevice(device); IoCompleteRequest(irp, NTLSTATUS_SUCCESS, 0); return NTLSTATUS_PENDING; default: return NTLSTATUS_NOT_SUPPORTED; } } // Implements the storage Interface Pnp operation. static KSTATUS UsbStorageInterfacePnp(DEVICE_OBJECT* device, IRP* irp, USB_STORAGE_EXTENSION* storage, IO_PNP_MINOR_FUNCTION minor) { if (!device || !irp || !storage) return NTLSTATUS_INVALID_PARAMETER; KSTATUS st = NTLSTATUS_SUCCESS; switch (minor) { case IRP_MN_START_DEVICE: st = UsbStorageStartDevice(device, storage); if (!KSUCCESS(st)) { IoCompleteRequest(irp, st, 0); return NTLSTATUS_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 (!storage->SurpriseRemoved) device->PnpState = DevicePnpStarted; return IoForwardIrpToLower(device, irp); case IRP_MN_SURPRISE_REMOVAL: storage->SurpriseRemoved = true; storage->Started = false; if (storage->XhciDevice) { storage->XhciDevice->MassSurpriseRemoved = true; storage->XhciDevice->MassActive = false; } if (storage->DiskDevice) (void)IoNotifyDiskSurpriseRemoved(storage->DiskDevice->u.Disk.DiskIndex); device->PnpState = DevicePnpSurpriseRemoved; return IoForwardIrpToLower(device, irp); case IRP_MN_REMOVE_DEVICE: { storage->Removed = true; storage->Started = false; if (storage->DiskDevice) (void)IoUnregisterDiskDevice(storage->DiskDevice->u.Disk.DiskIndex); if (storage->BulkInPipe) { UsbDereferencePipe(storage->BulkInPipe); storage->BulkInPipe = (USB_PIPE*)0; } if (storage->BulkOutPipe) { UsbDereferencePipe(storage->BulkOutPipe); storage->BulkOutPipe = (USB_PIPE*)0; } if (storage->RemoveLockReady) { IoReleaseRemoveLockAndWait(&storage->RemoveLock, irp); IoDestroyRemoveLock(&storage->RemoveLock); storage->RemoveLockReady = false; } device->PnpState = DevicePnpDeleted; KSTATUS lower = storage->LowerDevice ? IoForwardIrpToLower(device, irp) : NTLSTATUS_SUCCESS; if (storage->LowerDevice) { IoDetachDevice(device); storage->LowerDevice = (DEVICE_OBJECT*)0; } IoDeleteDevice(device); if (lower == NTLSTATUS_NOT_SUPPORTED) { IoCompleteRequest(irp, NTLSTATUS_SUCCESS, 0); return NTLSTATUS_PENDING; } if (lower != NTLSTATUS_PENDING && !KSUCCESS(lower)) { IoCompleteRequest(irp, lower, 0); return NTLSTATUS_PENDING; } return lower; } default: return IoForwardIrpToLower(device, irp); } } // Implements the storage Dispatch Pnp operation. KSTATUS UsbStorageDispatchPnp(DEVICE_OBJECT* device, IRP* irp) { if (!device || !irp) return NTLSTATUS_INVALID_PARAMETER; IO_STACK_LOCATION* ios = IoGetCurrentIrpStackLocation(irp); if (!ios || ios->MajorFunction != IRP_MJ_PNP) return NTLSTATUS_INVALID_PARAMETER; USB_STORAGE_EXTENSION* iface = UsbStorageFromInterfaceDevice(device); if (iface) return UsbStorageInterfacePnp(device, irp, iface, (IO_PNP_MINOR_FUNCTION)ios->MinorFunction); USB_STORAGE_EXTENSION* disk = UsbStorageFromDiskDevice(device); if (disk) return UsbStorageDiskPnp(device, irp, disk, (IO_PNP_MINOR_FUNCTION)ios->MinorFunction); return NTLSTATUS_INVALID_PARAMETER; } // Implements the storage Add Device operation. KSTATUS UsbStorageAddDevice(DRIVER_OBJECT* driver, DEVICE_OBJECT* physicalDeviceObject) { if (!driver || !physicalDeviceObject || physicalDeviceObject->Type != DeviceObjectPhysical) return NTLSTATUS_INVALID_PARAMETER; USB_PDO_EXTENSION* pdoExt = (USB_PDO_EXTENSION*)physicalDeviceObject->DeviceExtension; if (!pdoExt || pdoExt->Kind != UsbPdoInterface || !pdoExt->Device) return NTLSTATUS_INVALID_PARAMETER; if (pdoExt->InterfaceDescriptor.bInterfaceClass != USB_CLASS_MASS_STORAGE || pdoExt->InterfaceDescriptor.bInterfaceSubClass != USB_MASS_SUBCLASS_SCSI || pdoExt->InterfaceDescriptor.bInterfaceProtocol != USB_MASS_PROTOCOL_BOT) return NTLSTATUS_INVALID_PARAMETER; if (!UsbStorageFindBulkEndpoint(pdoExt, true) || !UsbStorageFindBulkEndpoint(pdoExt, false)) return NTLSTATUS_NOT_FOUND; DEVICE_OBJECT* fdo = (DEVICE_OBJECT*)0; KSTATUS st = IoCreateDevice(driver, sizeof(USB_STORAGE_EXTENSION), &fdo); if (!KSUCCESS(st)) return st; (void)IoSetDeviceName(fdo, SYS_SV_LITERAL("usb-storage")); USB_STORAGE_EXTENSION* ext = (USB_STORAGE_EXTENSION*)fdo->DeviceExtension; ext->Kind = UsbStorageExtensionInterface; ext->Self = fdo; ext->InterfacePdo = physicalDeviceObject; ext->Device = pdoExt->Device; ext->XhciDevice = (XHCI_DEVICE*)pdoExt->Device->HcdDeviceContext; ext->InterfaceNumber = pdoExt->InterfaceDescriptor.bInterfaceNumber; ext->IoBusy = 0; st = IoInitializeRemoveLock(&ext->RemoveLock); if (!KSUCCESS(st)) { IoDeleteDevice(fdo); return st; } ext->RemoveLockReady = true; ext->LowerDevice = IoAttachDeviceToDeviceStack(fdo, physicalDeviceObject); if (!ext->LowerDevice) { IoDestroyRemoveLock(&ext->RemoveLock); ext->RemoveLockReady = false; IoDeleteDevice(fdo); return NTLSTATUS_FAILURE; } DbgPrint("usb-storage: FDO attached slot=%u if=%u\n", ext->Device->SlotId, pdoExt->InterfaceDescriptor.bInterfaceNumber); return NTLSTATUS_SUCCESS; } // Initializes storage Driver. KSTATUS UsbInitializeStorageDriver(void) { memset(&gUsbStorageDriver, 0, sizeof(gUsbStorageDriver)); IoInitializeDriverObject(&gUsbStorageDriver, SYS_SV_LITERAL("usb-storage")); gUsbStorageDriver.IdTable = &gUsbStorageIdTable; gUsbStorageDriver.DriverAddDevice = UsbStorageAddDevice; gUsbStorageDriver.MajorFunction[IRP_MJ_READ] = UsbStorageDispatchReadWrite; gUsbStorageDriver.MajorFunction[IRP_MJ_WRITE] = UsbStorageDispatchReadWrite; gUsbStorageDriver.MajorFunction[IRP_MJ_FLUSH_BUFFERS] = UsbStorageDispatchFlush; gUsbStorageDriver.MajorFunction[IRP_MJ_PNP] = UsbStorageDispatchPnp; return NTLSTATUS_SUCCESS; }