Initial commit
commit
e2e5017331
|
|
@ -0,0 +1,7 @@
|
||||||
|
build/
|
||||||
|
build*/
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
.vscode/settings.json
|
||||||
|
third-party/concurrentqueue/benchmarks
|
||||||
|
third-party/concurrentqueue/tests
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
{
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Win32",
|
||||||
|
"includePath": [
|
||||||
|
],
|
||||||
|
"defines": [
|
||||||
|
"_WIN32",
|
||||||
|
"__WIN32",
|
||||||
|
"_DEBUG",
|
||||||
|
"UNICODE",
|
||||||
|
"_UNICODE"
|
||||||
|
],
|
||||||
|
"windowsSdkVersion": "10.0.17134.0",
|
||||||
|
"compilerPath": "C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.14.26428/bin/Hostx64/x64/cl.exe",
|
||||||
|
"cStandard": "c11",
|
||||||
|
"cppStandard": "c++17",
|
||||||
|
"intelliSenseMode": "msvc-x64",
|
||||||
|
"configurationProvider": "vector-of-bool.cmake-tools",
|
||||||
|
"compileCommands": "${workspaceFolder}/build/compile_commands.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Linux",
|
||||||
|
"includePath": [
|
||||||
|
"/usr/include/**",
|
||||||
|
"/usr/local/include/**",
|
||||||
|
"${workspaceRoot}/**"
|
||||||
|
],
|
||||||
|
"defines": [
|
||||||
|
"_DEBUG",
|
||||||
|
"UNICODE",
|
||||||
|
"_UNICODE"
|
||||||
|
],
|
||||||
|
"compilerPath": "/usr/bin/gcc",
|
||||||
|
"cStandard": "c11",
|
||||||
|
"cppStandard": "c++17",
|
||||||
|
"intelliSenseMode": "gcc-x64",
|
||||||
|
"configurationProvider": "vector-of-bool.cmake-tools",
|
||||||
|
"compileCommands": "${workspaceFolder}/build/compile_commands.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 4
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
cmake_minimum_required(VERSION 3.2)
|
||||||
|
project(icsneonext VERSION 0.1.0)
|
||||||
|
|
||||||
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
|
include_directories(${CMAKE_SOURCE_DIR})
|
||||||
|
|
||||||
|
# Enable Warnings
|
||||||
|
if(MSVC)
|
||||||
|
# Force to always compile with W4
|
||||||
|
if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]")
|
||||||
|
string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
|
||||||
|
else()
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4")
|
||||||
|
endif()
|
||||||
|
else() #if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-nested-anon-types -Wno-gnu-anonymous-struct -Wno-unknown-pragmas -Wno-zero-length-array -pedantic")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# libftdi
|
||||||
|
if(NOT WIN32)
|
||||||
|
include_directories(${CMAKE_SOURCE_DIR} third-party/libftdi/src third-party/libftdi/ftdipp)
|
||||||
|
add_subdirectory(third-party/libftdi)
|
||||||
|
endif(NOT WIN32)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
file(GLOB PLATFORM_SRC ${CMAKE_SOURCE_DIR}/platform/windows/*.cpp)
|
||||||
|
else()
|
||||||
|
file(GLOB PLATFORM_SRC ${CMAKE_SOURCE_DIR}/platform/linux/*.cpp)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(COMMON_SRC
|
||||||
|
communication/messagedecoder.cpp
|
||||||
|
communication/multichannelcommunication.cpp
|
||||||
|
communication/communication.cpp
|
||||||
|
communication/icommunication.cpp
|
||||||
|
device/devicefinder.cpp
|
||||||
|
device/device.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
set(SRC_FILES ${COMMON_SRC} ${PLATFORM_SRC})
|
||||||
|
|
||||||
|
add_library(icsneocpp
|
||||||
|
api/icsneocpp/icsneocpp.cpp
|
||||||
|
${SRC_FILES}
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(icsneoc SHARED
|
||||||
|
api/icsneoc/icsneoc.cpp
|
||||||
|
api/icsneocpp/icsneocpp.cpp
|
||||||
|
${SRC_FILES}
|
||||||
|
)
|
||||||
|
|
||||||
|
# libftdi
|
||||||
|
if(NOT WIN32)
|
||||||
|
find_package(Threads)
|
||||||
|
set_property(TARGET ftdi1-static PROPERTY POSITION_INDEPENDENT_CODE ON)
|
||||||
|
set_property(TARGET ftdipp1-static PROPERTY POSITION_INDEPENDENT_CODE ON)
|
||||||
|
target_link_libraries(icsneocpp ftdipp1-static)
|
||||||
|
target_link_libraries(icsneoc ftdipp1-static)
|
||||||
|
target_link_libraries(icsneocpp ftdi1-static)
|
||||||
|
target_link_libraries(icsneoc ftdi1-static)
|
||||||
|
target_link_libraries(icsneocpp ${CMAKE_THREAD_LIBS_INIT})
|
||||||
|
target_link_libraries(icsneoc ${CMAKE_THREAD_LIBS_INIT})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
|
||||||
|
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
|
||||||
|
include(CPack)
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
Hardware
|
||||||
|
=========
|
||||||
|
|
||||||
|
STM32 devices
|
||||||
|
ValueCAN 4
|
||||||
|
CAN 2.0 works
|
||||||
|
|
||||||
|
FTDI devices
|
||||||
|
neoVI FIRE
|
||||||
|
HSCAN works
|
||||||
|
ValueCAN 3
|
||||||
|
CAN works
|
||||||
|
RADStar 2
|
||||||
|
CAN works
|
||||||
|
|
@ -0,0 +1,130 @@
|
||||||
|
#ifndef __cplusplus
|
||||||
|
#error "icsneoc.cpp must be compiled with a C++ compiler!"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define ICSNEOC_MAKEDLL
|
||||||
|
|
||||||
|
#include "include/icsneoc.h"
|
||||||
|
#include "api/icsneocpp/include/icsneocpp.h"
|
||||||
|
#include "platform/include/dynamiclib.h"
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
using namespace icsneo;
|
||||||
|
|
||||||
|
// Holds references for the shared_ptrs so they do not get freed until we're ready
|
||||||
|
static std::vector<std::shared_ptr<Device>> connectableFoundDevices, connectedDevices;
|
||||||
|
|
||||||
|
// Any shared_ptrs we've let go should be placed here so they're not accessed
|
||||||
|
static std::vector<Device*> freedDevices;
|
||||||
|
|
||||||
|
void icsneoFindAllDevices(neodevice_t* devices, size_t* count) {
|
||||||
|
icsneoFreeUnconnectedDevices(); // Mark previous results as freed so they can no longer be connected to
|
||||||
|
auto foundDevices = icsneo::FindAllDevices();
|
||||||
|
|
||||||
|
auto inputSize = *count;
|
||||||
|
*count = foundDevices.size();
|
||||||
|
auto outputSize = *count;
|
||||||
|
if(outputSize > inputSize) {
|
||||||
|
// TODO an error should be returned that the data was truncated
|
||||||
|
outputSize = inputSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(size_t i = 0; i < outputSize; i++) {
|
||||||
|
connectableFoundDevices.push_back(foundDevices[i]);
|
||||||
|
devices[i] = foundDevices[i]->getNeoDevice();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void icsneoFreeUnconnectedDevices() {
|
||||||
|
for(auto& devptr : connectableFoundDevices) {
|
||||||
|
freedDevices.push_back(devptr.get());
|
||||||
|
}
|
||||||
|
connectableFoundDevices.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool icsneoSerialNumToString(uint32_t num, char* str, size_t* count) {
|
||||||
|
auto result = Device::SerialNumToString(num);
|
||||||
|
if(*count <= result.length()) {
|
||||||
|
*count = result.length() + 1; // This is how big of a buffer we need
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
strcpy(str, result.c_str()); // TODO bad
|
||||||
|
*count = result.length();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t icsneoSerialStringToNum(const char* str) {
|
||||||
|
return Device::SerialStringToNum(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool icsneoIsValidNeoDevice(const neodevice_t* device) {
|
||||||
|
// If this neodevice_t was returned by a previous search, it will no longer be valid (as the underlying icsneo::Device is freed)
|
||||||
|
return std::find(freedDevices.begin(), freedDevices.end(), device->device) == freedDevices.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool icsneoOpenDevice(const neodevice_t* device) {
|
||||||
|
if(!icsneoIsValidNeoDevice(device))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(!device->device->open())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// We connected successfully, move the device to connected devices
|
||||||
|
std::vector<std::vector<std::shared_ptr<Device>>::iterator> itemsToMove;
|
||||||
|
for(auto it = connectableFoundDevices.begin(); it < connectableFoundDevices.end(); it++) {
|
||||||
|
if((*it).get() == device->device)
|
||||||
|
itemsToMove.push_back(it);
|
||||||
|
}
|
||||||
|
for(auto it : itemsToMove) {
|
||||||
|
connectedDevices.push_back(*it);
|
||||||
|
connectableFoundDevices.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool icsneoCloseDevice(const neodevice_t* device) {
|
||||||
|
if(!icsneoIsValidNeoDevice(device))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(!device->device->close())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// We disconnected successfully, free the device and mark it as freed
|
||||||
|
std::vector<std::vector<std::shared_ptr<Device>>::iterator> itemsToDelete;
|
||||||
|
for(auto it = connectedDevices.begin(); it < connectedDevices.end(); it++) {
|
||||||
|
if((*it).get() == device->device)
|
||||||
|
itemsToDelete.push_back(it);
|
||||||
|
}
|
||||||
|
for(auto it : itemsToDelete)
|
||||||
|
connectedDevices.erase(it);
|
||||||
|
|
||||||
|
freedDevices.push_back(device->device);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool icsneoGoOnline(const neodevice_t* device) {
|
||||||
|
if(!icsneoIsValidNeoDevice(device))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return device->device->goOnline();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool icsneoGoOffline(const neodevice_t* device) {
|
||||||
|
if(!icsneoIsValidNeoDevice(device))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return device->device->goOffline();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool icsneoIsOnline(const neodevice_t* device) {
|
||||||
|
if(!icsneoIsValidNeoDevice(device))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return device->device->isOnline();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
#ifndef __ICSNEOC_H_
|
||||||
|
#define __ICSNEOC_H_
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include "device/include/neodevice.h" // For neodevice_t
|
||||||
|
#include "platform/include/dynamiclib.h" // Dynamic library loading and exporting
|
||||||
|
|
||||||
|
#ifndef ICSNEOC_DYNAMICLOAD
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern void DLLExport icsneoFindAllDevices(neodevice_t* devices, size_t* count);
|
||||||
|
|
||||||
|
extern void DLLExport icsneoFreeUnconnectedDevices();
|
||||||
|
|
||||||
|
extern bool DLLExport icsneoSerialNumToString(uint32_t num, char* str, size_t* count);
|
||||||
|
|
||||||
|
extern uint32_t DLLExport icsneoSerialStringToNum(const char* str);
|
||||||
|
|
||||||
|
extern bool DLLExport icsneoIsValidNeoDevice(const neodevice_t* device);
|
||||||
|
|
||||||
|
extern bool DLLExport icsneoOpenDevice(const neodevice_t* device);
|
||||||
|
|
||||||
|
extern bool DLLExport icsneoCloseDevice(const neodevice_t* device);
|
||||||
|
|
||||||
|
extern bool DLLExport icsneoGoOnline(const neodevice_t* device);
|
||||||
|
|
||||||
|
extern bool DLLExport icsneoGoOffline(const neodevice_t* device);
|
||||||
|
|
||||||
|
extern bool DLLExport icsneoIsOnline(const neodevice_t* device);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#else // ICSNEOC_DYNAMICLOAD
|
||||||
|
|
||||||
|
typedef void(*fn_icsneoFindAllDevices)(neodevice_t* devices, size_t* count);
|
||||||
|
fn_icsneoFindAllDevices icsneoFindAllDevices;
|
||||||
|
|
||||||
|
typedef void(*fn_icsneoFreeUnconnectedDevices)();
|
||||||
|
fn_icsneoFreeUnconnectedDevices icsneoFreeUnconnectedDevices;
|
||||||
|
|
||||||
|
typedef bool(*fn_icsneoSerialNumToString)(uint32_t num, char* str, size_t* count);
|
||||||
|
fn_icsneoSerialNumToString icsneoSerialNumToString;
|
||||||
|
|
||||||
|
typedef uint32_t(*fn_icsneoSerialStringToNum)(const char* str);
|
||||||
|
fn_icsneoSerialStringToNum icsneoSerialStringToNum;
|
||||||
|
|
||||||
|
typedef bool(*fn_icsneoIsValidNeoDevice)(const neodevice_t* device);
|
||||||
|
fn_icsneoIsValidNeoDevice icsneoIsValidNeoDevice;
|
||||||
|
|
||||||
|
typedef bool(*fn_icsneoOpenDevice)(const neodevice_t* device);
|
||||||
|
fn_icsneoOpenDevice icsneoOpenDevice;
|
||||||
|
|
||||||
|
typedef bool(*fn_icsneoCloseDevice)(const neodevice_t* device);
|
||||||
|
fn_icsneoCloseDevice icsneoCloseDevice;
|
||||||
|
|
||||||
|
typedef bool(*fn_icsneoGoOnline)(const neodevice_t* device);
|
||||||
|
fn_icsneoGoOnline icsneoGoOnline;
|
||||||
|
|
||||||
|
typedef bool(*fn_icsneoGoOffline)(const neodevice_t* device);
|
||||||
|
fn_icsneoGoOffline icsneoGoOffline;
|
||||||
|
|
||||||
|
typedef bool(*fn_icsneoIsOnline)(const neodevice_t* device);
|
||||||
|
fn_icsneoIsOnline icsneoIsOnline;
|
||||||
|
|
||||||
|
#define ICSNEO_IMPORT(func) func = (fn_##func)icsneoDynamicLibraryGetFunction(icsneoLibraryHandle, #func)
|
||||||
|
#define ICSNEO_IMPORTASSERT(func) if((ICSNEO_IMPORT(func)) == NULL) return 3
|
||||||
|
void* icsneoLibraryHandle = NULL;
|
||||||
|
bool icsneoInitialized = false;
|
||||||
|
bool icsneoDestroyed = false;
|
||||||
|
int icsneoInit() {
|
||||||
|
icsneoDestroyed = false;
|
||||||
|
if(icsneoInitialized)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
icsneoLibraryHandle = icsneoDynamicLibraryLoad();
|
||||||
|
if(icsneoLibraryHandle == NULL)
|
||||||
|
return 2;
|
||||||
|
|
||||||
|
ICSNEO_IMPORTASSERT(icsneoFindAllDevices);
|
||||||
|
ICSNEO_IMPORTASSERT(icsneoFreeUnconnectedDevices);
|
||||||
|
ICSNEO_IMPORTASSERT(icsneoSerialNumToString);
|
||||||
|
ICSNEO_IMPORTASSERT(icsneoSerialStringToNum);
|
||||||
|
ICSNEO_IMPORTASSERT(icsneoIsValidNeoDevice);
|
||||||
|
ICSNEO_IMPORTASSERT(icsneoOpenDevice);
|
||||||
|
ICSNEO_IMPORTASSERT(icsneoCloseDevice);
|
||||||
|
ICSNEO_IMPORTASSERT(icsneoGoOnline);
|
||||||
|
ICSNEO_IMPORTASSERT(icsneoGoOffline);
|
||||||
|
ICSNEO_IMPORTASSERT(icsneoIsOnline);
|
||||||
|
|
||||||
|
icsneoInitialized = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool icsneoClose() ICSNEO_DESTRUCTOR {
|
||||||
|
icsneoInitialized = false;
|
||||||
|
if(icsneoDestroyed)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return icsneoDestroyed = icsneoDynamicLibraryClose(icsneoLibraryHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // ICSNEOC_DYNAMICLOAD
|
||||||
|
|
||||||
|
#endif // __ICSNEOC_H_
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
#include <iterator>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "include/icsneocpp.h"
|
||||||
|
#include "device/include/devicefinder.h"
|
||||||
|
|
||||||
|
using namespace icsneo;
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<Device>> icsneo::FindAllDevices() {
|
||||||
|
return DeviceFinder::FindAll();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
#ifndef __ICSNEOCPP_H_
|
||||||
|
#define __ICSNEOCPP_H_
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "device/include/device.h"
|
||||||
|
|
||||||
|
namespace icsneo {
|
||||||
|
std::vector<std::shared_ptr<Device>> FindAllDevices();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
#include "communication/include/communication.h"
|
||||||
|
#include <chrono>
|
||||||
|
#include <iostream>
|
||||||
|
#include <queue>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <cstring>
|
||||||
|
#include "communication/include/messagedecoder.h"
|
||||||
|
|
||||||
|
using namespace icsneo;
|
||||||
|
|
||||||
|
int Communication::messageCallbackIDCounter = 1;
|
||||||
|
|
||||||
|
uint8_t Communication::ICSChecksum(const std::vector<uint8_t>& data) {
|
||||||
|
uint32_t checksum = 0;
|
||||||
|
for(auto i = 0; i < data.size(); i++)
|
||||||
|
checksum += data[i];
|
||||||
|
checksum = ~checksum;
|
||||||
|
checksum++;
|
||||||
|
return (uint8_t)checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t>& Communication::packetWrap(std::vector<uint8_t>& data, bool addChecksum) {
|
||||||
|
if(addChecksum)
|
||||||
|
data.push_back(ICSChecksum(data));
|
||||||
|
data.insert(data.begin(), 0xAA);
|
||||||
|
if(align16bit && data.size() % 2 == 1)
|
||||||
|
data.push_back('A');
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Communication::open() {
|
||||||
|
if(isOpen)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
spawnThreads();
|
||||||
|
isOpen = true;
|
||||||
|
return impl->open();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Communication::spawnThreads() {
|
||||||
|
readTaskThread = std::thread(&Communication::readTask, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Communication::joinThreads() {
|
||||||
|
if(readTaskThread.joinable())
|
||||||
|
readTaskThread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Communication::close() {
|
||||||
|
if(!isOpen)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
isOpen = false;
|
||||||
|
closing = true;
|
||||||
|
joinThreads();
|
||||||
|
|
||||||
|
return impl->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Communication::sendPacket(std::vector<uint8_t>& bytes) {
|
||||||
|
return impl->write(Communication::packetWrap(bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Communication::sendCommand(Communication::Command cmd, std::vector<uint8_t> arguments) {
|
||||||
|
std::vector<uint8_t> bytes;
|
||||||
|
bytes.push_back((uint8_t)cmd);
|
||||||
|
for(auto& b : arguments)
|
||||||
|
bytes.push_back(b);
|
||||||
|
bytes.insert(bytes.begin(), 0xB | ((uint8_t)bytes.size() << 4));
|
||||||
|
return sendPacket(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Communication::addMessageCallback(const MessageCallback& cb) {
|
||||||
|
messageCallbacks.insert(std::make_pair(messageCallbackIDCounter, cb));
|
||||||
|
return messageCallbackIDCounter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Communication::removeMessageCallback(int id) {
|
||||||
|
try {
|
||||||
|
messageCallbacks.erase(id);
|
||||||
|
return true;
|
||||||
|
} catch(...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Communication::readTask() {
|
||||||
|
std::vector<uint8_t> readBytes;
|
||||||
|
MessageDecoder decoder;
|
||||||
|
|
||||||
|
while(!closing) {
|
||||||
|
readBytes.clear();
|
||||||
|
if(impl->readWait(readBytes)) {
|
||||||
|
if(decoder.input(readBytes)) {
|
||||||
|
for(auto& msg : decoder.output()) {
|
||||||
|
for(auto& cb : messageCallbacks) { // We might have closed while reading or processing
|
||||||
|
if(!closing) {
|
||||||
|
cb.second.callIfMatch(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
#include "communication/include/icommunication.h"
|
||||||
|
|
||||||
|
using namespace icsneo;
|
||||||
|
|
||||||
|
bool ICommunication::read(std::vector<uint8_t>& bytes, size_t limit) {
|
||||||
|
// A limit of zero indicates no limit
|
||||||
|
if(limit == 0)
|
||||||
|
limit = (size_t)-1;
|
||||||
|
|
||||||
|
if(limit > (readQueue.size_approx() + 4))
|
||||||
|
limit = (readQueue.size_approx() + 4);
|
||||||
|
|
||||||
|
if(bytes.capacity() < limit)
|
||||||
|
bytes.resize(limit);
|
||||||
|
|
||||||
|
size_t actuallyRead = readQueue.try_dequeue_bulk(bytes.data(), limit);
|
||||||
|
|
||||||
|
bytes.resize(actuallyRead);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ICommunication::readWait(std::vector<uint8_t>& bytes, std::chrono::milliseconds timeout, size_t limit) {
|
||||||
|
// A limit of zero indicates no limit
|
||||||
|
if(limit == 0)
|
||||||
|
limit = (size_t)-1;
|
||||||
|
|
||||||
|
if(limit > (readQueue.size_approx() + 4))
|
||||||
|
limit = (readQueue.size_approx() + 4);
|
||||||
|
|
||||||
|
bytes.resize(limit);
|
||||||
|
|
||||||
|
size_t actuallyRead = readQueue.wait_dequeue_bulk_timed(bytes.data(), limit, timeout);
|
||||||
|
|
||||||
|
bytes.resize(actuallyRead);
|
||||||
|
|
||||||
|
return actuallyRead > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ICommunication::write(const std::vector<uint8_t>& bytes) {
|
||||||
|
return writeQueue.enqueue(WriteOperation(bytes));
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
#ifndef __COMMUNICATION_H_
|
||||||
|
#define __COMMUNICATION_H_
|
||||||
|
|
||||||
|
#include "communication/include/icommunication.h"
|
||||||
|
#include "communication/include/network.h"
|
||||||
|
#include "communication/include/messagecallback.h"
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
#include <atomic>
|
||||||
|
#include <thread>
|
||||||
|
#include <queue>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
namespace icsneo {
|
||||||
|
|
||||||
|
class Communication {
|
||||||
|
public:
|
||||||
|
static uint8_t ICSChecksum(const std::vector<uint8_t>& data);
|
||||||
|
|
||||||
|
Communication(std::shared_ptr<ICommunication> com) : impl(com) {}
|
||||||
|
virtual ~Communication() { close(); }
|
||||||
|
|
||||||
|
bool open();
|
||||||
|
bool close();
|
||||||
|
virtual void spawnThreads();
|
||||||
|
virtual void joinThreads();
|
||||||
|
bool rawWrite(const std::vector<uint8_t>& bytes) { return impl->write(bytes); }
|
||||||
|
std::vector<uint8_t>& packetWrap(std::vector<uint8_t>& data, bool addChecksum = true);
|
||||||
|
bool sendPacket(std::vector<uint8_t>& bytes);
|
||||||
|
|
||||||
|
enum class Command : uint8_t {
|
||||||
|
EnableNetworkCommunication = 0x07,
|
||||||
|
RequestSerialNumber = 0xA1
|
||||||
|
};
|
||||||
|
virtual bool sendCommand(Command cmd, bool boolean) { return sendCommand(cmd, std::vector<uint8_t>({ (uint8_t)boolean })); }
|
||||||
|
virtual bool sendCommand(Command cmd, std::vector<uint8_t> arguments = {});
|
||||||
|
|
||||||
|
int addMessageCallback(const MessageCallback& cb);
|
||||||
|
bool removeMessageCallback(int id);
|
||||||
|
|
||||||
|
void setAlign16Bit(bool enable) { align16bit = enable; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::shared_ptr<ICommunication> impl;
|
||||||
|
static int messageCallbackIDCounter;
|
||||||
|
std::map<int, MessageCallback> messageCallbacks;
|
||||||
|
std::atomic<bool> closing{false};
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool isOpen = false;
|
||||||
|
bool align16bit = true; // Not needed for Gigalog, Galaxy, etc and newer
|
||||||
|
|
||||||
|
std::thread readTaskThread;
|
||||||
|
void readTask();
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
#ifndef __ICOMMUNICATION_H_
|
||||||
|
#define __ICOMMUNICATION_H_
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <chrono>
|
||||||
|
#include <atomic>
|
||||||
|
#include <thread>
|
||||||
|
#include "third-party/concurrentqueue/blockingconcurrentqueue.h"
|
||||||
|
|
||||||
|
namespace icsneo {
|
||||||
|
|
||||||
|
class ICommunication {
|
||||||
|
public:
|
||||||
|
virtual ~ICommunication() {}
|
||||||
|
virtual bool open() = 0;
|
||||||
|
virtual bool isOpen() = 0;
|
||||||
|
virtual bool close() = 0;
|
||||||
|
virtual bool read(std::vector<uint8_t>& bytes, size_t limit = 0);
|
||||||
|
virtual bool readWait(std::vector<uint8_t>& bytes, std::chrono::milliseconds timeout = std::chrono::milliseconds(100), size_t limit = 0);
|
||||||
|
virtual bool write(const std::vector<uint8_t>& bytes);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
class WriteOperation {
|
||||||
|
public:
|
||||||
|
WriteOperation() {}
|
||||||
|
WriteOperation(std::vector<uint8_t> b) { bytes = b; }
|
||||||
|
std::vector<uint8_t> bytes;
|
||||||
|
};
|
||||||
|
enum IOTaskState {
|
||||||
|
LAUNCH,
|
||||||
|
WAIT
|
||||||
|
};
|
||||||
|
virtual void readTask() = 0;
|
||||||
|
virtual void writeTask() = 0;
|
||||||
|
moodycamel::BlockingConcurrentQueue<uint8_t> readQueue;
|
||||||
|
moodycamel::BlockingConcurrentQueue<WriteOperation> writeQueue;
|
||||||
|
std::thread readThread, writeThread;
|
||||||
|
std::atomic<bool> closing{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
#ifndef __MESSAGECALLBACK_H_
|
||||||
|
#define __MESSAGECALLBACK_H_
|
||||||
|
|
||||||
|
#include "communication/message/include/message.h"
|
||||||
|
#include "communication/include/messagefilter.h"
|
||||||
|
#include <memory>
|
||||||
|
#include <functional>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace icsneo {
|
||||||
|
|
||||||
|
class MessageCallback {
|
||||||
|
public:
|
||||||
|
typedef std::function< void( std::shared_ptr<Message> ) > fn_messageCallback;
|
||||||
|
|
||||||
|
MessageCallback(fn_messageCallback cb, std::shared_ptr<MessageFilter> f) : callback(cb), filter(f) {}
|
||||||
|
MessageCallback(fn_messageCallback cb, MessageFilter f = MessageFilter()) : callback(cb), filter(std::make_shared<MessageFilter>(f)) {}
|
||||||
|
|
||||||
|
// Allow the filter to be placed first if the user wants (maybe in the case of a lambda)
|
||||||
|
MessageCallback(MessageFilter f, fn_messageCallback cb) { MessageCallback(cb, f); }
|
||||||
|
|
||||||
|
virtual bool callIfMatch(const std::shared_ptr<Message>& message) const {
|
||||||
|
bool ret = filter->match(message);
|
||||||
|
if(ret)
|
||||||
|
callback(message);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
const MessageFilter& getFilter() const { return *filter; }
|
||||||
|
const fn_messageCallback& getCallback() const { return callback; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
fn_messageCallback callback;
|
||||||
|
std::shared_ptr<MessageFilter> filter;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CANMessageCallback : public MessageCallback {
|
||||||
|
public:
|
||||||
|
CANMessageCallback(fn_messageCallback cb, CANMessageFilter f = CANMessageFilter()) : MessageCallback(cb, std::make_shared<CANMessageFilter>(f)) {}
|
||||||
|
|
||||||
|
// Allow the filter to be placed first if the user wants (maybe in the case of a lambda)
|
||||||
|
CANMessageCallback(CANMessageFilter f, fn_messageCallback cb) : MessageCallback(cb, std::make_shared<CANMessageFilter>(f)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,258 @@
|
||||||
|
#ifndef __MESSAGEDECODER_H_
|
||||||
|
#define __MESSAGEDECODER_H_
|
||||||
|
|
||||||
|
#include "communication/message/include/message.h"
|
||||||
|
#include "communication/message/include/canmessage.h"
|
||||||
|
#include "communication/include/network.h"
|
||||||
|
#include <queue>
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace icsneo {
|
||||||
|
|
||||||
|
class MessageDecoder {
|
||||||
|
public:
|
||||||
|
bool input(const std::vector<uint8_t>& bytes);
|
||||||
|
std::vector<std::shared_ptr<Message>> output();
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum class ReadState {
|
||||||
|
SearchForHeader,
|
||||||
|
ParseHeader,
|
||||||
|
ParseLongStylePacketHeader,
|
||||||
|
GetData
|
||||||
|
};
|
||||||
|
ReadState state = ReadState::SearchForHeader;
|
||||||
|
|
||||||
|
int currentIndex = 0;
|
||||||
|
int messageLength = 0;
|
||||||
|
int headerSize = 0;
|
||||||
|
bool checksum = false;
|
||||||
|
bool gotGoodMessages = false; // Tracks whether we've ever gotten a good message
|
||||||
|
Message message;
|
||||||
|
std::deque<uint8_t> bytes;
|
||||||
|
|
||||||
|
void processMessage(const Message& message);
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<Message>> processedMessages;
|
||||||
|
|
||||||
|
typedef uint16_t icscm_bitfield;
|
||||||
|
struct CoreMiniMsg {
|
||||||
|
CANMessage toCANMessage(Network netid);
|
||||||
|
union {
|
||||||
|
uint16_t CxTRB0SID16;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
icscm_bitfield IDE : 1;
|
||||||
|
icscm_bitfield SRR : 1;
|
||||||
|
icscm_bitfield SID : 11;
|
||||||
|
icscm_bitfield NETWORKINDEX : 3;//DO NOT CLOBBER THIS
|
||||||
|
} CxTRB0SID;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
icscm_bitfield : 13;
|
||||||
|
icscm_bitfield EDL : 1;
|
||||||
|
icscm_bitfield BRS : 1;
|
||||||
|
icscm_bitfield ESI : 1;
|
||||||
|
} CxTRB0FD;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
icscm_bitfield ErrRxOnlyBreak : 1;
|
||||||
|
icscm_bitfield ErrRxOnlyBreakSync : 1;
|
||||||
|
icscm_bitfield ID : 11;
|
||||||
|
icscm_bitfield NETWORKINDEX : 3;//DO NOT CLOBBER THIS
|
||||||
|
} CxLIN3;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
uint8_t D8;
|
||||||
|
uint8_t options : 4;
|
||||||
|
uint8_t TXMSG : 1;
|
||||||
|
uint8_t NETWORKINDEX : 3;//DO NOT CLOBBER THIS
|
||||||
|
} C1xJ1850;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
uint8_t D8;
|
||||||
|
uint8_t options : 4;
|
||||||
|
uint8_t TXMSG : 1;
|
||||||
|
uint8_t NETWORKINDEX : 3;//DO NOT CLOBBER THIS
|
||||||
|
} C1xISO;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
uint8_t D8;
|
||||||
|
uint8_t options : 4;
|
||||||
|
uint8_t TXMSG : 1;
|
||||||
|
uint8_t NETWORKINDEX : 3;//DO NOT CLOBBER THIS
|
||||||
|
} C1xJ1708;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
icscm_bitfield FCS_AVAIL : 1;
|
||||||
|
icscm_bitfield RUNT_FRAME : 1;
|
||||||
|
icscm_bitfield DISABLE_PADDING : 1;
|
||||||
|
icscm_bitfield PREEMPTION_ENABLED : 1;
|
||||||
|
icscm_bitfield MPACKET_TYPE : 4;
|
||||||
|
icscm_bitfield MPACKET_FRAG_CNT : 2;
|
||||||
|
icscm_bitfield : 6;
|
||||||
|
} C1xETH;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
uint16_t ID : 11;
|
||||||
|
uint16_t STARTUP : 1;
|
||||||
|
uint16_t SYNC : 1;
|
||||||
|
uint16_t NULL_FRAME : 1;
|
||||||
|
uint16_t PAYLOAD_PREAMBLE : 1;
|
||||||
|
uint16_t RESERVED_0 : 1;
|
||||||
|
} C1xFlex;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
uint8_t daqType;
|
||||||
|
uint8_t ethDaqRes1;
|
||||||
|
} C1xETHDAQ;
|
||||||
|
};
|
||||||
|
union {
|
||||||
|
uint16_t CxTRB0EID16;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
icscm_bitfield EID : 12;
|
||||||
|
icscm_bitfield TXMSG : 1;
|
||||||
|
icscm_bitfield TXAborted : 1;
|
||||||
|
icscm_bitfield TXLostArb : 1;
|
||||||
|
icscm_bitfield TXError : 1;
|
||||||
|
} CxTRB0EID;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
uint8_t LINByte9;
|
||||||
|
uint8_t ErrTxRxMismatch : 1;
|
||||||
|
uint8_t TxChkSumEnhanced : 1;
|
||||||
|
uint8_t TXMaster : 1;
|
||||||
|
uint8_t TXSlave : 1;
|
||||||
|
uint8_t ErrRxBreakNot0 : 1;
|
||||||
|
uint8_t ErrRxBreakTooShort : 1;
|
||||||
|
uint8_t ErrRxSyncNot55 : 1;
|
||||||
|
uint8_t ErrRxDataGreater8 : 1;
|
||||||
|
} CxLIN;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
uint8_t D9;
|
||||||
|
uint8_t D10;
|
||||||
|
} C2xJ1850;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
uint8_t D9;
|
||||||
|
uint8_t D10;
|
||||||
|
} C2xISO;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
uint8_t D9;
|
||||||
|
uint8_t D10;
|
||||||
|
} C2xJ1708;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
uint16_t txlen : 12;
|
||||||
|
uint16_t TXMSG : 1;
|
||||||
|
uint16_t : 3;
|
||||||
|
} C2xETH;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
uint16_t HDR_CRC_10 : 1;
|
||||||
|
uint16_t PAYLOAD_LEN : 7;
|
||||||
|
uint16_t RESERVED_1 : 4;
|
||||||
|
uint16_t TXMSG : 1;
|
||||||
|
uint16_t RESERVED_2 : 3;
|
||||||
|
} C2xFlex;
|
||||||
|
};
|
||||||
|
union {
|
||||||
|
// For use by CAN
|
||||||
|
uint16_t CxTRB0DLC16;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
icscm_bitfield DLC : 4;
|
||||||
|
icscm_bitfield RB0 : 1;
|
||||||
|
icscm_bitfield IVRIF : 1;
|
||||||
|
icscm_bitfield HVEnable : 1;// must be cleared before passing into CAN driver
|
||||||
|
icscm_bitfield ExtendedNetworkIndexBit : 1;//DO NOT CLOBBER THIS
|
||||||
|
icscm_bitfield RB1 : 1;
|
||||||
|
icscm_bitfield RTR : 1;
|
||||||
|
icscm_bitfield EID : 6;
|
||||||
|
} CxTRB0DLC;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
icscm_bitfield len : 4;
|
||||||
|
icscm_bitfield ExtendedNetworkIndexBit2 : 1;//DO NOT CLOBBER THIS
|
||||||
|
icscm_bitfield UpdateSlaveOnce : 1;
|
||||||
|
icscm_bitfield HasUpdatedSlaveOnce : 1;
|
||||||
|
icscm_bitfield ExtendedNetworkIndexBit : 1;//DO NOT CLOBBER THIS
|
||||||
|
icscm_bitfield BusRecovered : 1;
|
||||||
|
icscm_bitfield SyncFerr : 1;//!< We got framing error in our sync byte.
|
||||||
|
icscm_bitfield MidFerr : 1;//!< We got framing error in our message id.
|
||||||
|
icscm_bitfield SlaveByteFerr : 1;//!< We got framing error in one of our slave bytes.
|
||||||
|
icscm_bitfield TxAborted : 1;//!< This transmit was aborted.
|
||||||
|
icscm_bitfield breakOnly : 1;
|
||||||
|
icscm_bitfield : 2;
|
||||||
|
} CxLIN2;
|
||||||
|
// For use by JVPW
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
icscm_bitfield len : 4;
|
||||||
|
icscm_bitfield ExtendedNetworkIndexBit2 : 1;//DO NOT CLOBBER THIS
|
||||||
|
icscm_bitfield just_tx_timestamp : 1;
|
||||||
|
icscm_bitfield first_seg : 1;
|
||||||
|
icscm_bitfield ExtendedNetworkIndexBit : 1;// do not clobber ExtendedNetworkIndexBit
|
||||||
|
icscm_bitfield D11 : 8;
|
||||||
|
} C3xJ1850;
|
||||||
|
// For use by the ISO/KEYWORD
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
icscm_bitfield len : 4;
|
||||||
|
icscm_bitfield ExtendedNetworkIndexBit2 : 1;//DO NOT CLOBBER THIS
|
||||||
|
icscm_bitfield FRM : 1;
|
||||||
|
icscm_bitfield INIT : 1;
|
||||||
|
icscm_bitfield ExtendedNetworkIndexBit : 1;// do not clobber ExtendedNetworkIndexBit
|
||||||
|
icscm_bitfield D11 : 8;
|
||||||
|
} C3xISO;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
icscm_bitfield len : 4;
|
||||||
|
icscm_bitfield ExtendedNetworkIndexBit2 : 1;//DO NOT CLOBBER THIS
|
||||||
|
icscm_bitfield FRM : 1;
|
||||||
|
icscm_bitfield : 1;
|
||||||
|
icscm_bitfield ExtendedNetworkIndexBit : 1;// do not clobber ExtendedNetworkIndexBit
|
||||||
|
icscm_bitfield pri : 8;
|
||||||
|
} C3xJ1708;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
uint16_t rsvd;
|
||||||
|
} C3xETH;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
uint16_t CYCLE : 6;
|
||||||
|
uint16_t HDR_CRC_9_0 : 10;
|
||||||
|
} C3xFlex;
|
||||||
|
};
|
||||||
|
unsigned char CxTRB0Dall[8];
|
||||||
|
union {
|
||||||
|
uint16_t CxTRB0STAT;
|
||||||
|
uint16_t J1850_TX_ID;
|
||||||
|
};
|
||||||
|
union {
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
uint32_t uiTimeStamp10uS;
|
||||||
|
union {
|
||||||
|
uint32_t uiTimeStamp10uSMSB;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
unsigned : 28;
|
||||||
|
unsigned res_0s : 3;// must be 0!!!
|
||||||
|
unsigned bIsExtended : 1;// always 1 for CoreMiniMsgExtended.
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
int64_t uiTimeStampLarge;
|
||||||
|
uint8_t uiTimeStampBytes[8];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
#ifndef __MESSAGEFILTER_H_
|
||||||
|
#define __MESSAGEFILTER_H_
|
||||||
|
|
||||||
|
#include "communication/include/network.h"
|
||||||
|
#include "communication/message/include/message.h"
|
||||||
|
#include "communication/message/include/canmessage.h"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace icsneo {
|
||||||
|
|
||||||
|
class MessageFilter {
|
||||||
|
public:
|
||||||
|
MessageFilter() : matchAny(true) {}
|
||||||
|
MessageFilter(Network::Type type) : type(type) {}
|
||||||
|
MessageFilter(Network::NetID netid) : netid(netid) {}
|
||||||
|
virtual ~MessageFilter() {}
|
||||||
|
|
||||||
|
virtual bool match(const std::shared_ptr<Message>& message) const {
|
||||||
|
if(matchAny)
|
||||||
|
return true;
|
||||||
|
if(!matchType(message->network.getType()))
|
||||||
|
return false;
|
||||||
|
if(!matchNetID(message->network.getNetID()))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool matchAny = false;
|
||||||
|
|
||||||
|
Network::Type type = Network::Type::Invalid; // Matching a type of invalid will match any
|
||||||
|
bool matchType(Network::Type mtype) const {
|
||||||
|
if(type == Network::Type::Invalid)
|
||||||
|
return true;
|
||||||
|
return type == mtype;
|
||||||
|
}
|
||||||
|
|
||||||
|
Network::NetID netid = Network::NetID::Invalid; // Matching a netid of invalid will match any
|
||||||
|
bool matchNetID(Network::NetID mnetid) const {
|
||||||
|
if(netid == Network::NetID::Invalid)
|
||||||
|
return true;
|
||||||
|
return netid == mnetid;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class CANMessageFilter : public MessageFilter {
|
||||||
|
public:
|
||||||
|
CANMessageFilter() : MessageFilter(Network::Type::CAN), arbid(INVALID_ARBID) {}
|
||||||
|
CANMessageFilter(uint32_t arbid) : MessageFilter(Network::Type::CAN), arbid(arbid) {}
|
||||||
|
|
||||||
|
bool match(const std::shared_ptr<Message>& message) const {
|
||||||
|
if(!MessageFilter::match(message))
|
||||||
|
return false;
|
||||||
|
const auto canMessage = std::dynamic_pointer_cast<CANMessage>(message);
|
||||||
|
if(canMessage == nullptr || !matchArbID(canMessage->arbid))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr uint32_t INVALID_ARBID = 0xffffffff;
|
||||||
|
uint32_t arbid;
|
||||||
|
bool matchArbID(uint32_t marbid) const {
|
||||||
|
if(arbid == INVALID_ARBID)
|
||||||
|
return true;
|
||||||
|
return arbid == marbid;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
#ifndef __MULTICHANNELCOMMUNICATION_H_
|
||||||
|
#define __MULTICHANNELCOMMUNICATION_H_
|
||||||
|
|
||||||
|
#include "communication/include/communication.h"
|
||||||
|
#include "communication/include/icommunication.h"
|
||||||
|
|
||||||
|
namespace icsneo {
|
||||||
|
|
||||||
|
class MultiChannelCommunication : public Communication {
|
||||||
|
public:
|
||||||
|
MultiChannelCommunication(std::shared_ptr<ICommunication> com) : Communication(com) {}
|
||||||
|
void spawnThreads();
|
||||||
|
void joinThreads();
|
||||||
|
bool sendCommand(Communication::Command cmd, std::vector<uint8_t> arguments);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool preprocessPacket(std::deque<uint8_t>& usbReadFifo);
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum class CommandType : uint8_t {
|
||||||
|
PlasmaReadRequest = 0x10, // Status read request to HSC
|
||||||
|
PlasmaStatusResponse = 0x11, // Status response by HSC
|
||||||
|
HostPC_to_Vnet1 = 0x20, // Host PC data to Vnet module-1
|
||||||
|
Vnet1_to_HostPC = 0x21, // Vnet module-1 data to host PC
|
||||||
|
HostPC_to_Vnet2 = 0x30, // Host PC data to Vnet module-2
|
||||||
|
Vnet2_to_HostPC = 0x31, // Vnet module-2 data to host PC
|
||||||
|
HostPC_to_Vnet3 = 0x40, // Host PC data to Vnet module-3
|
||||||
|
Vnet3_to_HostPC = 0x41, // Vnet module-3 data to host PC
|
||||||
|
HostPC_to_SDCC1 = 0x50, // Host PC data to write to SDCC-1
|
||||||
|
HostPC_from_SDCC1 = 0x51, // Host PC wants data read from SDCC-1
|
||||||
|
SDCC1_to_HostPC = 0x52, // SDCC-1 data to host PC
|
||||||
|
HostPC_to_SDCC2 = 0x60, // Host PC data to write to SDCC-2
|
||||||
|
HostPC_from_SDCC2 = 0x61, // Host PC wants data read from SDCC-2
|
||||||
|
SDCC2_to_HostPC = 0x62, // SDCC-2 data to host PC
|
||||||
|
PC_to_LSOC = 0x70, // Host PC data to LSOCC
|
||||||
|
LSOCC_to_PC = 0x71, // LSOCC data to host PC
|
||||||
|
HostPC_to_Microblaze = 0x80, // Host PC data to microblaze processor
|
||||||
|
Microblaze_to_HostPC = 0x81 // Microblaze processor data to host PC
|
||||||
|
};
|
||||||
|
static bool CommandTypeIsValid(CommandType cmd) {
|
||||||
|
switch(cmd) {
|
||||||
|
case CommandType::PlasmaReadRequest:
|
||||||
|
case CommandType::PlasmaStatusResponse:
|
||||||
|
case CommandType::HostPC_to_Vnet1:
|
||||||
|
case CommandType::Vnet1_to_HostPC:
|
||||||
|
case CommandType::HostPC_to_Vnet2:
|
||||||
|
case CommandType::Vnet2_to_HostPC:
|
||||||
|
case CommandType::HostPC_to_Vnet3:
|
||||||
|
case CommandType::Vnet3_to_HostPC:
|
||||||
|
case CommandType::HostPC_to_SDCC1:
|
||||||
|
case CommandType::HostPC_from_SDCC1:
|
||||||
|
case CommandType::SDCC1_to_HostPC:
|
||||||
|
case CommandType::HostPC_to_SDCC2:
|
||||||
|
case CommandType::HostPC_from_SDCC2:
|
||||||
|
case CommandType::SDCC2_to_HostPC:
|
||||||
|
case CommandType::PC_to_LSOC:
|
||||||
|
case CommandType::LSOCC_to_PC:
|
||||||
|
case CommandType::HostPC_to_Microblaze:
|
||||||
|
case CommandType::Microblaze_to_HostPC:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static bool CommandTypeHasAddress(CommandType cmd) {
|
||||||
|
// Check CommandTypeIsValid before this, you will get false on an invalid command
|
||||||
|
switch(cmd) {
|
||||||
|
case CommandType::SDCC1_to_HostPC:
|
||||||
|
case CommandType::SDCC2_to_HostPC:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static uint16_t CommandTypeDefinesLength(CommandType cmd) {
|
||||||
|
// Check CommandTypeIsValid before this, you will get 0 on an invalid command
|
||||||
|
switch(cmd) {
|
||||||
|
case CommandType::PlasmaStatusResponse:
|
||||||
|
return 2;
|
||||||
|
default:
|
||||||
|
return 0; // Length is defined by following bytes in message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class PreprocessState {
|
||||||
|
SearchForCommand,
|
||||||
|
ParseAddress,
|
||||||
|
ParseLength,
|
||||||
|
GetData
|
||||||
|
};
|
||||||
|
PreprocessState state = PreprocessState::SearchForCommand;
|
||||||
|
uint16_t currentCommandLength;
|
||||||
|
CommandType currentCommandType;
|
||||||
|
size_t currentReadIndex = 0;
|
||||||
|
|
||||||
|
std::thread mainChannelReadThread;
|
||||||
|
void readTask();
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,340 @@
|
||||||
|
#ifndef __NETWORKID_H_
|
||||||
|
#define __NETWORKID_H_
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <ostream>
|
||||||
|
|
||||||
|
namespace icsneo {
|
||||||
|
|
||||||
|
class Network {
|
||||||
|
public:
|
||||||
|
enum class NetID : uint16_t {
|
||||||
|
Device = 0,
|
||||||
|
HSCAN = 1,
|
||||||
|
MSCAN = 2,
|
||||||
|
SWCAN = 3,
|
||||||
|
LSFTCAN = 4,
|
||||||
|
FordSCP = 5,
|
||||||
|
J1708 = 6,
|
||||||
|
Aux = 7,
|
||||||
|
J1850VPW = 8,
|
||||||
|
ISO = 9,
|
||||||
|
ISOPIC = 10,
|
||||||
|
Main51 = 11,
|
||||||
|
RED = 12,
|
||||||
|
SCI = 13,
|
||||||
|
ISO2 = 14,
|
||||||
|
ISO14230 = 15,
|
||||||
|
LIN = 16,
|
||||||
|
OP_Ethernet1 = 17,
|
||||||
|
OP_Ethernet2 = 18,
|
||||||
|
OP_Ethernet3 = 19,
|
||||||
|
ISO3 = 41,
|
||||||
|
HSCAN2 = 42,
|
||||||
|
HSCAN3 = 44,
|
||||||
|
OP_Ethernet4 = 45,
|
||||||
|
OP_Ethernet5 = 46,
|
||||||
|
ISO4 = 47,
|
||||||
|
LIN2 = 48,
|
||||||
|
LIN3 = 49,
|
||||||
|
LIN4 = 50,
|
||||||
|
MOST = 51,
|
||||||
|
Red_App_Error = 52,
|
||||||
|
CGI = 53,
|
||||||
|
Reset_Status = 54,
|
||||||
|
FB_Status = 55,
|
||||||
|
App_Signal_Status = 56,
|
||||||
|
Read_Datalink_Cm_Tx_Msg = 57,
|
||||||
|
Read_Datalink_Cm_Rx_Msg = 58,
|
||||||
|
Logging_Overflow = 59,
|
||||||
|
Read_Settings_Ex = 60,
|
||||||
|
HSCAN4 = 61,
|
||||||
|
HSCAN5 = 62,
|
||||||
|
RS232 = 63,
|
||||||
|
UART = 64,
|
||||||
|
UART2 = 65,
|
||||||
|
UART3 = 66,
|
||||||
|
UART4 = 67,
|
||||||
|
SWCAN2 = 68,
|
||||||
|
Ethernet_DAQ = 69,
|
||||||
|
Data_To_Host = 70,
|
||||||
|
TextAPI_To_Host = 71,
|
||||||
|
OP_Ethernet6 = 73,
|
||||||
|
Red_VBat = 74,
|
||||||
|
OP_Ethernet7 = 75,
|
||||||
|
OP_Ethernet8 = 76,
|
||||||
|
OP_Ethernet9 = 77,
|
||||||
|
OP_Ethernet10 = 78,
|
||||||
|
OP_Ethernet11 = 79,
|
||||||
|
FlexRay1a = 80,
|
||||||
|
FlexRay1b = 81,
|
||||||
|
FlexRay2a = 82,
|
||||||
|
FlexRay2b = 83,
|
||||||
|
LIN5 = 84,
|
||||||
|
FlexRay = 85,
|
||||||
|
FlexRay2 = 86,
|
||||||
|
OP_Ethernet12 = 87,
|
||||||
|
MOST25 = 90,
|
||||||
|
MOST50 = 91,
|
||||||
|
MOST150 = 92,
|
||||||
|
Ethernet = 93,
|
||||||
|
GMFSA = 94,
|
||||||
|
TCP = 95,
|
||||||
|
HSCAN6 = 96,
|
||||||
|
HSCAN7 = 97,
|
||||||
|
LIN6 = 98,
|
||||||
|
LSFTCAN2 = 99,
|
||||||
|
HW_COM_Latency_Test = 512,
|
||||||
|
Device_Status = 513,
|
||||||
|
Invalid = 0xffff
|
||||||
|
};
|
||||||
|
enum class Type {
|
||||||
|
CAN,
|
||||||
|
LIN,
|
||||||
|
FlexRay,
|
||||||
|
MOST,
|
||||||
|
Ethernet,
|
||||||
|
Other,
|
||||||
|
Invalid
|
||||||
|
};
|
||||||
|
static constexpr const char* GetTypeString(Type type) {
|
||||||
|
switch(type) {
|
||||||
|
case Type::CAN:
|
||||||
|
return "CAN";
|
||||||
|
case Type::LIN:
|
||||||
|
return "LIN";
|
||||||
|
case Type::FlexRay:
|
||||||
|
return "FlexRay";
|
||||||
|
case Type::MOST:
|
||||||
|
return "MOST";
|
||||||
|
case Type::Other:
|
||||||
|
return "Other";
|
||||||
|
case Type::Invalid:
|
||||||
|
default:
|
||||||
|
return "Invalid Type";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static constexpr Type GetTypeOfNetID(NetID netid) {
|
||||||
|
switch(netid) {
|
||||||
|
case NetID::HSCAN:
|
||||||
|
case NetID::MSCAN:
|
||||||
|
case NetID::SWCAN:
|
||||||
|
case NetID::LSFTCAN:
|
||||||
|
case NetID::HSCAN2:
|
||||||
|
case NetID::HSCAN3:
|
||||||
|
case NetID::HSCAN4:
|
||||||
|
case NetID::HSCAN5:
|
||||||
|
case NetID::SWCAN2:
|
||||||
|
case NetID::HSCAN6:
|
||||||
|
case NetID::HSCAN7:
|
||||||
|
case NetID::LSFTCAN2:
|
||||||
|
return Type::CAN;
|
||||||
|
case NetID::LIN:
|
||||||
|
case NetID::LIN2:
|
||||||
|
case NetID::LIN3:
|
||||||
|
case NetID::LIN4:
|
||||||
|
case NetID::LIN5:
|
||||||
|
case NetID::LIN6:
|
||||||
|
return Type::LIN;
|
||||||
|
case NetID::FlexRay:
|
||||||
|
case NetID::FlexRay1a:
|
||||||
|
case NetID::FlexRay1b:
|
||||||
|
case NetID::FlexRay2:
|
||||||
|
case NetID::FlexRay2a:
|
||||||
|
case NetID::FlexRay2b:
|
||||||
|
return Type::FlexRay;
|
||||||
|
case NetID::MOST:
|
||||||
|
case NetID::MOST25:
|
||||||
|
case NetID::MOST50:
|
||||||
|
case NetID::MOST150:
|
||||||
|
return Type::MOST;
|
||||||
|
case NetID::Invalid:
|
||||||
|
return Type::Invalid;
|
||||||
|
default:
|
||||||
|
return Type::Other;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static constexpr const char* GetNetIDString(NetID netid) {
|
||||||
|
switch(netid) {
|
||||||
|
case NetID::Device:
|
||||||
|
return "Device";
|
||||||
|
case NetID::HSCAN:
|
||||||
|
return "HSCAN";
|
||||||
|
case NetID::MSCAN:
|
||||||
|
return "MSCAN";
|
||||||
|
case NetID::SWCAN:
|
||||||
|
return "SWCAN";
|
||||||
|
case NetID::LSFTCAN:
|
||||||
|
return "LSFTCAN";
|
||||||
|
case NetID::FordSCP:
|
||||||
|
return "FordSCP";
|
||||||
|
case NetID::J1708:
|
||||||
|
return "J1708";
|
||||||
|
case NetID::Aux:
|
||||||
|
return "Aux";
|
||||||
|
case NetID::J1850VPW:
|
||||||
|
return "J1850 VPW";
|
||||||
|
case NetID::ISO:
|
||||||
|
return "ISO";
|
||||||
|
case NetID::ISOPIC:
|
||||||
|
return "ISOPIC";
|
||||||
|
case NetID::Main51:
|
||||||
|
return "Main51";
|
||||||
|
case NetID::RED:
|
||||||
|
return "RED";
|
||||||
|
case NetID::SCI:
|
||||||
|
return "SCI";
|
||||||
|
case NetID::ISO2:
|
||||||
|
return "ISO 2";
|
||||||
|
case NetID::ISO14230:
|
||||||
|
return "ISO 14230";
|
||||||
|
case NetID::LIN:
|
||||||
|
return "LIN";
|
||||||
|
case NetID::OP_Ethernet1:
|
||||||
|
return "Ethernet 1";
|
||||||
|
case NetID::OP_Ethernet2:
|
||||||
|
return "Ethernet 2";
|
||||||
|
case NetID::OP_Ethernet3:
|
||||||
|
return "Ethernet 3";
|
||||||
|
case NetID::ISO3:
|
||||||
|
return "ISO 3";
|
||||||
|
case NetID::HSCAN2:
|
||||||
|
return "HSCAN 2";
|
||||||
|
case NetID::HSCAN3:
|
||||||
|
return "HSCAN 3";
|
||||||
|
case NetID::OP_Ethernet4:
|
||||||
|
return "Ethernet 4";
|
||||||
|
case NetID::OP_Ethernet5:
|
||||||
|
return "Ethernet 5";
|
||||||
|
case NetID::ISO4:
|
||||||
|
return "ISO 4";
|
||||||
|
case NetID::LIN2:
|
||||||
|
return "LIN 2";
|
||||||
|
case NetID::LIN3:
|
||||||
|
return "LIN 3";
|
||||||
|
case NetID::LIN4:
|
||||||
|
return "LIN 4";
|
||||||
|
case NetID::MOST:
|
||||||
|
return "MOST";
|
||||||
|
case NetID::Red_App_Error:
|
||||||
|
return "Red App Error";
|
||||||
|
case NetID::CGI:
|
||||||
|
return "CGI";
|
||||||
|
case NetID::Reset_Status:
|
||||||
|
return "Reset Status";
|
||||||
|
case NetID::FB_Status:
|
||||||
|
return "FB Status";
|
||||||
|
case NetID::App_Signal_Status:
|
||||||
|
return "App Signal Status";
|
||||||
|
case NetID::Read_Datalink_Cm_Tx_Msg:
|
||||||
|
return "Read Datalink Cm Tx Msg";
|
||||||
|
case NetID::Read_Datalink_Cm_Rx_Msg:
|
||||||
|
return "Read Datalink Cm Rx Msg";
|
||||||
|
case NetID::Logging_Overflow:
|
||||||
|
return "Logging Overflow";
|
||||||
|
case NetID::Read_Settings_Ex:
|
||||||
|
return "Read Settings Ex";
|
||||||
|
case NetID::HSCAN4:
|
||||||
|
return "HSCAN 4";
|
||||||
|
case NetID::HSCAN5:
|
||||||
|
return "HSCAN 5";
|
||||||
|
case NetID::RS232:
|
||||||
|
return "RS232";
|
||||||
|
case NetID::UART:
|
||||||
|
return "UART";
|
||||||
|
case NetID::UART2:
|
||||||
|
return "UART 2";
|
||||||
|
case NetID::UART3:
|
||||||
|
return "UART 3";
|
||||||
|
case NetID::UART4:
|
||||||
|
return "UART 4";
|
||||||
|
case NetID::SWCAN2:
|
||||||
|
return "SWCAN 2";
|
||||||
|
case NetID::Ethernet_DAQ:
|
||||||
|
return "Ethernet DAQ";
|
||||||
|
case NetID::Data_To_Host:
|
||||||
|
return "Data To Host";
|
||||||
|
case NetID::TextAPI_To_Host:
|
||||||
|
return "TextAPI To Host";
|
||||||
|
case NetID::OP_Ethernet6:
|
||||||
|
return "Ethernet 6";
|
||||||
|
case NetID::Red_VBat:
|
||||||
|
return "Red VBat";
|
||||||
|
case NetID::OP_Ethernet7:
|
||||||
|
return "Ethernet 7";
|
||||||
|
case NetID::OP_Ethernet8:
|
||||||
|
return "Ethernet 8";
|
||||||
|
case NetID::OP_Ethernet9:
|
||||||
|
return "Ethernet 9";
|
||||||
|
case NetID::OP_Ethernet10:
|
||||||
|
return "Ethernet 10";
|
||||||
|
case NetID::OP_Ethernet11:
|
||||||
|
return "Ethernet 11";
|
||||||
|
case NetID::FlexRay1a:
|
||||||
|
return "FlexRay 1a";
|
||||||
|
case NetID::FlexRay1b:
|
||||||
|
return "FlexRay 1b";
|
||||||
|
case NetID::FlexRay2a:
|
||||||
|
return "FlexRay 2a";
|
||||||
|
case NetID::FlexRay2b:
|
||||||
|
return "FlexRay 2b";
|
||||||
|
case NetID::LIN5:
|
||||||
|
return "LIN 5";
|
||||||
|
case NetID::FlexRay:
|
||||||
|
return "FlexRay";
|
||||||
|
case NetID::FlexRay2:
|
||||||
|
return "FlexRay 2";
|
||||||
|
case NetID::OP_Ethernet12:
|
||||||
|
return "Ethernet 12";
|
||||||
|
case NetID::MOST25:
|
||||||
|
return "MOST25";
|
||||||
|
case NetID::MOST50:
|
||||||
|
return "MOST50";
|
||||||
|
case NetID::MOST150:
|
||||||
|
return "MOST150";
|
||||||
|
case NetID::Ethernet:
|
||||||
|
return "Ethernet";
|
||||||
|
case NetID::GMFSA:
|
||||||
|
return "GMFSA";
|
||||||
|
case NetID::TCP:
|
||||||
|
return "TCP";
|
||||||
|
case NetID::HSCAN6:
|
||||||
|
return "HSCAN 6";
|
||||||
|
case NetID::HSCAN7:
|
||||||
|
return "HSCAN 7";
|
||||||
|
case NetID::LIN6:
|
||||||
|
return "LIN 6";
|
||||||
|
case NetID::LSFTCAN2:
|
||||||
|
return "LSFTCAN 2";
|
||||||
|
case NetID::HW_COM_Latency_Test:
|
||||||
|
return "HW COM Latency Test";
|
||||||
|
case NetID::Device_Status:
|
||||||
|
return "Device Status";
|
||||||
|
case NetID::Invalid:
|
||||||
|
default:
|
||||||
|
return "Invalid Network";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Network() { setValue(NetID::Invalid); }
|
||||||
|
Network(uint16_t netid) { setValue((NetID)netid); }
|
||||||
|
Network(NetID netid) { setValue(netid); }
|
||||||
|
NetID getNetID() const { return value; }
|
||||||
|
Type getType() const { return type; }
|
||||||
|
friend std::ostream& operator<<(std::ostream& os, const Network& network) {
|
||||||
|
os << GetNetIDString(network.getNetID());
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
NetID value; // Always use setValue so that value and type stay in sync
|
||||||
|
Type type;
|
||||||
|
void setValue(NetID id) {
|
||||||
|
value = id;
|
||||||
|
type = GetTypeOfNetID(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
#ifndef __CANMESSAGE_H_
|
||||||
|
#define __CANMESSAGE_H_
|
||||||
|
|
||||||
|
#include "communication/message/include/message.h"
|
||||||
|
|
||||||
|
namespace icsneo {
|
||||||
|
|
||||||
|
class CANMessage : public Message {
|
||||||
|
public:
|
||||||
|
uint32_t arbid;
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
#ifndef __MESSAGE_H_
|
||||||
|
#define __MESSAGE_H_
|
||||||
|
|
||||||
|
#include "communication/include/network.h"
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace icsneo {
|
||||||
|
|
||||||
|
class Message {
|
||||||
|
public:
|
||||||
|
virtual ~Message() {}
|
||||||
|
Network network;
|
||||||
|
std::vector<uint8_t> data;
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,139 @@
|
||||||
|
#include "communication/include/messagedecoder.h"
|
||||||
|
#include "communication/include/communication.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using namespace icsneo;
|
||||||
|
|
||||||
|
CANMessage MessageDecoder::CoreMiniMsg::toCANMessage(Network network) {
|
||||||
|
CANMessage msg;
|
||||||
|
msg.network = network;
|
||||||
|
msg.arbid = CxTRB0SID.SID;
|
||||||
|
msg.data.reserve(CxTRB0DLC.DLC);
|
||||||
|
for(auto i = 0; i < CxTRB0DLC.DLC; i++)
|
||||||
|
msg.data.push_back(CxTRB0Dall[i]);
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MessageDecoder::input(const std::vector<uint8_t>& inputBytes) {
|
||||||
|
bool haveEnoughData = true;
|
||||||
|
bytes.insert(bytes.end(), inputBytes.begin(), inputBytes.end());
|
||||||
|
|
||||||
|
while(haveEnoughData) {
|
||||||
|
switch(state) {
|
||||||
|
case ReadState::SearchForHeader:
|
||||||
|
if(bytes.size() < 1) {
|
||||||
|
haveEnoughData = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(bytes[0] == 0xAA) { // 0xAA denotes the beginning of a packet
|
||||||
|
state = ReadState::ParseHeader;
|
||||||
|
currentIndex = 1;
|
||||||
|
} else {
|
||||||
|
//std::cout << (int)bytes[0] << " ";
|
||||||
|
bytes.pop_front(); // Discard
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ReadState::ParseHeader:
|
||||||
|
if(bytes.size() < 2) {
|
||||||
|
haveEnoughData = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
messageLength = bytes[1] >> 4 & 0xf; // Upper nibble of the second byte denotes the message length
|
||||||
|
message.network = Network(bytes[1] & 0xf); // Lower nibble of the second byte is the network ID
|
||||||
|
if(messageLength == 0) { // A length of zero denotes a long style packet
|
||||||
|
state = ReadState::ParseLongStylePacketHeader;
|
||||||
|
checksum = false;
|
||||||
|
headerSize = 6;
|
||||||
|
} else {
|
||||||
|
state = ReadState::GetData;
|
||||||
|
checksum = true;
|
||||||
|
headerSize = 2;
|
||||||
|
messageLength += 2; // The message length given in short messages does not include header
|
||||||
|
}
|
||||||
|
currentIndex++;
|
||||||
|
break;
|
||||||
|
case ReadState::ParseLongStylePacketHeader:
|
||||||
|
if(bytes.size() < 6) {
|
||||||
|
haveEnoughData = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
messageLength = bytes[2]; // Long messages have a little endian length on bytes 3 and 4
|
||||||
|
messageLength |= bytes[3] << 8;
|
||||||
|
message.network = Network((bytes[5] << 8) | bytes[4]); // Long messages have their netid stored as little endian on bytes 5 and 6
|
||||||
|
currentIndex += 4;
|
||||||
|
|
||||||
|
/* Long messages can't have a length less than 4, because that would indicate a negative payload size.
|
||||||
|
* Unlike the short message length, the long message length encompasses everything from the 0xAA to the
|
||||||
|
* end of the payload. The short message length, for reference, only encompasses the length of the actual
|
||||||
|
* payload, and not the header or checksum.
|
||||||
|
*/
|
||||||
|
if(messageLength < 4 || messageLength > 4000) {
|
||||||
|
bytes.pop_front();
|
||||||
|
//std::cout << "skipping long message with length " << messageLength << std::endl;
|
||||||
|
state = ReadState::SearchForHeader;
|
||||||
|
} else {
|
||||||
|
state = ReadState::GetData;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ReadState::GetData:
|
||||||
|
// We do not include the checksum in messageLength so it doesn't get copied into the payload buffer
|
||||||
|
if(bytes.size() < messageLength + (checksum ? 1 : 0)) { // Read until we have the rest of the message
|
||||||
|
haveEnoughData = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
message.data.clear();
|
||||||
|
if(messageLength > 0)
|
||||||
|
message.data.reserve(messageLength - headerSize);
|
||||||
|
|
||||||
|
while(currentIndex < messageLength)
|
||||||
|
message.data.push_back(bytes[currentIndex++]);
|
||||||
|
|
||||||
|
if(!checksum || bytes[currentIndex] == Communication::ICSChecksum(message.data)) {
|
||||||
|
// Got a good packet
|
||||||
|
gotGoodMessages = true;
|
||||||
|
processMessage(message);
|
||||||
|
for (auto i = 0; i < messageLength; i++)
|
||||||
|
bytes.pop_front();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if(gotGoodMessages) // Don't complain unless we've already gotten a good message, in case we started in the middle of a stream
|
||||||
|
std::cout << "Dropping message due to bad checksum" << std::endl;
|
||||||
|
bytes.pop_front(); // Drop the first byte so it doesn't get picked up again
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset for the next packet
|
||||||
|
currentIndex = 0;
|
||||||
|
state = ReadState::SearchForHeader;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return processedMessages.size() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<Message>> MessageDecoder::output() {
|
||||||
|
auto ret = std::move(processedMessages);
|
||||||
|
processedMessages = std::vector<std::shared_ptr<Message>>(); // Reset the vector
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDecoder::processMessage(const Message& msg) {
|
||||||
|
switch(msg.network.getType()) {
|
||||||
|
case Network::Type::CAN:
|
||||||
|
if(msg.data.size() >= 24) {
|
||||||
|
CoreMiniMsg* cmsg = (CoreMiniMsg*)msg.data.data();
|
||||||
|
processedMessages.push_back(std::make_shared<CANMessage>(cmsg->toCANMessage(msg.network)));
|
||||||
|
} else {
|
||||||
|
//std::cout << "bad CAN frame " << msg.data.size() << std::endl;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// if(msg.network.getNetID() != Network::NetID::Device)
|
||||||
|
// std::cout << "Message: " << msg.network << " with data length " << msg.data.size() << std::endl;
|
||||||
|
processedMessages.push_back(std::make_shared<Message>(msg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,128 @@
|
||||||
|
#include "communication/include/multichannelcommunication.h"
|
||||||
|
#include "communication/include/messagedecoder.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
|
using namespace icsneo;
|
||||||
|
|
||||||
|
void MultiChannelCommunication::spawnThreads() {
|
||||||
|
mainChannelReadThread = std::thread(&MultiChannelCommunication::readTask, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiChannelCommunication::joinThreads() {
|
||||||
|
if(mainChannelReadThread.joinable())
|
||||||
|
mainChannelReadThread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MultiChannelCommunication::sendCommand(Communication::Command cmd, std::vector<uint8_t> arguments) {
|
||||||
|
std::vector<uint8_t> bytes;
|
||||||
|
bytes.push_back((uint8_t)cmd);
|
||||||
|
for(auto& b : arguments)
|
||||||
|
bytes.push_back(b);
|
||||||
|
bytes.insert(bytes.begin(), 0xB | ((uint8_t)bytes.size() << 4));
|
||||||
|
bytes = Communication::packetWrap(bytes);
|
||||||
|
bytes.insert(bytes.begin(), {(uint8_t)CommandType::HostPC_to_Vnet1, (uint8_t)bytes.size(), (uint8_t)(bytes.size() >> 8)});
|
||||||
|
return rawWrite(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiChannelCommunication::readTask() {
|
||||||
|
bool readMore = true;
|
||||||
|
std::deque<uint8_t> usbReadFifo;
|
||||||
|
std::vector<uint8_t> readBytes;
|
||||||
|
std::vector<uint8_t> payloadBytes;
|
||||||
|
MessageDecoder decoder;
|
||||||
|
|
||||||
|
while(!closing) {
|
||||||
|
if(readMore) {
|
||||||
|
readBytes.clear();
|
||||||
|
if(impl->readWait(readBytes)) {
|
||||||
|
readMore = false;
|
||||||
|
usbReadFifo.insert(usbReadFifo.end(), std::make_move_iterator(readBytes.begin()), std::make_move_iterator(readBytes.end()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch(state) {
|
||||||
|
case PreprocessState::SearchForCommand:
|
||||||
|
if(usbReadFifo.size() < 1) {
|
||||||
|
readMore = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentCommandType = (CommandType)usbReadFifo[0];
|
||||||
|
|
||||||
|
if(!CommandTypeIsValid(currentCommandType)) {
|
||||||
|
std::cout << "cnv" << std::hex << (int)currentCommandType << ' ' << std::dec;
|
||||||
|
usbReadFifo.pop_front();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentReadIndex = 1;
|
||||||
|
|
||||||
|
if(CommandTypeHasAddress(currentCommandType)) {
|
||||||
|
state = PreprocessState::ParseAddress;
|
||||||
|
continue; // No commands which define an address also define a length, so we can just continue from there
|
||||||
|
}
|
||||||
|
|
||||||
|
currentCommandLength = CommandTypeDefinesLength(currentCommandType);
|
||||||
|
if(currentCommandLength == 0) {
|
||||||
|
state = PreprocessState::ParseLength;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
state = PreprocessState::GetData;
|
||||||
|
continue;
|
||||||
|
case PreprocessState::ParseAddress:
|
||||||
|
// The address is represented by a 4 byte little endian
|
||||||
|
// Don't care about it yet
|
||||||
|
currentReadIndex += 4;
|
||||||
|
// Intentionally fall through
|
||||||
|
case PreprocessState::ParseLength:
|
||||||
|
state = PreprocessState::ParseLength; // Set state in case we've fallen through, but later need to go around again
|
||||||
|
|
||||||
|
if(usbReadFifo.size() < currentReadIndex + 2) { // Come back we have more data
|
||||||
|
readMore = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The length is represented by a 2 byte little endian
|
||||||
|
currentCommandLength = usbReadFifo[currentReadIndex++];
|
||||||
|
currentCommandLength |= usbReadFifo[currentReadIndex++] << 8;
|
||||||
|
// Intentionally fall through
|
||||||
|
case PreprocessState::GetData:
|
||||||
|
state = PreprocessState::GetData; // Set state in case we've fallen through, but later need to go around again
|
||||||
|
|
||||||
|
if(usbReadFifo.size() <= currentReadIndex + currentCommandLength) { // Come back we have more data
|
||||||
|
readMore = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//std::cout << std::dec << "Got a multichannel message! Size: " << currentCommandLength << std::hex << std::setfill('0') << std::setw(2) << " Cmd: 0x" << (int)currentCommandType << std::endl;
|
||||||
|
for(auto i = 0; i < currentReadIndex; i++)
|
||||||
|
usbReadFifo.pop_front();
|
||||||
|
|
||||||
|
payloadBytes.clear();
|
||||||
|
payloadBytes.reserve(currentCommandLength);
|
||||||
|
for(auto i = 0; i < currentCommandLength; i++) {
|
||||||
|
//std::cout << (int)usbReadFifo[0] << ' ';
|
||||||
|
payloadBytes.push_back(usbReadFifo[0]);
|
||||||
|
// if(i % 16 == 15)
|
||||||
|
// std::cout << std::endl;
|
||||||
|
usbReadFifo.pop_front();
|
||||||
|
}
|
||||||
|
//std::cout << std::dec << std::endl;
|
||||||
|
|
||||||
|
if(decoder.input(payloadBytes)) {
|
||||||
|
for(auto& msg : decoder.output()) {
|
||||||
|
for(auto& cb : messageCallbacks) {
|
||||||
|
if(!closing) { // We might have closed while reading or processing
|
||||||
|
cb.second.callIfMatch(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state = PreprocessState::SearchForCommand;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,156 @@
|
||||||
|
#include "include/device.h"
|
||||||
|
#include "communication/include/messagecallback.h"
|
||||||
|
#include <string.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
using namespace icsneo;
|
||||||
|
|
||||||
|
static const uint8_t fromBase36Table[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12,
|
||||||
|
13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 14, 15,
|
||||||
|
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35 };
|
||||||
|
|
||||||
|
static const char toBase36Table[36] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
|
||||||
|
'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' };
|
||||||
|
|
||||||
|
static const uint32_t toBase36Powers[7] = { 1, 36, 1296, 46656, 1679616, 60466176, 2176782336 };
|
||||||
|
|
||||||
|
#define MIN_BASE36_SERIAL (16796160)
|
||||||
|
#define MAX_SERIAL (2176782335)
|
||||||
|
|
||||||
|
std::string Device::SerialNumToString(uint32_t serial) {
|
||||||
|
if(serial == 0 || serial > MAX_SERIAL)
|
||||||
|
return "0";
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
if(serial >= MIN_BASE36_SERIAL) {
|
||||||
|
for (auto i = 5; i >= 0; i--) {
|
||||||
|
ss << toBase36Table[serial / toBase36Powers[i]];
|
||||||
|
serial = serial % toBase36Powers[i];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ss << serial;
|
||||||
|
}
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t Device::SerialStringToNum(const std::string& serial) {
|
||||||
|
if(Device::SerialStringIsNumeric(serial)) {
|
||||||
|
try {
|
||||||
|
return std::stoi(serial);
|
||||||
|
} catch(...) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(serial.length() != 6)
|
||||||
|
return 0; // Non-numeric serial numbers should be 6 characters
|
||||||
|
|
||||||
|
uint32_t ret = 0;
|
||||||
|
for (auto i = 0; i < 6; i++) {
|
||||||
|
ret *= 36;
|
||||||
|
ret += fromBase36Table[(unsigned char)serial[i]];
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Device::SerialStringIsNumeric(const std::string& serial) {
|
||||||
|
if(serial.length() == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(serial.length() == 1)
|
||||||
|
return isdigit(serial[0]);
|
||||||
|
|
||||||
|
// Check the first two characters, at least one should be a character if we need to do a base36 conversion
|
||||||
|
return isdigit(serial[0]) && isdigit(serial[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Device::enableMessagePolling() {
|
||||||
|
if(messagePollingCallbackID != 0) // We are already polling
|
||||||
|
return;
|
||||||
|
|
||||||
|
messagePollingCallbackID = com->addMessageCallback(MessageCallback([this](std::shared_ptr<Message> message) {
|
||||||
|
pollingContainer.enqueue(message);
|
||||||
|
enforcePollingMessageLimit();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Device::disableMessagePolling() {
|
||||||
|
if(messagePollingCallbackID == 0)
|
||||||
|
return true; // Not currently polling
|
||||||
|
|
||||||
|
auto ret = com->removeMessageCallback(messagePollingCallbackID);
|
||||||
|
getMessages(); // Flush any messages still in the container
|
||||||
|
messagePollingCallbackID = 0;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<Message>> Device::getMessages() {
|
||||||
|
std::vector<std::shared_ptr<Message>> ret;
|
||||||
|
getMessages(ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Device::getMessages(std::vector<std::shared_ptr<Message>>& container, size_t limit) {
|
||||||
|
// A limit of zero indicates no limit
|
||||||
|
auto oglimit = limit;
|
||||||
|
if(limit == 0)
|
||||||
|
limit = (size_t)-1;
|
||||||
|
|
||||||
|
if(limit > (pollingContainer.size_approx() + 4))
|
||||||
|
limit = (pollingContainer.size_approx() + 4);
|
||||||
|
|
||||||
|
if(container.capacity() < limit)
|
||||||
|
container.resize(limit);
|
||||||
|
|
||||||
|
size_t actuallyRead = pollingContainer.try_dequeue_bulk(container.data(), limit);
|
||||||
|
|
||||||
|
container.resize(actuallyRead);
|
||||||
|
|
||||||
|
return actuallyRead <= oglimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Device::enforcePollingMessageLimit() {
|
||||||
|
while(pollingContainer.size_approx() > pollingMessageLimit) {
|
||||||
|
std::shared_ptr<Message> throwAway;
|
||||||
|
pollingContainer.try_dequeue(throwAway);
|
||||||
|
// TODO Flag an error for the user!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Device::open() {
|
||||||
|
if(!com)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return com->open();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Device::close() {
|
||||||
|
if(!com)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return com->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Device::goOnline() {
|
||||||
|
if(!com->sendCommand(Communication::Command::EnableNetworkCommunication, true))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(!com->sendCommand(Communication::Command::RequestSerialNumber))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
com->addMessageCallback(CANMessageCallback([](std::shared_ptr<Message> message) {
|
||||||
|
std::shared_ptr<CANMessage> canMessage = std::static_pointer_cast<CANMessage>(message);
|
||||||
|
std::cout << "CAN 0x" << std::hex << canMessage->arbid << std::dec << " [" << canMessage->data.size() << "] " << std::hex;
|
||||||
|
for(const auto& b : canMessage->data)
|
||||||
|
std::cout << (int)b << ' ';
|
||||||
|
std::cout << std::dec << std::endl;
|
||||||
|
}));
|
||||||
|
|
||||||
|
return online = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Device::goOffline() {
|
||||||
|
return com->sendCommand(Communication::Command::EnableNetworkCommunication, false);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
#include "device/include/devicefinder.h"
|
||||||
|
#include "device/neovifire/include/neovifire.h"
|
||||||
|
#include "device/neovifire2/include/neovifire2.h"
|
||||||
|
#include "device/plasion/include/neoviion.h"
|
||||||
|
#include "device/plasion/include/neoviplasma.h"
|
||||||
|
#include "device/radstar2/include/radstar2.h"
|
||||||
|
#include "device/radsupermoon/include/radsupermoon.h"
|
||||||
|
#include "device/valuecan3/include/valuecan3.h"
|
||||||
|
#include "device/valuecan4/include/valuecan4.h"
|
||||||
|
#include "device/vividcan/include/vividcan.h"
|
||||||
|
|
||||||
|
using namespace icsneo;
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<Device>> DeviceFinder::FindAll() {
|
||||||
|
std::vector<std::shared_ptr<Device>> foundDevices;
|
||||||
|
std::vector<std::vector<std::shared_ptr<Device>>> findResults;
|
||||||
|
|
||||||
|
findResults.push_back(NeoVIFIRE::Find());
|
||||||
|
findResults.push_back(NeoVIFIRE2::Find());
|
||||||
|
findResults.push_back(NeoVIION::Find());
|
||||||
|
findResults.push_back(NeoVIPLASMA::Find());
|
||||||
|
findResults.push_back(RADStar2::Find());
|
||||||
|
findResults.push_back(RADSupermoon::Find());
|
||||||
|
findResults.push_back(ValueCAN3::Find());
|
||||||
|
findResults.push_back(ValueCAN4::Find());
|
||||||
|
findResults.push_back(VividCAN::Find());
|
||||||
|
|
||||||
|
for(auto& results : findResults) {
|
||||||
|
if(results.size())
|
||||||
|
foundDevices.insert(foundDevices.end(), std::make_move_iterator(results.begin()), std::make_move_iterator(results.end()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return foundDevices;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
#ifndef __DEVICE_H__
|
||||||
|
#define __DEVICE_H__
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include <cstring>
|
||||||
|
#include "device/include/neodevice.h"
|
||||||
|
#include "communication/include/communication.h"
|
||||||
|
#include "third-party/concurrentqueue/concurrentqueue.h"
|
||||||
|
|
||||||
|
namespace icsneo {
|
||||||
|
|
||||||
|
class Device {
|
||||||
|
public:
|
||||||
|
Device(neodevice_t neodevice = {}) {
|
||||||
|
data = neodevice;
|
||||||
|
data.device = this;
|
||||||
|
setProductName("undefined");
|
||||||
|
}
|
||||||
|
virtual ~Device() {
|
||||||
|
disableMessagePolling();
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string SerialNumToString(uint32_t serial);
|
||||||
|
static uint32_t SerialStringToNum(const std::string& serial);
|
||||||
|
static bool SerialStringIsNumeric(const std::string& serial);
|
||||||
|
|
||||||
|
std::string getProductName() const { return data.type; }
|
||||||
|
uint16_t getUSBProductId() const { return usbProductId; }
|
||||||
|
std::string getSerial() const { return data.serial; }
|
||||||
|
uint32_t getSerialNumber() const { return Device::SerialStringToNum(getSerial()); }
|
||||||
|
const neodevice_t& getNeoDevice() const { return data; }
|
||||||
|
|
||||||
|
virtual bool open();
|
||||||
|
virtual bool close();
|
||||||
|
virtual bool isOnline() const { return online; }
|
||||||
|
virtual bool goOnline();
|
||||||
|
virtual bool goOffline();
|
||||||
|
|
||||||
|
// Message polling related functions
|
||||||
|
void enableMessagePolling();
|
||||||
|
bool disableMessagePolling();
|
||||||
|
std::vector<std::shared_ptr<Message>> getMessages();
|
||||||
|
bool getMessages(std::vector<std::shared_ptr<Message>>& container, size_t limit = 0);
|
||||||
|
size_t getPollingMessageLimit() { return pollingMessageLimit; }
|
||||||
|
void setPollingMessageLimit(size_t newSize) {
|
||||||
|
pollingMessageLimit = newSize;
|
||||||
|
enforcePollingMessageLimit();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint16_t usbProductId = 0;
|
||||||
|
bool online = false;
|
||||||
|
int messagePollingCallbackID = 0;
|
||||||
|
std::shared_ptr<Communication> com;
|
||||||
|
|
||||||
|
neodevice_t& getWritableNeoDevice() { return data; }
|
||||||
|
void setProductName(const std::string& newName) {
|
||||||
|
#pragma warning( disable : 4996 )
|
||||||
|
auto copied = newName.copy(data.type, sizeof(data.type) - 1);
|
||||||
|
data.type[copied] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
neodevice_t data;
|
||||||
|
|
||||||
|
size_t pollingMessageLimit = 20000;
|
||||||
|
moodycamel::ConcurrentQueue<std::shared_ptr<Message>> pollingContainer;
|
||||||
|
void enforcePollingMessageLimit();
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
#ifndef __DEVICEFINDER_H_
|
||||||
|
#define __DEVICEFINDER_H_
|
||||||
|
|
||||||
|
#include "device/include/device.h"
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace icsneo {
|
||||||
|
|
||||||
|
class DeviceFinder {
|
||||||
|
public:
|
||||||
|
static std::vector<std::shared_ptr<Device>> FindAll();
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
#ifndef __NEODEVICE_H_
|
||||||
|
#define __NEODEVICE_H_
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
// A forward declaration is needed as there is a circular dependency
|
||||||
|
namespace icsneo {
|
||||||
|
class Device;
|
||||||
|
};
|
||||||
|
typedef icsneo::Device* devicehandle_t;
|
||||||
|
#else
|
||||||
|
typedef void* devicehandle_t;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef int32_t neodevice_handle_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
devicehandle_t device;
|
||||||
|
neodevice_handle_t handle;
|
||||||
|
char serial[7];
|
||||||
|
char type[64];
|
||||||
|
} neodevice_t;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
#ifndef __NEOVIFIRE_H_
|
||||||
|
#define __NEOVIFIRE_H_
|
||||||
|
|
||||||
|
#include "device/include/device.h"
|
||||||
|
#include "platform/include/ftdi.h"
|
||||||
|
|
||||||
|
namespace icsneo {
|
||||||
|
|
||||||
|
class NeoVIFIRE : public Device {
|
||||||
|
public:
|
||||||
|
static constexpr const char* PRODUCT_NAME = "neoVI FIRE";
|
||||||
|
static constexpr const uint16_t USB_PRODUCT_ID = 0x0701;
|
||||||
|
NeoVIFIRE(neodevice_t neodevice) : Device(neodevice) {
|
||||||
|
com = std::make_shared<Communication>(std::make_shared<FTDI>(getWritableNeoDevice()));
|
||||||
|
setProductName(PRODUCT_NAME);
|
||||||
|
usbProductId = USB_PRODUCT_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Mode : char {
|
||||||
|
Application = 'A',
|
||||||
|
Bootloader = 'B'
|
||||||
|
};
|
||||||
|
|
||||||
|
bool goOnline() {
|
||||||
|
// Enter mode is only needed on very old FIRE devices, will be ignored by newer devices
|
||||||
|
if(!enterMode(Mode::Application))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return Device::goOnline();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool enterMode(Mode mode) {
|
||||||
|
// Included for compatibility with bootloaders on very old FIRE devices
|
||||||
|
// Mode will be a uppercase char like 'A'
|
||||||
|
if(!com->rawWrite({ (uint8_t)mode }))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// We then expect to see that same mode back in lowercase
|
||||||
|
// This won't happen in the case of new devices, though, so we assume it worked
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<std::shared_ptr<Device>> Find() {
|
||||||
|
std::vector<std::shared_ptr<Device>> found;
|
||||||
|
|
||||||
|
for(auto neodevice : FTDI::FindByProduct(USB_PRODUCT_ID))
|
||||||
|
found.push_back(std::make_shared<NeoVIFIRE>(neodevice));
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
#ifndef __NEOVIFIRE2_H_
|
||||||
|
#define __NEOVIFIRE2_H_
|
||||||
|
|
||||||
|
#include "device/include/device.h"
|
||||||
|
#include "platform/include/ftdi.h"
|
||||||
|
|
||||||
|
namespace icsneo {
|
||||||
|
|
||||||
|
class NeoVIFIRE2 : public Device {
|
||||||
|
public:
|
||||||
|
static constexpr const char* PRODUCT_NAME = "neoVI FIRE 2";
|
||||||
|
static constexpr const uint16_t USB_PRODUCT_ID = 0x1000;
|
||||||
|
NeoVIFIRE2(neodevice_t neodevice) : Device(neodevice) {
|
||||||
|
com = std::make_shared<Communication>(std::make_shared<FTDI>(getWritableNeoDevice()));
|
||||||
|
setProductName(PRODUCT_NAME);
|
||||||
|
usbProductId = USB_PRODUCT_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<std::shared_ptr<Device>> Find() {
|
||||||
|
std::vector<std::shared_ptr<Device>> found;
|
||||||
|
|
||||||
|
for(auto neodevice : FTDI::FindByProduct(USB_PRODUCT_ID))
|
||||||
|
found.push_back(std::make_shared<NeoVIFIRE2>(neodevice));
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
#ifndef __NEOVIION_H_
|
||||||
|
#define __NEOVIION_H_
|
||||||
|
|
||||||
|
#include "device/plasion/include/plasion.h"
|
||||||
|
#include "platform/include/ftdi.h"
|
||||||
|
|
||||||
|
namespace icsneo {
|
||||||
|
|
||||||
|
class NeoVIION : public Plasion {
|
||||||
|
public:
|
||||||
|
static constexpr const char* PRODUCT_NAME = "neoVI ION";
|
||||||
|
static constexpr const uint16_t USB_PRODUCT_ID = 0x0901;
|
||||||
|
NeoVIION(neodevice_t neodevice) : Plasion(neodevice) {
|
||||||
|
setProductName(PRODUCT_NAME);
|
||||||
|
usbProductId = USB_PRODUCT_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<std::shared_ptr<Device>> Find() {
|
||||||
|
std::vector<std::shared_ptr<Device>> found;
|
||||||
|
|
||||||
|
for(auto neodevice : FTDI::FindByProduct(USB_PRODUCT_ID))
|
||||||
|
found.push_back(std::make_shared<NeoVIION>(neodevice));
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
#ifndef __NEOVIPLASMA_H_
|
||||||
|
#define __NEOVIPLASMA_H_
|
||||||
|
|
||||||
|
#include "device/plasion/include/plasion.h"
|
||||||
|
#include "platform/include/ftdi.h"
|
||||||
|
|
||||||
|
namespace icsneo {
|
||||||
|
|
||||||
|
class NeoVIPLASMA : public Plasion {
|
||||||
|
public:
|
||||||
|
static constexpr const char* PRODUCT_NAME = "neoVI PLASMA";
|
||||||
|
static constexpr const uint16_t USB_PRODUCT_ID = 0x0801;
|
||||||
|
NeoVIPLASMA(neodevice_t neodevice) : Plasion(neodevice) {
|
||||||
|
setProductName(PRODUCT_NAME);
|
||||||
|
usbProductId = USB_PRODUCT_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<std::shared_ptr<Device>> Find() {
|
||||||
|
std::vector<std::shared_ptr<Device>> found;
|
||||||
|
|
||||||
|
for(auto neodevice : FTDI::FindByProduct(USB_PRODUCT_ID))
|
||||||
|
found.push_back(std::make_shared<NeoVIPLASMA>(neodevice));
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
#ifndef __PLASION_H_
|
||||||
|
#define __PLASION_H_
|
||||||
|
|
||||||
|
#include "device/include/device.h"
|
||||||
|
#include "communication/include/multichannelcommunication.h"
|
||||||
|
#include "platform/include/ftdi.h"
|
||||||
|
|
||||||
|
namespace icsneo {
|
||||||
|
|
||||||
|
class Plasion : public Device {
|
||||||
|
public:
|
||||||
|
Plasion(neodevice_t neodevice) : Device(neodevice) {
|
||||||
|
com = std::make_shared<MultiChannelCommunication>(std::make_shared<FTDI>(getWritableNeoDevice()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
#ifndef __RADSTAR2_H_
|
||||||
|
#define __RADSTAR2_H_
|
||||||
|
|
||||||
|
#include "device/include/device.h"
|
||||||
|
#include "platform/include/ftdi.h"
|
||||||
|
|
||||||
|
namespace icsneo {
|
||||||
|
|
||||||
|
class RADStar2 : public Device {
|
||||||
|
public:
|
||||||
|
// Serial numbers start with RS
|
||||||
|
static constexpr const char* PRODUCT_NAME = "RADStar 2";
|
||||||
|
static constexpr const uint16_t USB_PRODUCT_ID = 0x0005;
|
||||||
|
RADStar2(neodevice_t neodevice) : Device(neodevice) {
|
||||||
|
com = std::make_shared<Communication>(std::make_shared<FTDI>(getWritableNeoDevice()));
|
||||||
|
setProductName(PRODUCT_NAME);
|
||||||
|
usbProductId = USB_PRODUCT_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<std::shared_ptr<Device>> Find() {
|
||||||
|
std::vector<std::shared_ptr<Device>> found;
|
||||||
|
|
||||||
|
for(auto neodevice : FTDI::FindByProduct(USB_PRODUCT_ID))
|
||||||
|
found.push_back(std::make_shared<RADStar2>(neodevice));
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
#ifndef __RADSUPERMOON_H_
|
||||||
|
#define __RADSUPERMOON_H_
|
||||||
|
|
||||||
|
#include "device/include/device.h"
|
||||||
|
#include "platform/include/ftdi.h"
|
||||||
|
|
||||||
|
namespace icsneo {
|
||||||
|
|
||||||
|
class RADSupermoon : public Device {
|
||||||
|
public:
|
||||||
|
// Serial numbers start with VV
|
||||||
|
static constexpr const char* PRODUCT_NAME = "RADSupermoon";
|
||||||
|
static constexpr const uint16_t USB_PRODUCT_ID = 0x1201;
|
||||||
|
RADSupermoon(neodevice_t neodevice) : Device(neodevice) {
|
||||||
|
com = std::make_shared<Communication>(std::make_shared<FTDI>(getWritableNeoDevice()));
|
||||||
|
com->setAlign16Bit(false);
|
||||||
|
setProductName(PRODUCT_NAME);
|
||||||
|
usbProductId = USB_PRODUCT_ID;
|
||||||
|
}
|
||||||
|
// RSM does not connect at all yet (needs FTDI D3xx driver, not the 2xx compatible one)
|
||||||
|
|
||||||
|
static std::vector<std::shared_ptr<Device>> Find() {
|
||||||
|
std::vector<std::shared_ptr<Device>> found;
|
||||||
|
|
||||||
|
for(auto neodevice : FTDI::FindByProduct(USB_PRODUCT_ID))
|
||||||
|
found.push_back(std::make_shared<RADSupermoon>(neodevice));
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
#ifndef __VALUECAN3_H_
|
||||||
|
#define __VALUECAN3_H_
|
||||||
|
|
||||||
|
#include "device/include/device.h"
|
||||||
|
#include "platform/include/ftdi.h"
|
||||||
|
|
||||||
|
namespace icsneo {
|
||||||
|
|
||||||
|
class ValueCAN3 : public Device {
|
||||||
|
public:
|
||||||
|
static constexpr const char* PRODUCT_NAME = "ValueCAN 3";
|
||||||
|
static constexpr const uint16_t USB_PRODUCT_ID = 0x0601;
|
||||||
|
ValueCAN3(neodevice_t neodevice) : Device(neodevice) {
|
||||||
|
com = std::make_shared<Communication>(std::make_shared<FTDI>(getWritableNeoDevice()));
|
||||||
|
setProductName(PRODUCT_NAME);
|
||||||
|
usbProductId = USB_PRODUCT_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<std::shared_ptr<Device>> Find() {
|
||||||
|
std::vector<std::shared_ptr<Device>> found;
|
||||||
|
|
||||||
|
for(auto neodevice : FTDI::FindByProduct(USB_PRODUCT_ID))
|
||||||
|
found.push_back(std::make_shared<ValueCAN3>(neodevice));
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
#ifndef __VALUECAN4_H_
|
||||||
|
#define __VALUECAN4_H_
|
||||||
|
|
||||||
|
#include "device/include/device.h"
|
||||||
|
#include "platform/include/stm32.h"
|
||||||
|
|
||||||
|
namespace icsneo {
|
||||||
|
|
||||||
|
class ValueCAN4 : public Device {
|
||||||
|
public:
|
||||||
|
// Serial numbers are V0 for 4-4, VE for 4-2EL, V2 for 4-2, and V1 for 4-1
|
||||||
|
static constexpr const char* PRODUCT_NAME = "ValueCAN 4";
|
||||||
|
static constexpr const uint16_t USB_PRODUCT_ID = 0x1101;
|
||||||
|
ValueCAN4(neodevice_t neodevice) : Device(neodevice) {
|
||||||
|
com = std::make_shared<Communication>(std::make_shared<STM32>(getWritableNeoDevice()));
|
||||||
|
setProductName(PRODUCT_NAME);
|
||||||
|
usbProductId = USB_PRODUCT_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<std::shared_ptr<Device>> Find() {
|
||||||
|
std::vector<std::shared_ptr<Device>> found;
|
||||||
|
|
||||||
|
for(auto neodevice : STM32::FindByProduct(USB_PRODUCT_ID))
|
||||||
|
found.push_back(std::make_shared<ValueCAN4>(neodevice));
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
#ifndef __VIVIDCAN_H_
|
||||||
|
#define __VIVIDCAN_H_
|
||||||
|
|
||||||
|
#include "device/include/device.h"
|
||||||
|
#include "platform/include/stm32.h"
|
||||||
|
|
||||||
|
namespace icsneo {
|
||||||
|
|
||||||
|
class VividCAN : public Device {
|
||||||
|
public:
|
||||||
|
// Serial numbers start with VV
|
||||||
|
static constexpr const char* PRODUCT_NAME = "VividCAN";
|
||||||
|
static constexpr const uint16_t USB_PRODUCT_ID = 0x1102;
|
||||||
|
VividCAN(neodevice_t neodevice) : Device(neodevice) {
|
||||||
|
com = std::make_shared<Communication>(std::make_shared<STM32>(getWritableNeoDevice()));
|
||||||
|
setProductName(PRODUCT_NAME);
|
||||||
|
usbProductId = USB_PRODUCT_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool goOnline() { return false; }
|
||||||
|
|
||||||
|
static std::vector<std::shared_ptr<Device>> Find() {
|
||||||
|
std::vector<std::shared_ptr<Device>> found;
|
||||||
|
|
||||||
|
for(auto neodevice : STM32::FindByProduct(USB_PRODUCT_ID))
|
||||||
|
found.push_back(std::make_shared<VividCAN>(neodevice));
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
#ifndef __DYNAMICLIB_H_
|
||||||
|
#define __DYNAMICLIB_H_
|
||||||
|
|
||||||
|
#if defined _WIN32
|
||||||
|
#include "platform/windows/include/dynamiclib.h"
|
||||||
|
#elif defined __linux__
|
||||||
|
#include "platform/linux/include/dynamiclib.h"
|
||||||
|
#else
|
||||||
|
#warning "This platform is not supported by the dynamic library driver"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
#ifndef __FTDI_H_
|
||||||
|
#define __FTDI_H_
|
||||||
|
|
||||||
|
#define INTREPID_USB_VENDOR_ID (0x093c)
|
||||||
|
|
||||||
|
#if defined _WIN32
|
||||||
|
#include "platform/windows/include/ftdi.h"
|
||||||
|
#elif defined __linux__
|
||||||
|
#include "platform/linux/include/ftdi.h"
|
||||||
|
#else
|
||||||
|
#warning "This platform is not supported by the FTDI driver"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
#ifndef __REGISTRY_H_
|
||||||
|
#define __REGISTRY_H_
|
||||||
|
|
||||||
|
#if defined _WIN32
|
||||||
|
#include "platform/windows/include/registry.h"
|
||||||
|
#else
|
||||||
|
#warning "This platform is not supported by the registry driver"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
#ifndef __STM32_H_
|
||||||
|
#define __STM32_H_
|
||||||
|
|
||||||
|
#define INTREPID_USB_VENDOR_ID (0x093c)
|
||||||
|
|
||||||
|
#if defined _WIN32
|
||||||
|
#include "platform/windows/include/stm32.h"
|
||||||
|
#elif defined __linux__
|
||||||
|
#include "platform/linux/include/stm32.h"
|
||||||
|
#else
|
||||||
|
#warning "This platform is not supported by the STM32 driver"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,125 @@
|
||||||
|
#include "platform/linux/include/ftdi.h"
|
||||||
|
#include "platform/include/ftdi.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
using namespace icsneo;
|
||||||
|
|
||||||
|
// Instantiate static variables
|
||||||
|
neodevice_handle_t FTDI::handleCounter = 1;
|
||||||
|
Ftdi::Context FTDI::context;
|
||||||
|
std::vector<FTDI::FTDIDevice> FTDI::searchResultDevices;
|
||||||
|
|
||||||
|
/* Theory: Ftdi::List::find_all gives us back Ftdi::Context objects, but these can't be passed
|
||||||
|
* back and forth with C nicely. So we wrap the Ftdi::Context objects in FTDIDevice classes which
|
||||||
|
* will give it a nice neodevice_handle_t handle that we can reference it by. These FTDIDevice objects are
|
||||||
|
* stored in searchResultDevices, and then moved into the instantiated FTDI class by the constructor.
|
||||||
|
*/
|
||||||
|
std::vector<neodevice_t> FTDI::FindByProduct(int product) {
|
||||||
|
constexpr size_t deviceSerialBufferLength = sizeof(device.serial);
|
||||||
|
std::vector<neodevice_t> found;
|
||||||
|
|
||||||
|
auto devlist = std::unique_ptr<Ftdi::List>(Ftdi::List::find_all(context, INTREPID_USB_VENDOR_ID, product));
|
||||||
|
searchResultDevices.clear();
|
||||||
|
for(auto it = devlist->begin(); it != devlist->end(); it++)
|
||||||
|
searchResultDevices.push_back(*it); // The upconversion to FTDIDevice will assign a handle
|
||||||
|
|
||||||
|
for(auto& dev : searchResultDevices) {
|
||||||
|
neodevice_t d;
|
||||||
|
auto& serial = dev.serial();
|
||||||
|
strncpy(d.serial, serial.c_str(), deviceSerialBufferLength - 1);
|
||||||
|
d.serial[deviceSerialBufferLength - 1] = '\0'; // strncpy does not write a null terminator if serial is too long
|
||||||
|
d.handle = dev.handle;
|
||||||
|
found.push_back(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FTDI::IsHandleValid(neodevice_handle_t handle) {
|
||||||
|
for(auto& dev : searchResultDevices) {
|
||||||
|
if(dev.handle != handle)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FTDI::GetDeviceForHandle(neodevice_handle_t handle, FTDIDevice& device) {
|
||||||
|
for(auto& dev : searchResultDevices) {
|
||||||
|
if(dev.handle != handle)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
device = dev;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FTDI::FTDI(neodevice_t& forDevice) : device(forDevice) {
|
||||||
|
openable = GetDeviceForHandle(forDevice.handle, ftdiDevice);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FTDI::open() {
|
||||||
|
if(isOpen() || !openable)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(ftdiDevice.open())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ftdiDevice.set_usb_read_timeout(100);
|
||||||
|
ftdiDevice.set_usb_write_timeout(1000);
|
||||||
|
ftdiDevice.reset();
|
||||||
|
ftdiDevice.set_baud_rate(500000);
|
||||||
|
ftdiDevice.flush();
|
||||||
|
|
||||||
|
// Create threads
|
||||||
|
closing = false;
|
||||||
|
readThread = std::thread(&FTDI::readTask, this);
|
||||||
|
writeThread = std::thread(&FTDI::writeTask, this);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FTDI::close() {
|
||||||
|
if(!isOpen())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
closing = true;
|
||||||
|
|
||||||
|
if(readThread.joinable())
|
||||||
|
readThread.join();
|
||||||
|
|
||||||
|
if(writeThread.joinable())
|
||||||
|
writeThread.join();
|
||||||
|
|
||||||
|
ftdiDevice.set_dtr(false);
|
||||||
|
|
||||||
|
if(ftdiDevice.close())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FTDI::readTask() {
|
||||||
|
constexpr size_t READ_BUFFER_SIZE = 8;
|
||||||
|
uint8_t readbuf[READ_BUFFER_SIZE];
|
||||||
|
while(!closing) {
|
||||||
|
auto readBytes = ftdiDevice.read(readbuf, READ_BUFFER_SIZE);
|
||||||
|
if(readBytes > 0)
|
||||||
|
readQueue.enqueue_bulk(readbuf, readBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FTDI::writeTask() {
|
||||||
|
WriteOperation writeOp;
|
||||||
|
while(!closing) {
|
||||||
|
if(!writeQueue.wait_dequeue_timed(writeOp, std::chrono::milliseconds(100)))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ftdiDevice.write(writeOp.bytes.data(), (int)writeOp.bytes.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
#ifndef __DYNAMICLIB_H_LINUX_
|
||||||
|
#define __DYNAMICLIB_H_LINUX_
|
||||||
|
|
||||||
|
#include <dlfcn.h>
|
||||||
|
|
||||||
|
// Nothing special is needed to export
|
||||||
|
#define DLLExport
|
||||||
|
|
||||||
|
// #ifndef ICSNEO_NO_AUTO_DESTRUCT
|
||||||
|
// #define ICSNEO_DESTRUCTOR __attribute__((destructor));
|
||||||
|
// #else
|
||||||
|
#define ICSNEO_DESTRUCTOR
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
#define icsneoDynamicLibraryLoad() dlopen("/media/paulywog/Windows 10/Users/phollinsky/Code/icsneonext/build/libicsneoc.so", RTLD_LAZY)
|
||||||
|
#define icsneoDynamicLibraryGetFunction(handle, func) dlsym(handle, func)
|
||||||
|
#define icsneoDynamicLibraryClose(handle) (dlclose(handle) == 0)
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
#ifndef __FTDI_H_LINUX_
|
||||||
|
#define __FTDI_H_LINUX_
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <atomic>
|
||||||
|
#include <ftdi.hpp>
|
||||||
|
#include "device/include/neodevice.h"
|
||||||
|
#include "communication/include/icommunication.h"
|
||||||
|
#include "third-party/concurrentqueue/blockingconcurrentqueue.h"
|
||||||
|
|
||||||
|
namespace icsneo {
|
||||||
|
|
||||||
|
class FTDI : public ICommunication {
|
||||||
|
public:
|
||||||
|
static constexpr neodevice_handle_t INVALID_HANDLE = 0x7fffffff; // int32_t max value
|
||||||
|
static std::vector<neodevice_t> FindByProduct(int product);
|
||||||
|
static bool IsHandleValid(neodevice_handle_t handle);
|
||||||
|
|
||||||
|
FTDI(neodevice_t& forDevice);
|
||||||
|
~FTDI() { close(); }
|
||||||
|
bool open();
|
||||||
|
bool close();
|
||||||
|
bool isOpen() { return ftdiDevice.is_open(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
static Ftdi::Context context;
|
||||||
|
static neodevice_handle_t handleCounter;
|
||||||
|
class FTDIDevice : public Ftdi::Context {
|
||||||
|
public:
|
||||||
|
FTDIDevice() {}
|
||||||
|
FTDIDevice(const Ftdi::Context &x) : Ftdi::Context(x) {
|
||||||
|
handle = handleCounter++;
|
||||||
|
}
|
||||||
|
neodevice_handle_t handle = INVALID_HANDLE;
|
||||||
|
};
|
||||||
|
static std::vector<FTDIDevice> searchResultDevices;
|
||||||
|
static bool GetDeviceForHandle(neodevice_handle_t handle, FTDIDevice& device);
|
||||||
|
|
||||||
|
void readTask();
|
||||||
|
void writeTask();
|
||||||
|
bool openable; // Set to false in the constructor if the object has not been found in searchResultDevices
|
||||||
|
|
||||||
|
neodevice_t& device;
|
||||||
|
FTDIDevice ftdiDevice;
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
#ifndef __STM32_LINUX_H_
|
||||||
|
#define __STM32_LINUX_H_
|
||||||
|
|
||||||
|
#include "communication/include/icommunication.h"
|
||||||
|
#include "device/include/neodevice.h"
|
||||||
|
#include <chrono>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
namespace icsneo {
|
||||||
|
|
||||||
|
class STM32 : public ICommunication {
|
||||||
|
public:
|
||||||
|
STM32(neodevice_t& forDevice) : device(forDevice) {}
|
||||||
|
static std::vector<neodevice_t> FindByProduct(int product);
|
||||||
|
|
||||||
|
bool open();
|
||||||
|
bool isOpen();
|
||||||
|
bool close();
|
||||||
|
|
||||||
|
private:
|
||||||
|
neodevice_t& device;
|
||||||
|
int fd = -1;
|
||||||
|
static constexpr neodevice_handle_t HANDLE_OFFSET = 10;
|
||||||
|
|
||||||
|
void readTask();
|
||||||
|
void writeTask();
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,283 @@
|
||||||
|
#include "platform/include/stm32.h"
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <map>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
using namespace icsneo;
|
||||||
|
|
||||||
|
class Directory {
|
||||||
|
public:
|
||||||
|
class Listing {
|
||||||
|
public:
|
||||||
|
Listing(std::string newName, uint8_t newType) : name(newName), type(newType) {}
|
||||||
|
const std::string& getName() const { return name; }
|
||||||
|
uint8_t getType() const { return type; }
|
||||||
|
private:
|
||||||
|
std::string name;
|
||||||
|
uint8_t type;
|
||||||
|
};
|
||||||
|
Directory(std::string directory) {
|
||||||
|
dir = opendir(directory.c_str());
|
||||||
|
}
|
||||||
|
~Directory() {
|
||||||
|
if(openedSuccessfully())
|
||||||
|
closedir(dir);
|
||||||
|
dir = nullptr;
|
||||||
|
}
|
||||||
|
bool openedSuccessfully() { return dir != nullptr; }
|
||||||
|
std::vector<Listing> ls() {
|
||||||
|
std::vector<Listing> results;
|
||||||
|
struct dirent* entry;
|
||||||
|
while((entry = readdir(dir)) != nullptr) {
|
||||||
|
std::string name = entry->d_name;
|
||||||
|
if(name != "." && name != "..") // Ignore parent and self
|
||||||
|
results.emplace_back(name, entry->d_type);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
DIR* dir;
|
||||||
|
};
|
||||||
|
|
||||||
|
class USBSerialGetter {
|
||||||
|
public:
|
||||||
|
USBSerialGetter(std::string usbid) {
|
||||||
|
std::stringstream ss;
|
||||||
|
auto colonpos = usbid.find(":");
|
||||||
|
if(colonpos == std::string::npos) {
|
||||||
|
succeeded = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ss << "/sys/bus/usb/devices/" << usbid.substr(0, colonpos) << "/serial";
|
||||||
|
try {
|
||||||
|
std::ifstream reader(ss.str());
|
||||||
|
std::getline(reader, serial);
|
||||||
|
} catch(...) {
|
||||||
|
succeeded = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
succeeded = true;
|
||||||
|
}
|
||||||
|
bool success() const { return succeeded; }
|
||||||
|
const std::string& getSerial() const { return serial; }
|
||||||
|
private:
|
||||||
|
bool succeeded;
|
||||||
|
std::string serial;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<neodevice_t> STM32::FindByProduct(int product) {
|
||||||
|
std::vector<neodevice_t> found;
|
||||||
|
|
||||||
|
Directory directory("/sys/bus/usb/drivers/cdc_acm"); // Query the STM32 driver
|
||||||
|
if(!directory.openedSuccessfully())
|
||||||
|
return found;
|
||||||
|
|
||||||
|
std::vector<std::string> foundusbs;
|
||||||
|
for(auto& entry : directory.ls()) {
|
||||||
|
/* This directory will have directories (links) for all devices using the cdc_acm driver (as STM32 devices do)
|
||||||
|
* There will also be other files and directories providing information about the driver in here. We want to ignore them.
|
||||||
|
* Devices will be named like "7-2:1.0" where 7 is the enumeration for the USB controller, 2 is the device enumeration on
|
||||||
|
* that specific controller (will change if the device is unplugged and replugged), 1 is the device itself and 0 is
|
||||||
|
* enumeration for different services provided by the device. We're looking for the service that provides TTY.
|
||||||
|
* For now we find the directories with a digit for the first character, these are likely to be our USB devices.
|
||||||
|
*/
|
||||||
|
if(isdigit(entry.getName()[0]) && entry.getType() == DT_LNK)
|
||||||
|
foundusbs.emplace_back(entry.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pair the USB and TTY if found
|
||||||
|
std::map<std::string, std::string> foundttys;
|
||||||
|
for(auto& usb : foundusbs) {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "/sys/bus/usb/drivers/cdc_acm/" << usb << "/tty";
|
||||||
|
Directory devicedir(ss.str());
|
||||||
|
if(!devicedir.openedSuccessfully()) // The tty directory doesn't exist, because this is not the tty service we want
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto listing = devicedir.ls();
|
||||||
|
if(listing.size() != 1) // We either got no serial ports or multiple, either way no good
|
||||||
|
continue;
|
||||||
|
|
||||||
|
foundttys.insert(std::make_pair(usb, listing[0].getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're going to remove from the map if this is not the product we're looking for
|
||||||
|
for(auto iter = foundttys.begin(); iter != foundttys.end(); ) {
|
||||||
|
const auto& dev = *iter;
|
||||||
|
const std::string matchString = "PRODUCT=";
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "/sys/class/tty/" << dev.second << "/device/uevent"; // Read the uevent file, which contains should have a line like "PRODUCT=93c/1101/100"
|
||||||
|
std::ifstream fs(ss.str());
|
||||||
|
std::string productLine;
|
||||||
|
size_t pos = std::string::npos;
|
||||||
|
do {
|
||||||
|
std::getline(fs, productLine, '\n');
|
||||||
|
} while(((pos = productLine.find(matchString)) == std::string::npos) && !fs.eof());
|
||||||
|
|
||||||
|
if(pos != 0) { // We did not find a product line... weird
|
||||||
|
iter = foundttys.erase(iter); // Remove the element, this also moves iter forward for us
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t firstSlashPos = productLine.find('/', matchString.length());
|
||||||
|
if(firstSlashPos == std::string::npos) {
|
||||||
|
iter = foundttys.erase(iter);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
size_t pidpos = firstSlashPos + 1;
|
||||||
|
|
||||||
|
std::string vidstr = productLine.substr(matchString.length(), firstSlashPos - matchString.length());
|
||||||
|
std::string pidstr = productLine.substr(pidpos, productLine.find('/', pidpos) - pidpos); // In hex like "1101" or "93c"
|
||||||
|
|
||||||
|
uint16_t vid, pid;
|
||||||
|
try {
|
||||||
|
vid = (uint16_t)std::stoul(vidstr, nullptr, 16);
|
||||||
|
pid = (uint16_t)std::stoul(pidstr, nullptr, 16);
|
||||||
|
} catch(...) {
|
||||||
|
iter = foundttys.erase(iter); // We could not parse the numbers
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(vid != INTREPID_USB_VENDOR_ID || pid != product) {
|
||||||
|
iter = foundttys.erase(iter); // Not the right VID or PID, remove
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
iter++; // If the loop ends without erasing the iter from the map, the item is good
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point, foundttys contains the the devices we want
|
||||||
|
|
||||||
|
// Get the serial number, create the neodevice_t
|
||||||
|
for(auto& dev : foundttys) {
|
||||||
|
neodevice_t device;
|
||||||
|
|
||||||
|
USBSerialGetter getter(dev.first);
|
||||||
|
if(!getter.success())
|
||||||
|
continue; // Failure, could not get serial number
|
||||||
|
|
||||||
|
// In ttyACM0, we want the i to be the first character of the number
|
||||||
|
size_t i;
|
||||||
|
for(i = 0; i < dev.second.length(); i++) {
|
||||||
|
if(isdigit(dev.second[i]))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Now we try to parse the number so we have a handle for later
|
||||||
|
try {
|
||||||
|
device.handle = (neodevice_handle_t)std::stoul(dev.second.substr(i));
|
||||||
|
/* The TTY numbering starts at zero, but we want to keep zero for an undefined
|
||||||
|
* handle, so add a constant, and we'll subtract that constant in the open function.
|
||||||
|
*/
|
||||||
|
device.handle += HANDLE_OFFSET;
|
||||||
|
} catch(...) {
|
||||||
|
continue; // Somehow this failed, have to toss the device
|
||||||
|
}
|
||||||
|
|
||||||
|
device.serial[getter.getSerial().copy(device.serial, sizeof(device.serial)-1)] = '\0';
|
||||||
|
|
||||||
|
found.push_back(device); // Finally, add device to search results
|
||||||
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool STM32::open() {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "/dev/ttyACM" << (int)(device.handle - HANDLE_OFFSET);
|
||||||
|
fd = ::open(ss.str().c_str(), O_RDWR | O_NOCTTY | O_SYNC);
|
||||||
|
if(!isOpen()) {
|
||||||
|
std::cout << "Open of " << ss.str().c_str() << " failed with " << strerror(errno) << ' ';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct termios tty;
|
||||||
|
|
||||||
|
if(tcgetattr(fd, &tty) < 0) {
|
||||||
|
close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cfsetspeed(&tty, B500000); // Set speed to 500kbaud
|
||||||
|
|
||||||
|
tty.c_cflag |= (CLOCAL | CREAD); // Ignore modem controls
|
||||||
|
tty.c_cflag &= ~CSIZE;
|
||||||
|
tty.c_cflag |= CS8; // 8-bit characters
|
||||||
|
tty.c_cflag &= ~PARENB; // No parity bit
|
||||||
|
tty.c_cflag &= ~CSTOPB; // One stop bit
|
||||||
|
tty.c_cflag &= ~CRTSCTS; // No hardware flow control
|
||||||
|
|
||||||
|
// Non-canonical mode
|
||||||
|
tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
|
||||||
|
tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
|
||||||
|
tty.c_oflag &= ~OPOST;
|
||||||
|
|
||||||
|
// Fetch bytes as they become available
|
||||||
|
// See http://man7.org/linux/man-pages/man3/termios.3.html
|
||||||
|
tty.c_cc[VMIN] = 0;
|
||||||
|
tty.c_cc[VTIME] = 1; // 100ms timeout (1 decisecond, what?)
|
||||||
|
|
||||||
|
if(tcsetattr(fd, TCSAFLUSH, &tty) != 0) { // Flushes input and output buffers as well as setting settings
|
||||||
|
close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create threads
|
||||||
|
readThread = std::thread(&STM32::readTask, this);
|
||||||
|
writeThread = std::thread(&STM32::writeTask, this);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool STM32::isOpen() {
|
||||||
|
return fd >= 0; // Negative fd indicates error or not opened yet
|
||||||
|
}
|
||||||
|
|
||||||
|
bool STM32::close() {
|
||||||
|
if(!isOpen())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
closing = true;
|
||||||
|
|
||||||
|
if(readThread.joinable())
|
||||||
|
readThread.join();
|
||||||
|
|
||||||
|
if(writeThread.joinable())
|
||||||
|
writeThread.join();
|
||||||
|
|
||||||
|
int ret = ::close(fd);
|
||||||
|
fd = -1;
|
||||||
|
|
||||||
|
return ret == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void STM32::readTask() {
|
||||||
|
constexpr size_t READ_BUFFER_SIZE = 8;
|
||||||
|
uint8_t readbuf[READ_BUFFER_SIZE];
|
||||||
|
while(!closing) {
|
||||||
|
auto bytesRead = ::read(fd, readbuf, READ_BUFFER_SIZE);
|
||||||
|
if(bytesRead > 0)
|
||||||
|
readQueue.enqueue_bulk(readbuf, bytesRead);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void STM32::writeTask() {
|
||||||
|
WriteOperation writeOp;
|
||||||
|
while(!closing) {
|
||||||
|
if(!writeQueue.wait_dequeue_timed(writeOp, std::chrono::milliseconds(100)))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const auto writeSize = writeOp.bytes.size();
|
||||||
|
int actualWritten = ::write(fd, writeOp.bytes.data(), writeSize);
|
||||||
|
if(actualWritten != writeSize)
|
||||||
|
std::cout << "Failure to write " << writeSize << " bytes, wrote " << actualWritten << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
#ifndef __DYNAMICLIB_H_WINDOWS_
|
||||||
|
#define __DYNAMICLIB_H_WINDOWS_
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
|
||||||
|
#ifdef ICSNEOC_MAKEDLL
|
||||||
|
#define DLLExport __declspec(dllexport)
|
||||||
|
#else
|
||||||
|
#define DLLExport __declspec(dllimport)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// MSVC does not have the ability to specify a destructor
|
||||||
|
#define ICSNEO_DESTRUCTOR
|
||||||
|
|
||||||
|
#define icsneoDynamicLibraryLoad() LoadLibrary(L"C:\\Users\\Phollinsky\\Code\\icsneonext\\build\\icsneoc.dll")
|
||||||
|
#define icsneoDynamicLibraryGetFunction(handle, func) GetProcAddress((HMODULE) handle, func)
|
||||||
|
#define icsneoDynamicLibraryClose(handle) FreeLibrary((HMODULE) handle)
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
#ifndef __FTDI_WINDOWS_H_
|
||||||
|
#define __FTDI_WINDOWS_H_
|
||||||
|
|
||||||
|
#include "platform/windows/include/vcp.h"
|
||||||
|
|
||||||
|
namespace icsneo {
|
||||||
|
|
||||||
|
class FTDI : public VCP {
|
||||||
|
public:
|
||||||
|
FTDI(neodevice_t& forDevice) : VCP(forDevice) {}
|
||||||
|
static std::vector<neodevice_t> FindByProduct(int product) { return VCP::FindByProduct(product, L"serenum"); }
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
#ifndef __REGISTRY_H_WINDOWS_
|
||||||
|
#define __REGISTRY_H_WINDOWS_
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace icsneo {
|
||||||
|
|
||||||
|
class Registry {
|
||||||
|
public:
|
||||||
|
// Get string value
|
||||||
|
static bool Get(std::wstring path, std::wstring key, std::wstring& value);
|
||||||
|
static bool Get(std::string path, std::string key, std::string& value);
|
||||||
|
|
||||||
|
// Get DWORD value
|
||||||
|
static bool Get(std::wstring path, std::wstring key, uint32_t& value);
|
||||||
|
static bool Get(std::string path, std::string key, uint32_t& value);
|
||||||
|
|
||||||
|
private:
|
||||||
|
class Key {
|
||||||
|
public:
|
||||||
|
Key(std::wstring path, bool readwrite = false);
|
||||||
|
~Key();
|
||||||
|
HKEY GetKey() { return key; }
|
||||||
|
bool IsOpen() { return key != nullptr; }
|
||||||
|
private:
|
||||||
|
HKEY key;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
#ifndef __STM32_WINDOWS_H_
|
||||||
|
#define __STM32_WINDOWS_H_
|
||||||
|
|
||||||
|
#include "platform/windows/include/vcp.h"
|
||||||
|
|
||||||
|
namespace icsneo {
|
||||||
|
|
||||||
|
class STM32 : public VCP {
|
||||||
|
public:
|
||||||
|
STM32(neodevice_t& forDevice) : VCP(forDevice) {}
|
||||||
|
static std::vector<neodevice_t> FindByProduct(int product) { return VCP::FindByProduct(product, L"usbser"); }
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
#ifndef __VCP_H_WINDOWS_
|
||||||
|
#define __VCP_H_WINDOWS_
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <Windows.h>
|
||||||
|
#include "device/include/neodevice.h"
|
||||||
|
#include "communication/include/icommunication.h"
|
||||||
|
|
||||||
|
namespace icsneo {
|
||||||
|
|
||||||
|
// Virtual COM Port Communication
|
||||||
|
class VCP : public ICommunication {
|
||||||
|
public:
|
||||||
|
static std::vector<neodevice_t> FindByProduct(int product, wchar_t* driverName);
|
||||||
|
static bool IsHandleValid(neodevice_handle_t handle);
|
||||||
|
typedef void(*fn_boolCallback)(bool success);
|
||||||
|
|
||||||
|
VCP(neodevice_t& forDevice) : device(forDevice) {
|
||||||
|
overlappedRead.hEvent = INVALID_HANDLE_VALUE;
|
||||||
|
overlappedWrite.hEvent = INVALID_HANDLE_VALUE;
|
||||||
|
overlappedWait.hEvent = INVALID_HANDLE_VALUE;
|
||||||
|
}
|
||||||
|
~VCP() { close(); }
|
||||||
|
bool open() { return open(false); }
|
||||||
|
void openAsync(fn_boolCallback callback);
|
||||||
|
bool close();
|
||||||
|
bool isOpen() { return handle != INVALID_HANDLE_VALUE; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool open(bool fromAsync);
|
||||||
|
bool opening = false;
|
||||||
|
neodevice_t& device;
|
||||||
|
HANDLE handle = INVALID_HANDLE_VALUE;
|
||||||
|
OVERLAPPED overlappedRead = {};
|
||||||
|
OVERLAPPED overlappedWrite = {};
|
||||||
|
OVERLAPPED overlappedWait = {};
|
||||||
|
std::vector<std::shared_ptr<std::thread>> threads;
|
||||||
|
void readTask();
|
||||||
|
void writeTask();
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
#include "platform/windows/include/registry.h"
|
||||||
|
#include <codecvt>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using namespace icsneo;
|
||||||
|
|
||||||
|
static std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
|
||||||
|
|
||||||
|
Registry::Key::Key(std::wstring path, bool readwrite) {
|
||||||
|
DWORD dwDisposition;
|
||||||
|
if(readwrite)
|
||||||
|
RegCreateKeyExW(HKEY_LOCAL_MACHINE, path.c_str(), 0, nullptr, 0, KEY_QUERY_VALUE | KEY_WRITE, nullptr, &key, &dwDisposition);
|
||||||
|
else
|
||||||
|
RegOpenKeyExW(HKEY_LOCAL_MACHINE, path.c_str(), 0, KEY_READ, &key);
|
||||||
|
}
|
||||||
|
|
||||||
|
Registry::Key::~Key() {
|
||||||
|
if(IsOpen())
|
||||||
|
RegCloseKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Registry::Get(std::wstring path, std::wstring key, std::wstring& value) {
|
||||||
|
Key regKey(path);
|
||||||
|
if(!regKey.IsOpen())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Query for the type and size of the data
|
||||||
|
DWORD type, size;
|
||||||
|
auto ret = RegQueryValueExW(regKey.GetKey(), key.c_str(), nullptr, &type, (LPBYTE)nullptr, &size);
|
||||||
|
if(ret != ERROR_SUCCESS)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Query for the data itself
|
||||||
|
std::vector<wchar_t> data(size / 2 + 1);
|
||||||
|
DWORD bytesRead = size; // We want to read up to the size we got earlier
|
||||||
|
ret = RegQueryValueExW(regKey.GetKey(), key.c_str(), nullptr, &type, (LPBYTE)data.data(), &bytesRead);
|
||||||
|
if(ret != ERROR_SUCCESS)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
value = data.data();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Registry::Get(std::string path, std::string key, std::string& value) {
|
||||||
|
std::wstring wvalue;
|
||||||
|
bool ret = Get(converter.from_bytes(path), converter.from_bytes(key), wvalue);
|
||||||
|
value = converter.to_bytes(wvalue);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Registry::Get(std::wstring path, std::wstring key, uint32_t& value) {
|
||||||
|
Key regKey(path);
|
||||||
|
if(!regKey.IsOpen())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Query for the data
|
||||||
|
DWORD type, size, kvalue;
|
||||||
|
auto ret = RegQueryValueExW(regKey.GetKey(), key.c_str(), nullptr, &type, (LPBYTE)&kvalue, &size);
|
||||||
|
if(ret != ERROR_SUCCESS || type != REG_DWORD)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
value = kvalue;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Registry::Get(std::string path, std::string key, uint32_t& value) {
|
||||||
|
return Get(converter.from_bytes(path), converter.from_bytes(key), value);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,329 @@
|
||||||
|
#include "platform/windows/include/ftdi.h"
|
||||||
|
#include "platform/include/ftdi.h"
|
||||||
|
#include "platform/include/registry.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <sstream>
|
||||||
|
#include <cwctype>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <codecvt>
|
||||||
|
#include <limits>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
using namespace icsneo;
|
||||||
|
|
||||||
|
static std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
|
||||||
|
static const std::wstring DRIVER_SERVICES_REG_KEY = L"SYSTEM\\CurrentControlSet\\services\\";
|
||||||
|
static const std::wstring ALL_ENUM_REG_KEY = L"SYSTEM\\CurrentControlSet\\Enum\\";
|
||||||
|
static constexpr unsigned int RETRY_TIMES = 5;
|
||||||
|
static constexpr unsigned int RETRY_DELAY = 50;
|
||||||
|
|
||||||
|
std::vector<neodevice_t> VCP::FindByProduct(int product, wchar_t* driverName) {
|
||||||
|
std::vector<neodevice_t> found;
|
||||||
|
|
||||||
|
std::wstringstream regss;
|
||||||
|
regss << DRIVER_SERVICES_REG_KEY << driverName << L"\\Enum\\";
|
||||||
|
std::wstring driverEnumRegKey = regss.str();
|
||||||
|
|
||||||
|
uint32_t deviceCount = 0;
|
||||||
|
if(!Registry::Get(driverEnumRegKey, L"Count", deviceCount)) {
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(uint32_t i = 0; i < deviceCount; i++) {
|
||||||
|
neodevice_t device = {};
|
||||||
|
|
||||||
|
// First we want to look at what devices FTDI is enumerating (inside driverEnumRegKey)
|
||||||
|
// The entry for a ValueCAN 3 with SN 138635 looks like "FTDIBUS\VID_093C+PID_0601+138635A\0000"
|
||||||
|
// The entry for a ValueCAN 4 with SN V20227 looks like "USB\VID_093C&PID_1101\V20227"
|
||||||
|
std::wstringstream ss;
|
||||||
|
ss << i;
|
||||||
|
std::wstring entry;
|
||||||
|
if(!Registry::Get(driverEnumRegKey, ss.str(), entry))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::transform(entry.begin(), entry.end(), entry.begin(), std::towupper);
|
||||||
|
|
||||||
|
std::wstringstream vss;
|
||||||
|
vss << "VID_" << std::setfill(L'0') << std::setw(4) << std::uppercase << std::hex << INTREPID_USB_VENDOR_ID; // Intrepid Vendor ID
|
||||||
|
if(entry.find(vss.str()) == std::wstring::npos)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::wstringstream pss;
|
||||||
|
pss << "PID_" << std::setfill(L'0') << std::setw(4) << std::uppercase << std::hex << product;
|
||||||
|
auto pidpos = entry.find(pss.str());
|
||||||
|
if(pidpos == std::wstring::npos)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Okay, this is a device we want
|
||||||
|
// Get the serial number
|
||||||
|
auto startchar = entry.find(L"+", pidpos + 1);
|
||||||
|
if(startchar == std::wstring::npos)
|
||||||
|
startchar = entry.find(L"\\", pidpos + 1);
|
||||||
|
bool conversionError = false;
|
||||||
|
int sn = 0;
|
||||||
|
try {
|
||||||
|
sn = std::stoi(entry.substr(startchar + 1));
|
||||||
|
} catch(...) {
|
||||||
|
conversionError = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstringstream oss;
|
||||||
|
if(!sn || conversionError) {
|
||||||
|
// This is a device with characters in the serial number
|
||||||
|
oss << entry.substr(startchar + 1, 6);
|
||||||
|
} else {
|
||||||
|
oss << sn;
|
||||||
|
}
|
||||||
|
|
||||||
|
strcpy_s(device.serial, sizeof(device.serial), converter.to_bytes(oss.str()).c_str());
|
||||||
|
|
||||||
|
// Serial number is saved, we want the COM port number now
|
||||||
|
// This will be stored under ALL_ENUM_REG_KEY\entry\Device Parameters\PortName (entry from the FTDI_ENUM)
|
||||||
|
std::wstringstream dpss;
|
||||||
|
dpss << ALL_ENUM_REG_KEY << entry << L"\\Device Parameters";
|
||||||
|
std::wstring port;
|
||||||
|
Registry::Get(dpss.str(), L"PortName", port); // TODO If error do something else (Plasma maybe?)
|
||||||
|
std::transform(port.begin(), port.end(), port.begin(), std::towupper);
|
||||||
|
auto compos = port.find(L"COM");
|
||||||
|
device.handle = 0;
|
||||||
|
if(compos != std::wstring::npos) {
|
||||||
|
try {
|
||||||
|
device.handle = std::stoi(port.substr(compos + 3));
|
||||||
|
} catch(...) {} // In case of this, or any other error, handle has already been initialized to 0
|
||||||
|
}
|
||||||
|
|
||||||
|
found.push_back(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VCP::IsHandleValid(neodevice_handle_t handle) {
|
||||||
|
if(handle < 1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(handle > 256) // Windows default max COM port is COM256
|
||||||
|
return false; // TODO Enumerate subkeys of HKLM\HARDWARE\DEVICEMAP\SERIALCOMM as a user might have more serial ports somehow
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VCP::open(bool fromAsync) {
|
||||||
|
if(isOpen() || (!fromAsync && opening))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(!IsHandleValid(device.handle))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
opening = true;
|
||||||
|
|
||||||
|
std::wstringstream comss;
|
||||||
|
comss << L"\\\\.\\COM" << device.handle;
|
||||||
|
|
||||||
|
// We're going to attempt to open 5 (RETRY_TIMES) times in a row
|
||||||
|
for(int i = 0; !isOpen() && i < RETRY_TIMES; i++) {
|
||||||
|
handle = CreateFileW(comss.str().c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr);
|
||||||
|
if(GetLastError() == ERROR_SUCCESS)
|
||||||
|
break; // We have the file handle
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(RETRY_DELAY));
|
||||||
|
}
|
||||||
|
|
||||||
|
opening = false;
|
||||||
|
|
||||||
|
if(!isOpen())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Set the timeouts
|
||||||
|
COMMTIMEOUTS timeouts;
|
||||||
|
if(!GetCommTimeouts(handle, &timeouts)) {
|
||||||
|
close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeouts.WriteTotalTimeoutConstant = 10000;
|
||||||
|
timeouts.WriteTotalTimeoutMultiplier = 0;
|
||||||
|
|
||||||
|
if(!SetCommTimeouts(handle, &timeouts)) {
|
||||||
|
close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the COM state
|
||||||
|
DCB comstate;
|
||||||
|
if(!GetCommState(handle, &comstate)) {
|
||||||
|
close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
comstate.BaudRate = 115200;
|
||||||
|
comstate.ByteSize = 8;
|
||||||
|
comstate.Parity = NOPARITY;
|
||||||
|
comstate.StopBits = 0;
|
||||||
|
comstate.fDtrControl = DTR_CONTROL_ENABLE;
|
||||||
|
comstate.fRtsControl = RTS_CONTROL_ENABLE;
|
||||||
|
|
||||||
|
if(!SetCommState(handle, &comstate)) {
|
||||||
|
close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PurgeComm(handle, PURGE_RXCLEAR);
|
||||||
|
|
||||||
|
// Set up events so that overlapped IO can work with them
|
||||||
|
overlappedRead.hEvent = CreateEvent(nullptr, false, false, nullptr);
|
||||||
|
overlappedWrite.hEvent = CreateEvent(nullptr, false, false, nullptr);
|
||||||
|
overlappedWait.hEvent = CreateEvent(nullptr, true, false, nullptr);
|
||||||
|
if (overlappedRead.hEvent == nullptr || overlappedWrite.hEvent == nullptr || overlappedWait.hEvent == nullptr) {
|
||||||
|
close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up event so that we will satisfy overlappedWait when a character comes in
|
||||||
|
if(!SetCommMask(handle, EV_RXCHAR)) {
|
||||||
|
close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Set up some sort of shared memory, save which COM port we have open so we don't try to open it again
|
||||||
|
|
||||||
|
// Create threads
|
||||||
|
readThread = std::thread(&VCP::readTask, this);
|
||||||
|
writeThread = std::thread(&VCP::writeTask, this);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VCP::openAsync(fn_boolCallback callback) {
|
||||||
|
threads.push_back(std::make_shared<std::thread>([&]() {
|
||||||
|
callback(open(true));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VCP::close() {
|
||||||
|
if(!isOpen())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
closing = true; // Signal the threads that we are closing
|
||||||
|
for(auto& t : threads)
|
||||||
|
t->join(); // Wait for the threads to close
|
||||||
|
readThread.join();
|
||||||
|
writeThread.join();
|
||||||
|
|
||||||
|
if(!CloseHandle(handle))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
handle = INVALID_HANDLE_VALUE;
|
||||||
|
|
||||||
|
bool ret = true; // If one of the events fails closing, we probably still want to try and close the others
|
||||||
|
if(overlappedRead.hEvent != INVALID_HANDLE_VALUE) {
|
||||||
|
if(!CloseHandle(overlappedRead.hEvent))
|
||||||
|
ret = false;
|
||||||
|
}
|
||||||
|
if(overlappedWrite.hEvent != INVALID_HANDLE_VALUE) {
|
||||||
|
if(!CloseHandle(overlappedWrite.hEvent))
|
||||||
|
ret = false;
|
||||||
|
}
|
||||||
|
if(overlappedWait.hEvent != INVALID_HANDLE_VALUE) {
|
||||||
|
if(!CloseHandle(overlappedWait.hEvent))
|
||||||
|
ret = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Set up some sort of shared memory, free which COM port we had open so we can try to open it again
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VCP::readTask() {
|
||||||
|
constexpr size_t READ_BUFFER_SIZE = 8;
|
||||||
|
uint8_t readbuf[READ_BUFFER_SIZE];
|
||||||
|
IOTaskState state = LAUNCH;
|
||||||
|
DWORD bytesRead = 0;
|
||||||
|
while(!closing) {
|
||||||
|
switch(state) {
|
||||||
|
case LAUNCH: {
|
||||||
|
COMSTAT comStatus;
|
||||||
|
unsigned long errorCodes;
|
||||||
|
if(!ClearCommError(handle, &errorCodes, &comStatus))
|
||||||
|
std::cout << "Error clearing com err" << std::endl;
|
||||||
|
|
||||||
|
bytesRead = 0;
|
||||||
|
if(ReadFile(handle, readbuf, READ_BUFFER_SIZE, nullptr, &overlappedRead)) {
|
||||||
|
if(GetOverlappedResult(handle, &overlappedRead, &bytesRead, FALSE)) {
|
||||||
|
if(bytesRead)
|
||||||
|
readQueue.enqueue_bulk(readbuf, bytesRead);
|
||||||
|
} else {
|
||||||
|
std::cout <<"Readfile succeeded but not enqueued " << GetLastError() << std::endl;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto err = GetLastError();
|
||||||
|
if(err == ERROR_SUCCESS)
|
||||||
|
std::cout << "Error was success?" << std::endl;
|
||||||
|
|
||||||
|
if(err == ERROR_IO_PENDING)
|
||||||
|
state = WAIT;
|
||||||
|
else
|
||||||
|
std::cout << "ReadFile failed " << err << std::endl;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case WAIT: {
|
||||||
|
auto ret = WaitForSingleObject(overlappedRead.hEvent, 100);
|
||||||
|
if(ret == WAIT_OBJECT_0) {
|
||||||
|
auto err = GetLastError();
|
||||||
|
if(GetOverlappedResult(handle, &overlappedRead, &bytesRead, FALSE)) {
|
||||||
|
readQueue.enqueue_bulk(readbuf, bytesRead);
|
||||||
|
state = LAUNCH;
|
||||||
|
} else
|
||||||
|
std::cout << "ReadFile deferred failed " << err << std::endl;
|
||||||
|
}
|
||||||
|
if(ret == WAIT_ABANDONED) {
|
||||||
|
state = LAUNCH;
|
||||||
|
std::cout << "Readfile abandoned" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VCP::writeTask() {
|
||||||
|
IOTaskState state = LAUNCH;
|
||||||
|
VCP::WriteOperation writeOp;
|
||||||
|
DWORD bytesWritten = 0;
|
||||||
|
while(!closing) {
|
||||||
|
switch(state) {
|
||||||
|
case LAUNCH: {
|
||||||
|
if(!writeQueue.wait_dequeue_timed(writeOp, std::chrono::milliseconds(100)))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
bytesWritten = 0;
|
||||||
|
if(WriteFile(handle, writeOp.bytes.data(), (DWORD)writeOp.bytes.size(), nullptr, &overlappedWrite))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto err = GetLastError();
|
||||||
|
if(err == ERROR_IO_PENDING) {
|
||||||
|
state = WAIT;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
std::cout << "Writefile failed " << err << std::endl;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case WAIT: {
|
||||||
|
auto ret = WaitForSingleObject(overlappedWrite.hEvent, 50);
|
||||||
|
if(ret == WAIT_OBJECT_0) {
|
||||||
|
if(!GetOverlappedResult(handle, &overlappedWrite, &bytesWritten, FALSE)) {
|
||||||
|
std::cout << "Writefile deferred failed " << GetLastError() << std::endl;
|
||||||
|
}
|
||||||
|
state = LAUNCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ret == WAIT_ABANDONED) {
|
||||||
|
std::cout << "Writefile deferred abandoned" << std::endl;
|
||||||
|
state = LAUNCH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
*.ipch
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.sdf
|
||||||
|
*.opensdf
|
||||||
|
*.exe
|
||||||
|
*.pdb
|
||||||
|
*.vs
|
||||||
|
*.VC.db
|
||||||
|
build/bin/
|
||||||
|
build/*.log
|
||||||
|
build/msvc14/*.log
|
||||||
|
build/msvc14/obj/
|
||||||
|
build/msvc12/*.log
|
||||||
|
build/msvc12/obj/
|
||||||
|
build/msvc11/*.log
|
||||||
|
build/msvc11/obj/
|
||||||
|
build/xcode/build/
|
||||||
|
tests/fuzztests/fuzztests.log
|
||||||
|
benchmarks/benchmarks.log
|
||||||
|
tests/CDSChecker/*.o
|
||||||
|
tests/CDSChecker/*.log
|
||||||
|
tests/CDSChecker/model-checker/
|
||||||
|
tests/relacy/freelist.exe
|
||||||
|
tests/relacy/spmchash.exe
|
||||||
|
tests/relacy/log.txt
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
This license file applies to everything in this repository except that which
|
||||||
|
is explicitly annotated as being written by other authors, i.e. the Boost
|
||||||
|
queue (included in the benchmarks for comparison), Intel's TBB library (ditto),
|
||||||
|
the CDSChecker tool (used for verification), the Relacy model checker (ditto),
|
||||||
|
and Jeff Preshing's semaphore implementation (used in the blocking queue) which
|
||||||
|
has a zlib license (embedded in blockingconcurrentqueue.h).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Simplified BSD License:
|
||||||
|
|
||||||
|
Copyright (c) 2013-2016, Cameron Desrochers.
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
- Redistributions of source code must retain the above copyright notice, this list of
|
||||||
|
conditions and the following disclaimer.
|
||||||
|
- Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||||
|
conditions and the following disclaimer in the documentation and/or other materials
|
||||||
|
provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||||
|
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||||
|
THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
||||||
|
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||||||
|
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||||
|
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
I have also chosen to dual-license under the Boost Software License as an alternative to
|
||||||
|
the Simplified BSD license above:
|
||||||
|
|
||||||
|
Boost Software License - Version 1.0 - August 17th, 2003
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person or organization
|
||||||
|
obtaining a copy of the software and accompanying documentation covered by
|
||||||
|
this license (the "Software") to use, reproduce, display, distribute,
|
||||||
|
execute, and transmit the Software, and to prepare derivative works of the
|
||||||
|
Software, and to permit third-parties to whom the Software is furnished to
|
||||||
|
do so, all subject to the following:
|
||||||
|
|
||||||
|
The copyright notices in the Software and this entire statement, including
|
||||||
|
the above license grant, this restriction and the following disclaimer,
|
||||||
|
must be included in all copies of the Software, in whole or in part, and
|
||||||
|
all derivative works of the Software, unless such copies or derivative
|
||||||
|
works are solely in the form of machine-executable object code generated by
|
||||||
|
a source language processor.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||||
|
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||||
|
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
|
|
@ -0,0 +1,486 @@
|
||||||
|
# moodycamel::ConcurrentQueue<T>
|
||||||
|
|
||||||
|
An industrial-strength lock-free queue for C++.
|
||||||
|
|
||||||
|
Note: If all you need is a single-producer, single-consumer queue, I have [one of those too][spsc].
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Knock-your-socks-off [blazing fast performance][benchmarks].
|
||||||
|
- Single-header implementation. Just drop it in your project.
|
||||||
|
- Fully thread-safe lock-free queue. Use concurrently from any number of threads.
|
||||||
|
- C++11 implementation -- elements are moved (instead of copied) where possible.
|
||||||
|
- Templated, obviating the need to deal exclusively with pointers -- memory is managed for you.
|
||||||
|
- No artificial limitations on element types or maximum count.
|
||||||
|
- Memory can be allocated once up-front, or dynamically as needed.
|
||||||
|
- Fully portable (no assembly; all is done through standard C++11 primitives).
|
||||||
|
- Supports super-fast bulk operations.
|
||||||
|
- Includes a low-overhead blocking version (BlockingConcurrentQueue).
|
||||||
|
- Exception safe.
|
||||||
|
|
||||||
|
## Reasons to use
|
||||||
|
|
||||||
|
There are not that many full-fledged lock-free queues for C++. Boost has one, but it's limited to objects with trivial
|
||||||
|
assignment operators and trivial destructors, for example.
|
||||||
|
Intel's TBB queue isn't lock-free, and requires trivial constructors too.
|
||||||
|
There're many academic papers that implement lock-free queues in C++, but usable source code is
|
||||||
|
hard to find, and tests even more so.
|
||||||
|
|
||||||
|
This queue not only has less limitations than others (for the most part), but [it's also faster][benchmarks].
|
||||||
|
It's been fairly well-tested, and offers advanced features like **bulk enqueueing/dequeueing**
|
||||||
|
(which, with my new design, is much faster than one element at a time, approaching and even surpassing
|
||||||
|
the speed of a non-concurrent queue even under heavy contention).
|
||||||
|
|
||||||
|
In short, there was a lock-free queue shaped hole in the C++ open-source universe, and I set out
|
||||||
|
to fill it with the fastest, most complete, and well-tested design and implementation I could.
|
||||||
|
The result is `moodycamel::ConcurrentQueue` :-)
|
||||||
|
|
||||||
|
## Reasons *not* to use
|
||||||
|
|
||||||
|
The fastest synchronization of all is the kind that never takes place. Fundamentally,
|
||||||
|
concurrent data structures require some synchronization, and that takes time. Every effort
|
||||||
|
was made, of course, to minimize the overhead, but if you can avoid sharing data between
|
||||||
|
threads, do so!
|
||||||
|
|
||||||
|
Why use concurrent data structures at all, then? Because they're gosh darn convenient! (And, indeed,
|
||||||
|
sometimes sharing data concurrently is unavoidable.)
|
||||||
|
|
||||||
|
My queue is **not linearizable** (see the next section on high-level design). The foundations of
|
||||||
|
its design assume that producers are independent; if this is not the case, and your producers
|
||||||
|
co-ordinate amongst themselves in some fashion, be aware that the elements won't necessarily
|
||||||
|
come out of the queue in the same order they were put in *relative to the ordering formed by that co-ordination*
|
||||||
|
(but they will still come out in the order they were put in by any *individual* producer). If this affects
|
||||||
|
your use case, you may be better off with another implementation; either way, it's an important limitation
|
||||||
|
to be aware of.
|
||||||
|
|
||||||
|
My queue is also **not NUMA aware**, and does a lot of memory re-use internally, meaning it probably doesn't
|
||||||
|
scale particularly well on NUMA architectures; however, I don't know of any other lock-free queue that *is*
|
||||||
|
NUMA aware (except for [SALSA][salsa], which is very cool, but has no publicly available implementation that I know of).
|
||||||
|
|
||||||
|
Finally, the queue is **not sequentially consistent**; there *is* a happens-before relationship between when an element is put
|
||||||
|
in the queue and when it comes out, but other things (such as pumping the queue until it's empty) require more thought
|
||||||
|
to get right in all eventualities, because explicit memory ordering may have to be done to get the desired effect. In other words,
|
||||||
|
it can sometimes be difficult to use the queue correctly. This is why it's a good idea to follow the [samples][samples.md] where possible.
|
||||||
|
On the other hand, the upside of this lack of sequential consistency is better performance.
|
||||||
|
|
||||||
|
## High-level design
|
||||||
|
|
||||||
|
Elements are stored internally using contiguous blocks instead of linked lists for better performance.
|
||||||
|
The queue is made up of a collection of sub-queues, one for each producer. When a consumer
|
||||||
|
wants to dequeue an element, it checks all the sub-queues until it finds one that's not empty.
|
||||||
|
All of this is largely transparent to the user of the queue, however -- it mostly just works<sup>TM</sup>.
|
||||||
|
|
||||||
|
One particular consequence of this design, however, (which seems to be non-intuitive) is that if two producers
|
||||||
|
enqueue at the same time, there is no defined ordering between the elements when they're later dequeued.
|
||||||
|
Normally this is fine, because even with a fully linearizable queue there'd be a race between the producer
|
||||||
|
threads and so you couldn't rely on the ordering anyway. However, if for some reason you do extra explicit synchronization
|
||||||
|
between the two producer threads yourself, thus defining a total order between enqueue operations, you might expect
|
||||||
|
that the elements would come out in the same total order, which is a guarantee my queue does not offer. At that
|
||||||
|
point, though, there semantically aren't really two separate producers, but rather one that happens to be spread
|
||||||
|
across multiple threads. In this case, you can still establish a total ordering with my queue by creating
|
||||||
|
a single producer token, and using that from both threads to enqueue (taking care to synchronize access to the token,
|
||||||
|
of course, but there was already extra synchronization involved anyway).
|
||||||
|
|
||||||
|
I've written a more detailed [overview of the internal design][blog], as well as [the full
|
||||||
|
nitty-gritty details of the design][design], on my blog. Finally, the
|
||||||
|
[source][source] itself is available for perusal for those interested in its implementation.
|
||||||
|
|
||||||
|
## Basic use
|
||||||
|
|
||||||
|
The entire queue's implementation is contained in **one header**, [`concurrentqueue.h`][concurrentqueue.h].
|
||||||
|
Simply download and include that to use the queue. The blocking version is in a separate header,
|
||||||
|
[`blockingconcurrentqueue.h`][blockingconcurrentqueue.h], that depends on the first.
|
||||||
|
The implementation makes use of certain key C++11 features, so it requires a fairly recent compiler
|
||||||
|
(e.g. VS2012+ or g++ 4.8; note that g++ 4.6 has a known bug with `std::atomic` and is thus not supported).
|
||||||
|
The algorithm implementations themselves are platform independent.
|
||||||
|
|
||||||
|
Use it like you would any other templated queue, with the exception that you can use
|
||||||
|
it from many threads at once :-)
|
||||||
|
|
||||||
|
Simple example:
|
||||||
|
|
||||||
|
#include "concurrentqueue.h"
|
||||||
|
|
||||||
|
moodycamel::ConcurrentQueue<int> q;
|
||||||
|
q.enqueue(25);
|
||||||
|
|
||||||
|
int item;
|
||||||
|
bool found = q.try_dequeue(item);
|
||||||
|
assert(found && item == 25);
|
||||||
|
|
||||||
|
Description of basic methods:
|
||||||
|
- `ConcurrentQueue(size_t initialSizeEstimate)`
|
||||||
|
Constructor which optionally accepts an estimate of the number of elements the queue will hold
|
||||||
|
- `enqueue(T&& item)`
|
||||||
|
Enqueues one item, allocating extra space if necessary
|
||||||
|
- `try_enqueue(T&& item)`
|
||||||
|
Enqueues one item, but only if enough memory is already allocated
|
||||||
|
- `try_dequeue(T& item)`
|
||||||
|
Dequeues one item, returning true if an item was found or false if the queue appeared empty
|
||||||
|
|
||||||
|
Note that it is up to the user to ensure that the queue object is completely constructed before
|
||||||
|
being used by any other threads (this includes making the memory effects of construction
|
||||||
|
visible, possibly via a memory barrier). Similarly, it's important that all threads have
|
||||||
|
finished using the queue (and the memory effects have fully propagated) before it is
|
||||||
|
destructed.
|
||||||
|
|
||||||
|
There's usually two versions of each method, one "explicit" version that takes a user-allocated per-producer or
|
||||||
|
per-consumer token, and one "implicit" version that works without tokens. Using the explicit methods is almost
|
||||||
|
always faster (though not necessarily by a huge factor). Apart from performance, the primary distinction between them
|
||||||
|
is their sub-queue allocation behaviour for enqueue operations: Using the implicit enqueue methods causes an
|
||||||
|
automatically-allocated thread-local producer sub-queue to be allocated (it is marked for reuse once the thread exits).
|
||||||
|
Explicit producers, on the other hand, are tied directly to their tokens' lifetimes (and are also recycled as needed).
|
||||||
|
|
||||||
|
Full API (pseudocode):
|
||||||
|
|
||||||
|
# Allocates more memory if necessary
|
||||||
|
enqueue(item) : bool
|
||||||
|
enqueue(prod_token, item) : bool
|
||||||
|
enqueue_bulk(item_first, count) : bool
|
||||||
|
enqueue_bulk(prod_token, item_first, count) : bool
|
||||||
|
|
||||||
|
# Fails if not enough memory to enqueue
|
||||||
|
try_enqueue(item) : bool
|
||||||
|
try_enqueue(prod_token, item) : bool
|
||||||
|
try_enqueue_bulk(item_first, count) : bool
|
||||||
|
try_enqueue_bulk(prod_token, item_first, count) : bool
|
||||||
|
|
||||||
|
# Attempts to dequeue from the queue (never allocates)
|
||||||
|
try_dequeue(item&) : bool
|
||||||
|
try_dequeue(cons_token, item&) : bool
|
||||||
|
try_dequeue_bulk(item_first, max) : size_t
|
||||||
|
try_dequeue_bulk(cons_token, item_first, max) : size_t
|
||||||
|
|
||||||
|
# If you happen to know which producer you want to dequeue from
|
||||||
|
try_dequeue_from_producer(prod_token, item&) : bool
|
||||||
|
try_dequeue_bulk_from_producer(prod_token, item_first, max) : size_t
|
||||||
|
|
||||||
|
# A not-necessarily-accurate count of the total number of elements
|
||||||
|
size_approx() : size_t
|
||||||
|
|
||||||
|
## Blocking version
|
||||||
|
|
||||||
|
As mentioned above, a full blocking wrapper of the queue is provided that adds
|
||||||
|
`wait_dequeue` and `wait_dequeue_bulk` methods in addition to the regular interface.
|
||||||
|
This wrapper is extremely low-overhead, but slightly less fast than the non-blocking
|
||||||
|
queue (due to the necessary bookkeeping involving a lightweight semaphore).
|
||||||
|
|
||||||
|
There are also timed versions that allow a timeout to be specified (either in microseconds
|
||||||
|
or with a `std::chrono` object).
|
||||||
|
|
||||||
|
The only major caveat with the blocking version is that you must be careful not to
|
||||||
|
destroy the queue while somebody is waiting on it. This generally means you need to
|
||||||
|
know for certain that another element is going to come along before you call one of
|
||||||
|
the blocking methods. (To be fair, the non-blocking version cannot be destroyed while
|
||||||
|
in use either, but it can be easier to coordinate the cleanup.)
|
||||||
|
|
||||||
|
Blocking example:
|
||||||
|
|
||||||
|
#include "blockingconcurrentqueue.h"
|
||||||
|
|
||||||
|
moodycamel::BlockingConcurrentQueue<int> q;
|
||||||
|
std::thread producer([&]() {
|
||||||
|
for (int i = 0; i != 100; ++i) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(i % 10));
|
||||||
|
q.enqueue(i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
std::thread consumer([&]() {
|
||||||
|
for (int i = 0; i != 100; ++i) {
|
||||||
|
int item;
|
||||||
|
q.wait_dequeue(item);
|
||||||
|
assert(item == i);
|
||||||
|
|
||||||
|
if (q.wait_dequeue_timed(item, std::chrono::milliseconds(5))) {
|
||||||
|
++i;
|
||||||
|
assert(item == i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
producer.join();
|
||||||
|
consumer.join();
|
||||||
|
|
||||||
|
assert(q.size_approx() == 0);
|
||||||
|
|
||||||
|
## Advanced features
|
||||||
|
|
||||||
|
#### Tokens
|
||||||
|
|
||||||
|
The queue can take advantage of extra per-producer and per-consumer storage if
|
||||||
|
it's available to speed up its operations. This takes the form of "tokens":
|
||||||
|
You can create a consumer token and/or a producer token for each thread or task
|
||||||
|
(tokens themselves are not thread-safe), and use the methods that accept a token
|
||||||
|
as their first parameter:
|
||||||
|
|
||||||
|
moodycamel::ConcurrentQueue<int> q;
|
||||||
|
|
||||||
|
moodycamel::ProducerToken ptok(q);
|
||||||
|
q.enqueue(ptok, 17);
|
||||||
|
|
||||||
|
moodycamel::ConsumerToken ctok(q);
|
||||||
|
int item;
|
||||||
|
q.try_dequeue(ctok, item);
|
||||||
|
assert(item == 17);
|
||||||
|
|
||||||
|
If you happen to know which producer you want to consume from (e.g. in
|
||||||
|
a single-producer, multi-consumer scenario), you can use the `try_dequeue_from_producer`
|
||||||
|
methods, which accept a producer token instead of a consumer token, and cut some overhead.
|
||||||
|
|
||||||
|
Note that tokens work with the blocking version of the queue too.
|
||||||
|
|
||||||
|
When producing or consuming many elements, the most efficient way is to:
|
||||||
|
|
||||||
|
1. Use the bulk methods of the queue with tokens
|
||||||
|
2. Failing that, use the bulk methods without tokens
|
||||||
|
3. Failing that, use the single-item methods with tokens
|
||||||
|
4. Failing that, use the single-item methods without tokens
|
||||||
|
|
||||||
|
Having said that, don't create tokens willy-nilly -- ideally there would be
|
||||||
|
one token (of each kind) per thread. The queue will work with what it is
|
||||||
|
given, but it performs best when used with tokens.
|
||||||
|
|
||||||
|
Note that tokens aren't actually tied to any given thread; it's not technically
|
||||||
|
required that they be local to the thread, only that they be used by a single
|
||||||
|
producer/consumer at a time.
|
||||||
|
|
||||||
|
#### Bulk operations
|
||||||
|
|
||||||
|
Thanks to the [novel design][blog] of the queue, it's just as easy to enqueue/dequeue multiple
|
||||||
|
items as it is to do one at a time. This means that overhead can be cut drastically for
|
||||||
|
bulk operations. Example syntax:
|
||||||
|
|
||||||
|
moodycamel::ConcurrentQueue<int> q;
|
||||||
|
|
||||||
|
int items[] = { 1, 2, 3, 4, 5 };
|
||||||
|
q.enqueue_bulk(items, 5);
|
||||||
|
|
||||||
|
int results[5]; // Could also be any iterator
|
||||||
|
size_t count = q.try_dequeue_bulk(results, 5);
|
||||||
|
for (size_t i = 0; i != count; ++i) {
|
||||||
|
assert(results[i] == items[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#### Preallocation (correctly using `try_enqueue`)
|
||||||
|
|
||||||
|
`try_enqueue`, unlike just plain `enqueue`, will never allocate memory. If there's not enough room in the
|
||||||
|
queue, it simply returns false. The key to using this method properly, then, is to ensure enough space is
|
||||||
|
pre-allocated for your desired maximum element count.
|
||||||
|
|
||||||
|
The constructor accepts a count of the number of elements that it should reserve space for. Because the
|
||||||
|
queue works with blocks of elements, however, and not individual elements themselves, the value to pass
|
||||||
|
in order to obtain an effective number of pre-allocated element slots is non-obvious.
|
||||||
|
|
||||||
|
First, be aware that the count passed is rounded up to the next multiple of the block size. Note that the
|
||||||
|
default block size is 32 (this can be changed via the traits). Second, once a slot in a block has been
|
||||||
|
enqueued to, that slot cannot be re-used until the rest of the block has completely been completely filled
|
||||||
|
up and then completely emptied. This affects the number of blocks you need in order to account for the
|
||||||
|
overhead of partially-filled blocks. Third, each producer (whether implicit or explicit) claims and recycles
|
||||||
|
blocks in a different manner, which again affects the number of blocks you need to account for a desired number of
|
||||||
|
usable slots.
|
||||||
|
|
||||||
|
Suppose you want the queue to be able to hold at least `N` elements at any given time. Without delving too
|
||||||
|
deep into the rather arcane implementation details, here are some simple formulas for the number of elements
|
||||||
|
to request for pre-allocation in such a case. Note the division is intended to be arithmetic division and not
|
||||||
|
integer division (in order for `ceil()` to work).
|
||||||
|
|
||||||
|
For explicit producers (using tokens to enqueue):
|
||||||
|
|
||||||
|
(ceil(N / BLOCK_SIZE) + 1) * MAX_NUM_PRODUCERS * BLOCK_SIZE
|
||||||
|
|
||||||
|
For implicit producers (no tokens):
|
||||||
|
|
||||||
|
(ceil(N / BLOCK_SIZE) - 1 + 2 * MAX_NUM_PRODUCERS) * BLOCK_SIZE
|
||||||
|
|
||||||
|
When using mixed producer types:
|
||||||
|
|
||||||
|
((ceil(N / BLOCK_SIZE) - 1) * (MAX_EXPLICIT_PRODUCERS + 1) + 2 * (MAX_IMPLICIT_PRODUCERS + MAX_EXPLICIT_PRODUCERS)) * BLOCK_SIZE
|
||||||
|
|
||||||
|
If these formulas seem rather inconvenient, you can use the constructor overload that accepts the minimum
|
||||||
|
number of elements (`N`) and the maximum number of explicit and implicit producers directly, and let it do the
|
||||||
|
computation for you.
|
||||||
|
|
||||||
|
Finally, it's important to note that because the queue is only eventually consistent and takes advantage of
|
||||||
|
weak memory ordering for speed, there's always a possibility that under contention `try_enqueue` will fail
|
||||||
|
even if the queue is correctly pre-sized for the desired number of elements. (e.g. A given thread may think that
|
||||||
|
the queue's full even when that's no longer the case.) So no matter what, you still need to handle the failure
|
||||||
|
case (perhaps looping until it succeeds), unless you don't mind dropping elements.
|
||||||
|
|
||||||
|
#### Exception safety
|
||||||
|
|
||||||
|
The queue is exception safe, and will never become corrupted if used with a type that may throw exceptions.
|
||||||
|
The queue itself never throws any exceptions (operations fail gracefully (return false) if memory allocation
|
||||||
|
fails instead of throwing `std::bad_alloc`).
|
||||||
|
|
||||||
|
It is important to note that the guarantees of exception safety only hold if the element type never throws
|
||||||
|
from its destructor, and that any iterators passed into the queue (for bulk operations) never throw either.
|
||||||
|
Note that in particular this means `std::back_inserter` iterators must be used with care, since the vector
|
||||||
|
being inserted into may need to allocate and throw a `std::bad_alloc` exception from inside the iterator;
|
||||||
|
so be sure to reserve enough capacity in the target container first if you do this.
|
||||||
|
|
||||||
|
The guarantees are presently as follows:
|
||||||
|
- Enqueue operations are rolled back completely if an exception is thrown from an element's constructor.
|
||||||
|
For bulk enqueue operations, this means that elements are copied instead of moved (in order to avoid
|
||||||
|
having only some of the objects be moved in the event of an exception). Non-bulk enqueues always use
|
||||||
|
the move constructor if one is available.
|
||||||
|
- If the assignment operator throws during a dequeue operation (both single and bulk), the element(s) are
|
||||||
|
considered dequeued regardless. In such a case, the dequeued elements are all properly destructed before
|
||||||
|
the exception is propagated, but there's no way to get the elements themselves back.
|
||||||
|
- Any exception that is thrown is propagated up the call stack, at which point the queue is in a consistent
|
||||||
|
state.
|
||||||
|
|
||||||
|
Note: If any of your type's copy constructors/move constructors/assignment operators don't throw, be sure
|
||||||
|
to annotate them with `noexcept`; this will avoid the exception-checking overhead in the queue where possible
|
||||||
|
(even with zero-cost exceptions, there's still a code size impact that has to be taken into account).
|
||||||
|
|
||||||
|
#### Traits
|
||||||
|
|
||||||
|
The queue also supports a traits template argument which defines various types, constants,
|
||||||
|
and the memory allocation and deallocation functions that are to be used by the queue. The typical pattern
|
||||||
|
to providing your own traits is to create a class that inherits from the default traits
|
||||||
|
and override only the values you wish to change. Example:
|
||||||
|
|
||||||
|
struct MyTraits : public moodycamel::ConcurrentQueueDefaultTraits
|
||||||
|
{
|
||||||
|
static const size_t BLOCK_SIZE = 256; // Use bigger blocks
|
||||||
|
};
|
||||||
|
|
||||||
|
moodycamel::ConcurrentQueue<int, MyTraits> q;
|
||||||
|
|
||||||
|
#### How to dequeue types without calling the constructor
|
||||||
|
|
||||||
|
The normal way to dequeue an item is to pass in an existing object by reference, which
|
||||||
|
is then assigned to internally by the queue (using the move-assignment operator if possible).
|
||||||
|
This can pose a problem for types that are
|
||||||
|
expensive to construct or don't have a default constructor; fortunately, there is a simple
|
||||||
|
workaround: Create a wrapper class that copies the memory contents of the object when it
|
||||||
|
is assigned by the queue (a poor man's move, essentially). Note that this only works if
|
||||||
|
the object contains no internal pointers. Example:
|
||||||
|
|
||||||
|
struct MyObjectMover {
|
||||||
|
inline void operator=(MyObject&& obj)
|
||||||
|
{
|
||||||
|
std::memcpy(data, &obj, sizeof(MyObject));
|
||||||
|
|
||||||
|
// TODO: Cleanup obj so that when it's destructed by the queue
|
||||||
|
// it doesn't corrupt the data of the object we just moved it into
|
||||||
|
}
|
||||||
|
|
||||||
|
inline MyObject& obj() { return *reinterpret_cast<MyObject*>(data); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
align(alignof(MyObject)) char data[sizeof(MyObject)];
|
||||||
|
};
|
||||||
|
|
||||||
|
A less dodgy alternative, if moves are cheap but default construction is not, is to use a
|
||||||
|
wrapper that defers construction until the object is assigned, enabling use of the move
|
||||||
|
constructor:
|
||||||
|
|
||||||
|
struct MyObjectMover {
|
||||||
|
inline void operator=(MyObject&& x) {
|
||||||
|
new (data) MyObject(std::move(x));
|
||||||
|
created = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline MyObject& obj() {
|
||||||
|
assert(created);
|
||||||
|
return *reinterpret_cast<MyObject*>(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
~MyObjectMover() {
|
||||||
|
if (created)
|
||||||
|
obj().~MyObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
align(alignof(MyObject)) char data[sizeof(MyObject)];
|
||||||
|
bool created = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
## Samples
|
||||||
|
|
||||||
|
There are some more detailed samples [here][samples.md]. The source of
|
||||||
|
the [unit tests][unittest-src] and [benchmarks][benchmark-src] are available for reference as well.
|
||||||
|
|
||||||
|
## Benchmarks
|
||||||
|
|
||||||
|
See my blog post for some [benchmark results][benchmarks] (including versus `boost::lockfree::queue` and `tbb::concurrent_queue`),
|
||||||
|
or run the benchmarks yourself (requires MinGW and certain GnuWin32 utilities to build on Windows, or a recent
|
||||||
|
g++ on Linux):
|
||||||
|
|
||||||
|
cd build
|
||||||
|
make benchmarks
|
||||||
|
bin/benchmarks
|
||||||
|
|
||||||
|
The short version of the benchmarks is that it's so fast (especially the bulk methods), that if you're actually
|
||||||
|
using the queue to *do* anything, the queue won't be your bottleneck.
|
||||||
|
|
||||||
|
## Tests (and bugs)
|
||||||
|
|
||||||
|
I've written quite a few unit tests as well as a randomized long-running fuzz tester. I also ran the
|
||||||
|
core queue algorithm through the [CDSChecker][cdschecker] C++11 memory model model checker. Some of the
|
||||||
|
inner algorithms were tested separately using the [Relacy][relacy] model checker, and full integration
|
||||||
|
tests were also performed with Relacy.
|
||||||
|
I've tested
|
||||||
|
on Linux (Fedora 19) and Windows (7), but only on x86 processors so far (Intel and AMD). The code was
|
||||||
|
written to be platform-independent, however, and should work across all processors and OSes.
|
||||||
|
|
||||||
|
Due to the complexity of the implementation and the difficult-to-test nature of lock-free code in general,
|
||||||
|
there may still be bugs. If anyone is seeing buggy behaviour, I'd like to hear about it! (Especially if
|
||||||
|
a unit test for it can be cooked up.) Just open an issue on GitHub.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
I'm releasing the source of this repository (with the exception of third-party code, i.e. the Boost queue
|
||||||
|
(used in the benchmarks for comparison), Intel's TBB library (ditto), CDSChecker, Relacy, and Jeff Preshing's
|
||||||
|
cross-platform semaphore, which all have their own licenses)
|
||||||
|
under a simplified BSD license. I'm also dual-licensing under the Boost Software License.
|
||||||
|
See the [LICENSE.md][license] file for more details.
|
||||||
|
|
||||||
|
Note that lock-free programming is a patent minefield, and this code may very
|
||||||
|
well violate a pending patent (I haven't looked), though it does not to my present knowledge.
|
||||||
|
I did design and implement this queue from scratch.
|
||||||
|
|
||||||
|
## Diving into the code
|
||||||
|
|
||||||
|
If you're interested in the source code itself, it helps to have a rough idea of how it's laid out. This
|
||||||
|
section attempts to describe that.
|
||||||
|
|
||||||
|
The queue is formed of several basic parts (listed here in roughly the order they appear in the source). There's the
|
||||||
|
helper functions (e.g. for rounding to a power of 2). There's the default traits of the queue, which contain the
|
||||||
|
constants and malloc/free functions used by the queue. There's the producer and consumer tokens. Then there's the queue's
|
||||||
|
public API itself, starting with the constructor, destructor, and swap/assignment methods. There's the public enqueue methods,
|
||||||
|
which are all wrappers around a small set of private enqueue methods found later on. There's the dequeue methods, which are
|
||||||
|
defined inline and are relatively straightforward.
|
||||||
|
|
||||||
|
Then there's all the main internal data structures. First, there's a lock-free free list, used for recycling spent blocks (elements
|
||||||
|
are enqueued to blocks internally). Then there's the block structure itself, which has two different ways of tracking whether
|
||||||
|
it's fully emptied or not (remember, given two parallel consumers, there's no way to know which one will finish first) depending on where it's used.
|
||||||
|
Then there's a small base class for the two types of internal SPMC producer queues (one for explicit producers that holds onto memory
|
||||||
|
but attempts to be faster, and one for implicit ones which attempt to recycle more memory back into the parent but is a little slower).
|
||||||
|
The explicit producer is defined first, then the implicit one. They both contain the same general four methods: One to enqueue, one to
|
||||||
|
dequeue, one to enqueue in bulk, and one to dequeue in bulk. (Obviously they have constructors and destructors too, and helper methods.)
|
||||||
|
The main difference between them is how the block handling is done (they both use the same blocks, but in different ways, and map indices
|
||||||
|
to them in different ways).
|
||||||
|
|
||||||
|
Finally, there's the miscellaneous internal methods: There's the ones that handle the initial block pool (populated when the queue is constructed),
|
||||||
|
and an abstract block pool that comprises the initial pool and any blocks on the free list. There's ones that handle the producer list
|
||||||
|
(a lock-free add-only linked list of all the producers in the system). There's ones that handle the implicit producer lookup table (which
|
||||||
|
is really a sort of specialized TLS lookup). And then there's some helper methods for allocating and freeing objects, and the data members
|
||||||
|
of the queue itself, followed lastly by the free-standing swap functions.
|
||||||
|
|
||||||
|
|
||||||
|
[blog]: http://moodycamel.com/blog/2014/a-fast-general-purpose-lock-free-queue-for-c++
|
||||||
|
[design]: http://moodycamel.com/blog/2014/detailed-design-of-a-lock-free-queue
|
||||||
|
[samples.md]: https://github.com/cameron314/concurrentqueue/blob/master/samples.md
|
||||||
|
[source]: https://github.com/cameron314/concurrentqueue
|
||||||
|
[concurrentqueue.h]: https://github.com/cameron314/concurrentqueue/blob/master/concurrentqueue.h
|
||||||
|
[blockingconcurrentqueue.h]: https://github.com/cameron314/concurrentqueue/blob/master/blockingconcurrentqueue.h
|
||||||
|
[unittest-src]: https://github.com/cameron314/concurrentqueue/tree/master/tests/unittests
|
||||||
|
[benchmarks]: http://moodycamel.com/blog/2014/a-fast-general-purpose-lock-free-queue-for-c++#benchmarks
|
||||||
|
[benchmark-src]: https://github.com/cameron314/concurrentqueue/tree/master/benchmarks
|
||||||
|
[license]: https://github.com/cameron314/concurrentqueue/blob/master/LICENSE.md
|
||||||
|
[cdschecker]: http://demsky.eecs.uci.edu/c11modelchecker.html
|
||||||
|
[relacy]: http://www.1024cores.net/home/relacy-race-detector
|
||||||
|
[spsc]: https://github.com/cameron314/readerwriterqueue
|
||||||
|
[salsa]: http://webee.technion.ac.il/~idish/ftp/spaa049-gidron.pdf
|
||||||
|
|
@ -0,0 +1,981 @@
|
||||||
|
// Provides an efficient blocking version of moodycamel::ConcurrentQueue.
|
||||||
|
// ©2015-2016 Cameron Desrochers. Distributed under the terms of the simplified
|
||||||
|
// BSD license, available at the top of concurrentqueue.h.
|
||||||
|
// Uses Jeff Preshing's semaphore implementation (under the terms of its
|
||||||
|
// separate zlib license, embedded below).
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "concurrentqueue.h"
|
||||||
|
#include <type_traits>
|
||||||
|
#include <cerrno>
|
||||||
|
#include <memory>
|
||||||
|
#include <chrono>
|
||||||
|
#include <ctime>
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
// Avoid including windows.h in a header; we only need a handful of
|
||||||
|
// items, so we'll redeclare them here (this is relatively safe since
|
||||||
|
// the API generally has to remain stable between Windows versions).
|
||||||
|
// I know this is an ugly hack but it still beats polluting the global
|
||||||
|
// namespace with thousands of generic names or adding a .cpp for nothing.
|
||||||
|
extern "C" {
|
||||||
|
struct _SECURITY_ATTRIBUTES;
|
||||||
|
__declspec(dllimport) void* __stdcall CreateSemaphoreW(_SECURITY_ATTRIBUTES* lpSemaphoreAttributes, long lInitialCount, long lMaximumCount, const wchar_t* lpName);
|
||||||
|
__declspec(dllimport) int __stdcall CloseHandle(void* hObject);
|
||||||
|
__declspec(dllimport) unsigned long __stdcall WaitForSingleObject(void* hHandle, unsigned long dwMilliseconds);
|
||||||
|
__declspec(dllimport) int __stdcall ReleaseSemaphore(void* hSemaphore, long lReleaseCount, long* lpPreviousCount);
|
||||||
|
}
|
||||||
|
#elif defined(__MACH__)
|
||||||
|
#include <mach/mach.h>
|
||||||
|
#elif defined(__unix__)
|
||||||
|
#include <semaphore.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace moodycamel
|
||||||
|
{
|
||||||
|
namespace details
|
||||||
|
{
|
||||||
|
// Code in the mpmc_sema namespace below is an adaptation of Jeff Preshing's
|
||||||
|
// portable + lightweight semaphore implementations, originally from
|
||||||
|
// https://github.com/preshing/cpp11-on-multicore/blob/master/common/sema.h
|
||||||
|
// LICENSE:
|
||||||
|
// Copyright (c) 2015 Jeff Preshing
|
||||||
|
//
|
||||||
|
// This software is provided 'as-is', without any express or implied
|
||||||
|
// warranty. In no event will the authors be held liable for any damages
|
||||||
|
// arising from the use of this software.
|
||||||
|
//
|
||||||
|
// Permission is granted to anyone to use this software for any purpose,
|
||||||
|
// including commercial applications, and to alter it and redistribute it
|
||||||
|
// freely, subject to the following restrictions:
|
||||||
|
//
|
||||||
|
// 1. The origin of this software must not be misrepresented; you must not
|
||||||
|
// claim that you wrote the original software. If you use this software
|
||||||
|
// in a product, an acknowledgement in the product documentation would be
|
||||||
|
// appreciated but is not required.
|
||||||
|
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
// misrepresented as being the original software.
|
||||||
|
// 3. This notice may not be removed or altered from any source distribution.
|
||||||
|
namespace mpmc_sema
|
||||||
|
{
|
||||||
|
#if defined(_WIN32)
|
||||||
|
class Semaphore
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
void* m_hSema;
|
||||||
|
|
||||||
|
Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
|
||||||
|
Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Semaphore(int initialCount = 0)
|
||||||
|
{
|
||||||
|
assert(initialCount >= 0);
|
||||||
|
const long maxLong = 0x7fffffff;
|
||||||
|
m_hSema = CreateSemaphoreW(nullptr, initialCount, maxLong, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
~Semaphore()
|
||||||
|
{
|
||||||
|
CloseHandle(m_hSema);
|
||||||
|
}
|
||||||
|
|
||||||
|
void wait()
|
||||||
|
{
|
||||||
|
const unsigned long infinite = 0xffffffff;
|
||||||
|
WaitForSingleObject(m_hSema, infinite);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool try_wait()
|
||||||
|
{
|
||||||
|
const unsigned long RC_WAIT_TIMEOUT = 0x00000102;
|
||||||
|
return WaitForSingleObject(m_hSema, 0) != RC_WAIT_TIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool timed_wait(std::uint64_t usecs)
|
||||||
|
{
|
||||||
|
const unsigned long RC_WAIT_TIMEOUT = 0x00000102;
|
||||||
|
return WaitForSingleObject(m_hSema, (unsigned long)(usecs / 1000)) != RC_WAIT_TIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
void signal(int count = 1)
|
||||||
|
{
|
||||||
|
ReleaseSemaphore(m_hSema, count, nullptr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#elif defined(__MACH__)
|
||||||
|
//---------------------------------------------------------
|
||||||
|
// Semaphore (Apple iOS and OSX)
|
||||||
|
// Can't use POSIX semaphores due to http://lists.apple.com/archives/darwin-kernel/2009/Apr/msg00010.html
|
||||||
|
//---------------------------------------------------------
|
||||||
|
class Semaphore
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
semaphore_t m_sema;
|
||||||
|
|
||||||
|
Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
|
||||||
|
Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Semaphore(int initialCount = 0)
|
||||||
|
{
|
||||||
|
assert(initialCount >= 0);
|
||||||
|
semaphore_create(mach_task_self(), &m_sema, SYNC_POLICY_FIFO, initialCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
~Semaphore()
|
||||||
|
{
|
||||||
|
semaphore_destroy(mach_task_self(), m_sema);
|
||||||
|
}
|
||||||
|
|
||||||
|
void wait()
|
||||||
|
{
|
||||||
|
semaphore_wait(m_sema);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool try_wait()
|
||||||
|
{
|
||||||
|
return timed_wait(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool timed_wait(std::uint64_t timeout_usecs)
|
||||||
|
{
|
||||||
|
mach_timespec_t ts;
|
||||||
|
ts.tv_sec = static_cast<unsigned int>(timeout_usecs / 1000000);
|
||||||
|
ts.tv_nsec = (timeout_usecs % 1000000) * 1000;
|
||||||
|
|
||||||
|
// added in OSX 10.10: https://developer.apple.com/library/prerelease/mac/documentation/General/Reference/APIDiffsMacOSX10_10SeedDiff/modules/Darwin.html
|
||||||
|
kern_return_t rc = semaphore_timedwait(m_sema, ts);
|
||||||
|
|
||||||
|
return rc != KERN_OPERATION_TIMED_OUT && rc != KERN_ABORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
void signal()
|
||||||
|
{
|
||||||
|
semaphore_signal(m_sema);
|
||||||
|
}
|
||||||
|
|
||||||
|
void signal(int count)
|
||||||
|
{
|
||||||
|
while (count-- > 0)
|
||||||
|
{
|
||||||
|
semaphore_signal(m_sema);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#elif defined(__unix__)
|
||||||
|
//---------------------------------------------------------
|
||||||
|
// Semaphore (POSIX, Linux)
|
||||||
|
//---------------------------------------------------------
|
||||||
|
class Semaphore
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
sem_t m_sema;
|
||||||
|
|
||||||
|
Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
|
||||||
|
Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Semaphore(int initialCount = 0)
|
||||||
|
{
|
||||||
|
assert(initialCount >= 0);
|
||||||
|
sem_init(&m_sema, 0, initialCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
~Semaphore()
|
||||||
|
{
|
||||||
|
sem_destroy(&m_sema);
|
||||||
|
}
|
||||||
|
|
||||||
|
void wait()
|
||||||
|
{
|
||||||
|
// http://stackoverflow.com/questions/2013181/gdb-causes-sem-wait-to-fail-with-eintr-error
|
||||||
|
int rc;
|
||||||
|
do {
|
||||||
|
rc = sem_wait(&m_sema);
|
||||||
|
} while (rc == -1 && errno == EINTR);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool try_wait()
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
do {
|
||||||
|
rc = sem_trywait(&m_sema);
|
||||||
|
} while (rc == -1 && errno == EINTR);
|
||||||
|
return !(rc == -1 && errno == EAGAIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool timed_wait(std::uint64_t usecs)
|
||||||
|
{
|
||||||
|
struct timespec ts;
|
||||||
|
const int usecs_in_1_sec = 1000000;
|
||||||
|
const int nsecs_in_1_sec = 1000000000;
|
||||||
|
clock_gettime(CLOCK_REALTIME, &ts);
|
||||||
|
ts.tv_sec += usecs / usecs_in_1_sec;
|
||||||
|
ts.tv_nsec += (usecs % usecs_in_1_sec) * 1000;
|
||||||
|
// sem_timedwait bombs if you have more than 1e9 in tv_nsec
|
||||||
|
// so we have to clean things up before passing it in
|
||||||
|
if (ts.tv_nsec >= nsecs_in_1_sec) {
|
||||||
|
ts.tv_nsec -= nsecs_in_1_sec;
|
||||||
|
++ts.tv_sec;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rc;
|
||||||
|
do {
|
||||||
|
rc = sem_timedwait(&m_sema, &ts);
|
||||||
|
} while (rc == -1 && errno == EINTR);
|
||||||
|
return !(rc == -1 && errno == ETIMEDOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void signal()
|
||||||
|
{
|
||||||
|
sem_post(&m_sema);
|
||||||
|
}
|
||||||
|
|
||||||
|
void signal(int count)
|
||||||
|
{
|
||||||
|
while (count-- > 0)
|
||||||
|
{
|
||||||
|
sem_post(&m_sema);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#else
|
||||||
|
#error Unsupported platform! (No semaphore wrapper available)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//---------------------------------------------------------
|
||||||
|
// LightweightSemaphore
|
||||||
|
//---------------------------------------------------------
|
||||||
|
class LightweightSemaphore
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
typedef std::make_signed<std::size_t>::type ssize_t;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::atomic<ssize_t> m_count;
|
||||||
|
Semaphore m_sema;
|
||||||
|
|
||||||
|
bool waitWithPartialSpinning(std::int64_t timeout_usecs = -1)
|
||||||
|
{
|
||||||
|
ssize_t oldCount;
|
||||||
|
// Is there a better way to set the initial spin count?
|
||||||
|
// If we lower it to 1000, testBenaphore becomes 15x slower on my Core i7-5930K Windows PC,
|
||||||
|
// as threads start hitting the kernel semaphore.
|
||||||
|
int spin = 10000;
|
||||||
|
while (--spin >= 0)
|
||||||
|
{
|
||||||
|
oldCount = m_count.load(std::memory_order_relaxed);
|
||||||
|
if ((oldCount > 0) && m_count.compare_exchange_strong(oldCount, oldCount - 1, std::memory_order_acquire, std::memory_order_relaxed))
|
||||||
|
return true;
|
||||||
|
std::atomic_signal_fence(std::memory_order_acquire); // Prevent the compiler from collapsing the loop.
|
||||||
|
}
|
||||||
|
oldCount = m_count.fetch_sub(1, std::memory_order_acquire);
|
||||||
|
if (oldCount > 0)
|
||||||
|
return true;
|
||||||
|
if (timeout_usecs < 0)
|
||||||
|
{
|
||||||
|
m_sema.wait();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (m_sema.timed_wait((std::uint64_t)timeout_usecs))
|
||||||
|
return true;
|
||||||
|
// At this point, we've timed out waiting for the semaphore, but the
|
||||||
|
// count is still decremented indicating we may still be waiting on
|
||||||
|
// it. So we have to re-adjust the count, but only if the semaphore
|
||||||
|
// wasn't signaled enough times for us too since then. If it was, we
|
||||||
|
// need to release the semaphore too.
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
oldCount = m_count.load(std::memory_order_acquire);
|
||||||
|
if (oldCount >= 0 && m_sema.try_wait())
|
||||||
|
return true;
|
||||||
|
if (oldCount < 0 && m_count.compare_exchange_strong(oldCount, oldCount + 1, std::memory_order_relaxed, std::memory_order_relaxed))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t waitManyWithPartialSpinning(ssize_t max, std::int64_t timeout_usecs = -1)
|
||||||
|
{
|
||||||
|
assert(max > 0);
|
||||||
|
ssize_t oldCount;
|
||||||
|
int spin = 10000;
|
||||||
|
while (--spin >= 0)
|
||||||
|
{
|
||||||
|
oldCount = m_count.load(std::memory_order_relaxed);
|
||||||
|
if (oldCount > 0)
|
||||||
|
{
|
||||||
|
ssize_t newCount = oldCount > max ? oldCount - max : 0;
|
||||||
|
if (m_count.compare_exchange_strong(oldCount, newCount, std::memory_order_acquire, std::memory_order_relaxed))
|
||||||
|
return oldCount - newCount;
|
||||||
|
}
|
||||||
|
std::atomic_signal_fence(std::memory_order_acquire);
|
||||||
|
}
|
||||||
|
oldCount = m_count.fetch_sub(1, std::memory_order_acquire);
|
||||||
|
if (oldCount <= 0)
|
||||||
|
{
|
||||||
|
if (timeout_usecs < 0)
|
||||||
|
m_sema.wait();
|
||||||
|
else if (!m_sema.timed_wait((std::uint64_t)timeout_usecs))
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
oldCount = m_count.load(std::memory_order_acquire);
|
||||||
|
if (oldCount >= 0 && m_sema.try_wait())
|
||||||
|
break;
|
||||||
|
if (oldCount < 0 && m_count.compare_exchange_strong(oldCount, oldCount + 1, std::memory_order_relaxed, std::memory_order_relaxed))
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (max > 1)
|
||||||
|
return 1 + tryWaitMany(max - 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
LightweightSemaphore(ssize_t initialCount = 0) : m_count(initialCount)
|
||||||
|
{
|
||||||
|
assert(initialCount >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool tryWait()
|
||||||
|
{
|
||||||
|
ssize_t oldCount = m_count.load(std::memory_order_relaxed);
|
||||||
|
while (oldCount > 0)
|
||||||
|
{
|
||||||
|
if (m_count.compare_exchange_weak(oldCount, oldCount - 1, std::memory_order_acquire, std::memory_order_relaxed))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void wait()
|
||||||
|
{
|
||||||
|
if (!tryWait())
|
||||||
|
waitWithPartialSpinning();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool wait(std::int64_t timeout_usecs)
|
||||||
|
{
|
||||||
|
return tryWait() || waitWithPartialSpinning(timeout_usecs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acquires between 0 and (greedily) max, inclusive
|
||||||
|
ssize_t tryWaitMany(ssize_t max)
|
||||||
|
{
|
||||||
|
assert(max >= 0);
|
||||||
|
ssize_t oldCount = m_count.load(std::memory_order_relaxed);
|
||||||
|
while (oldCount > 0)
|
||||||
|
{
|
||||||
|
ssize_t newCount = oldCount > max ? oldCount - max : 0;
|
||||||
|
if (m_count.compare_exchange_weak(oldCount, newCount, std::memory_order_acquire, std::memory_order_relaxed))
|
||||||
|
return oldCount - newCount;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acquires at least one, and (greedily) at most max
|
||||||
|
ssize_t waitMany(ssize_t max, std::int64_t timeout_usecs)
|
||||||
|
{
|
||||||
|
assert(max >= 0);
|
||||||
|
ssize_t result = tryWaitMany(max);
|
||||||
|
if (result == 0 && max > 0)
|
||||||
|
result = waitManyWithPartialSpinning(max, timeout_usecs);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t waitMany(ssize_t max)
|
||||||
|
{
|
||||||
|
ssize_t result = waitMany(max, -1);
|
||||||
|
assert(result > 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void signal(ssize_t count = 1)
|
||||||
|
{
|
||||||
|
assert(count >= 0);
|
||||||
|
ssize_t oldCount = m_count.fetch_add(count, std::memory_order_release);
|
||||||
|
ssize_t toRelease = -oldCount < count ? -oldCount : count;
|
||||||
|
if (toRelease > 0)
|
||||||
|
{
|
||||||
|
m_sema.signal((int)toRelease);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t availableApprox() const
|
||||||
|
{
|
||||||
|
ssize_t count = m_count.load(std::memory_order_relaxed);
|
||||||
|
return count > 0 ? count : 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // end namespace mpmc_sema
|
||||||
|
} // end namespace details
|
||||||
|
|
||||||
|
|
||||||
|
// This is a blocking version of the queue. It has an almost identical interface to
|
||||||
|
// the normal non-blocking version, with the addition of various wait_dequeue() methods
|
||||||
|
// and the removal of producer-specific dequeue methods.
|
||||||
|
template<typename T, typename Traits = ConcurrentQueueDefaultTraits>
|
||||||
|
class BlockingConcurrentQueue
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
typedef ::moodycamel::ConcurrentQueue<T, Traits> ConcurrentQueue;
|
||||||
|
typedef details::mpmc_sema::LightweightSemaphore LightweightSemaphore;
|
||||||
|
|
||||||
|
public:
|
||||||
|
typedef typename ConcurrentQueue::producer_token_t producer_token_t;
|
||||||
|
typedef typename ConcurrentQueue::consumer_token_t consumer_token_t;
|
||||||
|
|
||||||
|
typedef typename ConcurrentQueue::index_t index_t;
|
||||||
|
typedef typename ConcurrentQueue::size_t size_t;
|
||||||
|
typedef typename std::make_signed<size_t>::type ssize_t;
|
||||||
|
|
||||||
|
static const size_t BLOCK_SIZE = ConcurrentQueue::BLOCK_SIZE;
|
||||||
|
static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = ConcurrentQueue::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD;
|
||||||
|
static const size_t EXPLICIT_INITIAL_INDEX_SIZE = ConcurrentQueue::EXPLICIT_INITIAL_INDEX_SIZE;
|
||||||
|
static const size_t IMPLICIT_INITIAL_INDEX_SIZE = ConcurrentQueue::IMPLICIT_INITIAL_INDEX_SIZE;
|
||||||
|
static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = ConcurrentQueue::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE;
|
||||||
|
static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = ConcurrentQueue::EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE;
|
||||||
|
static const size_t MAX_SUBQUEUE_SIZE = ConcurrentQueue::MAX_SUBQUEUE_SIZE;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Creates a queue with at least `capacity` element slots; note that the
|
||||||
|
// actual number of elements that can be inserted without additional memory
|
||||||
|
// allocation depends on the number of producers and the block size (e.g. if
|
||||||
|
// the block size is equal to `capacity`, only a single block will be allocated
|
||||||
|
// up-front, which means only a single producer will be able to enqueue elements
|
||||||
|
// without an extra allocation -- blocks aren't shared between producers).
|
||||||
|
// This method is not thread safe -- it is up to the user to ensure that the
|
||||||
|
// queue is fully constructed before it starts being used by other threads (this
|
||||||
|
// includes making the memory effects of construction visible, possibly with a
|
||||||
|
// memory barrier).
|
||||||
|
explicit BlockingConcurrentQueue(size_t capacity = 6 * BLOCK_SIZE)
|
||||||
|
: inner(capacity), sema(create<LightweightSemaphore>(), &BlockingConcurrentQueue::template destroy<LightweightSemaphore>)
|
||||||
|
{
|
||||||
|
assert(reinterpret_cast<ConcurrentQueue*>((BlockingConcurrentQueue*)1) == &((BlockingConcurrentQueue*)1)->inner && "BlockingConcurrentQueue must have ConcurrentQueue as its first member");
|
||||||
|
if (!sema) {
|
||||||
|
MOODYCAMEL_THROW(std::bad_alloc());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockingConcurrentQueue(size_t minCapacity, size_t maxExplicitProducers, size_t maxImplicitProducers)
|
||||||
|
: inner(minCapacity, maxExplicitProducers, maxImplicitProducers), sema(create<LightweightSemaphore>(), &BlockingConcurrentQueue::template destroy<LightweightSemaphore>)
|
||||||
|
{
|
||||||
|
assert(reinterpret_cast<ConcurrentQueue*>((BlockingConcurrentQueue*)1) == &((BlockingConcurrentQueue*)1)->inner && "BlockingConcurrentQueue must have ConcurrentQueue as its first member");
|
||||||
|
if (!sema) {
|
||||||
|
MOODYCAMEL_THROW(std::bad_alloc());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable copying and copy assignment
|
||||||
|
BlockingConcurrentQueue(BlockingConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION;
|
||||||
|
BlockingConcurrentQueue& operator=(BlockingConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION;
|
||||||
|
|
||||||
|
// Moving is supported, but note that it is *not* a thread-safe operation.
|
||||||
|
// Nobody can use the queue while it's being moved, and the memory effects
|
||||||
|
// of that move must be propagated to other threads before they can use it.
|
||||||
|
// Note: When a queue is moved, its tokens are still valid but can only be
|
||||||
|
// used with the destination queue (i.e. semantically they are moved along
|
||||||
|
// with the queue itself).
|
||||||
|
BlockingConcurrentQueue(BlockingConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT
|
||||||
|
: inner(std::move(other.inner)), sema(std::move(other.sema))
|
||||||
|
{ }
|
||||||
|
|
||||||
|
inline BlockingConcurrentQueue& operator=(BlockingConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT
|
||||||
|
{
|
||||||
|
return swap_internal(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swaps this queue's state with the other's. Not thread-safe.
|
||||||
|
// Swapping two queues does not invalidate their tokens, however
|
||||||
|
// the tokens that were created for one queue must be used with
|
||||||
|
// only the swapped queue (i.e. the tokens are tied to the
|
||||||
|
// queue's movable state, not the object itself).
|
||||||
|
inline void swap(BlockingConcurrentQueue& other) MOODYCAMEL_NOEXCEPT
|
||||||
|
{
|
||||||
|
swap_internal(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
BlockingConcurrentQueue& swap_internal(BlockingConcurrentQueue& other)
|
||||||
|
{
|
||||||
|
if (this == &other) {
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
inner.swap(other.inner);
|
||||||
|
sema.swap(other.sema);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Enqueues a single item (by copying it).
|
||||||
|
// Allocates memory if required. Only fails if memory allocation fails (or implicit
|
||||||
|
// production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0,
|
||||||
|
// or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
|
||||||
|
// Thread-safe.
|
||||||
|
inline bool enqueue(T const& item)
|
||||||
|
{
|
||||||
|
if ((details::likely)(inner.enqueue(item))) {
|
||||||
|
sema->signal();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enqueues a single item (by moving it, if possible).
|
||||||
|
// Allocates memory if required. Only fails if memory allocation fails (or implicit
|
||||||
|
// production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0,
|
||||||
|
// or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
|
||||||
|
// Thread-safe.
|
||||||
|
inline bool enqueue(T&& item)
|
||||||
|
{
|
||||||
|
if ((details::likely)(inner.enqueue(std::move(item)))) {
|
||||||
|
sema->signal();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enqueues a single item (by copying it) using an explicit producer token.
|
||||||
|
// Allocates memory if required. Only fails if memory allocation fails (or
|
||||||
|
// Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
|
||||||
|
// Thread-safe.
|
||||||
|
inline bool enqueue(producer_token_t const& token, T const& item)
|
||||||
|
{
|
||||||
|
if ((details::likely)(inner.enqueue(token, item))) {
|
||||||
|
sema->signal();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enqueues a single item (by moving it, if possible) using an explicit producer token.
|
||||||
|
// Allocates memory if required. Only fails if memory allocation fails (or
|
||||||
|
// Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
|
||||||
|
// Thread-safe.
|
||||||
|
inline bool enqueue(producer_token_t const& token, T&& item)
|
||||||
|
{
|
||||||
|
if ((details::likely)(inner.enqueue(token, std::move(item)))) {
|
||||||
|
sema->signal();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enqueues several items.
|
||||||
|
// Allocates memory if required. Only fails if memory allocation fails (or
|
||||||
|
// implicit production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE
|
||||||
|
// is 0, or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
|
||||||
|
// Note: Use std::make_move_iterator if the elements should be moved instead of copied.
|
||||||
|
// Thread-safe.
|
||||||
|
template<typename It>
|
||||||
|
inline bool enqueue_bulk(It itemFirst, size_t count)
|
||||||
|
{
|
||||||
|
if ((details::likely)(inner.enqueue_bulk(std::forward<It>(itemFirst), count))) {
|
||||||
|
sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enqueues several items using an explicit producer token.
|
||||||
|
// Allocates memory if required. Only fails if memory allocation fails
|
||||||
|
// (or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
|
||||||
|
// Note: Use std::make_move_iterator if the elements should be moved
|
||||||
|
// instead of copied.
|
||||||
|
// Thread-safe.
|
||||||
|
template<typename It>
|
||||||
|
inline bool enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count)
|
||||||
|
{
|
||||||
|
if ((details::likely)(inner.enqueue_bulk(token, std::forward<It>(itemFirst), count))) {
|
||||||
|
sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enqueues a single item (by copying it).
|
||||||
|
// Does not allocate memory. Fails if not enough room to enqueue (or implicit
|
||||||
|
// production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE
|
||||||
|
// is 0).
|
||||||
|
// Thread-safe.
|
||||||
|
inline bool try_enqueue(T const& item)
|
||||||
|
{
|
||||||
|
if (inner.try_enqueue(item)) {
|
||||||
|
sema->signal();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enqueues a single item (by moving it, if possible).
|
||||||
|
// Does not allocate memory (except for one-time implicit producer).
|
||||||
|
// Fails if not enough room to enqueue (or implicit production is
|
||||||
|
// disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0).
|
||||||
|
// Thread-safe.
|
||||||
|
inline bool try_enqueue(T&& item)
|
||||||
|
{
|
||||||
|
if (inner.try_enqueue(std::move(item))) {
|
||||||
|
sema->signal();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enqueues a single item (by copying it) using an explicit producer token.
|
||||||
|
// Does not allocate memory. Fails if not enough room to enqueue.
|
||||||
|
// Thread-safe.
|
||||||
|
inline bool try_enqueue(producer_token_t const& token, T const& item)
|
||||||
|
{
|
||||||
|
if (inner.try_enqueue(token, item)) {
|
||||||
|
sema->signal();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enqueues a single item (by moving it, if possible) using an explicit producer token.
|
||||||
|
// Does not allocate memory. Fails if not enough room to enqueue.
|
||||||
|
// Thread-safe.
|
||||||
|
inline bool try_enqueue(producer_token_t const& token, T&& item)
|
||||||
|
{
|
||||||
|
if (inner.try_enqueue(token, std::move(item))) {
|
||||||
|
sema->signal();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enqueues several items.
|
||||||
|
// Does not allocate memory (except for one-time implicit producer).
|
||||||
|
// Fails if not enough room to enqueue (or implicit production is
|
||||||
|
// disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0).
|
||||||
|
// Note: Use std::make_move_iterator if the elements should be moved
|
||||||
|
// instead of copied.
|
||||||
|
// Thread-safe.
|
||||||
|
template<typename It>
|
||||||
|
inline bool try_enqueue_bulk(It itemFirst, size_t count)
|
||||||
|
{
|
||||||
|
if (inner.try_enqueue_bulk(std::forward<It>(itemFirst), count)) {
|
||||||
|
sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enqueues several items using an explicit producer token.
|
||||||
|
// Does not allocate memory. Fails if not enough room to enqueue.
|
||||||
|
// Note: Use std::make_move_iterator if the elements should be moved
|
||||||
|
// instead of copied.
|
||||||
|
// Thread-safe.
|
||||||
|
template<typename It>
|
||||||
|
inline bool try_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count)
|
||||||
|
{
|
||||||
|
if (inner.try_enqueue_bulk(token, std::forward<It>(itemFirst), count)) {
|
||||||
|
sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Attempts to dequeue from the queue.
|
||||||
|
// Returns false if all producer streams appeared empty at the time they
|
||||||
|
// were checked (so, the queue is likely but not guaranteed to be empty).
|
||||||
|
// Never allocates. Thread-safe.
|
||||||
|
template<typename U>
|
||||||
|
inline bool try_dequeue(U& item)
|
||||||
|
{
|
||||||
|
if (sema->tryWait()) {
|
||||||
|
while (!inner.try_dequeue(item)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempts to dequeue from the queue using an explicit consumer token.
|
||||||
|
// Returns false if all producer streams appeared empty at the time they
|
||||||
|
// were checked (so, the queue is likely but not guaranteed to be empty).
|
||||||
|
// Never allocates. Thread-safe.
|
||||||
|
template<typename U>
|
||||||
|
inline bool try_dequeue(consumer_token_t& token, U& item)
|
||||||
|
{
|
||||||
|
if (sema->tryWait()) {
|
||||||
|
while (!inner.try_dequeue(token, item)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempts to dequeue several elements from the queue.
|
||||||
|
// Returns the number of items actually dequeued.
|
||||||
|
// Returns 0 if all producer streams appeared empty at the time they
|
||||||
|
// were checked (so, the queue is likely but not guaranteed to be empty).
|
||||||
|
// Never allocates. Thread-safe.
|
||||||
|
template<typename It>
|
||||||
|
inline size_t try_dequeue_bulk(It itemFirst, size_t max)
|
||||||
|
{
|
||||||
|
size_t count = 0;
|
||||||
|
max = (size_t)sema->tryWaitMany((LightweightSemaphore::ssize_t)(ssize_t)max);
|
||||||
|
while (count != max) {
|
||||||
|
count += inner.template try_dequeue_bulk<It&>(itemFirst, max - count);
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempts to dequeue several elements from the queue using an explicit consumer token.
|
||||||
|
// Returns the number of items actually dequeued.
|
||||||
|
// Returns 0 if all producer streams appeared empty at the time they
|
||||||
|
// were checked (so, the queue is likely but not guaranteed to be empty).
|
||||||
|
// Never allocates. Thread-safe.
|
||||||
|
template<typename It>
|
||||||
|
inline size_t try_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max)
|
||||||
|
{
|
||||||
|
size_t count = 0;
|
||||||
|
max = (size_t)sema->tryWaitMany((LightweightSemaphore::ssize_t)(ssize_t)max);
|
||||||
|
while (count != max) {
|
||||||
|
count += inner.template try_dequeue_bulk<It&>(token, itemFirst, max - count);
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Blocks the current thread until there's something to dequeue, then
|
||||||
|
// dequeues it.
|
||||||
|
// Never allocates. Thread-safe.
|
||||||
|
template<typename U>
|
||||||
|
inline void wait_dequeue(U& item)
|
||||||
|
{
|
||||||
|
sema->wait();
|
||||||
|
while (!inner.try_dequeue(item)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blocks the current thread until either there's something to dequeue
|
||||||
|
// or the timeout (specified in microseconds) expires. Returns false
|
||||||
|
// without setting `item` if the timeout expires, otherwise assigns
|
||||||
|
// to `item` and returns true.
|
||||||
|
// Using a negative timeout indicates an indefinite timeout,
|
||||||
|
// and is thus functionally equivalent to calling wait_dequeue.
|
||||||
|
// Never allocates. Thread-safe.
|
||||||
|
template<typename U>
|
||||||
|
inline bool wait_dequeue_timed(U& item, std::int64_t timeout_usecs)
|
||||||
|
{
|
||||||
|
if (!sema->wait(timeout_usecs)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
while (!inner.try_dequeue(item)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blocks the current thread until either there's something to dequeue
|
||||||
|
// or the timeout expires. Returns false without setting `item` if the
|
||||||
|
// timeout expires, otherwise assigns to `item` and returns true.
|
||||||
|
// Never allocates. Thread-safe.
|
||||||
|
template<typename U, typename Rep, typename Period>
|
||||||
|
inline bool wait_dequeue_timed(U& item, std::chrono::duration<Rep, Period> const& timeout)
|
||||||
|
{
|
||||||
|
return wait_dequeue_timed(item, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blocks the current thread until there's something to dequeue, then
|
||||||
|
// dequeues it using an explicit consumer token.
|
||||||
|
// Never allocates. Thread-safe.
|
||||||
|
template<typename U>
|
||||||
|
inline void wait_dequeue(consumer_token_t& token, U& item)
|
||||||
|
{
|
||||||
|
sema->wait();
|
||||||
|
while (!inner.try_dequeue(token, item)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blocks the current thread until either there's something to dequeue
|
||||||
|
// or the timeout (specified in microseconds) expires. Returns false
|
||||||
|
// without setting `item` if the timeout expires, otherwise assigns
|
||||||
|
// to `item` and returns true.
|
||||||
|
// Using a negative timeout indicates an indefinite timeout,
|
||||||
|
// and is thus functionally equivalent to calling wait_dequeue.
|
||||||
|
// Never allocates. Thread-safe.
|
||||||
|
template<typename U>
|
||||||
|
inline bool wait_dequeue_timed(consumer_token_t& token, U& item, std::int64_t timeout_usecs)
|
||||||
|
{
|
||||||
|
if (!sema->wait(timeout_usecs)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
while (!inner.try_dequeue(token, item)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blocks the current thread until either there's something to dequeue
|
||||||
|
// or the timeout expires. Returns false without setting `item` if the
|
||||||
|
// timeout expires, otherwise assigns to `item` and returns true.
|
||||||
|
// Never allocates. Thread-safe.
|
||||||
|
template<typename U, typename Rep, typename Period>
|
||||||
|
inline bool wait_dequeue_timed(consumer_token_t& token, U& item, std::chrono::duration<Rep, Period> const& timeout)
|
||||||
|
{
|
||||||
|
return wait_dequeue_timed(token, item, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempts to dequeue several elements from the queue.
|
||||||
|
// Returns the number of items actually dequeued, which will
|
||||||
|
// always be at least one (this method blocks until the queue
|
||||||
|
// is non-empty) and at most max.
|
||||||
|
// Never allocates. Thread-safe.
|
||||||
|
template<typename It>
|
||||||
|
inline size_t wait_dequeue_bulk(It itemFirst, size_t max)
|
||||||
|
{
|
||||||
|
size_t count = 0;
|
||||||
|
max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max);
|
||||||
|
while (count != max) {
|
||||||
|
count += inner.template try_dequeue_bulk<It&>(itemFirst, max - count);
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempts to dequeue several elements from the queue.
|
||||||
|
// Returns the number of items actually dequeued, which can
|
||||||
|
// be 0 if the timeout expires while waiting for elements,
|
||||||
|
// and at most max.
|
||||||
|
// Using a negative timeout indicates an indefinite timeout,
|
||||||
|
// and is thus functionally equivalent to calling wait_dequeue_bulk.
|
||||||
|
// Never allocates. Thread-safe.
|
||||||
|
template<typename It>
|
||||||
|
inline size_t wait_dequeue_bulk_timed(It itemFirst, size_t max, std::int64_t timeout_usecs)
|
||||||
|
{
|
||||||
|
size_t count = 0;
|
||||||
|
max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max, timeout_usecs);
|
||||||
|
while (count != max) {
|
||||||
|
count += inner.template try_dequeue_bulk<It&>(itemFirst, max - count);
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempts to dequeue several elements from the queue.
|
||||||
|
// Returns the number of items actually dequeued, which can
|
||||||
|
// be 0 if the timeout expires while waiting for elements,
|
||||||
|
// and at most max.
|
||||||
|
// Never allocates. Thread-safe.
|
||||||
|
template<typename It, typename Rep, typename Period>
|
||||||
|
inline size_t wait_dequeue_bulk_timed(It itemFirst, size_t max, std::chrono::duration<Rep, Period> const& timeout)
|
||||||
|
{
|
||||||
|
return wait_dequeue_bulk_timed<It&>(itemFirst, max, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempts to dequeue several elements from the queue using an explicit consumer token.
|
||||||
|
// Returns the number of items actually dequeued, which will
|
||||||
|
// always be at least one (this method blocks until the queue
|
||||||
|
// is non-empty) and at most max.
|
||||||
|
// Never allocates. Thread-safe.
|
||||||
|
template<typename It>
|
||||||
|
inline size_t wait_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max)
|
||||||
|
{
|
||||||
|
size_t count = 0;
|
||||||
|
max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max);
|
||||||
|
while (count != max) {
|
||||||
|
count += inner.template try_dequeue_bulk<It&>(token, itemFirst, max - count);
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempts to dequeue several elements from the queue using an explicit consumer token.
|
||||||
|
// Returns the number of items actually dequeued, which can
|
||||||
|
// be 0 if the timeout expires while waiting for elements,
|
||||||
|
// and at most max.
|
||||||
|
// Using a negative timeout indicates an indefinite timeout,
|
||||||
|
// and is thus functionally equivalent to calling wait_dequeue_bulk.
|
||||||
|
// Never allocates. Thread-safe.
|
||||||
|
template<typename It>
|
||||||
|
inline size_t wait_dequeue_bulk_timed(consumer_token_t& token, It itemFirst, size_t max, std::int64_t timeout_usecs)
|
||||||
|
{
|
||||||
|
size_t count = 0;
|
||||||
|
max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max, timeout_usecs);
|
||||||
|
while (count != max) {
|
||||||
|
count += inner.template try_dequeue_bulk<It&>(token, itemFirst, max - count);
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempts to dequeue several elements from the queue using an explicit consumer token.
|
||||||
|
// Returns the number of items actually dequeued, which can
|
||||||
|
// be 0 if the timeout expires while waiting for elements,
|
||||||
|
// and at most max.
|
||||||
|
// Never allocates. Thread-safe.
|
||||||
|
template<typename It, typename Rep, typename Period>
|
||||||
|
inline size_t wait_dequeue_bulk_timed(consumer_token_t& token, It itemFirst, size_t max, std::chrono::duration<Rep, Period> const& timeout)
|
||||||
|
{
|
||||||
|
return wait_dequeue_bulk_timed<It&>(token, itemFirst, max, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Returns an estimate of the total number of elements currently in the queue. This
|
||||||
|
// estimate is only accurate if the queue has completely stabilized before it is called
|
||||||
|
// (i.e. all enqueue and dequeue operations have completed and their memory effects are
|
||||||
|
// visible on the calling thread, and no further operations start while this method is
|
||||||
|
// being called).
|
||||||
|
// Thread-safe.
|
||||||
|
inline size_t size_approx() const
|
||||||
|
{
|
||||||
|
return (size_t)sema->availableApprox();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Returns true if the underlying atomic variables used by
|
||||||
|
// the queue are lock-free (they should be on most platforms).
|
||||||
|
// Thread-safe.
|
||||||
|
static bool is_lock_free()
|
||||||
|
{
|
||||||
|
return ConcurrentQueue::is_lock_free();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
template<typename U>
|
||||||
|
static inline U* create()
|
||||||
|
{
|
||||||
|
auto p = (Traits::malloc)(sizeof(U));
|
||||||
|
return p != nullptr ? new (p) U : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename U, typename A1>
|
||||||
|
static inline U* create(A1&& a1)
|
||||||
|
{
|
||||||
|
auto p = (Traits::malloc)(sizeof(U));
|
||||||
|
return p != nullptr ? new (p) U(std::forward<A1>(a1)) : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename U>
|
||||||
|
static inline void destroy(U* p)
|
||||||
|
{
|
||||||
|
if (p != nullptr) {
|
||||||
|
p->~U();
|
||||||
|
}
|
||||||
|
(Traits::free)(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
ConcurrentQueue inner;
|
||||||
|
std::unique_ptr<LightweightSemaphore, void (*)(LightweightSemaphore*)> sema;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template<typename T, typename Traits>
|
||||||
|
inline void swap(BlockingConcurrentQueue<T, Traits>& a, BlockingConcurrentQueue<T, Traits>& b) MOODYCAMEL_NOEXCEPT
|
||||||
|
{
|
||||||
|
a.swap(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // end namespace moodycamel
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,87 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
//#define MCDBGQ_TRACKMEM 1
|
||||||
|
//#define MCDBGQ_NOLOCKFREE_FREELIST 1
|
||||||
|
//#define MCDBGQ_USEDEBUGFREELIST 1
|
||||||
|
//#define MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX 1
|
||||||
|
//#define MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH 1
|
||||||
|
|
||||||
|
#if defined(_WIN32) || defined(__WINDOWS__) || defined(__WIN32__)
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#include <windows.h>
|
||||||
|
namespace moodycamel { namespace debug {
|
||||||
|
struct DebugMutex {
|
||||||
|
DebugMutex() { InitializeCriticalSectionAndSpinCount(&cs, 0x400); }
|
||||||
|
~DebugMutex() { DeleteCriticalSection(&cs); }
|
||||||
|
|
||||||
|
void lock() { EnterCriticalSection(&cs); }
|
||||||
|
void unlock() { LeaveCriticalSection(&cs); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
CRITICAL_SECTION cs;
|
||||||
|
};
|
||||||
|
} }
|
||||||
|
#else
|
||||||
|
#include <mutex>
|
||||||
|
namespace moodycamel { namespace debug {
|
||||||
|
struct DebugMutex {
|
||||||
|
void lock() { m.lock(); }
|
||||||
|
void unlock() { m.unlock(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::mutex m;
|
||||||
|
};
|
||||||
|
} }
|
||||||
|
#define
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace moodycamel { namespace debug {
|
||||||
|
struct DebugLock {
|
||||||
|
explicit DebugLock(DebugMutex& mutex)
|
||||||
|
: mutex(mutex)
|
||||||
|
{
|
||||||
|
mutex.lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
~DebugLock()
|
||||||
|
{
|
||||||
|
mutex.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
DebugMutex& mutex;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template<typename N>
|
||||||
|
struct DebugFreeList {
|
||||||
|
DebugFreeList() : head(nullptr) { }
|
||||||
|
DebugFreeList(DebugFreeList&& other) : head(other.head) { other.head = nullptr; }
|
||||||
|
void swap(DebugFreeList& other) { std::swap(head, other.head); }
|
||||||
|
|
||||||
|
inline void add(N* node)
|
||||||
|
{
|
||||||
|
DebugLock lock(mutex);
|
||||||
|
node->freeListNext = head;
|
||||||
|
head = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline N* try_get()
|
||||||
|
{
|
||||||
|
DebugLock lock(mutex);
|
||||||
|
if (head == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto prevHead = head;
|
||||||
|
head = head->freeListNext;
|
||||||
|
return prevHead;
|
||||||
|
}
|
||||||
|
|
||||||
|
N* head_unsafe() const { return head; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
N* head;
|
||||||
|
DebugMutex mutex;
|
||||||
|
};
|
||||||
|
} }
|
||||||
|
|
@ -0,0 +1,375 @@
|
||||||
|
# Samples for moodycamel::ConcurrentQueue
|
||||||
|
|
||||||
|
Here are some example usage scenarios with sample code. Note that most
|
||||||
|
use the simplest version of each available method for demonstration purposes,
|
||||||
|
but they can all be adapted to use tokens and/or the corresponding bulk methods for
|
||||||
|
extra speed.
|
||||||
|
|
||||||
|
|
||||||
|
## Hello queue
|
||||||
|
|
||||||
|
ConcurrentQueue<int> q;
|
||||||
|
|
||||||
|
for (int i = 0; i != 123; ++i)
|
||||||
|
q.enqueue(i);
|
||||||
|
|
||||||
|
int item;
|
||||||
|
for (int i = 0; i != 123; ++i) {
|
||||||
|
q.try_dequeue(item);
|
||||||
|
assert(item == i);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## Hello concurrency
|
||||||
|
|
||||||
|
Basic example of how to use the queue from multiple threads, with no
|
||||||
|
particular goal (i.e. it does nothing, but in an instructive way).
|
||||||
|
|
||||||
|
ConcurrentQueue<int> q;
|
||||||
|
int dequeued[100] = { 0 };
|
||||||
|
std::thread threads[20];
|
||||||
|
|
||||||
|
// Producers
|
||||||
|
for (int i = 0; i != 10; ++i) {
|
||||||
|
threads[i] = std::thread([&](int i) {
|
||||||
|
for (int j = 0; j != 10; ++j) {
|
||||||
|
q.enqueue(i * 10 + j);
|
||||||
|
}
|
||||||
|
}, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consumers
|
||||||
|
for (int i = 10; i != 20; ++i) {
|
||||||
|
threads[i] = std::thread([&]() {
|
||||||
|
int item;
|
||||||
|
for (int j = 0; j != 20; ++j) {
|
||||||
|
if (q.try_dequeue(item)) {
|
||||||
|
++dequeued[item];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for all threads
|
||||||
|
for (int i = 0; i != 20; ++i) {
|
||||||
|
threads[i].join();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect any leftovers (could be some if e.g. consumers finish before producers)
|
||||||
|
int item;
|
||||||
|
while (q.try_dequeue(item)) {
|
||||||
|
++dequeued[item];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure everything went in and came back out!
|
||||||
|
for (int i = 0; i != 100; ++i) {
|
||||||
|
assert(dequeued[i] == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## Bulk up
|
||||||
|
|
||||||
|
Same as previous example, but runs faster.
|
||||||
|
|
||||||
|
ConcurrentQueue<int> q;
|
||||||
|
int dequeued[100] = { 0 };
|
||||||
|
std::thread threads[20];
|
||||||
|
|
||||||
|
// Producers
|
||||||
|
for (int i = 0; i != 10; ++i) {
|
||||||
|
threads[i] = std::thread([&](int i) {
|
||||||
|
int items[10];
|
||||||
|
for (int j = 0; j != 10; ++j) {
|
||||||
|
items[j] = i * 10 + j;
|
||||||
|
}
|
||||||
|
q.enqueue_bulk(items, 10);
|
||||||
|
}, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consumers
|
||||||
|
for (int i = 10; i != 20; ++i) {
|
||||||
|
threads[i] = std::thread([&]() {
|
||||||
|
int items[20];
|
||||||
|
for (std::size_t count = q.try_dequeue_bulk(items, 20); count != 0; --count) {
|
||||||
|
++dequeued[items[count - 1]];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for all threads
|
||||||
|
for (int i = 0; i != 20; ++i) {
|
||||||
|
threads[i].join();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect any leftovers (could be some if e.g. consumers finish before producers)
|
||||||
|
int items[10];
|
||||||
|
std::size_t count;
|
||||||
|
while ((count = q.try_dequeue_bulk(items, 10)) != 0) {
|
||||||
|
for (std::size_t i = 0; i != count; ++i) {
|
||||||
|
++dequeued[items[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure everything went in and came back out!
|
||||||
|
for (int i = 0; i != 100; ++i) {
|
||||||
|
assert(dequeued[i] == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## Producer/consumer model (simultaneous)
|
||||||
|
|
||||||
|
In this model, one set of threads is producing items,
|
||||||
|
and the other is consuming them concurrently until all of
|
||||||
|
them have been consumed. The counters are required to
|
||||||
|
ensure that all items eventually get consumed.
|
||||||
|
|
||||||
|
ConcurrentQueue<Item> q;
|
||||||
|
const int ProducerCount = 8;
|
||||||
|
const int ConsumerCount = 8;
|
||||||
|
std::thread producers[ProducerCount];
|
||||||
|
std::thread consumers[ConsumerCount];
|
||||||
|
std::atomic<int> doneProducers(0);
|
||||||
|
std::atomic<int> doneConsumers(0);
|
||||||
|
for (int i = 0; i != ProducerCount; ++i) {
|
||||||
|
producers[i] = std::thread([&]() {
|
||||||
|
while (produce) {
|
||||||
|
q.enqueue(produceItem());
|
||||||
|
}
|
||||||
|
doneProducers.fetch_add(1, std::memory_order_release);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (int i = 0; i != ConsumerCount; ++i) {
|
||||||
|
consumers[i] = std::thread([&]() {
|
||||||
|
Item item;
|
||||||
|
bool itemsLeft;
|
||||||
|
do {
|
||||||
|
// It's important to fence (if the producers have finished) *before* dequeueing
|
||||||
|
itemsLeft = doneProducers.load(std::memory_order_acquire) != ProducerCount;
|
||||||
|
while (q.try_dequeue(item)) {
|
||||||
|
itemsLeft = true;
|
||||||
|
consumeItem(item);
|
||||||
|
}
|
||||||
|
} while (itemsLeft || doneConsumers.fetch_add(1, std::memory_order_acq_rel) + 1 == ConsumerCount);
|
||||||
|
// The condition above is a bit tricky, but it's necessary to ensure that the
|
||||||
|
// last consumer sees the memory effects of all the other consumers before it
|
||||||
|
// calls try_dequeue for the last time
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (int i = 0; i != ProducerCount; ++i) {
|
||||||
|
producers[i].join();
|
||||||
|
}
|
||||||
|
for (int i = 0; i != ConsumerCount; ++i) {
|
||||||
|
consumers[i].join();
|
||||||
|
}
|
||||||
|
|
||||||
|
## Producer/consumer model (simultaneous, blocking)
|
||||||
|
|
||||||
|
The blocking version is different, since either the number of elements being produced needs
|
||||||
|
to be known ahead of time, or some other coordination is required to tell the consumers when
|
||||||
|
to stop calling wait_dequeue (not shown here). This is necessary because otherwise a consumer
|
||||||
|
could end up blocking forever -- and destroying a queue while a consumer is blocking on it leads
|
||||||
|
to undefined behaviour.
|
||||||
|
|
||||||
|
BlockingConcurrentQueue<Item> q;
|
||||||
|
const int ProducerCount = 8;
|
||||||
|
const int ConsumerCount = 8;
|
||||||
|
std::thread producers[ProducerCount];
|
||||||
|
std::thread consumers[ConsumerCount];
|
||||||
|
std::atomic<int> promisedElementsRemaining(ProducerCount * 1000);
|
||||||
|
for (int i = 0; i != ProducerCount; ++i) {
|
||||||
|
producers[i] = std::thread([&]() {
|
||||||
|
for (int j = 0; j != 1000; ++j) {
|
||||||
|
q.enqueue(produceItem());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (int i = 0; i != ConsumerCount; ++i) {
|
||||||
|
consumers[i] = std::thread([&]() {
|
||||||
|
Item item;
|
||||||
|
while (promisedElementsRemaining.fetch_sub(1, std::memory_order_relaxed)) {
|
||||||
|
q.wait_dequeue(item);
|
||||||
|
consumeItem(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (int i = 0; i != ProducerCount; ++i) {
|
||||||
|
producers[i].join();
|
||||||
|
}
|
||||||
|
for (int i = 0; i != ConsumerCount; ++i) {
|
||||||
|
consumers[i].join();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## Producer/consumer model (separate stages)
|
||||||
|
|
||||||
|
ConcurrentQueue<Item> q;
|
||||||
|
|
||||||
|
// Production stage
|
||||||
|
std::thread threads[8];
|
||||||
|
for (int i = 0; i != 8; ++i) {
|
||||||
|
threads[i] = std::thread([&]() {
|
||||||
|
while (produce) {
|
||||||
|
q.enqueue(produceItem());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (int i = 0; i != 8; ++i) {
|
||||||
|
threads[i].join();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consumption stage
|
||||||
|
std::atomic<int> doneConsumers(0);
|
||||||
|
for (int i = 0; i != 8; ++i) {
|
||||||
|
threads[i] = std::thread([&]() {
|
||||||
|
Item item;
|
||||||
|
do {
|
||||||
|
while (q.try_dequeue(item)) {
|
||||||
|
consumeItem(item);
|
||||||
|
}
|
||||||
|
// Loop again one last time if we're the last producer (with the acquired
|
||||||
|
// memory effects of the other producers):
|
||||||
|
} while (doneConsumers.fetch_add(1, std::memory_order_acq_rel) + 1 == 8);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (int i = 0; i != 8; ++i) {
|
||||||
|
threads[i].join();
|
||||||
|
}
|
||||||
|
|
||||||
|
Note that there's no point trying to use the blocking queue with this model, since
|
||||||
|
there's no need to use the `wait` methods (all the elements are produced before any
|
||||||
|
are consumed), and hence the complexity would be the same but with additional overhead.
|
||||||
|
|
||||||
|
|
||||||
|
## Object pool
|
||||||
|
|
||||||
|
If you don't know what threads will be using the queue in advance,
|
||||||
|
you can't really declare any long-term tokens. The obvious solution
|
||||||
|
is to use the implicit methods (that don't take any tokens):
|
||||||
|
|
||||||
|
// A pool of 'Something' objects that can be safely accessed
|
||||||
|
// from any thread
|
||||||
|
class SomethingPool
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Something getSomething()
|
||||||
|
{
|
||||||
|
Something obj;
|
||||||
|
queue.try_dequeue(obj);
|
||||||
|
|
||||||
|
// If the dequeue succeeded, obj will be an object from the
|
||||||
|
// thread pool, otherwise it will be the default-constructed
|
||||||
|
// object as declared above
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
void recycleSomething(Something&& obj)
|
||||||
|
{
|
||||||
|
queue.enqueue(std::move(obj));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
## Threadpool task queue
|
||||||
|
|
||||||
|
BlockingConcurrentQueue<Task> q;
|
||||||
|
|
||||||
|
// To create a task from any thread:
|
||||||
|
q.enqueue(...);
|
||||||
|
|
||||||
|
// On threadpool threads:
|
||||||
|
Task task;
|
||||||
|
while (true) {
|
||||||
|
q.wait_dequeue(task);
|
||||||
|
|
||||||
|
// Process task...
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## Multithreaded game loop
|
||||||
|
|
||||||
|
BlockingConcurrentQueue<Task> q;
|
||||||
|
std::atomic<int> pendingTasks(0);
|
||||||
|
|
||||||
|
// On threadpool threads:
|
||||||
|
Task task;
|
||||||
|
while (true) {
|
||||||
|
q.wait_dequeue(task);
|
||||||
|
|
||||||
|
// Process task...
|
||||||
|
|
||||||
|
pendingTasks.fetch_add(-1, std::memory_order_release);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whenever a new task needs to be processed for the frame:
|
||||||
|
pendingTasks.fetch_add(1, std::memory_order_release);
|
||||||
|
q.enqueue(...);
|
||||||
|
|
||||||
|
// To wait for all the frame's tasks to complete before rendering:
|
||||||
|
while (pendingTasks.load(std::memory_order_acquire) != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Alternatively you could help out the thread pool while waiting:
|
||||||
|
while (pendingTasks.load(std::memory_order_acquire) != 0) {
|
||||||
|
if (!q.try_dequeue(task)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process task...
|
||||||
|
|
||||||
|
pendingTasks.fetch_add(-1, std::memory_order_release);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## Pump until empty
|
||||||
|
|
||||||
|
This might be useful if, for example, you want to process any remaining items
|
||||||
|
in the queue before it's destroyed. Note that it is your responsibility
|
||||||
|
to ensure that the memory effects of any enqueue operations you wish to see on
|
||||||
|
the dequeue thread are visible (i.e. if you're waiting for a certain set of elements,
|
||||||
|
you need to use memory fences to ensure that those elements are visible to the dequeue
|
||||||
|
thread after they've been enqueued).
|
||||||
|
|
||||||
|
ConcurrentQueue<Item> q;
|
||||||
|
|
||||||
|
// Single-threaded pumping:
|
||||||
|
Item item;
|
||||||
|
while (q.try_dequeue(item)) {
|
||||||
|
// Process item...
|
||||||
|
}
|
||||||
|
// q is guaranteed to be empty here, unless there is another thread enqueueing still or
|
||||||
|
// there was another thread dequeueing at one point and its memory effects have not
|
||||||
|
// yet been propagated to this thread.
|
||||||
|
|
||||||
|
// Multi-threaded pumping:
|
||||||
|
std::thread threads[8];
|
||||||
|
std::atomic<int> doneConsumers(0);
|
||||||
|
for (int i = 0; i != 8; ++i) {
|
||||||
|
threads[i] = std::thread([&]() {
|
||||||
|
Item item;
|
||||||
|
do {
|
||||||
|
while (q.try_dequeue(item)) {
|
||||||
|
// Process item...
|
||||||
|
}
|
||||||
|
} while (doneConsumers.fetch_add(1, std::memory_order_acq_rel) + 1 == 8);
|
||||||
|
// If there are still enqueue operations happening on other threads,
|
||||||
|
// then the queue may not be empty at this point. However, if all enqueue
|
||||||
|
// operations completed before we finished pumping (and the propagation of
|
||||||
|
// their memory effects too), and all dequeue operations apart from those
|
||||||
|
// our threads did above completed before we finished pumping (and the
|
||||||
|
// propagation of their memory effects too), then the queue is guaranteed
|
||||||
|
// to be empty at this point.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (int i = 0; i != 8; ++i) {
|
||||||
|
threads[i].join();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## Wait for a queue to become empty (without dequeueing)
|
||||||
|
|
||||||
|
You can't (robustly) :-) However, you can set up your own atomic counter and
|
||||||
|
poll that instead (see the game loop example). If you're satisfied with merely an estimate, you can use
|
||||||
|
`size_approx()`. Note that `size_approx()` may return 0 even if the queue is
|
||||||
|
not completely empty, unless the queue has already stabilized first (no threads
|
||||||
|
are enqueueing or dequeueing, and all memory effects of any previous operations
|
||||||
|
have been propagated to the thread before it calls `size_approx()`).
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
# Normal stuff
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
*.lo
|
||||||
|
*.la
|
||||||
|
*.pc
|
||||||
|
.deps/
|
||||||
|
.libs/
|
||||||
|
.kdev4/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# kdevelop
|
||||||
|
*.kdevelop.pcs
|
||||||
|
*.kdevses
|
||||||
|
|
||||||
|
# Doxygen documentation
|
||||||
|
Doxyfile
|
||||||
|
Doxyfile.xml
|
||||||
|
doc/Doxyfile
|
||||||
|
doc/html
|
||||||
|
doc/man
|
||||||
|
doc/xml
|
||||||
|
|
||||||
|
# examples
|
||||||
|
examples/baud_test
|
||||||
|
examples/bitbang
|
||||||
|
examples/bitbang2
|
||||||
|
examples/bitbang_cbus
|
||||||
|
examples/bitbang_ft2232
|
||||||
|
examples/find_all
|
||||||
|
examples/find_all_pp
|
||||||
|
examples/serial_test
|
||||||
|
examples/simple
|
||||||
|
|
||||||
|
# Backup files and stuff from patches
|
||||||
|
*.orig
|
||||||
|
*.rej
|
||||||
|
*~
|
||||||
|
.*.swp
|
||||||
|
|
||||||
|
# libftdi specific
|
||||||
|
libftdi1-config
|
||||||
|
libftdi1.spec
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
CMakeCache.txt
|
||||||
|
cmake_install.cmake
|
||||||
|
CMakeFiles
|
||||||
|
|
||||||
|
# Misc. binaries
|
||||||
|
*.dylib
|
||||||
|
opt
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
Main developers:
|
||||||
|
|
||||||
|
Intra2net AG <opensource@intra2net.com>
|
||||||
|
|
||||||
|
Contributors in alphabetical order,
|
||||||
|
see Changelog for full details:
|
||||||
|
|
||||||
|
Adam Malinowski <amalinowski75@gmail.com>
|
||||||
|
Alain Abbas <aa@libertech.fr>
|
||||||
|
Alexander Lehmann <lehmanna@in.tum.de>
|
||||||
|
Alex Harford <harford@gmail.com>
|
||||||
|
Anders Larsen <al@alarsen.net>
|
||||||
|
Andrei Errapart <a.errapart@trenz-electronic.de>
|
||||||
|
Andrew John Rogers <andrew@rogerstech.co.uk>
|
||||||
|
Arnim Läuger <arnim.laeuger@gmx.net>
|
||||||
|
Aurelien Jarno <aurelien@aurel32.net>
|
||||||
|
Benjamin Vanheuverzwijn <bvanheu@gmail.com>
|
||||||
|
Chris Morgan <chmorgan@gmail.com>
|
||||||
|
Chris Zeh <chris.w.zeh@gmail.com>
|
||||||
|
Clifford Wolf <clifford@clifford.at>
|
||||||
|
Daniel Kirkham <dk2@kirkham.id.au>
|
||||||
|
David Challis <dchallis@qsimaging.com>
|
||||||
|
Davide Michelizza <dmichelizza@gmail.com>
|
||||||
|
Denis Sirotkin <reg.libftdi@demitel.ru>
|
||||||
|
Emil <emil@datel.co.uk>
|
||||||
|
Eric Schott <eric@morningjoy.com>
|
||||||
|
Eugene Hutorny <eugene@hutorny.in.ua>
|
||||||
|
Evan Nemerson <evan@coeus-group.com>
|
||||||
|
Evgeny Sinelnikov <sin@geoft.ru>
|
||||||
|
Fahrzin Hemmati <fahhem@gmail.com>
|
||||||
|
Flynn Marquardt <ftdi@flynnux.de>
|
||||||
|
Forest Crossman <cyrozap@gmail.com>
|
||||||
|
Ian Abbott <abbotti@mev.co.uk>
|
||||||
|
Jared Boone <jared@sharebrained.com>
|
||||||
|
Jarkko Sonninen <kasper@iki.fi>
|
||||||
|
Jean-Daniel Merkli <jdmerkli@computerscience.ch>
|
||||||
|
Jochen Sprickerhof <jochen@sprickerhof.de>
|
||||||
|
Joe Zbiciak <intvnut@gmail.com>
|
||||||
|
Jon Beniston <jon@beniston.com>
|
||||||
|
Juergen Beisert <juergen.beisert@weihenstephan.org>
|
||||||
|
Lorenz Moesenlechner <lorenz@hcilab.org>
|
||||||
|
Marek Vavruša <marek@vavrusa.com>
|
||||||
|
Marius Kintel <kintel@sim.no>
|
||||||
|
Mark Hämmerling <mail@markh.de>
|
||||||
|
Matthias Janke <janke@physi.uni-heidelberg.de>
|
||||||
|
Matthias Kranz <matthias@hcilab.org>
|
||||||
|
Matthias Richter <mail.to.mr@gmx.de>
|
||||||
|
Matthijs ten Berge <m.h.tenberge@alumnus.utwente.nl>
|
||||||
|
Max <max@koeln.ccc.de>
|
||||||
|
Maxwell Dreytser <admin@mdtech.us>
|
||||||
|
Michel Zou <xantares09@hotmail.com>
|
||||||
|
Mike Frysinger <vapier.adi@gmail.com>
|
||||||
|
Nathael Pajani <nathael.pajani@ed3l.fr>
|
||||||
|
Nathan Fraser <ndf@undershorts.org>
|
||||||
|
Oleg Seiljus <oseiljus@xverve.com>
|
||||||
|
Paul Fertser <fercerpav@gmail.com>
|
||||||
|
Peter Holik <peter@holik.at>
|
||||||
|
Raphael Assenat <raph@8d.com>
|
||||||
|
Robert Cox <Robert.cox@novatechweb.com>
|
||||||
|
Robin Haberkorn <haberkorn@metratec.com>
|
||||||
|
Rodney Sinclair <rodney@sinclairrf.com>
|
||||||
|
Rogier Wolff <R.E.Wolff@harddisk-recovery.nl>
|
||||||
|
Rolf Fiedler <derRolf@gmx-topmail.de>
|
||||||
|
Salvador Eduardo Tropea <salvador@inti.gob.ar>
|
||||||
|
Stephan Linz <linz@li-pro.net>
|
||||||
|
Steven Turner <steven.turner@ftdichip.com>
|
||||||
|
Tarek Heiland <tarek@illimitable.com>
|
||||||
|
Thilo Schulz <thilo@tjps.eu>
|
||||||
|
Thimo Eichstaedt <abc@digithi.de>
|
||||||
|
Thomas Fischl <fischl@fundf.net>
|
||||||
|
Thomas Klose <thomas.klose@hiperscan.com>
|
||||||
|
Tim Ansell <mithro@mithis.com>
|
||||||
|
Tom Saunders <trsaunders@gmail.com>
|
||||||
|
Uwe Bonnes <bon@elektron.ikp.physik.tu-darmstadt.de>
|
||||||
|
Vladimir Yakovlev <nagos@inbox.ru>
|
||||||
|
Wilfried Holzke <libftdi@holzke.net>
|
||||||
|
Xiaofan Chen <xiaofanc@gmail.com>
|
||||||
|
Yegor Yefremov <yegorslists@googlemail.com>
|
||||||
|
Yi-Shin Li <ysli@araisrobo.com>
|
||||||
|
|
@ -0,0 +1,244 @@
|
||||||
|
# Project
|
||||||
|
project(libftdi1)
|
||||||
|
set(MAJOR_VERSION 1)
|
||||||
|
set(MINOR_VERSION 4)
|
||||||
|
set(PACKAGE libftdi1)
|
||||||
|
set(VERSION_STRING ${MAJOR_VERSION}.${MINOR_VERSION})
|
||||||
|
set(VERSION ${VERSION_STRING})
|
||||||
|
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
if("${CMAKE_BUILD_TYPE}" STREQUAL "")
|
||||||
|
set(CMAKE_BUILD_TYPE RelWithDebInfo)
|
||||||
|
endif("${CMAKE_BUILD_TYPE}" STREQUAL "")
|
||||||
|
set(CMAKE_COLOR_MAKEFILE ON)
|
||||||
|
cmake_minimum_required(VERSION 2.6 FATAL_ERROR)
|
||||||
|
|
||||||
|
add_definitions(-Wall)
|
||||||
|
|
||||||
|
# Debug build
|
||||||
|
message("-- Build type: ${CMAKE_BUILD_TYPE}")
|
||||||
|
if(${CMAKE_BUILD_TYPE} STREQUAL Debug)
|
||||||
|
add_definitions(-DDEBUG)
|
||||||
|
endif(${CMAKE_BUILD_TYPE} STREQUAL Debug)
|
||||||
|
|
||||||
|
# find libusb
|
||||||
|
find_package ( USB1 REQUIRED )
|
||||||
|
include_directories ( ${LIBUSB_INCLUDE_DIR} )
|
||||||
|
|
||||||
|
# Find Boost (optional package)
|
||||||
|
find_package(Boost)
|
||||||
|
|
||||||
|
# Set components
|
||||||
|
set(CPACK_COMPONENTS_ALL sharedlibs staticlibs headers)
|
||||||
|
set(CPACK_COMPONENT_SHAREDLIBS_DISPLAY_NAME "Shared libraries")
|
||||||
|
set(CPACK_COMPONENT_STATICLIBS_DISPLAY_NAME "Static libraries")
|
||||||
|
set(CPACK_COMPONENT_HEADERS_DISPLAY_NAME "C++ Headers")
|
||||||
|
|
||||||
|
set(CPACK_COMPONENT_SHAREDLIBS_DESCRIPTION
|
||||||
|
"Shared library for general use.")
|
||||||
|
set(CPACK_COMPONENT_STATICLIBS_DESCRIPTION
|
||||||
|
"Static library, good if you want to embed libftdi1 in your application.")
|
||||||
|
set(CPACK_COMPONENT_HEADERS_DESCRIPTION
|
||||||
|
"C/C++ header files.")
|
||||||
|
|
||||||
|
set(CPACK_COMPONENT_SHAREDLIBS_GROUP "Development")
|
||||||
|
set(CPACK_COMPONENT_STATICLIBS_GROUP "Development")
|
||||||
|
set(CPACK_COMPONENT_HEADERS_GROUP "Development")
|
||||||
|
|
||||||
|
option ( STATICLIBS "Build static libraries" ON )
|
||||||
|
|
||||||
|
# guess LIB_SUFFIX, don't take debian multiarch into account
|
||||||
|
if ( NOT DEFINED LIB_SUFFIX )
|
||||||
|
if( CMAKE_SYSTEM_NAME MATCHES "Linux"
|
||||||
|
AND NOT CMAKE_CROSSCOMPILING
|
||||||
|
AND NOT EXISTS "/etc/debian_version"
|
||||||
|
AND NOT EXISTS "/etc/arch-release" )
|
||||||
|
if ( "${CMAKE_SIZEOF_VOID_P}" EQUAL "8" )
|
||||||
|
set ( LIB_SUFFIX 64 )
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if(NOT APPLE)
|
||||||
|
if(CMAKE_SIZEOF_VOID_P EQUAL 4)
|
||||||
|
SET(PACK_ARCH "")
|
||||||
|
else(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||||
|
SET(PACK_ARCH .x86_64)
|
||||||
|
endif(CMAKE_SIZEOF_VOID_P EQUAL 4)
|
||||||
|
else(NOT APPLE)
|
||||||
|
SET(PACK_ARCH "")
|
||||||
|
endif(NOT APPLE)
|
||||||
|
|
||||||
|
# Package information
|
||||||
|
set(CPACK_PACKAGE_VERSION ${VERSION_STRING})
|
||||||
|
set(CPACK_PACKAGE_CONTACT "Intra2net AG <libftdi@developer.intra2net.com>")
|
||||||
|
set(CPACK_PACKAGE_DESCRIPTION "libftdi1 library.")
|
||||||
|
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY ${CPACK_PACKAGE_DESCRIPTION}
|
||||||
|
)
|
||||||
|
# Package settings
|
||||||
|
if ( UNIX )
|
||||||
|
set(CPACK_GENERATOR "DEB;RPM")
|
||||||
|
set(CPACK_CMAKE_GENERATOR "Unix Makefiles")
|
||||||
|
set(CPACK_PACKAGE_NAME ${PROJECT_NAME})
|
||||||
|
set(CPACK_PACKAGE_FILE_NAME ${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}${PACK_ARCH})
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if ( WIN32 )
|
||||||
|
set ( CPACK_GENERATOR "NSIS" )
|
||||||
|
set ( CPACK_CMAKE_GENERATOR "MinGW Makefiles" )
|
||||||
|
set ( CPACK_PACKAGE_NAME "${PROJECT_NAME}" )
|
||||||
|
set ( CPACK_PACKAGE_VENDOR "" )
|
||||||
|
set ( CPACK_PACKAGE_INSTALL_DIRECTORY "libftdi1" )
|
||||||
|
set ( CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${VERSION_STRING}-win32")
|
||||||
|
set ( CPACK_NSIS_DISPLAY_NAME "libftdi1" )
|
||||||
|
set ( CPACK_NSIS_MODIFY_PATH ON )
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set(CPACK_RESOURCE_FILE_LICENSE ${PROJECT_SOURCE_DIR}/LICENSE)
|
||||||
|
|
||||||
|
set(CPACK_SOURCE_GENERATOR TGZ)
|
||||||
|
set(CPACK_SOURCE_IGNORE_FILES "\\\\.git;~$;build/")
|
||||||
|
set(CPACK_SOURCE_PACKAGE_FILE_NAME ${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION})
|
||||||
|
|
||||||
|
# Subdirectories
|
||||||
|
if ( UNIX )
|
||||||
|
set ( CPACK_SET_DESTDIR ON )
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
# "make dist" target
|
||||||
|
set(ARCHIVE_NAME ${CMAKE_PROJECT_NAME}-${VERSION_STRING})
|
||||||
|
add_custom_target(dist
|
||||||
|
COMMAND git archive --prefix=${ARCHIVE_NAME}/ HEAD
|
||||||
|
| bzip2 > ${CMAKE_BINARY_DIR}/${ARCHIVE_NAME}.tar.bz2
|
||||||
|
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
|
||||||
|
|
||||||
|
# Tests
|
||||||
|
option ( BUILD_TESTS "Build unit tests with Boost Unit Test framework" ON )
|
||||||
|
|
||||||
|
# Documentation
|
||||||
|
option ( DOCUMENTATION "Generate API documentation with Doxygen" ON )
|
||||||
|
|
||||||
|
|
||||||
|
find_package ( Doxygen )
|
||||||
|
if ( DOCUMENTATION AND DOXYGEN_FOUND )
|
||||||
|
|
||||||
|
# Find doxy config
|
||||||
|
message(STATUS "Doxygen found.")
|
||||||
|
|
||||||
|
# Copy doxy.config.in
|
||||||
|
set(top_srcdir ${PROJECT_SOURCE_DIR})
|
||||||
|
configure_file(${PROJECT_SOURCE_DIR}/doc/Doxyfile.in ${CMAKE_BINARY_DIR}/Doxyfile )
|
||||||
|
configure_file(${PROJECT_SOURCE_DIR}/doc/Doxyfile.xml.in ${CMAKE_BINARY_DIR}/Doxyfile.xml )
|
||||||
|
|
||||||
|
# Run doxygen
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${CMAKE_BINARY_DIR}/doc/html/index.html
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/doc
|
||||||
|
COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_BINARY_DIR}/Doxyfile
|
||||||
|
DEPENDS ${c_headers};${c_sources};${cpp_sources};${cpp_headers}
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_target(docs ALL DEPENDS ${CMAKE_BINARY_DIR}/doc/html/index.html)
|
||||||
|
|
||||||
|
message(STATUS "Generating API documentation with Doxygen")
|
||||||
|
else(DOCUMENTATION AND DOXYGEN_FOUND)
|
||||||
|
message(STATUS "Not generating API documentation")
|
||||||
|
endif(DOCUMENTATION AND DOXYGEN_FOUND)
|
||||||
|
|
||||||
|
add_subdirectory(src)
|
||||||
|
add_subdirectory(ftdipp)
|
||||||
|
add_subdirectory(python)
|
||||||
|
add_subdirectory(ftdi_eeprom)
|
||||||
|
add_subdirectory(examples)
|
||||||
|
add_subdirectory(packages)
|
||||||
|
add_subdirectory(test)
|
||||||
|
|
||||||
|
# PkgConfig
|
||||||
|
set(prefix ${CMAKE_INSTALL_PREFIX})
|
||||||
|
set(exec_prefix ${CMAKE_INSTALL_PREFIX}/bin)
|
||||||
|
set(includedir ${CMAKE_INSTALL_PREFIX}/include/${PROJECT_NAME})
|
||||||
|
|
||||||
|
if(${UNIX})
|
||||||
|
set(libdir ${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX})
|
||||||
|
endif(${UNIX})
|
||||||
|
if(${WIN32})
|
||||||
|
set(libdir ${CMAKE_INSTALL_PREFIX}/bin)
|
||||||
|
endif(${WIN32})
|
||||||
|
|
||||||
|
configure_file(${PROJECT_SOURCE_DIR}/libftdi1.spec.in ${CMAKE_BINARY_DIR}/libftdi1.spec @ONLY)
|
||||||
|
configure_file(${PROJECT_SOURCE_DIR}/libftdi1.pc.in ${CMAKE_BINARY_DIR}/libftdi1.pc @ONLY)
|
||||||
|
configure_file(${PROJECT_SOURCE_DIR}/libftdipp1.pc.in ${CMAKE_BINARY_DIR}/libftdipp1.pc @ONLY)
|
||||||
|
install(FILES ${CMAKE_BINARY_DIR}/libftdi1.pc ${CMAKE_BINARY_DIR}/libftdipp1.pc
|
||||||
|
DESTINATION lib${LIB_SUFFIX}/pkgconfig)
|
||||||
|
|
||||||
|
if (UNIX OR MINGW)
|
||||||
|
configure_file ( libftdi1-config.in ${CMAKE_CURRENT_BINARY_DIR}/libftdi1-config @ONLY )
|
||||||
|
install ( PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/libftdi1-config
|
||||||
|
DESTINATION bin )
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
# config script install path
|
||||||
|
if ( NOT DEFINED LIBFTDI_CMAKE_CONFIG_DIR )
|
||||||
|
set ( LIBFTDI_CMAKE_CONFIG_DIR lib${LIB_SUFFIX}/cmake/libftdi1 )
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set ( LIBFTDI_INCLUDE_DIR ${includedir} )
|
||||||
|
set ( LIBFTDI_INCLUDE_DIRS ${LIBFTDI_INCLUDE_DIR} )
|
||||||
|
set ( LIBFTDI_LIBRARY ftdi1 )
|
||||||
|
set ( LIBFTDI_LIBRARIES ${LIBFTDI_LIBRARY} )
|
||||||
|
list ( APPEND LIBFTDI_LIBRARIES ${LIBUSB_LIBRARIES} )
|
||||||
|
set ( LIBFTDI_STATIC_LIBRARY ftdi1.a )
|
||||||
|
set ( LIBFTDI_STATIC_LIBRARIES ${LIBFTDI_STATIC_LIBRARY} )
|
||||||
|
list ( APPEND LIBFTDI_STATIC_LIBRARIES ${LIBUSB_LIBRARIES} )
|
||||||
|
if (FTDI_BUILD_CPP)
|
||||||
|
set ( LIBFTDIPP_LIBRARY ftdipp1 )
|
||||||
|
set ( LIBFTDIPP_LIBRARIES ${LIBFTDIPP_LIBRARY} )
|
||||||
|
list ( APPEND LIBFTDIPP_LIBRARIES ${LIBUSB_LIBRARIES} )
|
||||||
|
endif ()
|
||||||
|
set ( LIBFTDI_LIBRARY_DIRS ${libdir} )
|
||||||
|
set ( LIBFTDI_ROOT_DIR ${prefix} )
|
||||||
|
set ( LIBFTDI_VERSION_STRING ${VERSION_STRING} )
|
||||||
|
set ( LIBFTDI_VERSION_MAJOR ${MAJOR_VERSION} )
|
||||||
|
set ( LIBFTDI_VERSION_MINOR ${MINOR_VERSION} )
|
||||||
|
|
||||||
|
set ( LIBFTDI_USE_FILE ${CMAKE_INSTALL_PREFIX}/${LIBFTDI_CMAKE_CONFIG_DIR}/UseLibFTDI1.cmake )
|
||||||
|
|
||||||
|
if(CMAKE_VERSION VERSION_LESS 2.8.8)
|
||||||
|
configure_file ( cmake/LibFTDI1Config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/LibFTDI1Config.cmake @ONLY )
|
||||||
|
configure_file ( cmake/LibFTDI1ConfigVersion.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/LibFTDI1ConfigVersion.cmake @ONLY )
|
||||||
|
else ()
|
||||||
|
include(CMakePackageConfigHelpers)
|
||||||
|
|
||||||
|
configure_package_config_file (
|
||||||
|
cmake/LibFTDI1Config.cmake.in
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/LibFTDI1Config.cmake
|
||||||
|
INSTALL_DESTINATION ${LIBFTDI_CMAKE_CONFIG_DIR}
|
||||||
|
PATH_VARS
|
||||||
|
LIBFTDI_USE_FILE
|
||||||
|
LIBFTDI_ROOT_DIR
|
||||||
|
LIBFTDI_INCLUDE_DIR
|
||||||
|
LIBFTDI_INCLUDE_DIRS
|
||||||
|
LIBFTDI_LIBRARY_DIRS
|
||||||
|
NO_CHECK_REQUIRED_COMPONENTS_MACRO
|
||||||
|
)
|
||||||
|
write_basic_package_version_file (
|
||||||
|
LibFTDI1ConfigVersion.cmake
|
||||||
|
VERSION ${LIBFTDI_VERSION_STRING}
|
||||||
|
COMPATIBILITY AnyNewerVersion
|
||||||
|
)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
|
||||||
|
install ( FILES
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/LibFTDI1Config.cmake
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/LibFTDI1ConfigVersion.cmake
|
||||||
|
cmake/UseLibFTDI1.cmake
|
||||||
|
|
||||||
|
DESTINATION ${LIBFTDI_CMAKE_CONFIG_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
include(CPack)
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
3. The name of the author may not be used to endorse or promote products
|
||||||
|
derived from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
@ -0,0 +1,339 @@
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 2, June 1991
|
||||||
|
|
||||||
|
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The licenses for most software are designed to take away your
|
||||||
|
freedom to share and change it. By contrast, the GNU General Public
|
||||||
|
License is intended to guarantee your freedom to share and change free
|
||||||
|
software--to make sure the software is free for all its users. This
|
||||||
|
General Public License applies to most of the Free Software
|
||||||
|
Foundation's software and to any other program whose authors commit to
|
||||||
|
using it. (Some other Free Software Foundation software is covered by
|
||||||
|
the GNU Lesser General Public License instead.) You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
this service if you wish), that you receive source code or can get it
|
||||||
|
if you want it, that you can change the software or use pieces of it
|
||||||
|
in new free programs; and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to make restrictions that forbid
|
||||||
|
anyone to deny you these rights or to ask you to surrender the rights.
|
||||||
|
These restrictions translate to certain responsibilities for you if you
|
||||||
|
distribute copies of the software, or if you modify it.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must give the recipients all the rights that
|
||||||
|
you have. You must make sure that they, too, receive or can get the
|
||||||
|
source code. And you must show them these terms so they know their
|
||||||
|
rights.
|
||||||
|
|
||||||
|
We protect your rights with two steps: (1) copyright the software, and
|
||||||
|
(2) offer you this license which gives you legal permission to copy,
|
||||||
|
distribute and/or modify the software.
|
||||||
|
|
||||||
|
Also, for each author's protection and ours, we want to make certain
|
||||||
|
that everyone understands that there is no warranty for this free
|
||||||
|
software. If the software is modified by someone else and passed on, we
|
||||||
|
want its recipients to know that what they have is not the original, so
|
||||||
|
that any problems introduced by others will not reflect on the original
|
||||||
|
authors' reputations.
|
||||||
|
|
||||||
|
Finally, any free program is threatened constantly by software
|
||||||
|
patents. We wish to avoid the danger that redistributors of a free
|
||||||
|
program will individually obtain patent licenses, in effect making the
|
||||||
|
program proprietary. To prevent this, we have made it clear that any
|
||||||
|
patent must be licensed for everyone's free use or not licensed at all.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. This License applies to any program or other work which contains
|
||||||
|
a notice placed by the copyright holder saying it may be distributed
|
||||||
|
under the terms of this General Public License. The "Program", below,
|
||||||
|
refers to any such program or work, and a "work based on the Program"
|
||||||
|
means either the Program or any derivative work under copyright law:
|
||||||
|
that is to say, a work containing the Program or a portion of it,
|
||||||
|
either verbatim or with modifications and/or translated into another
|
||||||
|
language. (Hereinafter, translation is included without limitation in
|
||||||
|
the term "modification".) Each licensee is addressed as "you".
|
||||||
|
|
||||||
|
Activities other than copying, distribution and modification are not
|
||||||
|
covered by this License; they are outside its scope. The act of
|
||||||
|
running the Program is not restricted, and the output from the Program
|
||||||
|
is covered only if its contents constitute a work based on the
|
||||||
|
Program (independent of having been made by running the Program).
|
||||||
|
Whether that is true depends on what the Program does.
|
||||||
|
|
||||||
|
1. You may copy and distribute verbatim copies of the Program's
|
||||||
|
source code as you receive it, in any medium, provided that you
|
||||||
|
conspicuously and appropriately publish on each copy an appropriate
|
||||||
|
copyright notice and disclaimer of warranty; keep intact all the
|
||||||
|
notices that refer to this License and to the absence of any warranty;
|
||||||
|
and give any other recipients of the Program a copy of this License
|
||||||
|
along with the Program.
|
||||||
|
|
||||||
|
You may charge a fee for the physical act of transferring a copy, and
|
||||||
|
you may at your option offer warranty protection in exchange for a fee.
|
||||||
|
|
||||||
|
2. You may modify your copy or copies of the Program or any portion
|
||||||
|
of it, thus forming a work based on the Program, and copy and
|
||||||
|
distribute such modifications or work under the terms of Section 1
|
||||||
|
above, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) You must cause the modified files to carry prominent notices
|
||||||
|
stating that you changed the files and the date of any change.
|
||||||
|
|
||||||
|
b) You must cause any work that you distribute or publish, that in
|
||||||
|
whole or in part contains or is derived from the Program or any
|
||||||
|
part thereof, to be licensed as a whole at no charge to all third
|
||||||
|
parties under the terms of this License.
|
||||||
|
|
||||||
|
c) If the modified program normally reads commands interactively
|
||||||
|
when run, you must cause it, when started running for such
|
||||||
|
interactive use in the most ordinary way, to print or display an
|
||||||
|
announcement including an appropriate copyright notice and a
|
||||||
|
notice that there is no warranty (or else, saying that you provide
|
||||||
|
a warranty) and that users may redistribute the program under
|
||||||
|
these conditions, and telling the user how to view a copy of this
|
||||||
|
License. (Exception: if the Program itself is interactive but
|
||||||
|
does not normally print such an announcement, your work based on
|
||||||
|
the Program is not required to print an announcement.)
|
||||||
|
|
||||||
|
These requirements apply to the modified work as a whole. If
|
||||||
|
identifiable sections of that work are not derived from the Program,
|
||||||
|
and can be reasonably considered independent and separate works in
|
||||||
|
themselves, then this License, and its terms, do not apply to those
|
||||||
|
sections when you distribute them as separate works. But when you
|
||||||
|
distribute the same sections as part of a whole which is a work based
|
||||||
|
on the Program, the distribution of the whole must be on the terms of
|
||||||
|
this License, whose permissions for other licensees extend to the
|
||||||
|
entire whole, and thus to each and every part regardless of who wrote it.
|
||||||
|
|
||||||
|
Thus, it is not the intent of this section to claim rights or contest
|
||||||
|
your rights to work written entirely by you; rather, the intent is to
|
||||||
|
exercise the right to control the distribution of derivative or
|
||||||
|
collective works based on the Program.
|
||||||
|
|
||||||
|
In addition, mere aggregation of another work not based on the Program
|
||||||
|
with the Program (or with a work based on the Program) on a volume of
|
||||||
|
a storage or distribution medium does not bring the other work under
|
||||||
|
the scope of this License.
|
||||||
|
|
||||||
|
3. You may copy and distribute the Program (or a work based on it,
|
||||||
|
under Section 2) in object code or executable form under the terms of
|
||||||
|
Sections 1 and 2 above provided that you also do one of the following:
|
||||||
|
|
||||||
|
a) Accompany it with the complete corresponding machine-readable
|
||||||
|
source code, which must be distributed under the terms of Sections
|
||||||
|
1 and 2 above on a medium customarily used for software interchange; or,
|
||||||
|
|
||||||
|
b) Accompany it with a written offer, valid for at least three
|
||||||
|
years, to give any third party, for a charge no more than your
|
||||||
|
cost of physically performing source distribution, a complete
|
||||||
|
machine-readable copy of the corresponding source code, to be
|
||||||
|
distributed under the terms of Sections 1 and 2 above on a medium
|
||||||
|
customarily used for software interchange; or,
|
||||||
|
|
||||||
|
c) Accompany it with the information you received as to the offer
|
||||||
|
to distribute corresponding source code. (This alternative is
|
||||||
|
allowed only for noncommercial distribution and only if you
|
||||||
|
received the program in object code or executable form with such
|
||||||
|
an offer, in accord with Subsection b above.)
|
||||||
|
|
||||||
|
The source code for a work means the preferred form of the work for
|
||||||
|
making modifications to it. For an executable work, complete source
|
||||||
|
code means all the source code for all modules it contains, plus any
|
||||||
|
associated interface definition files, plus the scripts used to
|
||||||
|
control compilation and installation of the executable. However, as a
|
||||||
|
special exception, the source code distributed need not include
|
||||||
|
anything that is normally distributed (in either source or binary
|
||||||
|
form) with the major components (compiler, kernel, and so on) of the
|
||||||
|
operating system on which the executable runs, unless that component
|
||||||
|
itself accompanies the executable.
|
||||||
|
|
||||||
|
If distribution of executable or object code is made by offering
|
||||||
|
access to copy from a designated place, then offering equivalent
|
||||||
|
access to copy the source code from the same place counts as
|
||||||
|
distribution of the source code, even though third parties are not
|
||||||
|
compelled to copy the source along with the object code.
|
||||||
|
|
||||||
|
4. You may not copy, modify, sublicense, or distribute the Program
|
||||||
|
except as expressly provided under this License. Any attempt
|
||||||
|
otherwise to copy, modify, sublicense or distribute the Program is
|
||||||
|
void, and will automatically terminate your rights under this License.
|
||||||
|
However, parties who have received copies, or rights, from you under
|
||||||
|
this License will not have their licenses terminated so long as such
|
||||||
|
parties remain in full compliance.
|
||||||
|
|
||||||
|
5. You are not required to accept this License, since you have not
|
||||||
|
signed it. However, nothing else grants you permission to modify or
|
||||||
|
distribute the Program or its derivative works. These actions are
|
||||||
|
prohibited by law if you do not accept this License. Therefore, by
|
||||||
|
modifying or distributing the Program (or any work based on the
|
||||||
|
Program), you indicate your acceptance of this License to do so, and
|
||||||
|
all its terms and conditions for copying, distributing or modifying
|
||||||
|
the Program or works based on it.
|
||||||
|
|
||||||
|
6. Each time you redistribute the Program (or any work based on the
|
||||||
|
Program), the recipient automatically receives a license from the
|
||||||
|
original licensor to copy, distribute or modify the Program subject to
|
||||||
|
these terms and conditions. You may not impose any further
|
||||||
|
restrictions on the recipients' exercise of the rights granted herein.
|
||||||
|
You are not responsible for enforcing compliance by third parties to
|
||||||
|
this License.
|
||||||
|
|
||||||
|
7. If, as a consequence of a court judgment or allegation of patent
|
||||||
|
infringement or for any other reason (not limited to patent issues),
|
||||||
|
conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot
|
||||||
|
distribute so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you
|
||||||
|
may not distribute the Program at all. For example, if a patent
|
||||||
|
license would not permit royalty-free redistribution of the Program by
|
||||||
|
all those who receive copies directly or indirectly through you, then
|
||||||
|
the only way you could satisfy both it and this License would be to
|
||||||
|
refrain entirely from distribution of the Program.
|
||||||
|
|
||||||
|
If any portion of this section is held invalid or unenforceable under
|
||||||
|
any particular circumstance, the balance of the section is intended to
|
||||||
|
apply and the section as a whole is intended to apply in other
|
||||||
|
circumstances.
|
||||||
|
|
||||||
|
It is not the purpose of this section to induce you to infringe any
|
||||||
|
patents or other property right claims or to contest validity of any
|
||||||
|
such claims; this section has the sole purpose of protecting the
|
||||||
|
integrity of the free software distribution system, which is
|
||||||
|
implemented by public license practices. Many people have made
|
||||||
|
generous contributions to the wide range of software distributed
|
||||||
|
through that system in reliance on consistent application of that
|
||||||
|
system; it is up to the author/donor to decide if he or she is willing
|
||||||
|
to distribute software through any other system and a licensee cannot
|
||||||
|
impose that choice.
|
||||||
|
|
||||||
|
This section is intended to make thoroughly clear what is believed to
|
||||||
|
be a consequence of the rest of this License.
|
||||||
|
|
||||||
|
8. If the distribution and/or use of the Program is restricted in
|
||||||
|
certain countries either by patents or by copyrighted interfaces, the
|
||||||
|
original copyright holder who places the Program under this License
|
||||||
|
may add an explicit geographical distribution limitation excluding
|
||||||
|
those countries, so that distribution is permitted only in or among
|
||||||
|
countries not thus excluded. In such case, this License incorporates
|
||||||
|
the limitation as if written in the body of this License.
|
||||||
|
|
||||||
|
9. The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the Program
|
||||||
|
specifies a version number of this License which applies to it and "any
|
||||||
|
later version", you have the option of following the terms and conditions
|
||||||
|
either of that version or of any later version published by the Free
|
||||||
|
Software Foundation. If the Program does not specify a version number of
|
||||||
|
this License, you may choose any version ever published by the Free Software
|
||||||
|
Foundation.
|
||||||
|
|
||||||
|
10. If you wish to incorporate parts of the Program into other free
|
||||||
|
programs whose distribution conditions are different, write to the author
|
||||||
|
to ask for permission. For software which is copyrighted by the Free
|
||||||
|
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||||
|
make exceptions for this. Our decision will be guided by the two goals
|
||||||
|
of preserving the free status of all derivatives of our free software and
|
||||||
|
of promoting the sharing and reuse of software generally.
|
||||||
|
|
||||||
|
NO WARRANTY
|
||||||
|
|
||||||
|
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||||
|
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||||
|
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||||
|
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||||
|
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||||
|
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||||
|
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||||
|
REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||||
|
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||||
|
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||||
|
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||||
|
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||||
|
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGES.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
convey the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program is interactive, make it output a short notice like this
|
||||||
|
when it starts in an interactive mode:
|
||||||
|
|
||||||
|
Gnomovision version 69, Copyright (C) year name of author
|
||||||
|
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, the commands you use may
|
||||||
|
be called something other than `show w' and `show c'; they could even be
|
||||||
|
mouse-clicks or menu items--whatever suits your program.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or your
|
||||||
|
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||||
|
necessary. Here is a sample; alter the names:
|
||||||
|
|
||||||
|
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||||
|
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||||
|
|
||||||
|
<signature of Ty Coon>, 1 April 1989
|
||||||
|
Ty Coon, President of Vice
|
||||||
|
|
||||||
|
This General Public License does not permit incorporating your program into
|
||||||
|
proprietary programs. If your program is a subroutine library, you may
|
||||||
|
consider it more useful to permit linking proprietary applications with the
|
||||||
|
library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License.
|
||||||
|
|
@ -0,0 +1,481 @@
|
||||||
|
GNU LIBRARY GENERAL PUBLIC LICENSE
|
||||||
|
Version 2, June 1991
|
||||||
|
|
||||||
|
Copyright (C) 1991 Free Software Foundation, Inc.
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
[This is the first released version of the library GPL. It is
|
||||||
|
numbered 2 because it goes with version 2 of the ordinary GPL.]
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The licenses for most software are designed to take away your
|
||||||
|
freedom to share and change it. By contrast, the GNU General Public
|
||||||
|
Licenses are intended to guarantee your freedom to share and change
|
||||||
|
free software--to make sure the software is free for all its users.
|
||||||
|
|
||||||
|
This license, the Library General Public License, applies to some
|
||||||
|
specially designated Free Software Foundation software, and to any
|
||||||
|
other libraries whose authors decide to use it. You can use it for
|
||||||
|
your libraries, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
this service if you wish), that you receive source code or can get it
|
||||||
|
if you want it, that you can change the software or use pieces of it
|
||||||
|
in new free programs; and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to make restrictions that forbid
|
||||||
|
anyone to deny you these rights or to ask you to surrender the rights.
|
||||||
|
These restrictions translate to certain responsibilities for you if
|
||||||
|
you distribute copies of the library, or if you modify it.
|
||||||
|
|
||||||
|
For example, if you distribute copies of the library, whether gratis
|
||||||
|
or for a fee, you must give the recipients all the rights that we gave
|
||||||
|
you. You must make sure that they, too, receive or can get the source
|
||||||
|
code. If you link a program with the library, you must provide
|
||||||
|
complete object files to the recipients so that they can relink them
|
||||||
|
with the library, after making changes to the library and recompiling
|
||||||
|
it. And you must show them these terms so they know their rights.
|
||||||
|
|
||||||
|
Our method of protecting your rights has two steps: (1) copyright
|
||||||
|
the library, and (2) offer you this license which gives you legal
|
||||||
|
permission to copy, distribute and/or modify the library.
|
||||||
|
|
||||||
|
Also, for each distributor's protection, we want to make certain
|
||||||
|
that everyone understands that there is no warranty for this free
|
||||||
|
library. If the library is modified by someone else and passed on, we
|
||||||
|
want its recipients to know that what they have is not the original
|
||||||
|
version, so that any problems introduced by others will not reflect on
|
||||||
|
the original authors' reputations.
|
||||||
|
|
||||||
|
Finally, any free program is threatened constantly by software
|
||||||
|
patents. We wish to avoid the danger that companies distributing free
|
||||||
|
software will individually obtain patent licenses, thus in effect
|
||||||
|
transforming the program into proprietary software. To prevent this,
|
||||||
|
we have made it clear that any patent must be licensed for everyone's
|
||||||
|
free use or not licensed at all.
|
||||||
|
|
||||||
|
Most GNU software, including some libraries, is covered by the ordinary
|
||||||
|
GNU General Public License, which was designed for utility programs. This
|
||||||
|
license, the GNU Library General Public License, applies to certain
|
||||||
|
designated libraries. This license is quite different from the ordinary
|
||||||
|
one; be sure to read it in full, and don't assume that anything in it is
|
||||||
|
the same as in the ordinary license.
|
||||||
|
|
||||||
|
The reason we have a separate public license for some libraries is that
|
||||||
|
they blur the distinction we usually make between modifying or adding to a
|
||||||
|
program and simply using it. Linking a program with a library, without
|
||||||
|
changing the library, is in some sense simply using the library, and is
|
||||||
|
analogous to running a utility program or application program. However, in
|
||||||
|
a textual and legal sense, the linked executable is a combined work, a
|
||||||
|
derivative of the original library, and the ordinary General Public License
|
||||||
|
treats it as such.
|
||||||
|
|
||||||
|
Because of this blurred distinction, using the ordinary General
|
||||||
|
Public License for libraries did not effectively promote software
|
||||||
|
sharing, because most developers did not use the libraries. We
|
||||||
|
concluded that weaker conditions might promote sharing better.
|
||||||
|
|
||||||
|
However, unrestricted linking of non-free programs would deprive the
|
||||||
|
users of those programs of all benefit from the free status of the
|
||||||
|
libraries themselves. This Library General Public License is intended to
|
||||||
|
permit developers of non-free programs to use free libraries, while
|
||||||
|
preserving your freedom as a user of such programs to change the free
|
||||||
|
libraries that are incorporated in them. (We have not seen how to achieve
|
||||||
|
this as regards changes in header files, but we have achieved it as regards
|
||||||
|
changes in the actual functions of the Library.) The hope is that this
|
||||||
|
will lead to faster development of free libraries.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow. Pay close attention to the difference between a
|
||||||
|
"work based on the library" and a "work that uses the library". The
|
||||||
|
former contains code derived from the library, while the latter only
|
||||||
|
works together with the library.
|
||||||
|
|
||||||
|
Note that it is possible for a library to be covered by the ordinary
|
||||||
|
General Public License rather than by this special one.
|
||||||
|
|
||||||
|
GNU LIBRARY GENERAL PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. This License Agreement applies to any software library which
|
||||||
|
contains a notice placed by the copyright holder or other authorized
|
||||||
|
party saying it may be distributed under the terms of this Library
|
||||||
|
General Public License (also called "this License"). Each licensee is
|
||||||
|
addressed as "you".
|
||||||
|
|
||||||
|
A "library" means a collection of software functions and/or data
|
||||||
|
prepared so as to be conveniently linked with application programs
|
||||||
|
(which use some of those functions and data) to form executables.
|
||||||
|
|
||||||
|
The "Library", below, refers to any such software library or work
|
||||||
|
which has been distributed under these terms. A "work based on the
|
||||||
|
Library" means either the Library or any derivative work under
|
||||||
|
copyright law: that is to say, a work containing the Library or a
|
||||||
|
portion of it, either verbatim or with modifications and/or translated
|
||||||
|
straightforwardly into another language. (Hereinafter, translation is
|
||||||
|
included without limitation in the term "modification".)
|
||||||
|
|
||||||
|
"Source code" for a work means the preferred form of the work for
|
||||||
|
making modifications to it. For a library, complete source code means
|
||||||
|
all the source code for all modules it contains, plus any associated
|
||||||
|
interface definition files, plus the scripts used to control compilation
|
||||||
|
and installation of the library.
|
||||||
|
|
||||||
|
Activities other than copying, distribution and modification are not
|
||||||
|
covered by this License; they are outside its scope. The act of
|
||||||
|
running a program using the Library is not restricted, and output from
|
||||||
|
such a program is covered only if its contents constitute a work based
|
||||||
|
on the Library (independent of the use of the Library in a tool for
|
||||||
|
writing it). Whether that is true depends on what the Library does
|
||||||
|
and what the program that uses the Library does.
|
||||||
|
|
||||||
|
1. You may copy and distribute verbatim copies of the Library's
|
||||||
|
complete source code as you receive it, in any medium, provided that
|
||||||
|
you conspicuously and appropriately publish on each copy an
|
||||||
|
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||||
|
all the notices that refer to this License and to the absence of any
|
||||||
|
warranty; and distribute a copy of this License along with the
|
||||||
|
Library.
|
||||||
|
|
||||||
|
You may charge a fee for the physical act of transferring a copy,
|
||||||
|
and you may at your option offer warranty protection in exchange for a
|
||||||
|
fee.
|
||||||
|
|
||||||
|
2. You may modify your copy or copies of the Library or any portion
|
||||||
|
of it, thus forming a work based on the Library, and copy and
|
||||||
|
distribute such modifications or work under the terms of Section 1
|
||||||
|
above, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The modified work must itself be a software library.
|
||||||
|
|
||||||
|
b) You must cause the files modified to carry prominent notices
|
||||||
|
stating that you changed the files and the date of any change.
|
||||||
|
|
||||||
|
c) You must cause the whole of the work to be licensed at no
|
||||||
|
charge to all third parties under the terms of this License.
|
||||||
|
|
||||||
|
d) If a facility in the modified Library refers to a function or a
|
||||||
|
table of data to be supplied by an application program that uses
|
||||||
|
the facility, other than as an argument passed when the facility
|
||||||
|
is invoked, then you must make a good faith effort to ensure that,
|
||||||
|
in the event an application does not supply such function or
|
||||||
|
table, the facility still operates, and performs whatever part of
|
||||||
|
its purpose remains meaningful.
|
||||||
|
|
||||||
|
(For example, a function in a library to compute square roots has
|
||||||
|
a purpose that is entirely well-defined independent of the
|
||||||
|
application. Therefore, Subsection 2d requires that any
|
||||||
|
application-supplied function or table used by this function must
|
||||||
|
be optional: if the application does not supply it, the square
|
||||||
|
root function must still compute square roots.)
|
||||||
|
|
||||||
|
These requirements apply to the modified work as a whole. If
|
||||||
|
identifiable sections of that work are not derived from the Library,
|
||||||
|
and can be reasonably considered independent and separate works in
|
||||||
|
themselves, then this License, and its terms, do not apply to those
|
||||||
|
sections when you distribute them as separate works. But when you
|
||||||
|
distribute the same sections as part of a whole which is a work based
|
||||||
|
on the Library, the distribution of the whole must be on the terms of
|
||||||
|
this License, whose permissions for other licensees extend to the
|
||||||
|
entire whole, and thus to each and every part regardless of who wrote
|
||||||
|
it.
|
||||||
|
|
||||||
|
Thus, it is not the intent of this section to claim rights or contest
|
||||||
|
your rights to work written entirely by you; rather, the intent is to
|
||||||
|
exercise the right to control the distribution of derivative or
|
||||||
|
collective works based on the Library.
|
||||||
|
|
||||||
|
In addition, mere aggregation of another work not based on the Library
|
||||||
|
with the Library (or with a work based on the Library) on a volume of
|
||||||
|
a storage or distribution medium does not bring the other work under
|
||||||
|
the scope of this License.
|
||||||
|
|
||||||
|
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||||
|
License instead of this License to a given copy of the Library. To do
|
||||||
|
this, you must alter all the notices that refer to this License, so
|
||||||
|
that they refer to the ordinary GNU General Public License, version 2,
|
||||||
|
instead of to this License. (If a newer version than version 2 of the
|
||||||
|
ordinary GNU General Public License has appeared, then you can specify
|
||||||
|
that version instead if you wish.) Do not make any other change in
|
||||||
|
these notices.
|
||||||
|
|
||||||
|
Once this change is made in a given copy, it is irreversible for
|
||||||
|
that copy, so the ordinary GNU General Public License applies to all
|
||||||
|
subsequent copies and derivative works made from that copy.
|
||||||
|
|
||||||
|
This option is useful when you wish to copy part of the code of
|
||||||
|
the Library into a program that is not a library.
|
||||||
|
|
||||||
|
4. You may copy and distribute the Library (or a portion or
|
||||||
|
derivative of it, under Section 2) in object code or executable form
|
||||||
|
under the terms of Sections 1 and 2 above provided that you accompany
|
||||||
|
it with the complete corresponding machine-readable source code, which
|
||||||
|
must be distributed under the terms of Sections 1 and 2 above on a
|
||||||
|
medium customarily used for software interchange.
|
||||||
|
|
||||||
|
If distribution of object code is made by offering access to copy
|
||||||
|
from a designated place, then offering equivalent access to copy the
|
||||||
|
source code from the same place satisfies the requirement to
|
||||||
|
distribute the source code, even though third parties are not
|
||||||
|
compelled to copy the source along with the object code.
|
||||||
|
|
||||||
|
5. A program that contains no derivative of any portion of the
|
||||||
|
Library, but is designed to work with the Library by being compiled or
|
||||||
|
linked with it, is called a "work that uses the Library". Such a
|
||||||
|
work, in isolation, is not a derivative work of the Library, and
|
||||||
|
therefore falls outside the scope of this License.
|
||||||
|
|
||||||
|
However, linking a "work that uses the Library" with the Library
|
||||||
|
creates an executable that is a derivative of the Library (because it
|
||||||
|
contains portions of the Library), rather than a "work that uses the
|
||||||
|
library". The executable is therefore covered by this License.
|
||||||
|
Section 6 states terms for distribution of such executables.
|
||||||
|
|
||||||
|
When a "work that uses the Library" uses material from a header file
|
||||||
|
that is part of the Library, the object code for the work may be a
|
||||||
|
derivative work of the Library even though the source code is not.
|
||||||
|
Whether this is true is especially significant if the work can be
|
||||||
|
linked without the Library, or if the work is itself a library. The
|
||||||
|
threshold for this to be true is not precisely defined by law.
|
||||||
|
|
||||||
|
If such an object file uses only numerical parameters, data
|
||||||
|
structure layouts and accessors, and small macros and small inline
|
||||||
|
functions (ten lines or less in length), then the use of the object
|
||||||
|
file is unrestricted, regardless of whether it is legally a derivative
|
||||||
|
work. (Executables containing this object code plus portions of the
|
||||||
|
Library will still fall under Section 6.)
|
||||||
|
|
||||||
|
Otherwise, if the work is a derivative of the Library, you may
|
||||||
|
distribute the object code for the work under the terms of Section 6.
|
||||||
|
Any executables containing that work also fall under Section 6,
|
||||||
|
whether or not they are linked directly with the Library itself.
|
||||||
|
|
||||||
|
6. As an exception to the Sections above, you may also compile or
|
||||||
|
link a "work that uses the Library" with the Library to produce a
|
||||||
|
work containing portions of the Library, and distribute that work
|
||||||
|
under terms of your choice, provided that the terms permit
|
||||||
|
modification of the work for the customer's own use and reverse
|
||||||
|
engineering for debugging such modifications.
|
||||||
|
|
||||||
|
You must give prominent notice with each copy of the work that the
|
||||||
|
Library is used in it and that the Library and its use are covered by
|
||||||
|
this License. You must supply a copy of this License. If the work
|
||||||
|
during execution displays copyright notices, you must include the
|
||||||
|
copyright notice for the Library among them, as well as a reference
|
||||||
|
directing the user to the copy of this License. Also, you must do one
|
||||||
|
of these things:
|
||||||
|
|
||||||
|
a) Accompany the work with the complete corresponding
|
||||||
|
machine-readable source code for the Library including whatever
|
||||||
|
changes were used in the work (which must be distributed under
|
||||||
|
Sections 1 and 2 above); and, if the work is an executable linked
|
||||||
|
with the Library, with the complete machine-readable "work that
|
||||||
|
uses the Library", as object code and/or source code, so that the
|
||||||
|
user can modify the Library and then relink to produce a modified
|
||||||
|
executable containing the modified Library. (It is understood
|
||||||
|
that the user who changes the contents of definitions files in the
|
||||||
|
Library will not necessarily be able to recompile the application
|
||||||
|
to use the modified definitions.)
|
||||||
|
|
||||||
|
b) Accompany the work with a written offer, valid for at
|
||||||
|
least three years, to give the same user the materials
|
||||||
|
specified in Subsection 6a, above, for a charge no more
|
||||||
|
than the cost of performing this distribution.
|
||||||
|
|
||||||
|
c) If distribution of the work is made by offering access to copy
|
||||||
|
from a designated place, offer equivalent access to copy the above
|
||||||
|
specified materials from the same place.
|
||||||
|
|
||||||
|
d) Verify that the user has already received a copy of these
|
||||||
|
materials or that you have already sent this user a copy.
|
||||||
|
|
||||||
|
For an executable, the required form of the "work that uses the
|
||||||
|
Library" must include any data and utility programs needed for
|
||||||
|
reproducing the executable from it. However, as a special exception,
|
||||||
|
the source code distributed need not include anything that is normally
|
||||||
|
distributed (in either source or binary form) with the major
|
||||||
|
components (compiler, kernel, and so on) of the operating system on
|
||||||
|
which the executable runs, unless that component itself accompanies
|
||||||
|
the executable.
|
||||||
|
|
||||||
|
It may happen that this requirement contradicts the license
|
||||||
|
restrictions of other proprietary libraries that do not normally
|
||||||
|
accompany the operating system. Such a contradiction means you cannot
|
||||||
|
use both them and the Library together in an executable that you
|
||||||
|
distribute.
|
||||||
|
|
||||||
|
7. You may place library facilities that are a work based on the
|
||||||
|
Library side-by-side in a single library together with other library
|
||||||
|
facilities not covered by this License, and distribute such a combined
|
||||||
|
library, provided that the separate distribution of the work based on
|
||||||
|
the Library and of the other library facilities is otherwise
|
||||||
|
permitted, and provided that you do these two things:
|
||||||
|
|
||||||
|
a) Accompany the combined library with a copy of the same work
|
||||||
|
based on the Library, uncombined with any other library
|
||||||
|
facilities. This must be distributed under the terms of the
|
||||||
|
Sections above.
|
||||||
|
|
||||||
|
b) Give prominent notice with the combined library of the fact
|
||||||
|
that part of it is a work based on the Library, and explaining
|
||||||
|
where to find the accompanying uncombined form of the same work.
|
||||||
|
|
||||||
|
8. You may not copy, modify, sublicense, link with, or distribute
|
||||||
|
the Library except as expressly provided under this License. Any
|
||||||
|
attempt otherwise to copy, modify, sublicense, link with, or
|
||||||
|
distribute the Library is void, and will automatically terminate your
|
||||||
|
rights under this License. However, parties who have received copies,
|
||||||
|
or rights, from you under this License will not have their licenses
|
||||||
|
terminated so long as such parties remain in full compliance.
|
||||||
|
|
||||||
|
9. You are not required to accept this License, since you have not
|
||||||
|
signed it. However, nothing else grants you permission to modify or
|
||||||
|
distribute the Library or its derivative works. These actions are
|
||||||
|
prohibited by law if you do not accept this License. Therefore, by
|
||||||
|
modifying or distributing the Library (or any work based on the
|
||||||
|
Library), you indicate your acceptance of this License to do so, and
|
||||||
|
all its terms and conditions for copying, distributing or modifying
|
||||||
|
the Library or works based on it.
|
||||||
|
|
||||||
|
10. Each time you redistribute the Library (or any work based on the
|
||||||
|
Library), the recipient automatically receives a license from the
|
||||||
|
original licensor to copy, distribute, link with or modify the Library
|
||||||
|
subject to these terms and conditions. You may not impose any further
|
||||||
|
restrictions on the recipients' exercise of the rights granted herein.
|
||||||
|
You are not responsible for enforcing compliance by third parties to
|
||||||
|
this License.
|
||||||
|
|
||||||
|
11. If, as a consequence of a court judgment or allegation of patent
|
||||||
|
infringement or for any other reason (not limited to patent issues),
|
||||||
|
conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot
|
||||||
|
distribute so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you
|
||||||
|
may not distribute the Library at all. For example, if a patent
|
||||||
|
license would not permit royalty-free redistribution of the Library by
|
||||||
|
all those who receive copies directly or indirectly through you, then
|
||||||
|
the only way you could satisfy both it and this License would be to
|
||||||
|
refrain entirely from distribution of the Library.
|
||||||
|
|
||||||
|
If any portion of this section is held invalid or unenforceable under any
|
||||||
|
particular circumstance, the balance of the section is intended to apply,
|
||||||
|
and the section as a whole is intended to apply in other circumstances.
|
||||||
|
|
||||||
|
It is not the purpose of this section to induce you to infringe any
|
||||||
|
patents or other property right claims or to contest validity of any
|
||||||
|
such claims; this section has the sole purpose of protecting the
|
||||||
|
integrity of the free software distribution system which is
|
||||||
|
implemented by public license practices. Many people have made
|
||||||
|
generous contributions to the wide range of software distributed
|
||||||
|
through that system in reliance on consistent application of that
|
||||||
|
system; it is up to the author/donor to decide if he or she is willing
|
||||||
|
to distribute software through any other system and a licensee cannot
|
||||||
|
impose that choice.
|
||||||
|
|
||||||
|
This section is intended to make thoroughly clear what is believed to
|
||||||
|
be a consequence of the rest of this License.
|
||||||
|
|
||||||
|
12. If the distribution and/or use of the Library is restricted in
|
||||||
|
certain countries either by patents or by copyrighted interfaces, the
|
||||||
|
original copyright holder who places the Library under this License may add
|
||||||
|
an explicit geographical distribution limitation excluding those countries,
|
||||||
|
so that distribution is permitted only in or among countries not thus
|
||||||
|
excluded. In such case, this License incorporates the limitation as if
|
||||||
|
written in the body of this License.
|
||||||
|
|
||||||
|
13. The Free Software Foundation may publish revised and/or new
|
||||||
|
versions of the Library General Public License from time to time.
|
||||||
|
Such new versions will be similar in spirit to the present version,
|
||||||
|
but may differ in detail to address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the Library
|
||||||
|
specifies a version number of this License which applies to it and
|
||||||
|
"any later version", you have the option of following the terms and
|
||||||
|
conditions either of that version or of any later version published by
|
||||||
|
the Free Software Foundation. If the Library does not specify a
|
||||||
|
license version number, you may choose any version ever published by
|
||||||
|
the Free Software Foundation.
|
||||||
|
|
||||||
|
14. If you wish to incorporate parts of the Library into other free
|
||||||
|
programs whose distribution conditions are incompatible with these,
|
||||||
|
write to the author to ask for permission. For software which is
|
||||||
|
copyrighted by the Free Software Foundation, write to the Free
|
||||||
|
Software Foundation; we sometimes make exceptions for this. Our
|
||||||
|
decision will be guided by the two goals of preserving the free status
|
||||||
|
of all derivatives of our free software and of promoting the sharing
|
||||||
|
and reuse of software generally.
|
||||||
|
|
||||||
|
NO WARRANTY
|
||||||
|
|
||||||
|
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||||
|
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||||
|
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||||
|
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||||
|
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||||
|
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||||
|
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||||
|
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||||
|
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||||
|
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||||
|
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||||
|
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||||
|
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||||
|
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||||
|
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||||
|
DAMAGES.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Libraries
|
||||||
|
|
||||||
|
If you develop a new library, and you want it to be of the greatest
|
||||||
|
possible use to the public, we recommend making it free software that
|
||||||
|
everyone can redistribute and change. You can do so by permitting
|
||||||
|
redistribution under these terms (or, alternatively, under the terms of the
|
||||||
|
ordinary General Public License).
|
||||||
|
|
||||||
|
To apply these terms, attach the following notices to the library. It is
|
||||||
|
safest to attach them to the start of each source file to most effectively
|
||||||
|
convey the exclusion of warranty; and each file should have at least the
|
||||||
|
"copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the library's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Library General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library 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
|
||||||
|
Library General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Library General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or your
|
||||||
|
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||||
|
necessary. Here is a sample; alter the names:
|
||||||
|
|
||||||
|
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||||
|
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
||||||
|
|
||||||
|
<signature of Ty Coon>, 1 April 1990
|
||||||
|
Ty Coon, President of Vice
|
||||||
|
|
||||||
|
That's all there is to it!
|
||||||
|
|
@ -0,0 +1,251 @@
|
||||||
|
New in 1.4 - 2017-08-07
|
||||||
|
-----------------------
|
||||||
|
* New ftdi_usb_open_bus_addr() open function
|
||||||
|
* Use BM/R series baud rate computation for FT230X
|
||||||
|
* ftdi_get_error_string() now returns const char*
|
||||||
|
* C++ API: Ability to open devices with empty descriptor strings
|
||||||
|
* C++ API: Fix enumerations for buffer purge and modem controls
|
||||||
|
* small build fixes and improvements in the python examples
|
||||||
|
* ftdi_eeprom / eeprom handling:
|
||||||
|
* New API function: ftdi_eeprom_get_strings()
|
||||||
|
* Fix USE_SERIAL handling for 230X type chips
|
||||||
|
* Make ftdi_read_eeprom_location() endianness independent
|
||||||
|
* Fix flashing of FT245R
|
||||||
|
|
||||||
|
New in 1.3 - 2016-05-20
|
||||||
|
-----------------------
|
||||||
|
* Added ftdi_usb_get_strings2() to prevent automatic device close (Fahrzin Hemmati)
|
||||||
|
* Added ftdi_transfer_data_cancel() for cancellation of a submitted transfer,
|
||||||
|
avoided resubmittion of a canceled transfer in the callbacks,
|
||||||
|
replaced calls to libusb_handle_events with
|
||||||
|
libusb_handle_events_timeout_completed (Eugene Hutorny)
|
||||||
|
* ftdi_eeprom / eeprom handling:
|
||||||
|
* Add support for arbitrary user data (Salvador Eduardo Tropea)
|
||||||
|
* Add --build-eeprom support (Salvador Eduardo Tropea)
|
||||||
|
* Fix use_usb_version config file option (Thilo Schulz)
|
||||||
|
* Ability to include other config files in EEPROM config file (Thilo Schulz)
|
||||||
|
* Add external oscillator enable bit (Raphael Assenat)
|
||||||
|
* Support channel configuration (Stephan Linz)
|
||||||
|
* Added --device option to ftdi_eeprom to specify FTDI device (Robin Haberkorn)
|
||||||
|
* Fixed EEPROM user-area space checks for FT232R and FT245R chips (Robin Haberkorn)
|
||||||
|
* Various improvements to CBUS handling, including the EEPROM (Robin Haberkorn)
|
||||||
|
* swig wrapper: Fix handling of binary strings in ftdi_write_data()
|
||||||
|
for python 3 (xantares09)
|
||||||
|
* cbus python example code (Rodney Sinclair)
|
||||||
|
* ftdi_stream: fix timeout setting (Ларионов Даниил)
|
||||||
|
* Fixed typo in CBUS defines: CBUSG_DRIVE1 -> CBUSH_DRIVE1
|
||||||
|
|
||||||
|
New in 1.2 - 2014-11-21
|
||||||
|
-----------------------
|
||||||
|
* Support for FT230X devices (Uwe Bonnes)
|
||||||
|
* ftdi_usb_get_strings(): Don't try to open an already open device (Denis Sirotkin)
|
||||||
|
* Support for finding devices bricked by the Windows driver (Forest Crossman)
|
||||||
|
* cmake build system: New LibFTDI1ConfigVersion.cmake file (xantares09)
|
||||||
|
* Fix a typo in the MPSSE command CLK_BYTES_OR_LOW (Benjamin Vanheuverzwijn)
|
||||||
|
* Minor fixes for MSVC++ (Andrei Errapart)
|
||||||
|
* Various small code improvements (Florian Preinstorfer, Jochen Sprickerhof, xantares09)
|
||||||
|
|
||||||
|
New in 1.1 - 2014-02-05
|
||||||
|
-----------------------
|
||||||
|
* Fix FT232H eeprom suspend pulldown setting (Davide Michelizza)
|
||||||
|
* Fix FT232H eeprom user area size (Davide Michelizza)
|
||||||
|
* Improved mingw build (Paul Fertser and Michel Zou)
|
||||||
|
* C++ wrapper: Get/set functions for USB timeouts (Jochen Sprickerhof)
|
||||||
|
* Partial support for FT230X (Nathael Pajani)
|
||||||
|
* New API function: ftdi_eeprom_set_strings() (Nathael Pajani)
|
||||||
|
* Prevent possible segfault in ftdi_eeprom_decode() (Nathael Pajani)
|
||||||
|
* Save device release number in eeprom (Jarkko Sonninen)
|
||||||
|
* Fix "self powered" eeprom flag (Jarkko Sonninen)
|
||||||
|
* Improved python wrapper (Michel Zou)
|
||||||
|
* Many buildsystem improvements (Michel Zou and Mike Frysinger)
|
||||||
|
* See the git history for more changes and fixes
|
||||||
|
|
||||||
|
New in 1.0 - 2013-01-29
|
||||||
|
-----------------------
|
||||||
|
* Ported to libusb 1.x (initial work by Jie Zhang)
|
||||||
|
* Many eeprom handling improvements (Uwe Bonnes, Anders Larsen)
|
||||||
|
* Renamed pkconfig, library .so etc. files to "libftdi1" (Intra2net)
|
||||||
|
* ftdi_eeprom is part of libftdi now (Intra2net)
|
||||||
|
|
||||||
|
* New baudrate calculation code + unit tests (Uwe Bonnes and Intra2net)
|
||||||
|
* Improved python bindings including python3 support (Michel Zou)
|
||||||
|
* Switched completely to cmake build system (Intra2net)
|
||||||
|
* cmake: Easy libftdi discovery via find_package() (Michel Zou)
|
||||||
|
* eeprom handling now done via get()/set() functions (Uwe Bonnes)
|
||||||
|
* C++ wrapper: Fixed use-after-free in List::find_all() (Intra2net)
|
||||||
|
* Documentation updates (Xiaofan Chen)
|
||||||
|
* See the git history for more changes and fixes
|
||||||
|
|
||||||
|
New in 0.20 - 2012-03-19
|
||||||
|
------------------------
|
||||||
|
* Support for FT232H (Uwe Bonnes)
|
||||||
|
* Fixed install location of header files (Uwe Bonnes and Intra2net)
|
||||||
|
* Backported serial_test tool from libftdi 1.x (Uwe Bonnes)
|
||||||
|
|
||||||
|
New in 0.19 - 2011-05-23
|
||||||
|
------------------------
|
||||||
|
* Make kernel driver detach configurable (Thomas Klose)
|
||||||
|
* Correct ftdi_poll_modem_status() result code (Tom Saunders)
|
||||||
|
* cmake build system improvements (Evgeny Sinelnikov)
|
||||||
|
* Fix uninitialized memory access in async mode (Intra2net)
|
||||||
|
* Support for FT232R eeprom features (Hermann Kraus)
|
||||||
|
* Fix size returned by ftdi_read_data (Hermann Kraus)
|
||||||
|
* C++ wrapper: Fix infinite recursion in set_bitmode (Intra2net)
|
||||||
|
* Improvements to the python wrapper (Flynn Marquardt and Chris Zeh)
|
||||||
|
|
||||||
|
New in 0.18 - 2010-06-25
|
||||||
|
------------------------
|
||||||
|
* Add ftdi_eeprom_free() to free allocated memory in eeprom (Wilfried Holzke)
|
||||||
|
* More generic error message for the FTDI kernel driver (Intra2net)
|
||||||
|
* Honor CPPFLAGS in python wrapper build (Alexander Lehmann)
|
||||||
|
* cmake: Fix package creation on 32-bit machines (Uwe Bonnes)
|
||||||
|
* Fix swig argument constraints (Intra2net)
|
||||||
|
* Don't segfault if device is closed or ftdi context is invalid (Intra2net)
|
||||||
|
* Ability to disable build of examples / documentation (Mike Frysinger and Intra2net)
|
||||||
|
* Fix typo in python wrapper build (Mike Frysinger)
|
||||||
|
* Autoconf build system improvements (Mike Frysinger)
|
||||||
|
|
||||||
|
New in 0.17 - 2009-12-19
|
||||||
|
------------------------
|
||||||
|
* C++ wrapper: Reduced code duplication and small other changes (Intra2net)
|
||||||
|
* Deprecated old ftdi_enable_bitbang() function (Intra2net)
|
||||||
|
* New ftdi_usb_open_desc_index() function (Intra2net)
|
||||||
|
* Added baud rate test example code (Intra2net)
|
||||||
|
* New serial input example code (Jim Paris)
|
||||||
|
* Fix modem status byte filtering for USB high speed chips (Intra2net and Jim Paris)
|
||||||
|
* Add bitmode for synchronous fifo in FT2232H (Uwe Bonnes)
|
||||||
|
* Fix usb_set_configuration() call on Windows 64 (NIL)
|
||||||
|
* Fix usb index in ftdi_convert_baudrate() for FT2232H/FT4232H chips (Thimo Eichstaedt)
|
||||||
|
* Set initial baudrate on correct interface instead of always the first one (Thimo Eichstaedt)
|
||||||
|
* Call usb_set_configuration() on Windows only (Uwe Bonnes)
|
||||||
|
* 64 bit and other buildsystem fixes (Uwe Bonnes)
|
||||||
|
* Don't build --with-async-mode w/ libusb-compat-0.1 (Clifford Wolf)
|
||||||
|
* Functions for read/write of a single eeprom location (Oleg Seiljus)
|
||||||
|
* Protect against double close of usb device (Nathan Fraser)
|
||||||
|
* Fix out-of-tree-build in python wrapper (Aurelien Jarno)
|
||||||
|
* Autoconf and doxygen cleanup (Jim Paris)
|
||||||
|
|
||||||
|
New in 0.16 - 2009-05-08
|
||||||
|
------------------------
|
||||||
|
* C++ wrapper: Reopen the device after calling get_strings() in Context::open() (Marek Vavruša and Intra2net)
|
||||||
|
* C++ wrapper: Fixed an inheritance problem (Marek Vavruša and Intra2net)
|
||||||
|
* C++ wrapper: Relicensed under GPLv2 + linking exception (Marek Vavruša and Intra2net)
|
||||||
|
* Support for FT2232H and FT4232H (David Challis, Alex Harford and Intra2net)
|
||||||
|
* Support for mingw cross compile (Uwe Bonnes)
|
||||||
|
* Python bindings and minor autoconf cleanup (Tarek Heiland)
|
||||||
|
* Code cleanup in various places (Intra2net)
|
||||||
|
* Fixed ftdi_read_chipid in some cases (Matthias Richter)
|
||||||
|
* eeprom decode function and small cleanups (Marius Kintel)
|
||||||
|
* cmake system improvements (Marius Kintel and Intra2net)
|
||||||
|
* Fix compilation in -ansi -pedantic mode (Matthias Janke)
|
||||||
|
|
||||||
|
New in 0.15 - 2008-12-19
|
||||||
|
------------------------
|
||||||
|
* Full C++ wrapper. Needs boost (Marek Vavruša and Intra2net)
|
||||||
|
* cmake rules (Marek Vavruša)
|
||||||
|
|
||||||
|
New in 0.14 - 2008-09-09
|
||||||
|
------------------------
|
||||||
|
* Fixed flow control code for second FT2232 interface (Marek Vavruša)
|
||||||
|
* Ability to set flow control via one USB call (Marek Vavruša)
|
||||||
|
* 64 bit build support in the RPM spec file (Uwe Bonnes)
|
||||||
|
* Small fix to the RPM spec file (Uwe Bonnes)
|
||||||
|
* Ability to set RS232 break type (Intra2net)
|
||||||
|
* Grouped flow control and modem status code together (Intra2net)
|
||||||
|
|
||||||
|
New in 0.13 - 2008-06-13
|
||||||
|
------------------------
|
||||||
|
* Build .spec file via configure.in (Intra2net)
|
||||||
|
* Fixed "libusb-config --cflags" call (Mike Frysinger and Intra2net)
|
||||||
|
* Always set usb configuration (Mike Frysinger and Intra2net)
|
||||||
|
* Improved libusb-win32 support (Mike Frysinger)
|
||||||
|
|
||||||
|
New in 0.12 - 2008-04-16
|
||||||
|
------------------------
|
||||||
|
* Fix build of documentation for "out of tree" builds
|
||||||
|
* Fix USB config descriptor in the eeprom (Juergen Beisert)
|
||||||
|
* Ability to purge RX/TX buffers separately (Arnim Läuger)
|
||||||
|
* Setting of event and error character (Arnim Läuger)
|
||||||
|
* Poll modem status function (Arnim Läuger and Intra2net)
|
||||||
|
* Updated documentation and created AUTHORS file
|
||||||
|
|
||||||
|
New in 0.11 - 2008-03-01
|
||||||
|
------------------------
|
||||||
|
* Vala bindings helper functions (ftdi_new, ftdi_free, ftdi_list_free2) (Even Nermerson)
|
||||||
|
* Support for different EEPROM sizes (Andrew John Rogers, andrew@rogerstech.co.uk)
|
||||||
|
* Async write support. Linux only and no error handling.
|
||||||
|
You have to enable it via --with-async-mode.
|
||||||
|
* Detection of R-type chips
|
||||||
|
* FTDIChip-ID read support (Peter Holik)
|
||||||
|
|
||||||
|
New in 0.10 - 2007-05-08
|
||||||
|
------------------------
|
||||||
|
* Examples for libftdi_usb_find_all and CBUS mode
|
||||||
|
* Fixed ftdi_list_free
|
||||||
|
* Small cosmetic changes
|
||||||
|
|
||||||
|
New in 0.9 - 2007-02-09
|
||||||
|
-----------------------
|
||||||
|
* Fixed build without doxygen
|
||||||
|
* Correct .so file library version
|
||||||
|
|
||||||
|
New in 0.8 - 2007-02-08
|
||||||
|
-----------------------
|
||||||
|
* Complete doxygen documentation and examples
|
||||||
|
* Extended FT2232C bitbang mode example code (Max)
|
||||||
|
* ftdi_usb_get_strings function to get device ID strings (Matthijs ten Berge)
|
||||||
|
* Fix ftdi_read_pins on PowerPC systems (Thomas Fischl)
|
||||||
|
* Automatically detach ftdi_sio kernel driver (Uwe Bonnes and Intra2net)
|
||||||
|
* Configurable flow control (Lorenz Moesenlechner and Matthias Kranz)
|
||||||
|
|
||||||
|
New in 0.7 - 2005-10-11
|
||||||
|
-----------------------
|
||||||
|
* Baudrate calculation fix for FT2232C (Steven Turner/FTDI)
|
||||||
|
* Find all devices by vendor/product id (Tim Ansell and Intra2net)
|
||||||
|
* Documentation updates (Tim Ansell)
|
||||||
|
|
||||||
|
New in 0.6 - 2005-04-24
|
||||||
|
-----------------------
|
||||||
|
* Set library version on .so file again
|
||||||
|
* Configurable serial line parameters (Alain Abbas)
|
||||||
|
* Improved filtering of status bytes (Evgeny Sinelnikov)
|
||||||
|
* Extended FT2232C support (Uwe Bonnes)
|
||||||
|
* Small improvement to the baudrate calculation code (Emil)
|
||||||
|
* Error handling cleanup (Rogier Wolff and Intra2net)
|
||||||
|
|
||||||
|
New in 0.5 - 2004-09-24
|
||||||
|
-----------------------
|
||||||
|
* New autoconf suite
|
||||||
|
* pkgconfig support
|
||||||
|
* Status byte filtering now works for "big" readbuffer sizes (Thanks Evgeny!)
|
||||||
|
* Open device by description and/or serial (Evgeny Sinelnikov)
|
||||||
|
* Improved error handling (Evgeny Sinelnikov)
|
||||||
|
|
||||||
|
New in 0.4 - 2004-06-15
|
||||||
|
-----------------------
|
||||||
|
* Fixed filtering of status bytes (Readbuffer size is now 64 bytes)
|
||||||
|
* FT2232C support (Steven Turner/FTDI)
|
||||||
|
* New baudrate calculation code (Ian Abbott)
|
||||||
|
* Automatic detection of chip type
|
||||||
|
* Important: ftdi_write_data now returns the bytes written
|
||||||
|
* Fixed defaults values in ftdi_eeprom_initdefaults (Jean-Daniel Merkli)
|
||||||
|
* Reset internal readbuffer offsets for reset()/purge_buffers()
|
||||||
|
* Small typo fixes (Mark Haemmerling)
|
||||||
|
|
||||||
|
New in 0.3 - 2004-03-25
|
||||||
|
-----------------------
|
||||||
|
* Improved read function which takes arbitrary input buffer sizes
|
||||||
|
Attention: Call ftdi_deinit() on exit to free used memory
|
||||||
|
* Vastly increased read/write performance (configurable chunksize, default is 4096)
|
||||||
|
* Set/get latency timer function working (Thanks Steven Turner/FTDI)
|
||||||
|
* Increased library version because the changes require recompilation
|
||||||
|
|
||||||
|
New in 0.2 - 2004-01-03
|
||||||
|
-----------------------
|
||||||
|
* EEPROM build fix by Daniel Kirkham (Melbourne, Australia)
|
||||||
|
* Implemented basic ftdi_read_data() function
|
||||||
|
* EEPROM write fixes
|
||||||
|
|
||||||
|
New in 0.1 - 2003-06-10
|
||||||
|
-----------------------
|
||||||
|
* First public release
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
# - Try to find the freetype library
|
||||||
|
# Once done this defines
|
||||||
|
#
|
||||||
|
# LIBUSB_FOUND - system has libusb
|
||||||
|
# LIBUSB_INCLUDE_DIR - the libusb include directory
|
||||||
|
# LIBUSB_LIBRARIES - Link these to use libusb
|
||||||
|
|
||||||
|
# Copyright (c) 2006, 2008 Laurent Montel, <montel@kde.org>
|
||||||
|
#
|
||||||
|
# Redistribution and use is allowed according to the terms of the BSD license.
|
||||||
|
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
|
||||||
|
|
||||||
|
|
||||||
|
if (LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES)
|
||||||
|
|
||||||
|
# in cache already
|
||||||
|
set(LIBUSB_FOUND TRUE)
|
||||||
|
|
||||||
|
else (LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES)
|
||||||
|
IF (NOT WIN32)
|
||||||
|
# use pkg-config to get the directories and then use these values
|
||||||
|
# in the FIND_PATH() and FIND_LIBRARY() calls
|
||||||
|
find_package(PkgConfig)
|
||||||
|
pkg_check_modules(PC_LIBUSB libusb-1.0)
|
||||||
|
ENDIF(NOT WIN32)
|
||||||
|
|
||||||
|
FIND_PATH(LIBUSB_INCLUDE_DIR libusb.h
|
||||||
|
PATHS ${PC_LIBUSB_INCLUDEDIR} ${PC_LIBUSB_INCLUDE_DIRS})
|
||||||
|
|
||||||
|
FIND_LIBRARY(LIBUSB_LIBRARIES NAMES usb-1.0
|
||||||
|
PATHS ${PC_LIBUSB_LIBDIR} ${PC_LIBUSB_LIBRARY_DIRS})
|
||||||
|
|
||||||
|
include(FindPackageHandleStandardArgs)
|
||||||
|
FIND_PACKAGE_HANDLE_STANDARD_ARGS(LIBUSB DEFAULT_MSG LIBUSB_LIBRARIES LIBUSB_INCLUDE_DIR)
|
||||||
|
|
||||||
|
MARK_AS_ADVANCED(LIBUSB_INCLUDE_DIR LIBUSB_LIBRARIES)
|
||||||
|
|
||||||
|
endif (LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES)
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
The C library "libftdi1" is distributed under the
|
||||||
|
GNU Library General Public License version 2.
|
||||||
|
|
||||||
|
A copy of the GNU Library General Public License (LGPL) is included
|
||||||
|
in this distribution, in the file COPYING.LIB.
|
||||||
|
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
|
The C++ wrapper "ftdipp1" is distributed under the GNU General
|
||||||
|
Public License version 2 (with a special exception described below).
|
||||||
|
|
||||||
|
A copy of the GNU General Public License (GPL) is included
|
||||||
|
in this distribution, in the file COPYING.GPL.
|
||||||
|
|
||||||
|
As a special exception, if other files instantiate templates or use macros
|
||||||
|
or inline functions from this file, or you compile this file and link it
|
||||||
|
with other works to produce a work based on this file, this file
|
||||||
|
does not by itself cause the resulting work to be covered
|
||||||
|
by the GNU General Public License.
|
||||||
|
|
||||||
|
However the source code for this file must still be made available
|
||||||
|
in accordance with section (3) of the GNU General Public License.
|
||||||
|
|
||||||
|
This exception does not invalidate any other reasons why a work based
|
||||||
|
on this file might be covered by the GNU General Public License.
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
libftdi version 1.4
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
|
||||||
|
libftdi - A library (using libusb) to talk to FTDI's UART/FIFO chips
|
||||||
|
including the popular bitbang mode.
|
||||||
|
|
||||||
|
The following chips are supported:
|
||||||
|
* FT230X
|
||||||
|
- FT4232H / FT2232H
|
||||||
|
- FT232R / FT245R
|
||||||
|
- FT2232L / FT2232D / FT2232C
|
||||||
|
- FT232BM / FT245BM (and the BL/BQ variants)
|
||||||
|
- FT8U232AM / FT8U245AM
|
||||||
|
|
||||||
|
libftdi requires libusb 1.x.
|
||||||
|
|
||||||
|
The AUTHORS file contains a list of all the people
|
||||||
|
that made libftdi possible what it is today.
|
||||||
|
|
||||||
|
Changes
|
||||||
|
-------
|
||||||
|
* New ftdi_usb_open_bus_addr() open function
|
||||||
|
* Use BM/R series baud rate computation for FT230X
|
||||||
|
* ftdi_get_error_string() now returns const char*
|
||||||
|
* C++ API: Ability to open devices with empty descriptor strings
|
||||||
|
* C++ API: Fix enumerations for buffer purge and modem controls
|
||||||
|
* small build fixes and improvements in the python examples
|
||||||
|
* ftdi_eeprom / eeprom handling:
|
||||||
|
* New API function: ftdi_eeprom_get_strings()
|
||||||
|
* Fix USE_SERIAL handling for 230X type chips
|
||||||
|
* Make ftdi_read_eeprom_location() endianness independent
|
||||||
|
* Fix flashing of FT245R
|
||||||
|
|
||||||
|
You'll find the newest version of libftdi at:
|
||||||
|
https://www.intra2net.com/en/developer/libftdi
|
||||||
|
|
||||||
|
|
||||||
|
Quick start
|
||||||
|
-----------
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
|
||||||
|
cmake -DCMAKE_INSTALL_PREFIX="/usr" ../
|
||||||
|
make
|
||||||
|
make install
|
||||||
|
|
||||||
|
More verbose build instructions are in "README.build"
|
||||||
|
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
www.intra2net.com 2003-2017 Intra2net AG
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
Here is a short tutorial on how to build libftdi git under
|
||||||
|
Ubuntu 12.10, But it is similar on other Linux distros.
|
||||||
|
|
||||||
|
1) Install the build tools
|
||||||
|
sudo apt-get install build-essential (yum install make automake gcc gcc-c++ kernel-devel)
|
||||||
|
sudo apt-get install git-core (yum install git)
|
||||||
|
sudo apt-get install cmake (yum install cmake)
|
||||||
|
sudo apt-get install doxygen (for building documentations) (yum install doxygen)
|
||||||
|
|
||||||
|
2) Install dependencies
|
||||||
|
sudo apt-get install libusb-1.0-devel (yum install libusb-devel)
|
||||||
|
(if the system comes with older version like 1.0.8 or
|
||||||
|
earlier, it is recommended you build libusbx-1.0.14 or later).
|
||||||
|
|
||||||
|
sudo apt-get install libconfuse-dev (for ftdi-eeprom) (yum install libconfuse-devel)
|
||||||
|
sudo apt-get install swig python-dev (for python bindings) (yum install swig python-devel)
|
||||||
|
sudo apt-get install libboost-all-dev (for C++ binding and unit test) (yum install boost-devel)
|
||||||
|
|
||||||
|
3) Clone the git repository
|
||||||
|
mkdir libftdi
|
||||||
|
cd libftdi
|
||||||
|
git clone git://developer.intra2net.com/libftdi
|
||||||
|
|
||||||
|
If you are building the release tar ball, just extract the source
|
||||||
|
tar ball.
|
||||||
|
|
||||||
|
4) Build the git source and install
|
||||||
|
cd libftdi
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
cmake -DCMAKE_INSTALL_PREFIX="/usr" ../
|
||||||
|
make
|
||||||
|
sudo make install
|
||||||
|
|
||||||
|
5) carry out some tests
|
||||||
|
cd examples
|
||||||
|
|
||||||
|
mcuee@Ubuntu1210VM:~/Desktop/build/libftdi/libftdi/build/examples$
|
||||||
|
./find_all_pp -v 0x0403 -p 0x6001
|
||||||
|
Found devices ( VID: 0x403, PID: 0x6001 )
|
||||||
|
------------------------------------------------
|
||||||
|
FTDI (0x8730800): ftdi, usb serial converter, ftDEH51S (Open OK)
|
||||||
|
FTDI (0x8730918): FTDI, FT232R USB UART, A8007Ub5 (Open OK)
|
||||||
|
|
||||||
|
mcuee@Ubuntu1210VM:~/Desktop/build/libftdi/libftdi/build/examples$ ./eeprom
|
||||||
|
2 FTDI devices found: Only Readout on EEPROM done. Use
|
||||||
|
VID/PID/desc/serial to select device
|
||||||
|
Decoded values of device 1:
|
||||||
|
Chip type 1 ftdi_eeprom_size: 128
|
||||||
|
0x000: 00 00 03 04 01 60 00 04 a0 16 08 00 10 01 94 0a .....`.. ........
|
||||||
|
0x010: 9e 2a c8 12 0a 03 66 00 74 00 64 00 69 00 2a 03 .*....f. t.d.i.*.
|
||||||
|
0x020: 75 00 73 00 62 00 20 00 73 00 65 00 72 00 69 00 u.s.b. . s.e.r.i.
|
||||||
|
0x030: 61 00 6c 00 20 00 63 00 6f 00 6e 00 76 00 65 00 a.l. .c. o.n.v.e.
|
||||||
|
0x040: 72 00 74 00 65 00 72 00 12 03 66 00 74 00 44 00 r.t.e.r. ..f.t.D.
|
||||||
|
0x050: 45 00 48 00 35 00 31 00 53 00 02 03 00 00 00 00 E.H.5.1. S.......
|
||||||
|
0x060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
|
||||||
|
0x070: 00 00 00 00 00 00 00 00 00 00 00 00 01 00 16 02 ........ ........
|
||||||
|
VID: 0x0403
|
||||||
|
PID: 0x6001
|
||||||
|
Release: 0x0400
|
||||||
|
Bus Powered: 44 mA USB Remote Wake Up
|
||||||
|
Manufacturer: ftdi
|
||||||
|
Product: usb serial converter
|
||||||
|
Serial: ftDEH51S
|
||||||
|
Checksum : 0216
|
||||||
|
Enable Remote Wake Up
|
||||||
|
PNP: 1
|
||||||
|
Decoded values of device 2:
|
||||||
|
Chip type 3 ftdi_eeprom_size: 128
|
||||||
|
0x000: 00 40 03 04 01 60 00 00 a0 2d 08 00 00 00 98 0a .@...`.. .-......
|
||||||
|
0x010: a2 20 c2 12 23 10 05 00 0a 03 46 00 54 00 44 00 . ..#... ..F.T.D.
|
||||||
|
0x020: 49 00 20 03 46 00 54 00 32 00 33 00 32 00 52 00 I. .F.T. 2.3.2.R.
|
||||||
|
0x030: 20 00 55 00 53 00 42 00 20 00 55 00 41 00 52 00 .U.S.B. .U.A.R.
|
||||||
|
0x040: 54 00 12 03 41 00 38 00 30 00 30 00 37 00 55 00 T...A.8. 0.0.7.U.
|
||||||
|
0x050: 62 00 35 00 c9 bf 1c 80 00 00 00 00 00 00 00 00 b.5..... ........
|
||||||
|
0x060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
|
||||||
|
0x070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0f 23 ........ .......#
|
||||||
|
0x080: 2c 04 d3 fb 00 00 c9 bf 1c 80 42 00 00 00 00 00 ,....... ..B.....
|
||||||
|
0x090: 00 00 00 00 00 00 00 00 38 41 32 52 4a 33 47 4f ........ 8A2RJ3GO
|
||||||
|
VID: 0x0403
|
||||||
|
PID: 0x6001
|
||||||
|
Release: 0x0000
|
||||||
|
Bus Powered: 90 mA USB Remote Wake Up
|
||||||
|
Manufacturer: FTDI
|
||||||
|
Product: FT232R USB UART
|
||||||
|
Serial: A8007Ub5
|
||||||
|
Checksum : 230f
|
||||||
|
Internal EEPROM
|
||||||
|
Enable Remote Wake Up
|
||||||
|
PNP: 1
|
||||||
|
Channel A has Mode UART VCP
|
||||||
|
C0 Function: TXLED
|
||||||
|
C1 Function: RXLED
|
||||||
|
C2 Function: TXDEN
|
||||||
|
C3 Function: PWREN
|
||||||
|
C4 Function: SLEEP
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
* How to cross compile libftdi-1.x for Windows? *
|
||||||
|
1 - Prepare a pkg-config wrapper according to
|
||||||
|
https://www.flameeyes.eu/autotools-mythbuster/pkgconfig/cross-compiling.html ,
|
||||||
|
additionally export PKG_CONFIG_ALLOW_SYSTEM_CFLAGS and
|
||||||
|
PKG_CONFIG_ALLOW_SYSTEM_LIBS.
|
||||||
|
2 - Write a CMake toolchain file according to
|
||||||
|
http://www.vtk.org/Wiki/CmakeMingw . Change the path to your future sysroot.
|
||||||
|
3 - Get libusb sources (either by cloning the git repo or by downloading a
|
||||||
|
tarball). Unpack, autogen.sh (when building from git), and configure like this:
|
||||||
|
./configure --build=`./config.guess` --host=i686-w64-mingw32 \
|
||||||
|
--prefix=/usr --with-sysroot=$HOME/i686-w64-mingw32-root/
|
||||||
|
4 - run
|
||||||
|
make install DESTDIR=$HOME/i686-w64-mingw32-root/
|
||||||
|
5 - go to libftdi-1.x source directory and run
|
||||||
|
cmake -DCMAKE_TOOLCHAIN_FILE=~/Toolchain-mingw.cmake \
|
||||||
|
-DCMAKE_INSTALL_PREFIX="/usr" \
|
||||||
|
-DPKG_CONFIG_EXECUTABLE=`which i686-w64-mingw32-pkg-config`
|
||||||
|
6 - run
|
||||||
|
make install DESTDIR=$HOME/i686-w64-mingw32-root/
|
||||||
|
|
||||||
|
* How to run libftdi 1.x under Windows *
|
||||||
|
|
||||||
|
On 26-Jan-2014, libusbx and libusb project were merged with the release
|
||||||
|
of libusb-1.0.18 and now the project is called libusb.
|
||||||
|
|
||||||
|
libusb Windows backend will need to rely on a proper driver to run.
|
||||||
|
Please refer to the following wiki page for proper driver installation.
|
||||||
|
https://github.com/libusb/libusb/wiki/Windows#wiki-How_to_use_libusb_on_Windows
|
||||||
|
|
||||||
|
As of 26-Jan-2014, libusb Windows backend supports WinUSB,
|
||||||
|
libusb0.sys and libusbk.sys driver. However, libusb's support of
|
||||||
|
libusb0.sys and libusbk.sys is considered to be less mature than
|
||||||
|
WinUSB. Therefore, WinUSB driver installation using Zadig
|
||||||
|
is recommended.
|
||||||
|
|
||||||
|
Take note once you replace the original FTDI driver with WinUSB driver,
|
||||||
|
you can no longer use the functionality the original FTDI driver provides
|
||||||
|
(eg. Virtual Serial Port or D2XX).
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
*** TODO for 1.0 release ***
|
||||||
|
Documentation:
|
||||||
|
- Document the new EEPROM function
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
# libConfuse is a configuration file parser library
|
||||||
|
# available at http://www.nongnu.org/confuse/
|
||||||
|
#
|
||||||
|
# The module defines the following variables:
|
||||||
|
# CONFUSE_FOUND - the system has Confuse
|
||||||
|
# CONFUSE_INCLUDE_DIR - where to find confuse.h
|
||||||
|
# CONFUSE_INCLUDE_DIRS - confuse includes
|
||||||
|
# CONFUSE_LIBRARY - where to find the Confuse library
|
||||||
|
# CONFUSE_LIBRARIES - aditional libraries
|
||||||
|
# CONFUSE_ROOT_DIR - root dir (ex. /usr/local)
|
||||||
|
|
||||||
|
#=============================================================================
|
||||||
|
# Copyright 2010-2013, Julien Schueller
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
# list of conditions and the following disclaimer.
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
# The views and conclusions contained in the software and documentation are those
|
||||||
|
# of the authors and should not be interpreted as representing official policies,
|
||||||
|
# either expressed or implied, of the FreeBSD Project.
|
||||||
|
#=============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
find_path ( CONFUSE_INCLUDE_DIR
|
||||||
|
NAMES confuse.h
|
||||||
|
)
|
||||||
|
|
||||||
|
set ( CONFUSE_INCLUDE_DIRS ${CONFUSE_INCLUDE_DIR} )
|
||||||
|
|
||||||
|
find_library ( CONFUSE_LIBRARY
|
||||||
|
NAMES confuse
|
||||||
|
)
|
||||||
|
|
||||||
|
set ( CONFUSE_LIBRARIES ${CONFUSE_LIBRARY} )
|
||||||
|
|
||||||
|
|
||||||
|
# try to guess root dir from include dir
|
||||||
|
if ( CONFUSE_INCLUDE_DIR )
|
||||||
|
string ( REGEX REPLACE "(.*)/include.*" "\\1" CONFUSE_ROOT_DIR ${CONFUSE_INCLUDE_DIR} )
|
||||||
|
# try to guess root dir from library dir
|
||||||
|
elseif ( CONFUSE_LIBRARY )
|
||||||
|
string ( REGEX REPLACE "(.*)/lib[/|32|64].*" "\\1" CONFUSE_ROOT_DIR ${CONFUSE_LIBRARY} )
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
|
||||||
|
# handle the QUIETLY and REQUIRED arguments
|
||||||
|
include ( FindPackageHandleStandardArgs )
|
||||||
|
find_package_handle_standard_args( Confuse DEFAULT_MSG CONFUSE_LIBRARY CONFUSE_INCLUDE_DIR )
|
||||||
|
|
||||||
|
mark_as_advanced (
|
||||||
|
CONFUSE_LIBRARY
|
||||||
|
CONFUSE_LIBRARIES
|
||||||
|
CONFUSE_INCLUDE_DIR
|
||||||
|
CONFUSE_INCLUDE_DIRS
|
||||||
|
CONFUSE_ROOT_DIR
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
# Try to find Libintl functionality
|
||||||
|
# Once done this will define
|
||||||
|
#
|
||||||
|
# LIBINTL_FOUND - system has Libintl
|
||||||
|
# LIBINTL_INCLUDE_DIR - Libintl include directory
|
||||||
|
# LIBINTL_LIBRARIES - Libraries needed to use Libintl
|
||||||
|
#
|
||||||
|
# TODO: This will enable translations only if Gettext functionality is
|
||||||
|
# present in libc. Must have more robust system for release, where Gettext
|
||||||
|
# functionality can also reside in standalone Gettext library, or the one
|
||||||
|
# embedded within kdelibs (cf. gettext.m4 from Gettext source).
|
||||||
|
|
||||||
|
# Copyright (c) 2006, Chusslove Illich, <caslav.ilic@gmx.net>
|
||||||
|
# Copyright (c) 2007, Alexander Neundorf, <neundorf@kde.org>
|
||||||
|
#
|
||||||
|
# Redistribution and use is allowed according to the terms of the BSD license.
|
||||||
|
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
|
||||||
|
|
||||||
|
if(LIBINTL_INCLUDE_DIR AND LIBINTL_LIB_FOUND)
|
||||||
|
set(Libintl_FIND_QUIETLY TRUE)
|
||||||
|
endif(LIBINTL_INCLUDE_DIR AND LIBINTL_LIB_FOUND)
|
||||||
|
|
||||||
|
find_path(LIBINTL_INCLUDE_DIR libintl.h)
|
||||||
|
|
||||||
|
set(LIBINTL_LIB_FOUND FALSE)
|
||||||
|
|
||||||
|
if(LIBINTL_INCLUDE_DIR)
|
||||||
|
include(CheckFunctionExists)
|
||||||
|
check_function_exists(dgettext LIBINTL_LIBC_HAS_DGETTEXT)
|
||||||
|
|
||||||
|
if (LIBINTL_LIBC_HAS_DGETTEXT)
|
||||||
|
set(LIBINTL_LIBRARIES)
|
||||||
|
set(LIBINTL_LIB_FOUND TRUE)
|
||||||
|
else (LIBINTL_LIBC_HAS_DGETTEXT)
|
||||||
|
find_library(LIBINTL_LIBRARIES NAMES intl libintl )
|
||||||
|
if(LIBINTL_LIBRARIES)
|
||||||
|
set(LIBINTL_LIB_FOUND TRUE)
|
||||||
|
endif(LIBINTL_LIBRARIES)
|
||||||
|
endif (LIBINTL_LIBC_HAS_DGETTEXT)
|
||||||
|
|
||||||
|
endif(LIBINTL_INCLUDE_DIR)
|
||||||
|
|
||||||
|
include(FindPackageHandleStandardArgs)
|
||||||
|
find_package_handle_standard_args(Libintl DEFAULT_MSG LIBINTL_INCLUDE_DIR LIBINTL_LIB_FOUND)
|
||||||
|
|
||||||
|
mark_as_advanced(LIBINTL_INCLUDE_DIR LIBINTL_LIBRARIES LIBINTL_LIBC_HAS_DGETTEXT LIBINTL_LIB_FOUND)
|
||||||
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
# - Try to find the freetype library
|
||||||
|
# Once done this defines
|
||||||
|
#
|
||||||
|
# LIBUSB_FOUND - system has libusb
|
||||||
|
# LIBUSB_INCLUDE_DIR - the libusb include directory
|
||||||
|
# LIBUSB_LIBRARIES - Link these to use libusb
|
||||||
|
|
||||||
|
# Copyright (c) 2006, 2008 Laurent Montel, <montel@kde.org>
|
||||||
|
#
|
||||||
|
# Redistribution and use is allowed according to the terms of the BSD license.
|
||||||
|
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
|
||||||
|
|
||||||
|
|
||||||
|
if (LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES)
|
||||||
|
|
||||||
|
# in cache already
|
||||||
|
set(LIBUSB_FOUND TRUE)
|
||||||
|
|
||||||
|
else (LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES)
|
||||||
|
# use pkg-config to get the directories and then use these values
|
||||||
|
# in the FIND_PATH() and FIND_LIBRARY() calls
|
||||||
|
find_package(PkgConfig)
|
||||||
|
pkg_check_modules(PC_LIBUSB libusb-1.0)
|
||||||
|
|
||||||
|
FIND_PATH(LIBUSB_INCLUDE_DIR libusb.h
|
||||||
|
PATH_SUFFIXES libusb-1.0
|
||||||
|
PATHS ${PC_LIBUSB_INCLUDEDIR} ${PC_LIBUSB_INCLUDE_DIRS})
|
||||||
|
|
||||||
|
FIND_LIBRARY(LIBUSB_LIBRARIES NAMES usb-1.0
|
||||||
|
PATHS ${PC_LIBUSB_LIBDIR} ${PC_LIBUSB_LIBRARY_DIRS})
|
||||||
|
|
||||||
|
include(FindPackageHandleStandardArgs)
|
||||||
|
FIND_PACKAGE_HANDLE_STANDARD_ARGS(LIBUSB DEFAULT_MSG LIBUSB_LIBRARIES LIBUSB_INCLUDE_DIR)
|
||||||
|
|
||||||
|
MARK_AS_ADVANCED(LIBUSB_INCLUDE_DIR LIBUSB_LIBRARIES)
|
||||||
|
|
||||||
|
endif (LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES)
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
# -*- cmake -*-
|
||||||
|
#
|
||||||
|
# LibFTDI1Config.cmake(.in)
|
||||||
|
#
|
||||||
|
# Copyright (C) 2013 Intra2net AG and the libftdi developers
|
||||||
|
#
|
||||||
|
# This file is part of LibFTDI.
|
||||||
|
#
|
||||||
|
# LibFTDI is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License
|
||||||
|
# version 2.1 as published by the Free Software Foundation;
|
||||||
|
#
|
||||||
|
|
||||||
|
# Use the following variables to compile and link against LibFTDI:
|
||||||
|
# LIBFTDI_FOUND - True if LibFTDI was found on your system
|
||||||
|
# LIBFTDI_USE_FILE - The file making LibFTDI usable
|
||||||
|
# LIBFTDI_DEFINITIONS - Definitions needed to build with LibFTDI
|
||||||
|
# LIBFTDI_INCLUDE_DIRS - Directory where ftdi.h can be found
|
||||||
|
# LIBFTDI_INCLUDE_DIRS - List of directories of LibFTDI and it's dependencies
|
||||||
|
# LIBFTDI_LIBRARY - LibFTDI library location
|
||||||
|
# LIBFTDI_LIBRARIES - List of libraries to link against LibFTDI library
|
||||||
|
# LIBFTDIPP_LIBRARY - LibFTDI C++ wrapper library location
|
||||||
|
# LIBFTDIPP_LIBRARIES - List of libraries to link against LibFTDI C++ wrapper
|
||||||
|
# LIBFTDI_LIBRARY_DIRS - List of directories containing LibFTDI' libraries
|
||||||
|
# LIBFTDI_ROOT_DIR - The base directory of LibFTDI
|
||||||
|
# LIBFTDI_VERSION_STRING - A human-readable string containing the version
|
||||||
|
# LIBFTDI_VERSION_MAJOR - The major version of LibFTDI
|
||||||
|
# LIBFTDI_VERSION_MINOR - The minor version of LibFTDI
|
||||||
|
# LIBFTDI_VERSION_PATCH - The patch version of LibFTDI
|
||||||
|
# LIBFTDI_PYTHON_MODULE_PATH - Path to the python module
|
||||||
|
|
||||||
|
set ( LIBFTDI_FOUND 1 )
|
||||||
|
set ( LIBFTDI_USE_FILE "@LIBFTDI_USE_FILE@" )
|
||||||
|
|
||||||
|
set ( LIBFTDI_DEFINITIONS "@LIBFTDI_DEFINITIONS@" )
|
||||||
|
set ( LIBFTDI_INCLUDE_DIR "@LIBFTDI_INCLUDE_DIR@" )
|
||||||
|
set ( LIBFTDI_INCLUDE_DIRS "@LIBFTDI_INCLUDE_DIRS@" )
|
||||||
|
set ( LIBFTDI_LIBRARY "@LIBFTDI_LIBRARY@" )
|
||||||
|
set ( LIBFTDI_LIBRARIES "@LIBFTDI_LIBRARIES@" )
|
||||||
|
set ( LIBFTDI_STATIC_LIBRARY "@LIBFTDI_STATIC_LIBRARY@" )
|
||||||
|
set ( LIBFTDI_STATIC_LIBRARIES "@LIBFTDI_STATIC_LIBRARIES@" )
|
||||||
|
set ( LIBFTDIPP_LIBRARY "@LIBFTDIPP_LIBRARY@" )
|
||||||
|
set ( LIBFTDIPP_LIBRARIES "@LIBFTDIPP_LIBRARIES@" )
|
||||||
|
set ( LIBFTDI_LIBRARY_DIRS "@LIBFTDI_LIBRARY_DIRS@" )
|
||||||
|
set ( LIBFTDI_ROOT_DIR "@LIBFTDI_ROOT_DIR@" )
|
||||||
|
|
||||||
|
set ( LIBFTDI_VERSION_STRING "@LIBFTDI_VERSION_STRING@" )
|
||||||
|
set ( LIBFTDI_VERSION_MAJOR "@LIBFTDI_VERSION_MAJOR@" )
|
||||||
|
set ( LIBFTDI_VERSION_MINOR "@LIBFTDI_VERSION_MINOR@" )
|
||||||
|
set ( LIBFTDI_VERSION_PATCH "@LIBFTDI_VERSION_PATCH@" )
|
||||||
|
|
||||||
|
set ( LIBFTDI_PYTHON_MODULE_PATH "@LIBFTDI_PYTHON_MODULE_PATH@" )
|
||||||
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
# This is a basic version file for the Config-mode of find_package().
|
||||||
|
# It is used by write_basic_package_version_file() as input file for configure_file()
|
||||||
|
# to create a version-file which can be installed along a config.cmake file.
|
||||||
|
#
|
||||||
|
# The created file sets PACKAGE_VERSION_EXACT if the current version string and
|
||||||
|
# the requested version string are exactly the same and it sets
|
||||||
|
# PACKAGE_VERSION_COMPATIBLE if the current version is >= requested version.
|
||||||
|
# The variable CVF_VERSION must be set before calling configure_file().
|
||||||
|
|
||||||
|
set(PACKAGE_VERSION "@LIBFTDI_VERSION_STRING@")
|
||||||
|
|
||||||
|
if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}" )
|
||||||
|
set(PACKAGE_VERSION_COMPATIBLE FALSE)
|
||||||
|
else()
|
||||||
|
set(PACKAGE_VERSION_COMPATIBLE TRUE)
|
||||||
|
if( "${PACKAGE_FIND_VERSION}" STREQUAL "${PACKAGE_VERSION}")
|
||||||
|
set(PACKAGE_VERSION_EXACT TRUE)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# if the installed or the using project don't have CMAKE_SIZEOF_VOID_P set, ignore it:
|
||||||
|
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "" OR "8" STREQUAL "")
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# check that the installed version has the same 32/64bit-ness as the one which is currently searching:
|
||||||
|
if(NOT "${CMAKE_SIZEOF_VOID_P}" STREQUAL "8")
|
||||||
|
math(EXPR installedBits "8 * 8")
|
||||||
|
set(PACKAGE_VERSION "${PACKAGE_VERSION} (${installedBits}bit)")
|
||||||
|
set(PACKAGE_VERSION_UNSUITABLE TRUE)
|
||||||
|
endif()
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
set(CMAKE_SYSTEM_NAME Linux)
|
||||||
|
set(CMAKE_C_COMPILER gcc -m32)
|
||||||
|
set(CMAKE_CXX_COMPILER g++ -m32)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH /usr/lib)
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
# the name of the target operating system
|
||||||
|
SET(CMAKE_SYSTEM_NAME Windows)
|
||||||
|
|
||||||
|
# which compilers to use for C and C++
|
||||||
|
SET(CMAKE_C_COMPILER i686-w64-mingw32-gcc)
|
||||||
|
SET(CMAKE_CXX_COMPILER i686-w64-mingw32-g++)
|
||||||
|
|
||||||
|
# here is the target environment located
|
||||||
|
SET(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32 )
|
||||||
|
|
||||||
|
# adjust the default behaviour of the FIND_XXX() commands:
|
||||||
|
# search headers and libraries in the target environment, search
|
||||||
|
# programs in the host environment
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||||
|
set (CMAKE_RC_COMPILER i686-w64-mingw32-windres)
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
# the name of the target operating system
|
||||||
|
SET(CMAKE_SYSTEM_NAME Windows)
|
||||||
|
|
||||||
|
# which compilers to use for C and C++
|
||||||
|
SET(CMAKE_C_COMPILER i386-mingw32msvc-gcc)
|
||||||
|
SET(CMAKE_CXX_COMPILER i386-mingw32msvc-g++)
|
||||||
|
|
||||||
|
# here is the target environment located
|
||||||
|
SET(CMAKE_FIND_ROOT_PATH /opt/cross/i386-mingw32msvc )
|
||||||
|
|
||||||
|
# adjust the default behaviour of the FIND_XXX() commands:
|
||||||
|
# search headers and libraries in the target environment, search
|
||||||
|
# programs in the host environment
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
# the name of the target operating system
|
||||||
|
SET(CMAKE_SYSTEM_NAME Windows)
|
||||||
|
|
||||||
|
# which compilers to use for C and C++
|
||||||
|
SET(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
|
||||||
|
SET(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
|
||||||
|
|
||||||
|
# here is the target environment located
|
||||||
|
SET(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32 )
|
||||||
|
|
||||||
|
# adjust the default behaviour of the FIND_XXX() commands:
|
||||||
|
# search headers and libraries in the target environment, search
|
||||||
|
# programs in the host environment
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||||
|
set (CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# -*- cmake -*-
|
||||||
|
#
|
||||||
|
# UseLibFTDI.cmake
|
||||||
|
#
|
||||||
|
# Copyright (C) 2013 Intra2net AG and the libftdi developers
|
||||||
|
#
|
||||||
|
# This file is part of LibFTDI.
|
||||||
|
#
|
||||||
|
# LibFTDI is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License
|
||||||
|
# version 2.1 as published by the Free Software Foundation;
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
add_definitions ( ${LIBFTDI_DEFINITIONS} )
|
||||||
|
include_directories ( ${LIBFTDI_INCLUDE_DIRS} )
|
||||||
|
link_directories ( ${LIBFTDI_LIBRARY_DIRS} )
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,26 @@
|
||||||
|
# Doxyfile 1.7.4
|
||||||
|
|
||||||
|
# xml generation only
|
||||||
|
# keep settings but shut off all other generation
|
||||||
|
@INCLUDE = Doxyfile
|
||||||
|
|
||||||
|
GENERATE_TODOLIST = NO
|
||||||
|
GENERATE_TESTLIST = NO
|
||||||
|
GENERATE_BUGLIST = NO
|
||||||
|
GENERATE_DEPRECATEDLIST= NO
|
||||||
|
GENERATE_HTML = NO
|
||||||
|
GENERATE_DOCSET = NO
|
||||||
|
GENERATE_HTMLHELP = NO
|
||||||
|
GENERATE_CHI = NO
|
||||||
|
GENERATE_QHP = NO
|
||||||
|
GENERATE_ECLIPSEHELP = NO
|
||||||
|
GENERATE_TREEVIEW = NO
|
||||||
|
GENERATE_LATEX = NO
|
||||||
|
GENERATE_RTF = NO
|
||||||
|
GENERATE_MAN = NO
|
||||||
|
GENERATE_AUTOGEN_DEF = NO
|
||||||
|
GENERATE_PERLMOD = NO
|
||||||
|
GENERATE_TAGFILE =
|
||||||
|
GENERATE_LEGEND = NO
|
||||||
|
|
||||||
|
GENERATE_XML = YES
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
Here we try to document what we know about the EEPROM Structure.
|
||||||
|
|
||||||
|
Even with a 93xx66 EEPROM, at maximum 256 Bytes are used
|
||||||
|
|
||||||
|
All important things happen in the first
|
||||||
|
0x14(FT232/245), 0x16(FT2232CD), 0x18(FT232/245R) or 0x1a (FT2232H/4432H) bytes
|
||||||
|
|
||||||
|
Type | Use extra EEPROM space
|
||||||
|
FT2XXB | No
|
||||||
|
|
||||||
|
Byte.BIT| TYPE_AM TYPE_BM TYPE_2232C TYPE_R TYPE_2232H TYPE_4232H
|
||||||
|
00.0 | 0 0 channel_a_type 232R/245R channel_a_type 0
|
||||||
|
00.1 | 0 0 channel_a_type channel_a_type 0
|
||||||
|
00.2 | 0 0 channel_a_type high_current channel_a_type 0
|
||||||
|
00.3 | 0 0 channel_a_driver channel_a_driver channel_a_driver channel_a_driver
|
||||||
|
00.4 | 0 0 high_current_a 0 0 0
|
||||||
|
00.5 | 0 0 0 0 0 0
|
||||||
|
00.6 | 0 0 0 0 0 0
|
||||||
|
00.7 | 0 0 0 0 SUSPEND_DBUS7 channel_c_driver
|
||||||
|
|
||||||
|
On TYPE_R 00.0 is set for the FT245R and cleared for the FT232R
|
||||||
|
On TYPE_R 00.3 set mean D2XX, on other devices VCP
|
||||||
|
|
||||||
|
01.0 | 0 0 channel_b_type channel_b_type 0
|
||||||
|
01.1 | 0 0 channel_b_type channel_b_type 0
|
||||||
|
01.2 | 0 0 channel_b_type 0 channel_b_type 0
|
||||||
|
01.3 | 0 0 channel_b_driver 0 channel_b_driver channel_b_driver
|
||||||
|
01.4 | 0 0 high_current_b 0 0 0
|
||||||
|
01.5 | 0 0 0 0 0 0
|
||||||
|
01.6 | 0 0 0 0 0
|
||||||
|
01.7 | 0 0 0 0 0 channel_d_driver
|
||||||
|
|
||||||
|
Fixme: Missing 4232H validation
|
||||||
|
|
||||||
|
02 | Vendor ID (VID) LSB (all)
|
||||||
|
03 | Vendor ID (VID) MSB (all)
|
||||||
|
04 | Product ID (PID) LSB (all)
|
||||||
|
05 | Product ID (PID) MSB (all)
|
||||||
|
06 | Device release number LSB (not tested on TYPE_4232H)
|
||||||
|
07 | Device release number MSB (not tested on TYPE_4232H)
|
||||||
|
|
|
||||||
|
08.4 | Battery powered
|
||||||
|
08.5 | Remote wakeup
|
||||||
|
08.6 | Self powered: 1, bus powered: 0
|
||||||
|
08.7 | Always 1
|
||||||
|
|
|
||||||
|
09 | Max power (mA/2)
|
||||||
|
|
|
||||||
|
Byte.BIT| TYPE_AM TYPE_BM TYPE_2232C TYPE_R TYPE_2232H TYPE_4232H
|
||||||
|
0a.0 | 0 IsoIn IsoIn part A 0 0 0
|
||||||
|
0a.1 | 0 IsoOut IsoOut part A 0 0 0
|
||||||
|
0a.2 | 0 suspend_pull_down suspend_pull_down suspend_pull_down suspend_pull_down
|
||||||
|
0a.3 | 0 use_serial use_serial use_serial
|
||||||
|
0a.4 | 0 change_usb_version change_usb_version
|
||||||
|
0a.5 | 0 0 IsoIn part B 0 0 0
|
||||||
|
0a.6 | 0 0 IsoOut part B 0 0 0
|
||||||
|
0a.7 | 0 - reserved
|
||||||
|
|
||||||
|
0b | TYPE_R Bitmask Invert, 0 else
|
||||||
|
Byte.BIT| TYPE_4232H
|
||||||
|
0b.4 | channel_a_rs485enable
|
||||||
|
0b.5 | channel_b_rs485enable
|
||||||
|
0b.6 | channel_c_rs485enable
|
||||||
|
0b.7 | channel_d_rs485enable
|
||||||
|
|
||||||
|
Byte | TYPE_AM TYPE_BM TYPE_2232C TYPE_R TYPE_2232H TYPE_4232H
|
||||||
|
0c | 0 USB-VER-LSB USB-VER-LSB 0 ? ?
|
||||||
|
0d | 0 USB-VER-MSB USB-VER-MSB 0 ? ?
|
||||||
|
(On several FT2232H different values were observed -> The value is unused
|
||||||
|
if change USB version is not set, so it might contain garbage)
|
||||||
|
|
||||||
|
0e | OFFSET Vendor
|
||||||
|
0f | Len VENDOR
|
||||||
|
|
||||||
|
10 | Offset Product
|
||||||
|
11 | Length Product
|
||||||
|
|
||||||
|
12 | Offset Serial
|
||||||
|
13 | Length Serial
|
||||||
|
|
||||||
|
Byte.BIT| TYPE_AM TYPE_BM TYPE_2232C TYPE_R TYPE_2232H TYPE_4232H
|
||||||
|
14.3:0 | UA UA CHIP CBUS[0] AL A
|
||||||
|
14.7:0 | UA UA CHIP CBUS[1] AH B
|
||||||
|
15.3:0 | UA UA 0 CBUS[2] BL C
|
||||||
|
15.7:0 | UA UA 0 CBUS[3] BH D
|
||||||
|
16.3:0 | UA UA UA CBUS[4] 0 0
|
||||||
|
16.7:0 | UA UA UA 0 0 0
|
||||||
|
|
||||||
|
CHIP values:
|
||||||
|
0x46: EEPROM is a 93xx46
|
||||||
|
0x56: EEPROM is a 93xx56
|
||||||
|
0x66: EEPROM is a 93xx66
|
||||||
|
|
||||||
|
17 UA UA UA 0 0 0
|
||||||
|
18 UA UA UA VENDOR CHIP CHIP
|
||||||
|
19 UA UA UA VENDOR 0 0
|
||||||
|
|
||||||
|
1a UA (all)
|
||||||
|
|
||||||
|
|
||||||
|
Additional fields after the serial string:
|
||||||
|
0x00, 0x00 - reserved for "legacy port name prefix"
|
||||||
|
0x00, 0x00 - reserved for plug and play options
|
||||||
|
(Observed values with PnP == 0:
|
||||||
|
0x02 0x03 0x01 0x00)
|
||||||
|
|
||||||
|
Note: The additional fields after the serial number string
|
||||||
|
collide with the official FTDI formula from AN_121 regarding
|
||||||
|
the start of the user area:
|
||||||
|
"Start Address = the address following the last byte of SerialNumber string."
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# Astyle settings used to format our source code
|
||||||
|
/usr/bin/astyle --indent=spaces=4 --indent-switches --brackets=break \
|
||||||
|
--convert-tabs --keep-one-line-statements --keep-one-line-blocks \
|
||||||
|
$*
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
*** Checklist for a new libftdi release ***
|
||||||
|
- Update ChangeLog and AUTHORS via git history
|
||||||
|
(git log --oneline latest_release..HEAD)
|
||||||
|
|
||||||
|
- Update version number in the following files:
|
||||||
|
- CMakeLists.txt
|
||||||
|
- README
|
||||||
|
|
||||||
|
- Run "make dist"
|
||||||
|
|
||||||
|
- Diff tarball to previous version, check if all
|
||||||
|
important changes are in the ChangeLog
|
||||||
|
|
||||||
|
- Ensure all modifications are checked in
|
||||||
|
|
||||||
|
- Sign tarball, build .src.rpm and sign it, too
|
||||||
|
|
||||||
|
- Create git tag:
|
||||||
|
- git tag -s -u 24F006F5 v1.XX
|
||||||
|
- git tag -d latest_release ; git tag latest_release
|
||||||
|
- git push --tags
|
||||||
|
|
||||||
|
- Website
|
||||||
|
- Upload tarball and .src.rpm
|
||||||
|
- Add ChangeLog to main page
|
||||||
|
- Update URLs in download section
|
||||||
|
- Generate API documentation and upload it
|
||||||
|
|
||||||
|
- Announce on mailinglist
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
option(EXAMPLES "Build example programs" ON)
|
||||||
|
|
||||||
|
if (EXAMPLES)
|
||||||
|
# Includes
|
||||||
|
include_directories( ${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
message(STATUS "Building example programs.")
|
||||||
|
|
||||||
|
# Targets
|
||||||
|
add_executable(simple simple.c)
|
||||||
|
add_executable(bitbang bitbang.c)
|
||||||
|
add_executable(bitbang2 bitbang2.c)
|
||||||
|
add_executable(bitbang_cbus bitbang_cbus.c)
|
||||||
|
add_executable(bitbang_ft2232 bitbang_ft2232.c)
|
||||||
|
add_executable(find_all find_all.c)
|
||||||
|
add_executable(serial_test serial_test.c)
|
||||||
|
add_executable(baud_test baud_test.c)
|
||||||
|
add_executable(stream_test stream_test.c)
|
||||||
|
add_executable(eeprom eeprom.c)
|
||||||
|
|
||||||
|
# Linkage
|
||||||
|
target_link_libraries(simple ftdi1)
|
||||||
|
target_link_libraries(bitbang ftdi1)
|
||||||
|
target_link_libraries(bitbang2 ftdi1)
|
||||||
|
target_link_libraries(bitbang_cbus ftdi1)
|
||||||
|
target_link_libraries(bitbang_ft2232 ftdi1)
|
||||||
|
target_link_libraries(find_all ftdi1)
|
||||||
|
target_link_libraries(serial_test ftdi1)
|
||||||
|
target_link_libraries(baud_test ftdi1)
|
||||||
|
target_link_libraries(stream_test ftdi1)
|
||||||
|
target_link_libraries(eeprom ftdi1)
|
||||||
|
|
||||||
|
# libftdi++ examples
|
||||||
|
if(FTDI_BUILD_CPP)
|
||||||
|
if(Boost_FOUND)
|
||||||
|
message(STATUS "Building libftdi++ examples.")
|
||||||
|
include_directories(BEFORE ${CMAKE_SOURCE_DIR}/ftdipp
|
||||||
|
${Boost_INCLUDE_DIRS})
|
||||||
|
|
||||||
|
# Target
|
||||||
|
add_executable(find_all_pp find_all_pp.cpp)
|
||||||
|
|
||||||
|
# Linkage
|
||||||
|
target_link_libraries(find_all_pp ftdipp1)
|
||||||
|
endif(Boost_FOUND)
|
||||||
|
endif(FTDI_BUILD_CPP)
|
||||||
|
|
||||||
|
# Source includes
|
||||||
|
include_directories(BEFORE ${CMAKE_SOURCE_DIR}/src)
|
||||||
|
|
||||||
|
else(EXAMPLES)
|
||||||
|
message(STATUS "Not building example programs.")
|
||||||
|
endif(EXAMPLES)
|
||||||
|
|
@ -0,0 +1,224 @@
|
||||||
|
/* baud_test.c
|
||||||
|
*
|
||||||
|
* test setting the baudrate and compare it with the expected runtime
|
||||||
|
*
|
||||||
|
* options:
|
||||||
|
* -p <devicestring> defaults to "i:0x0403:0x6001" (this is the first FT232R with default id)
|
||||||
|
* d:<devicenode> path of bus and device-node (e.g. "003/001") within usb device tree (usually at /proc/bus/usb/)
|
||||||
|
* i:<vendor>:<product> first device with given vendor and product id,
|
||||||
|
* ids can be decimal, octal (preceded by "0") or hex (preceded by "0x")
|
||||||
|
* i:<vendor>:<product>:<index> as above with index being the number of the device (starting with 0)
|
||||||
|
* if there are more than one
|
||||||
|
* s:<vendor>:<product>:<serial> first device with given vendor id, product id and serial string
|
||||||
|
* -d <datasize to send in bytes>
|
||||||
|
* -b <baudrate> (divides by 16 if bitbang as taken from the ftdi datasheets)
|
||||||
|
* -m <mode to use> r: serial a: async bitbang s:sync bitbang
|
||||||
|
* -c <chunksize>
|
||||||
|
*
|
||||||
|
* (C) 2009 by Gerd v. Egidy <gerd.von.egidy@intra2net.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <ftdi.h>
|
||||||
|
|
||||||
|
double get_prec_time()
|
||||||
|
{
|
||||||
|
struct timeval tv;
|
||||||
|
double res;
|
||||||
|
|
||||||
|
gettimeofday(&tv,NULL);
|
||||||
|
|
||||||
|
res=tv.tv_sec;
|
||||||
|
res+=((double)tv.tv_usec/1000000);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
struct ftdi_context *ftdi;
|
||||||
|
int i, t;
|
||||||
|
unsigned char *txbuf;
|
||||||
|
unsigned char *rxbuf;
|
||||||
|
double start, duration, plan;
|
||||||
|
int retval= 0;
|
||||||
|
|
||||||
|
// default values
|
||||||
|
int baud=9600;
|
||||||
|
int set_baud;
|
||||||
|
int datasize=100000;
|
||||||
|
|
||||||
|
char default_devicedesc[] = "i:0x0403:0x6001";
|
||||||
|
char *devicedesc=default_devicedesc;
|
||||||
|
int txchunksize=256;
|
||||||
|
enum ftdi_mpsse_mode test_mode=BITMODE_BITBANG;
|
||||||
|
|
||||||
|
while ((t = getopt (argc, argv, "b:d:p:m:c:")) != -1)
|
||||||
|
{
|
||||||
|
switch (t)
|
||||||
|
{
|
||||||
|
case 'd':
|
||||||
|
datasize = atoi (optarg);
|
||||||
|
break;
|
||||||
|
case 'm':
|
||||||
|
switch (*optarg)
|
||||||
|
{
|
||||||
|
case 'r':
|
||||||
|
// serial
|
||||||
|
test_mode=BITMODE_RESET;
|
||||||
|
break;
|
||||||
|
case 'a':
|
||||||
|
// async
|
||||||
|
test_mode=BITMODE_BITBANG;
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
// sync
|
||||||
|
test_mode=BITMODE_SYNCBB;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'b':
|
||||||
|
baud = atoi (optarg);
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
devicedesc=optarg;
|
||||||
|
break;
|
||||||
|
case 'c':
|
||||||
|
txchunksize = atoi (optarg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
txbuf=malloc(txchunksize);
|
||||||
|
rxbuf=malloc(txchunksize);
|
||||||
|
if (txbuf == NULL || rxbuf == NULL)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "can't malloc\n");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((ftdi = ftdi_new()) == 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ftdi_new failed\n");
|
||||||
|
retval = EXIT_FAILURE;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ftdi_usb_open_string(ftdi, devicedesc) < 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"Can't open ftdi device: %s\n",ftdi_get_error_string(ftdi));
|
||||||
|
retval = EXIT_FAILURE;
|
||||||
|
goto do_deinit;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_baud=baud;
|
||||||
|
if (test_mode!=BITMODE_RESET)
|
||||||
|
{
|
||||||
|
// we do bitbang, so real baudrate / 16
|
||||||
|
set_baud=baud/16;
|
||||||
|
}
|
||||||
|
|
||||||
|
ftdi_set_baudrate(ftdi,set_baud);
|
||||||
|
printf("real baudrate used: %d\n",(test_mode==BITMODE_RESET) ? ftdi->baudrate : ftdi->baudrate*16);
|
||||||
|
|
||||||
|
if (ftdi_set_bitmode(ftdi, 0xFF,test_mode) < 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"Can't set mode: %s\n",ftdi_get_error_string(ftdi));
|
||||||
|
retval = EXIT_FAILURE;
|
||||||
|
goto do_close;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (test_mode==BITMODE_RESET)
|
||||||
|
{
|
||||||
|
// serial 8N1: 8 data bits, 1 startbit, 1 stopbit
|
||||||
|
plan=((double)(datasize*10))/baud;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// bitbang means 8 bits at once
|
||||||
|
plan=((double)datasize)/baud;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("this test should take %.2f seconds\n",plan);
|
||||||
|
|
||||||
|
// prepare data to send: 0 and 1 bits alternating (except for serial start/stopbit):
|
||||||
|
// maybe someone wants to look at this with a scope or logic analyzer
|
||||||
|
for (i=0; i<txchunksize; i++)
|
||||||
|
{
|
||||||
|
if (test_mode==BITMODE_RESET)
|
||||||
|
txbuf[i]=0xAA;
|
||||||
|
else
|
||||||
|
txbuf[i]=(i%2) ? 0xff : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ftdi_write_data_set_chunksize(ftdi, txchunksize) < 0 ||
|
||||||
|
ftdi_read_data_set_chunksize(ftdi, txchunksize) < 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"Can't set chunksize: %s\n",ftdi_get_error_string(ftdi));
|
||||||
|
retval = EXIT_FAILURE;
|
||||||
|
goto do_close;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (test_mode==BITMODE_SYNCBB)
|
||||||
|
{
|
||||||
|
// completely clear the receive buffer before beginning
|
||||||
|
while (ftdi_read_data(ftdi, rxbuf, txchunksize)>0);
|
||||||
|
}
|
||||||
|
|
||||||
|
start=get_prec_time();
|
||||||
|
|
||||||
|
// don't wait for more data to arrive, take what we get and keep on sending
|
||||||
|
// yes, we really would like to have libusb 1.0+ with async read/write...
|
||||||
|
ftdi->usb_read_timeout=1;
|
||||||
|
|
||||||
|
i=0;
|
||||||
|
while (i < datasize)
|
||||||
|
{
|
||||||
|
int sendsize=txchunksize;
|
||||||
|
if (i+sendsize > datasize)
|
||||||
|
sendsize=datasize-i;
|
||||||
|
|
||||||
|
if ((sendsize=ftdi_write_data(ftdi, txbuf, sendsize)) < 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"write failed at %d: %s\n",
|
||||||
|
i, ftdi_get_error_string(ftdi));
|
||||||
|
retval = EXIT_FAILURE;
|
||||||
|
goto do_close;
|
||||||
|
}
|
||||||
|
|
||||||
|
i+=sendsize;
|
||||||
|
|
||||||
|
if (test_mode==BITMODE_SYNCBB)
|
||||||
|
{
|
||||||
|
// read the same amount of data as sent
|
||||||
|
ftdi_read_data(ftdi, rxbuf, sendsize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
duration=get_prec_time()-start;
|
||||||
|
printf("and took %.4f seconds, this is %.0f baud or factor %.3f\n",duration,(plan*baud)/duration,plan/duration);
|
||||||
|
do_close:
|
||||||
|
ftdi_usb_close(ftdi);
|
||||||
|
do_deinit:
|
||||||
|
ftdi_free(ftdi);
|
||||||
|
done:
|
||||||
|
if(rxbuf)
|
||||||
|
free(rxbuf);
|
||||||
|
if(txbuf)
|
||||||
|
free(txbuf);
|
||||||
|
exit (retval);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
/* This program is distributed under the GPL, version 2 */
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <ftdi.h>
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
struct ftdi_context *ftdi;
|
||||||
|
int f,i;
|
||||||
|
unsigned char buf[1];
|
||||||
|
int retval = 0;
|
||||||
|
|
||||||
|
if ((ftdi = ftdi_new()) == 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ftdi_new failed\n");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
f = ftdi_usb_open(ftdi, 0x0403, 0x6001);
|
||||||
|
|
||||||
|
if (f < 0 && f != -5)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "unable to open ftdi device: %d (%s)\n", f, ftdi_get_error_string(ftdi));
|
||||||
|
retval = 1;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("ftdi open succeeded: %d\n",f);
|
||||||
|
|
||||||
|
printf("enabling bitbang mode\n");
|
||||||
|
ftdi_set_bitmode(ftdi, 0xFF, BITMODE_BITBANG);
|
||||||
|
|
||||||
|
usleep(3 * 1000000);
|
||||||
|
|
||||||
|
buf[0] = 0x0;
|
||||||
|
printf("turning everything on\n");
|
||||||
|
f = ftdi_write_data(ftdi, buf, 1);
|
||||||
|
if (f < 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"write failed for 0x%x, error %d (%s)\n",buf[0],f, ftdi_get_error_string(ftdi));
|
||||||
|
}
|
||||||
|
|
||||||
|
usleep(3 * 1000000);
|
||||||
|
|
||||||
|
buf[0] = 0xFF;
|
||||||
|
printf("turning everything off\n");
|
||||||
|
f = ftdi_write_data(ftdi, buf, 1);
|
||||||
|
if (f < 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"write failed for 0x%x, error %d (%s)\n",buf[0],f, ftdi_get_error_string(ftdi));
|
||||||
|
}
|
||||||
|
|
||||||
|
usleep(3 * 1000000);
|
||||||
|
|
||||||
|
for (i = 0; i < 32; i++)
|
||||||
|
{
|
||||||
|
buf[0] = 0 | (0xFF ^ 1 << (i % 8));
|
||||||
|
if ( i > 0 && (i % 8) == 0)
|
||||||
|
{
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
printf("%02hhx ",buf[0]);
|
||||||
|
fflush(stdout);
|
||||||
|
f = ftdi_write_data(ftdi, buf, 1);
|
||||||
|
if (f < 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"write failed for 0x%x, error %d (%s)\n",buf[0],f, ftdi_get_error_string(ftdi));
|
||||||
|
}
|
||||||
|
usleep(1 * 1000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
printf("disabling bitbang mode\n");
|
||||||
|
ftdi_disable_bitbang(ftdi);
|
||||||
|
|
||||||
|
ftdi_usb_close(ftdi);
|
||||||
|
done:
|
||||||
|
ftdi_free(ftdi);
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
/* ftdi_out.c
|
||||||
|
*
|
||||||
|
* Output a (stream of) byte(s) in bitbang mode to the
|
||||||
|
* ftdi245 chip that is (hopefully) attached.
|
||||||
|
*
|
||||||
|
* We have a little board that has a FT245BM chip and
|
||||||
|
* the 8 outputs are connected to several different
|
||||||
|
* things that we can turn on and off with this program.
|
||||||
|
*
|
||||||
|
* If you have an idea about hardware that can easily
|
||||||
|
* interface onto an FTDI chip, I'd like to collect
|
||||||
|
* ideas. If I find it worthwhile to make, I'll consider
|
||||||
|
* making it, I'll even send you a prototype (against
|
||||||
|
* cost-of-material) if you want.
|
||||||
|
*
|
||||||
|
* At "harddisk-recovery.nl" they have a little board that
|
||||||
|
* controls the power to two harddrives and two fans.
|
||||||
|
*
|
||||||
|
* -- REW R.E.Wolff@BitWizard.nl
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* This program was based on libftdi_example_bitbang2232.c
|
||||||
|
* which doesn't carry an author or attribution header.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* This program is distributed under the GPL, version 2.
|
||||||
|
* Millions copies of the GPL float around the internet.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <ftdi.h>
|
||||||
|
|
||||||
|
void ftdi_fatal (struct ftdi_context *ftdi, char *str)
|
||||||
|
{
|
||||||
|
fprintf (stderr, "%s: %s\n",
|
||||||
|
str, ftdi_get_error_string (ftdi));
|
||||||
|
ftdi_free(ftdi);
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
struct ftdi_context *ftdi;
|
||||||
|
int i, t;
|
||||||
|
unsigned char data;
|
||||||
|
int delay = 100000; /* 100 thousand microseconds: 1 tenth of a second */
|
||||||
|
|
||||||
|
while ((t = getopt (argc, argv, "d:")) != -1)
|
||||||
|
{
|
||||||
|
switch (t)
|
||||||
|
{
|
||||||
|
case 'd':
|
||||||
|
delay = atoi (optarg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((ftdi = ftdi_new()) == 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ftdi_bew failed\n");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ftdi_usb_open(ftdi, 0x0403, 0x6001) < 0)
|
||||||
|
ftdi_fatal (ftdi, "Can't open ftdi device");
|
||||||
|
|
||||||
|
if (ftdi_set_bitmode(ftdi, 0xFF, BITMODE_BITBANG) < 0)
|
||||||
|
ftdi_fatal (ftdi, "Can't enable bitbang");
|
||||||
|
|
||||||
|
for (i=optind; i < argc ; i++)
|
||||||
|
{
|
||||||
|
sscanf (argv[i], "%x", &t);
|
||||||
|
data = t;
|
||||||
|
if (ftdi_write_data(ftdi, &data, 1) < 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"write failed for 0x%x: %s\n",
|
||||||
|
data, ftdi_get_error_string(ftdi));
|
||||||
|
}
|
||||||
|
usleep(delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
ftdi_usb_close(ftdi);
|
||||||
|
ftdi_free(ftdi);
|
||||||
|
exit (0);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
/* bitbang_cbus.c
|
||||||
|
|
||||||
|
Example to use CBUS bitbang mode of newer chipsets.
|
||||||
|
You must enable CBUS bitbang mode in the EEPROM first.
|
||||||
|
|
||||||
|
Thanks to Steve Brown <sbrown@ewol.com> for the
|
||||||
|
the information how to do it.
|
||||||
|
|
||||||
|
The top nibble controls input/output and the bottom nibble
|
||||||
|
controls the state of the lines set to output. The datasheet isn't clear
|
||||||
|
what happens if you set a bit in the output register when that line is
|
||||||
|
conditioned for input. This is described in more detail
|
||||||
|
in the FT232R bitbang app note.
|
||||||
|
|
||||||
|
BITMASK
|
||||||
|
CBUS Bits
|
||||||
|
3210 3210
|
||||||
|
xxxx xxxx
|
||||||
|
| |------ Output Control 0->LO, 1->HI
|
||||||
|
|----------- Input/Output 0->Input, 1->Output
|
||||||
|
|
||||||
|
Example:
|
||||||
|
All pins to output with 0 bit high: 0xF1 (11110001)
|
||||||
|
Bits 0 and 1 to input, 2 and 3 to output and masked high: 0xCC (11001100)
|
||||||
|
|
||||||
|
The input is standard "0x" hex notation.
|
||||||
|
A carriage return terminates the program.
|
||||||
|
|
||||||
|
This program is distributed under the GPL, version 2
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <ftdi.h>
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
struct ftdi_context *ftdi;
|
||||||
|
int f;
|
||||||
|
unsigned char buf[1];
|
||||||
|
unsigned char bitmask;
|
||||||
|
char input[10];
|
||||||
|
|
||||||
|
if ((ftdi = ftdi_new()) == 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ftdi_new failed\n");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
f = ftdi_usb_open(ftdi, 0x0403, 0x6001);
|
||||||
|
if (f < 0 && f != -5)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "unable to open ftdi device: %d (%s)\n", f, ftdi_get_error_string(ftdi));
|
||||||
|
ftdi_free(ftdi);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
printf("ftdi open succeeded: %d\n",f);
|
||||||
|
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
// Set bitmask from input
|
||||||
|
fgets(input, sizeof(input) - 1, stdin);
|
||||||
|
if (input[0] == '\n') break;
|
||||||
|
bitmask = strtol(input, NULL, 0);
|
||||||
|
printf("Using bitmask 0x%02x\n", bitmask);
|
||||||
|
f = ftdi_set_bitmode(ftdi, bitmask, BITMODE_CBUS);
|
||||||
|
if (f < 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "set_bitmode failed for 0x%x, error %d (%s)\n", bitmask, f, ftdi_get_error_string(ftdi));
|
||||||
|
ftdi_usb_close(ftdi);
|
||||||
|
ftdi_free(ftdi);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// read CBUS
|
||||||
|
f = ftdi_read_pins(ftdi, &buf[0]);
|
||||||
|
if (f < 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "read_pins failed, error %d (%s)\n", f, ftdi_get_error_string(ftdi));
|
||||||
|
ftdi_usb_close(ftdi);
|
||||||
|
ftdi_free(ftdi);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
printf("Read returned 0x%01x\n", buf[0] & 0x0f);
|
||||||
|
}
|
||||||
|
printf("disabling bitbang mode\n");
|
||||||
|
ftdi_disable_bitbang(ftdi);
|
||||||
|
|
||||||
|
ftdi_usb_close(ftdi);
|
||||||
|
ftdi_free(ftdi);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
[Basic Details]
|
||||||
|
Device Type=6
|
||||||
|
VID PID Type=0
|
||||||
|
USB VID=0403
|
||||||
|
USB PID=6001
|
||||||
|
[USB Power Options]
|
||||||
|
Bus Powered=1
|
||||||
|
Self Powered=0
|
||||||
|
Max Bus Power=44
|
||||||
|
[USB Serial Number Control]
|
||||||
|
Prefix=FT
|
||||||
|
Use Fixed Serial Number=0
|
||||||
|
Fixed Serial Number=FTDECZJT
|
||||||
|
[USB Remote WakeUp]
|
||||||
|
Enable Remote WakeUp=1
|
||||||
|
[Windows Plug and Play]
|
||||||
|
Enable Plug and Play=0
|
||||||
|
[USB String Descriptors]
|
||||||
|
Manufacturer=FTDI
|
||||||
|
Product=USB Serial Converter
|
||||||
|
[Programming Options]
|
||||||
|
Only Program Blank Devices=0
|
||||||
|
[BM Device Specific Options]
|
||||||
|
USB Version Number=1
|
||||||
|
Disable Serial Number=0
|
||||||
|
IO Pin Pull Down in Suspend=0
|
||||||
|
[Dual Device Specific Options A]
|
||||||
|
RS 232 mode=1
|
||||||
|
245 FIFO mode=0
|
||||||
|
245 CPU FIFO mode=0
|
||||||
|
OPTO Isolate mode=1
|
||||||
|
High Current Drive=0
|
||||||
|
[Dual Device Specific Options B]
|
||||||
|
RS 232 mode=1
|
||||||
|
245 FIFO mode=0
|
||||||
|
245 CPU FIFO mode=0
|
||||||
|
OPTO Isolate mode=0
|
||||||
|
High Current Drive=0
|
||||||
|
[Dual Device Driver Options A]
|
||||||
|
Virtual Com Port Driver=1
|
||||||
|
D2XX Driver=0
|
||||||
|
[Dual Device Driver Options B]
|
||||||
|
Virtual Com Port Driver=1
|
||||||
|
D2XX Driver=0
|
||||||
|
[R Device Specific Options]
|
||||||
|
Invert TXD=0
|
||||||
|
Invert RXD=0
|
||||||
|
Invert RTS#=0
|
||||||
|
Invert CTS#=0
|
||||||
|
Invert DTR#=0
|
||||||
|
Invert DSR#=0
|
||||||
|
Invert DCD#=0
|
||||||
|
Invert RI#=0
|
||||||
|
C0 Signal=10
|
||||||
|
C1 Signal=10
|
||||||
|
C2 Signal=10
|
||||||
|
C3 Signal=10
|
||||||
|
C4 Signal=5
|
||||||
|
Enable Ext Osc=0
|
||||||
|
High Current I/O=0
|
||||||
|
Load D2XX Driver=0
|
||||||
|
In EndPoint Size=0
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
/* bitbang_ft2232.c
|
||||||
|
|
||||||
|
Output some flickering in bitbang mode to the FT2232
|
||||||
|
|
||||||
|
Thanks to max@koeln.ccc.de for fixing and extending
|
||||||
|
the example for the second channel.
|
||||||
|
|
||||||
|
This program is distributed under the GPL, version 2
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <ftdi.h>
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
struct ftdi_context *ftdi, *ftdi2;
|
||||||
|
unsigned char buf[1];
|
||||||
|
int f,i;
|
||||||
|
|
||||||
|
// Init 1. channel
|
||||||
|
if ((ftdi = ftdi_new()) == 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ftdi_new failed\n");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
ftdi_set_interface(ftdi, INTERFACE_A);
|
||||||
|
f = ftdi_usb_open(ftdi, 0x0403, 0x6001);
|
||||||
|
if (f < 0 && f != -5)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "unable to open ftdi device: %d (%s)\n", f, ftdi_get_error_string(ftdi));
|
||||||
|
ftdi_free(ftdi);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
printf("ftdi open succeeded(channel 1): %d\n",f);
|
||||||
|
|
||||||
|
printf("enabling bitbang mode(channel 1)\n");
|
||||||
|
ftdi_set_bitmode(ftdi, 0xFF, BITMODE_BITBANG);
|
||||||
|
|
||||||
|
// Init 2. channel
|
||||||
|
if ((ftdi2 = ftdi_new()) == 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ftdi_new failed\n");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
ftdi_set_interface(ftdi2, INTERFACE_B);
|
||||||
|
f = ftdi_usb_open(ftdi2, 0x0403, 0x6001);
|
||||||
|
if (f < 0 && f != -5)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "unable to open ftdi device: %d (%s)\n", f, ftdi_get_error_string(ftdi2));
|
||||||
|
ftdi_free(ftdi2);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
printf("ftdi open succeeded(channel 2): %d\n",f);
|
||||||
|
|
||||||
|
printf("enabling bitbang mode (channel 2)\n");
|
||||||
|
ftdi_set_bitmode(ftdi2, 0xFF, BITMODE_BITBANG);
|
||||||
|
|
||||||
|
// Write data
|
||||||
|
printf("startloop\n");
|
||||||
|
for (i = 0; i < 23; i++)
|
||||||
|
{
|
||||||
|
buf[0] = 0x1;
|
||||||
|
printf("porta: %02i: 0x%02x \n",i,buf[0]);
|
||||||
|
f = ftdi_write_data(ftdi, buf, 1);
|
||||||
|
if (f < 0)
|
||||||
|
fprintf(stderr,"write failed on channel 1 for 0x%x, error %d (%s)\n", buf[0], f, ftdi_get_error_string(ftdi));
|
||||||
|
usleep(1 * 1000000);
|
||||||
|
|
||||||
|
buf[0] = 0x2;
|
||||||
|
printf("porta: %02i: 0x%02x \n",i,buf[0]);
|
||||||
|
f = ftdi_write_data(ftdi, buf, 1);
|
||||||
|
if (f < 0)
|
||||||
|
fprintf(stderr,"write failed on channel 1 for 0x%x, error %d (%s)\n", buf[0], f, ftdi_get_error_string(ftdi));
|
||||||
|
usleep(1 * 1000000);
|
||||||
|
|
||||||
|
buf[0] = 0x1;
|
||||||
|
printf("portb: %02i: 0x%02x \n",i,buf[0]);
|
||||||
|
f = ftdi_write_data(ftdi2, buf, 1);
|
||||||
|
if (f < 0)
|
||||||
|
fprintf(stderr,"write failed on channel 2 for 0x%x, error %d (%s)\n", buf[0], f, ftdi_get_error_string(ftdi2));
|
||||||
|
usleep(1 * 1000000);
|
||||||
|
|
||||||
|
buf[0] = 0x2;
|
||||||
|
printf("portb: %02i: 0x%02x \n",i,buf[0]);
|
||||||
|
f = ftdi_write_data(ftdi2, buf, 1);
|
||||||
|
if (f < 0)
|
||||||
|
fprintf(stderr,"write failed on channel 2 for 0x%x, error %d (%s)\n", buf[0], f, ftdi_get_error_string(ftdi2));
|
||||||
|
usleep(1 * 1000000);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
printf("disabling bitbang mode(channel 1)\n");
|
||||||
|
ftdi_disable_bitbang(ftdi);
|
||||||
|
ftdi_usb_close(ftdi);
|
||||||
|
ftdi_free(ftdi);
|
||||||
|
|
||||||
|
printf("disabling bitbang mode(channel 2)\n");
|
||||||
|
ftdi_disable_bitbang(ftdi2);
|
||||||
|
ftdi_usb_close(ftdi2);
|
||||||
|
ftdi_free(ftdi2);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
cmake_minimum_required ( VERSION 2.8 )
|
||||||
|
|
||||||
|
project ( example C )
|
||||||
|
|
||||||
|
find_package ( LibFTDI1 NO_MODULE REQUIRED )
|
||||||
|
include ( ${LIBFTDI_USE_FILE} )
|
||||||
|
|
||||||
|
add_executable ( example main.c )
|
||||||
|
target_link_libraries( example ${LIBFTDI_LIBRARIES} )
|
||||||
|
|
||||||
|
install ( TARGETS example
|
||||||
|
DESTINATION bin )
|
||||||
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
/* main.c
|
||||||
|
|
||||||
|
Example for ftdi_new()
|
||||||
|
|
||||||
|
This program is distributed under the GPL, version 2
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <ftdi.h>
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
struct ftdi_context *ftdi;
|
||||||
|
int retval = EXIT_SUCCESS;
|
||||||
|
|
||||||
|
if ((ftdi = ftdi_new()) == 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ftdi_new failed\n");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,299 @@
|
||||||
|
/* LIBFTDI EEPROM access example
|
||||||
|
|
||||||
|
This program is distributed under the GPL, version 2
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <ftdi.h>
|
||||||
|
|
||||||
|
int read_decode_eeprom(struct ftdi_context *ftdi)
|
||||||
|
{
|
||||||
|
int i, j, f;
|
||||||
|
int value;
|
||||||
|
int size;
|
||||||
|
unsigned char buf[256];
|
||||||
|
|
||||||
|
f = ftdi_read_eeprom(ftdi);
|
||||||
|
if (f < 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ftdi_read_eeprom: %d (%s)\n",
|
||||||
|
f, ftdi_get_error_string(ftdi));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ftdi_get_eeprom_value(ftdi, CHIP_SIZE, & value);
|
||||||
|
if (value <0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "No EEPROM found or EEPROM empty\n");
|
||||||
|
fprintf(stderr, "On empty EEPROM, use -w option to write default values\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
fprintf(stderr, "Chip type %d ftdi_eeprom_size: %d\n", ftdi->type, value);
|
||||||
|
if (ftdi->type == TYPE_R)
|
||||||
|
size = 0xa0;
|
||||||
|
else
|
||||||
|
size = value;
|
||||||
|
ftdi_get_eeprom_buf(ftdi, buf, size);
|
||||||
|
for (i=0; i < size; i += 16)
|
||||||
|
{
|
||||||
|
fprintf(stdout,"0x%03x:", i);
|
||||||
|
|
||||||
|
for (j = 0; j< 8; j++)
|
||||||
|
fprintf(stdout," %02x", buf[i+j]);
|
||||||
|
fprintf(stdout," ");
|
||||||
|
for (; j< 16; j++)
|
||||||
|
fprintf(stdout," %02x", buf[i+j]);
|
||||||
|
fprintf(stdout," ");
|
||||||
|
for (j = 0; j< 8; j++)
|
||||||
|
fprintf(stdout,"%c", isprint(buf[i+j])?buf[i+j]:'.');
|
||||||
|
fprintf(stdout," ");
|
||||||
|
for (; j< 16; j++)
|
||||||
|
fprintf(stdout,"%c", isprint(buf[i+j])?buf[i+j]:'.');
|
||||||
|
fprintf(stdout,"\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
f = ftdi_eeprom_decode(ftdi, 1);
|
||||||
|
if (f < 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ftdi_eeprom_decode: %d (%s)\n",
|
||||||
|
f, ftdi_get_error_string(ftdi));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
struct ftdi_context *ftdi;
|
||||||
|
int f, i;
|
||||||
|
int vid = 0;
|
||||||
|
int pid = 0;
|
||||||
|
char const *desc = 0;
|
||||||
|
char const *serial = 0;
|
||||||
|
int erase = 0;
|
||||||
|
int use_defaults = 0;
|
||||||
|
int large_chip = 0;
|
||||||
|
int do_write = 0;
|
||||||
|
int retval = 0;
|
||||||
|
int value;
|
||||||
|
|
||||||
|
if ((ftdi = ftdi_new()) == 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Failed to allocate ftdi structure :%s \n",
|
||||||
|
ftdi_get_error_string(ftdi));
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
while ((i = getopt(argc, argv, "d::ev:p:l:P:S:w")) != -1)
|
||||||
|
{
|
||||||
|
switch (i)
|
||||||
|
{
|
||||||
|
case 'd':
|
||||||
|
use_defaults = 1;
|
||||||
|
if (optarg)
|
||||||
|
large_chip = 0x66;
|
||||||
|
break;
|
||||||
|
case 'e':
|
||||||
|
erase = 1;
|
||||||
|
break;
|
||||||
|
case 'v':
|
||||||
|
vid = strtoul(optarg, NULL, 0);
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
pid = strtoul(optarg, NULL, 0);
|
||||||
|
break;
|
||||||
|
case 'P':
|
||||||
|
desc = optarg;
|
||||||
|
break;
|
||||||
|
case 'S':
|
||||||
|
serial = optarg;
|
||||||
|
break;
|
||||||
|
case 'w':
|
||||||
|
do_write = 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "usage: %s [options]\n", *argv);
|
||||||
|
fprintf(stderr, "\t-d[num] Work with default valuesfor 128 Byte "
|
||||||
|
"EEPROM or for 256 Byte EEPROM if some [num] is given\n");
|
||||||
|
fprintf(stderr, "\t-w write\n");
|
||||||
|
fprintf(stderr, "\t-e erase\n");
|
||||||
|
fprintf(stderr, "\t-v verbose decoding\n");
|
||||||
|
fprintf(stderr, "\t-p <number> Search for device with PID == number\n");
|
||||||
|
fprintf(stderr, "\t-v <number> Search for device with VID == number\n");
|
||||||
|
fprintf(stderr, "\t-P <string? Search for device with given "
|
||||||
|
"product description\n");
|
||||||
|
fprintf(stderr, "\t-S <string? Search for device with given "
|
||||||
|
"serial number\n");
|
||||||
|
retval = -1;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select first interface
|
||||||
|
ftdi_set_interface(ftdi, INTERFACE_ANY);
|
||||||
|
|
||||||
|
if (!vid && !pid && desc == NULL && serial == NULL)
|
||||||
|
{
|
||||||
|
struct ftdi_device_list *devlist, *curdev;
|
||||||
|
int res;
|
||||||
|
if ((res = ftdi_usb_find_all(ftdi, &devlist, 0, 0)) < 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "No FTDI with default VID/PID found\n");
|
||||||
|
retval = EXIT_FAILURE;
|
||||||
|
goto do_deinit;
|
||||||
|
}
|
||||||
|
if (res > 1)
|
||||||
|
{
|
||||||
|
int i = 1;
|
||||||
|
fprintf(stderr, "%d FTDI devices found: Only Readout on EEPROM done. ",res);
|
||||||
|
fprintf(stderr, "Use VID/PID/desc/serial to select device\n");
|
||||||
|
for (curdev = devlist; curdev != NULL; curdev= curdev->next, i++)
|
||||||
|
{
|
||||||
|
f = ftdi_usb_open_dev(ftdi, curdev->dev);
|
||||||
|
if (f<0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Unable to open device %d: (%s)",
|
||||||
|
i, ftdi_get_error_string(ftdi));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
fprintf(stderr, "Decoded values of device %d:\n", i);
|
||||||
|
read_decode_eeprom(ftdi);
|
||||||
|
ftdi_usb_close(ftdi);
|
||||||
|
}
|
||||||
|
ftdi_list_free(&devlist);
|
||||||
|
retval = EXIT_SUCCESS;
|
||||||
|
goto do_deinit;
|
||||||
|
}
|
||||||
|
else if (res == 1)
|
||||||
|
{
|
||||||
|
f = ftdi_usb_open_dev(ftdi, devlist[0].dev);
|
||||||
|
if (f<0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Unable to open device %d: (%s)",
|
||||||
|
i, ftdi_get_error_string(ftdi));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "No devices found\n");
|
||||||
|
f = 0;
|
||||||
|
}
|
||||||
|
ftdi_list_free(&devlist);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Open device
|
||||||
|
f = ftdi_usb_open_desc(ftdi, vid, pid, desc, serial);
|
||||||
|
if (f < 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Device VID 0x%04x PID 0x%04x", vid, pid);
|
||||||
|
if (desc)
|
||||||
|
fprintf(stderr, " Desc %s", desc);
|
||||||
|
if (serial)
|
||||||
|
fprintf(stderr, " Serial %s", serial);
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
fprintf(stderr, "unable to open ftdi device: %d (%s)\n",
|
||||||
|
f, ftdi_get_error_string(ftdi));
|
||||||
|
|
||||||
|
retval = -1;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (erase)
|
||||||
|
{
|
||||||
|
f = ftdi_erase_eeprom(ftdi); /* needed to determine EEPROM chip type */
|
||||||
|
if (f < 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Erase failed: %s",
|
||||||
|
ftdi_get_error_string(ftdi));
|
||||||
|
retval = -2;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (ftdi_get_eeprom_value(ftdi, CHIP_TYPE, & value) <0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ftdi_get_eeprom_value: %d (%s)\n",
|
||||||
|
f, ftdi_get_error_string(ftdi));
|
||||||
|
}
|
||||||
|
if (value == -1)
|
||||||
|
fprintf(stderr, "No EEPROM\n");
|
||||||
|
else if (value == 0)
|
||||||
|
fprintf(stderr, "Internal EEPROM\n");
|
||||||
|
else
|
||||||
|
fprintf(stderr, "Found 93x%02x\n", value);
|
||||||
|
retval = 0;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (use_defaults)
|
||||||
|
{
|
||||||
|
ftdi_eeprom_initdefaults(ftdi, NULL, NULL, NULL);
|
||||||
|
if (ftdi_set_eeprom_value(ftdi, MAX_POWER, 500) <0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ftdi_set_eeprom_value: %d (%s)\n",
|
||||||
|
f, ftdi_get_error_string(ftdi));
|
||||||
|
}
|
||||||
|
if (large_chip)
|
||||||
|
if (ftdi_set_eeprom_value(ftdi, CHIP_TYPE, 0x66) <0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ftdi_set_eeprom_value: %d (%s)\n",
|
||||||
|
f, ftdi_get_error_string(ftdi));
|
||||||
|
}
|
||||||
|
f=(ftdi_eeprom_build(ftdi));
|
||||||
|
if (f < 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ftdi_eeprom_build: %d (%s)\n",
|
||||||
|
f, ftdi_get_error_string(ftdi));
|
||||||
|
retval = -1;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (do_write)
|
||||||
|
{
|
||||||
|
ftdi_eeprom_initdefaults(ftdi, NULL, NULL, NULL);
|
||||||
|
f = ftdi_erase_eeprom(ftdi);
|
||||||
|
if (ftdi_set_eeprom_value(ftdi, MAX_POWER, 500) <0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ftdi_set_eeprom_value: %d (%s)\n",
|
||||||
|
f, ftdi_get_error_string(ftdi));
|
||||||
|
}
|
||||||
|
f = ftdi_erase_eeprom(ftdi);/* needed to determine EEPROM chip type */
|
||||||
|
if (ftdi_get_eeprom_value(ftdi, CHIP_TYPE, & value) <0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ftdi_get_eeprom_value: %d (%s)\n",
|
||||||
|
f, ftdi_get_error_string(ftdi));
|
||||||
|
}
|
||||||
|
if (value == -1)
|
||||||
|
fprintf(stderr, "No EEPROM\n");
|
||||||
|
else if (value == 0)
|
||||||
|
fprintf(stderr, "Internal EEPROM\n");
|
||||||
|
else
|
||||||
|
fprintf(stderr, "Found 93x%02x\n", value);
|
||||||
|
f=(ftdi_eeprom_build(ftdi));
|
||||||
|
if (f < 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Erase failed: %s",
|
||||||
|
ftdi_get_error_string(ftdi));
|
||||||
|
retval = -2;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
f = ftdi_write_eeprom(ftdi);
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ftdi_eeprom_decode: %d (%s)\n",
|
||||||
|
f, ftdi_get_error_string(ftdi));
|
||||||
|
retval = 1;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
retval = read_decode_eeprom(ftdi);
|
||||||
|
done:
|
||||||
|
ftdi_usb_close(ftdi);
|
||||||
|
do_deinit:
|
||||||
|
ftdi_free(ftdi);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
/* find_all.c
|
||||||
|
|
||||||
|
Example for ftdi_usb_find_all()
|
||||||
|
|
||||||
|
This program is distributed under the GPL, version 2
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <ftdi.h>
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
int ret, i;
|
||||||
|
struct ftdi_context *ftdi;
|
||||||
|
struct ftdi_device_list *devlist, *curdev;
|
||||||
|
char manufacturer[128], description[128];
|
||||||
|
int retval = EXIT_SUCCESS;
|
||||||
|
|
||||||
|
if ((ftdi = ftdi_new()) == 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ftdi_new failed\n");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((ret = ftdi_usb_find_all(ftdi, &devlist, 0, 0)) < 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ftdi_usb_find_all failed: %d (%s)\n", ret, ftdi_get_error_string(ftdi));
|
||||||
|
retval = EXIT_FAILURE;
|
||||||
|
goto do_deinit;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Number of FTDI devices found: %d\n", ret);
|
||||||
|
|
||||||
|
i = 0;
|
||||||
|
for (curdev = devlist; curdev != NULL; i++)
|
||||||
|
{
|
||||||
|
printf("Checking device: %d\n", i);
|
||||||
|
if ((ret = ftdi_usb_get_strings(ftdi, curdev->dev, manufacturer, 128, description, 128, NULL, 0)) < 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ftdi_usb_get_strings failed: %d (%s)\n", ret, ftdi_get_error_string(ftdi));
|
||||||
|
retval = EXIT_FAILURE;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
printf("Manufacturer: %s, Description: %s\n\n", manufacturer, description);
|
||||||
|
curdev = curdev->next;
|
||||||
|
}
|
||||||
|
done:
|
||||||
|
ftdi_list_free(&devlist);
|
||||||
|
do_deinit:
|
||||||
|
ftdi_free(ftdi);
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
/* final_all_pp.cpp
|
||||||
|
|
||||||
|
Simple libftdi-cpp usage
|
||||||
|
|
||||||
|
This program is distributed under the GPL, version 2
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ftdi.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
using namespace Ftdi;
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
// Show help
|
||||||
|
if (argc > 1)
|
||||||
|
{
|
||||||
|
if (strcmp(argv[1],"-h") == 0 || strcmp(argv[1],"--help") == 0)
|
||||||
|
{
|
||||||
|
std::cout << "Usage: " << argv[0] << " [-v VENDOR_ID] [-p PRODUCT_ID]" << std::endl;
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse args
|
||||||
|
int vid = 0x0403, pid = 0x6010, tmp = 0;
|
||||||
|
for (int i = 0; i < (argc - 1); i++)
|
||||||
|
{
|
||||||
|
if (strcmp(argv[i], "-v") == 0)
|
||||||
|
if ((tmp = strtol(argv[++i], 0, 16)) >= 0)
|
||||||
|
vid = tmp;
|
||||||
|
|
||||||
|
if (strcmp(argv[i], "-p") == 0)
|
||||||
|
if ((tmp = strtol(argv[++i], 0, 16)) >= 0)
|
||||||
|
pid = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print header
|
||||||
|
std::cout << std::hex << std::showbase
|
||||||
|
<< "Found devices ( VID: " << vid << ", PID: " << pid << " )"
|
||||||
|
<< std::endl
|
||||||
|
<< "------------------------------------------------"
|
||||||
|
<< std::endl << std::dec;
|
||||||
|
|
||||||
|
// Print whole list
|
||||||
|
Context context;
|
||||||
|
List* list = List::find_all(context, vid, pid);
|
||||||
|
for (List::iterator it = list->begin(); it != list->end(); it++)
|
||||||
|
{
|
||||||
|
std::cout << "FTDI (" << &*it << "): "
|
||||||
|
<< it->vendor() << ", "
|
||||||
|
<< it->description() << ", "
|
||||||
|
<< it->serial();
|
||||||
|
|
||||||
|
// Open test
|
||||||
|
if(it->open() == 0)
|
||||||
|
std::cout << " (Open OK)";
|
||||||
|
else
|
||||||
|
std::cout << " (Open FAILED)";
|
||||||
|
|
||||||
|
it->close();
|
||||||
|
|
||||||
|
std::cout << std::endl;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
delete list;
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,179 @@
|
||||||
|
/* serial_test.c
|
||||||
|
|
||||||
|
Read/write data via serial I/O
|
||||||
|
|
||||||
|
This program is distributed under the GPL, version 2
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <ftdi.h>
|
||||||
|
|
||||||
|
static int exitRequested = 0;
|
||||||
|
/*
|
||||||
|
* sigintHandler --
|
||||||
|
*
|
||||||
|
* SIGINT handler, so we can gracefully exit when the user hits ctrl-C.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
sigintHandler(int signum)
|
||||||
|
{
|
||||||
|
exitRequested = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
struct ftdi_context *ftdi;
|
||||||
|
unsigned char buf[1024];
|
||||||
|
int f = 0, i;
|
||||||
|
int vid = 0x403;
|
||||||
|
int pid = 0;
|
||||||
|
int baudrate = 115200;
|
||||||
|
int interface = INTERFACE_ANY;
|
||||||
|
int do_write = 0;
|
||||||
|
unsigned int pattern = 0xffff;
|
||||||
|
int retval = EXIT_FAILURE;
|
||||||
|
|
||||||
|
while ((i = getopt(argc, argv, "i:v:p:b:w::")) != -1)
|
||||||
|
{
|
||||||
|
switch (i)
|
||||||
|
{
|
||||||
|
case 'i': // 0=ANY, 1=A, 2=B, 3=C, 4=D
|
||||||
|
interface = strtoul(optarg, NULL, 0);
|
||||||
|
break;
|
||||||
|
case 'v':
|
||||||
|
vid = strtoul(optarg, NULL, 0);
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
pid = strtoul(optarg, NULL, 0);
|
||||||
|
break;
|
||||||
|
case 'b':
|
||||||
|
baudrate = strtoul(optarg, NULL, 0);
|
||||||
|
break;
|
||||||
|
case 'w':
|
||||||
|
do_write = 1;
|
||||||
|
if (optarg)
|
||||||
|
pattern = strtoul(optarg, NULL, 0);
|
||||||
|
if (pattern > 0xff)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Please provide a 8 bit pattern\n");
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "usage: %s [-i interface] [-v vid] [-p pid] [-b baudrate] [-w [pattern]]\n", *argv);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init
|
||||||
|
if ((ftdi = ftdi_new()) == 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ftdi_new failed\n");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!vid && !pid && (interface == INTERFACE_ANY))
|
||||||
|
{
|
||||||
|
ftdi_set_interface(ftdi, INTERFACE_ANY);
|
||||||
|
struct ftdi_device_list *devlist;
|
||||||
|
int res;
|
||||||
|
if ((res = ftdi_usb_find_all(ftdi, &devlist, 0, 0)) < 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "No FTDI with default VID/PID found\n");
|
||||||
|
goto do_deinit;
|
||||||
|
}
|
||||||
|
if (res == 1)
|
||||||
|
{
|
||||||
|
f = ftdi_usb_open_dev(ftdi, devlist[0].dev);
|
||||||
|
if (f<0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Unable to open device %d: (%s)",
|
||||||
|
i, ftdi_get_error_string(ftdi));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ftdi_list_free(&devlist);
|
||||||
|
if (res > 1)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%d Devices found, please select Device with VID/PID\n", res);
|
||||||
|
/* TODO: List Devices*/
|
||||||
|
goto do_deinit;
|
||||||
|
}
|
||||||
|
if (res == 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "No Devices found with default VID/PID\n");
|
||||||
|
goto do_deinit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Select interface
|
||||||
|
ftdi_set_interface(ftdi, interface);
|
||||||
|
|
||||||
|
// Open device
|
||||||
|
f = ftdi_usb_open(ftdi, vid, pid);
|
||||||
|
}
|
||||||
|
if (f < 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "unable to open ftdi device: %d (%s)\n", f, ftdi_get_error_string(ftdi));
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set baudrate
|
||||||
|
f = ftdi_set_baudrate(ftdi, baudrate);
|
||||||
|
if (f < 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "unable to set baudrate: %d (%s)\n", f, ftdi_get_error_string(ftdi));
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set line parameters
|
||||||
|
*
|
||||||
|
* TODO: Make these parameters settable from the command line
|
||||||
|
*
|
||||||
|
* Parameters are choosen that sending a continous stream of 0x55
|
||||||
|
* should give a square wave
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
f = ftdi_set_line_property(ftdi, 8, STOP_BIT_1, NONE);
|
||||||
|
if (f < 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "unable to set line parameters: %d (%s)\n", f, ftdi_get_error_string(ftdi));
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (do_write)
|
||||||
|
for(i=0; i<1024; i++)
|
||||||
|
buf[i] = pattern;
|
||||||
|
|
||||||
|
signal(SIGINT, sigintHandler);
|
||||||
|
while (!exitRequested)
|
||||||
|
{
|
||||||
|
if (do_write)
|
||||||
|
f = ftdi_write_data(ftdi, buf,
|
||||||
|
(baudrate/512 >sizeof(buf))?sizeof(buf):
|
||||||
|
(baudrate/512)?baudrate/512:1);
|
||||||
|
else
|
||||||
|
f = ftdi_read_data(ftdi, buf, sizeof(buf));
|
||||||
|
if (f<0)
|
||||||
|
usleep(1 * 1000000);
|
||||||
|
else if(f> 0 && !do_write)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "read %d bytes\n", f);
|
||||||
|
fwrite(buf, f, 1, stdout);
|
||||||
|
fflush(stderr);
|
||||||
|
fflush(stdout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
signal(SIGINT, SIG_DFL);
|
||||||
|
retval = EXIT_SUCCESS;
|
||||||
|
|
||||||
|
ftdi_usb_close(ftdi);
|
||||||
|
do_deinit:
|
||||||
|
ftdi_free(ftdi);
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue