/*++ Copyright (c) 1999 Microsoft Corporation Module Name: usb.c Abstract: ESC/POS (serial) interface for USB Point-of-Sale devices Author: ervinp Environment: Kernel mode Revision History: --*/ #include #include #include #include #include "escpos.h" #include "debug.h" NTSTATUS InitUSB(PARENTFDOEXT *parentFdoExt) /*++ Routine Description: Intialize USB-related data Arguments: parentFdoExt - device extension for targetted device object Return Value: NT status code --*/ { NTSTATUS status; status = GetDeviceDescriptor(parentFdoExt); if (NT_SUCCESS(status)){ status = GetConfigDescriptor(parentFdoExt); if (NT_SUCCESS(status)){ status = SelectConfiguration(parentFdoExt); } } return status; } NTSTATUS GetConfigDescriptor(PARENTFDOEXT *parentFdoExt) /*++ Routine Description: Function retrieves the configuration descriptor from the device Arguments: parentFdoExt - device extension for targetted device object Return Value: NT status code --*/ { URB urb = { 0 }; USB_CONFIGURATION_DESCRIPTOR configDescBase = { 0 }; NTSTATUS status; PAGED_CODE(); /* * Get the first part of the configuration descriptor. * It will tell us the size of the full configuration descriptor, * including all the following interface descriptors, etc. */ UsbBuildGetDescriptorRequest(&urb, (USHORT) sizeof(struct _URB_CONTROL_DESCRIPTOR_REQUEST), USB_CONFIGURATION_DESCRIPTOR_TYPE, 0, 0, (PVOID)&configDescBase, NULL, sizeof(USB_CONFIGURATION_DESCRIPTOR), NULL); status = SubmitUrb(parentFdoExt->topDevObj, &urb, TRUE, NULL, NULL); if (NT_SUCCESS(status)) { ULONG configDescLen = configDescBase.wTotalLength; /* * Now allocate the right-sized buffer for the full configuration descriptor. */ ASSERT(configDescLen < 0x1000); parentFdoExt->configDesc = ALLOCPOOL(NonPagedPool, configDescLen); if (parentFdoExt->configDesc) { RtlZeroMemory(parentFdoExt->configDesc, configDescLen); UsbBuildGetDescriptorRequest(&urb, (USHORT) sizeof(struct _URB_CONTROL_DESCRIPTOR_REQUEST), USB_CONFIGURATION_DESCRIPTOR_TYPE, 0, 0, parentFdoExt->configDesc, NULL, configDescLen, NULL); status = SubmitUrb(parentFdoExt->topDevObj, &urb, TRUE, NULL, NULL); if (NT_SUCCESS(status)) { DBGVERBOSE(("Got config desc @ %ph, len=%xh.", parentFdoExt->configDesc, urb.UrbControlDescriptorRequest.TransferBufferLength)); ASSERT(urb.UrbControlDescriptorRequest.TransferBufferLength == configDescLen); } } else { status = STATUS_INSUFFICIENT_RESOURCES; } } ASSERT(NT_SUCCESS(status)); return status; } NTSTATUS GetDeviceDescriptor(PARENTFDOEXT *parentFdoExt) /*++ Routine Description: Function retrieves the device descriptor from the device Arguments: parentFdoExt - device extension for targetted device object Return Value: NT status code --*/ { URB urb; NTSTATUS status; PAGED_CODE(); RtlZeroMemory(&parentFdoExt->deviceDesc, sizeof(parentFdoExt->deviceDesc)); UsbBuildGetDescriptorRequest(&urb, (USHORT) sizeof(struct _URB_CONTROL_DESCRIPTOR_REQUEST), USB_DEVICE_DESCRIPTOR_TYPE, 0, 0, (PVOID)&parentFdoExt->deviceDesc, NULL, sizeof(parentFdoExt->deviceDesc), NULL); status = SubmitUrb(parentFdoExt->topDevObj, &urb, TRUE, NULL, NULL); if (NT_SUCCESS(status)){ DBGVERBOSE(("Got device desc @ %ph, len=%xh (should be %xh).", (PVOID)&parentFdoExt->deviceDesc, urb.UrbControlDescriptorRequest.TransferBufferLength, sizeof(parentFdoExt->deviceDesc))); } ASSERT(NT_SUCCESS(status)); return status; } NTSTATUS ReadPipe( PARENTFDOEXT *parentFdoExt, USBD_PIPE_HANDLE pipeHandle, READPACKET *readPacket, BOOLEAN synchronous ) { PURB urb; NTSTATUS status; ASSERT(pipeHandle); DBGVERBOSE(("ReadPipe: dataLen=%xh, sync=%xh", readPacket->length, (ULONG)synchronous)); urb = ALLOCPOOL(NonPagedPool, sizeof(URB)); if (urb){ RtlZeroMemory(urb, sizeof(URB)); urb->UrbBulkOrInterruptTransfer.Hdr.Function = URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER; urb->UrbBulkOrInterruptTransfer.Hdr.Length = sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER); urb->UrbBulkOrInterruptTransfer.PipeHandle = pipeHandle; urb->UrbBulkOrInterruptTransfer.TransferBufferLength = readPacket->length; urb->UrbBulkOrInterruptTransfer.TransferBufferMDL = NULL; urb->UrbBulkOrInterruptTransfer.TransferBuffer = readPacket->data; urb->UrbBulkOrInterruptTransfer.TransferFlags = USBD_SHORT_TRANSFER_OK | USBD_TRANSFER_DIRECTION_IN; urb->UrbBulkOrInterruptTransfer.UrbLink = NULL; if (synchronous){ /* * Synchronous read. */ status = SubmitUrb(parentFdoExt->topDevObj, urb, TRUE, NULL, 0); readPacket->length = urb->UrbBulkOrInterruptTransfer.TransferBufferLength; FREEPOOL(urb); } else { /* * Asynchronous read. * Completion routine will free URB. */ IncrementPendingActionCount(parentFdoExt); readPacket->urb = urb; status = SubmitUrb( parentFdoExt->topDevObj, urb, FALSE, // asynchronous ReadPipeCompletion, // completion routine readPacket // completion context ); } } else { status = STATUS_INSUFFICIENT_RESOURCES; } return status; } NTSTATUS ReadPipeCompletion(IN PDEVICE_OBJECT devObj, IN PIRP irp, IN PVOID context) { READPACKET *readPacket = (READPACKET *)context; NTSTATUS status = irp->IoStatus.Status; POSPDOEXT *pdoExt; KIRQL oldIrql; ASSERT(readPacket->signature == READPACKET_SIG); pdoExt = readPacket->context; /* * Set the readPacket's length to the actual length returned by the device. */ ASSERT(readPacket->urb->UrbBulkOrInterruptTransfer.TransferBufferLength <= readPacket->length); readPacket->length = readPacket->urb->UrbBulkOrInterruptTransfer.TransferBufferLength; DBGVERBOSE(("ReadPipeCompletion: irp=%ph, status=%xh, data=%ph, len=%xh, context=%ph.", irp, status, readPacket->data, readPacket->length, readPacket->context)); FREEPOOL(readPacket->urb); readPacket->urb = BAD_POINTER; if (NT_SUCCESS(status)){ DBGSHOWBYTES("READ PIPE result", readPacket->data, readPacket->length); if (pdoExt){ KeAcquireSpinLock(&pdoExt->devExtSpinLock, &oldIrql); ASSERT(pdoExt->inputEndpointInfo.endpointIsBusy); pdoExt->inputEndpointInfo.endpointIsBusy = FALSE; /* * Queue this completed readPacket */ ASSERT(readPacket->offset == 0); /* * Do NOT queue empty readPackets. */ if(readPacket->length == 0) FreeReadPacket(readPacket); else { InsertTailList(&pdoExt->completedReadPacketsList, &readPacket->listEntry); pdoExt->totalQueuedReadDataLength += readPacket->length; } KeReleaseSpinLock(&pdoExt->devExtSpinLock, oldIrql); /* * Now try to satisfy pending read IRPs with the completed readPacket data. */ SatisfyPendingReads(pdoExt); } else { DBGVERBOSE(("Just debug testing -- not processing read result")); FreeReadPacket(readPacket); } } else { FreeReadPacket(readPacket); } /* * If there are more read IRPs pending, issue another read. */ if (pdoExt){ BOOLEAN scheduleAnotherRead; KeAcquireSpinLock(&pdoExt->devExtSpinLock, &oldIrql); scheduleAnotherRead = !IsListEmpty(&pdoExt->pendingReadIrpsList); KeReleaseSpinLock(&pdoExt->devExtSpinLock, oldIrql); if (scheduleAnotherRead){ DBGVERBOSE(("ReadPipeCompletion: scheduling read workItem")); ExQueueWorkItem(&pdoExt->readWorkItem, DelayedWorkQueue); } } /* * This IRP was allocated by SubmitUrb(). Free it here. * Return STATUS_MORE_PROCESSING_REQUIRED so the kernel does not * continue processing this IRP. */ IoFreeIrp(irp); DecrementPendingActionCount(pdoExt->parentFdoExt); return STATUS_MORE_PROCESSING_REQUIRED; } NTSTATUS WritePipe(PARENTFDOEXT *parentFdoExt, USBD_PIPE_HANDLE pipeHandle, PUCHAR data, ULONG dataLen) { URB urb; NTSTATUS status; ASSERT(pipeHandle); DBGSHOWBYTES("WritePipe bytes:", data, dataLen); urb.UrbBulkOrInterruptTransfer.Hdr.Function = URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER; urb.UrbBulkOrInterruptTransfer.Hdr.Length = sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER); urb.UrbBulkOrInterruptTransfer.PipeHandle = pipeHandle; urb.UrbBulkOrInterruptTransfer.TransferBufferLength = dataLen; urb.UrbBulkOrInterruptTransfer.TransferBufferMDL = NULL; urb.UrbBulkOrInterruptTransfer.TransferBuffer = data; urb.UrbBulkOrInterruptTransfer.TransferFlags = USBD_SHORT_TRANSFER_OK | USBD_TRANSFER_DIRECTION_OUT; urb.UrbBulkOrInterruptTransfer.UrbLink = NULL; status = SubmitUrb(parentFdoExt->topDevObj, &urb, TRUE, NULL, NULL); return status; } NTSTATUS SubmitUrb( PDEVICE_OBJECT pdo, PURB urb, BOOLEAN synchronous, PVOID completionRoutine, PVOID completionContext) /*++ Routine Description: Send the URB to the USB device. If synchronous is TRUE, ignore the completion info and synchonize the IRP; otherwise, don't synchronize and set the provided completion routine for the IRP. Arguments: Return Value: NT status code --*/ { PIRP irp; NTSTATUS status; /* * Allocate the IRP to send the buffer down the USB stack. * * Don't use IoBuildDeviceIoControlRequest (because it queues * the IRP on the current thread's irp list and may * cause the calling process to hang if the IopCompleteRequest APC * does not fire and dequeue the IRP). */ irp = IoAllocateIrp(pdo->StackSize, FALSE); if (irp){ PIO_STACK_LOCATION nextSp; #if STATUS_ENDPOINT DBGVERBOSE(("SubmitUrb: submitting URB %ph on IRP %ph (sync=%d)", urb, irp, synchronous)); #endif nextSp = IoGetNextIrpStackLocation(irp); nextSp->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL; nextSp->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_SUBMIT_URB; /* * Attach the URB to this IRP. */ nextSp->Parameters.Others.Argument1 = urb; if (synchronous){ status = CallDriverSync(pdo, irp); IoFreeIrp(irp); } else { /* * Caller's completion routine will free the irp * when it completes. */ ASSERT(completionRoutine); ASSERT(completionContext); IoSetCompletionRoutine( irp, completionRoutine, completionContext, TRUE, TRUE, TRUE); status = IoCallDriver(pdo, irp); } } else { status = STATUS_INSUFFICIENT_RESOURCES; } return status; } NTSTATUS SelectConfiguration(PARENTFDOEXT *parentFdoExt) { USBD_INTERFACE_LIST_ENTRY interfaceList[2]; NTSTATUS status; /* * We only look at vendor-class interfaces */ // BUGBUG - limited to one interface interfaceList[0].InterfaceDescriptor = USBD_ParseConfigurationDescriptorEx( parentFdoExt->configDesc, parentFdoExt->configDesc, -1, -1, USB_INTERFACE_CLASS_VENDOR, -1, -1); if (interfaceList[0].InterfaceDescriptor){ PURB urb; interfaceList[1].InterfaceDescriptor = NULL; urb = USBD_CreateConfigurationRequestEx(parentFdoExt->configDesc, &interfaceList[0]); if (urb){ status = SubmitUrb(parentFdoExt->topDevObj, urb, TRUE, NULL, NULL); if (NT_SUCCESS(status)){ PUSBD_INTERFACE_INFORMATION interfaceInfo; parentFdoExt->configHandle = urb->UrbSelectConfiguration.ConfigurationHandle; interfaceInfo = &urb->UrbSelectConfiguration.Interface; parentFdoExt->interfaceInfo = MemDup(interfaceInfo, interfaceInfo->Length); if (parentFdoExt->interfaceInfo){ DBGVERBOSE(("SelectConfiguration: got interfaceInfo @ %ph.", parentFdoExt->interfaceInfo)); } else { status = STATUS_INSUFFICIENT_RESOURCES; } } else { DBGERR(("SelectConfiguration: URB failed w/ %xh.", status)); } FREEPOOL(urb); } else { status = STATUS_INSUFFICIENT_RESOURCES; } } else { status = STATUS_DEVICE_DATA_ERROR; } return status; } NTSTATUS CreatePdoForEachEndpointPair(PARENTFDOEXT *parentFdoExt) /*++ Routine Description: Walk the USB endpoints. For each input/output endpoint pair, create one PDO which will be exposed as a COM (serial) port interface. BUGBUG - right now, this only looks at the first interface (on the default confuguration) Arguments: parentFdoExt - device extension for the targetted device object Return Value: NT status code --*/ { NTSTATUS status; ULONG maxPossiblePDOs, deviceRelationsSize; maxPossiblePDOs = (parentFdoExt->interfaceInfo->NumberOfPipes/2); deviceRelationsSize = sizeof(DEVICE_RELATIONS) + maxPossiblePDOs*sizeof(PDEVICE_OBJECT); parentFdoExt->deviceRelations = ALLOCPOOL(NonPagedPool, deviceRelationsSize); if (parentFdoExt->deviceRelations){ ULONG inputPipeIndex = 0, outputPipeIndex = 0, statusPipeIndex = 0, comInterfaceIndex = 0; RtlZeroMemory(parentFdoExt->deviceRelations, deviceRelationsSize); status = STATUS_NO_MATCH; while (TRUE){ UCHAR endPtAddr; USBD_PIPE_TYPE pipeType; #if STATUS_ENDPOINT /* * In the case of Serial Emulation, the protocol is that all DATA endpoints * will be of type BULK and all STATUS endpoints will be of type INTERRUPT. */ if(parentFdoExt->posFlag & SERIAL_EMULATION) { while(inputPipeIndex < parentFdoExt->interfaceInfo->NumberOfPipes) { endPtAddr = parentFdoExt->interfaceInfo->Pipes[inputPipeIndex].EndpointAddress; pipeType = parentFdoExt->interfaceInfo->Pipes[inputPipeIndex].PipeType; if((pipeType == UsbdPipeTypeBulk) && (endPtAddr & USB_ENDPOINT_DIRECTION_MASK)) break; inputPipeIndex++; } while(outputPipeIndex < parentFdoExt->interfaceInfo->NumberOfPipes) { endPtAddr = parentFdoExt->interfaceInfo->Pipes[outputPipeIndex].EndpointAddress; pipeType = parentFdoExt->interfaceInfo->Pipes[outputPipeIndex].PipeType; if((pipeType == UsbdPipeTypeBulk) && !(endPtAddr & USB_ENDPOINT_DIRECTION_MASK)) break; outputPipeIndex++; } while(statusPipeIndex < parentFdoExt->interfaceInfo->NumberOfPipes) { endPtAddr = parentFdoExt->interfaceInfo->Pipes[statusPipeIndex].EndpointAddress; pipeType = parentFdoExt->interfaceInfo->Pipes[statusPipeIndex].PipeType; if((pipeType == UsbdPipeTypeInterrupt) && (endPtAddr & USB_ENDPOINT_DIRECTION_MASK)) break; statusPipeIndex++; } if(!(statusPipeIndex < parentFdoExt->interfaceInfo->NumberOfPipes)) break; } else { #endif /* * No Serial Emulation required. Find only DATA endpoints * which can be of either type in this case. */ while(inputPipeIndex < parentFdoExt->interfaceInfo->NumberOfPipes) { endPtAddr = parentFdoExt->interfaceInfo->Pipes[inputPipeIndex].EndpointAddress; pipeType = parentFdoExt->interfaceInfo->Pipes[inputPipeIndex].PipeType; if((pipeType == UsbdPipeTypeInterrupt) || (pipeType == UsbdPipeTypeBulk)) { if(endPtAddr & USB_ENDPOINT_DIRECTION_MASK) { break; } } inputPipeIndex++; } while(outputPipeIndex < parentFdoExt->interfaceInfo->NumberOfPipes) { endPtAddr = parentFdoExt->interfaceInfo->Pipes[outputPipeIndex].EndpointAddress; pipeType = parentFdoExt->interfaceInfo->Pipes[outputPipeIndex].PipeType; if((pipeType == UsbdPipeTypeInterrupt) || (pipeType == UsbdPipeTypeBulk)) { if(!(endPtAddr & USB_ENDPOINT_DIRECTION_MASK)) { break; } } outputPipeIndex++; } #if STATUS_ENDPOINT } #endif if ((inputPipeIndex < parentFdoExt->interfaceInfo->NumberOfPipes) && (outputPipeIndex < parentFdoExt->interfaceInfo->NumberOfPipes)){ /* * We've found a pair (in/out) of endpoints. * Create a PDO to represent a COM (serial) port interface on these endpoints. */ PUSBD_PIPE_INFORMATION inputPipeInfo = &parentFdoExt->interfaceInfo->Pipes[inputPipeIndex]; PUSBD_PIPE_INFORMATION outputPipeInfo = &parentFdoExt->interfaceInfo->Pipes[outputPipeIndex]; ENDPOINTINFO inputEndpointInfo, outputEndpointInfo, statusEndpointInfo; #if EPSON_PRINTER /* * On the EPSON printer, we want to talk to the endpoints size 0x40, * not the other pair with length 8. */ if ((inputPipeInfo->MaximumPacketSize == 8) && (outputPipeInfo->MaximumPacketSize == 8)){ inputPipeIndex++, outputPipeIndex++; continue; } #endif inputEndpointInfo.pipeHandle = inputPipeInfo->PipeHandle; inputEndpointInfo.pipeLen = inputPipeInfo->MaximumPacketSize; inputEndpointInfo.endpointIsBusy = FALSE; outputEndpointInfo.pipeHandle = outputPipeInfo->PipeHandle; outputEndpointInfo.pipeLen = outputPipeInfo->MaximumPacketSize; outputEndpointInfo.endpointIsBusy = FALSE; #if STATUS_ENDPOINT if(parentFdoExt->posFlag & SERIAL_EMULATION) { PUSBD_PIPE_INFORMATION statusPipeInfo = &parentFdoExt->interfaceInfo->Pipes[statusPipeIndex]; statusEndpointInfo.pipeHandle = statusPipeInfo->PipeHandle; statusEndpointInfo.pipeLen = statusPipeInfo->MaximumPacketSize; statusEndpointInfo.endpointIsBusy = FALSE; } #endif status = CreateCOMPdo(parentFdoExt, comInterfaceIndex++, &inputEndpointInfo, &outputEndpointInfo, &statusEndpointInfo); if (NT_SUCCESS(status)){ DBGVERBOSE(("CreatePdoForEachEndpointPair: created COMPdo with in/out len %xh/%xh.", inputEndpointInfo.pipeLen, outputEndpointInfo.pipeLen)); inputPipeIndex++, outputPipeIndex++, statusPipeIndex++; } else { DBGERR(("CreatePdoForEachEndpointPair: CreateCOMPdo failed with %xh.", status)); break; } } else { if((parentFdoExt->posFlag & ODD_ENDPOINT) && ((inputPipeIndex + outputPipeIndex) < (2 * parentFdoExt->interfaceInfo->NumberOfPipes))) { /* * We've found an odd endpoint. * Create a PDO to represent a COM (serial) port interface on this endpoint. */ PUSBD_PIPE_INFORMATION oddPipeInfo = &parentFdoExt->interfaceInfo->Pipes[MIN(inputPipeIndex, outputPipeIndex)]; ENDPOINTINFO oddEndpointInfo; oddEndpointInfo.pipeHandle = oddPipeInfo->PipeHandle; oddEndpointInfo.pipeLen = oddPipeInfo->MaximumPacketSize; oddEndpointInfo.endpointIsBusy = FALSE; if(inputPipeIndex < parentFdoExt->interfaceInfo->NumberOfPipes) status = CreateCOMPdo(parentFdoExt, comInterfaceIndex++, &oddEndpointInfo, NULL, NULL); else status = CreateCOMPdo(parentFdoExt, comInterfaceIndex++, NULL, &oddEndpointInfo, NULL); if (NT_SUCCESS(status)){ DBGVERBOSE(("CreatePdoForEachEndpointPair: created COMPdo with len %xh.", oddEndpointInfo.pipeLen)); } else { DBGERR(("CreatePdoForEachEndpointPair: CreateCOMPdo failed with %xh.", status)); break; } } break; } } } else { status = STATUS_INSUFFICIENT_RESOURCES; } ASSERT(NT_SUCCESS(status)); return status; }