ChimeraTK-DeviceAccess 03.29.00
Loading...
Searching...
No Matches
GenericMuxedInterruptDistributor.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
4
5#include "async/SubDomain.h"
6
7#include <nlohmann/json.hpp>
8
9#include <boost/bimap.hpp>
10
11#include <atomic>
12#include <chrono>
13#include <iostream>
14#include <sstream>
15#include <thread>
16#include <vector>
17
18namespace ChimeraTK::async {
20 inline static constexpr const int jsonDescriptorVersion = 1;
21 inline static constexpr const char* const VERSION_JSON_KEY = "version";
22 inline static constexpr const char* const OPTIONS_JSON_KEY = "options";
23 inline static constexpr const char* const PATH_JSON_KEY = "path";
24 };
25
27
28 /********************************************************************************************************************/
29
34 boost::bimap<std::string, GmidOptionCode> makeBimap(
35 std::initializer_list<typename boost::bimap<std::string, GmidOptionCode>::value_type> list) {
36 return {list.begin(), list.end()};
37 }
38
39 /********************************************************************************************************************/
40
41 static const auto OptionCodeMap = makeBimap({
42 {"SIE", SIE},
43 {"IER", IER},
44 {"MER", MER},
45 {"MIE", MIE},
46 {"GIE", GIE},
47 {"ISR", ISR},
48 {"ICR", ICR},
49 {"IAR", IAR},
50 {"IPR", IPR},
51 {"CIE", CIE},
52 // Then the unacceptable ones
53 {"IMR", IMaskR},
54 {"IModeR", IModeR}, // REMOVE?
55 {"ILR", ILR}, // REMOVE?
56 {"IVR", IVR}, // REMOVE?
57 {"IVAR", IVAR}, // REMOVE?
58 {"IVEAR", IVEAR}, // REMOVE?
59 {"INVALID_OPTION_CODE", INVALID_OPTION_CODE},
60 });
61
62 /********************************************************************************************************************/
63
67 inline uint32_t iToMask(const uint32_t ithInterrupt) {
68 return 0x1U << ithInterrupt;
69 }
70
71 /********************************************************************************************************************/
72
80 GmidOptionCode getOptionRegisterEnum(const std::string& opt) {
81 auto it = OptionCodeMap.left.find(opt);
82 if(it != OptionCodeMap.left.end()) { // If a valid code
83 return it->second; // return the corresponding enum.
84 }
85 // invalid code
87 }
88
89 /********************************************************************************************************************/
90
94 std::string getOptionRegisterStr(GmidOptionCode optCode) {
95 auto it = OptionCodeMap.right.find(optCode);
96 if(it != OptionCodeMap.right.end()) { // If a valid code
97 return it->second; // return the corresponding str.
98 }
99 // invalid code
100 return "INVALID_OPTION_CODE";
101 }
102
103 /********************************************************************************************************************/
104
109 std::string explainOptCode(GmidOptionCode optCode) {
110 // clang-format off
111 static std::map<GmidOptionCode, std::string> mapOptCode2Message= {
112 {ISR, "Interrupt Status Register"},
113 {IER, "Interrupt Enable Register"},
114 {MER, "Master Enable Register"},
115 {MIE, "Master Interrupt Enable"},
116 {GIE, "Global Interrupt Enable"},
117 {ICR, "Interrupt Clear Register"},
118 {IAR, "Interrupt Acknowledge Register"},
119 {IPR, "Interrupt Pending Register"},
120 {SIE, "Set Interrupt Enable"},
121 {CIE, "Clear Interrupt Enable"},
122 {IMaskR,
123 "Interrupt Mask Register, not to be confused with Interrupt Mode Register"},
124 {IModeR,
125 "Interrupt Mode Register, what AXI INTC v4.1 calls this 'IMR', not to be confused with Interrupt Mask "
126 "Register, which we call 'IMR'"},//REMOVE?
127 {IVR, "Interrupt Vector Register"}, //REMOVE?
128 {ILR, "Interrupt Level Register"},//REMOVE?
129 {IVAR, "Interrupt Vector Address Register"},//REMOVE?
130 {IVEAR, "Interrupt Vector Extended Address Register"},//REMOVE?
131 {INVALID_OPTION_CODE, "No such option code is known"}
132 };
133 // clang-format on
134 return getOptionRegisterStr(optCode) + " (" + mapOptCode2Message[optCode] + ")";
135 }
136
137 /********************************************************************************************************************/
138
143 std::string strSetToStr(const std::set<std::string>& strSet, char delimiter = ',') {
144 std::string result;
145 if(not strSet.empty()) {
146 for(const auto& str : strSet) {
147 result += str + delimiter;
148 }
149 result.pop_back();
150 }
151 return result;
152 }
153
154 /********************************************************************************************************************/
155
161 std::string intVecToStr(const std::vector<size_t>& intVec, char delimiter = ',') {
162 std::string result;
163 if(not intVec.empty()) {
164 for(const auto& i : intVec) {
165 result += std::to_string(i) + delimiter;
166 }
167 result.pop_back();
168 }
169 return result;
170 }
171
172 /********************************************************************************************************************/
173
177 std::string controllerIDToStr(const std::vector<size_t>& controllerID) {
178 return "[" + intVecToStr(controllerID, ',') + "]";
179 }
180
181 /********************************************************************************************************************/
190 std::pair<std::bitset<OPTION_CODE_COUNT>, std::string> parseAndValidateJsonDescriptionStrV0(
191 const std::vector<size_t>& controllerID, const std::string& descriptionJsonStr) {
192 /*
193 * throws ChimeraTK::logic_error if there are any problems:
194 * throws if there are any unexpected keys
195 * throws if there the json snippet is unparsable
196 * throws if there the path is not in the snippet
197 * throws if the json version is != 1
198 * throws if invalid options given.
199 * This does not validate the logic of the 'options' combination,
200 * this only validates the json snippet.
201 */
202
203 static constexpr const int defaultJsonDescriptorVersion = JdkV1::jsonDescriptorVersion;
204 static const std::vector<std::string> defaultOptionRegisterNames = {"ISR", "IER"};
205 std::string registerPath;
206 std::bitset<OPTION_CODE_COUNT> optionRegisterSettings(0);
207
208 nlohmann::json descriptionJson;
209 try { // **Parse jsonDescriptor and sanitize inputs**
210 descriptionJson = nlohmann::json::parse(descriptionJsonStr);
211 }
212 catch(const nlohmann::json::parse_error& ex) {
213 std::ostringstream oss;
214 oss << "GenericMuxedInterruptDistributor " << controllerIDToStr(controllerID)
215 << " was unable to parse map file json snippet " << descriptionJsonStr;
216 throw ChimeraTK::logic_error(oss.str());
217 }
218
219 // Check that there are no unexpected json keys, throw if there are unexpected keys.
220 for(auto& el : descriptionJson.items()) {
221 if(el.key() != JdkV1::PATH_JSON_KEY and el.key() != JdkV1::OPTIONS_JSON_KEY and
222 el.key() != JdkV1::VERSION_JSON_KEY) {
223 std::ostringstream oss;
224 oss << "Unknown JSON key '" << el.key() << "' provided to map file for GenericMuxedInterruptDistributor "
225 << controllerIDToStr(controllerID);
226 throw ChimeraTK::logic_error(oss.str());
227 }
228 }
229
230 try { // to get registerPath
231 descriptionJson[JdkV1::PATH_JSON_KEY].get_to(registerPath);
232 }
233 catch(const nlohmann::json::exception& e) {
234 std::ostringstream oss;
235 oss << "Map file json register path key '" << JdkV1::PATH_JSON_KEY
236 << "' error for GenericMuxedInterruptDistributor " << controllerIDToStr(controllerID) << ": " << e.what();
237 throw ChimeraTK::logic_error(oss.str());
238 }
239
240 try { // Get Version and check version
241 if(descriptionJson.value("version", defaultJsonDescriptorVersion) != JdkV1::jsonDescriptorVersion) {
242 // version: version of this json descriptor.
243 // if version != 1, or whatever the expected version currently is, throw; if no version, assume 1
244 std::ostringstream oss;
245 oss << "GenericMuxedInterruptDistributor " << controllerIDToStr(controllerID) << " expects a "
246 << JdkV1::VERSION_JSON_KEY << " " << JdkV1::jsonDescriptorVersion << " JSON descriptor, "
248 << descriptionJson.value(JdkV1::VERSION_JSON_KEY, defaultJsonDescriptorVersion) << " was received.";
249 throw ChimeraTK::logic_error(oss.str());
250 }
251 }
252 catch(const nlohmann::json::exception& e) {
253 std::ostringstream oss;
254 oss << "Map file json " << JdkV1::VERSION_JSON_KEY << " key error for GenericMuxedInterruptDistributor "
255 << controllerIDToStr(controllerID) << ": " << e.what();
256 throw ChimeraTK::logic_error(oss.str());
257 }
258
259 std::vector<std::string> optionRegisterNames;
260 try { // Get Options
261 optionRegisterNames = descriptionJson.value(JdkV1::OPTIONS_JSON_KEY, defaultOptionRegisterNames);
262 // Defaults options are used since options are optional
263 }
264 catch(const nlohmann::json::exception& e) {
265 std::ostringstream oss;
266 oss << "Map file json " << JdkV1::OPTIONS_JSON_KEY << " key error for GenericMuxedInterruptDistributor "
267 << controllerIDToStr(controllerID) << ": " << e.what();
268 throw ChimeraTK::logic_error(oss.str());
269 }
270
271 /*
272 * Note that the case where the json option is provided but empty is covered by
273 * turning on ISR in the constructor and turn on IER with a check below.
274 */
275
276 std::set<std::string> invalidOptionRegisterNamesEncountered;
277 for(const auto& orn : optionRegisterNames) {
278 if(GmidOptionCode ornCode = getOptionRegisterEnum(orn); ornCode != INVALID_OPTION_CODE) {
279 optionRegisterSettings.set(ornCode);
280 }
281 else {
282 invalidOptionRegisterNamesEncountered.insert(orn);
283 }
284 }
285 if(!invalidOptionRegisterNamesEncountered.empty()) { // Throw if there are unknown options
286 std::ostringstream oss;
287 oss << "Invalid register options " << strSetToStr(invalidOptionRegisterNamesEncountered)
288 << " supplied in the map file json descriptor (key = " << JdkV1::OPTIONS_JSON_KEY
289 << ") for GenericMuxedInterruptDistributor " << controllerIDToStr(controllerID);
290 throw ChimeraTK::logic_error(oss.str());
291 }
292
293 return std::make_pair(optionRegisterSettings, registerPath);
294 } // parseAndValidateJsonDescriptionStrV0
295
296 /********************************************************************************************************************/
297
303 std::bitset<OPTION_CODE_COUNT> const& optionRegisterSettings, std::vector<size_t> const& controllerID) {
304 /*
305 * This enforces the following rules:
306 * throw if ISR not set
307 * throw if neither IER nor IMaskR are set.
308 * throw if IMaskR and IER are both set.
309 * throw if SIE xor CIE is set
310 * throw if IMaskR is set as well as SIE or CIE
311 * throw if ICR and IAR are both set.
312 * throw if more than 0 of MIE, GIE, MER are set.
313 * Temporary: throw if IMaskR is set
314 */
315
316 /*----------------------------------------------------------------------------------------------------------------*/
317 // Throw if only SIE or CIE is there, but not both
318 if(optionRegisterSettings.test(SIE) != optionRegisterSettings.test(CIE)) {
319 std::ostringstream oss;
320 oss << "Invalid register " << JdkV1::OPTIONS_JSON_KEY
321 << " combination specified in map file json descriptor for GenericMuxedInterruptDistributor "
322 << controllerIDToStr(controllerID) << ": Only " << explainOptCode(SIE) << " or " << explainOptCode(CIE)
323 << " is set, but not both.";
324 throw ChimeraTK::logic_error(oss.str());
325 }
326 /*----------------------------------------------------------------------------------------------------------------*/
327 // Throw if IMaskR is set as well as SIE or CIE
328 if(optionRegisterSettings.test(IMaskR)) {
329 if(optionRegisterSettings.test(SIE)) {
330 std::ostringstream oss;
331 oss << "Invalid register " << JdkV1::OPTIONS_JSON_KEY
332 << " combination specified in map file json descriptor for GenericMuxedInterruptDistributor "
333 << controllerIDToStr(controllerID) << ": " << explainOptCode(SIE) << " and " << explainOptCode(IMaskR)
334 << " cannot not both be set.";
335 throw ChimeraTK::logic_error(oss.str());
336 }
337 if(optionRegisterSettings.test(CIE)) {
338 std::ostringstream oss;
339 oss << "Invalid register " << JdkV1::OPTIONS_JSON_KEY
340 << " combination specified in map file json descriptor for GenericMuxedInterruptDistributor "
341 << controllerIDToStr(controllerID) << ": " << explainOptCode(CIE) << " and " << explainOptCode(IMaskR)
342 << " cannot not both be set.";
343 throw ChimeraTK::logic_error(oss.str());
344 }
345 }
346 /*----------------------------------------------------------------------------------------------------------------*/
347 // Throw if both ICR and IAR are there
348 if(optionRegisterSettings.test(ICR) and optionRegisterSettings.test(IAR)) {
349 std::ostringstream oss;
350 oss << "Invalid register " << JdkV1::OPTIONS_JSON_KEY
351 << " combination specified in map file json descriptor for GenericMuxedInterruptDistributor "
352 << controllerIDToStr(controllerID) << ": " << explainOptCode(ICR) << " and " << explainOptCode(IAR)
353 << " cannot not both be set.";
354 throw ChimeraTK::logic_error(oss.str());
355 }
356 /*----------------------------------------------------------------------------------------------------------------*/
357 // Throw if both IMaskR and IER are there
358 if(optionRegisterSettings.test(IMaskR) and optionRegisterSettings.test(IER)) {
359 std::ostringstream oss;
360 oss << "Invalid register " << JdkV1::OPTIONS_JSON_KEY
361 << " combination specified in map file json descriptor for GenericMuxedInterruptDistributor "
362 << controllerIDToStr(controllerID) << ": Only " << explainOptCode(IMaskR) << " and " << explainOptCode(IER)
363 << " cannot not both be set.";
364 throw ChimeraTK::logic_error(oss.str());
365 }
366 /*----------------------------------------------------------------------------------------------------------------*/
367 // Throw if neither IER nor IMaskR is set. This should be impossible.
368 if(not(optionRegisterSettings.test(IMaskR) or optionRegisterSettings.test(IER))) {
369 std::ostringstream oss;
370 oss << "Invalid register " << JdkV1::OPTIONS_JSON_KEY << " for GenericMuxedInterruptDistributor "
371 << controllerIDToStr(controllerID) << ": Neither " << explainOptCode(IMaskR) << " nor " << explainOptCode(IER)
372 << " are set, one of the two is required.";
373 throw ChimeraTK::logic_error(oss.str());
374 }
375
376 /*----------------------------------------------------------------------------------------------------------------*/
377 // Throw if more than one entry of [MIE, GIE, MER] is there (test all combinations)
378 int nMieGieMer = static_cast<int>(optionRegisterSettings.test(MIE)) +
379 static_cast<int>(optionRegisterSettings.test(GIE)) + static_cast<int>(optionRegisterSettings.test(MER));
380 if(nMieGieMer > 1) {
381 std::ostringstream oss;
382 oss << "Invalid register " << JdkV1::OPTIONS_JSON_KEY
383 << " combination specified in map file json descriptor for GenericMuxedInterruptDistributor "
384 << controllerIDToStr(controllerID) << ": Only one out of " << explainOptCode(MIE) << ", "
385 << explainOptCode(GIE) << ", and " << explainOptCode(MER) << " can be set; " << std::to_string(nMieGieMer)
386 << " are set.";
387 throw ChimeraTK::logic_error(oss.str());
388 }
389
390 /*----------------------------------------------------------------------------------------------------------------*/
391 // Throw if unsupported options options received
392 if(optionRegisterSettings.test(IModeR)) { // REMOVE?
393 std::ostringstream oss;
394 oss << "Unsupported register " << JdkV1::OPTIONS_JSON_KEY
395 << " specified in map file json descriptor for GenericMuxedInterruptDistributor "
396 << controllerIDToStr(controllerID) << ": While " << explainOptCode(IModeR)
397 << " is a defined options in the AXI IntC v4.1 register space, it is not currently an allowed options in"
398 " the GenericMuxedInterruptDistributor";
399 throw ChimeraTK::logic_error(oss.str());
400 }
401
402 if(optionRegisterSettings.test(IVEAR) or optionRegisterSettings.test(IVAR) or optionRegisterSettings.test(IVR) or
403 optionRegisterSettings.test(ILR) or optionRegisterSettings.test(IModeR)) { // REMOVE?
404 std::ostringstream oss;
405 oss << "Unsupported register " << JdkV1::OPTIONS_JSON_KEY
406 << " specified in map file json descriptor for GenericMuxedInterruptDistributor "
407 << controllerIDToStr(controllerID) << ": While " << explainOptCode(ILR) << ", " << explainOptCode(IVR) << ", "
408 << explainOptCode(IVAR) << ", and " << explainOptCode(IVEAR)
409 << "are defined options in the AXI IntC v4.1 register space, they are not currently allowed options in the "
410 "GenericMuxedInterruptDistributor";
411 throw ChimeraTK::logic_error(oss.str());
412 }
413
414 if(optionRegisterSettings.test(IMaskR)) { // Temporary, this throw should be removed in a later version. TODO
415 std::ostringstream oss;
416 oss << "Unsupported register " << JdkV1::OPTIONS_JSON_KEY
417 << " specified in map file json descriptor for GenericMuxedInterruptDistributor "
418 << controllerIDToStr(controllerID) << ": " << explainOptCode(IMaskR)
419 << " is not currently an allowed options in the GenericMuxedInterruptDistributor, but should be supported "
420 "in a later version. ";
421 throw ChimeraTK::logic_error(oss.str());
422 }
423 /*----------------------------------------------------------------------------------------------------------------*/
424 // throw if ISR is not set. This should be impossible.
425 if(not optionRegisterSettings.test(ISR)) {
426 std::ostringstream oss;
427 oss << explainOptCode(ISR) << " is required but is not enabled for GenericMuxedInterruptDistributor "
428 << controllerIDToStr(controllerID);
429 throw ChimeraTK::logic_error(oss.str());
430 }
431 } // steriliseOptionRegisterSettings
432
433 /********************************************************************************************************************/
434 /********************************************************************************************************************/
435 /********************************************************************************************************************/
436
438 const boost::shared_ptr<SubDomain<std::nullptr_t>>& parent, const std::string& registerPath,
439 std::bitset<GmidOptionCode::OPTION_CODE_COUNT> optionRegisterSettings)
440 : MuxedInterruptDistributor(parent), _path(registerPath.c_str()) {
441 // Set required registers
442 optionRegisterSettings.set(ISR); // Ensure that the required option ISR is always set.
443 if(not optionRegisterSettings.test(IMaskR)) { // Ensure IMaskR or IER is on
444 optionRegisterSettings.set(IER);
445 }
446 // Note that we currently don't allowing IMaskR, but use of it gets caught later.
447
448 // Here we could explicitly note that we're ignoring IPR with optionRegisterSettings.reset(IPR)
449
450 /*----------------------------------------------------------------------------------------------------------------*/
451 steriliseOptionRegisterSettings(optionRegisterSettings, parent->getId());
452
453 /*----------------------------------------------------------------------------------------------------------------*/
454 _isr = _backend->getRegisterAccessor<uint32_t>(_path / getOptionRegisterStr(ISR), 1, 0, {});
455
456 _ierIsReallyImaskr = optionRegisterSettings.test(IMaskR);
457 _ier = _backend->getRegisterAccessor<uint32_t>(
459
460 // Set the clear/acknowledge register {ICR, IAR, ISR}
461 if(optionRegisterSettings.test(ICR)) { // Use ICR as the clear register
462 _icr = _backend->getRegisterAccessor<uint32_t>(_path / getOptionRegisterStr(ICR), 1, 0, {});
463 }
464 else if(optionRegisterSettings.test(IAR)) { // Use IAR as the clear register
465 _icr = _backend->getRegisterAccessor<uint32_t>(_path / getOptionRegisterStr(IAR), 1, 0, {});
466 }
467 else { // Use ISR to clear interrupts using its write 1 to clear feature
468 _icr = _backend->getRegisterAccessor<uint32_t>(_path / getOptionRegisterStr(ISR), 1, 0, {});
469 }
470
471 _hasMer = optionRegisterSettings.test(MER) or optionRegisterSettings.test(MIE) or optionRegisterSettings.test(GIE);
472 if(_hasMer) {
473 GmidOptionCode _optionMerMieGie =
474 optionRegisterSettings.test(MIE) ? MIE : (optionRegisterSettings.test(GIE) ? GIE : MER);
475 _mer = _backend->getRegisterAccessor<uint32_t>(_path / getOptionRegisterStr(_optionMerMieGie), 1, 0, {});
476 }
477
478 // steriliseOptionRegisterSettings ensures that either SIE and CIE are both enabled or neither are
479 _haveSieAndCie = optionRegisterSettings.test(SIE);
480 if(_haveSieAndCie) {
481 _sie = _backend->getRegisterAccessor<uint32_t>(_path / getOptionRegisterStr(SIE), 1, 0, {});
482 _cie = _backend->getRegisterAccessor<uint32_t>(_path / getOptionRegisterStr(CIE), 1, 0, {});
483 }
484
485 /*----------------------------------------------------------------------------------------------------------------*/
486 // Check register readability/writeability
487 // We have to check the catalogue, because this is the expected behaviour, so we
488 // are allowed to throw a logic error here.
489 // If the accessor actually behaves differently, it will cause a runtime error when used (which is expected
490 // behaviour and OK.) For map-file based backends the behaviour of catalogue and accessor should always be
491 // consistent.
492 auto catalogue = _backend->getRegisterCatalogue();
493
494 auto checkRedable = [catalogue](auto& accessor) {
495 auto description = catalogue.getRegister(accessor->getName());
496 if(!description.isReadable()) {
498 "GenericMuxedInterruptDistributor: Handshake register not readable: " + accessor->getName());
499 }
500 };
501 auto checkWriteable = [catalogue](auto& accessor) {
502 auto description = catalogue.getRegister(accessor->getName());
503 if(!description.isWriteable()) {
505 "GenericMuxedInterruptDistributor: Handshake register not writeable: " + accessor->getName());
506 }
507 };
508
509 checkRedable(_isr); // we only read from it
510 checkWriteable(_ier); // we only write to ier as we are the only user (otherwise we need sie and cie)
511 checkWriteable(_icr); // usually write only
512 if(_mer) {
513 // we only write, never read
514 checkWriteable(_mer);
515 }
516 if(_sie) {
517 checkWriteable(_sie); // usually write only
518 }
519 if(_cie) {
520 checkWriteable(_cie); // usually write only
521 }
522 } // constructor
523
524 /********************************************************************************************************************/
526 // Stop and join the watchdog thread first, so it does not outlive this object or touch a closing backend.
527 _stopWatchdog = true;
528 if(_watchdogThread.joinable()) {
529 _watchdogThread.join();
530 }
531
532 if(_backend->isFunctional()) {
533 try {
535 }
536 catch(ChimeraTK::logic_error& e) {
537 // This try/catch is just to silence the linter. ChimeraTK logic errors can always be avoided by checking the
538 // according pre-condition. We did so by checking isFunctional, so there should be no exception here.
539 std::cerr << "Logic error in ~GenericMuxedInterruptDistributor: " << e.what() << " TERMINATING!" << std::endl;
540 std::terminate();
541 }
542 }
543
544 } // destructor
545
546 /********************************************************************************************************************/
547 /********************************************************************************************************************/
548 /********************************************************************************************************************/
550 while(!_stopWatchdog.load()) {
551 std::this_thread::sleep_for(_watchdogInterval);
552
553 if(_stopWatchdog.load()) {
554 break;
555 }
556
557 // Atomically read-and-clear the handler heartbeat.
558 // NOTE: we use the default memory order (seq_cst) here for simplicity/robustness. Explicitly
559 // passing std::memory_order_acq_rel would be strictly better.
560 bool handlerRan = _handlerRan.exchange(false);
561
562 // If we have already raised a device exception, stay dormant until the device has genuinely recovered.
563 if(_watchdogAlerted.load()) {
564 // Normally this flag is cleared in activate() when the device is re-opened/re-activated.
565 // Keeping it as fallback if a recovery path bypasses activate()).
566 if(_backend->isFunctional()) {
567 _watchdogAlerted.store(false);
568 }
569 // else: still wedged/closed, keep waiting.
570 continue;
571 }
572
573 if(!handlerRan) {
574 // The device is down (exception active or closed) - do not probe the ISR (logic error)
575 if(!_backend->isFunctional()) {
576 continue;
577 }
578
579 // No interrupt handler run completed between the two watchdog samples, so
580 // check if the ISR actually still has pending, enabled bits.
581 uint32_t pendingInterrupts = _activeInterrupts;
582 try {
583 _isr->read();
584 pendingInterrupts &= _isr->accessData(0);
585 }
587 // ISR could not be read because the backend is already in an exception state.
588 _watchdogAlerted.store(true);
589 continue;
590 }
591 catch(ChimeraTK::logic_error& e) {
592 // ISR could not be read because the device is not open.
593 std::cerr << "Watchdog: logic error reading ISR (device likely closed): " << e.what() << std::endl;
594 _watchdogAlerted.store(true);
595 continue;
596 }
597
598 // If the ISR is zero the device is simply idle
599 if(pendingInterrupts == 0) {
600 continue;
601 }
602
603 _backend->setException("GenericMuxedInterruptDistributor: interrupt handler did not run for " +
604 std::to_string(std::chrono::duration_cast<std::chrono::milliseconds>(_watchdogInterval).count()) +
605 " ms. Setting device exception.");
606
607 // Go dormant until the handler runs again (device recovers). Do NOT break/detach: the thread must stay
608 // alive so it can raise the device exception again after a reopen, and the destructor's join() is the
609 // single, race-free cleanup point.
610 _watchdogAlerted.store(true);
611 }
612 // else: handler ran at least once since the last sample — nothing to do this interval.
613 }
614 }
615
616 /********************************************************************************************************************/
618 try {
619 _icr->accessData(0) = mask;
620 _icr->write();
621 }
623 }
624 }
625
626 /********************************************************************************************************************/
627 inline void GenericMuxedInterruptDistributor::clearOneInterrupt(uint32_t ithInterrupt) {
628 clearInterruptsFromMask(iToMask(ithInterrupt));
629 }
630
631 /********************************************************************************************************************/
635
636 /********************************************************************************************************************/
640
641 /********************************************************************************************************************/
643 _activeInterrupts &= ~mask;
644 try {
646 // IMaskR is used, so SIE and CIE are not defined.
647 _ier->accessData(0) = ~_activeInterrupts;
648 _ier->write();
649 }
650 else {
651 if(_haveSieAndCie) {
652 _cie->accessData(0) = mask;
653 _cie->write();
654 }
655 else {
656 _ier->accessData(0) = _activeInterrupts;
657 _ier->write();
658 }
659 }
661 }
663 }
664 }
665
666 /********************************************************************************************************************/
667 inline void GenericMuxedInterruptDistributor::disableOneInterrupt(uint32_t ithInterrupt) {
668 disableInterruptsFromMask(iToMask(ithInterrupt));
669 }
670
671 /********************************************************************************************************************/
673 /*
674 * - When creating an accessor to "!0:N" (or a nested interrupt "!0:N:M") //MIR = IMR = IMaskR.
675 * - if SIE and CIE are there, it writes ``1<<N`` to SIE and _clears_ with ``1<<N``
676 * - if IMR is there, it writes ~(``1<<N``) to IMR and _clears_ with ``1<<N``
677 * - if neither (SIE and CIE) nor MIR are present, or only IER is there, it writes ``1<<N`` to IER
678 * and _clears_ with ``1<<N``
679 * - When creating accessor "!0:L" while still holding "!0:N"
680 * - if SIE and CIE are there, it writes `1<<L` to SIE and to CIE
681 * - if IMR is there, it writes ~( (``1<<N``) | (`1<<L`) ) to MIR and _clears_ with `1<<L`
682 * - if neither (SIE and CIE) nor IMR are present, or only IER is there, it writes ( ``1<<N``)|(`1<<L`)
683 * to IER and _clears_ with `1<<L`
684 */
685 _activeInterrupts |= mask;
686 try {
687 if(_ierIsReallyImaskr) { // Set IMaskR in the form of IER
688 _ier->accessData(0) = ~_activeInterrupts;
689 _ier->write();
690 // When IMaskR is used, SIE and CIE cannot be defined.
691 }
692 else {
693 if(_haveSieAndCie) { // Set SIE
694 _sie->accessData(0) = _activeInterrupts;
695 _sie->write();
696 }
697 else { // Set IER, which actually is IER and not IMaskR
698 _ier->accessData(0) = _activeInterrupts;
699 _ier->write();
700 }
701 }
702 }
704 }
705 } // enableInterruptFromMask
706
707 /********************************************************************************************************************/
708 inline void GenericMuxedInterruptDistributor::enableOneInterrupt(uint32_t ithInterrupt) {
709 enableInterruptsFromMask(iToMask(ithInterrupt));
710 }
711
712 /********************************************************************************************************************/
714 try {
715 // after distributing and clearing one snapshot, re-read the ISR .
716 uint32_t processedMask = 0; // bits already distributed in this handle() run
717 uint32_t ipr;
718 _isr->read();
719 ipr = _activeInterrupts & _isr->accessData(0);
720 do {
721 // Only distribute bits that were not already handled in a previous round of this run.
722 uint32_t newBits = ipr & ~processedMask;
723 for(auto const& [i, subDomainWeakPtr] : _subDomains) {
724 // i is the bit index of the subDomain
725 if(newBits & iToMask(i)) {
726 if(auto subDomain = subDomainWeakPtr.lock(); subDomain) {
727 // The weak pointer might have gone.
728 // TODO FIXME: We need a cleanup function which removes the map entry.
729 // Otherwise we might be stuck with a bad weak pointer which is tried in each handle() call.
730
731 subDomain->distribute(nullptr, version);
732
733 // Requirement: nested interrupt handlers must clear their active interrupt flag first,
734 // then the parent interrupt flags are cleared.
735 // why not first clear and then distribute?
737 }
738 }
739 } // for
740 processedMask |= ipr;
741
742 // Re-read to catch stragglers that arrived during distribution (interrupts asserted during the
743 // distribute()/clearOneInterrupt() calls above and therefore missed by the first snapshot). Giving the
744 // hardware (experimental) tiny moment to latch newly arrived interrupts, then loop while an enabled bit that we
745 // have not yet processed in this run is still pending.
746 std::this_thread::sleep_for(std::chrono::microseconds(20));
747 _isr->read();
748 ipr = _activeInterrupts & _isr->accessData(0);
749 } while((ipr & ~processedMask) != 0);
750
751 // Signal that the interrupt handler has completed a run. The watchdog thread reads-and-clears
752 // this flag via exchange() to detect a wedged / starved interrupt handler.
753 //
754 // NOTE: we use the default memory order (seq_cst) here for simplicity/robustness. Explicitly
755 // passing std::memory_order_release would be strictly better.
756 _handlerRan.store(true);
757 }
759 // There's nothing to do. The transferElement part of _activeInterrupts has already called the backend's setException
760 }
761 } // handle
762
763 /********************************************************************************************************************/
764 std::unique_ptr<GenericMuxedInterruptDistributor> GenericMuxedInterruptDistributor::create(
765 [[maybe_unused]] std::string const& description, const boost::shared_ptr<SubDomain<std::nullptr_t>>& parent) {
766 /*
767 * This is a factory function. It parses the json, and calls the constructor.
768 * It returns an initalized GenericMuxedInterruptDistributor.
769 * 'description' is a JSON snippet containing configuration data
770 */
771
772 auto parseResult = parseAndValidateJsonDescriptionStrV0(parent->getId(), description);
773 std::bitset<OPTION_CODE_COUNT> optionRegisterSettings = parseResult.first;
774 std::string registerPath = parseResult.second;
775
776 return std::make_unique<GenericMuxedInterruptDistributor>(parent, registerPath, optionRegisterSettings);
777 } // create
778
779 /********************************************************************************************************************/
781 if(_hasMer) { // Set MER: turn on the Master Enable and HIE (hardware interrupt enable) bits
782 try {
783 _mer->accessData(0) = 0x00000003;
784 _mer->write();
785 }
787 }
788 }
789
790 // Disable any interrupts for which there is no valid subDomain.
791 uint32_t activeInterrupts = 0;
792 for(auto const& [i, subDomainWeakPtr] : _subDomains) {
793 try {
794 if(subDomainWeakPtr.lock()) {
795 activeInterrupts |= iToMask(i);
796 }
797 }
799 }
800 } // for
801 enableInterruptsFromMask(activeInterrupts);
802
804
805 // Re-arm the watchdog.
806 _watchdogAlerted.store(false);
807
808 // Start the watchdog thread if it is not already running. It monitors that the interrupt handler
809 // keeps executing by checking a counter.
810 if(!_watchdogThread.joinable()) {
811 _stopWatchdog = false;
813 }
814
815 // Activate all existing sub-domains. We have to implement a loop here because the parent activate is calling
816 // activateSubDomain() internally, which is not necessary because we already wrote to the hardware to do the
817 // handshake.
818 for(auto& subDomainIter : _subDomains) {
819 auto subDomain = subDomainIter.second.lock();
820 if(subDomain) {
821 subDomain->activate(nullptr, version);
822 }
823 }
824 } // activate
825
826 /********************************************************************************************************************/
828 SubDomain<std::nullptr_t>& subDomain, VersionNumber const& version) {
829 auto index = subDomain.getId().back();
830
833
834 subDomain.activate(nullptr, version);
835 }
836
837 /********************************************************************************************************************/
838} // namespace ChimeraTK::async
Class for generating and holding version numbers without exposing a numeric representation.
std::atomic< bool > _handlerRan
Set true by handle() after each completed run.
void clearInterruptsFromMask(uint32_t mask)
In mask, 1 bits clear the corresponding registers, 0 bits do nothing.
void enableInterruptsFromMask(uint32_t mask)
For each bit in mask that is a 1, the corresponding interrupt gets enabled, and the internal copy of ...
static constexpr std::chrono::milliseconds _watchdogInterval
Watchdog polling/decision interval (experimental)
void disableInterruptsFromMask(uint32_t mask)
Disables each interrupt corresponding to the 1 bits in mask, and updates _activeInterrupts.
boost::shared_ptr< NDRegisterAccessor< uint32_t > > _ier
boost::shared_ptr< NDRegisterAccessor< uint32_t > > _cie
std::atomic< bool > _watchdogAlerted
Set true once the watchdog has raised a device exception.
boost::shared_ptr< NDRegisterAccessor< uint32_t > > _isr
boost::shared_ptr< NDRegisterAccessor< uint32_t > > _sie
void handle(VersionNumber version) override
Handle gets called when a trigger comes in.
void activateSubDomain(SubDomain< std::nullptr_t > &subDomain, VersionNumber const &version) override
Function to activate a (new) single SubDomain if the MuxedInterruptDistributor is already active.
boost::shared_ptr< NDRegisterAccessor< uint32_t > > _icr
boost::shared_ptr< NDRegisterAccessor< uint32_t > > _mer
std::thread _watchdogThread
Watchdog thread and its stop flag.
static std::unique_ptr< GenericMuxedInterruptDistributor > create(std::string const &description, const boost::shared_ptr< SubDomain< std::nullptr_t > > &parent)
Create parses the json configuration snippet 'description', and calls the constructor.
GenericMuxedInterruptDistributor(const boost::shared_ptr< SubDomain< std::nullptr_t > > &parent, const std::string &registerPath, std::bitset<(ulong) GmidOptionCode::OPTION_CODE_COUNT > optionRegisterSettings)
Interface base class for interrupt controller handlers.
std::map< size_t, boost::weak_ptr< SubDomain< std::nullptr_t > > > _subDomains
Send backend-specific asynchronous data to different distributors:
Definition SubDomain.h:33
std::vector< size_t > getId()
Definition SubDomain.h:53
void activate(BackendSpecificDataType, VersionNumber v)
Definition SubDomain.h:211
Exception thrown when a logic error has occured.
Definition Exception.h:51
const char * what() const noexcept override
Return the message describing what exactly went wrong.
Definition Exception.cpp:20
Exception thrown when a runtime error has occured.
Definition Exception.h:18
std::string explainOptCode(GmidOptionCode optCode)
This returns strings explaining the option code acronyms for use in error messages.
std::pair< std::bitset< OPTION_CODE_COUNT >, std::string > parseAndValidateJsonDescriptionStrV0(const std::vector< size_t > &controllerID, const std::string &descriptionJsonStr)
This extracts and validates data from the json snippet 'descriptorJsonStr' that matches the version 1...
std::string getOptionRegisterStr(GmidOptionCode optCode)
Given the Register option code enum, returns the corresponding string.
std::string strSetToStr(const std::set< std::string > &strSet, char delimiter=',')
The default delimiter is ',' TODO move this to some string helper library.
std::string controllerIDToStr(const std::vector< size_t > &controllerID)
Return a string describing the controllerID of the form "[1,2,3]".
std::string intVecToStr(const std::vector< size_t > &intVec, char delimiter=',')
Return a string describing the intVec of the form "1,2,3" The default delimiter is ',...
GmidOptionCode getOptionRegisterEnum(const std::string &opt)
If the string is not a recognized option code, returns GmidOptionCode::INVALID_OPTION_CODE It is not ...
uint32_t iToMask(const uint32_t ithInterrupt)
Return a 32 bit mask with the ithInterrupt bit from the left set to 1 and all others 0.
boost::bimap< std::string, GmidOptionCode > makeBimap(std::initializer_list< typename boost::bimap< std::string, GmidOptionCode >::value_type > list)
This is an initializer for a boost::bimap so that it can be produced using nice syntax.
void steriliseOptionRegisterSettings(std::bitset< OPTION_CODE_COUNT > const &optionRegisterSettings, std::vector< size_t > const &controllerID)
Ensures permissible combinations of option registers by throwing ChimeraTK::logic_error if there are ...
std::string to_string(const std::string &v)