/** @file ConnectionUSB.cpp @author Lime Microsystems (www.limemicro.com) @brief Data writing and reading through USB port */ #include "ConnectionUSB.h" #include <string.h> #ifdef __unix__ #include <thread> #include <chrono> #endif #define USB_TIMEOUT 1000 #define HW_LDIGIRED L"DigiRed" #define HW_LDIGIGREEN L"DigiGreen" #define HW_LSTREAMER L"Stream" #define HW_DIGIRED "DigiRed" #define HW_DIGIGREEN "DigiGreen" #define HW_STREAMER "Stream" #define CTR_W_REQCODE 0xC1 #define CTR_W_VALUE 0x0000 #define CTR_W_INDEX 0x0000 #define CTR_R_REQCODE 0xC0 #define CTR_R_VALUE 0x0000 #define CTR_R_INDEX 0x0000 /** @brief Initializes port type and object necessary to communicate to usb device. */ ConnectionUSB::ConnectionUSB() { m_hardwareName = ""; isConnected = false; m_connectionType = USB_PORT; #ifndef __unix__ USBDevicePrimary = new CCyUSBDevice(NULL); OutCtrEndPt = NULL; InCtrEndPt = NULL; InCtrlEndPt3 = NULL; OutCtrlEndPt3 = NULL; #else dev_handle = 0; devs = 0; int r = libusb_init(&ctx); //initialize the library for the session we just declared if(r < 0) printf("Init Error %i\n", r); //there was an error libusb_set_debug(ctx, 3); //set verbosity level to 3, as suggested in the documentation #endif currentDeviceIndex = -1; } /** @brief Closes connection to chip and deallocates used memory. */ ConnectionUSB::~ConnectionUSB() { Close(); #ifndef __unix__ delete USBDevicePrimary; #else libusb_exit(ctx); #endif } /** @brief Automatically open first available chip connected to usb port. @return 0-success, other-failure */ IConnection::DeviceStatus ConnectionUSB::Open() { currentDeviceIndex = -1; if(m_deviceNames.size() == 0) RefreshDeviceList(); #ifndef __unix__ for(int i=0; i<USBDevicePrimary->DeviceCount(); ++i) if( Open(i) == SUCCESS) { currentDeviceIndex = i; return SUCCESS; } #else if(Open(0) == SUCCESS) return SUCCESS; #endif return FAILURE; } /** @brief Tries to open connected USB device and find communication endpoints. @return Returns 1-Success, 0-EndPoints not found or device didn't connect. */ IConnection::DeviceStatus ConnectionUSB::Open(unsigned index) { #ifndef __unix__ wstring m_hardwareDesc = L""; if( index < USBDevicePrimary->DeviceCount()) { if(USBDevicePrimary->Open(index)) { m_hardwareDesc = USBDevicePrimary->Product; unsigned int pos; //determine connected board type pos = m_hardwareDesc.find(HW_LDIGIRED, 0); if( pos != wstring::npos ) m_hardwareName = HW_DIGIRED; else if (m_hardwareDesc.find(HW_LSTREAMER, 0) != wstring::npos) m_hardwareName = HW_STREAMER; else m_hardwareName = HW_STREAMER; if (InCtrlEndPt3) { delete InCtrlEndPt3; InCtrlEndPt3 = NULL; } InCtrlEndPt3 = new CCyControlEndPoint(*USBDevicePrimary->ControlEndPt); if (OutCtrlEndPt3) { delete OutCtrlEndPt3; OutCtrlEndPt3 = NULL; } OutCtrlEndPt3 = new CCyControlEndPoint(*USBDevicePrimary->ControlEndPt); InCtrlEndPt3->ReqCode = CTR_R_REQCODE; InCtrlEndPt3->Value = CTR_R_VALUE; InCtrlEndPt3->Index = CTR_R_INDEX; OutCtrlEndPt3->ReqCode = CTR_W_REQCODE; OutCtrlEndPt3->Value = CTR_W_VALUE; OutCtrlEndPt3->Index = CTR_W_INDEX; for (int i=0; i<USBDevicePrimary->EndPointCount(); i++) if(USBDevicePrimary->EndPoints[i]->Address == 0x01) { OutEndPt = USBDevicePrimary->EndPoints[i]; long len = OutEndPt->MaxPktSize * 64; OutEndPt->SetXferSize(len); break; } for (int i=0; i<USBDevicePrimary->EndPointCount(); i++) if(USBDevicePrimary->EndPoints[i]->Address == 0x81) { InEndPt = USBDevicePrimary->EndPoints[i]; long len = InEndPt->MaxPktSize * 64; InEndPt->SetXferSize(len); break; } isConnected = true; return SUCCESS; } //successfully opened device } //if has devices return FAILURE; #else if(index >= 0 && index < m_dev_pid_vid.size()) { dev_handle = libusb_open_device_with_vid_pid(ctx, m_dev_pid_vid[index].second, m_dev_pid_vid[index].first); if(dev_handle == 0) return FAILURE; if(libusb_kernel_driver_active(dev_handle, 0) == 1) //find out if kernel driver is attached { printf("Kernel Driver Active\n"); if(libusb_detach_kernel_driver(dev_handle, 0) == 0) //detach it printf("Kernel Driver Detached!\n"); } int r = libusb_claim_interface(dev_handle, 0); //claim interface 0 (the first) of device if(r < 0) { printf("Cannot Claim Interface\n"); return CANNOT_CLAIM_INTERFACE; } printf("Claimed Interface\n"); isConnected = true; return SUCCESS; } else { return FAILURE; } #endif } /** @brief Closes communication to device. */ void ConnectionUSB::Close() { #ifndef __unix__ USBDevicePrimary->Close(); InEndPt = NULL; OutEndPt = NULL; if (InCtrlEndPt3) { delete InCtrlEndPt3; InCtrlEndPt3 = NULL; } if (OutCtrlEndPt3) { delete OutCtrlEndPt3; OutCtrlEndPt3 = NULL; } #else if(dev_handle != 0) { libusb_release_interface(dev_handle, 0); libusb_close(dev_handle); dev_handle = 0; } #endif isConnected = false; } /** @brief Returns connection status @return 1-connection open, 0-connection closed. */ bool ConnectionUSB::IsOpen() { #ifndef __unix__ return USBDevicePrimary->IsOpen() && isConnected; #else return isConnected; #endif } /** @brief Sends given data buffer to chip through USB port. @param buffer data buffer, must not be longer than 64 bytes. @param length given buffer size. @param timeout_ms timeout limit for operation in milliseconds @return number of bytes sent. */ int ConnectionUSB::Write(const unsigned char *buffer, const int length, int timeout_ms) { long len = length; if(IsOpen()) { unsigned char* wbuffer = new unsigned char[length]; memcpy(wbuffer, buffer, length); if(m_hardwareName == HW_DIGIRED || m_hardwareName == HW_STREAMER) { #ifndef __unix__ if(OutCtrlEndPt3) OutCtrlEndPt3->Write(wbuffer, len); else len = 0; #else len = libusb_control_transfer(dev_handle, LIBUSB_REQUEST_TYPE_VENDOR,CTR_W_REQCODE ,CTR_W_VALUE, CTR_W_INDEX, wbuffer, length, USB_TIMEOUT); #endif } else { #ifndef __unix__ if(OutCtrEndPt) OutCtrEndPt->XferData(wbuffer, len); else len = 0; #else int actual = 0; libusb_bulk_transfer(dev_handle, 0x01, wbuffer, len, &actual, USB_TIMEOUT); len = actual; #endif } delete wbuffer; } else return 0; return len; } /** @brief Reads data coming from the chip through USB port. @param buffer pointer to array where received data will be copied, array must be big enough to fit received data. @param length number of bytes to read from chip. @param timeout_ms timeout limit for operation in milliseconds @return number of bytes received. */ int ConnectionUSB::Read(unsigned char *buffer, const int length, int timeout_ms) { long len = length; if(IsOpen()) { if(m_hardwareName == HW_DIGIRED || m_hardwareName == HW_STREAMER) { #ifndef __unix__ if(InCtrlEndPt3) InCtrlEndPt3->Read(buffer, len); else len = 0; #else len = libusb_control_transfer(dev_handle, LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN ,CTR_R_REQCODE ,CTR_R_VALUE, CTR_R_INDEX, buffer, len, USB_TIMEOUT); #endif } else { #ifndef __unix__ if(InCtrEndPt) InCtrEndPt->XferData(buffer, len); else len = 0; #else int actual = 0; libusb_bulk_transfer(dev_handle, 0x81, buffer, len, &actual, USB_TIMEOUT); len = actual; #endif } } return len; } #ifdef __unix__ /** @brief Function for handling libusb callbacks */ void callback_libusbtransfer(libusb_transfer *trans) { USBTransferContext *context = reinterpret_cast<USBTransferContext*>(trans->user_data); switch(trans->status) { case LIBUSB_TRANSFER_CANCELLED: printf("Transfer canceled\n" ); context->bytesXfered = trans->actual_length; context->done = true; context->used = false; context->reset(); break; case LIBUSB_TRANSFER_COMPLETED: if(trans->actual_length == context->bytesExpected) { context->bytesXfered = trans->actual_length; context->done = true; } //printf("Transfer complete %i\n", trans->actual_length); break; case LIBUSB_TRANSFER_ERROR: printf("TRANSFER ERRRO\n"); break; case LIBUSB_TRANSFER_TIMED_OUT: printf("transfer timed out\n"); break; case LIBUSB_TRANSFER_OVERFLOW: printf("transfer overflow\n"); break; case LIBUSB_TRANSFER_STALL: printf("transfer stalled\n"); break; } context->mPacketProcessed.notify_one(); } #endif /** @brief Finds all chips connected to usb ports @return number of devices found */ int ConnectionUSB::RefreshDeviceList() { #ifndef __unix__ USBDevicePrimary->Close(); currentDeviceIndex = -1; m_deviceNames.clear(); string name; if (USBDevicePrimary->DeviceCount()) { for (int i=0; i<USBDevicePrimary->DeviceCount(); ++i) { Open(i); name = DeviceName(); m_deviceNames.push_back(name); } currentDeviceIndex = -1; } #else m_dev_pid_vid.clear(); m_deviceNames.clear(); int usbDeviceCount = libusb_get_device_list(ctx, &devs); if(usbDeviceCount > 0) { libusb_device_descriptor desc; for(int i=0; i<usbDeviceCount; ++i) { int r = libusb_get_device_descriptor(devs[i], &desc); if(r<0) printf("failed to get device description\n"); int pid = desc.idProduct; int vid = desc.idVendor; if( vid == 1204) { if(pid == 34323) { m_hardwareName = HW_DIGIGREEN; m_deviceNames.push_back("DigiGreen"); m_dev_pid_vid.push_back( pair<int,int>(pid,vid)); } else if(pid == 241) { m_hardwareName = HW_DIGIRED; libusb_device_handle *tempDev_handle; tempDev_handle = libusb_open_device_with_vid_pid(ctx, vid, pid); if(libusb_kernel_driver_active(tempDev_handle, 0) == 1) //find out if kernel driver is attached { if(libusb_detach_kernel_driver(tempDev_handle, 0) == 0) //detach it printf("Kernel Driver Detached!\n"); } if(libusb_claim_interface(tempDev_handle, 0) < 0) //claim interface 0 (the first) of device { printf("Cannot Claim Interface\n"); } string fullName; //check operating speed int speed = libusb_get_device_speed(devs[i]); if(speed == LIBUSB_SPEED_HIGH) fullName = "USB 2.0"; else if(speed == LIBUSB_SPEED_SUPER) fullName = "USB 3.0"; else fullName = "USB"; fullName += " ("; //read device name char data[255]; memset(data, 0, 255); int st = libusb_get_string_descriptor_ascii(tempDev_handle, 2, (unsigned char*)data, 255); if(strlen(data) > 0) fullName += data; fullName += ")"; libusb_close(tempDev_handle); m_deviceNames.push_back(fullName); m_dev_pid_vid.push_back( pair<int,int>(pid,vid)); } } } } else { libusb_free_device_list(devs, 1); return 0; } #endif return m_deviceNames.size(); } void ConnectionUSB::ClearComm() { } /** @return name of currently opened device as string. */ string ConnectionUSB::DeviceName() { #ifndef __unix__ string name; char tempName[USB_STRING_MAXLEN]; //memcpy(tempName, USBDevicePrimary->FriendlyName, USB_STRING_MAXLEN); //name = tempName; for (int i = 0; i < USB_STRING_MAXLEN; ++i) tempName[i] = USBDevicePrimary->DeviceName[i]; if (USBDevicePrimary->bSuperSpeed == true) name = "USB 3.0"; else if (USBDevicePrimary->bHighSpeed == true) name = "USB 2.0"; else name = "USB"; name += " ("; name += tempName; name += ")"; return name; #else if(dev_handle != 0) { char data[255]; int st = libusb_get_string_descriptor_ascii(dev_handle, 2, (unsigned char*)data, 255); return string(data); } return "no name"; #endif } /** @brief Starts asynchronous data reading from board @param *buffer buffer where to store received data @param length number of bytes to read @return handle of transfer context */ int ConnectionUSB::BeginDataReading(char *buffer, long length) { int i = 0; bool contextFound = false; //find not used context for(i = 0; i<USB_MAX_CONTEXTS; i++) { if(!contexts[i].used) { contextFound = true; break; } } if(!contextFound) return -1; contexts[i].used = true; #ifndef __unix__ if(InEndPt) contexts[i].context = InEndPt->BeginDataXfer((unsigned char*)buffer, length, contexts[i].inOvLap); return i; #else unsigned int Timeout = 1000; libusb_transfer *tr = contexts[i].transfer; libusb_fill_bulk_transfer(tr, dev_handle, 0x81, (unsigned char*)buffer, length, callback_libusbtransfer, &contexts[i], Timeout); contexts[i].done = false; contexts[i].bytesXfered = 0; contexts[i].bytesExpected = length; int status = libusb_submit_transfer(tr); int actual = 0; //int status = libusb_bulk_transfer(dev_handle, 0x81, (unsigned char*)buffer, length, &actual, USB_TIMEOUT); if(status != 0) printf("ERROR BEGIN DATA TRANSFER %s\n", libusb_error_name(status)); #endif return i; } /** @brief Waits for asynchronous data reception @param contextHandle handle of which context data to wait @param timeout_ms number of miliseconds to wait @return 1-data received, 0-data not received */ int ConnectionUSB::WaitForReading(int contextHandle, unsigned int timeout_ms) { if( contexts[contextHandle].used == true && contextHandle >= 0) { int status = 0; #ifndef __unix__ if(InEndPt) status = InEndPt->WaitForXfer(contexts[contextHandle].inOvLap, timeout_ms); return status; #else struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 0; if(libusb_handle_events_timeout_completed(ctx, &tv, NULL) != 0) printf("error libusb_handle_events %i\n", status); auto t1 = chrono::high_resolution_clock::now(); auto t2 = chrono::high_resolution_clock::now(); while(contexts[contextHandle].done == false && std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count() < timeout_ms) { struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 300; std::this_thread::yield(); //if(libusb_handle_events(ctx) != 0) if(libusb_handle_events_timeout_completed(ctx, &tv, NULL) != 0) printf("error libusb_handle_events %i\n", status); t2 = chrono::high_resolution_clock::now(); //std::this_thread::sleep_for(std::chrono::milliseconds(1)); } std::unique_lock<std::mutex> lck(contexts[contextHandle].m_lock); while(contexts[contextHandle].done == false) //is changed in libusb callback { if(contexts[contextHandle].mPacketProcessed.wait_for(lck, std::chrono::milliseconds(timeout_ms)) == std::cv_status::timeout) return 0; } return contexts[contextHandle].done == true; #endif } else return 0; } /** @brief Finishes asynchronous data reading from board @param buffer array where to store received data @param length number of bytes to read, function changes this value to number of bytes actually received @param contextHandle handle of which context to finish @return false failure, true number of bytes received */ int ConnectionUSB::FinishDataReading(char *buffer, long &length, int contextHandle) { if( contexts[contextHandle].used == true && contextHandle >= 0) { #ifndef __unix__ int status = 0; if(InEndPt) status = InEndPt->FinishDataXfer((unsigned char*)buffer, length, contexts[contextHandle].inOvLap, contexts[contextHandle].context); contexts[contextHandle].used = false; contexts[contextHandle].reset(); return length; #else length = contexts[contextHandle].bytesXfered; contexts[contextHandle].used = false; contexts[contextHandle].reset(); return length; #endif } else return 0; } int ConnectionUSB::ReadDataBlocking(char *buffer, long &length, int timeout_ms) { #ifndef __unix__ return InEndPt->XferData((unsigned char*)buffer, length); #else return 0; #endif } /** @brief Aborts reading operations */ void ConnectionUSB::AbortReading() { #ifndef __unix__ InEndPt->Abort(); #else for(int i=0; i<USB_MAX_CONTEXTS; ++i) { libusb_cancel_transfer( contexts[i].transfer ); } #endif } /** @brief Starts asynchronous data Sending to board @param *buffer buffer to send @param length number of bytes to send @return handle of transfer context */ int ConnectionUSB::BeginDataSending(const char *buffer, long length) { int i = 0; //find not used context bool contextFound = false; for(i = 0; i<USB_MAX_CONTEXTS; i++) { if(!contextsToSend[i].used) { contextFound = true; break; } } if(!contextFound) return -1; contextsToSend[i].used = true; #ifndef __unix__ if(OutEndPt) contextsToSend[i].context = OutEndPt->BeginDataXfer((unsigned char*)buffer, length, contextsToSend[i].inOvLap); return i; #else unsigned int Timeout = 1000; libusb_transfer *tr = contextsToSend[i].transfer; //libusb_set_iso_packet_lengths(contexts[i].transfer, 512*64); libusb_fill_bulk_transfer(tr, dev_handle, 0x1, (unsigned char*)buffer, length, callback_libusbtransfer, &contextsToSend[i], Timeout); contextsToSend[i].done = false; contextsToSend[i].bytesXfered = 0; contextsToSend[i].bytesExpected = length; libusb_submit_transfer(tr); #endif return i; } /** @brief Waits for asynchronous data sending @param contextHandle handle of which context data to wait @param timeout_ms number of miliseconds to wait @return 1-data received, 0-data not received */ int ConnectionUSB::WaitForSending(int contextHandle, unsigned int timeout_ms) { if( contextsToSend[contextHandle].used == true ) { #ifndef __unix__ int status = 0; if(OutEndPt) status = OutEndPt->WaitForXfer(contextsToSend[contextHandle].inOvLap, timeout_ms); return status; #else auto t1 = chrono::high_resolution_clock::now(); auto t2 = chrono::high_resolution_clock::now(); while(contextsToSend[contextHandle].done == false && std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count() < timeout_ms) { /* struct timeval tv; tv.tv_sec = 1; tv.tv_usec = 0; int status = libusb_handle_events_timeout_completed(ctx, &tv, NULL); if(status != 0) printf("error libusb_handle_events %i\n", status);*/ t2 = chrono::high_resolution_clock::now(); //std::this_thread::sleep_for(std::chrono::milliseconds(1)); } std::unique_lock<std::mutex> lck(contextsToSend[contextHandle].m_lock); while(contextsToSend[contextHandle].done == false) //is changed in libusb callback { if(contextsToSend[contextHandle].mPacketProcessed.wait_for(lck, std::chrono::milliseconds(timeout_ms)) == std::cv_status::timeout) return 0; } return contextsToSend[contextHandle].done == true; #endif } else return 0; } /** @brief Finishes asynchronous data sending to board @param buffer array where to store received data @param length number of bytes to read, function changes this value to number of bytes acctually received @param contextHandle handle of which context to finish @return false failure, true number of bytes sent */ int ConnectionUSB::FinishDataSending(const char *buffer, long &length, int contextHandle) { if( contextsToSend[contextHandle].used == true) { #ifndef __unix__ if(OutEndPt) OutEndPt->FinishDataXfer((unsigned char*)buffer, length, contextsToSend[contextHandle].inOvLap, contextsToSend[contextHandle].context); contextsToSend[contextHandle].used = false; contextsToSend[contextHandle].reset(); return length; #else length = contextsToSend[contextHandle].bytesXfered; contextsToSend[contextHandle].used = false; contextsToSend[contextHandle].reset(); return length; #endif } else return 0; } /** @brief Aborts sending operations */ void ConnectionUSB::AbortSending() { #ifndef __unix__ OutEndPt->Abort(); #else for (int i = 0; i<USB_MAX_CONTEXTS; ++i) { libusb_cancel_transfer(contextsToSend[i].transfer); } for(int i=0; i<USB_MAX_CONTEXTS; ++i) { contextsToSend[i].used = false; contextsToSend[i].reset(); } #endif } int ConnectionUSB::GetOpenedIndex() { return currentDeviceIndex; } /** @brief Returns found devices names @return vector of device names */ vector<string> ConnectionUSB::GetDeviceNames() { return m_deviceNames; }