diff --git a/src/add-ons/kernel/bus_managers/usb/Transfer.cpp b/src/add-ons/kernel/bus_managers/usb/Transfer.cpp index 85312a75f1..d763aca47c 100644 --- a/src/add-ons/kernel/bus_managers/usb/Transfer.cpp +++ b/src/add-ons/kernel/bus_managers/usb/Transfer.cpp @@ -14,6 +14,9 @@ Transfer::Transfer(Pipe *pipe) : fPipe(pipe), fVector(&fData), fVectorCount(0), + fBaseAddress(NULL), + fFragmented(false), + fActualLength(0), fCallback(NULL), fCallbackCookie(NULL), fRequestData(NULL) @@ -42,6 +45,7 @@ Transfer::SetRequestData(usb_request_data *data) void Transfer::SetData(uint8 *data, size_t dataLength) { + fBaseAddress = data; fData.iov_base = data; fData.iov_len = dataLength; @@ -56,6 +60,7 @@ Transfer::SetVector(iovec *vector, size_t vectorCount) fVector = new(std::nothrow) iovec[vectorCount]; memcpy(fVector, vector, vectorCount * sizeof(iovec)); fVectorCount = vectorCount; + fBaseAddress = fVector[0].iov_base; } @@ -65,10 +70,37 @@ Transfer::VectorLength() size_t length = 0; for (size_t i = 0; i < fVectorCount; i++) length += fVector[i].iov_len; + + // the data is to large and would overflow the allocator + if (length > USB_MAX_FRAGMENT_SIZE) { + length = USB_MAX_FRAGMENT_SIZE; + fFragmented = true; + } + return length; } +void +Transfer::AdvanceByFragment(size_t actualLength) +{ + size_t length = USB_MAX_FRAGMENT_SIZE; + for (size_t i = 0; i < fVectorCount; i++) { + if (fVector[i].iov_len <= length) { + length -= fVector[i].iov_len; + fVector[i].iov_len = 0; + continue; + } + + (uint8 *)fVector[i].iov_base += length; + fVector[i].iov_len -= length; + break; + } + + fActualLength += actualLength; +} + + void Transfer::SetCallback(usb_callback_func callback, void *cookie) { @@ -81,5 +113,6 @@ void Transfer::Finished(uint32 status, size_t actualLength) { if (fCallback) - fCallback(fCallbackCookie, status, fVector[0].iov_base, actualLength); + fCallback(fCallbackCookie, status, fBaseAddress, + fActualLength + actualLength); } diff --git a/src/add-ons/kernel/bus_managers/usb/usb_p.h b/src/add-ons/kernel/bus_managers/usb/usb_p.h index 03f36fc651..46fec81c79 100644 --- a/src/add-ons/kernel/bus_managers/usb/usb_p.h +++ b/src/add-ons/kernel/bus_managers/usb/usb_p.h @@ -85,6 +85,7 @@ typedef enum { #define USB_OBJECT_DEVICE 0x00000040 #define USB_OBJECT_HUB 0x00000080 +#define USB_MAX_FRAGMENT_SIZE B_PAGE_SIZE * 96 class Stack { public: @@ -528,6 +529,9 @@ public: size_t VectorCount() { return fVectorCount; }; size_t VectorLength(); + bool IsFragmented() { return fFragmented; }; + void AdvanceByFragment(size_t actualLength); + void SetCallback(usb_callback_func callback, void *cookie); @@ -539,6 +543,9 @@ private: iovec fData; iovec *fVector; size_t fVectorCount; + void *fBaseAddress; + bool fFragmented; + size_t fActualLength; usb_callback_func fCallback; void *fCallbackCookie; diff --git a/src/add-ons/kernel/busses/usb/ehci.cpp b/src/add-ons/kernel/busses/usb/ehci.cpp index d05400712f..05b505b8e3 100644 --- a/src/add-ons/kernel/busses/usb/ehci.cpp +++ b/src/add-ons/kernel/busses/usb/ehci.cpp @@ -1055,11 +1055,11 @@ EHCI::FinishTransfers() #ifndef HAIKU_TARGET_PLATFORM_HAIKU area_id clonedArea = -1; + void *clonedMemory = NULL; if (transfer->user_area >= B_OK) { // we got a userspace output buffer, need to clone // the area for that space first and map the iovecs // to this cloned area. - void *clonedMemory = NULL; clonedArea = clone_area("userspace accessor", &clonedMemory, B_ANY_ADDRESS, B_WRITE_AREA | B_KERNEL_WRITE_AREA, @@ -1076,8 +1076,11 @@ EHCI::FinishTransfers() &nextDataToggle); #ifndef HAIKU_TARGET_PLATFORM_HAIKU - if (clonedArea >= B_OK) + if (clonedArea >= B_OK) { + for (size_t i = 0; i < vectorCount; i++) + (uint8 *)vector[i].iov_base -= (addr_t)clonedMemory; delete_area(clonedArea); + } #endif // !HAIKU_TARGET_PLATFORM_HAIKU } else { // calculate transfered length @@ -1086,8 +1089,24 @@ EHCI::FinishTransfers() &nextDataToggle); } - UnlinkQueueHead(transfer->queue_head, &fFreeListHead); transfer->transfer->TransferPipe()->SetDataToggle(nextDataToggle); + if (transfer->transfer->IsFragmented()) { + // this transfer may still have data left + transfer->transfer->AdvanceByFragment(actualLength); + if (transfer->transfer->VectorLength() > 0) { + FreeDescriptorChain(transfer->data_descriptor); + FillQueueWithData(transfer->transfer, + transfer->queue_head, + &transfer->data_descriptor, NULL); + break; + } + + // the transfer is done, but we already set the + // actualLength with AdvanceByFragment() + actualLength = 0; + } + + UnlinkQueueHead(transfer->queue_head, &fFreeListHead); transfer->transfer->Finished(B_OK, actualLength); transferDone = true; break; @@ -1348,7 +1367,8 @@ EHCI::FillQueueWithData(Transfer *transfer, ehci_qh *queueHead, queueHead->overlay.alt_next_phy = EHCI_QTD_TERMINATE; *_dataDescriptor = firstDescriptor; - *_directionIn = directionIn; + if (_directionIn) + *_directionIn = directionIn; return B_OK; } diff --git a/src/add-ons/kernel/busses/usb/uhci.cpp b/src/add-ons/kernel/busses/usb/uhci.cpp index a0e451ba6f..615a19ad4d 100644 --- a/src/add-ons/kernel/busses/usb/uhci.cpp +++ b/src/add-ons/kernel/busses/usb/uhci.cpp @@ -506,6 +506,13 @@ UHCI::SubmitTransfer(Transfer *transfer) if (transfer->TransferPipe()->Type() & USB_OBJECT_CONTROL_PIPE) return SubmitRequest(transfer); + return SubmitTransfer(transfer, false); +} + + +status_t +UHCI::SubmitTransfer(Transfer *transfer, bool resubmit) +{ Pipe *pipe = transfer->TransferPipe(); bool directionIn = (pipe->Direction() == Pipe::In); @@ -539,6 +546,12 @@ UHCI::SubmitTransfer(Transfer *transfer) } uhci_qh *transferQueue = CreateTransferQueue(firstDescriptor); + if (resubmit) { + // the transfer is already pending, we just submit another fragment + queue->AppendTransfer(transferQueue); + return B_OK; + } + result = AddPendingTransfer(transfer, queue, transferQueue, firstDescriptor, firstDescriptor, directionIn); if (result < B_OK) { @@ -818,11 +831,11 @@ UHCI::FinishTransfers() #ifndef HAIKU_TARGET_PLATFORM_HAIKU area_id clonedArea = -1; + void *clonedMemory = NULL; if (transfer->user_area >= B_OK) { // we got a userspace output buffer, need to clone // the area for that space first and map the iovecs // to this cloned area. - void *clonedMemory = NULL; clonedArea = clone_area("userspace accessor", &clonedMemory, B_ANY_ADDRESS, B_WRITE_AREA | B_KERNEL_WRITE_AREA, @@ -839,8 +852,11 @@ UHCI::FinishTransfers() &lastDataToggle); #ifndef HAIKU_TARGET_PLATFORM_HAIKU - if (clonedArea >= B_OK) + if (clonedArea >= B_OK) { + for (size_t i = 0; i < vectorCount; i++) + (uint8 *)vector[i].iov_base -= (addr_t)clonedMemory; delete_area(clonedArea); + } #endif // !HAIKU_TARGET_PLATFORM_HAIKU } else { // read the actual length that was sent @@ -848,9 +864,24 @@ UHCI::FinishTransfers() transfer->first_descriptor, &lastDataToggle); } + transfer->transfer->TransferPipe()->SetDataToggle(lastDataToggle == 0); + if (transfer->transfer->IsFragmented()) { + // this transfer may still have data left + transfer->transfer->AdvanceByFragment(actualLength); + if (transfer->transfer->VectorLength() > 0) { + // resubmit the advanced transfer so the rest + // of the buffers are transmitted over the bus + SubmitTransfer(transfer->transfer, true); + break; + } + + // the transfer is done, but we already set the + // actualLength with AdvanceByFragment() + actualLength = 0; + } + FreeDescriptorChain(transfer->first_descriptor); FreeTransferQueue(transfer->transfer_queue); - transfer->transfer->TransferPipe()->SetDataToggle(lastDataToggle == 0); transfer->transfer->Finished(B_OK, actualLength); transferDone = true; break; @@ -873,7 +904,6 @@ UHCI::FinishTransfers() delete transfer->transfer; delete transfer; transfer = next; - Unlock(); } } else { diff --git a/src/add-ons/kernel/busses/usb/uhci.h b/src/add-ons/kernel/busses/usb/uhci.h index c811bb5ec8..796f08eaf8 100644 --- a/src/add-ons/kernel/busses/usb/uhci.h +++ b/src/add-ons/kernel/busses/usb/uhci.h @@ -68,6 +68,7 @@ public: status_t Start(); virtual status_t SubmitTransfer(Transfer *transfer); + status_t SubmitTransfer(Transfer *transfer, bool resubmit); status_t SubmitRequest(Transfer *transfer); static status_t AddTo(Stack *stack);