ChimeraTK-DeviceAccess  03.18.00
BackendFactory.cc
Go to the documentation of this file.
1 // SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
2 // SPDX-License-Identifier: LGPL-3.0-or-later
3 
4 #include "BackendFactory.h"
5 
6 #include "RebotBackend.h"
7 #include "Utilities.h"
8 
9 #include <boost/algorithm/string.hpp>
10 #ifdef CHIMERATK_HAVE_PCIE_BACKEND
11 # include "PcieBackend.h"
12 #endif
13 #ifdef CHIMERATK_HAVE_XDMA_BACKEND
14 # include "XdmaBackend.h"
15 #endif
16 #ifdef CHIMERATK_HAVE_UIO_BACKEND
17 # include "UioBackend.h"
18 #endif
19 // Clang tidy reports a false positive. It seems to be case-sensitive although it should not be.
20 // clang-format is fixing this correctly.
21 // NOLINTNEXTLINE(llvm-include-order)
22 #include "DeviceAccessVersion.h"
23 #include "DMapFileParser.h"
24 #include "DummyBackend.h"
25 #include "Exception.h"
26 #include "ExceptionDummyBackend.h"
28 #include "SharedDummyBackend.h"
29 #include "SubdeviceBackend.h"
30 
31 #include <boost/bind/bind.hpp>
32 #include <boost/function.hpp>
33 
34 #include <dlfcn.h>
35 #include <utility>
36 
37 #ifdef _DEBUG
38 # include <iostream>
39 #endif
40 
41 namespace ChimeraTK {
42 
43  /********************************************************************************************************************/
44 
45  void BackendFactory::registerBackendType(const std::string& backendType,
46  boost::shared_ptr<DeviceBackend> (*creatorFunction)(
47  std::string address, std::map<std::string, std::string> parameters),
48  const std::vector<std::string>& sdmParameterNames, const std::string& version) {
49 #ifdef _DEBUG
50  std::cout << "adding:" << backendType << std::endl << std::flush;
51 #endif
53 
54  if(creatorMap.find(backendType) != creatorMap.end()) {
55  throw ChimeraTK::logic_error("A backend with the type name '" + backendType + "' has already been registered.");
56  }
57 
58  if(version != CHIMERATK_DEVICEACCESS_VERSION) {
59  // Register a function that throws an exception with the message when trying
60  // to create a backend. We do not throw here because this would throw an
61  // uncatchable exception inside the static instance of a backend registerer,
62  // which would violate the policy that a dmap file with broken backends
63  // should be usable if the particular backend is not used.
64  std::stringstream errorMessage;
65  errorMessage << "Backend plugin '" << backendType << "' compiled with wrong DeviceAccess version " << version
66  << ". Please recompile with version " << CHIMERATK_DEVICEACCESS_VERSION;
67  std::string errorMessageString = errorMessage.str();
68  // FIXME #11279 Implement API breaking changes from linter warnings
69  // NOLINTBEGIN(performance-unnecessary-value-param)
70  creatorMap_compat[make_pair(backendType, "")] = [errorMessageString](std::string host, std::string instance,
71  std::list<std::string> parameters, std::string mapFileName) {
73  host, instance, parameters, mapFileName, errorMessageString);
74  };
75  creatorMap[backendType] = [errorMessageString](std::string,
76  std::map<std::string, std::string>) -> boost::shared_ptr<ChimeraTK::DeviceBackend> {
77  // NOLINTEND(performance-unnecessary-value-param)
78  throw ChimeraTK::logic_error(errorMessageString);
79  };
80  return;
81  }
82  creatorMap[backendType] = creatorFunction;
83  // FIXME #11279 Implement API breaking changes from linter warnings
84  // NOLINTBEGIN(performance-unnecessary-value-param)
85  creatorMap_compat[make_pair(backendType, "")] = [creatorFunction, sdmParameterNames](std::string,
86  std::string instance, std::list<std::string> parameters,
87  std::string mapFileName) {
88  // NOLINTEND(performance-unnecessary-value-param)
89  std::map<std::string, std::string> pars;
90  size_t i = 0;
91  for(auto& p : parameters) {
92  if(i >= sdmParameterNames.size()) break;
93  pars[sdmParameterNames[i]] = p;
94  ++i;
95  }
96  if(!mapFileName.empty()) {
97  if(pars["map"].empty()) {
98  pars["map"] = mapFileName;
99  }
100  else {
101  std::cout << "WARNING: You have specified the map file name twice, in "
102  "the parameter list and in the 3rd "
103  "column of the DMAP file."
104  << std::endl;
105  std::cout << "Please only specify the map file name in the parameter list!" << std::endl;
106  }
107  }
108  return creatorFunction(std::move(instance), pars);
109  };
110  }
111 
112  /********************************************************************************************************************/
113 
114  void BackendFactory::registerBackendType(const std::string& interface, const std::string& protocol,
115  boost::shared_ptr<DeviceBackend> (*creatorFunction)(
116  std::string host, std::string instance, std::list<std::string> parameters, std::string mapFileName),
117  const std::string& version) {
118 #ifdef _DEBUG
119  std::cout << "adding:" << interface << std::endl << std::flush;
120 #endif
122  if(version != CHIMERATK_DEVICEACCESS_VERSION) {
123  // Register a function that throws an exception with the message when trying
124  // to create a backend. We do not throw here because this would throw an
125  // uncatchable exception inside the static instance of a backend registerer,
126  // which would violate the policy that a dmap file with broken backends
127  // should be usable if the particular backend is not used.
128  std::stringstream errorMessage;
129  errorMessage << "Backend plugin '" << interface << "' compiled with wrong DeviceAccess version " << version
130  << ". Please recompile with version " << CHIMERATK_DEVICEACCESS_VERSION;
131  std::string errorMessageString = errorMessage.str();
132  // FIXME #11279 Implement API breaking changes from linter warnings
133  // NOLINTBEGIN(performance-unnecessary-value-param)
134  creatorMap_compat[make_pair(interface, protocol)] = [errorMessageString](std::string host, std::string instance,
135  std::list<std::string> parameters,
136  std::string mapFileName) {
138  host, instance, parameters, mapFileName, errorMessageString);
139  };
140  creatorMap[interface] = [errorMessageString](std::string,
141  std::map<std::string, std::string>) -> boost::shared_ptr<ChimeraTK::DeviceBackend> {
142  // NOLINTEND(performance-unnecessary-value-param)
143  throw ChimeraTK::logic_error(errorMessageString);
144  };
145  return;
146  }
147  creatorMap_compat[make_pair(interface, protocol)] = creatorFunction;
148  // FIXME #11279 Implement API breaking changes from linter warnings
149  // NOLINTBEGIN(performance-unnecessary-value-param)
150  creatorMap[interface] = [interface](std::string,
151  std::map<std::string, std::string>) -> boost::shared_ptr<ChimeraTK::DeviceBackend> {
152  // NOLINTEND(performance-unnecessary-value-param)
153  throw ChimeraTK::logic_error("The backend type '" + interface +
154  "' does not yet support ChimeraTK device "
155  "descriptors! Please update the backend!");
156  };
157  }
158 
159  /********************************************************************************************************************/
160 
161  void BackendFactory::setDMapFilePath(std::string dMapFilePath) {
162  _dMapFile = std::move(dMapFilePath);
164  }
165  /********************************************************************************************************************/
166 
168  return _dMapFile;
169  }
170  /********************************************************************************************************************/
171 
172  BackendFactory::BackendFactory() {
173 #ifdef CHIMERATK_HAVE_PCIE_BACKEND
175 #endif
176 #ifdef CHIMERATK_HAVE_XDMA_BACKEND
178 #endif
179 #ifdef CHIMERATK_HAVE_UIO_BACKEND
181 #endif
183  registerBackendType("rebot", &RebotBackend::createInstance, {"ip", "port", "map", "timeout"});
186  registerBackendType("sharedMemoryDummy", &SharedDummyBackend::createInstance, {"map"});
187  registerBackendType("ExceptionDummy", &ExceptionDummy::createInstance, {"map"});
188  }
189  /********************************************************************************************************************/
190 
192 #ifdef _DEBUG
193  std::cout << "getInstance" << std::endl << std::flush;
194 #endif
195  static BackendFactory factoryInstance;
196  return factoryInstance;
197  }
198 
199  /********************************************************************************************************************/
200 
201  boost::shared_ptr<DeviceBackend> BackendFactory::createBackend(const std::string& aliasOrUri) {
202  std::lock_guard<std::mutex> lock(_mutex);
203 
204  if(Utilities::isDeviceDescriptor(aliasOrUri) || Utilities::isSdm(aliasOrUri)) {
205  // it is a URI, directly create a deviceinfo and call the internal creator
206  // function
207  DeviceInfoMap::DeviceInfo deviceInfo;
208  deviceInfo.uri = aliasOrUri;
209  return createBackendInternal(deviceInfo);
210  }
211 
212  // it's not a URI. Try finding the alias in the dmap file.
213  if(_dMapFile.empty()) {
214  throw ChimeraTK::logic_error("DMap file not set.");
215  }
216  auto deviceInfo = Utilities::aliasLookUp(aliasOrUri, _dMapFile);
217  return createBackendInternal(deviceInfo);
218  }
219 
220  /********************************************************************************************************************/
221 
222  boost::shared_ptr<DeviceBackend> BackendFactory::createBackendInternal(const DeviceInfoMap::DeviceInfo& deviceInfo) {
223 #ifdef _DEBUG
224  std::cout << "uri to parse" << deviceInfo.uri << std::endl;
225  std::cout << "Entries" << creatorMap.size() << std::endl << std::flush;
226 #endif
227 
228  // Check if backend already exists
229  auto iterator = _existingBackends.find(deviceInfo.uri);
230  if(iterator != _existingBackends.end()) {
231  auto strongPtr = iterator->second.lock();
232  if(strongPtr) {
233  return strongPtr;
234  }
235  }
236 
237  // Check if descriptor string is a ChimeraTK device descriptor
238  if(Utilities::isDeviceDescriptor(deviceInfo.uri)) {
239  auto cdd = Utilities::parseDeviceDesciptor(deviceInfo.uri);
240  try {
241  auto backend = (creatorMap.at(cdd.backendType))(cdd.address, cdd.parameters);
242  _existingBackends[deviceInfo.uri] = backend;
243  return backend;
244  }
245  catch(std::out_of_range&) {
246  throw ChimeraTK::logic_error("Unknown backend: \"" + cdd.backendType + "\" at " + deviceInfo.dmapFileName +
247  ":" + std::to_string(deviceInfo.dmapFileLineNumber) + " for " + deviceInfo.uri);
248  }
249  }
250 
251  // Check for deprecated SDM or device node
252  // Note: Deprecation message was added 2022-07-28. Remove functionality past end of 2023.
253  Sdm sdm;
254  if(Utilities::isSdm(deviceInfo.uri)) {
255  sdm = Utilities::parseSdm(deviceInfo.uri);
256  std::cout << "Using the SDM descriptor is deprecated. Please change to CDD (ChimeraTK device descriptor)."
257  << std::endl;
258  }
259  else {
260  sdm = Utilities::parseDeviceString(deviceInfo.uri);
261  std::cout
262  << "Using the device node in a dmap file is deprecated. Please change to CDD (ChimeraTK device descriptor)."
263  << std::endl;
264  }
265  for(auto& iter : creatorMap_compat) {
266  if((iter.first.first == sdm.interface)) {
267  auto backend = (iter.second)(sdm.host, sdm.instance, sdm.parameters, deviceInfo.mapFileName);
268  boost::weak_ptr<DeviceBackend> weakBackend = backend;
269  _existingBackends[deviceInfo.uri] = weakBackend;
270  // return the shared pointer, not the weak pointer
271  return backend;
272  }
273  }
274 
275  throw ChimeraTK::logic_error("Unregistered device: Interface = " + sdm.interface + " Protocol = " + sdm.protocol);
276  return {}; // won't execute
277  }
278 
279  /********************************************************************************************************************/
280 
281  void BackendFactory::loadPluginLibrary(const std::string& soFile) {
282  // reset flag to check if the registerBackedType() function was called
284 
285  // open the plugin library. RTLD_LAZY is enough since all symbols are loaded
286  // when needed
287  void* hndl = dlopen(soFile.c_str(), RTLD_LAZY);
288  if(hndl == nullptr) {
289  throw ChimeraTK::logic_error(dlerror());
290  }
291 
292  // if no backend was registered, close the library and throw an exception
294  dlclose(hndl);
296  "'" + soFile + "' is not a valid DeviceAccess plugin, it does not register any backends!");
297  }
298  }
299 
300  /********************************************************************************************************************/
301 
303  if(_dMapFile.empty()) {
304  return;
305  }
306 
307  auto dmap = DMapFileParser::parse(_dMapFile);
308 
309  for(const auto& lib : dmap->getPluginLibraries()) {
310  try {
311  loadPluginLibrary(lib);
312  }
313  catch(ChimeraTK::logic_error& e) {
314  // Ignore library loading errors when doing this automatically for all
315  // plugins in the dmap file to keep dmap files usable if the broken
316  // backends are not used. Print a warning that the loading failed.
317  std::cerr << "Error: Caught exception loading plugin '" << lib << "' specified in dmap file: " << e.what()
318  << std::endl;
319  std::cerr << "Some backends will not be available!" << std::endl;
320  }
321  }
322  }
323 
324  /********************************************************************************************************************/
325 
326  // FIXME #11279 Implement API breaking changes from linter warnings
327  // NOLINTBEGIN(performance-unnecessary-value-param)
328  boost::shared_ptr<DeviceBackend> BackendFactory::failedRegistrationThrowerFunction(std::string /*host*/,
329  std::string /*instance*/, std::list<std::string> /*parameters*/, std::string /*mapFileName*/,
330  std::string exception_what) {
331  // NOLINTEND(performance-unnecessary-value-param)
332  throw ChimeraTK::logic_error(std::move(exception_what));
333  }
334 } // namespace ChimeraTK
ExceptionDummyBackend.h
ChimeraTK::BackendFactory::createBackend
boost::shared_ptr< DeviceBackend > createBackend(const std::string &aliasOrUri)
Create a new backend and return the instance as a shared pointer.
Definition: BackendFactory.cc:201
ChimeraTK::Sdm::host
std::string host
Definition: Utilities.h:42
ChimeraTK::SubdeviceBackend::createInstance
static boost::shared_ptr< DeviceBackend > createInstance(std::string address, std::map< std::string, std::string > parameters)
Definition: SubdeviceBackend.cc:23
ChimeraTK::DMapFileParser::parse
static DeviceInfoMapPointer parse(const std::string &file_name)
Performs parsing of specified DMAP file.
Definition: DMapFileParser.cpp:18
ChimeraTK::Sdm::interface
std::string interface
Definition: Utilities.h:43
ChimeraTK::Utilities::parseDeviceDesciptor
DeviceDescriptor parseDeviceDesciptor(std::string cddString)
Parse a ChimeraTK device descriptor (CDD) and return the information in the DeviceDescriptor struct.
Definition: Utilities.cpp:47
XdmaBackend.h
ChimeraTK::BackendFactory::loadPluginLibrary
void loadPluginLibrary(const std::string &soFile)
Load a shared library (.so file) with a Backend at run time.
Definition: BackendFactory.cc:281
ChimeraTK::BackendFactory::getInstance
static BackendFactory & getInstance()
Static function to get an instance of factory.
Definition: BackendFactory.cc:191
DummyBackend.h
Utilities.h
ChimeraTK::DeviceInfoMap::DeviceInfo::mapFileName
std::string mapFileName
name of the MAP file storing information about PCIe registers mapping
Definition: DeviceInfoMap.h:35
ChimeraTK::BackendFactory::_mutex
std::mutex _mutex
A mutex to protect backend creation and returning to keep the maps consistent.
Definition: BackendFactory.h:112
ChimeraTK::ExceptionDummy::createInstance
static boost::shared_ptr< DeviceBackend > createInstance(std::string address, std::map< std::string, std::string > parameters)
Definition: ExceptionDummyBackend.cc:98
PcieBackend.h
ChimeraTK::Sdm::instance
std::string instance
Definition: Utilities.h:44
ChimeraTK::Sdm
This structure holds the information of an SDM.
Definition: Utilities.h:40
SharedDummyBackend.h
ChimeraTK::BackendFactory::_existingBackends
std::map< std::string, boost::weak_ptr< DeviceBackend > > _existingBackends
A map of all created backends.
Definition: BackendFactory.h:108
LogicalNameMappingBackend.h
ChimeraTK::BackendFactory::setDMapFilePath
void setDMapFilePath(std::string dMapFilePath)
This function sets the _DMapFilePath.
Definition: BackendFactory.cc:161
ChimeraTK::DeviceInfoMap::DeviceInfo::dmapFileName
std::string dmapFileName
name of the DMAP file
Definition: DeviceInfoMap.h:37
cdd
std::string cdd
Definition: testAsyncRead.cpp:25
ChimeraTK::BackendFactory::getDMapFilePath
std::string getDMapFilePath()
Returns the _DMapFilePath.
Definition: BackendFactory.cc:167
ChimeraTK::DeviceInfoMap::DeviceInfo::dmapFileLineNumber
uint32_t dmapFileLineNumber
line number in DMAP file storing listed above information
Definition: DeviceInfoMap.h:38
ChimeraTK::BackendFactory::createBackendInternal
boost::shared_ptr< DeviceBackend > createBackendInternal(const DeviceInfoMap::DeviceInfo &deviceInfo)
Internal function to return a DeviceBackend.
Definition: BackendFactory.cc:222
ChimeraTK::BackendFactory
BackendFactory is a the factory class to create devices.
Definition: BackendFactory.h:26
ChimeraTK::Utilities::parseDeviceString
Sdm parseDeviceString(const std::string &deviceString)
Parse an old-style device string (either path to device node or map file name for dummies)
Definition: Utilities.cpp:260
ChimeraTK::PcieBackend::createInstance
static boost::shared_ptr< DeviceBackend > createInstance(std::string address, std::map< std::string, std::string > parameters)
Definition: PcieBackend.cc:305
ChimeraTK::Utilities::isSdm
bool isSdm(const std::string &theString)
Check wehter the given string seems to be an SDM.
Definition: Utilities.cpp:28
ChimeraTK::BackendFactory::loadAllPluginsFromDMapFile
void loadAllPluginsFromDMapFile()
Load all shared libraries specified in the dmap file.
Definition: BackendFactory.cc:302
ChimeraTK::UioBackend::createInstance
static boost::shared_ptr< DeviceBackend > createInstance(std::string address, std::map< std::string, std::string > parameters)
Definition: UioBackend.cc:16
ChimeraTK::BackendFactory::called_registerBackendType
bool called_registerBackendType
Flag whether the function registerBackendType() was called.
Definition: BackendFactory.h:127
DMapFileParser.h
ChimeraTK::Utilities::parseSdm
Sdm parseSdm(const std::string &sdmString)
Parse an SDM URI and return the device information in the Sdm struct.
Definition: Utilities.cpp:189
ChimeraTK::BackendFactory::failedRegistrationThrowerFunction
static boost::shared_ptr< DeviceBackend > failedRegistrationThrowerFunction(std::string host, std::string instance, std::list< std::string > parameters, std::string mapFileName, std::string exception_what)
Definition: BackendFactory.cc:328
ChimeraTK::DeviceInfoMap::DeviceInfo
Stores information about one device.
Definition: DeviceInfoMap.h:30
ChimeraTK::logic_error::what
const char * what() const noexcept override
Return the message describing what exactly went wrong.
Definition: Exception.cpp:20
UioBackend.h
ChimeraTK::Sdm::parameters
std::list< std::string > parameters
Definition: Utilities.h:46
ChimeraTK::Utilities::isDeviceDescriptor
bool isDeviceDescriptor(std::string theString)
Check whether the given string seems to be a CDD.
Definition: Utilities.cpp:37
ChimeraTK::XdmaBackend::createInstance
static boost::shared_ptr< DeviceBackend > createInstance(std::string address, std::map< std::string, std::string > parameters)
Definition: XdmaBackend.cc:152
RebotBackend.h
ChimeraTK::BackendFactory::creatorMap
std::map< std::string, boost::function< boost::shared_ptr< DeviceBackend > std::string address, std::map< std::string, std::string > parameters)> > creatorMap
Holds device type and function pointer to the createInstance function of plugin.
Definition: BackendFactory.h:91
BackendFactory.h
ChimeraTK::LogicalNameMappingBackend::createInstance
static boost::shared_ptr< DeviceBackend > createInstance(std::string address, std::map< std::string, std::string > parameters)
Definition: LogicalNameMappingBackend.cc:108
ChimeraTK::Utilities::aliasLookUp
DeviceInfoMap::DeviceInfo aliasLookUp(const std::string &aliasName, const std::string &dmapFilePath)
Search for an alias in a given DMap file and return the DeviceInfo entry.
Definition: Utilities.cpp:286
ChimeraTK::Sdm::protocol
std::string protocol
Definition: Utilities.h:45
ChimeraTK::BackendFactory::_dMapFile
std::string _dMapFile
The dmap file set at run time.
Definition: BackendFactory.h:84
ChimeraTK::DeviceInfoMap::DeviceInfo::uri
std::string uri
uri which describes the device (or name of the device file in /dev in backward compatibility mode)
Definition: DeviceInfoMap.h:33
ChimeraTK::BackendFactory::registerBackendType
void registerBackendType(const std::string &backendType, boost::shared_ptr< DeviceBackend >(*creatorFunction)(std::string address, std::map< std::string, std::string > parameters), const std::vector< std::string > &sdmParameterNames={}, const std::string &deviceAccessVersion=CHIMERATK_DEVICEACCESS_VERSION)
Register a backend by the name backendType with the given creatorFunction.
Definition: BackendFactory.cc:45
Exception.h
ChimeraTK::RebotBackend::createInstance
static boost::shared_ptr< DeviceBackend > createInstance(std::string address, std::map< std::string, std::string > parameters)
Definition: RebotBackend.cc:139
SubdeviceBackend.h
ChimeraTK
Definition: DummyBackend.h:16
ChimeraTK::to_string
std::string to_string(Boolean &value)
Definition: SupportedUserTypes.h:59
ChimeraTK::SharedDummyBackend::createInstance
static boost::shared_ptr< DeviceBackend > createInstance(std::string address, std::map< std::string, std::string > parameters)
Definition: SharedDummyBackend.cc:114
ChimeraTK::DummyBackend::createInstance
static boost::shared_ptr< DeviceBackend > createInstance(std::string address, std::map< std::string, std::string > parameters)
Definition: DummyBackend.cc:159
ChimeraTK::logic_error
Exception thrown when a logic error has occured.
Definition: Exception.h:51
ChimeraTK::BackendFactory::creatorMap_compat
std::map< std::pair< std::string, std::string >, boost::function< boost::shared_ptr< DeviceBackend > std::string host, std::string instance, std::list< std::string > parameters, std::string mapFileName)> > creatorMap_compat
Compatibility creatorMap for old-style backends which just take a plain list of parameters.
Definition: BackendFactory.h:98