/*++ Copyright (C) 1999- Microsoft Corporation Module Name: camusb.cpp Abstract: This module implements CUsbCamera object Author: William Hsieh (williamh) created Revision History: --*/ #include "ptppch.h" #include #include #include // // Private IOCTL to workaround #446466 (Whistler) // #define IOCTL_SEND_USB_REQUEST_PTP CTL_CODE(FILE_DEVICE_USB_SCAN,IOCTL_INDEX+20,METHOD_BUFFERED,FILE_ANY_ACCESS) // // Constructor for CUsbCamera // CUsbCamera::CUsbCamera() : m_hUSB(NULL), m_hEventUSB(NULL), m_hEventCancel(NULL), m_hEventRead(NULL), m_pUsbData(NULL), m_UsbDataSize(0), m_prevOpCode(0), m_prevTranId(0) { DBG_FN("CUsbCamera::CUsbCamera"); memset(&m_EndpointInfo, NULL, sizeof(m_EndpointInfo)); } CUsbCamera::~CUsbCamera() { } // // This function takes care of USB-specific processing for opening // a connection with a device. // // Input: // DevicePortName -- name used to access device via CreateFile // pIPTPEventCB -- IWiaPTPEventCallback interface pointer // HRESULT CUsbCamera::Open( LPWSTR DevicePortName, PTPEventCallback pPTPEventCB, PTPDataCallback pPTPDataCB, LPVOID pEventParam, BOOL bEnableEvents ) { USES_CONVERSION; DBG_FN("CUsbCamera::Open"); HRESULT hr = S_OK; // // Call the base class Open function first // hr = CPTPCamera::Open(DevicePortName, pPTPEventCB, pPTPDataCB, pEventParam, bEnableEvents); if (FAILED(hr)) { wiauDbgError("Open", "base class Open failed"); return hr; } // // Open another handle to talk with the device, to work around possible // bug in Usbscan.sys // m_hEventUSB = ::CreateFile(W2T(DevicePortName), // file name GENERIC_READ | GENERIC_WRITE, // desired access 0, // sharing mode NULL, // security descriptor OPEN_EXISTING, // creation disposition FILE_FLAG_OVERLAPPED, // file attributes NULL // template file ); if (m_hEventUSB == INVALID_HANDLE_VALUE) { hr = HRESULT_FROM_WIN32(::GetLastError()); wiauDbgErrorHr(hr, "Open", "CreateFile failed"); m_hUSB = NULL; return hr; } // // Open a handle to talk with the device // m_hUSB = ::CreateFile(W2T(DevicePortName), // file name GENERIC_READ | GENERIC_WRITE, // desired access 0, // sharing mode NULL, // security descriptor OPEN_EXISTING, // creation disposition 0, // file attributes NULL // template file ); if (m_hUSB == INVALID_HANDLE_VALUE) { hr = HRESULT_FROM_WIN32(::GetLastError()); wiauDbgErrorHr(hr, "Open", "Second CreateFile failed"); m_hUSB = NULL; return hr; } // // Create event handle that will cancel interrupt pipe read // m_hEventCancel = CreateEvent(NULL, TRUE, FALSE, NULL); if (!m_hEventCancel) { hr = HRESULT_FROM_WIN32(::GetLastError()); wiauDbgErrorHr(hr, "Open", "CreateEvent failed"); return hr; } // // Create event handle for reading interrupt pipe // m_hEventRead = CreateEvent(NULL, TRUE, FALSE, NULL); if (!m_hEventRead) { hr = HRESULT_FROM_WIN32(::GetLastError()); wiauDbgErrorHr(hr, "Open", "CreateEvent failed"); return hr; } // // Set up array used by WaitForMultipleObjects // m_EventHandles[0] = m_hEventCancel; m_EventHandles[1] = m_hEventRead; // // Get the pipe configuration information of each pipe // USBSCAN_PIPE_CONFIGURATION PipeCfg; DWORD BytesReturned; if (!DeviceIoControl(m_hUSB, IOCTL_GET_PIPE_CONFIGURATION, NULL, 0, &PipeCfg, sizeof(PipeCfg), &BytesReturned, NULL)) { hr = HRESULT_FROM_WIN32(::GetLastError()); wiauDbgErrorHr(hr, "Open", "get pipe config DeviceIoControl failed"); return hr; } // // Loop through the pipe configurations and store the information we'll need // later (maximum packet size and address). Also make sure there is at least // one endpoint of each: bulk-in, bulk-out, and interrupt. // USBSCAN_PIPE_INFORMATION *pPipeInfo; // Temporary pointer for (ULONG count = 0; count < PipeCfg.NumberOfPipes; count++) { pPipeInfo = &PipeCfg.PipeInfo[count]; switch (pPipeInfo->PipeType) { case USBSCAN_PIPE_BULK: if (pPipeInfo->EndpointAddress & BULKIN_FLAG) { m_EndpointInfo.BulkInMaxSize = pPipeInfo->MaximumPacketSize; m_EndpointInfo.BulkInAddress = pPipeInfo->EndpointAddress; wiauDbgTrace("Open", "found a bulk-in endpoint, address = 0x%04x, packet size = %d, index = %d", pPipeInfo->EndpointAddress, pPipeInfo->MaximumPacketSize, count); } else { m_EndpointInfo.BulkOutMaxSize = pPipeInfo->MaximumPacketSize; m_EndpointInfo.BulkOutAddress = pPipeInfo->EndpointAddress; wiauDbgTrace("Open", "found a bulk-out endpoint, address = 0x%04x, packet size = %d, index = %d", pPipeInfo->EndpointAddress, pPipeInfo->MaximumPacketSize, count); } break; case USBSCAN_PIPE_INTERRUPT: m_EndpointInfo.InterruptMaxSize = pPipeInfo->MaximumPacketSize; m_EndpointInfo.InterruptAddress = pPipeInfo->EndpointAddress; wiauDbgTrace("Open", "found an interrupt endpoint, address = 0x%02x, packet size = %d, index = %d", pPipeInfo->EndpointAddress, pPipeInfo->MaximumPacketSize, count); break; default: wiauDbgTrace("Open", "found an endpoint of unknown type, type = 0x%04x, address = 0x%02x, packet size = %d, index = %d", pPipeInfo->PipeType, pPipeInfo->EndpointAddress, pPipeInfo->MaximumPacketSize, count); } } // // Each of these endpoints must be present and have non-zero packet size // if (!m_EndpointInfo.BulkInMaxSize || !m_EndpointInfo.BulkOutMaxSize || !m_EndpointInfo.InterruptMaxSize) { wiauDbgError("Open", "At least one endpoint is invalid"); return E_FAIL; } // // Allocate a re-usable buffer for handling the USB header during reads // and writes. It needs to be large enough to hold one packet and large // enough to hold a USB header. // m_UsbDataSize = max(m_EndpointInfo.BulkInMaxSize, m_EndpointInfo.BulkOutMaxSize); while (m_UsbDataSize < sizeof(m_pUsbData->Header)) { m_UsbDataSize += m_UsbDataSize; } m_pUsbData = (PUSB_PTP_DATA) new BYTE[m_UsbDataSize]; if (!m_pUsbData) { wiauDbgError("Open", "memory allocation failed"); return E_OUTOFMEMORY; } return hr; } // // This function closes the connection to the camera // HRESULT CUsbCamera::Close() { DBG_FN("CUsbCamera::Close"); HRESULT hr = S_OK; // // Call the base class Close function first // hr = CPTPCamera::Close(); if (FAILED(hr)) { wiauDbgError("Close", "base class Close failed"); } // // Signal event to cancel interrupt pipe I/O // if (!SetEvent(m_hEventCancel)) { hr = HRESULT_FROM_WIN32(::GetLastError()); wiauDbgErrorHr(hr, "Close", "SetEvent failed"); } else { if (m_bEventsEnabled) { // // We need to wait until event thread finishes, otherwise driver DLL may get unloaded // with a running thread in it // DWORD ret = WaitForSingleObject(m_hEventThread, INFINITE); if (ret != WAIT_OBJECT_0) { hr = HRESULT_FROM_WIN32(::GetLastError()); wiauDbgErrorHr(hr, "Close", "WaitForSingleObject failed"); } } } // // Close handle to the event thread // if (m_hEventThread) { CloseHandle(m_hEventThread); m_hEventThread = NULL; } // // Close the file handles and event handles // if (m_hUSB) { CloseHandle(m_hUSB); m_hUSB = NULL; } if (m_hEventUSB) { CloseHandle(m_hEventUSB); m_hEventUSB = NULL; } if (m_hEventCancel) { CloseHandle(m_hEventCancel); m_hEventCancel = NULL; } if (m_hEventRead) { CloseHandle(m_hEventRead); m_hEventRead = NULL; } // // Free memory used for reading/writing data // if (m_pUsbData) { delete[] (BYTE*)m_pUsbData; m_pUsbData = NULL; } return hr; } // // This function writes a command buffer to the device // // Input: // pCommand -- pointer to the command to send // NumParams -- number of parameters in the command // HRESULT CUsbCamera::SendCommand( PTP_COMMAND *pCommand, UINT NumParams ) { DBG_FN("CUsbCamera::SendCommand"); HRESULT hr = S_OK; if (!pCommand || NumParams > COMMAND_NUMPARAMS_MAX) { wiauDbgError("SendCommand", "invalid arg"); return E_INVALIDARG; } // // Check for the reset command, and send it via the control pipe instead // if (pCommand->OpCode == PTP_OPCODE_RESETDEVICE) { wiauDbgTrace("SendCommand", "sending reset request"); hr = ResetDevice(); if (FAILED(hr)) { wiauDbgError("SendCommand", "ResetDevice failed"); return hr; } } else { // // Put the PTP command into a USB container // m_UsbCommand.Header.Len = sizeof(m_UsbCommand.Header) + sizeof(DWORD) * NumParams; m_UsbCommand.Header.Type = PTPCONTAINER_TYPE_COMMAND; m_UsbCommand.Header.Code = pCommand->OpCode; m_UsbCommand.Header.TransactionId = pCommand->TransactionId; if (NumParams > 0) { memcpy(m_UsbCommand.Params, pCommand->Params, sizeof(DWORD) * NumParams); } // // Send the command to the device // DWORD BytesWritten = 0; wiauDbgTrace("SendCommand", "writing command"); if (!WriteFile(m_hUSB, &m_UsbCommand, m_UsbCommand.Header.Len, &BytesWritten, NULL)) { hr = HRESULT_FROM_WIN32(::GetLastError()); wiauDbgErrorHr(hr, "SendCommand", "WriteFile failed"); return hr; } if (BytesWritten != m_UsbCommand.Header.Len) { wiauDbgError("SendCommand", "wrong amount of data written = %d", BytesWritten); return E_FAIL; } // // If the amount written is a multiple of the packet size, send a null packet // if (m_UsbCommand.Header.Len % m_EndpointInfo.BulkOutMaxSize == 0) { wiauDbgTrace("SendCommand", "sending null packet"); if (!WriteFile(m_hUSB, NULL, 0, &BytesWritten, NULL)) { hr = HRESULT_FROM_WIN32(::GetLastError()); wiauDbgErrorHr(hr, "SendCommand", "second WriteFile failed"); return hr; } if (BytesWritten != 0) { wiauDbgError("SendCommand", "wrong amount of data written = %d -", BytesWritten); return E_FAIL; } } } // // Save the opcode, because we need it for the data container header // m_prevOpCode = pCommand->OpCode; m_prevTranId = pCommand->TransactionId; wiauDbgTrace("SendCommand", "command successfully sent"); return hr; } // // This function reads bulk data from the device // // Input: // pData -- pointer to a buffer to receive read data // BufferSize -- size of buffer // HRESULT CUsbCamera::ReadData( BYTE *pData, UINT *pBufferSize ) { DBG_FN("CUsbCamera::ReadData"); HRESULT hr = S_OK; BOOL bAbortTransfer = FALSE; if (!pData || !pBufferSize || *pBufferSize == 0) { wiauDbgError("ReadData", "invalid arg"); return E_INVALIDARG; } // // First read the header from the device // memset(m_pUsbData, NULL, m_UsbDataSize); DWORD BytesRead = 0; wiauDbgTrace("ReadData", "reading data header"); if (!ReadFile(m_hUSB, m_pUsbData, sizeof(m_pUsbData->Header), &BytesRead, NULL)) { hr = HRESULT_FROM_WIN32(::GetLastError()); wiauDbgErrorHr(hr, "ReadData", "ReadFile failed"); return hr; } if (BytesRead != sizeof(m_pUsbData->Header)) { wiauDbgError("ReadData", "wrong amount of data read = %d", BytesRead); return E_FAIL; } // // Check the type code in the header to make sure it's correct // if (m_pUsbData->Header.Type != PTPCONTAINER_TYPE_DATA) { wiauDbgError("ReadData", "expected a data header but received type = %d", m_pUsbData->Header.Type); return E_FAIL; } // // Check the opcode and transaction id in the header just to make sure they are correct // if ((m_pUsbData->Header.Code != m_prevOpCode) || (m_pUsbData->Header.TransactionId != m_prevTranId)) { wiauDbgError("ReadData", "fields in the data header were incorrect, opcode=0x%04x tranid=0x%08x", m_pUsbData->Header.Code, m_pUsbData->Header.TransactionId); return E_FAIL; } // // Loop, reading the data. The callback function will be called at least 10 times during // the transfer. More if the buffer size is small. // LONG lOffset = 0; UINT BytesToRead = 0; UINT TotalRead = 0; UINT TotalToRead = m_pUsbData->Header.Len - sizeof(m_pUsbData->Header); UINT TotalRemaining = TotalToRead; // // Make sure the buffer is large enough, unless a callback function is being used // if (m_pDataCallbackParam == NULL && *pBufferSize < TotalToRead) { wiauDbgError("ReadData", "buffer is too small"); return E_FAIL; } // // When doing callbacks, read the data in chunk sizes slightly // larger the 1/10 the total and divisible by 4. // if (m_pDataCallbackParam) BytesToRead = (TotalToRead / 40 + 1) * 4; else BytesToRead = *pBufferSize; // // Set time out values for Usbscan // USBSCAN_TIMEOUT TimeOut; DWORD BytesReturned = 0; TimeOut.TimeoutRead = PTP_READ_TIMEOUT + max(BytesToRead / 100000, 114); TimeOut.TimeoutWrite = PTP_WRITE_TIMEOUT; TimeOut.TimeoutEvent = PTP_EVENT_TIMEOUT; if (!DeviceIoControl(m_hUSB, IOCTL_SET_TIMEOUT, &TimeOut, sizeof(TimeOut), NULL, 0, &BytesReturned, NULL)) { hr = HRESULT_FROM_WIN32(::GetLastError()); wiauDbgErrorHr(hr, "Open", "set timeout DeviceIoControl failed"); return hr; } while (TotalRemaining > 0) { // // Make sure the amount to read is never larger than the buffer size. The buffer size may // be updated by the callback function. // if (BytesToRead > *pBufferSize) BytesToRead = *pBufferSize; // // On the last read, the bytes to read may need to be reduced // if (BytesToRead > TotalRemaining) BytesToRead = TotalRemaining; wiauDbgTrace("ReadData", "reading a chunk of data = %d", BytesToRead); BytesRead = 0; if (!ReadFile(m_hUSB, pData, BytesToRead, &BytesRead, NULL)) { hr = HRESULT_FROM_WIN32(::GetLastError()); wiauDbgErrorHr(hr, "ReadData", "ReadFile failed"); return hr; } if ((BytesRead > *pBufferSize) || (BytesRead != BytesToRead)) { wiauDbgError("ReadData", "wrong amount of data read = %d -", BytesRead); return E_FAIL; } TotalRemaining -= BytesRead; TotalRead += BytesRead; if (m_pDataCallbackParam && !bAbortTransfer) { // // Call the callback function reporting percent complete, offset, and amount read. // The callback may update pData and BufferSize. // hr = m_pPTPDataCB(m_pDataCallbackParam, (TotalRead * 100 / TotalToRead), lOffset, BytesRead, &pData, (LONG *) pBufferSize); if (FAILED(hr)) { // // Report the error // wiauDbgErrorHr(hr, "ReadData", "data callback failed"); } // // Check if caller wants to cancel the transfer or returns error // if (hr == S_FALSE || FAILED(hr)) { // // Do not send CancelRequest to cameras that do not support it, just read the // remainder of the object without reporting progress and return S_FALSE. // // Cameras not supporting CancelRequest are: // all Sony cameras with DeviceVersion < 1.0004 // Nikon E2500 with DeviceVersion = 1.0 // const double NIKON_E2500_VERSION_NOT_SUPPORTING_CANCEL = 1.0; const double MIN_SONY_VERSION_SUPPORTING_CANCEL = 1.0004; if ((GetHackModel() == HACK_MODEL_NIKON_E2500 && GetHackVersion() == NIKON_E2500_VERSION_NOT_SUPPORTING_CANCEL) || (GetHackModel() == HACK_MODEL_SONY && GetHackVersion() < MIN_SONY_VERSION_SUPPORTING_CANCEL)) { wiauDbgWarning("ReadData", "Transfer cancelled, reading but ignoring remainder of the object (%d bytes)", TotalRemaining); bAbortTransfer = TRUE; m_Phase = CAMERA_PHASE_RESPONSE; // camera will send response hr = S_OK; } else { wiauDbgWarning("ReadData", "Transfer cancelled, aborting current transfer"); hr = SendCancelRequest(m_prevTranId); if (FAILED(hr)) { wiauDbgErrorHr(hr, "ReadData", "SendCancelRequest failed"); return hr; } m_Phase = CAMERA_PHASE_IDLE; // camera will not send response return S_FALSE; } } } // // Increment the offset // lOffset += BytesRead; } if ((TotalRead + sizeof(m_pUsbData->Header)) % m_EndpointInfo.BulkInMaxSize == 0) { // // Read the extra null packet // wiauDbgTrace("ReadData", "reading a null packet"); BytesRead = 0; if (!ReadFile(m_hUSB, m_pUsbData, m_UsbDataSize, &BytesRead, NULL)) { hr = HRESULT_FROM_WIN32(::GetLastError()); wiauDbgErrorHr(hr, "ReadData", "ReadFile failed"); return hr; } if (BytesRead != 0) { wiauDbgError("ReadData", "tried to read null packet but read %d bytes instead", BytesRead); return E_FAIL; } } *pBufferSize = TotalRead; wiauDbgTrace("ReadData", "%d bytes of data successfully read", TotalRead); if (bAbortTransfer) hr = S_FALSE; return hr; } // // This function writes bulk data to the device // // Input: // pData -- pointer to a buffer of data to write // BufferSize -- amount of data to write // HRESULT CUsbCamera::SendData( BYTE *pData, UINT BufferSize ) { DBG_FN("CUsbCamera::SendData"); HRESULT hr = S_OK; if (!pData || BufferSize == 0) { wiauDbgError("SendData", "invalid arg"); return E_INVALIDARG; } // // Figure out how many packets it will take to contain the header // UINT BytesToWrite = m_EndpointInfo.BulkOutMaxSize; while (BytesToWrite < sizeof(m_pUsbData->Header)) { BytesToWrite += m_EndpointInfo.BulkOutMaxSize; } // // The first write will contain the USB header plus as much of the data as it // takes to fill out the packet. We need to write full packets, otherwise the device // will think the transfer is done. // UINT FirstWriteDataAmount = min(BufferSize, BytesToWrite - sizeof(m_pUsbData->Header)); BytesToWrite = sizeof(m_pUsbData->Header) + FirstWriteDataAmount; // // Fill out header fields // m_pUsbData->Header.Len = BufferSize + sizeof(m_pUsbData->Header); m_pUsbData->Header.Type = PTPCONTAINER_TYPE_DATA; m_pUsbData->Header.Code = m_prevOpCode; m_pUsbData->Header.TransactionId = m_prevTranId; // // Copy the part of the data needed to fill out the packets // memcpy(m_pUsbData->Data, pData, FirstWriteDataAmount); // // Write the header plus partial data // wiauDbgTrace("SendData", "Writing first packet, length = %d", BytesToWrite); DWORD BytesWritten = 0; if (!WriteFile(m_hUSB, m_pUsbData, BytesToWrite, &BytesWritten, NULL)) { hr = HRESULT_FROM_WIN32(::GetLastError()); wiauDbgErrorHr(hr, "SendData", "WriteFile failed"); return hr; } if (BytesWritten != BytesToWrite) { wiauDbgError("SendData", "wrong amount of data written = %d", BytesWritten); return E_FAIL; } UINT TotalBytesWritten = BytesWritten; // // The next write (if necessary) will include the remainder of the data // if (BufferSize > FirstWriteDataAmount) { BytesToWrite = BufferSize - FirstWriteDataAmount; BytesWritten = 0; wiauDbgTrace("SendData", "writing remainder of data, length = %d", BytesToWrite); if (!WriteFile(m_hUSB, &pData[FirstWriteDataAmount], BytesToWrite, &BytesWritten, NULL)) { hr = HRESULT_FROM_WIN32(::GetLastError()); wiauDbgErrorHr(hr, "SendData", "second WriteFile failed"); return hr; } if (BytesWritten != BytesToWrite) { wiauDbgError("SendData", "wrong amount of data written = %d -", BytesWritten); return E_FAIL; } TotalBytesWritten += BytesWritten; } // // If the amount written is exactly a multiple of the packet size, send an empty packet // so the device knows we are done sending data // if (TotalBytesWritten % m_EndpointInfo.BulkOutMaxSize == 0) { BytesWritten = 0; wiauDbgTrace("SendData", "writing null packet"); if (!WriteFile(m_hUSB, NULL, 0, &BytesWritten, NULL)) { hr = HRESULT_FROM_WIN32(::GetLastError()); wiauDbgErrorHr(hr, "SendData", "third WriteFile failed"); return hr; } if (BytesWritten != 0) { wiauDbgError("SendData", "wrong amount of data written = %d --", BytesWritten); return E_FAIL; } } wiauDbgTrace("SendData", "%d bytes of data successfully written", TotalBytesWritten); return hr; } // // This function reads the response data from the device // // Input: // pResponse -- pointer to a response structure to receive the response data // HRESULT CUsbCamera::ReadResponse( PTP_RESPONSE *pResponse ) { DBG_FN("CUsbCamera::ReadResponse"); HRESULT hr = S_OK; if (!pResponse) { wiauDbgError("ReadResponse", "invalid arg"); return E_INVALIDARG; } // // Handle response from reset command // if (m_prevOpCode == PTP_OPCODE_RESETDEVICE) { wiauDbgTrace("ReadResponse", "creating reset response"); pResponse->ResponseCode = PTP_RESPONSECODE_OK; pResponse->SessionId = m_SessionId; pResponse->TransactionId = m_prevTranId; } else { // // Clear the USB response buffer // memset(&m_UsbResponse, NULL, sizeof(m_UsbResponse)); // // Read the response from the device // DWORD BytesRead = 0; wiauDbgTrace("ReadResponse", "reading response"); if (!ReadFile(m_hUSB, &m_UsbResponse, sizeof(m_UsbResponse), &BytesRead, NULL)) { hr = HRESULT_FROM_WIN32(::GetLastError()); wiauDbgErrorHr(hr, "ReadResponse", "ReadFile failed"); return hr; } if ((BytesRead < sizeof(m_UsbResponse.Header)) || (BytesRead > sizeof(m_UsbResponse))) { wiauDbgError("ReadResponse", "wrong amount of data read = %d", BytesRead); return E_FAIL; } // // Check the type code in the response to make sure it's correct // if (m_UsbResponse.Header.Type != PTPCONTAINER_TYPE_RESPONSE) { wiauDbgError("ReadResponse", "expected a response but received type = %d", m_UsbResponse.Header.Type); return E_FAIL; } // // Unwrap the PTP response from the USB container // pResponse->ResponseCode = m_UsbResponse.Header.Code; pResponse->SessionId = m_SessionId; // USB doesn't care about session id, so just use the one we have stored pResponse->TransactionId = m_UsbResponse.Header.TransactionId; DWORD ParamLen = BytesRead - sizeof(m_UsbResponse.Header); if (ParamLen > 0) { memcpy(pResponse->Params, m_UsbResponse.Params, ParamLen); } } wiauDbgTrace("ReadResponse", "response successfully read"); return hr; } // // This function reads event data from the device // // Input: // pEvent -- pointer to a PTP event structure to receive the event data // HRESULT CUsbCamera::ReadEvent( PTP_EVENT *pEvent ) { DBG_FN("CUsbCamera::ReadEvent"); HRESULT hr = S_OK; if (!pEvent) { wiauDbgError("ReadEvent", "invalid arg"); return E_INVALIDARG; } // // Allocate buffer for reading event from camera. It should be big enough to accomodate // packet of maximum allowed size, otherwise we'll get INVALID_ARG // DWORD cbEventBufSize = max(sizeof(USB_PTP_EVENT), m_EndpointInfo.InterruptMaxSize); USB_PTP_EVENT *pEventBuf = (USB_PTP_EVENT*) new BYTE[cbEventBufSize]; if (pEventBuf == NULL) { wiauDbgError("ReadEvent", "Memory allocation failed"); hr = E_OUTOFMEMORY; goto Cleanup; } memset(pEventBuf, 0, cbEventBufSize); // // Read the event from the device. DeviceIoControl is called in overlapped mode. If // no information is ready on the interrupt pipe, GetOverlappedResult will wait for // data to arrive. Unfortunately, DeviceIoControl returns after each packet, so keep // reading until a short packet is received. // DWORD BytesRead = 0; DWORD TotalBytesRead = 0; BOOL bReceivedShortPacket = FALSE; BYTE *pData = (BYTE*) pEventBuf; wiauDbgTrace("ReadEvent", "reading event"); while (!bReceivedShortPacket) { if (!ResetEvent(m_hEventRead)) { hr = HRESULT_FROM_WIN32(::GetLastError()); wiauDbgErrorHr(hr, "ReadEvent", "ResetEvent failed"); return hr; } memset(&m_Overlapped, 0, sizeof(OVERLAPPED)); m_Overlapped.hEvent = m_hEventRead; if (!DeviceIoControl(m_hEventUSB, IOCTL_WAIT_ON_DEVICE_EVENT, NULL, 0, pData, cbEventBufSize - TotalBytesRead, &BytesRead, &m_Overlapped)) { hr = HRESULT_FROM_WIN32(::GetLastError()); if (hr == HRESULT_FROM_WIN32(ERROR_IO_PENDING)) { hr = S_OK; DWORD ret; wiauDbgTrace("ReadEvent", "waiting for interrupt pipe data"); ret = WaitForMultipleObjects(2, m_EventHandles, FALSE, INFINITE); if (ret == WAIT_FAILED) { hr = HRESULT_FROM_WIN32(::GetLastError()); wiauDbgErrorHr(hr, "ReadEvent", "WaitForMultipleObjects failed"); goto Cleanup; } else if (ret == WAIT_OBJECT_0) { // // Indicate to caller that I/O was cancelled // wiauDbgTrace("ReadEvent", "Cancelling I/O on the interrupt pipe"); hr = S_FALSE; HRESULT temphr = S_OK; // // Cancel the pending I/O on the interrupt pipe // if (!CancelIo(m_hEventUSB)) { temphr = HRESULT_FROM_WIN32(::GetLastError()); wiauDbgErrorHr(hr, "ReadEvent", "CancelIo failed"); } // // Exit point when I/O is cancelled!!! // goto Cleanup; } else { // // Get result of read // if (!GetOverlappedResult(m_hEventUSB, &m_Overlapped, &BytesRead, TRUE)) { hr = HRESULT_FROM_WIN32(::GetLastError()); wiauDbgErrorHr(hr, "ReadEvent", "GetOverlappedResult failed"); goto Cleanup; } } } else { wiauDbgErrorHr(hr, "ReadEvent", "DeviceIoControl failed"); goto Cleanup; } } if (BytesRead == 0) { bReceivedShortPacket = TRUE; } else { TotalBytesRead += BytesRead; pData += BytesRead; bReceivedShortPacket = (BytesRead % m_EndpointInfo.InterruptMaxSize != 0); } } // // Verify that camera sent correct amount of data // if ((TotalBytesRead < sizeof(USB_PTP_HEADER)) || (TotalBytesRead > sizeof(USB_PTP_EVENT))) { wiauDbgError("ReadEvent", "wrong amount of data read by DeviceIoControl = %d", TotalBytesRead); hr = E_FAIL; goto Cleanup; } // // Check the type code in the response to make sure it's correct // if (pEventBuf->Header.Type != PTPCONTAINER_TYPE_EVENT) { wiauDbgError("ReadEvent", "expected an event but received type = %d", pEventBuf->Header.Type); hr = E_FAIL; goto Cleanup; } // // Unwrap the PTP event from the USB container // pEvent->EventCode = pEventBuf->Header.Code; pEvent->SessionId = m_SessionId; // USB doesn't care about session id, so just use the one we have stored pEvent->TransactionId = pEventBuf->Header.TransactionId; DWORD ParamLen = TotalBytesRead - sizeof(pEventBuf->Header); if (ParamLen > 0) { memcpy(pEvent->Params, pEventBuf->Params, ParamLen); } wiauDbgTrace("ReadEvent", "event successfully read, byte count = %d", TotalBytesRead); Cleanup: if (pEventBuf) { delete[] (BYTE*)pEventBuf; pEventBuf = NULL; } return hr; } // // This function cancels the remainder of a data transfer. // HRESULT CUsbCamera::AbortTransfer() { DBG_FN("CUsbCamera::AbortTransfer"); HRESULT hr = S_OK; // // WIAFIX-8/28/2000-davepar Fill in the details: // 1. If usbscan.sys already transferred the data, clear it's buffer // 2. If not, send cancel control code to camera // return hr; } // // This function attempts to recover from an error. When this function returns, the // device will be in one of three states: // 1. Ready for more commands, indicated by S_OK // 2. Reset, indicated by S_FALSE // 3. Unreachable, indicated by FAILED(hr) // HRESULT CUsbCamera::RecoverFromError() { DBG_FN("CUsbCamera::RecoverFromError"); HRESULT hr = S_OK; // // WIAFIX-7/29/2000-davepar Maybe first should cancel all pending I/O with IOCTL_CANCEL_IO?? // // // Attempt to get status on the device // USB_PTPDEVICESTATUS DeviceStatus; hr = GetDeviceStatus(&DeviceStatus); // // If that worked, clear any stalls returned // if (SUCCEEDED(hr)) { hr = ClearStalls(&DeviceStatus); // // If clearing all the stalls worked, exit // if (SUCCEEDED(hr)) { wiauDbgTrace("RecoverFromError", "device is ready for more commands"); return S_OK; } } // // Either the GetDeviceStatus or ClearStall failed, reset the device // hr = ResetDevice(); // // If that worked, return S_FALSE // if (SUCCEEDED(hr)) { wiauDbgWarning("RecoverFromError", "the device was reset"); return S_FALSE; } // // If that fails, the device is unreachable // wiauDbgError("RecoverFromError", "ResetDevice failed"); return hr; } // // This function gets the device status, used mainly after an error occurs. It // may return an endpoint number that the device has intentionally stalled to // cancel a transaction. The caller should be prepared to clear the stall. // // Input: // pDeviceStatus -- the receive the status. // HRESULT CUsbCamera::GetDeviceStatus( USB_PTPDEVICESTATUS *pDeviceStatus ) { DBG_FN("CUsbCamera::GetDeviceStatus"); HRESULT hr = S_OK; // // Set up the request // IO_BLOCK_EX IoBlock; IoBlock.bRequest = USB_PTPREQUEST_GETSTATUS; IoBlock.bmRequestType = USB_PTPREQUEST_TYPE_IN; IoBlock.fTransferDirectionIn = TRUE; IoBlock.uOffset = 0; IoBlock.uLength = sizeof(*pDeviceStatus); IoBlock.pbyData = (UCHAR *) pDeviceStatus; IoBlock.uIndex = 0; pDeviceStatus->Header.Code = 0; // // Send the request // wiauDbgTrace("GetDeviceStatus", "sending GetDeviceStatus request"); DWORD BytesRead = 0; if (!DeviceIoControl(m_hUSB, IOCTL_SEND_USB_REQUEST_PTP, &IoBlock, sizeof(IoBlock), pDeviceStatus, sizeof(*pDeviceStatus), &BytesRead, NULL )) { hr = HRESULT_FROM_WIN32(::GetLastError()); wiauDbgErrorHr(hr, "GetDeviceStatus", "DeviceIoControl failed"); return hr; } if (BytesRead < sizeof(USB_PTPDEVICESTATUS_HEADER) || BytesRead > sizeof(*pDeviceStatus)) { wiauDbgError("GetDeviceStatus", "wrong amount of data returned = %d", BytesRead); return E_FAIL; } if((HIBYTE(pDeviceStatus->Header.Code) & 0xF0) != 0x20 && (HIBYTE(pDeviceStatus->Header.Code) & 0xF0) != 0xA0) { wiauDbgError("GetDeviceStatus", "PTP status code (0x%x)is invalid ", pDeviceStatus->Header.Code); return E_FAIL; } wiauDbgTrace("GetDeviceStatus", "read %d bytes", BytesRead); if (g_dwDebugFlags & WIAUDBG_DUMP) { wiauDbgTrace("GetDeviceStatus", "Dumping device status:"); wiauDbgTrace("GetDeviceStatus", " Length = 0x%04x", pDeviceStatus->Header.Len); wiauDbgTrace("GetDeviceStatus", " Response code = 0x%04x", pDeviceStatus->Header.Code); ULONG NumParams = (ULONG)min(MAX_NUM_PIPES, (BytesRead - sizeof(pDeviceStatus->Header) / sizeof(pDeviceStatus->Params[0]))); for (ULONG count = 0; count < NumParams; count++) { wiauDbgTrace("GetDeviceStatus", " Param %d = 0x%08x", count, pDeviceStatus->Params[count]); } } return hr; } // // This function clears all the stalls listed in the given device status // // Input: // pDeviceStatus -- lists zero or more stalled endpoints // HRESULT CUsbCamera::ClearStalls( USB_PTPDEVICESTATUS *pDeviceStatus ) { DBG_FN("CUsbCamera::ClearStalls"); HRESULT hr = S_OK; if (!pDeviceStatus) { wiauDbgError("ClearStalls", "invalid arg"); return E_INVALIDARG; } PIPE_TYPE PipeType; ULONG NumStalls = (pDeviceStatus->Header.Len - sizeof(pDeviceStatus->Header)) / sizeof(pDeviceStatus->Params[0]); for (ULONG count = 0; count < NumStalls; count++) { // // Translate the endpoint address to the pipe type // if ((UCHAR)pDeviceStatus->Params[count] == m_EndpointInfo.BulkInAddress) { PipeType = READ_DATA_PIPE; } else if ((UCHAR)pDeviceStatus->Params[count] == m_EndpointInfo.BulkOutAddress) { PipeType = WRITE_DATA_PIPE; } else if ((BYTE)pDeviceStatus->Params[count] == m_EndpointInfo.InterruptAddress) { PipeType = EVENT_PIPE; } else { // // Unrecognized, ignore it // wiauDbgError("ClearStalls", "unrecognized pipe address 0x%08x", pDeviceStatus->Params[count]); continue; } // // Reset the endpoint // DWORD BytesRead; if (!DeviceIoControl(m_hUSB, IOCTL_RESET_PIPE, &PipeType, sizeof(PipeType), NULL, 0, &BytesRead, NULL )) { hr = HRESULT_FROM_WIN32(::GetLastError()); wiauDbgErrorHr(hr, "ClearStalls", "DeviceIoControl failed"); return hr; } } if(NumStalls) { for(count = 0; count < 3; count++) { if(FAILED(GetDeviceStatus(pDeviceStatus))) { wiauDbgErrorHr(hr, "ClearStalls", "GetDeviceStatus failed"); return hr; } if(pDeviceStatus->Header.Code == PTP_RESPONSECODE_OK) { break; } } // // check if still there are stalled endpoints // if(pDeviceStatus->Header.Code != PTP_RESPONSECODE_OK) { hr = E_FAIL; } } // // Device should be ready to receive commands again // m_Phase = CAMERA_PHASE_IDLE; return hr; } // // Reset the device // HRESULT CUsbCamera::SendResetDevice() { DBG_FN("CUsbCamera::SendResetDevice"); HRESULT hr = S_OK; // // Set up the request // IO_BLOCK_EX IoBlock; IoBlock.bRequest = USB_PTPREQUEST_RESET; IoBlock.bmRequestType = USB_PTPREQUEST_TYPE_OUT; IoBlock.fTransferDirectionIn = FALSE; IoBlock.uOffset = 0; IoBlock.uLength = 0; IoBlock.pbyData = NULL; IoBlock.uIndex = 0; // // Send the request // DWORD BytesRead; if (!DeviceIoControl(m_hUSB, IOCTL_SEND_USB_REQUEST_PTP, &IoBlock, sizeof(IoBlock), NULL, 0, &BytesRead, NULL )) { hr = HRESULT_FROM_WIN32(::GetLastError()); wiauDbgErrorHr(hr, "ResetDevice", "DeviceIoControl failed"); goto Cleanup; } // // Let the device settle // Sleep(1000); // // See if reset helped // USB_PTPDEVICESTATUS DeviceStatus; hr = GetDeviceStatus(&DeviceStatus); if (FAILED(hr) || DeviceStatus.Header.Code != PTP_RESPONSECODE_OK) { hr = E_FAIL; // device is still unconscious goto Cleanup; } // // Side effect of reseting the device is that the phase, session id, and transaction id get reset // m_Phase = CAMERA_PHASE_IDLE; m_SessionId = 0; m_NextTransactionId = PTP_TRANSACTIONID_MIN; // // Indicate that camera was reset, so that CWiaMiniDriver can notify caller // m_bCameraWasReset = TRUE; Cleanup: return hr; } // // This function sends the class CancelRequest command to the device // and wait for the device to complete the request. // Input: // dwTransactionId -- transaction being canceled // HRESULT CUsbCamera::SendCancelRequest(DWORD dwTransactionId) { DBG_FN("CUsbCamera::CancelRequest"); HRESULT hr = S_OK; IO_BLOCK_EX IoBlock; USB_PTPCANCELIOREQUEST CancelRequest; DWORD BytesReturned; IoBlock.bRequest = USB_PTPREQUEST_CANCELIO; IoBlock.bmRequestType = USB_PTPREQUEST_TYPE_OUT; IoBlock.fTransferDirectionIn = FALSE; // Host to device IoBlock.uOffset = 0; // 0 for this request IoBlock.uLength = sizeof(USB_PTPCANCELIOREQUEST); // Data output length IoBlock.pbyData = (BYTE *)&CancelRequest; // output data IoBlock.uIndex = 0; // 0 for this request CancelRequest.Id = USB_PTPCANCELIO_ID; CancelRequest.TransactionId = dwTransactionId; if (DeviceIoControl(m_hUSB, IOCTL_SEND_USB_REQUEST_PTP, &IoBlock, sizeof(IoBlock), NULL, 0, &BytesReturned, NULL )) { // // Poll device until it returns to idle state // USB_PTPDEVICESTATUS DeviceStatus; const UINT MAX_CANCEL_RECOVERY_MILLISECONDS = 3000; const UINT SLEEP_BETWEEN_RETRIES = 100; DWORD RetryCounts = MAX_CANCEL_RECOVERY_MILLISECONDS / SLEEP_BETWEEN_RETRIES; while (RetryCounts--) { hr = GetDeviceStatus(&DeviceStatus); if (SUCCEEDED(hr)) { if (PTP_RESPONSECODE_OK == DeviceStatus.Header.Code) { // // CancelRequest completed and device is back idle // hr = S_OK; break; } else if (PTP_RESPONSECODE_DEVICEBUSY != DeviceStatus.Header.Code) { // // This is wrong. Device must be either busy or idle // wiauDbgError("SendCancelRequest", "Device is in invalid state, DeviceStatus=0x%X", DeviceStatus.Header.Code); hr = E_FAIL; break; } } else { if (RetryCounts) { hr = S_OK; wiauDbgWarning("CancelRequest", "GetDeviceStatus failed, retrying..."); } else { wiauDbgError("CancelRequest", "GetDeviceStatus failed"); } } Sleep(SLEEP_BETWEEN_RETRIES); } // // Flush system buffers - otherwise we'll get old data on next read // FlushFileBuffers(m_hUSB); } else { hr = HRESULT_FROM_WIN32(::GetLastError()); wiauDbgErrorHr(hr, "CancelRequest", "send USB request failed"); } return hr; }