/* USB Benchmark for libusb-win32 Copyright (C) 2010 Travis Robinson. website: http://sourceforge.net/projects/libusb-win32 This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, please visit www.gnu.org. */ #include #include #include #include #include "lusb0_usb.h" #define _BENCHMARK_VER_ONLY #include "benchmark_rc.rc" #define MAX_OUTSTANDING_TRANSFERS 10 // This is used only in VerifyData() for display information // about data validation mismatches. #define CONVDAT(format,...) printf("[data-mismatch] " format,__VA_ARGS__) // All output is directed through these macros. // #define LOG(LogTypeString,format,...) printf("%s[" __FUNCTION__ "] "format, LogTypeString, __VA_ARGS__) #define CONERR(format,...) LOG("Error:",format,__VA_ARGS__) #define CONMSG(format,...) LOG("",format,__VA_ARGS__) #define CONWRN(format,...) LOG("Warn:",format,__VA_ARGS__) #define CONDBG(format,...) LOG("",format,__VA_ARGS__) #define CONERR0(message) CONERR("%s", message) #define CONMSG0(message) CONMSG("%s", message) #define CONWRN0(message) CONWRN("%s", message) #define CONDBG0(message) CONDBG("%s", message) // This is the libusb-win32 return code for a transfer that timed out. #define TRANSFER_TIMEDOUT -116 // Custom vendor requests that must be implemented in the benchmark firmware. // Test selection can be bypassed with the "notestselect" argument. // enum BENCHMARK_DEVICE_COMMANDS { SET_TEST = 0x0E, GET_TEST = 0x0F, }; // Tests supported by the official benchmark firmware. // enum BENCHMARK_DEVICE_TEST_TYPE { TestTypeNone = 0x00, TestTypeRead = 0x01, TestTypeWrite = 0x02, TestTypeLoop = TestTypeRead|TestTypeWrite, }; // This software was mainly created for testing the libusb-win32 kernel & user driver. enum BENCHMARK_TRANSFER_MODE { // Tests for the libusb-win32 sync transfer function. TRANSFER_MODE_SYNC, // Test for async function, iso transfers, and queued transfers TRANSFER_MODE_ASYNC, }; // Holds all of the information about a test. struct BENCHMARK_TEST_PARAM { // User configurable value set from the command line. // INT Vid; // Vendor ID INT Pid; // Porduct ID INT Intf; // Interface number INT Altf; // Alt Interface number INT Ep; // Endpoint number (1-15) INT Refresh; // Refresh interval (ms) INT Timeout; // Transfer timeout (ms) INT Retry; // Number for times to retry a timed out transfer before aborting INT BufferSize; // Number of bytes to transfer INT BufferCount; // Number of outstanding asynchronous transfers BOOL NoTestSelect; // If true, don't send control message to select the test type. BOOL UseList; // Show the user a device list and let them choose a benchmark device. INT IsoPacketSize; // Isochronous packet size (defaults to the endpoints max packet size) INT Priority; // Priority to run this thread at. BOOL Verify; // Only for loop and read test. If true, verifies data integrity. BOOL VerifyDetails; // If true, prints detailed information for each invalid byte. enum BENCHMARK_DEVICE_TEST_TYPE TestType; // The benchmark test type. enum BENCHMARK_TRANSFER_MODE TransferMode; // Sync or Async // Internal value use during the test. // usb_dev_handle* DeviceHandle; struct usb_device* Device; BOOL IsCancelled; BOOL IsUserAborted; BYTE* VerifyBuffer; // Stores the verify test pattern for 1 packet. WORD VerifyBufferSize; // Size of VerifyBuffer }; // The benchmark transfer context used for asynchronous transfers. see TransferAsync(). struct BENCHMARK_TRANSFER_HANDLE { VOID* Context; BOOL InUse; CHAR* Data; INT DataMaxLength; INT ReturnCode; }; // Holds all of the information about a transfer. struct BENCHMARK_TRANSFER_PARAM { struct BENCHMARK_TEST_PARAM* Test; HANDLE ThreadHandle; DWORD ThreadID; struct usb_endpoint_descriptor Ep; INT IsoPacketSize; BOOL IsRunning; LONGLONG TotalTransferred; LONG LastTransferred; LONG Packets; DWORD StartTick; DWORD LastTick; DWORD LastStartTick; INT TotalTimeoutCount; INT RunningTimeoutCount; INT TotalErrorCount; INT RunningErrorCount; INT ShortTransferCount; INT TransferHandleNextIndex; INT TransferHandleWaitIndex; INT OutstandingTransferCount; struct BENCHMARK_TRANSFER_HANDLE TransferHandles[MAX_OUTSTANDING_TRANSFERS]; // Placeholder for end of structure; this is where the raw data for the // transfer buffer is allocated. // BYTE Buffer[0]; }; // Benchmark device api. struct usb_dev_handle* Bench_Open(WORD vid, WORD pid, INT interfaceNumber, INT altInterfaceNumber, struct usb_device** deviceForHandle); int Bench_SetTestType(struct usb_dev_handle* dev, enum BENCHMARK_DEVICE_TEST_TYPE testType, int intf); int Bench_GetTestType(struct usb_dev_handle* dev, enum BENCHMARK_DEVICE_TEST_TYPE* testType, int intf); // Critical section for running status. CRITICAL_SECTION DisplayCriticalSection; // Finds the interface for [interface_number] in a libusb-win32 config descriptor. // If first_interface is not NULL, it is set to the first interface in the config. // struct usb_interface_descriptor* usb_find_interface(struct usb_config_descriptor* config_descriptor, INT interface_number, INT alt_interface_number, struct usb_interface_descriptor** first_interface); // Internal function used by the benchmark application. void ShowHelp(void); void ShowCopyright(void); void SetTestDefaults(struct BENCHMARK_TEST_PARAM* test); char* GetParamStrValue(const char* src, const char* paramName); BOOL GetParamIntValue(const char* src, const char* paramName, INT* returnValue); int ValidateBenchmarkArgs(struct BENCHMARK_TEST_PARAM* testParam); int ParseBenchmarkArgs(struct BENCHMARK_TEST_PARAM* testParams, int argc, char **argv); void FreeTransferParam(struct BENCHMARK_TRANSFER_PARAM** testTransferRef); struct BENCHMARK_TRANSFER_PARAM* CreateTransferParam(struct BENCHMARK_TEST_PARAM* test, int endpointID); void GetAverageBytesSec(struct BENCHMARK_TRANSFER_PARAM* transferParam, DOUBLE* bps); void GetCurrentBytesSec(struct BENCHMARK_TRANSFER_PARAM* transferParam, DOUBLE* bps); void ShowRunningStatus(struct BENCHMARK_TRANSFER_PARAM* transferParam); void ShowTestInfo(struct BENCHMARK_TEST_PARAM* testParam); void ShowTransferInfo(struct BENCHMARK_TRANSFER_PARAM* transferParam); void WaitForTestTransfer(struct BENCHMARK_TRANSFER_PARAM* transferParam); void ResetRunningStatus(struct BENCHMARK_TRANSFER_PARAM* transferParam); // The thread transfer routine. DWORD TransferThreadProc(struct BENCHMARK_TRANSFER_PARAM* transferParams); #define TRANSFER_DISPLAY(TransferParam, ReadingString, WritingString) \ ((TransferParam->Ep.bEndpointAddress & USB_ENDPOINT_DIR_MASK) ? ReadingString : WritingString) #define INC_ROLL(IncField, RollOverValue) if ((++IncField) >= RollOverValue) IncField = 0 #define ENDPOINT_TYPE(TransferParam) (TransferParam->Ep.bmAttributes & 3) const char* TestDisplayString[] = {"None", "Read", "Write", "Loop", NULL}; const char* EndpointTypeDisplayString[] = {"Control", "Isochronous", "Bulk", "Interrupt", NULL}; void SetTestDefaults(struct BENCHMARK_TEST_PARAM* test) { memset(test,0,sizeof(struct BENCHMARK_TEST_PARAM)); test->Ep = 0x00; test->Vid = 0x0666; test->Pid = 0x0001; test->Refresh = 1000; test->Timeout = 5000; test->TestType = TestTypeLoop; test->BufferSize = 4096; test->BufferCount = 1; test->Priority = THREAD_PRIORITY_NORMAL; } struct usb_interface_descriptor* usb_find_interface(struct usb_config_descriptor* config_descriptor, INT interface_number, INT alt_interface_number, struct usb_interface_descriptor** first_interface) { struct usb_interface_descriptor* intf; int intfIndex; if (first_interface) *first_interface = NULL; if (!config_descriptor) return NULL; for (intfIndex = 0; intfIndex < config_descriptor->bNumInterfaces; intfIndex++) { if (config_descriptor->interface[intfIndex].num_altsetting) { intf = &config_descriptor->interface[intfIndex].altsetting[0]; if ((first_interface) && *first_interface == NULL) *first_interface = intf; if (intf->bInterfaceNumber == interface_number && (alt_interface_number==-1 || intf->bAlternateSetting==alt_interface_number)) { return intf; } } } return NULL; } struct usb_dev_handle* Bench_Open(WORD vid, WORD pid, INT interfaceNumber, INT altInterfaceNumber, struct usb_device** deviceForHandle) { struct usb_bus* bus; struct usb_device* dev; struct usb_dev_handle* udev; for (bus = usb_get_busses(); bus; bus = bus->next) { for (dev = bus->devices; dev; dev = dev->next) { if (dev->descriptor.idVendor == vid && dev->descriptor.idProduct == pid) { if ((udev = usb_open(dev))) { if (dev->descriptor.bNumConfigurations) { if (usb_find_interface(&dev->config[0], interfaceNumber, altInterfaceNumber, NULL) != NULL) { if (deviceForHandle) *deviceForHandle = dev; return udev; } } usb_close(udev); } } } } return NULL; } int Bench_SetTestType(struct usb_dev_handle* dev, enum BENCHMARK_DEVICE_TEST_TYPE testType, int intf) { char buffer[1]; int ret = 0; ret = usb_control_msg(dev, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN, SET_TEST, testType, intf, buffer, 1, 1000); return ret; } int Bench_GetTestType(struct usb_dev_handle* dev, enum BENCHMARK_DEVICE_TEST_TYPE* testType, int intf) { char buffer[1]; int ret = 0; ret = usb_control_msg(dev, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN, GET_TEST, 0, intf, buffer, 1, 1000); if (ret == 1) *testType = buffer[0]; return ret; } enum TRANSFER_VERIFY_STATE { TVS_START, TVS_KEY, TVS_DATA, TVS_FIND_START, }; INT VerifyData(struct BENCHMARK_TRANSFER_PARAM* transferParam, BYTE* data, INT dataLength) { WORD verifyDataSize = transferParam->Test->VerifyBufferSize; BYTE* verifyData = transferParam->Test->VerifyBuffer; BYTE keyC = 0; BOOL seedKey = TRUE; INT dataLeft = dataLength; INT dataIndex = 0; INT packetIndex = 0; INT verifyIndex = 0; while(dataLeft > 1) { verifyDataSize = dataLeft > transferParam->Test->VerifyBufferSize ? transferParam->Test->VerifyBufferSize : dataLeft; if (seedKey) keyC = data[dataIndex+1]; else { if (data[dataIndex+1]==0) { keyC=0; } else { keyC++; } } seedKey = FALSE; // Index 0 is always 0. // The key is always at index 1 verifyData[1] = keyC; if (memcmp(&data[dataIndex],verifyData,verifyDataSize) != 0) { // Packet verification failed. // Reset the key byte on the next packet. seedKey = TRUE; CONVDAT("data mismatch packet-index=%d data-index=%d\n", packetIndex, dataIndex); if (transferParam->Test->VerifyDetails) { for (verifyIndex=0; verifyIndexEp.bEndpointAddress & USB_ENDPOINT_DIR_MASK) { ret = usb_bulk_read( transferParam->Test->DeviceHandle, transferParam->Ep.bEndpointAddress, transferParam->Buffer, transferParam->Test->BufferSize, transferParam->Test->Timeout); } else { ret = usb_bulk_write( transferParam->Test->DeviceHandle, transferParam->Ep.bEndpointAddress, transferParam->Buffer, transferParam->Test->BufferSize, transferParam->Test->Timeout); } return ret; } int TransferAsync(struct BENCHMARK_TRANSFER_PARAM* transferParam, struct BENCHMARK_TRANSFER_HANDLE** handleRef) { int ret; struct BENCHMARK_TRANSFER_HANDLE* handle; *handleRef = NULL; // Submit transfers until the maximum number of outstanding transfer(s) is reached. while (transferParam->OutstandingTransferCount < transferParam->Test->BufferCount) { // Get the next available benchmark transfer handle. *handleRef = handle = &transferParam->TransferHandles[transferParam->TransferHandleNextIndex]; // If a libusb-win32 transfer context hasn't been setup for this benchmark transfer // handle, do it now. // if (!handle->Context) { // Data buffer(s) are located at the end of the transfer param. handle->Data = transferParam->Buffer + (transferParam->TransferHandleNextIndex * transferParam->Test->BufferSize); handle->DataMaxLength = transferParam->Test->BufferSize; switch (ENDPOINT_TYPE(transferParam)) { case USB_ENDPOINT_TYPE_ISOCHRONOUS: ret = usb_isochronous_setup_async(transferParam->Test->DeviceHandle, &handle->Context, transferParam->Ep.bEndpointAddress, transferParam->IsoPacketSize ? transferParam->IsoPacketSize : transferParam->Ep.wMaxPacketSize); break; case USB_ENDPOINT_TYPE_BULK: ret = usb_bulk_setup_async(transferParam->Test->DeviceHandle, &handle->Context, transferParam->Ep.bEndpointAddress); break; case USB_ENDPOINT_TYPE_INTERRUPT: ret = usb_interrupt_setup_async(transferParam->Test->DeviceHandle, &handle->Context, transferParam->Ep.bEndpointAddress); break; default: ret = -1; break; } if (ret < 0) { CONERR("failed creating transfer context ret=%d\n",ret); goto Done; } } // Submit this transfer now. handle->ReturnCode = ret = usb_submit_async(handle->Context, handle->Data, handle->DataMaxLength); if (ret < 0) goto Done; // Mark this handle has InUse. handle->InUse = TRUE; // When transfers ir successfully submitted, OutstandingTransferCount goes up; when // they are completed it goes down. // transferParam->OutstandingTransferCount++; // Move TransferHandleNextIndex to the next available transfer. INC_ROLL(transferParam->TransferHandleNextIndex, transferParam->Test->BufferCount); } // If the number of outstanding transfers has reached the limit, wait for the // oldest outstanding transfer to complete. // if (transferParam->OutstandingTransferCount == transferParam->Test->BufferCount) { // TransferHandleWaitIndex is the index of the oldest outstanding transfer. *handleRef = handle = &transferParam->TransferHandles[transferParam->TransferHandleWaitIndex]; // Only wait, cancelling & freeing is handled by the caller. handle->ReturnCode = ret = usb_reap_async_nocancel(handle->Context, transferParam->Test->Timeout); if (ret < 0) goto Done; // Mark this handle has no longer InUse. handle->InUse = FALSE; // When transfers ir successfully submitted, OutstandingTransferCount goes up; when // they are completed it goes down. // transferParam->OutstandingTransferCount--; // Move TransferHandleWaitIndex to the oldest outstanding transfer. INC_ROLL(transferParam->TransferHandleWaitIndex, transferParam->Test->BufferCount); } Done: return ret; } DWORD TransferThreadProc(struct BENCHMARK_TRANSFER_PARAM* transferParam) { int ret, i; struct BENCHMARK_TRANSFER_HANDLE* handle; char* data; transferParam->IsRunning = TRUE; while (!transferParam->Test->IsCancelled) { data = NULL; handle = NULL; if (transferParam->Test->TransferMode == TRANSFER_MODE_SYNC) { ret = TransferSync(transferParam); if (ret >= 0) data = transferParam->Buffer; } else if (transferParam->Test->TransferMode == TRANSFER_MODE_ASYNC) { ret = TransferAsync(transferParam, &handle); if ((handle) && ret >= 0) data = handle->Data; } else { CONERR("invalid transfer mode %d\n",transferParam->Test->TransferMode); goto Done; } if (ret < 0) { // The user pressed 'Q'. if (transferParam->Test->IsUserAborted) break; // Transfer timed out if (ret == TRANSFER_TIMEDOUT) { transferParam->TotalTimeoutCount++; transferParam->RunningTimeoutCount++; CONWRN("Timeout #%d %s on Ep%02Xh..\n", transferParam->RunningTimeoutCount, TRANSFER_DISPLAY(transferParam,"reading","writing"), transferParam->Ep.bEndpointAddress); if (transferParam->RunningTimeoutCount > transferParam->Test->Retry) break; } else { // An error (other than a timeout) occured. // usb_strerror()is not thread safe and should not be used // in a multi-threaded app. It's used here because // this is a test program. // transferParam->TotalErrorCount++; transferParam->RunningErrorCount++; CONERR("failed %s! %d of %d ret=%d: %s\n", TRANSFER_DISPLAY(transferParam,"reading","writing"), transferParam->RunningErrorCount, transferParam->Test->Retry+1, ret, usb_strerror()); usb_resetep(transferParam->Test->DeviceHandle, transferParam->Ep.bEndpointAddress); if (transferParam->RunningErrorCount > transferParam->Test->Retry) break; } ret = 0; } else { if (ret < transferParam->Test->BufferSize && !transferParam->Test->IsCancelled) { if (ret > 0) { transferParam->ShortTransferCount++; CONWRN("Short transfer on Ep%02Xh expected %d got %d.\n", transferParam->Ep.bEndpointAddress, transferParam->Test->BufferSize, ret); } else { CONWRN("Zero-length transfer on Ep%02Xh expected %d.\n", transferParam->Ep.bEndpointAddress, transferParam->Test->BufferSize); transferParam->TotalErrorCount++; transferParam->RunningErrorCount++; if (transferParam->RunningErrorCount > transferParam->Test->Retry) break; usb_resetep(transferParam->Test->DeviceHandle, transferParam->Ep.bEndpointAddress); } } else { transferParam->RunningErrorCount = 0; transferParam->RunningTimeoutCount = 0; } if ((transferParam->Test->Verify) && (transferParam->Ep.bEndpointAddress & USB_ENDPOINT_DIR_MASK)) { VerifyData(transferParam, data, ret); } } EnterCriticalSection(&DisplayCriticalSection); if (!transferParam->StartTick && transferParam->Packets >= 0) { transferParam->StartTick = GetTickCount(); transferParam->LastStartTick = transferParam->StartTick; transferParam->LastTick = transferParam->StartTick; transferParam->LastTransferred = 0; transferParam->TotalTransferred = 0; transferParam->Packets = 0; } else { if (!transferParam->LastStartTick) { transferParam->LastStartTick = transferParam->LastTick; transferParam->LastTransferred = 0; } transferParam->LastTick = GetTickCount(); transferParam->LastTransferred += ret; transferParam->TotalTransferred += ret; transferParam->Packets++; } LeaveCriticalSection(&DisplayCriticalSection); } Done: for (i=0; i < transferParam->Test->BufferCount; i++) { if (transferParam->TransferHandles[i].Context) { if (transferParam->TransferHandles[i].InUse) { if ((ret = usb_cancel_async(transferParam->TransferHandles[i].Context)) < 0) if (!transferParam->Test->IsUserAborted) CONERR("failed cancelling transfer! ret=%d\n",ret); transferParam->TransferHandles[i].InUse=FALSE; } usb_free_async(&transferParam->TransferHandles[i].Context); } } transferParam->IsRunning = FALSE; return 0; } char* GetParamStrValue(const char* src, const char* paramName) { return (strstr(src,paramName)==src) ? (char*)(src+strlen(paramName)) : NULL; } BOOL GetParamIntValue(const char* src, const char* paramName, INT* returnValue) { char* value = GetParamStrValue(src, paramName); if (value) { *returnValue = strtol(value, NULL, 0); return TRUE; } return FALSE; } int ValidateBenchmarkArgs(struct BENCHMARK_TEST_PARAM* testParam) { if (testParam->BufferCount < 1 || testParam->BufferCount > MAX_OUTSTANDING_TRANSFERS) { CONERR("Invalid BufferCount argument %d. BufferCount must be greater than 0 and less than or equal to %d.\n", testParam->BufferCount, MAX_OUTSTANDING_TRANSFERS); return -1; } return 0; } int ParseBenchmarkArgs(struct BENCHMARK_TEST_PARAM* testParams, int argc, char **argv) { #define GET_INT_VAL char arg[128]; char* value; int iarg; for (iarg=1; iarg < argc; iarg++) { if (strcpy_s(arg, _countof(arg), argv[iarg])!=ERROR_SUCCESS) return -1; strlwr(arg); if (GetParamIntValue(arg, "vid=", &testParams->Vid)) {} else if (GetParamIntValue(arg, "pid=", &testParams->Pid)) {} else if (GetParamIntValue(arg, "retry=", &testParams->Retry)) {} else if (GetParamIntValue(arg, "buffercount=", &testParams->BufferCount)) { if (testParams->BufferCount > 1) testParams->TransferMode = TRANSFER_MODE_ASYNC; } else if (GetParamIntValue(arg, "buffersize=", &testParams->BufferSize)) {} else if (GetParamIntValue(arg, "size=", &testParams->BufferSize)) {} else if (GetParamIntValue(arg, "timeout=", &testParams->Timeout)) {} else if (GetParamIntValue(arg, "intf=", &testParams->Intf)) {} else if (GetParamIntValue(arg, "altf=", &testParams->Altf)) {} else if (GetParamIntValue(arg, "ep=", &testParams->Ep)) { testParams->Ep &= 0xf; } else if (GetParamIntValue(arg, "refresh=", &testParams->Refresh)) {} else if (GetParamIntValue(arg, "isopacketsize=", &testParams->IsoPacketSize)) {} else if ((value=GetParamStrValue(arg,"mode="))) { if (GetParamStrValue(value,"sync")) { testParams->TransferMode = TRANSFER_MODE_SYNC; } else if (GetParamStrValue(value,"async")) { testParams->TransferMode = TRANSFER_MODE_ASYNC; } else { // Invalid EndpointType argument. CONERR("invalid transfer mode argument! %s\n",argv[iarg]); return -1; } } else if ((value=GetParamStrValue(arg,"priority="))) { if (GetParamStrValue(value,"lowest")) { testParams->Priority=THREAD_PRIORITY_LOWEST; } else if (GetParamStrValue(value,"belownormal")) { testParams->Priority=THREAD_PRIORITY_BELOW_NORMAL; } else if (GetParamStrValue(value,"normal")) { testParams->Priority=THREAD_PRIORITY_NORMAL; } else if (GetParamStrValue(value,"abovenormal")) { testParams->Priority=THREAD_PRIORITY_ABOVE_NORMAL; } else if (GetParamStrValue(value,"highest")) { testParams->Priority=THREAD_PRIORITY_HIGHEST; } else { CONERR("invalid priority argument! %s\n",argv[iarg]); return -1; } } else if (!stricmp(arg,"notestselect")) { testParams->NoTestSelect = TRUE; } else if (!stricmp(arg,"read")) { testParams->TestType = TestTypeRead; } else if (!stricmp(arg,"write")) { testParams->TestType = TestTypeWrite; } else if (!stricmp(arg,"loop")) { testParams->TestType = TestTypeLoop; } else if (!stricmp(arg,"list")) { testParams->UseList = TRUE; } else if (!stricmp(arg,"verifydetails")) { testParams->VerifyDetails = TRUE; testParams->Verify = TRUE; } else if (!stricmp(arg,"verify")) { testParams->Verify = TRUE; } else { CONERR("invalid argument! %s\n",argv[iarg]); return -1; } } return ValidateBenchmarkArgs(testParams); } INT CreateVerifyBuffer(struct BENCHMARK_TEST_PARAM* testParam, WORD endpointMaxPacketSize) { int i; BYTE indexC = 0; testParam->VerifyBuffer = malloc(endpointMaxPacketSize); if (!testParam->VerifyBuffer) { CONERR("memory allocation failure at line %d!\n",__LINE__); return -1; } testParam->VerifyBufferSize = endpointMaxPacketSize; for(i=0; i < endpointMaxPacketSize; i++) { testParam->VerifyBuffer[i] = indexC++; if (indexC == 0) indexC = 1; } return 0; } void FreeTransferParam(struct BENCHMARK_TRANSFER_PARAM** testTransferRef) { struct BENCHMARK_TRANSFER_PARAM* pTransferParam; if ((!testTransferRef) || !*testTransferRef) return; pTransferParam = *testTransferRef; if (pTransferParam->ThreadHandle) { CloseHandle(pTransferParam->ThreadHandle); pTransferParam->ThreadHandle = NULL; } free(pTransferParam); *testTransferRef = NULL; } struct BENCHMARK_TRANSFER_PARAM* CreateTransferParam(struct BENCHMARK_TEST_PARAM* test, int endpointID) { struct BENCHMARK_TRANSFER_PARAM* transferParam; struct usb_interface_descriptor* testInterface; int i; int allocSize = sizeof(struct BENCHMARK_TRANSFER_PARAM)+(test->BufferSize * test->BufferCount); transferParam = (struct BENCHMARK_TRANSFER_PARAM*) malloc(allocSize); if (transferParam) { memset(transferParam, 0, allocSize); transferParam->Test = test; if (!(testInterface = usb_find_interface(&test->Device->config[0], test->Intf, test->Altf, NULL))) { CONERR("failed locating interface %02Xh!\n", test->Intf); FreeTransferParam(&transferParam); goto Done; } for(i=0; i < testInterface->bNumEndpoints; i++) { if (!(endpointID & USB_ENDPOINT_ADDRESS_MASK)) { // Use first endpoint that matches the direction if ((testInterface->endpoint[i].bEndpointAddress & USB_ENDPOINT_DIR_MASK) == endpointID) { memcpy(&transferParam->Ep, &testInterface->endpoint[i],sizeof(struct usb_endpoint_descriptor)); break; } } else { if ((int)testInterface->endpoint[i].bEndpointAddress == endpointID) { memcpy(&transferParam->Ep, &testInterface->endpoint[i],sizeof(struct usb_endpoint_descriptor)); break; } } } if (!transferParam->Ep.bEndpointAddress) { CONERR("failed locating EP%02Xh!\n", endpointID); FreeTransferParam(&transferParam); goto Done; } if (transferParam->Test->BufferSize % transferParam->Ep.wMaxPacketSize) { CONERR("buffer size %d is not an interval of EP%02Xh maximum packet size of %d!\n", transferParam->Test->BufferSize, transferParam->Ep.bEndpointAddress, transferParam->Ep.wMaxPacketSize); FreeTransferParam(&transferParam); goto Done; } if (test->IsoPacketSize) transferParam->IsoPacketSize = test->IsoPacketSize; else transferParam->IsoPacketSize = transferParam->Ep.wMaxPacketSize; if (ENDPOINT_TYPE(transferParam) == USB_ENDPOINT_TYPE_ISOCHRONOUS) transferParam->Test->TransferMode = TRANSFER_MODE_ASYNC; ResetRunningStatus(transferParam); transferParam->ThreadHandle = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)TransferThreadProc, transferParam, CREATE_SUSPENDED, &transferParam->ThreadID); if (!transferParam->ThreadHandle) { CONERR0("failed creating thread!\n"); FreeTransferParam(&transferParam); goto Done; } // If verify mode is on, this is a loop test, and this is a write endpoint, fill // the buffers with the same test data sent by a benchmark device when running // a read only test. if (transferParam->Test->Verify && transferParam->Test->TestType == TestTypeLoop && !(transferParam->Ep.bEndpointAddress & USB_ENDPOINT_DIR_MASK)) { // Data Format: // [0][KeyByte] 2 3 4 5 ..to.. wMaxPacketSize (if data byte rolls it is incremented to 1) // Increment KeyByte and repeat // BYTE indexC=0; INT bufferIndex = 0; WORD dataIndex; INT packetIndex; INT packetCount = ((transferParam->Test->BufferCount*transferParam->Test->BufferSize) / transferParam->Ep.wMaxPacketSize); for(packetIndex = 0; packetIndex < packetCount; packetIndex++) { indexC = 2; for (dataIndex=0; dataIndex < transferParam->Ep.wMaxPacketSize; dataIndex++) { if (dataIndex == 0) // Start transferParam->Buffer[bufferIndex] = 0; else if (dataIndex == 1) // Key transferParam->Buffer[bufferIndex] = packetIndex & 0xFF; else // Data transferParam->Buffer[bufferIndex] = indexC++; // if wMaxPacketSize is > 255, indexC resets to 1. if (indexC == 0) indexC = 1; bufferIndex++; } } } } Done: if (!transferParam) CONERR0("failed creating transfer param!\n"); return transferParam; } void GetAverageBytesSec(struct BENCHMARK_TRANSFER_PARAM* transferParam, DOUBLE* bps) { DOUBLE ticksSec; if ((!transferParam->StartTick) || (transferParam->StartTick >= transferParam->LastTick) || transferParam->TotalTransferred==0) { *bps=0; } else { ticksSec = (transferParam->LastTick - transferParam->StartTick) / 1000.0; *bps = (transferParam->TotalTransferred / ticksSec); } } void GetCurrentBytesSec(struct BENCHMARK_TRANSFER_PARAM* transferParam, DOUBLE* bps) { DOUBLE ticksSec; if ((!transferParam->StartTick) || (!transferParam->LastStartTick) || (transferParam->LastTick <= transferParam->LastStartTick) || transferParam->LastTransferred==0) { *bps=0; } else { ticksSec = (transferParam->LastTick - transferParam->LastStartTick) / 1000.0; *bps = transferParam->LastTransferred / ticksSec; } } void ShowRunningStatus(struct BENCHMARK_TRANSFER_PARAM* transferParam) { struct BENCHMARK_TRANSFER_PARAM temp; DOUBLE bpsOverall; DOUBLE bpsLastTransfer; // LOCK the display critical section EnterCriticalSection(&DisplayCriticalSection); memcpy(&temp, transferParam, sizeof(struct BENCHMARK_TRANSFER_PARAM)); // UNLOCK the display critical section LeaveCriticalSection(&DisplayCriticalSection); if ((!temp.StartTick) || (temp.StartTick >= temp.LastTick)) { CONMSG("Synchronizing %d..\n", abs(transferParam->Packets)); } else { GetAverageBytesSec(&temp,&bpsOverall); GetCurrentBytesSec(&temp,&bpsLastTransfer); transferParam->LastStartTick = 0; CONMSG("Avg. Bytes/s: %.2f Transfers: %d Bytes/s: %.2f\n", bpsOverall, temp.Packets, bpsLastTransfer); } } void ShowTransferInfo(struct BENCHMARK_TRANSFER_PARAM* transferParam) { DOUBLE bpsAverage; DOUBLE bpsCurrent; DOUBLE elapsedSeconds; if (!transferParam) return; CONMSG("%s %s (Ep%02Xh) max packet size: %d\n", EndpointTypeDisplayString[ENDPOINT_TYPE(transferParam)], TRANSFER_DISPLAY(transferParam,"Read","Write"), transferParam->Ep.bEndpointAddress, transferParam->Ep.wMaxPacketSize); if (transferParam->StartTick) { GetAverageBytesSec(transferParam,&bpsAverage); GetCurrentBytesSec(transferParam,&bpsCurrent); CONMSG("\tTotal Bytes : %I64d\n", transferParam->TotalTransferred); CONMSG("\tTotal Transfers : %d\n", transferParam->Packets); if (transferParam->ShortTransferCount) { CONMSG("\tShort Transfers : %d\n", transferParam->ShortTransferCount); } if (transferParam->TotalTimeoutCount) { CONMSG("\tTimeout Errors : %d\n", transferParam->TotalTimeoutCount); } if (transferParam->TotalErrorCount) { CONMSG("\tOther Errors : %d\n", transferParam->TotalErrorCount); } CONMSG("\tAvg. Bytes/sec : %.2f\n", bpsAverage); if (transferParam->StartTick && transferParam->StartTick < transferParam->LastTick) { elapsedSeconds = (transferParam->LastTick - transferParam->StartTick) / 1000.0; CONMSG("\tElapsed Time : %.2f seconds\n", elapsedSeconds); } CONMSG0("\n"); } } void ShowTestInfo(struct BENCHMARK_TEST_PARAM* testParam) { if (!testParam) return; CONMSG("%s Test Information\n",TestDisplayString[testParam->TestType & 3]); CONMSG("\tVid / Pid : %04Xh / %04Xh\n", testParam->Vid, testParam->Pid); CONMSG("\tInterface # : %02Xh\n", testParam->Intf); CONMSG("\tPriority : %d\n", testParam->Priority); CONMSG("\tBuffer Size : %d\n", testParam->BufferSize); CONMSG("\tBuffer Count : %d\n", testParam->BufferCount); CONMSG("\tDisplay Refresh : %d (ms)\n", testParam->Refresh); CONMSG("\tTransfer Timeout: %d (ms)\n", testParam->Timeout); CONMSG("\tRetry Count : %d\n", testParam->Retry); CONMSG("\tVerify Data : %s%s\n", testParam->Verify ? "On" : "Off", (testParam->Verify && testParam->VerifyDetails) ? " (Detailed)" : ""); CONMSG0("\n"); } void WaitForTestTransfer(struct BENCHMARK_TRANSFER_PARAM* transferParam) { DWORD exitCode; while (transferParam) { if (!transferParam->IsRunning) { if (GetExitCodeThread(transferParam->ThreadHandle, &exitCode)) { if (exitCode == 0) { CONMSG("stopped Ep%02Xh thread.\tExitCode=%d\n", transferParam->Ep.bEndpointAddress, exitCode); break; } } else { CONERR("failed getting Ep%02Xh thread exit code!\n",transferParam->Ep.bEndpointAddress); break; } } Sleep(100); CONMSG("waiting for Ep%02Xh thread..\n", transferParam->Ep.bEndpointAddress); } } void ResetRunningStatus(struct BENCHMARK_TRANSFER_PARAM* transferParam) { if (!transferParam) return; transferParam->StartTick=0; transferParam->TotalTransferred=0; transferParam->Packets=-2; transferParam->LastTick=0; transferParam->RunningTimeoutCount=0; } int GetTestDeviceFromList(struct BENCHMARK_TEST_PARAM* testParam) { const int LINE_MAX_SIZE = 1024; const int STRING_MAX_SIZE = 256; const int NUM_STRINGS = 3; const int ALLOC_SIZE = LINE_MAX_SIZE + (STRING_MAX_SIZE * NUM_STRINGS); int userInput; char* buffer; char* line; char* product; char* manufacturer; char* serial; struct usb_bus* bus; struct usb_device* dev; usb_dev_handle* udev; struct usb_device* validDevices[256]; int deviceIndex=0; struct usb_interface_descriptor* firstInterface; int ret = -1; buffer = malloc(ALLOC_SIZE); if (!buffer) { CONERR0("failed allocating memory!\n"); return ret; } line = buffer; product = buffer + LINE_MAX_SIZE; manufacturer = product + STRING_MAX_SIZE; serial = manufacturer + STRING_MAX_SIZE; for (bus = usb_get_busses(); bus; bus = bus->next) { for (dev = bus->devices; dev; dev = dev->next) { udev = usb_open(dev); if (udev) { memset(buffer, 0, ALLOC_SIZE); line = buffer; if (dev->descriptor.iManufacturer) { if (usb_get_string_simple(udev, dev->descriptor.iManufacturer, manufacturer, STRING_MAX_SIZE - 1) > 0) { strcat(line,"("); strcat(line,manufacturer); strcat(line,") "); } } if (dev->descriptor.iProduct) { if (usb_get_string_simple(udev, dev->descriptor.iProduct, product, STRING_MAX_SIZE - 1) > 0) { strcat(line,product); strcat(line," "); } } if (dev->descriptor.iSerialNumber) { if (usb_get_string_simple(udev, dev->descriptor.iSerialNumber, serial, STRING_MAX_SIZE - 1) > 0) { strcat(line,"["); strcat(line,serial); strcat(line,"] "); } } if (!deviceIndex) CONMSG0("\n"); validDevices[deviceIndex++] = dev; CONMSG("%d. %04X:%04X %s\n", deviceIndex, dev->descriptor.idVendor, dev->descriptor.idProduct, line); usb_close(udev); } } } if (!deviceIndex) { CONERR0("No devices where found!\n"); ret = -1; goto Done; } CONMSG("\nSelect device (1-%d) :",deviceIndex); ret = _cscanf("%i",&userInput); if (ret != 1 || userInput < 1) { CONMSG0("\n"); CONMSG0("Aborting..\n"); ret = -1; goto Done; } CONMSG0("\n"); userInput--; if (userInput >= 0 && userInput < deviceIndex) { testParam->DeviceHandle = usb_open(validDevices[userInput]); if (testParam->DeviceHandle) { testParam->Device = validDevices[userInput]; testParam->Vid = testParam->Device->descriptor.idVendor; testParam->Pid = testParam->Device->descriptor.idProduct; if (usb_find_interface(&validDevices[userInput]->config[0],testParam->Intf, testParam->Altf, &firstInterface) == NULL) { // the specified (or default) interface didn't exist, use the first one. if (firstInterface != NULL) { testParam->Intf = firstInterface->bInterfaceNumber; } else { CONERR("device %04X:%04X does not have any interfaces!\n", testParam->Vid, testParam->Pid); ret = -1; goto Done; } } ret = 0; } } Done: if (buffer) free(buffer); return ret; } int main(int argc, char** argv) { struct BENCHMARK_TEST_PARAM Test; struct BENCHMARK_TRANSFER_PARAM* ReadTest = NULL; struct BENCHMARK_TRANSFER_PARAM* WriteTest = NULL; int key; if (argc == 1) { ShowHelp(); return -1; } ShowCopyright(); // NOTE: This is the log level for the benchmark application. // #if defined __ERROR_H__ usb_log_set_level(255); #endif SetTestDefaults(&Test); // Load the command line arguments. if (ParseBenchmarkArgs(&Test, argc, argv) < 0) return -1; // Initialize the critical section used for locking // the volatile members of the transfer params in order // to update/modify the running statistics. // InitializeCriticalSection(&DisplayCriticalSection); // Initialize the library. usb_init(); // Find all busses. usb_find_busses(); // Find all connected devices. usb_find_devices(); if (Test.UseList) { if (GetTestDeviceFromList(&Test) < 0) goto Done; } else { // Open a benchmark device. see Bench_Open(). Test.DeviceHandle = Bench_Open(Test.Vid, Test.Pid, Test.Intf, Test.Altf, &Test.Device); } if (!Test.DeviceHandle || !Test.Device) { CONERR("device %04X:%04X not found!\n",Test.Vid, Test.Pid); goto Done; } // If "NoTestSelect" appears in the command line then don't send the control // messages for selecting the test type. // if (!Test.NoTestSelect) { if (Bench_SetTestType(Test.DeviceHandle, Test.TestType, Test.Intf) != 1) { CONERR("setting bechmark test type #%d!\n%s\n", Test.TestType, usb_strerror()); goto Done; } } CONMSG("Benchmark device %04X:%04X opened..\n",Test.Vid, Test.Pid); // If reading from the device create the read transfer param. This will also create // a thread in a suspended state. // if (Test.TestType & TestTypeRead) { ReadTest = CreateTransferParam(&Test, Test.Ep | USB_ENDPOINT_DIR_MASK); if (!ReadTest) goto Done; } // If writing to the device create the write transfer param. This will also create // a thread in a suspended state. // if (Test.TestType & TestTypeWrite) { WriteTest = CreateTransferParam(&Test, Test.Ep); if (!WriteTest) goto Done; } // Set configuration #1. if (usb_set_configuration(Test.DeviceHandle, 1) < 0) { CONERR("setting configuration #%d!\n%s\n",1,usb_strerror()); goto Done; } // Claim_interface Test.Intf (Default is #0) if (usb_claim_interface(Test.DeviceHandle, Test.Intf) < 0) { CONERR("claiming interface #%d!\n%s\n", Test.Intf, usb_strerror()); goto Done; } // Set the alternate setting (Default is #0) if (usb_set_altinterface(Test.DeviceHandle, Test.Altf) < 0) { CONERR("selecting alternate setting #%d on interface #%d!\n%s\n", Test.Altf, Test.Intf, usb_strerror()); goto Done; } else { if (Test.Altf > 0) { CONDBG("selected alternate setting #%d on interface #%d\n",Test.Altf, Test.Intf); } } if (Test.Verify) { if (ReadTest && WriteTest) { if (CreateVerifyBuffer(&Test, WriteTest->Ep.wMaxPacketSize) < 0) goto Done; } else if (ReadTest) { if (CreateVerifyBuffer(&Test, ReadTest->Ep.wMaxPacketSize) < 0) goto Done; } } ShowTestInfo(&Test); ShowTransferInfo(ReadTest); ShowTransferInfo(WriteTest); CONMSG0("\nWhile the test is running:\n"); CONMSG0("Press 'Q' to quit\n"); CONMSG0("Press 'T' for test details\n"); CONMSG0("Press 'I' for status information\n"); CONMSG0("Press 'R' to reset averages\n"); CONMSG0("\nPress 'Q' to exit, any other key to begin.."); key = _getch(); CONMSG0("\n"); if (key=='Q' || key=='q') goto Done; // Set the thread priority and start it. if (ReadTest) { SetThreadPriority(ReadTest->ThreadHandle, Test.Priority); ResumeThread(ReadTest->ThreadHandle); } // Set the thread priority and start it. if (WriteTest) { SetThreadPriority(WriteTest->ThreadHandle, Test.Priority); ResumeThread(WriteTest->ThreadHandle); } while (!Test.IsCancelled) { Sleep(Test.Refresh); if (_kbhit()) { // A key was pressed. key = _getch(); switch (key) { case 'Q': case 'q': Test.IsUserAborted = TRUE; Test.IsCancelled = TRUE; break; case 'T': case 't': ShowTestInfo(&Test); break; case 'I': case 'i': // LOCK the display critical section EnterCriticalSection(&DisplayCriticalSection); // Print benchmark test details. ShowTransferInfo(ReadTest); ShowTransferInfo(WriteTest); // UNLOCK the display critical section LeaveCriticalSection(&DisplayCriticalSection); break; case 'R': case 'r': // LOCK the display critical section EnterCriticalSection(&DisplayCriticalSection); // Reset the running status. ResetRunningStatus(ReadTest); ResetRunningStatus(WriteTest); // UNLOCK the display critical section LeaveCriticalSection(&DisplayCriticalSection); break; } // Only one key at a time. while (_kbhit()) _getch(); } // If the read test should be running and it isn't, cancel the test. if ((ReadTest) && !ReadTest->IsRunning) { Test.IsCancelled = TRUE; break; } // If the write test should be running and it isn't, cancel the test. if ((WriteTest) && !WriteTest->IsRunning) { Test.IsCancelled = TRUE; break; } // Print benchmark stats if (ReadTest) ShowRunningStatus(ReadTest); else ShowRunningStatus(WriteTest); } // Wait for the transfer threads to complete gracefully if it // can be done in 10ms. All of the code from this point to // WaitForTestTransfer() is not required. It is here only to // improve response time when the test is cancelled. // Sleep(10); // If the thread is still running, abort and reset the endpoint. if ((ReadTest) && ReadTest->IsRunning) usb_resetep(Test.DeviceHandle, ReadTest->Ep.bEndpointAddress); // If the thread is still running, abort and reset the endpoint. if ((WriteTest) && WriteTest->IsRunning) usb_resetep(Test.DeviceHandle, WriteTest->Ep.bEndpointAddress); // Small delay incase usb_resetep() was called. Sleep(10); // WaitForTestTransfer will not return until the thread // has exited. WaitForTestTransfer(ReadTest); WaitForTestTransfer(WriteTest); // Print benchmark detailed stats ShowTestInfo(&Test); if (ReadTest) ShowTransferInfo(ReadTest); if (WriteTest) ShowTransferInfo(WriteTest); Done: if (Test.DeviceHandle) { usb_close(Test.DeviceHandle); Test.DeviceHandle = NULL; } if (Test.VerifyBuffer) { free(Test.VerifyBuffer); Test.VerifyBuffer = NULL; } FreeTransferParam(&ReadTest); FreeTransferParam(&WriteTest); DeleteCriticalSection(&DisplayCriticalSection); CONMSG0("Press any key to exit.."); _getch(); CONMSG0("\n"); return 0; } ////////////////////////////////////////////////////////////////////////////// /* END OF PROGRAM */ ////////////////////////////////////////////////////////////////////////////// void ShowHelp(void) { #define ID_HELP_TEXT 10020 #define ID_DOS_TEXT 300 CONST CHAR* src; DWORD src_count, charsWritten; HGLOBAL res_data; HANDLE handle; HRSRC hSrc; ShowCopyright(); hSrc = FindResourceA(NULL, MAKEINTRESOURCEA(ID_HELP_TEXT),MAKEINTRESOURCEA(ID_DOS_TEXT)); if (!hSrc) return; src_count = SizeofResource(NULL, hSrc); res_data = LoadResource(NULL, hSrc); if (!res_data) return; src = (char*) LockResource(res_data); if (!src) return; if ((handle=GetStdHandle(STD_ERROR_HANDLE)) != INVALID_HANDLE_VALUE) WriteConsoleA(handle, src, src_count, &charsWritten, NULL); } void ShowCopyright(void) { CONMSG0("libusb-win32 USB Benchmark v" RC_VERSION_STR "\n"); CONMSG0("Copyright (c) 2010 Travis Robinson. \n"); CONMSG0("website: http://sourceforge.net/projects/libusbdotnet\n"); }