/*++ Copyright (c) 1999 Microsoft Corporation Module Name: xsplit.c Abstract: splits a DMA transfer into multiple smallest transfers that the miniport can handle. Environment: kernel mode only Notes: Revision History: 6-20-99 : created --*/ #include "common.h" #ifdef ALLOC_PRAGMA #endif ULONG USBPORT_MakeSplitTransfer( PDEVICE_OBJECT FdoDeviceObject, PTRANSFER_SG_LIST SgList, PHCD_TRANSFER_CONTEXT SplitTransfer, ULONG MaxTransferLength, ULONG MaxPacketLength, PULONG Idx, PULONG Offset, ULONG BytesToMap, ULONG LengthMapped ) /*++ Routine Description: Arguments: Return Value: --*/ { PTRANSFER_SG_ENTRY32 sgEntry; PTRANSFER_SG_LIST splitSgList; ULONG length; sgEntry = &SgList->SgEntry[*Idx]; LOGENTRY(NULL, FdoDeviceObject, LOG_XFERS, 'splt', SplitTransfer, *Idx, *Offset); USBPORT_ASSERT(MaxTransferLength % MaxPacketLength == 0) if ((sgEntry->Length - *Offset) > MaxTransferLength) { // case 2, this transfer falls entirely within // this sg entry LOGENTRY(NULL, FdoDeviceObject, LOG_XFERS, 'spt1', MaxTransferLength, *Offset, sgEntry); // make one transfer from sg[Idx] // inc offset length = MaxTransferLength; splitSgList = &SplitTransfer->SgList; splitSgList->SgCount = 1; splitSgList->SgEntry[0].LogicalAddress.Hw32 = sgEntry->LogicalAddress.Hw32 + *Offset; splitSgList->SgEntry[0].SystemAddress = sgEntry->SystemAddress + *Offset; splitSgList->SgEntry[0].Length = length; // start offset is always 0 for the first element splitSgList->SgEntry[0].StartOffset = 0; SplitTransfer->Tp.TransferBufferLength = length; SplitTransfer->Tp.MiniportFlags = MPTX_SPLIT_TRANSFER; splitSgList->MdlVirtualAddress = SgList->MdlVirtualAddress + LengthMapped; splitSgList->MdlSystemAddress = SgList->MdlSystemAddress + LengthMapped; // indicate that this is a split child SET_FLAG(SplitTransfer->Flags, USBPORT_TXFLAG_SPLIT_CHILD); *Offset += length; BytesToMap -= length; } else { // make transfer from last part of sg[Idx] // and first part of sg[Idx+1] if necessary, // inc Idx // reset offset // case 2 length = sgEntry->Length - *Offset; LOGENTRY(NULL, FdoDeviceObject, LOG_XFERS, 'spt2', MaxTransferLength, *Offset, length); USBPORT_ASSERT(length <= MaxTransferLength); // last part of sg1; splitSgList = &SplitTransfer->SgList; LOGENTRY(NULL, FdoDeviceObject, LOG_XFERS, 'sgE1', splitSgList, sgEntry, 0); splitSgList->SgCount = 1; splitSgList->SgEntry[0].LogicalAddress.Hw32 = sgEntry->LogicalAddress.Hw32 + *Offset; splitSgList->SgEntry[0].SystemAddress = sgEntry->SystemAddress + *Offset; splitSgList->SgEntry[0].Length = length; // start offset is always 0 for the first element splitSgList->SgEntry[0].StartOffset = 0; SplitTransfer->Tp.TransferBufferLength = length; SplitTransfer->Tp.MiniportFlags = MPTX_SPLIT_TRANSFER; splitSgList->MdlVirtualAddress = SgList->MdlVirtualAddress + LengthMapped; splitSgList->MdlSystemAddress = SgList->MdlSystemAddress + LengthMapped; // indicate that this is a split child SET_FLAG(SplitTransfer->Flags, USBPORT_TXFLAG_SPLIT_CHILD); *Offset += length; BytesToMap -= length; // calculate max size of second part length = MaxTransferLength - length; if (length > BytesToMap) { length = BytesToMap; } if (length == 0) { (*Idx)++; *Offset = 0; } else { LOGENTRY(NULL, FdoDeviceObject, LOG_XFERS, 'spt3', MaxTransferLength, *Offset, length); (*Idx)++; *Offset = 0; sgEntry = &SgList->SgEntry[*Idx]; LOGENTRY(NULL, FdoDeviceObject, LOG_XFERS, 'sgE2', splitSgList, sgEntry, *Idx); splitSgList->SgCount++; splitSgList->SgEntry[1].LogicalAddress.Hw32 = sgEntry->LogicalAddress.Hw32 + *Offset; splitSgList->SgEntry[1].SystemAddress = sgEntry->SystemAddress + *Offset; splitSgList->SgEntry[1].Length = length; splitSgList->SgEntry[1].StartOffset = splitSgList->SgEntry[0].Length; SplitTransfer->Tp.TransferBufferLength += length; *Offset += length; BytesToMap -= length; } } LOGENTRY(NULL, FdoDeviceObject, LOG_XFERS, 'spt>', BytesToMap, 0, 0); return BytesToMap; } NTSTATUS USBPORT_SplitBulkInterruptTransfer( PDEVICE_OBJECT FdoDeviceObject, PHCD_ENDPOINT Endpoint, PHCD_TRANSFER_CONTEXT Transfer, PLIST_ENTRY TransferList ) /*++ Routine Description: Split a bulk or interrupt transfer Arguments: Return Value: nt status code --*/ { ULONG maxTransferLength, maxPacketLength; PHCD_TRANSFER_CONTEXT splitTransfer; PTRANSFER_SG_LIST sgList; LIST_ENTRY tmpList; ULONG idx, i, offset, bytesToMap, lengthMapped; ULONG numberOfSplits; PLIST_ENTRY listEntry; sgList = &Transfer->SgList; maxPacketLength = EP_MAX_PACKET(Endpoint); USBPORT_ASSERT(EP_MAX_TRANSFER(Endpoint) >= maxPacketLength); // round to the smallest multiple of max packet size maxTransferLength = (EP_MAX_TRANSFER(Endpoint)/maxPacketLength) * maxPacketLength; // some notes: // // // The MAXTRANSFER is equal to USB_PAGE_SIZE (4k) and // the transfer sg list is broken into usb pages. In this // case we construct a transfer structure for each pair of // sg entries we round the length of the first sg entry down // to the highest multiple of MAXPACKET so we get a split // pattern like this: // // {Sg1}{.Sg2.}{.Sg3.}{.Sg4.} sg entries // |------|------|------|------| page breaks // |----------------------| original transfer // <...><> // <...><> // <...><><.> // {1 }{2 }{3 }{4} splits // // The MAXTRANSFER is less than USB_PAGE_SIZE (4k) and the // transfer sg list is broken into usb pages. // // the pattern will look like this: // // {...Sg1......}{......Sg2......}{......Sg3......} // |----------------|----------------|----------------| // |-----------------------------------| // <..> // <..> // <..> // <><> // <..> // <..> // <..> // <><> // <..> // {1 }{2 }{3 }{4 }{5 }{6 }{7 }{8 }{9 } // cases: // case 1 - transfer lies within the sg entry // case 2 - transfer overlaps the sg entry // // The MAXTRANSFER is greater than USB_PAGE_SIZE (4k) // ie etc, we currently don't handle this case // // Note: since the buffer is currently completely mapped // and locked it is better to tune the mniport to take the // larger transfers if possible if (EP_MAX_TRANSFER(Endpoint) > USB_PAGE_SIZE) { BUGCHECK(USBBUGCODE_INTERNAL_ERROR, 0, 0, 0); } // allocate the split elements // mark the parent transfer as a split SET_FLAG(Transfer->Flags, USBPORT_TXFLAG_SPLIT); numberOfSplits = Transfer->Tp.TransferBufferLength / maxTransferLength + 1; LOGENTRY(NULL, FdoDeviceObject, LOG_XFERS, 'sptC', numberOfSplits, 0, 0); InitializeListHead(&tmpList); for (i=0; iTotalLength); if (splitTransfer == NULL) { goto SplitBulkInterruptTransfer_Fail; } RtlCopyMemory(splitTransfer, Transfer, Transfer->TotalLength); splitTransfer->MiniportContext = (PUCHAR) splitTransfer; splitTransfer->MiniportContext += splitTransfer->PrivateLength; InitializeListHead(&splitTransfer->DoubleBufferList); InsertTailList(&tmpList, &splitTransfer->TransferLink); } idx = 0; offset = 0; bytesToMap = Transfer->Tp.TransferBufferLength; lengthMapped = 0; while (bytesToMap) { LOGENTRY(NULL, FdoDeviceObject, LOG_XFERS, 'sptM', bytesToMap, offset, idx); listEntry = RemoveHeadList(&tmpList); splitTransfer = (PHCD_TRANSFER_CONTEXT) CONTAINING_RECORD( listEntry, struct _HCD_TRANSFER_CONTEXT, TransferLink); ASSERT_TRANSFER(splitTransfer); bytesToMap = USBPORT_MakeSplitTransfer(FdoDeviceObject, sgList, splitTransfer, maxTransferLength, maxPacketLength, &idx, &offset, bytesToMap, lengthMapped); lengthMapped += splitTransfer->Tp.TransferBufferLength; InsertTailList(TransferList, &splitTransfer->TransferLink); InsertTailList(&Transfer->SplitTransferList, &splitTransfer->SplitLink); } // free extra splits we did not use while (!IsListEmpty(&tmpList)) { listEntry = RemoveHeadList(&tmpList); splitTransfer = (PHCD_TRANSFER_CONTEXT) CONTAINING_RECORD( listEntry, struct _HCD_TRANSFER_CONTEXT, TransferLink); ASSERT_TRANSFER(splitTransfer); UNSIG(splitTransfer); FREE_POOL(FdoDeviceObject, splitTransfer); } return STATUS_SUCCESS; SplitBulkInterruptTransfer_Fail: TEST_TRAP(); // free the tmp list while (!IsListEmpty(&tmpList)) { listEntry = RemoveHeadList(&tmpList); splitTransfer = (PHCD_TRANSFER_CONTEXT) CONTAINING_RECORD( listEntry, struct _HCD_TRANSFER_CONTEXT, TransferLink); ASSERT_TRANSFER(splitTransfer); UNSIG(splitTransfer); FREE_POOL(FdoDeviceObject, splitTransfer); } return STATUS_INSUFFICIENT_RESOURCES; } #if 0 NTSTATUS USBPORT_SplitIsochronousTransfer( PDEVICE_OBJECT FdoDeviceObject, PHCD_ENDPOINT Endpoint, PHCD_TRANSFER_CONTEXT Transfer, PLIST_ENTRY TransferList ) /*++ Routine Description: Split an iso transfer Arguments: Return Value: none --*/ { PMINIPORT_ISO_TRANSFER isoTransfer, splitIsoTransfer; PHCD_TRANSFER_CONTEXT splitTransfer; LIST_ENTRY tmpList; // first figure out how many child transfer structures // we will need and allocate them, we do this based on // the most packets we can fit in to a request // we do not fixup the SG table for the child transfers // since this information is not passed to the miniport isoTransfer = Transfer->IsoTransfer; transferCount = 0; length = 0; maxSplitLength = EP_MAX_TRANSFER(Endpoint); for (i=0; iPacketCount; i++) { if (length + isoTransfer->Packets[i].Length > maxSplitLength) { length = 0; transferCount++; } else { length += isoTransfer->Packets[i].Length } } // transferCount is the number of child transfers, // allocate them the now and clone the parent InitializeListHead(tmpList); for (i=0; iTotalLength); sgList = &splitTransfer->SgList; // zap the sg table since we don't use it for children for (j=0; jSgCount; j++) { sgList->SgEntry[j].LogicalAddress = 0xffffffff; sgList->SgEntry[j].SystemAddress = USB_BAD_PTR; sgList->SgEntry[j].Length = 0xffffffff; sgList->SgEntry[j].StartOffset = 0xffffffff; } sgList->Flags = 0xFFFFFFFF; sgList->MdlVirtualAddress = USB_BAD_PTR; sgList->MdlSystemAddress = USB_BAD_PTR; sgList->SgCount = 0xFFFFFFFF; InsertTailList(&tmpList, &splitTransfer->TransferLink); } // we now have a list of child transfer structures. // intialize them pkt = 0; systemAddress = isoTransfer->SystemAddress; InitializeListHead(&Transfer->SplitTransferList); do { listEntry = RemoveHeadList(&tmpList); splitTransfer = (PHCD_TRANSFER_CONTEXT) CONTAINING_RECORD( listEntry, struct _HCD_TRANSFER_CONTEXT, TransferLink); ASSERT_TRANSFER(splitTransfer); SET_FLAG(transfer->Flags, USBPORT_TXFLAG_SPLIT_CHILD); splitIsoTransfer = splitTransfer->IsoTransfer; splitIsoTransfer->PacketCount = 0; splitIsoTransfer->SystemAdderess = systemAddress; splitLength = 0; i = 0; InsertTailList(TransferList, &splitTransfer->TransferLink); InsertTailList(Transfer->SplitTransferList, &splitTransfer->SplitLink); while (1) { if (splitLength + isoTransfer->Packets[pkt].Length > maxSplitLength) { // this transfer is filled, move to the next one systemAddress += splitLength; break; } else { splitIsoTransfer->Packets[i] = isoTransfer->Packets[pkt]; splitLength += splitIsoTransfer->Packets[i].Length; splitIsoTransfer->PacketCount++; pkt++; i++; } } } while (pkt < isoTransfer->PacketCount); } #endif VOID USBPORT_SplitTransfer( PDEVICE_OBJECT FdoDeviceObject, PHCD_ENDPOINT Endpoint, PHCD_TRANSFER_CONTEXT Transfer, PLIST_ENTRY TransferList ) /*++ Routine Description: Splits a transfer into multiple transfers of the proper size for the miniport. Returns a list of transfer structures that need to be added to the active list. If the transfer does not need to be split the list will contain only the original transfer. Arguments: Return Value: none --*/ { InitializeListHead(TransferList); InitializeListHead(&Transfer->SplitTransferList); Transfer->UsbdStatus = USBD_STATUS_SUCCESS; if (Transfer->Tp.TransferBufferLength <= EP_MAX_TRANSFER(Endpoint)) { // no split needed InsertTailList(TransferList, &Transfer->TransferLink); return; } switch(Endpoint->Parameters.TransferType) { case Interrupt: case Bulk: USBPORT_SplitBulkInterruptTransfer(FdoDeviceObject, Endpoint, Transfer, TransferList); break; case Control: // not supported yet // although currently not supported the USBD stack never // correctly implemented transfers > 4k so we fudge it here // BUGCHECK(USBBUGCODE_INTERNAL_ERROR, 0, 0, 0); break; case Isochronous: BUGCHECK(USBBUGCODE_INTERNAL_ERROR, 0, 0, 0); break; } } VOID USBPORT_DoneSplitTransfer( PHCD_TRANSFER_CONTEXT SplitTransfer ) /*++ Routine Description: Called when a split transfer is completed by hardware this function only completes active transfers Arguments: Return Value: --*/ { PHCD_TRANSFER_CONTEXT transfer; PHCD_ENDPOINT endpoint; PDEVICE_OBJECT fdoDeviceObject; KIRQL tIrql; endpoint = SplitTransfer->Endpoint; ASSERT_ENDPOINT(endpoint); fdoDeviceObject = endpoint->FdoDeviceObject; LOGENTRY(NULL, fdoDeviceObject, LOG_XFERS, 'dnSP', SplitTransfer, 0, 0); // get the parent transfer = SplitTransfer->Transfer; ASSERT_TRANSFER(transfer); // // copy the child data to the parent transfer // transfer->MiniportBytesTransferred += SplitTransfer->MiniportBytesTransferred; // error ? // if (SplitTransfer->UsbdStatus != USBD_STATUS_SUCCESS && !TEST_FLAG(SplitTransfer->Flags, USBPORT_TXFLAG_KILL_SPLIT)) { transfer->UsbdStatus = SplitTransfer->UsbdStatus; } ACQUIRE_TRANSFER_LOCK(fdoDeviceObject, transfer, tIrql); // remove this transfer from the list LOGENTRY(NULL, fdoDeviceObject, LOG_XFERS, 'rmSP', transfer, 0, SplitTransfer); RemoveEntryList(&SplitTransfer->SplitLink); // flush any triple buffers USBPORT_FlushAdapterDBs(fdoDeviceObject, SplitTransfer); // free this child UNSIG(SplitTransfer); FREE_POOL(fdoDeviceObject, SplitTransfer); // is the transfer complete? if (IsListEmpty(&transfer->SplitTransferList)) { LOGENTRY(NULL, fdoDeviceObject, LOG_XFERS, 'cpSP', transfer, 0, 0); RELEASE_TRANSFER_LOCK(fdoDeviceObject, transfer, tIrql); USBPORT_DoneTransfer(transfer); } else { RELEASE_TRANSFER_LOCK(fdoDeviceObject, transfer, tIrql); } } VOID USBPORT_CancelSplitTransfer( PDEVICE_OBJECT FdoDeviceObject, PHCD_TRANSFER_CONTEXT SplitTransfer ) /*++ Routine Description: Arguments: Return Value: --*/ { PHCD_TRANSFER_CONTEXT transfer; PHCD_ENDPOINT endpoint; PDEVICE_OBJECT fdoDeviceObject; KIRQL tIrql; endpoint = SplitTransfer->Endpoint; ASSERT_ENDPOINT(endpoint); fdoDeviceObject = endpoint->FdoDeviceObject; // remove the child, when all children are gone put the // parent on the cancel list LOGENTRY(NULL, fdoDeviceObject, LOG_XFERS, 'caSP', SplitTransfer, 0, 0); // get the parent transfer = SplitTransfer->Transfer; ASSERT_TRANSFER(transfer); // // copy the child data to the parent transfer // transfer->MiniportBytesTransferred += SplitTransfer->MiniportBytesTransferred; ACQUIRE_TRANSFER_LOCK(fdoDeviceObject, transfer, tIrql); // remove this transfer from the list LOGENTRY(NULL, fdoDeviceObject, LOG_XFERS, 'rmSP', transfer, 0, SplitTransfer); RemoveEntryList(&SplitTransfer->SplitLink); RELEASE_TRANSFER_LOCK(fdoDeviceObject, transfer, tIrql); // free this child UNSIG(SplitTransfer); FREE_POOL(fdoDeviceObject, SplitTransfer); // is the transfer complete? if (IsListEmpty(&transfer->SplitTransferList)) { LOGENTRY(NULL, fdoDeviceObject, LOG_XFERS, 'cpSC', transfer, 0, 0); InsertTailList(&endpoint->CancelList, &transfer->TransferLink); } }