ChimeraTK-DeviceAccess 03.27.01
Loading...
Searching...
No Matches
LNMBitRangeAccessPlugin.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 "LNMAccessorPlugin.h"
8#include "RawConverter.h"
9
10#include <boost/make_shared.hpp>
11
12#include <charconv>
13
14namespace ChimeraTK::LNMBackend {
15
23 public:
25
26 explicit ReferenceCountedUniqueLock(std::recursive_mutex& mutex) : _lock(mutex, std::defer_lock) {}
27
28 void lock();
29 void unlock();
30 [[nodiscard]] size_t useCount() const;
31
32 private:
33 thread_local static size_t targetUseCount;
34 std::unique_lock<std::recursive_mutex> _lock;
35 };
36
37 /********************************************************************************************************************/
38
39 thread_local size_t ReferenceCountedUniqueLock::targetUseCount;
40
41 /********************************************************************************************************************/
42
44 assert(_lock.owns_lock());
45 return targetUseCount;
46 }
47
48 /********************************************************************************************************************/
49
51 assert(targetUseCount > 0);
52 targetUseCount--;
53 _lock.unlock();
54 }
55 /********************************************************************************************************************/
56
58 _lock.lock();
59 targetUseCount++;
60 }
61
62 /********************************************************************************************************************/
63 /********************************************************************************************************************/
64 /********************************************************************************************************************/
65
66 // From https://stackoverflow.com/questions/1392059/algorithm-to-generate-bit-mask
67 constexpr uint64_t getMaskForNBits(uint64_t numberOfBits) {
68 // Prevent warning about undefined behavior if shifting right by 64 bit below
69 if(numberOfBits == 0) {
70 return 0;
71 }
72
73 return (static_cast<uint64_t>(-(numberOfBits != 0)) &
74 (static_cast<uint64_t>(-1) >> ((sizeof(uint64_t) * CHAR_BIT) - numberOfBits)));
75 }
76
77 /********************************************************************************************************************/
78
79 template<typename UserType>
82
83 /******************************************************************************************************************/
84 BitRangeAccessPluginDecorator(boost::shared_ptr<LogicalNameMappingBackend>& backend,
85 boost::shared_ptr<DeviceBackend>& targetDevice,
86 const boost::shared_ptr<ChimeraTK::NDRegisterAccessor<uint64_t>>& target, const std::string& name,
87 uint64_t shift, uint64_t numberOfBits, uint64_t dataInterpretationFractionalBits,
88 uint64_t dataInterpretationIsSigned)
89 : ChimeraTK::NDRegisterAccessorDecorator<UserType, uint64_t>(target), _shift(shift), _numberOfBits(numberOfBits),
90 _writeable{_target->isWriteable()} {
91 // Reset the version number. The target accessor may be shared between different decorators (e.g. multiple
92 // bit-range registers targeting the same physical register). In that case the target's version number may have
93 // been set by operations through another decorator, but from the user's perspective this is a fresh accessor.
94 // The test UnifiedBackendTest_B_6 checks this by verifying VersionNumber(nullptr).
95 this->_versionNumber = VersionNumber{nullptr};
96
97 // makeConverterLoopHelper expects a NumericAddressedBackend RegisterInfo, which we create with the relevant
98 // parameters.
99 NumericAddressedRegisterInfo registerInfo{name, 1, 0, sizeof(uint64_t), 0, uint32_t(numberOfBits),
100 int32_t(dataInterpretationFractionalBits), bool(dataInterpretationIsSigned)};
101 _converterLoopHelper =
102 RawConverter::ConverterLoopHelper::makeConverterLoopHelper<UserType>(registerInfo, 0, 0, *this);
103
104 if(_target->getNumberOfChannels() > 1 || _target->getNumberOfSamples() > 1) {
105 throw ChimeraTK::logic_error("LogicalNameMappingBackend BitRangeAccessPluginDecorator: " +
106 TransferElement::getName() + ": Cannot target non-scalar registers.");
107 }
108
109 auto& map = boost::fusion::at_key<uint64_t>(backend->sharedAccessorMap.table);
110
111 RegisterPath path{name};
112 path.setAltSeparator(".");
113 LogicalNameMappingBackend::AccessorKey key{targetDevice.get(), path};
114
115 auto it = map.find(key);
116 if(it != map.end()) {
117 _lock = ReferenceCountedUniqueLock(it->second.mutex);
118 }
119 else {
120 assert(false);
121 }
122
123 _baseBitMask = getMaskForNBits(_numberOfBits);
124 _maskOnTarget = _baseBitMask << _shift;
125 }
126
127 /******************************************************************************************************************/
128
129 void doPreRead(TransferType type) override {
130 _lock.lock();
131
132 _target->preRead(type);
133 }
134
135 /******************************************************************************************************************/
136
137 void doPostRead(TransferType type, bool hasNewData) override {
138 auto unlock = cppext::finally([this] { this->_lock.unlock(); });
139 _target->postRead(type, hasNewData);
140 if(!hasNewData) {
141 return;
142 }
143
144 _converterLoopHelper->doPostRead();
145 }
146
147 /******************************************************************************************************************/
148
149 template<class CookedType, typename RawType, RawConverter::SignificantBitsCase sc, RawConverter::FractionalCase fc,
150 bool isSigned>
152 [[maybe_unused]] size_t implParameter) {
153 static_assert(std::is_same_v<UserType, CookedType>);
154 if constexpr(!std::is_same_v<RawType, ChimeraTK::Void>) {
155 auto validity = _target->dataValidity();
156 uint64_t v{_target->accessData(0)};
157 v = (v & _maskOnTarget) >> _shift;
158
159 buffer_2D[0][0] = converter.toCooked(v);
160 // Do a quick check if the fixed point converter clamped. Then set the
161 // data validity faulty according to B.2.4.1
162 // For proper implementation of this, the fixed point converter needs to signalize
163 // that it had clamped. See https://redmine.msktools.desy.de/issues/12912
164 auto raw = converter.toRaw(buffer_2D[0][0]);
165 if(raw != v) {
166 validity = DataValidity::faulty;
167 }
168
169 this->_versionNumber = std::max(this->_versionNumber, _target->getVersionNumber());
170 this->_dataValidity = validity;
171 }
172 else {
173 assert(false);
174 }
175 }
176
177 /******************************************************************************************************************/
178
179 void doPreWrite(TransferType type, VersionNumber versionNumber) override {
180 _lock.lock();
181
182 if(!_writeable) {
184 "Register \"" + TransferElement::getName() + "\" with BitRange plugin is not writeable.");
185 }
186
187 _converterLoopHelper->doPreWrite();
188
189 _temporaryVersion = std::max(versionNumber, _target->getVersionNumber());
190 _target->preWrite(type, _temporaryVersion);
191 }
192
193 /******************************************************************************************************************/
194
195 template<class CookedType, typename RawType, RawConverter::SignificantBitsCase sc, RawConverter::FractionalCase fc,
196 bool isSigned>
198 [[maybe_unused]] size_t implParameter) {
199 static_assert(std::is_same_v<UserType, CookedType>);
200 if constexpr(!std::is_same_v<RawType, ChimeraTK::Void>) {
201 auto value = converter.toRaw(buffer_2D[0][0]);
202
203 // FIXME: Not setting the data validity according to the spec point B2.5.1.
204 // This needs a change in the fixedpoint converter to tell us that it has clamped the value to reliably work.
205 // To be revisited after fixing https://redmine.msktools.desy.de/issues/12912
206
207 // When in a transfer group, only the first accessor to write to the _target can call read() in its preWrite()
208 // Otherwise it will overwrite the
209 if(_target->isReadable() && (!TransferElement::_isInTransferGroup || _lock.useCount() == 1)) {
210 _target->read();
211 }
212
213 _target->accessData(0) &= ~_maskOnTarget;
214 _target->accessData(0) |= (value << _shift);
215
216 _target->setDataValidity(this->_dataValidity);
217 }
218 else {
219 assert(false);
220 }
221 }
222
223 /******************************************************************************************************************/
224
225 void doPostWrite(TransferType type, VersionNumber /*versionNumber*/) override {
226 auto unlock = cppext::finally([this] { this->_lock.unlock(); });
227 _target->postWrite(type, _temporaryVersion);
228 }
229
230 /******************************************************************************************************************/
231
232 void replaceTransferElement(boost::shared_ptr<ChimeraTK::TransferElement> newElement) override {
233 auto casted = boost::dynamic_pointer_cast<BitRangeAccessPluginDecorator<UserType>>(newElement);
234
235 // In a transfer group, we are trying to replaced with an accessor. Check if this accessor is for the
236 // same target and not us and check for overlapping bit range afterwards. If they overlap, switch us and
237 // the replacement read-only which switches the transfergroup read-only since we cannot guarantee the write
238 // order for overlapping bit ranges
239 if(casted && casted.get() != this && casted->_target == _target) {
240 // anding the two masks will yield 0 iff there is no overlap
241 if((casted->_maskOnTarget & _maskOnTarget) != 0) {
242 casted->_writeable = false;
243 _writeable = false;
244 }
245 }
247 }
248
249 /******************************************************************************************************************/
250
251 private:
252 uint64_t _shift;
253 uint64_t _numberOfBits;
254 uint64_t _maskOnTarget;
255 uint64_t _userTypeMask{getMaskForNBits(sizeof(UserType) * CHAR_BIT)};
256 uint64_t _targetTypeMask{getMaskForNBits(sizeof(uint64_t) * CHAR_BIT)};
257 uint64_t _baseBitMask;
258
259 ReferenceCountedUniqueLock _lock;
260 VersionNumber _temporaryVersion;
261 bool _writeable{false};
262 std::unique_ptr<RawConverter::ConverterLoopHelper> _converterLoopHelper;
263
264 using ChimeraTK::NDRegisterAccessorDecorator<UserType, uint64_t>::_target;
265 };
266
267 /********************************************************************************************************************/
268
270 const LNMBackendRegisterInfo& info, size_t pluginIndex, const std::map<std::string, std::string>& parameters)
271 : AccessorPlugin<BitRangeAccessPlugin>(info, pluginIndex, true) {
272 try {
273 const auto& shift = parameters.at("shift");
274
275 // This is how you are supposed to use std::from_chars with std::string
276 // NOLINTNEXTLINE(unsafe-buffer-usage)
277 auto [suffix, ec]{std::from_chars(shift.data(), shift.data() + shift.size(), _shift)};
278 if(ec != std::errc()) {
279 throw ChimeraTK::logic_error("LogicalNameMappingBackend BitRangeAccessPlugin: " + info.getRegisterName() +
280 R"(: Unparseable parameter "shift".)");
281 }
282 }
283 catch(std::out_of_range&) {
284 throw ChimeraTK::logic_error("LogicalNameMappingBackend BitRangeAccessPlugin: " + info.getRegisterName() +
285 R"(: Missing parameter "shift".)");
286 }
287
288 try {
289 const auto& numberOfBits = parameters.at("numberOfBits");
290 // This is how you are supposed to use std::from_chars with std::string
291 // NOLINTNEXTLINE(unsafe-buffer-usage)
292 auto [suffix, ec]{std::from_chars(numberOfBits.data(), numberOfBits.data() + numberOfBits.size(), _numberOfBits)};
293 if(ec != std::errc()) {
294 throw ChimeraTK::logic_error("LogicalNameMappingBackend BitRangeAccessPlugin: " + info.getRegisterName() +
295 R"(: Unparseable parameter "numberOfBits".)");
296 }
297 }
298 catch(std::out_of_range&) {
299 throw ChimeraTK::logic_error("LogicalNameMappingBackend BitRangeAccessPlugin: " + info.getRegisterName() +
300 R"(: Unparseable parameter "numberOfBits".)");
301 }
302
303 if(const auto it = parameters.find("fractionalBits"); it != parameters.end()) {
304 // This is how you are supposed to use std::from_chars with std::string
305 // NOLINTNEXTLINE(unsafe-buffer-usage)
306 auto [suffix, ec]{
307 std::from_chars(it->second.data(), it->second.data() + it->second.size(), dataInterpretationFractionalBits)};
308 if(ec != std::errc()) {
309 throw ChimeraTK::logic_error("LogicalNameMappingBackend BitRangeAccessPlugin: " + info.getRegisterName() +
310 R"(: Unparseable parameter "fractionalBits".)");
311 }
312 }
313
314 if(const auto it = parameters.find("signed"); it != parameters.end()) {
315 std::stringstream ss(it->second);
316 Boolean value;
317 ss >> value;
319 }
320 }
321
322 /********************************************************************************************************************/
323
325 // We do not support wait_for_new_data with this decorator
328 // also remove raw-type info from DataDescriptor
330 }
331
332 /********************************************************************************************************************/
333
334 template<typename UserType, typename TargetType>
335 boost::shared_ptr<NDRegisterAccessor<UserType>> BitRangeAccessPlugin::decorateAccessor(
336 boost::shared_ptr<LogicalNameMappingBackend>& backend, boost::shared_ptr<NDRegisterAccessor<TargetType>>& target,
337 const UndecoratedParams& params) {
338 if constexpr(std::is_same_v<TargetType, uint64_t>) {
339 if(params._wordOffsetInRegister != 0) {
340 throw ChimeraTK::logic_error(std::format("BitRangePlugin (on {}) cannot have a word offset", params._name));
341 }
342
343 if(params._numberOfWords > 1) {
344 throw ChimeraTK::logic_error(std::format(
345 "BitRangePlugin (on {}) must have size <=1, but {} was requested", params._name, params._numberOfWords));
346 }
347
350 std::format("BitRangePlugin (on {}) Unsupported flags in {}", params._name, params._flags.serialize()));
351 }
352
353 // Resolve the correct target device (must match the logic in getAccessor_impl)
354 std::string devName = _info.deviceName;
355 boost::shared_ptr<DeviceBackend> targetDevice;
356 if(devName != "this") {
357 targetDevice = backend->_devices[devName];
358 }
359 else {
360 targetDevice = backend;
361 }
362 return boost::make_shared<BitRangeAccessPluginDecorator<UserType>>(backend, targetDevice, target, params._name,
364 }
365
366 assert(false);
367
368 return {};
369 }
370
371} // namespace ChimeraTK::LNMBackend
void remove(AccessMode flag)
Remove the given flag from the set.
Definition AccessMode.cc:56
std::string serialize() const
Get a comma seperated list of all flag strings contained in the class.
Definition AccessMode.cc:68
bool has(AccessMode flag) const
Check if a certain flag is in the set.
Definition AccessMode.cc:20
Wrapper Class to avoid vector<bool> problems.
Definition Boolean.h:16
void setRawDataType(const DataType &d)
Set the raw data type.
@ none
The data type/concept does not exist, e.g. there is no raw transfer (do not confuse with Void)
LNMBackendRegisterInfo _info
RegisterInfo describing the the target register for which this plugin instance should work.
Base class for plugins that modify the behaviour of accessors in the logical name mapping backend.
BitRangeAccessPlugin(const LNMBackendRegisterInfo &info, size_t pluginIndex, const std::map< std::string, std::string > &parameters)
boost::shared_ptr< NDRegisterAccessor< UserType > > decorateAccessor(boost::shared_ptr< LogicalNameMappingBackend > &backend, boost::shared_ptr< NDRegisterAccessor< TargetType > > &target, const UndecoratedParams &accessorParams)
void doRegisterInfoUpdate() override
Implementation of the plugin specific register information update.
Helper class that keeps track of how many locks were taken on the recursive mutex in the current thre...
RegisterInfo structure for the LogicalNameMappingBackend.
RegisterPath getRegisterName() const override
Return full path name of the register (including modules)
AccessModeFlags supportedFlags
Supported AccessMode flags.
std::string deviceName
The target device alias.
std::pair< DeviceBackend *, RegisterPath > AccessorKey
Map of target accessors which are potentially shared across our accessors.
Base class for decorators of the NDRegisterAccessor.
void replaceTransferElement(boost::shared_ptr< ChimeraTK::TransferElement > newElement) override
N-dimensional register accessor.
std::vector< std::vector< UserType > > buffer_2D
Buffer of converted data elements.
Converter class for conversions from raw to cooked values.
UserType toCooked(RawType rawValue)
RawType toRaw(UserType cookedValue)
Class to store a register path name.
void setAltSeparator(const std::string &altSeparator)
set alternative separator.
DataValidity _dataValidity
The validity of the data in the application buffer.
VersionNumber _versionNumber
The version number of the last successful transfer.
bool _isInTransferGroup
Flag whether this TransferElement has been added to a TransferGroup or not.
const std::string & getName() const
Returns the name that identifies the process variable.
Class for generating and holding version numbers without exposing a numeric representation.
Exception thrown when a logic error has occured.
Definition Exception.h:51
constexpr uint64_t getMaskForNBits(uint64_t numberOfBits)
@ faulty
The data is considered valid.
@ wait_for_new_data
Make any read blocking until new data has arrived since the last read.
@ raw
Raw access: disable any possible conversion from the original hardware data type into the given UserT...
TransferType
Used to indicate the applicable operation on a Transferelement.
STL namespace.
void doPreRead(TransferType type) override
Backend specific implementation of preRead().
void doPreWriteImpl(RawConverter::Converter< CookedType, RawType, sc, fc, isSigned > converter, size_t implParameter)
void doPostReadImpl(RawConverter::Converter< CookedType, RawType, sc, fc, isSigned > converter, size_t implParameter)
void replaceTransferElement(boost::shared_ptr< ChimeraTK::TransferElement > newElement) override
void doPostRead(TransferType type, bool hasNewData) override
Backend specific implementation of postRead().
void doPreWrite(TransferType type, VersionNumber versionNumber) override
Backend specific implementation of preWrite().
BitRangeAccessPluginDecorator(boost::shared_ptr< LogicalNameMappingBackend > &backend, boost::shared_ptr< DeviceBackend > &targetDevice, const boost::shared_ptr< ChimeraTK::NDRegisterAccessor< uint64_t > > &target, const std::string &name, uint64_t shift, uint64_t numberOfBits, uint64_t dataInterpretationFractionalBits, uint64_t dataInterpretationIsSigned)
void doPostWrite(TransferType type, VersionNumber) override
Backend specific implementation of postWrite().
Helper struct to hold extra parameters needed by some plugins, used in decorateAccessor()