ChimeraTK-DeviceAccess 03.25.00
Loading...
Searching...
No Matches
testPcieBackend.cpp
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
7using namespace boost::unit_test_framework;
8
9#define PCIEDEV_TEST_SLOT 0
10#define LLRFDRV_TEST_SLOT 4
11#define PCIEUNI_TEST_SLOT 6
12
13#include "BackendFactory.h"
14#include "Device.h"
15#include "Exception.h"
16#include "NumericAddress.h"
17#include "PcieBackend.h"
18
19#include <sys/file.h>
20#include <sys/stat.h>
21#include <sys/types.h>
22
23namespace ChimeraTK {
24 using namespace ChimeraTK;
25}
26using namespace ChimeraTK;
28
29// constants for the registers and their contents. We keep the hard coded
30// values at one place and only use the constants in the code below.
31#define WORD_FIRMWARE_OFFSET 0x0
32#define WORD_COMPILATION_OFFSET 0x4
33#define WORD_USER_OFFSET 0xC
34#define WORD_CLK_CNT_OFFSET 0x10
35#define WORD_DUMMY_OFFSET 0x3C
36#define DMMY_AS_ASCII 0x444D4D59
37#define WORD_ADC_ENA_OFFSET 0x44
38#define N_WORDS_DMA 25
39#define PCIE_DEVICE "PCIE6"
40#define LLRF_DEVICE "LLRF10"
41// #define PCIE_UNI_DEVICE "PCIEUNI11"
42#define PCIE_UNI_DEVICE "PCIE0"
43#define NON_EXISTING_DEVICE "DUMMY9"
44
45/**********************************************************************************************************************/
46
47// Use a file lock on /var/run/lock/mtcadummy/<devicenode> for all device nodes
48// we are using in this test, to ensure we are not running concurrent tests in
49// parallel using the same kernel dummy drivers.
50//
51// Note: The lock is automatically released when the process terminates!
52struct TestLocker {
53 std::vector<std::string> usedNodes{"mtcadummys0", "llrfdummys4", "noioctldummys5", "pcieunidummys6"};
54
56 mkdir("/var/run/lock/mtcadummy",
57 0777); // ignore errors intentionally, as directory might already exist
58 for(auto& node : usedNodes) {
59 std::string lockfile = "/var/run/lock/mtcadummy/" + node;
60
61 // open dmap file for locking
62 int fd = open(lockfile.c_str(), O_WRONLY | O_CREAT, 0777);
63 if(fd == -1) {
64 std::cout << "Cannot open file '" << lockfile << "' for locking." << std::endl;
65 exit(1);
66 }
67
68 // obtain lock
69 int res = flock(fd, LOCK_EX);
70 if(res == -1) {
71 std::cout << "Cannot acquire lock on file 'shareddummyTest.dmap'." << std::endl;
72 exit(1);
73 }
74 }
75 }
76
78 for(auto& node : usedNodes) {
79 std::string lockfile = "/var/run/lock/mtcadummy/" + node;
80 unlink(lockfile.c_str());
81 }
82 }
83};
84static TestLocker testLocker;
85
86/**********************************************************************************************************************/
87
97static BackendFactory& factoryInstance = BackendFactory::getInstance();
99 public:
100 PcieBackendTest(std::string const& deviceFileName, unsigned int slot);
101
107 static void testConstructor();
108
114
115 // Try Creating a backend and check if it is connected.
116 void testCreateBackend();
117
118 // Try opening the created backend and check it's open status.
119 void testOpen();
120
121 // Try closing the created backend and check it's open status.
122 void testClose();
123
124 void testRead();
125 void testWriteArea();
126
127 void testReadRegister();
128 void testWriteRegister();
129
130 void testReadDMA();
131 void testWriteDMA();
132
133 void testReadDeviceInfo();
136
137 private:
138 PcieBackend _pcieBackend;
139 std::string _deviceFileName;
140 unsigned int _slot;
141
142 boost::shared_ptr<PcieBackend> _pcieBackendInstance;
143
144 // Internal function for better code readablility.
145 // Returns an error message. If the message is empty the test succeeded.
146 std::string checkDmaValues(std::vector<int32_t> const& dmaBuffer);
147};
148
149/**********************************************************************************************************************/
150
151class PcieBackendTestSuite : public test_suite {
152 public:
153 PcieBackendTestSuite(std::string const& deviceFileName, unsigned int slot) : test_suite("PcieBackend test suite") {
155 // add member function test cases to a test suite
156 boost::shared_ptr<PcieBackendTest> pcieBackendTest(new PcieBackendTest(deviceFileName, slot));
157
158 test_case* createBackendTestCase = BOOST_CLASS_TEST_CASE(&PcieBackendTest::testCreateBackend, pcieBackendTest);
159 test_case* openTestCase = BOOST_CLASS_TEST_CASE(&PcieBackendTest::testOpen, pcieBackendTest);
160
161 test_case* readTestCase = BOOST_CLASS_TEST_CASE(&PcieBackendTest::testRead, pcieBackendTest);
162 test_case* writeAreaTestCase = BOOST_CLASS_TEST_CASE(&PcieBackendTest::testWriteArea, pcieBackendTest);
163
164 test_case* readRegisterTestCase = BOOST_CLASS_TEST_CASE(&PcieBackendTest::testReadRegister, pcieBackendTest);
165 test_case* writeRegisterTestCase = BOOST_CLASS_TEST_CASE(&PcieBackendTest::testWriteRegister, pcieBackendTest);
166
167 test_case* readDMATestCase = BOOST_CLASS_TEST_CASE(&PcieBackendTest::testReadDMA, pcieBackendTest);
168 test_case* writeDMATestCase = BOOST_CLASS_TEST_CASE(&PcieBackendTest::testWriteDMA, pcieBackendTest);
169
170 test_case* readDeviceInfoTestCase = BOOST_CLASS_TEST_CASE(&PcieBackendTest::testReadDeviceInfo, pcieBackendTest);
171
172 test_case* closeTestCase = BOOST_CLASS_TEST_CASE(&PcieBackendTest::testClose, pcieBackendTest);
173
174 test_case* testConstructor = BOOST_TEST_CASE(&PcieBackendTest::testConstructor);
175
176 createBackendTestCase->depends_on(testConstructor);
177 openTestCase->depends_on(createBackendTestCase);
178 readTestCase->depends_on(openTestCase);
179 writeAreaTestCase->depends_on(readTestCase);
180 readRegisterTestCase->depends_on(writeAreaTestCase);
181 writeRegisterTestCase->depends_on(readRegisterTestCase);
182 readDMATestCase->depends_on(writeRegisterTestCase);
183 writeDMATestCase->depends_on(readDMATestCase);
184 readDeviceInfoTestCase->depends_on(writeDMATestCase);
185 closeTestCase->depends_on(readDeviceInfoTestCase);
186
187 add(testConstructor);
188
189 add(createBackendTestCase);
190 add(openTestCase);
191
192 add(readTestCase);
193 add(writeAreaTestCase);
194
195 add(readRegisterTestCase);
196 add(writeRegisterTestCase);
197
198 add(readDMATestCase);
199 add(writeDMATestCase);
200
201 add(readDeviceInfoTestCase);
202
203 add(closeTestCase);
204 }
205
206 private:
207};
208
209/**********************************************************************************************************************/
210
212 framework::master_test_suite().p_name.value = "PcieBackend test suite";
213
214 std::stringstream llrfdummyFileName;
215 llrfdummyFileName << "/dev/llrfdummys" << LLRFDRV_TEST_SLOT;
216 // framework::master_test_suite().add( new
217 // PcieBackendTestSuite(llrfdummyFileName.str(), LLRFDRV_TEST_SLOT) );
218 // framework::master_test_suite().add( new PcieBackendTestSuite(LLRF_DEVICE,
219 // LLRFDRV_TEST_SLOT) );
220
221 std::stringstream mtcadummyFileName;
222 mtcadummyFileName << "/dev/mtcadummys" << PCIEDEV_TEST_SLOT;
223 // framework::master_test_suite().add( new PcieBackendTestSuite(PCIE_DEVICE,
224 // PCIEDEV_TEST_SLOT) );
225
226 std::stringstream pcieunidummyFileName;
227 pcieunidummyFileName << "/dev/pcieunidummys" << PCIEUNI_TEST_SLOT;
228 framework::master_test_suite().add(new PcieBackendTestSuite(PCIE_UNI_DEVICE, PCIEUNI_TEST_SLOT));
229
230 return true;
231}
232
233/**********************************************************************************************************************/
234
235// The implementations of the individual tests
236
238 std::cout << "testConstructor" << std::endl;
239 PcieBackend pcieBackend("");
240 BOOST_CHECK(pcieBackend.isOpen() == false);
241}
242
243PcieBackendTest::PcieBackendTest(std::string const& deviceFileName, unsigned int slot)
244: _pcieBackend(deviceFileName), _deviceFileName(deviceFileName), _slot(slot) {}
245
246std::string PcieBackendTest::checkDmaValues(std::vector<int32_t> const& dmaBuffer) {
247 std::cout << "testDmaValues" << std::endl;
248 bool dmaValuesOK = true;
249 size_t i; // we need this after the loop
250 for(i = 0; i < dmaBuffer.size(); ++i) {
251 if(dmaBuffer[i] != static_cast<int32_t>(i * i)) {
252 dmaValuesOK = false;
253 break;
254 }
255 }
256
257 if(dmaValuesOK) {
258 return std::string(); // an empty string means test is ok
259 }
260
261 std::stringstream errorMessage;
262 errorMessage << "Content of transferred DMA block is not valid. First wrong "
263 "value at index "
264 << i << " is " << dmaBuffer[i] << std::endl;
265
266 return errorMessage.str();
267}
268
269/**********************************************************************************************************************/
270
272 std::cout << "testReadDeviceInfo" << std::endl;
273 // The device info returns slot and driver version (major and minor).
274 // For the dummy major and minor are the same as firmware and compilation,
275 // respectively.
276 int32_t major;
277 //_pcieBackendInstance->readReg(WORD_FIRMWARE_OFFSET, &major, /*bar*/ 0);
278 _pcieBackendInstance->read(/*bar*/ 0, WORD_FIRMWARE_OFFSET, &major, 4);
279 int32_t minor;
280 //_pcieBackendInstance->readReg(WORD_COMPILATION_OFFSET, &minor, /*bar*/ 0);
281 _pcieBackendInstance->read(/*bar*/ 0, WORD_COMPILATION_OFFSET, &minor, 4);
282 std::stringstream referenceInfo;
283 referenceInfo << "SLOT: " << _slot << " DRV VER: " << major << "." << minor;
284
285 std::string deviceInfo;
286 deviceInfo = _pcieBackendInstance->readDeviceInfo();
287 BOOST_CHECK_EQUAL(referenceInfo.str(), deviceInfo);
288}
289
290/**********************************************************************************************************************/
291
293 std::cout << "testReadDMA" << std::endl;
294 // Start the ADC on the dummy device. This will fill the "DMA" buffer with the
295 // default values (index^2) in the first 25 words.
296 //_pcieBackendInstance->writeReg(/*bar*/ 0, WORD_ADC_ENA_OFFSET, 1);
297 int32_t data = 1;
298 _pcieBackendInstance->write(/*bar*/ 0, WORD_ADC_ENA_OFFSET, &data, 4);
299
300 std::vector<int32_t> dmaUserBuffer(N_WORDS_DMA, -1);
301
302 _pcieBackendInstance->read(/*the dma bar*/ 2, /*offset*/ 0, &dmaUserBuffer[0], N_WORDS_DMA * sizeof(int32_t));
303
304 std::string errorMessage = checkDmaValues(dmaUserBuffer);
305 BOOST_CHECK_MESSAGE(errorMessage.empty(), errorMessage);
306
307 // test dma with offset
308 // read 20 words from address 5
309 std::vector<int32_t> smallBuffer(20, -1);
310 static const unsigned int readOffset = 5;
311 _pcieBackendInstance->read(
312 /*the dma bar*/ 2, /*offset*/ readOffset * sizeof(int32_t), &smallBuffer[0],
313 smallBuffer.size() * sizeof(int32_t));
314
315 for(size_t i = 0; i < smallBuffer.size(); ++i) {
316 BOOST_CHECK(smallBuffer[i] == static_cast<int32_t>((i + readOffset) * (i + readOffset)));
317 }
318}
319
320/**********************************************************************************************************************/
321
323 std::cout << "testWriteDMA" << std::endl;
324}
325
326/**********************************************************************************************************************/
327
329 std::cout << "testRead" << std::endl;
330 // FIXME: Change the driver to have the standard register set and adapt this
331 // code
332
333 // Read the first two words, which are WORD_FIRMWARE and WORD_COMPILATION
334 // We checked that single reading worked, so we use it to create the
335 // reference.
336 int32_t firmwareContent;
337 _pcieBackendInstance->read(/*bar*/ 0, WORD_FIRMWARE_OFFSET, &firmwareContent, 4);
338 int32_t compilationContent;
339 _pcieBackendInstance->read(/*bar*/ 0, WORD_COMPILATION_OFFSET, &compilationContent, 4);
340
341 // Now try reading them as area
342 int32_t twoWords[2];
343 twoWords[0] = 0xFFFFFFFF;
344 twoWords[1] = 0xFFFFFFFF;
345
346 _pcieBackendInstance->read(/*bar*/ 0, WORD_FIRMWARE_OFFSET, twoWords, 2 * sizeof(int32_t));
347 BOOST_CHECK((twoWords[0] == firmwareContent) && (twoWords[1] == compilationContent));
348
349 // now try to read only six of the eight bytes. This should throw an exception
350 // because it is not a multiple of 4.
351 BOOST_CHECK_THROW(
352 _pcieBackendInstance->read(/*bar*/ 0, /*offset*/ 0, twoWords, /*nBytes*/ 6), ChimeraTK::runtime_error);
353
354 // also check another bar
355 // Start the ADC on the dummy device. This will fill bar 2 (the "DMA" buffer)
356 // with the default values (index^2) in the first 25 words.
357 int32_t data = 1;
358 _pcieBackendInstance->write(/*bar*/ 0, WORD_ADC_ENA_OFFSET, &data, 4);
359 // use the same test as for DMA
360 std::vector<int32_t> bar2Buffer(N_WORDS_DMA, -1);
361 _pcieBackendInstance->read(/*the dma bar*/ 2, /*offset*/ 0, &bar2Buffer[0], N_WORDS_DMA * sizeof(int32_t));
362
363 std::string errorMessage = checkDmaValues(bar2Buffer);
364 BOOST_CHECK_MESSAGE(errorMessage.empty(), errorMessage);
365}
366
367/**********************************************************************************************************************/
368
370 std::cout << "testWriteArea" << std::endl;
371 // FIXME: Change the driver to have the standard register set and adapt this
372 // code
373
374 // Read the two WORD_CLK_CNT words, write them and read them back
375 int32_t originalClockCounts[2];
376 int32_t increasedClockCounts[2];
377 int32_t readbackClockCounts[2];
378
379 _pcieBackendInstance->read(/*bar*/ 0, WORD_CLK_CNT_OFFSET, originalClockCounts, 2 * sizeof(int32_t));
380 increasedClockCounts[0] = originalClockCounts[0] + 1;
381 increasedClockCounts[1] = originalClockCounts[1] + 1;
382 _pcieBackendInstance->write(/*bar*/ 0, WORD_CLK_CNT_OFFSET, increasedClockCounts, 2 * sizeof(int32_t));
383 _pcieBackendInstance->read(/*bar*/ 0, WORD_CLK_CNT_OFFSET, readbackClockCounts, 2 * sizeof(int32_t));
384 BOOST_CHECK(
385 (increasedClockCounts[0] == readbackClockCounts[0]) && (increasedClockCounts[1] == readbackClockCounts[1]));
386
387 // now try to write only six of the eight bytes. This should throw an
388 // exception because it is not a multiple of 4.
389 BOOST_CHECK_THROW(_pcieBackendInstance->write(/*bar*/ 0, WORD_CLK_CNT_OFFSET, originalClockCounts,
390 /*nBytes*/ 6),
392
393 // also test another bar (area in bar 2), the usual drill: write and read
394 // back, we know that reading works from the previous test
395 std::vector<int32_t> writeBuffer(N_WORDS_DMA, 0xABCDEF01);
396 std::vector<int32_t> readbackBuffer(N_WORDS_DMA, -1);
397 _pcieBackendInstance->write(/*bar*/ 2, 0, &writeBuffer[0], N_WORDS_DMA * sizeof(int32_t));
398 _pcieBackendInstance->read(/*bar*/ 2, 0, &readbackBuffer[0], N_WORDS_DMA * sizeof(int32_t));
399 BOOST_CHECK(readbackBuffer == writeBuffer);
400}
401
402/**********************************************************************************************************************/
403
405 std::cout << "testReadRegister" << std::endl;
406 // FIXME: Change the driver to have the standard register set and adapt this
407 // code
408
409 // read the WORD_COMPILATION register in bar 0. It's value is not 0.
410 int32_t dataWord = 0; // initialise with 0 so we can check if reading the content works.
411
412 _pcieBackendInstance->open(); // no need to check if this works because we did
413 // the open test first
414 //_pcieBackendInstance->readReg(WORD_DUMMY_OFFSET, &dataWord, /*bar*/ 0);
415 _pcieBackendInstance->read(/*bar*/ 0, WORD_DUMMY_OFFSET, &dataWord, 4);
416 BOOST_CHECK_EQUAL(dataWord, DMMY_AS_ASCII);
417
420 // BOOST_CHECK_THROW( _pcieBackendInstance->readReg(WORD_DUMMY_OFFSET,
421 // &dataWord, /*bar*/ 6),
422 BOOST_CHECK_THROW(_pcieBackendInstance->getRegisterAccessor<int>("#6/0x3C", 4, 0, {}), ChimeraTK::logic_error);
423}
424
425/**********************************************************************************************************************/
426
428 std::cout << "testWriteRegister" << std::endl;
429 // FIXME: Change the driver to have the standard register set and adapt this
430 // code
431
432 // We read the user register, increment it by one, write it and reread it.
433 // As we checked that reading work, this is a reliable test that writing is
434 // ok.
435 int32_t originalUserWord, newUserWord;
436 _pcieBackendInstance->read(/*bar*/ 0, WORD_USER_OFFSET, &originalUserWord, 4);
437 int32_t data = originalUserWord + 1;
438 _pcieBackendInstance->write(/*bar*/ 0, WORD_USER_OFFSET, &data, 4);
439 _pcieBackendInstance->read(/*bar*/ 0, WORD_USER_OFFSET, &newUserWord, 4);
440
441 BOOST_CHECK_EQUAL(originalUserWord + 1, newUserWord);
442}
443
444/**********************************************************************************************************************/
445
447 std::cout << "testClose" << std::endl;
448 /* Try closing the backend */
449 _pcieBackendInstance->close();
450 /* backend should not be open now */
451 BOOST_CHECK(_pcieBackendInstance->isOpen() == false);
452 // It always has to be possible to call close again.
453 _pcieBackendInstance->close();
454 BOOST_CHECK(_pcieBackendInstance->isOpen() == false);
455}
456
457/**********************************************************************************************************************/
458
460 std::cout << "testOpen" << std::endl;
461 _pcieBackendInstance->open();
462 BOOST_CHECK(_pcieBackendInstance->isOpen() == true);
463 // It must always be possible to re-open a backend. It should try to re-connect.
464 _pcieBackendInstance->open();
465 BOOST_CHECK(_pcieBackendInstance->isOpen());
466}
467
468/**********************************************************************************************************************/
469
471 std::cout << "testCreateBackend" << std::endl;
473 BOOST_CHECK_THROW(factoryInstance.createBackend(NON_EXISTING_DEVICE), ChimeraTK::logic_error);
475 _pcieBackendInstance = boost::dynamic_pointer_cast<PcieBackend>(factoryInstance.createBackend(_deviceFileName));
476 BOOST_CHECK(_pcieBackendInstance != nullptr);
478 BOOST_CHECK(_pcieBackendInstance->isOpen() == false);
479
480 // OK, now that we know that basic creation is working let's do some tests of
481 // the specifics of the create function. We use the device interface because
482 // it is much more convenient.
483
484 // There are four situations where the map-file information is coming from
485 // 1. From the dmap file (old way, third column in dmap file)
486 // 2. From the URI (new, recommended, not supported by dmap parser at the
487 // moment)
488 // 3. No map file at all (not supported by the dmap parser at the moment)
489 // 4. Both dmap file and URI contain the information (prints a warning and
490 // takes the one from the dmap file)
491
492 // 1. The original way with map file as third column in the dmap file
493 Device firstDevice;
494 firstDevice.open("PCIE0");
495 // this backend is without module in the register name
496 firstDevice.write<double>("WORD_USER", 48);
497 BOOST_CHECK(true);
498
499 // 2. Creating without map file in the dmap only works by putting an sdm on
500 // creation because we have to bypass the dmap file parser which at the time
501 // of writing this requires a map file as third column
502 Device secondDevice;
503 secondDevice.open("(pci:pcieunidummys6?map=mtcadummy.map)");
504 BOOST_CHECK(secondDevice.read<double>("BOARD/WORD_USER") == 48);
505
506 Device secondDevice2;
507 // try opening same device again.
508 secondDevice2.open("(pci:pcieunidummys6?map=mtcadummy.map)");
509 BOOST_CHECK(secondDevice2.read<double>("BOARD/WORD_USER") == 48);
510
511 // 3. We don't have a map file, so we have to use numerical addressing
512 Device thirdDevice;
513 thirdDevice.open("(pci:pcieunidummys6)");
514 BOOST_CHECK(thirdDevice.read<int32_t>(BAR() / 0 / 0xC) == 48 << 3); // The user register is on bar 0, address 0xC.
515 // We have no fixed point data conversion but 3
516 // fractional bits.
517
518 // 4. This should print a warning. We can't check that, so we just check that
519 // it does work like the other two options.
520 Device fourthDevice;
521 fourthDevice.open("PCIE_DOUBLEMAP");
522 BOOST_CHECK(fourthDevice.read<double>("BOARD/WORD_USER") == 48);
523
524 // close the backend for the following tests. One of the Devices has opened
525 // it...
526 _pcieBackendInstance->close();
527}
528
529/**********************************************************************************************************************/
BackendFactory is a the factory class to create devices.
static BackendFactory & getInstance()
Static function to get an instance of factory.
void setDMapFilePath(std::string dMapFilePath)
This function sets the _DMapFilePath.
boost::shared_ptr< DeviceBackend > createBackend(const std::string &aliasOrUri)
Create a new backend and return the instance as a shared pointer.
bool isOpen() override
Return whether a device has been opened or not.
Class allows to read/write registers from device.
Definition Device.h:39
UserType read(const RegisterPath &registerPathName, const AccessModeFlags &flags=AccessModeFlags({})) const
Inefficient convenience function to read a single-word register without obtaining an accessor.
Definition Device.h:296
void open(std::string const &aliasName)
Open a device by the given alias name from the DMAP file.
Definition Device.cc:58
void write(const RegisterPath &registerPathName, UserType value, const AccessModeFlags &flags=AccessModeFlags({}))
Inefficient convenience function to write a single-word register without obtaining an accessor.
Definition Device.h:317
A class to provide the Pcie device functionality.
Definition PcieBackend.h:17
Exception thrown when a logic error has occured.
Definition Exception.h:51
Exception thrown when a runtime error has occured.
Definition Exception.h:18
PcieBackendTest(std::string const &deviceFileName, unsigned int slot)
void testFailIfBackendClosed()
Test that all functions throw an exception if the backend is not opened.
void testFailIfClosed()
Tests whether openig of the backend works, and that the exception is thrown correctly if the backend ...
static void testConstructor()
A simple test which calls the default constructor and checks that the backend is closed.
PcieBackendTestSuite(std::string const &deviceFileName, unsigned int slot)
RegisterPath BAR()
The numeric_address::BAR() function can be used to directly access registers by numeric addresses,...
const std::string lockfile
std::vector< std::string > usedNodes
#define NON_EXISTING_DEVICE
#define WORD_FIRMWARE_OFFSET
#define DMMY_AS_ASCII
bool init_unit_test()
#define PCIEDEV_TEST_SLOT
#define LLRFDRV_TEST_SLOT
#define PCIEUNI_TEST_SLOT
#define WORD_USER_OFFSET
#define WORD_CLK_CNT_OFFSET
#define WORD_COMPILATION_OFFSET
#define WORD_DUMMY_OFFSET
#define N_WORDS_DMA
#define WORD_ADC_ENA_OFFSET
#define PCIE_UNI_DEVICE