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 <unistd.h>
7#include <thread>
8
10
11#include "uhal/ClientFactory.hpp"
12#include "uhal/Buffers.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
22
23Axi4Lite::MappedFile::MappedFile(const std::string& aPath, size_t aLength, int aProtFlags)
24 : mPath(aPath),
25 mFd(-1),
26 mBar(nullptr),
27 mLength(aLength),
28 mProtFlags(aProtFlags),
29 mLocked(false),
30 mBufferSize(0),
31 mBuffer(nullptr) {}
32
34 if (mBuffer != nullptr) free(mBuffer);
35 close();
36}
37
38const std::string& Axi4Lite::MappedFile::getPath() const { return mPath; }
39
40void Axi4Lite::MappedFile::setPath(const std::string& aPath) { mPath = aPath; }
41void Axi4Lite::MappedFile::setLength(size_t aLength) { mLength = aLength; }
42
44 if (mBar != nullptr) return;
45
46 mFd = ::open(mPath.c_str(), (mProtFlags & PROT_WRITE) ? O_RDWR : O_RDONLY);
47 if (mFd < 0) {
48 return;
49 }
50
51 void* lBar = mmap(nullptr, 4*mLength, mProtFlags, MAP_SHARED, mFd, 0);
52 mBar = (lBar == MAP_FAILED ? nullptr : (uint32_t*)lBar);
53}
54
56 if (mBar != nullptr) munmap(mBar, mLength);
57 mBar = nullptr;
58
59 if (mFd != -1) {
60 if (haveLock())
61 unlock();
62 int rc = ::close(mFd);
63 mFd = -1;
64 if (rc == -1)
65 log (uhal::Error(), "Failed to close file ", uhal::Quote(mPath), "; errno=", uhal::Integer(errno), ", meaning ", uhal::Quote (strerror(errno)));
66 }
67}
68
69
70void Axi4Lite::MappedFile::createBuffer(const size_t aNrBytes) {
71 if (mBuffer != NULL) {
72 if (mBufferSize >= aNrBytes)
73 return;
74 else {
75 free(mBuffer);
76 mBuffer = NULL;
77 mBufferSize = 0;
78 }
79 }
80
81 posix_memalign((void**)&mBuffer, 4096 /*alignment*/, aNrBytes + 4096);
82 if (mBuffer == NULL) {
83 exception::Axi4LiteCommunicationError lExc;
84 log(lExc, "Failed to allocate ", Integer(aNrBytes + 4096),
85 " bytes in Axi4Lite::MappedFile::createBuffer");
86 throw lExc;
87 }
88
89 mBufferSize = aNrBytes + 4096;
90}
91
92void Axi4Lite::MappedFile::read(const uint32_t aAddr, const uint32_t aNrWords,
93 std::vector<uint32_t>& aValues) {
94 if (mBar == nullptr) open();
95
96 for (size_t i(0); i < aNrWords; ++i) {
97 aValues.push_back(le32toh(mBar[aAddr + i]));
98 }
99}
100
101void Axi4Lite::MappedFile::write(const uint32_t aAddr, const std::vector<uint32_t>& aValues) {
102 write(aAddr, reinterpret_cast<const uint8_t*>(aValues.data()),
103 4 * aValues.size());
104}
105
106void Axi4Lite::MappedFile::write(const uint32_t aAddr, const uint8_t* const aPtr,
107 const size_t aNrBytes) {
108 if (mBar == nullptr) open();
109
110 assert((aNrBytes % 4) == 0);
111 uint32_t lNrWordsData = aNrBytes / 4;
112
113 auto lPtr32 = reinterpret_cast<const uint32_t*>(aPtr);
114 for (size_t i(0); i < lNrWordsData; ++i) {
115 mBar[aAddr + i] = lPtr32[i];
116 }
117}
118
119void Axi4Lite::MappedFile::write(const uint32_t aAddr,
120 const std::vector<std::pair<const uint8_t*, size_t> >& aData) {
121 if (mBar == nullptr) open();
122
123 size_t lNrBytes = 0;
124 for (size_t i = 0; i < aData.size(); i++) lNrBytes += aData.at(i).second;
125
126 assert((lNrBytes % 4) == 0);
127 size_t lNrWords = lNrBytes/4;
128
129 createBuffer(lNrBytes);
130
131 size_t k(0);
132 for (size_t i = 0; i < aData.size(); ++i) {
133 for (size_t j = 0; j < aData.at(i).second; ++j) {
134 mBuffer[k] = aData.at(i).first[j];
135 ++k;
136 }
137 }
138
139 auto mBuffer32b = reinterpret_cast<const uint32_t*>(mBuffer);
140
141 for (size_t i(0); i<lNrWords; ++i) {
142 mBar[aAddr + i] = htole32(mBuffer32b[i]);
143 }
144 // std::memcpy(mBar + aAddr, mBuffer, lNrBytes);
145
146}
147
148bool Axi4Lite::MappedFile::haveLock() const { return mLocked; }
149
151 if (flock(mFd, LOCK_EX) == -1) {
152 ipc::exception::MutexError lExc;
153 log(lExc, "Failed to lock device file ", uhal::Quote(mPath),
154 "; errno=", Integer(errno), ", meaning ", uhal::Quote(strerror(errno)));
155 throw lExc;
156 }
157 mLocked = true;
158}
159
161 if (flock(mFd, LOCK_UN) == -1) {
162 log(uhal::Warning(), "Failed to unlock device file ", uhal::Quote(mPath),
163 "; errno=", Integer(errno), ", meaning ", uhal::Quote(strerror(errno)));
164 } else
165 mLocked = false;
166}
167
168// Axi4Lite Transport
169std::string Axi4Lite::getSharedMemName(const std::string& aPath)
170{
171 std::string lSanitizedPath(aPath);
172 std::replace(lSanitizedPath.begin(), lSanitizedPath.end(), '/', ':');
173
174 return "/uhal::ipbusaxi4lite-2.0::" + lSanitizedPath;
175}
176
177std::string Axi4Lite::getDevicePath(const uhal::URI& aUri) {
178
179 std::string lPath = aUri.mHostname;
180
181 auto it = std::find_if( aUri.mArguments.begin(), aUri.mArguments.end(),
182 [](const std::pair<std::string, std::string>& element){ return element.first == "dev";} );
183
184 if ( it != aUri.mArguments.end() ) {
185 lPath += "/"+it->second;
186 }
187
188 return lPath;
189}
190
191Axi4Lite::Axi4Lite(const std::string& aId, const uhal::URI& aUri)
192 : IPbus<2, 0>(aId, aUri),
193 mConnected(false),
194 mMappedFile(getDevicePath(aUri), 64, PROT_WRITE),
197 mMaxInFlight(0),
198 mPageSize(0),
203
204 mSleepDuration = std::chrono::microseconds(50);
205
206 }
207
211
212
213
214void Axi4Lite::implementDispatch ( std::shared_ptr< uhal::Buffers > aBuffers )
215{
216 log(uhal::Debug(), "Axi4Lite client (URI: ", uhal::Quote(uri()), ") : implementDispatch method called");
217
218 if ( ! mConnected )
219 connect();
220
221 if ( mReplyQueue.size() == mMaxInFlight )
222 read();
223 write(aBuffers);
224}
225
226
228{
229 log(uhal::Debug(), "Axi4Lite client (URI: ", uhal::Quote(uri()), ") : Flush method called");
230 while ( !mReplyQueue.empty() )
231 read();
232
234
235 IPCScopedLock_t lLockGuard(*mIPCMutex);
236 mIPCMutex->endSession();
237}
238
239
241{
242 log(uhal::Notice(), "Axi4Lite client ", uhal::Quote(id()), " (URI: ", uhal::Quote(uri()), ") : closing device files since exception detected");
243
244 ClientInterface::returnBufferToPool ( mReplyQueue );
245
247
248 disconnect();
249
250 InnerProtocol::dispatchExceptionHandler();
251}
252
253
255{
256 if ( ! mConnected )
257 connect();
258
259 return mMaxPacketSize * 4;
260}
261
262
264{
265 if ( ! mConnected )
266 connect();
267
268 return mMaxPacketSize * 4;
269}
270
271
273{
274 IPCScopedLock_t lLockGuard(*mIPCMutex);
275 connect(lLockGuard);
276}
277
279{
280 // Read current value of session counter when reading status info from FPGA
281 // (So that can check whether this info is up-to-date later on, when sending next request packet)
283 mIPCSessionCount = mIPCMutex->getCounter();
284
285 log ( uhal::Debug() , "Axi4Lite client is opening device file " , uhal::Quote ( mMappedFile.getPath() ) , " (device-to-client)" );
286
287 // Minimal mapping to read the
290 std::vector<uint32_t> lStats;
291 mMappedFile.read(0, 4, lStats);
293 aGuard.unlock();
294
295 mNumberOfPages = lStats.at(0);
296 if ( (mMaxInFlight == 0) or (mMaxInFlight > mNumberOfPages) )
298 mPageSize = lStats.at(1);
299 if ( (mMaxPacketSize == 0) or (mMaxPacketSize >= mPageSize) )
301 mIndexNextPage = lStats.at(2);
302 mPublishedReplyPageCount = lStats.at(3);
304
305 //
306 constexpr uint32_t lSafetyMargin(4096);
307 // Set the memory mapping range to a value commensurate to the memory available in firmware
310
311 mConnected=true;
312
313 log ( uhal::Info() , "Axi4Lite client connected to device at ", uhal::Quote(mMappedFile.getPath()), ", FPGA has ", Integer(mNumberOfPages), " pages, each of size ", Integer(mPageSize), " words, index ", Integer(mIndexNextPage), " should be filled next" );
314
315}
316
318{
320 mConnected = false;
321}
322
323void Axi4Lite::write(const std::shared_ptr<uhal::Buffers>& aBuffers)
324{
325 if (not mMappedFile.haveLock()) {
327
328 IPCScopedLock_t lGuard(*mIPCMutex);
329 mIPCMutex->startSession();
331
332 // If these two numbers don't match, another client/process has sent packets
333 // more recently than this client has, so must re-read status info
334 if (mIPCExternalSessionActive or (mIPCMutex->getCounter() != mIPCSessionCount)) {
335 connect(lGuard);
336 }
337 }
338
339 log (uhal::Info(), "Axi4Lite client ", uhal::Quote(id()), " (URI: ", uhal::Quote(uri()), ") : writing ", Integer(aBuffers->sendCounter() / 4), "-word packet to page ", Integer(mIndexNextPage), " in ", uhal::Quote(mMappedFile.getPath()));
340
341 const uint32_t lHeaderWord = (0x10000 | (((aBuffers->sendCounter() / 4) - 1) & 0xFFFF));
342 std::vector<std::pair<const uint8_t*, size_t> > lDataToWrite;
343 lDataToWrite.push_back( std::make_pair(reinterpret_cast<const uint8_t*>(&lHeaderWord), sizeof lHeaderWord) );
344 lDataToWrite.push_back( std::make_pair(aBuffers->getSendBuffer(), aBuffers->sendCounter()) );
345
346 IPCScopedLock_t lGuard(*mIPCMutex);
347 mMappedFile.write(mIndexNextPage * mPageSize, lDataToWrite);
348 log (uhal::Debug(), "Wrote " , Integer((aBuffers->sendCounter() / 4) + 1), " 32-bit words at address " , Integer(mIndexNextPage * mPageSize), " ... ", PacketFmt(lDataToWrite));
349
351 mReplyQueue.push_back(aBuffers);
352}
353
354
356{
357 const size_t lPageIndexToRead = (mIndexNextPage - mReplyQueue.size() + mNumberOfPages) % mNumberOfPages;
358 SteadyClock_t::time_point lStartTime = SteadyClock_t::now();
359
361 {
362 uint32_t lHwPublishedPageCount = 0x0;
363
364 std::vector<uint32_t> lValues;
365 while ( true ) {
366 // FIXME : Improve by simply adding fileWrite method that takes uint32_t ref as argument (or returns uint32_t)
367 IPCScopedLock_t lGuard(*mIPCMutex);
368 mMappedFile.read(0, 4, lValues);
369 lHwPublishedPageCount = lValues.at(3);
370 log (uhal::Debug(), "Read status info from addr 0 (", Integer(lValues.at(0)), ", ", Integer(lValues.at(1)), ", ", Integer(lValues.at(2)), ", ", Integer(lValues.at(3)), "): ", PacketFmt((const uint8_t*)lValues.data(), 4 * lValues.size()));
371
372 if (lHwPublishedPageCount != mPublishedReplyPageCount) {
373 mPublishedReplyPageCount = lHwPublishedPageCount;
374 break;
375 }
376 // FIXME: Throw if published page count is invalid number
377
378 if (SteadyClock_t::now() - lStartTime > std::chrono::microseconds(getBoostTimeoutPeriod().total_microseconds())) {
379 exception::Axi4LiteTimeout lExc;
380 log(lExc, "Next page (index ", Integer(lPageIndexToRead), " count ", Integer(mPublishedReplyPageCount+1), ") of Axi4Lite device '" + mMappedFile.getPath() + "' is not ready after timeout period");
381 throw lExc;
382 }
383
384 log(uhal::Debug(), "Axi4Lite client ", uhal::Quote(id()), " (URI: ", uhal::Quote(uri()), ") : Trying to read page index ", Integer(lPageIndexToRead), " = count ", Integer(mReadReplyPageCount+1), "; published page count is ", Integer(lHwPublishedPageCount), "; sleeping for ", mSleepDuration.count(), "us");
385 if (mSleepDuration > std::chrono::microseconds(0))
386 std::this_thread::sleep_for( mSleepDuration );
387 lValues.clear();
388 }
389
390 log(uhal::Info(), "Axi4Lite client ", uhal::Quote(id()), " (URI: ", uhal::Quote(uri()), ") : Reading page ", Integer(lPageIndexToRead), " (published count ", Integer(lHwPublishedPageCount), ", surpasses required, ", Integer(mReadReplyPageCount + 1), ")");
391
392 }
394
395 // PART 1 : Read the page
396 std::shared_ptr<uhal::Buffers> lBuffers = mReplyQueue.front();
397 mReplyQueue.pop_front();
398
399 uint32_t lNrWordsToRead(lBuffers->replyCounter() >> 2);
400 lNrWordsToRead += 1;
401
402 std::vector<uint32_t> lPageContents;
403 IPCScopedLock_t lGuard(*mIPCMutex);
404 mMappedFile.read(4 + lPageIndexToRead * mPageSize, lNrWordsToRead , lPageContents);
405 lGuard.unlock();
406 log (uhal::Debug(), "Read " , Integer(lNrWordsToRead), " 32-bit words from address " , Integer(4 + lPageIndexToRead * 4 * mPageSize), " ... ", PacketFmt((const uint8_t*)lPageContents.data(), 4 * lPageContents.size()));
407
408 // PART 2 : Transfer to reply buffer
409 const std::deque< std::pair< uint8_t* , uint32_t > >& lReplyBuffers ( lBuffers->getReplyBuffer() );
410 size_t lNrWordsInPacket = (lPageContents.at(0) >> 16) + (lPageContents.at(0) & 0xFFFF);
411 if (lNrWordsInPacket != (lBuffers->replyCounter() >> 2))
412 log (uhal::Warning(), "Expected reply packet to contain ", Integer(lBuffers->replyCounter() >> 2), " words, but it actually contains ", Integer(lNrWordsInPacket), " words");
413
414 size_t lNrBytesCopied = 0;
415 for (const auto& lBuffer: lReplyBuffers)
416 {
417 // Don't copy more of page than was written to, for cases when less data received than expected
418 if ( lNrBytesCopied >= 4*lNrWordsInPacket)
419 break;
420
421 size_t lNrBytesToCopy = std::min( lBuffer.second , uint32_t(4*lNrWordsInPacket - lNrBytesCopied) );
422 memcpy ( lBuffer.first, &lPageContents.at(1 + (lNrBytesCopied / 4)), lNrBytesToCopy );
423 lNrBytesCopied += lNrBytesToCopy;
424 }
425
426
427 // PART 3 : Validate the packet contents
428 uhal::exception::exception* lExc = NULL;
429 try
430 {
431 lExc = ClientInterface::validate ( lBuffers );
432 }
433 catch ( uhal::exception::exception& aExc )
434 {
435 uhal::exception::ValidationError lExc2;
436 log ( lExc2 , "Exception caught during reply validation for Axi4Lite device with URI " , uhal::Quote ( this->uri() ) , "; what returned: " , uhal::Quote ( aExc.what() ) );
437 throw lExc2;
438 }
439
440 if (lExc != NULL)
441 lExc->throwAsDerivedType();
442}
443
444} // namespace uhal
void close()
Unmap and close file.
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::chrono::microseconds mSleepDuration
static std::string getSharedMemName(const std::string &)
ipc::SharedMemObject< IPCMutex_t > mIPCMutex
void connect()
Set up the connection to the device.
virtual void Flush()
Concrete implementation of the synchronization function to block until all buffers have been sent,...
static std::string getDevicePath(const uhal::URI &aUri)
std::unique_lock< IPCMutex_t > IPCScopedLock_t
std::deque< std::shared_ptr< uhal::Buffers > > mReplyQueue
The list of buffers still awaiting a reply.
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...
void implementDispatch(std::shared_ptr< uhal::Buffers > aBuffers)