DUNE-DAQ
DUNE Trigger and Data Acquisition software
Loading...
Searching...
No Matches
ProtocolAxi4Lite.cpp
Go to the documentation of this file.
2
3#include <fcntl.h>
4#include <sys/file.h>
5#include <sys/mman.h>
6#include <thread>
7#include <unistd.h>
8
10
11#include "uhal/Buffers.hpp"
12#include "uhal/ClientFactory.hpp"
13
14using uhal::Integer;
15
16UHAL_REGISTER_EXTERNAL_CLIENT(uhallibs::Axi4Lite, "ipbusaxi4lite-2.0", "AXI4 Lite IPBus client")
17// Syntax: ipbus-qdma-axi4l-2.0:///sys/bus/pci/devices/0000:<bus>:<dev>.<func>/resource<bar#>
18// Syntax: ipbusaxi4lite-2.0://<bus>:<dev>.<func>/resource<bar#>
19//
20namespace uhallibs {
21
22Axi4Lite::MappedFile::MappedFile(const std::string& aPath, size_t aLength, int aProtFlags)
23 : mPath(aPath)
24 , mFd(-1)
25 , mBar(nullptr)
26 , mLength(aLength)
27 , mProtFlags(aProtFlags)
28 , mLocked(false)
29 , mBufferSize(0)
30 , mBuffer(nullptr)
31{
32}
33
35{
36 if (mBuffer != nullptr)
37 free(mBuffer);
38 close();
39}
40
41const std::string&
43{
44 return mPath;
45}
46
47void
48Axi4Lite::MappedFile::setPath(const std::string& aPath)
49{
50 mPath = aPath;
51}
52void
54{
55 mLength = aLength;
56}
57
58void
60{
61 if (mBar != nullptr)
62 return;
63
64 mFd = ::open(mPath.c_str(), (mProtFlags & PROT_WRITE) ? O_RDWR : O_RDONLY);
65 if (mFd < 0) {
66 return;
67 }
68
69 void* lBar = mmap(nullptr, 4 * mLength, mProtFlags, MAP_SHARED, mFd, 0);
70 mBar = (lBar == MAP_FAILED ? nullptr : (uint32_t*)lBar);
71}
72
73void
75{
76 if (mBar != nullptr)
77 munmap(mBar, mLength);
78 mBar = nullptr;
79
80 if (mFd != -1) {
81 if (haveLock())
82 unlock();
83 int rc = ::close(mFd);
84 mFd = -1;
85 if (rc == -1)
86 log(uhal::Error(),
87 "Failed to close file ",
88 uhal::Quote(mPath),
89 "; errno=",
90 uhal::Integer(errno),
91 ", meaning ",
92 uhal::Quote(strerror(errno)));
93 }
94}
95
96void
98{
99 if (mBuffer != NULL) {
100 if (mBufferSize >= aNrBytes)
101 return;
102 else {
103 free(mBuffer);
104 mBuffer = NULL;
105 mBufferSize = 0;
106 }
107 }
108
109 posix_memalign((void**)&mBuffer, 4096 /*alignment*/, aNrBytes + 4096);
110 if (mBuffer == NULL) {
111 exception::Axi4LiteCommunicationError lExc;
112 log(lExc, "Failed to allocate ", Integer(aNrBytes + 4096), " bytes in Axi4Lite::MappedFile::createBuffer");
113 throw lExc;
114 }
115
116 mBufferSize = aNrBytes + 4096;
117}
118
119void
120Axi4Lite::MappedFile::read(const uint32_t aAddr, const uint32_t aNrWords, std::vector<uint32_t>& aValues)
121{
122 if (mBar == nullptr)
123 open();
124
125 for (size_t i(0); i < aNrWords; ++i) {
126 aValues.push_back(le32toh(mBar[aAddr + i]));
127 }
128}
129
130void
131Axi4Lite::MappedFile::write(const uint32_t aAddr, const std::vector<uint32_t>& aValues)
132{
133 write(aAddr, reinterpret_cast<const uint8_t*>(aValues.data()), 4 * aValues.size());
134}
135
136void
137Axi4Lite::MappedFile::write(const uint32_t aAddr, const uint8_t* const aPtr, const size_t aNrBytes)
138{
139 if (mBar == nullptr)
140 open();
141
142 assert((aNrBytes % 4) == 0);
143 uint32_t lNrWordsData = aNrBytes / 4;
144
145 auto lPtr32 = reinterpret_cast<const uint32_t*>(aPtr);
146 for (size_t i(0); i < lNrWordsData; ++i) {
147 mBar[aAddr + i] = lPtr32[i];
148 }
149}
150
151void
152Axi4Lite::MappedFile::write(const uint32_t aAddr, const std::vector<std::pair<const uint8_t*, size_t>>& aData)
153{
154 if (mBar == nullptr)
155 open();
156
157 size_t lNrBytes = 0;
158 for (size_t i = 0; i < aData.size(); i++)
159 lNrBytes += aData.at(i).second;
160
161 assert((lNrBytes % 4) == 0);
162 size_t lNrWords = lNrBytes / 4;
163
164 createBuffer(lNrBytes);
165
166 size_t k(0);
167 for (size_t i = 0; i < aData.size(); ++i) {
168 for (size_t j = 0; j < aData.at(i).second; ++j) {
169 mBuffer[k] = aData.at(i).first[j];
170 ++k;
171 }
172 }
173
174 auto mBuffer32b = reinterpret_cast<const uint32_t*>(mBuffer);
175
176 for (size_t i(0); i < lNrWords; ++i) {
177 mBar[aAddr + i] = htole32(mBuffer32b[i]);
178 }
179 // std::memcpy(mBar + aAddr, mBuffer, lNrBytes);
180}
181
182bool
184{
185 return mLocked;
186}
187
188void
190{
191 if (flock(mFd, LOCK_EX) == -1) {
192 ipc::exception::MutexError lExc;
193 log(lExc,
194 "Failed to lock device file ",
195 uhal::Quote(mPath),
196 "; errno=",
197 Integer(errno),
198 ", meaning ",
199 uhal::Quote(strerror(errno)));
200 throw lExc;
201 }
202 mLocked = true;
203}
204
205void
207{
208 if (flock(mFd, LOCK_UN) == -1) {
209 log(uhal::Warning(),
210 "Failed to unlock device file ",
211 uhal::Quote(mPath),
212 "; errno=",
213 Integer(errno),
214 ", meaning ",
215 uhal::Quote(strerror(errno)));
216 } else
217 mLocked = false;
218}
219
220// Axi4Lite Transport
221std::string
222Axi4Lite::getSharedMemName(const std::string& aPath)
223{
224 std::string lSanitizedPath(aPath);
225 std::replace(lSanitizedPath.begin(), lSanitizedPath.end(), '/', ':');
226
227 return "/uhal::ipbusaxi4lite-2.0::" + lSanitizedPath;
228}
229
230std::string
231Axi4Lite::getDevicePath(const uhal::URI& aUri)
232{
233
234 std::string lPath = aUri.mHostname;
235
236 auto it = std::find_if(aUri.mArguments.begin(),
237 aUri.mArguments.end(),
238 [](const std::pair<std::string, std::string>& element) { return element.first == "dev"; });
239
240 if (it != aUri.mArguments.end()) {
241 lPath += "/" + it->second;
242 }
243
244 return lPath;
245}
246
247Axi4Lite::Axi4Lite(const std::string& aId, const uhal::URI& aUri)
248 : IPbus<2, 0>(aId, aUri)
249 , mConnected(false)
250 , mMappedFile(getDevicePath(aUri), 64, PROT_WRITE)
252 , mNumberOfPages(0)
253 , mMaxInFlight(0)
254 , mPageSize(0)
255 , mMaxPacketSize(0)
256 , mIndexNextPage(0)
259{
260
261 mSleepDuration = std::chrono::microseconds(50);
262}
263
265
266void
267Axi4Lite::implementDispatch(std::shared_ptr<uhal::Buffers> aBuffers)
268{
269 log(uhal::Debug(), "Axi4Lite client (URI: ", uhal::Quote(uri()), ") : implementDispatch method called");
270
271 if (!mConnected)
272 connect();
273
274 if (mReplyQueue.size() == mMaxInFlight)
275 read();
276 write(aBuffers);
277}
278
279void
281{
282 log(uhal::Debug(), "Axi4Lite client (URI: ", uhal::Quote(uri()), ") : Flush method called");
283 while (!mReplyQueue.empty())
284 read();
285
287
288 IPCScopedLock_t lLockGuard(*mIPCMutex);
289 mIPCMutex->endSession();
290}
291
292void
294{
295 log(uhal::Notice(),
296 "Axi4Lite client ",
297 uhal::Quote(id()),
298 " (URI: ",
299 uhal::Quote(uri()),
300 ") : closing device files since exception detected");
301
302 ClientInterface::returnBufferToPool(mReplyQueue);
303
305
306 disconnect();
307
308 InnerProtocol::dispatchExceptionHandler();
309}
310
311uint32_t
313{
314 if (!mConnected)
315 connect();
316
317 return mMaxPacketSize * 4;
318}
319
320uint32_t
322{
323 if (!mConnected)
324 connect();
325
326 return mMaxPacketSize * 4;
327}
328
329void
331{
332 IPCScopedLock_t lLockGuard(*mIPCMutex);
333 connect(lLockGuard);
334}
335
336void
338{
339 // Read current value of session counter when reading status info from FPGA
340 // (So that can check whether this info is up-to-date later on, when sending next request packet)
342 mIPCSessionCount = mIPCMutex->getCounter();
343
344 log(uhal::Debug(),
345 "Axi4Lite client is opening device file ",
346 uhal::Quote(mMappedFile.getPath()),
347 " (device-to-client)");
348
349 // Minimal mapping to read the
352 std::vector<uint32_t> lStats;
353 mMappedFile.read(0, 4, lStats);
355 aGuard.unlock();
356
357 mNumberOfPages = lStats.at(0);
358 if ((mMaxInFlight == 0) or (mMaxInFlight > mNumberOfPages))
360 mPageSize = lStats.at(1);
361 if ((mMaxPacketSize == 0) or (mMaxPacketSize >= mPageSize))
363 mIndexNextPage = lStats.at(2);
364 mPublishedReplyPageCount = lStats.at(3);
366
367 //
368 constexpr uint32_t lSafetyMargin(4096);
369 // Set the memory mapping range to a value commensurate to the memory available in firmware
370 mMappedFile.setLength(mNumberOfPages * mPageSize + 4 + lSafetyMargin);
372
373 mConnected = true;
374
375 log(uhal::Info(),
376 "Axi4Lite client connected to device at ",
377 uhal::Quote(mMappedFile.getPath()),
378 ", FPGA has ",
379 Integer(mNumberOfPages),
380 " pages, each of size ",
381 Integer(mPageSize),
382 " words, index ",
383 Integer(mIndexNextPage),
384 " should be filled next");
385}
386
387void
389{
391 mConnected = false;
392}
393
394void
395Axi4Lite::write(const std::shared_ptr<uhal::Buffers>& aBuffers)
396{
397 if (not mMappedFile.haveLock()) {
399
400 IPCScopedLock_t lGuard(*mIPCMutex);
401 mIPCMutex->startSession();
403
404 // If these two numbers don't match, another client/process has sent packets
405 // more recently than this client has, so must re-read status info
406 if (mIPCExternalSessionActive or (mIPCMutex->getCounter() != mIPCSessionCount)) {
407 connect(lGuard);
408 }
409 }
410
411 log(uhal::Info(),
412 "Axi4Lite client ",
413 uhal::Quote(id()),
414 " (URI: ",
415 uhal::Quote(uri()),
416 ") : writing ",
417 Integer(aBuffers->sendCounter() / 4),
418 "-word packet to page ",
419 Integer(mIndexNextPage),
420 " in ",
421 uhal::Quote(mMappedFile.getPath()));
422
423 const uint32_t lHeaderWord = (0x10000 | (((aBuffers->sendCounter() / 4) - 1) & 0xFFFF));
424 std::vector<std::pair<const uint8_t*, size_t>> lDataToWrite;
425 lDataToWrite.push_back(std::make_pair(reinterpret_cast<const uint8_t*>(&lHeaderWord), sizeof lHeaderWord));
426 lDataToWrite.push_back(std::make_pair(aBuffers->getSendBuffer(), aBuffers->sendCounter()));
427
428 IPCScopedLock_t lGuard(*mIPCMutex);
429 mMappedFile.write(mIndexNextPage * mPageSize, lDataToWrite);
430 log(uhal::Debug(),
431 "Wrote ",
432 Integer((aBuffers->sendCounter() / 4) + 1),
433 " 32-bit words at address ",
434 Integer(mIndexNextPage * mPageSize),
435 " ... ",
436 PacketFmt(lDataToWrite));
437
439 mReplyQueue.push_back(aBuffers);
440}
441
442void
444{
445 const size_t lPageIndexToRead = (mIndexNextPage - mReplyQueue.size() + mNumberOfPages) % mNumberOfPages;
446 SteadyClock_t::time_point lStartTime = SteadyClock_t::now();
447
449 uint32_t lHwPublishedPageCount = 0x0;
450
451 std::vector<uint32_t> lValues;
452 while (true) {
453 // FIXME : Improve by simply adding fileWrite method that takes uint32_t ref as argument (or returns uint32_t)
454 IPCScopedLock_t lGuard(*mIPCMutex);
455 mMappedFile.read(0, 4, lValues);
456 lHwPublishedPageCount = lValues.at(3);
457 log(uhal::Debug(),
458 "Read status info from addr 0 (",
459 Integer(lValues.at(0)),
460 ", ",
461 Integer(lValues.at(1)),
462 ", ",
463 Integer(lValues.at(2)),
464 ", ",
465 Integer(lValues.at(3)),
466 "): ",
467 PacketFmt((const uint8_t*)lValues.data(), 4 * lValues.size()));
468
469 if (lHwPublishedPageCount != mPublishedReplyPageCount) {
470 mPublishedReplyPageCount = lHwPublishedPageCount;
471 break;
472 }
473 // FIXME: Throw if published page count is invalid number
474
475 if (SteadyClock_t::now() - lStartTime > std::chrono::microseconds(getBoostTimeoutPeriod().total_microseconds())) {
476 exception::Axi4LiteTimeout lExc;
477 log(lExc,
478 "Next page (index ",
479 Integer(lPageIndexToRead),
480 " count ",
481 Integer(mPublishedReplyPageCount + 1),
482 ") of Axi4Lite device '" + mMappedFile.getPath() + "' is not ready after timeout period");
483 throw lExc;
484 }
485
486 log(uhal::Debug(),
487 "Axi4Lite client ",
488 uhal::Quote(id()),
489 " (URI: ",
490 uhal::Quote(uri()),
491 ") : Trying to read page index ",
492 Integer(lPageIndexToRead),
493 " = count ",
494 Integer(mReadReplyPageCount + 1),
495 "; published page count is ",
496 Integer(lHwPublishedPageCount),
497 "; sleeping for ",
498 mSleepDuration.count(),
499 "us");
500 if (mSleepDuration > std::chrono::microseconds(0))
501 std::this_thread::sleep_for(mSleepDuration);
502 lValues.clear();
503 }
504
505 log(uhal::Info(),
506 "Axi4Lite client ",
507 uhal::Quote(id()),
508 " (URI: ",
509 uhal::Quote(uri()),
510 ") : Reading page ",
511 Integer(lPageIndexToRead),
512 " (published count ",
513 Integer(lHwPublishedPageCount),
514 ", surpasses required, ",
515 Integer(mReadReplyPageCount + 1),
516 ")");
517 }
519
520 // PART 1 : Read the page
521 std::shared_ptr<uhal::Buffers> lBuffers = mReplyQueue.front();
522 mReplyQueue.pop_front();
523
524 uint32_t lNrWordsToRead(lBuffers->replyCounter() >> 2);
525 lNrWordsToRead += 1;
526
527 std::vector<uint32_t> lPageContents;
528 IPCScopedLock_t lGuard(*mIPCMutex);
529 mMappedFile.read(4 + lPageIndexToRead * mPageSize, lNrWordsToRead, lPageContents);
530 lGuard.unlock();
531 log(uhal::Debug(),
532 "Read ",
533 Integer(lNrWordsToRead),
534 " 32-bit words from address ",
535 Integer(4 + lPageIndexToRead * 4 * mPageSize),
536 " ... ",
537 PacketFmt((const uint8_t*)lPageContents.data(), 4 * lPageContents.size()));
538
539 // PART 2 : Transfer to reply buffer
540 const std::deque<std::pair<uint8_t*, uint32_t>>& lReplyBuffers(lBuffers->getReplyBuffer());
541 size_t lNrWordsInPacket = (lPageContents.at(0) >> 16) + (lPageContents.at(0) & 0xFFFF);
542 if (lNrWordsInPacket != (lBuffers->replyCounter() >> 2))
543 log(uhal::Warning(),
544 "Expected reply packet to contain ",
545 Integer(lBuffers->replyCounter() >> 2),
546 " words, but it actually contains ",
547 Integer(lNrWordsInPacket),
548 " words");
549
550 size_t lNrBytesCopied = 0;
551 for (const auto& lBuffer : lReplyBuffers) {
552 // Don't copy more of page than was written to, for cases when less data received than expected
553 if (lNrBytesCopied >= 4 * lNrWordsInPacket)
554 break;
555
556 size_t lNrBytesToCopy = std::min(lBuffer.second, uint32_t(4 * lNrWordsInPacket - lNrBytesCopied));
557 memcpy(lBuffer.first, &lPageContents.at(1 + (lNrBytesCopied / 4)), lNrBytesToCopy);
558 lNrBytesCopied += lNrBytesToCopy;
559 }
560
561 // PART 3 : Validate the packet contents
562 uhal::exception::exception* lExc = NULL;
563 try {
564 lExc = ClientInterface::validate(lBuffers);
565 } catch (uhal::exception::exception& aExc) {
566 uhal::exception::ValidationError lExc2;
567 log(lExc2,
568 "Exception caught during reply validation for Axi4Lite device with URI ",
569 uhal::Quote(this->uri()),
570 "; what returned: ",
571 uhal::Quote(aExc.what()));
572 throw lExc2;
573 }
574
575 if (lExc != NULL)
576 lExc->throwAsDerivedType();
577}
578
579} // namespace uhal
void close()
Unmap and close file.
MappedFile(const std::string &aPath, size_t aLength, int aProtFlags=PROT_WRITE)
void open()
Open bus file and map it to memory.
void setPath(const std::string &aPath)
void write(const uint32_t aAddr, const std::vector< uint32_t > &aValues)
void createBuffer(const size_t aNrBytes)
Create a local buffer.
const std::string & getPath() const
void read(const uint32_t aAddr, const uint32_t aNrWords, std::vector< uint32_t > &aValues)
Transport protocol to transfer an IPbus buffer over Axi4Lite mapped in a 32b address space.
virtual ~Axi4Lite()
Destructor.
std::deque< std::shared_ptr< uhal::Buffers > > mReplyQueue
The list of buffers still awaiting a reply.
std::chrono::microseconds mSleepDuration
static std::string getSharedMemName(const std::string &)
ipc::SharedMemObject< IPCMutex_t > mIPCMutex
void connect()
Set up the connection to the device.
static std::string getDevicePath(const uhal::URI &aUri)
void disconnect()
Close the connection to the device.
virtual void dispatchExceptionHandler()
Function which tidies up this protocol layer in the event of an exception.
Axi4Lite(const std::string &aId, const uhal::URI &aUri)
void write(const std::shared_ptr< uhal::Buffers > &aBuffers)
Write request packet to next page in host-to-FPGA device file.
void read()
Read next pending reply packet from appropriate page of FPGA-to-host device file, and validate conten...
std::unique_lock< IPCMutex_t > IPCScopedLock_t
void implementDispatch(std::shared_ptr< uhal::Buffers > aBuffers)
PacketFmt(const uint8_t *const, const size_t)
Definition formatters.cpp:7