ChimeraTK-DeviceAccess 03.27.00
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 const boost::shared_ptr<ChimeraTK::NDRegisterAccessor<uint64_t>>& target, const std::string& name,
86 uint64_t shift, uint64_t numberOfBits, uint64_t dataInterpretationFractionalBits,
87 uint64_t dataInterpretationIsSigned)
88 : ChimeraTK::NDRegisterAccessorDecorator<UserType, uint64_t>(target), _shift(shift), _numberOfBits(numberOfBits),
89 _writeable{_target->isWriteable()} {
90 // makeConverterLoopHelper expects a NumericAddressedBackend RegisterInfo, which we create with the relevant
91 // parameters.
92 NumericAddressedRegisterInfo registerInfo{name, 1, 0, sizeof(uint64_t), 0, uint32_t(numberOfBits),
93 int32_t(dataInterpretationFractionalBits), bool(dataInterpretationIsSigned)};
94 _converterLoopHelper =
95 RawConverter::ConverterLoopHelper::makeConverterLoopHelper<UserType>(registerInfo, 0, 0, *this);
96
97 if(_target->getNumberOfChannels() > 1 || _target->getNumberOfSamples() > 1) {
98 throw ChimeraTK::logic_error("LogicalNameMappingBackend BitRangeAccessPluginDecorator: " +
99 TransferElement::getName() + ": Cannot target non-scalar registers.");
100 }
101
102 auto& map = boost::fusion::at_key<uint64_t>(backend->sharedAccessorMap.table);
103 RegisterPath path{name};
104 path.setAltSeparator(".");
105 LogicalNameMappingBackend::AccessorKey key{backend.get(), path};
106
107 auto it = map.find(key);
108 if(it != map.end()) {
109 _lock = ReferenceCountedUniqueLock(it->second.mutex);
110 }
111 else {
112 assert(false);
113 }
114
115 _baseBitMask = getMaskForNBits(_numberOfBits);
116 _maskOnTarget = _baseBitMask << _shift;
117 }
118
119 /******************************************************************************************************************/
120
121 void doPreRead(TransferType type) override {
122 _lock.lock();
123
124 _target->preRead(type);
125 }
126
127 /******************************************************************************************************************/
128
129 void doPostRead(TransferType type, bool hasNewData) override {
130 auto unlock = cppext::finally([this] { this->_lock.unlock(); });
131 _target->postRead(type, hasNewData);
132 if(!hasNewData) {
133 return;
134 }
135
136 _converterLoopHelper->doPostRead();
137 }
138
139 /******************************************************************************************************************/
140
141 template<class CookedType, typename RawType, RawConverter::SignificantBitsCase sc, RawConverter::FractionalCase fc,
142 bool isSigned>
144 [[maybe_unused]] size_t implParameter) {
145 static_assert(std::is_same_v<UserType, CookedType>);
146 if constexpr(!std::is_same_v<RawType, ChimeraTK::Void>) {
147 auto validity = _target->dataValidity();
148 uint64_t v{_target->accessData(0)};
149 v = (v & _maskOnTarget) >> _shift;
150
151 buffer_2D[0][0] = converter.toCooked(v);
152 // Do a quick check if the fixed point converter clamped. Then set the
153 // data validity faulty according to B.2.4.1
154 // For proper implementation of this, the fixed point converter needs to signalize
155 // that it had clamped. See https://redmine.msktools.desy.de/issues/12912
156 auto raw = converter.toRaw(buffer_2D[0][0]);
157 if(raw != v) {
158 validity = DataValidity::faulty;
159 }
160
161 this->_versionNumber = std::max(this->_versionNumber, _target->getVersionNumber());
162 this->_dataValidity = validity;
163 }
164 else {
165 assert(false);
166 }
167 }
168
169 /******************************************************************************************************************/
170
171 void doPreWrite(TransferType type, VersionNumber versionNumber) override {
172 _lock.lock();
173
174 if(!_writeable) {
176 "Register \"" + TransferElement::getName() + "\" with BitRange plugin is not writeable.");
177 }
178
179 _converterLoopHelper->doPreWrite();
180
181 _temporaryVersion = std::max(versionNumber, _target->getVersionNumber());
182 _target->preWrite(type, _temporaryVersion);
183 }
184
185 /******************************************************************************************************************/
186
187 template<class CookedType, typename RawType, RawConverter::SignificantBitsCase sc, RawConverter::FractionalCase fc,
188 bool isSigned>
190 [[maybe_unused]] size_t implParameter) {
191 static_assert(std::is_same_v<UserType, CookedType>);
192 if constexpr(!std::is_same_v<RawType, ChimeraTK::Void>) {
193 auto value = converter.toRaw(buffer_2D[0][0]);
194
195 // FIXME: Not setting the data validity according to the spec point B2.5.1.
196 // This needs a change in the fixedpoint converter to tell us that it has clamped the value to reliably work.
197 // To be revisted after fixing https://redmine.msktools.desy.de/issues/12912
198
199 // When in a transfer group, only the first accessor to write to the _target can call read() in its preWrite()
200 // Otherwise it will overwrite the
201 if(_target->isReadable() && (!TransferElement::_isInTransferGroup || _lock.useCount() == 1)) {
202 _target->read();
203 }
204
205 _target->accessData(0) &= ~_maskOnTarget;
206 _target->accessData(0) |= (value << _shift);
207
208 _target->setDataValidity(this->_dataValidity);
209 }
210 else {
211 assert(false);
212 }
213 }
214
215 /******************************************************************************************************************/
216
217 void doPostWrite(TransferType type, VersionNumber /*versionNumber*/) override {
218 auto unlock = cppext::finally([this] { this->_lock.unlock(); });
219 _target->postWrite(type, _temporaryVersion);
220 }
221
222 /******************************************************************************************************************/
223
224 void replaceTransferElement(boost::shared_ptr<ChimeraTK::TransferElement> newElement) override {
225 auto casted = boost::dynamic_pointer_cast<BitRangeAccessPluginDecorator<UserType>>(newElement);
226
227 // In a transfer group, we are trying to replaced with an accessor. Check if this accessor is for the
228 // same target and not us and check for overlapping bit range afterwards. If they overlap, switch us and
229 // the replacement read-only which switches the transfergroup read-only since we cannot guarantee the write
230 // order for overlapping bit ranges
231 if(casted && casted.get() != this && casted->_target == _target) {
232 // anding the two masks will yield 0 iff there is no overlap
233 if((casted->_maskOnTarget & _maskOnTarget) != 0) {
234 casted->_writeable = false;
235 _writeable = false;
236 }
237 }
239 }
240
241 /******************************************************************************************************************/
242
243 private:
244 uint64_t _shift;
245 uint64_t _numberOfBits;
246 uint64_t _maskOnTarget;
247 uint64_t _userTypeMask{getMaskForNBits(sizeof(UserType) * CHAR_BIT)};
248 uint64_t _targetTypeMask{getMaskForNBits(sizeof(uint64_t) * CHAR_BIT)};
249 uint64_t _baseBitMask;
250
251 ReferenceCountedUniqueLock _lock;
252 VersionNumber _temporaryVersion;
253 bool _writeable{false};
254 std::unique_ptr<RawConverter::ConverterLoopHelper> _converterLoopHelper;
255
256 using ChimeraTK::NDRegisterAccessorDecorator<UserType, uint64_t>::_target;
257 };
258
259 /********************************************************************************************************************/
260
262 const LNMBackendRegisterInfo& info, size_t pluginIndex, const std::map<std::string, std::string>& parameters)
263 : AccessorPlugin<BitRangeAccessPlugin>(info, pluginIndex, true) {
264 try {
265 const auto& shift = parameters.at("shift");
266
267 // This is how you are supposed to use std::from_chars with std::string
268 // NOLINTNEXTLINE(unsafe-buffer-usage)
269 auto [suffix, ec]{std::from_chars(shift.data(), shift.data() + shift.size(), _shift)};
270 if(ec != std::errc()) {
271 throw ChimeraTK::logic_error("LogicalNameMappingBackend BitRangeAccessPlugin: " + info.getRegisterName() +
272 R"(: Unparseable parameter "shift".)");
273 }
274 }
275 catch(std::out_of_range&) {
276 throw ChimeraTK::logic_error("LogicalNameMappingBackend BitRangeAccessPlugin: " + info.getRegisterName() +
277 R"(: Missing parameter "shift".)");
278 }
279
280 try {
281 const auto& numberOfBits = parameters.at("numberOfBits");
282 // This is how you are supposed to use std::from_chars with std::string
283 // NOLINTNEXTLINE(unsafe-buffer-usage)
284 auto [suffix, ec]{std::from_chars(numberOfBits.data(), numberOfBits.data() + numberOfBits.size(), _numberOfBits)};
285 if(ec != std::errc()) {
286 throw ChimeraTK::logic_error("LogicalNameMappingBackend BitRangeAccessPlugin: " + info.getRegisterName() +
287 R"(: Unparseable parameter "numberOfBits".)");
288 }
289 }
290 catch(std::out_of_range&) {
291 throw ChimeraTK::logic_error("LogicalNameMappingBackend BitRangeAccessPlugin: " + info.getRegisterName() +
292 R"(: Unparseable parameter "numberOfBits".)");
293 }
294
295 if(const auto it = parameters.find("fractionalBits"); it != parameters.end()) {
296 // This is how you are supposed to use std::from_chars with std::string
297 // NOLINTNEXTLINE(unsafe-buffer-usage)
298 auto [suffix, ec]{
299 std::from_chars(it->second.data(), it->second.data() + it->second.size(), dataInterpretationFractionalBits)};
300 if(ec != std::errc()) {
301 throw ChimeraTK::logic_error("LogicalNameMappingBackend BitRangeAccessPlugin: " + info.getRegisterName() +
302 R"(: Unparseable parameter "fractionalBits".)");
303 }
304 }
305
306 if(const auto it = parameters.find("signed"); it != parameters.end()) {
307 std::stringstream ss(it->second);
308 Boolean value;
309 ss >> value;
311 }
312 }
313
314 /********************************************************************************************************************/
315
317 // We do not support wait_for_new_data with this decorator
320 // also remove raw-type info from DataDescriptor
322 }
323
324 /********************************************************************************************************************/
325
326 template<typename UserType, typename TargetType>
327 boost::shared_ptr<NDRegisterAccessor<UserType>> BitRangeAccessPlugin::decorateAccessor(
328 boost::shared_ptr<LogicalNameMappingBackend>& backend, boost::shared_ptr<NDRegisterAccessor<TargetType>>& target,
329 const UndecoratedParams& params) {
330 if constexpr(std::is_same_v<TargetType, uint64_t>) {
331 return boost::make_shared<BitRangeAccessPluginDecorator<UserType>>(backend, target, params._name, _shift,
333 }
334
335 assert(false);
336
337 return {};
338 }
339
340} // namespace ChimeraTK::LNMBackend
void remove(AccessMode flag)
Remove the given flag from the set.
Definition AccessMode.cc:56
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::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)
BitRangeAccessPluginDecorator(boost::shared_ptr< LogicalNameMappingBackend > &backend, 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 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().
void doPostWrite(TransferType type, VersionNumber) override
Backend specific implementation of postWrite().
Helper struct to hold extra parameters needed by some plugins, used in decorateAccessor()