ChimeraTK-DeviceAccess 03.29.00
Loading...
Searching...
No Matches
SharedDummyBackend.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
5
6#include "BackendFactory.h"
7#include "Exception.h"
8#include "MapFileParser.h"
9#include "ProcessManagement.h"
10#include "Utilities.h"
11
12#include <boost/lambda/lambda.hpp>
13
14#include <algorithm>
15#include <cstring>
16#include <regex>
17#include <sstream>
18
19namespace ChimeraTK {
20
22 size_t instanceIdHash, const std::string& mapFileName, const std::string& dataConsistencyKeyDescriptor)
23 : DummyBackendBase(mapFileName, dataConsistencyKeyDescriptor), _mapFile(_resolvedMapFileName),
24 _barSizesInBytes(getBarSizesInBytesFromRegisterMapping()) {
25 retry:
26 try {
27 sharedMemoryManager = std::make_unique<SharedMemoryManager>(*this, instanceIdHash, _resolvedMapFileName);
28 }
29 catch(boost::interprocess::lock_exception&) {
30 std::cerr << "SharedDummyBackend: boost::interprocess error, clearing shared memory segment." << std::endl;
31 // remove shared memory and mutex
32 std::string name = Utilities::createShmName(instanceIdHash, _resolvedMapFileName, getUserName());
33 boost::interprocess::shared_memory_object::remove(name.c_str());
34 boost::interprocess::named_mutex::remove(name.c_str());
35 goto retry;
36 }
37
38 setupBarContents();
39 }
40
42 // Destroy the InterruptDispatcherInterface first because it keeps a reference to this backend.
43 sharedMemoryManager->intDispatcherIf.reset();
44 // all other objects clean up for themselves when they go out of scope.
45 }
46
47 // Construct a segment for each bar and set required size
48 void SharedDummyBackend::setupBarContents() {
49 for(auto& _barSizesInByte : _barSizesInBytes) {
50 std::string barName = SHARED_MEMORY_BAR_PREFIX + std::to_string(_barSizesInByte.first);
51
52 size_t barSizeInWords = (_barSizesInByte.second + sizeof(int32_t) - 1) / sizeof(int32_t);
53
54 try {
55 std::lock_guard<boost::interprocess::named_mutex> lock(sharedMemoryManager->interprocessMutex);
56 _barContents[_barSizesInByte.first] = sharedMemoryManager->findOrConstructVector(barName, barSizeInWords);
57 }
58 catch(boost::interprocess::bad_alloc&) {
59 // Clean up
60 sharedMemoryManager.reset();
61
62 std::string errMsg{"Could not allocate shared memory while constructing registers. "
63 "Please file a bug report at "
64 "https://github.com/ChimeraTK/DeviceAccess."};
65 throw ChimeraTK::logic_error(errMsg);
66 }
67 } /* for(barSizesInBytesIter) */
68 }
69
73
75 _opened = false;
76 }
77
78 void SharedDummyBackend::read(uint64_t bar, uint64_t address, int32_t* data, size_t sizeInBytes) {
79 if(!_opened) {
80 throw ChimeraTK::logic_error("Device is closed.");
81 }
83 checkSizeIsMultipleOfWordSize(sizeInBytes);
84 uint64_t wordBaseIndex = address / sizeof(int32_t);
85
86 std::lock_guard<boost::interprocess::named_mutex> lock(sharedMemoryManager->interprocessMutex);
87 for(uint64_t wordIndex = 0; wordIndex < sizeInBytes / sizeof(int32_t); ++wordIndex) {
88 TRY_REGISTER_ACCESS(data[wordIndex] = _barContents[bar]->at(wordBaseIndex + wordIndex););
89 }
90 }
91
92 void SharedDummyBackend::write(uint64_t bar, uint64_t address, int32_t const* data, size_t sizeInBytes) {
93 if(!_opened) {
94 throw ChimeraTK::logic_error("Device is closed.");
95 }
97 checkSizeIsMultipleOfWordSize(sizeInBytes);
98 uint64_t wordBaseIndex = address / sizeof(int32_t);
99
100 std::lock_guard<boost::interprocess::named_mutex> lock(sharedMemoryManager->interprocessMutex);
101
102 for(uint64_t wordIndex = 0; wordIndex < sizeInBytes / sizeof(int32_t); ++wordIndex) {
103 TRY_REGISTER_ACCESS(_barContents[bar]->at(wordBaseIndex + wordIndex) = data[wordIndex];);
104 }
105 }
106
108 std::stringstream info;
109 info << "SharedDummyBackend"; // TODO add map file name again
110 return info.str();
111 }
112
113 size_t SharedDummyBackend::getTotalRegisterSizeInBytes() const {
114 size_t totalRegSize = 0;
115 for(const auto& pair : _barSizesInBytes) {
116 totalRegSize += pair.second;
117 }
118 return totalRegSize;
119 }
120
121 void SharedDummyBackend::checkSizeIsMultipleOfWordSize(size_t sizeInBytes) {
122 if(sizeInBytes % sizeof(int32_t)) {
123 throw ChimeraTK::logic_error("Read/write size has to be a multiple of 4");
124 }
125 }
126
127 boost::shared_ptr<DeviceBackend> SharedDummyBackend::createInstance(
128 std::string address, std::map<std::string, std::string> parameters) {
129 // create instanceId from address and parameters.
130 // note, this approach is not perfect: in case two different device URIs are created by reordering their parameters
131 // and URIs are used from same process, the BackendFactory will create two SharedDummyBackend instances (since it
132 // uses the URI string as a key). The order of parameters is lost in the parameters map, so we will here assume the
133 // same instanceIdHash. That will lead further down to some error, since we will try to access the same shared
134 // memory segment twice in the same process.
135 size_t instanceIdHash = Utilities::shmDummyInstanceIdHash(address, parameters);
136 std::string mapFileName = parameters["map"];
137 if(mapFileName.empty()) {
138 throw ChimeraTK::logic_error("No map file name given.");
139 }
140
141 // the relative map file path is resolved (relative to the DMAP directory, then to the cwd) in the
142 // NumericAddressedBackend base class
143 return returnInstance<SharedDummyBackend>(address, instanceIdHash, mapFileName, parameters["DataConsistencyKeys"]);
144 }
145
147 this->sharedMemoryManager->intDispatcherIf->triggerInterrupt(interruptNumber);
148
149 // Since VersionNumber consistency is defined only per process, we generate a new one here
150 // and also in the triggered process
151 return {};
152 }
153
154 SharedDummyBackend::InterruptDispatcherInterface::InterruptDispatcherInterface(SharedDummyBackend& backend,
155 boost::interprocess::managed_shared_memory& shm, boost::interprocess::named_mutex& shmMutex)
156 : _shmMutex(shmMutex), _backend(backend) {
157 // locking not needed, already defined as atomic
158 _semBuf = shm.find_or_construct<ShmForSems>(boost::interprocess::unique_instance)();
159 _semId = getOwnPID();
160
161 _dispatcherThread = boost::movelib::unique_ptr<InterruptDispatcherThread>(new InterruptDispatcherThread(this));
162 }
163
164 SharedDummyBackend::InterruptDispatcherInterface::~InterruptDispatcherInterface() {
165 // stop thread and remove semaphore on destruction
166 _dispatcherThread.reset(); // stops and deletes thread which uses semaphore
167 try {
168 // The scope of the try-block is the scope of the lock_guard, which can throw when locking.
169 // All the lines in the try-block have to be executed under the lock, although not everything
170 // might be throwing.
171
172 std::lock_guard<boost::interprocess::named_mutex> lock(_shmMutex);
173 _semBuf->removeSem(_semId);
174 }
175 catch(boost::interprocess::interprocess_exception&) {
176 // interprocess_exception is only thrown if something seriously went wrong.
177 // In this case we don't want anyone to catch it but terminate.
178 std::terminate();
179 }
180 }
181
182 void SharedDummyBackend::InterruptDispatcherInterface::cleanupShm(boost::interprocess::managed_shared_memory& shm) {
183 shm.destroy<SharedMemoryVector>(boost::interprocess::unique_instance);
184 }
185 void SharedDummyBackend::InterruptDispatcherInterface::cleanupShm(
186 boost::interprocess::managed_shared_memory& shm, PidSet* pidSet) {
187 ShmForSems* semBuf = shm.find_or_construct<ShmForSems>(boost::interprocess::unique_instance)();
188 semBuf->cleanup(pidSet);
189 }
190
191 void SharedDummyBackend::InterruptDispatcherInterface::triggerInterrupt(uint32_t intNumber) {
192 std::list<boost::interprocess::interprocess_semaphore*> semList;
193 {
194 std::lock_guard<boost::interprocess::named_mutex> lock(_shmMutex);
195 // find list of processes and their semaphores
196 // update interrupt info.
197 semList = _semBuf->findSems(intNumber, true);
198 }
199 // trigger the interrupts
200 for(auto* sem : semList) {
201#ifdef _DEBUG
202 std::cout << " InterruptDispatcherInterface::triggerInterrupt: post sem for interrupt: " << intNumber
203 << std::endl;
204 _semBuf->print();
205#endif
206 sem->post();
207 }
208 }
209
210 SharedDummyBackend::InterruptDispatcherThread::InterruptDispatcherThread(
211 InterruptDispatcherInterface* dispatcherInterf)
212 : _dispatcherInterf(dispatcherInterf), _semId(dispatcherInterf->_semId), _semShm(dispatcherInterf->_semBuf) {
213 _thr = std::thread(&InterruptDispatcherThread::run, this);
214 }
215
216 SharedDummyBackend::InterruptDispatcherThread::~InterruptDispatcherThread() {
217 stop();
218 try {
219 _thr.join();
220 }
221
222 catch(boost::system::system_error&) {
223 std::terminate();
224 }
225 }
226
227 void SharedDummyBackend::InterruptDispatcherThread::run() {
228 // copy interrupt counts at the beginning, and then
229 // only look for different values. count up all values till they match
230 // map (controller,intNumber) -> count
231 // use map instead of vector because search is more efficient
232 std::map<std::pair<int, int>, std::uint32_t> lastInterruptState;
233 {
234 std::lock_guard<boost::interprocess::named_mutex> lock(_dispatcherInterf->_shmMutex);
235 for(auto& entry : _semShm->interruptEntries) {
236 assert(entry._controllerId == 0);
237 if(!entry.used) continue;
238 lastInterruptState[std::make_pair(entry._controllerId, entry._intNumber)] = entry._counter;
239 }
240 // we register a semaphore only after being ready
241 _sem = _semShm->addSem(_semId);
242 _started = true;
243 }
244
245 // local copy of shm contents, used to reduce lock time
246 InterruptEntry interruptEntries[maxInterruptEntries];
247
248 while(!_stop) {
249 _sem->wait();
250 {
251 std::lock_guard<boost::interprocess::named_mutex> lock(_dispatcherInterf->_shmMutex);
252 std::memcpy(interruptEntries, _semShm->interruptEntries, sizeof(interruptEntries));
253 }
254 for(auto& entry : interruptEntries) {
255 assert(entry._controllerId == 0);
256 if(!entry.used) continue;
257
258 // find match with controllerId and intNumber
259 auto key = std::make_pair(entry._controllerId, entry._intNumber);
260 auto it = lastInterruptState.find(key);
261 if(it != lastInterruptState.end()) {
262 while(it->second != entry._counter) {
263 // call trigger/dispatch
264#ifdef _DEBUG
265 std::cout << "existing interrupt event for x,y = " << entry._controllerId << ", " << entry._intNumber
266 << std::endl;
267#endif
268 handleInterrupt(entry._intNumber);
269 it->second++;
270 }
271 }
272 else {
273 // new interrupt number
274 // call trigger/dispatch count times
275#ifdef _DEBUG
276 std::cout << "count = " << entry._counter << " interrupt events for x,y = " << entry._controllerId << ", "
277 << entry._intNumber << std::endl;
278#endif
279 handleInterrupt(entry._intNumber);
280 lastInterruptState[key] = entry._counter;
281 }
282 }
283 }
284 }
285
286 void SharedDummyBackend::InterruptDispatcherThread::stop() noexcept {
287 _stop = true;
288 // we must wait until the semaphore is registered
289 try {
290 while(!_started) {
291 boost::this_thread::sleep_for(boost::chrono::milliseconds{10});
292 }
293 }
294 catch(const boost::thread_interrupted&) {
295 // Simply suppress the thread_interrupted here. This function is only called
296 // within a destructor, which anyway would have terminated the program when it sees the exception.
297 // There are two possible scenarios what can happen now.
298 // 1. _started is set and the destruction can continue normally.
299 // 2. _started is not set yet an we don't know if the semaphore is in the correct state.
300 // Again there are two possibilities:
301 // 2a_ The semaphore was set correctly and the destructor continues normally.
302 // 2b_ sem->post() throws and terminate() is called (which otherwise would have been called from the escaping
303 // thread_interrupted
304 }
305 catch(const boost::system::system_error&) {
306 // if something went really wrong we terminate here
307 std::terminate();
308 }
309
310 try {
311 _sem->post();
312 }
313 catch(const boost::interprocess::interprocess_exception&) {
314 std::terminate();
315 }
316 }
317
318 void SharedDummyBackend::InterruptDispatcherThread::handleInterrupt(uint32_t interruptNumber) {
319 SharedDummyBackend& backend = _dispatcherInterf->_backend;
320 auto asyncDomain = boost::dynamic_pointer_cast<async::DomainImpl<std::nullptr_t>>(
321 backend._asyncDomainsContainer.getDomain(interruptNumber));
322
323 if(!asyncDomain) {
324 // If the asyncDomain is not there, the pointer in the _asyncDomainsContainer must be nullptr as well.
325 // Otherwise the dynamic cast failed, which should never happen.
326 assert(!backend._asyncDomainsContainer.getDomain(interruptNumber));
327 return;
328 }
329
330 asyncDomain->distribute(nullptr);
331 }
332
333 SharedDummyBackend::ShmForSems::Sem* SharedDummyBackend::ShmForSems::addSem(SemId semId) {
334 // look up whether semaphore for id already exists and return error
335 for(auto& entry : semEntries) {
336 if(entry.used && entry.semId == semId) {
337 throw logic_error("error: semId already exists - check assumption about identifiers!");
338 }
339 }
340
341 for(auto& entry : semEntries) {
342 if(!entry.used) {
343 entry.semId = semId;
344 entry.used = true;
345 // It would be nice to also reset semaphores state, but if
346 // interrupt dispatcher thread which last used it terminated property its not necessary
347 // (since it calls post in destructor)
348 return &entry.s;
349 }
350 }
351 // increasing size not implemented
352 throw runtime_error("error: semaphore array full - increase maxSems!");
353 }
354
355 bool SharedDummyBackend::ShmForSems::removeSem(SemId semId) {
356 bool found = false;
357 for(auto& entry : semEntries) {
358 if(entry.used && entry.semId == semId) {
359 entry.used = false;
360 found = true;
361 break;
362 }
363 }
364 return found;
365 }
366 void SharedDummyBackend::ShmForSems::cleanup(PidSet* pidSet) {
367 for(auto& entry : semEntries) {
368 if(entry.used) {
369 if(std::find(std::begin(*pidSet), std::end(*pidSet), (int32_t)entry.semId) == std::end(*pidSet)) {
370 entry.used = false;
371 }
372 }
373 }
374 }
375
376 void SharedDummyBackend::ShmForSems::addInterrupt(uint32_t interruptNumber) {
377 bool found = false;
378 for(auto& entry : interruptEntries) {
379 if(entry.used && entry._controllerId == 0 && static_cast<uint32_t>(entry._intNumber) == interruptNumber) {
380 entry._counter++;
381 found = true;
382 break;
383 }
384 }
385 if(!found) {
386 bool added = false;
387 for(auto& entry : interruptEntries) {
388 if(!entry.used) {
389 entry.used = true;
390 entry._controllerId = 0;
391 entry._intNumber = static_cast<int>(interruptNumber);
392 entry._counter = 1;
393 added = true;
394 break;
395 }
396 }
397 if(!added) {
398 throw runtime_error("no place left in interruptEntries!");
399 }
400 }
401 }
402
403 std::list<SharedDummyBackend::ShmForSems::Sem*> SharedDummyBackend::ShmForSems::findSems(
404 uint32_t interruptNumber, bool update) {
405 std::list<Sem*> ret;
406 for(auto& entry : semEntries) {
407 if(entry.used) {
408 // we simply return all semaphores
409 ret.push_back(&entry.s);
410 }
411 }
412 if(update) addInterrupt(interruptNumber);
413 return ret;
414 }
415
416 void SharedDummyBackend::ShmForSems::print() {
417 std::cout << "shmem contents: " << std::endl;
418 for(auto& entry : semEntries) {
419 if(entry.used) std::cout << "sem : " << entry.semId << std::endl;
420 }
421 for(auto& entry : interruptEntries) {
422 if(entry.used) {
423 std::cout << "interrupt : " << entry._controllerId << "," << entry._intNumber << " count = " << entry._counter
424 << std::endl;
425 }
426 }
427
428 std::cout << std::endl;
429 }
430
431} // Namespace ChimeraTK
#define TRY_REGISTER_ACCESS(COMMAND)
std::string getUserName()
unsigned getOwnPID()
boost::interprocess::vector< int32_t, ShmemAllocator > PidSet
boost::interprocess::vector< int32_t, ShmemAllocator > SharedMemoryVector
void setOpenedAndClearException() noexcept
Backends should call this function at the end of a (successful) open() call.
void checkActiveException() final
Function to be called by backends when needing to check for an active exception.
std::atomic< bool > _opened
flag if backend is opened
Base class for DummyBackends, provides common functionality.
std::string _resolvedMapFileName
The resolved absolute path of the map file used for parsing and for any derived naming (e....
The shared dummy device opens a mapping file defining the registers and implements them in shared mem...
void read(uint64_t bar, uint64_t address, int32_t *data, size_t sizeInBytes) override
Read function to be implemented by backends.
SharedDummyBackend(size_t instanceIdHash, const std::string &mapFileName, const std::string &dataConsistencyKeyDescriptor="")
void open() override
Open the device.
std::string readDeviceInfo() override
Return a device information string containing hardware details like the firmware version number or th...
void closeImpl() override
All backends derrived from NumericAddressedBackend must implement closeImpl() instead of close.
VersionNumber triggerInterrupt(uint32_t interruptNumber) override
Simulate the arrival of an interrupt.
static boost::shared_ptr< DeviceBackend > createInstance(std::string address, std::map< std::string, std::string > parameters)
void write(uint64_t bar, uint64_t address, int32_t const *data, size_t sizeInBytes) override
Write function to be implemented by backends.
Class for generating and holding version numbers without exposing a numeric representation.
Exception thrown when a logic error has occured.
Definition Exception.h:51
std::size_t shmDummyInstanceIdHash(const std::string &address, const std::map< std::string, std::string > &parameters)
Generates shm dummy instanceId hash from address and parameter map, Intended for use with parseDevice...
std::string createShmName(std::size_t instanceIdHash, const std::string &mapFileName, const std::string &userName)
Generates shm dummy name from parameter hashes.
std::string to_string(const std::string &v)