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