ChimeraTK-DeviceAccess 03.25.00
Loading...
Searching...
No Matches
PcieBackend.cc
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
2// SPDX-License-Identifier: LGPL-3.0-or-later
3
4#include "PcieBackend.h"
5
6// the io constants and struct for the driver
7#include "llrfdrv_io_compat.h"
8#include "pciedev_io_compat.h"
9#include "pcieuni_io_compat.h"
10
11#include <sys/ioctl.h>
12
13#include <boost/bind/bind.hpp>
14#include <boost/shared_ptr.hpp>
15
16#include <fcntl.h>
17#include <unistd.h>
18
19#include <cerrno>
20#include <cstdio>
21#include <cstring>
22#include <iostream>
23#include <sstream>
24#include <utility>
25
26namespace ChimeraTK {
27
28 PcieBackend::PcieBackend(std::string deviceNodeName, const std::string& mapFileName)
29 : NumericAddressedBackend(mapFileName), _deviceID(0), _ioctlPhysicalSlot(0), _ioctlDriverVersion(0), _ioctlDMA(0),
30 _deviceNodeName(std::move(deviceNodeName)) {}
31
35
37#ifdef _DEBUG
38 std::cout << "open pcie dev" << std::endl;
39#endif
40 if(_opened) {
41 if(checkConnection()) return;
42 ::close(_deviceID);
43 }
44 _deviceID = ::open(_deviceNodeName.c_str(), O_RDWR);
45 if(_deviceID < 0) {
46 throw ChimeraTK::runtime_error(createErrorStringWithErrnoText("Cannot open device: "));
47 }
48
49 determineDriverAndConfigureIoctl();
50
52 }
53
54 void PcieBackend::determineDriverAndConfigureIoctl() {
55 // determine the driver by trying the physical slot ioctl
56 device_ioctrl_data ioctlData = {0, 0, 0, 0};
57
58 if(ioctl(_deviceID, PCIEDEV_PHYSICAL_SLOT, &ioctlData) >= 0) {
59 // it's the pciedev driver
60 _ioctlPhysicalSlot = PCIEDEV_PHYSICAL_SLOT;
61 _ioctlDriverVersion = PCIEDEV_DRIVER_VERSION;
62 _ioctlDMA = PCIEDEV_READ_DMA;
63 _readDMAFunction = [&](uint8_t bar, uint32_t address, int32_t* data, size_t sizeInBytes) {
64 PcieBackend::readDMAViaIoctl(bar, address, data, sizeInBytes);
65 };
66 _writeFunction = [&](uint8_t bar, uint32_t address, int32_t const* data, size_t sizeInBytes) {
67 writeWithStruct(bar, address, data, sizeInBytes);
68 };
69 _readFunction = [&](uint8_t bar, uint32_t address, int32_t* data, size_t sizeInBytes) {
70 readWithStruct(bar, address, data, sizeInBytes);
71 };
72 return;
73 }
74
75 if(ioctl(_deviceID, LLRFDRV_PHYSICAL_SLOT, &ioctlData) >= 0) {
76 // it's the llrf driver
77 _ioctlPhysicalSlot = LLRFDRV_PHYSICAL_SLOT;
78 _ioctlDriverVersion = LLRFDRV_DRIVER_VERSION;
79 _ioctlDMA = 0;
80 _readDMAFunction = [&](uint8_t bar, uint32_t address, int32_t* data, size_t sizeInBytes) {
81 PcieBackend::readDMAViaStruct(bar, address, data, sizeInBytes);
82 };
83 _writeFunction = [&](uint8_t bar, uint32_t address, int32_t const* data, size_t sizeInBytes) {
84 writeWithStruct(bar, address, data, sizeInBytes);
85 };
86 _readFunction = [&](uint8_t bar, uint32_t address, int32_t* data, size_t sizeInBytes) {
87 readWithStruct(bar, address, data, sizeInBytes);
88 };
89 return;
90 }
91
92 if(ioctl(_deviceID, PCIEUNI_PHYSICAL_SLOT, &ioctlData) >= 0) {
93 // it's the pcieuni
94 _ioctlPhysicalSlot = PCIEUNI_PHYSICAL_SLOT;
95 _ioctlDriverVersion = PCIEUNI_DRIVER_VERSION;
96 _ioctlDMA = PCIEUNI_READ_DMA;
97 _readDMAFunction = [&](uint8_t bar, uint32_t address, int32_t* data, size_t sizeInBytes) {
98 PcieBackend::readDMAViaIoctl(bar, address, data, sizeInBytes);
99 };
100 _writeFunction = [&](uint8_t bar, uint32_t address, int32_t const* data, size_t sizeInBytes) {
101 directWrite(bar, address, data, sizeInBytes);
102 };
103 _readFunction = [&](uint8_t bar, uint32_t address, int32_t* data, size_t sizeInBytes) {
104 directRead(bar, address, data, sizeInBytes);
105 };
106 return;
107 }
108
109 // No working driver. Close the device and throw an exception.
110 std::cerr << "Unsupported driver. " << createErrorStringWithErrnoText("Error is ") << std::endl;
111 ::close(_deviceID);
112 throw ChimeraTK::runtime_error("Unsupported driver in device" + _deviceNodeName);
113 }
114
116 if(_opened) {
117 ::close(_deviceID);
118 }
119 _opened = false;
120 }
121
122 bool PcieBackend::checkConnection() const {
123 // Note: This expects byte 0 of bar 0 to be readable. This is currently guaranteed by our firmware framework. If
124 // other firmware needs to be supported, this should be made configurable (via CDD). If a map file is used, we could
125 // also use the first readable address specified in the map file.
126
127 // read word 0 from bar 0 to check if device works
128 device_rw l_RW;
129 l_RW.barx_rw = 0;
130 l_RW.mode_rw = RW_D8;
131 l_RW.offset_rw = 0;
132 l_RW.size_rw = 0; // does not overwrite the struct but writes one word back to data
133 l_RW.data_rw = -1;
134 l_RW.rsrvd_rw = 0;
135 return ::read(_deviceID, &l_RW, sizeof(device_rw)) == sizeof(device_rw);
136 }
137
138 void PcieBackend::readInternal(uint8_t bar, uint32_t address, int32_t* data) {
139 device_rw l_RW;
140 assert(_opened);
141 l_RW.barx_rw = bar;
142 l_RW.mode_rw = RW_D32;
143 l_RW.offset_rw = address;
144 l_RW.size_rw = 0; // does not overwrite the struct but writes one word back to data
145 l_RW.data_rw = -1;
146 l_RW.rsrvd_rw = 0;
147
148 if(::read(_deviceID, &l_RW, sizeof(device_rw)) != sizeof(device_rw)) {
149 throw ChimeraTK::runtime_error(createErrorStringWithErrnoText("Cannot read data from device: "));
150 }
151 *data = static_cast<int32_t>(l_RW.data_rw);
152 }
153
154 void PcieBackend::directRead(uint8_t bar, uint32_t address, int32_t* data, size_t sizeInBytes) {
155 assert(_opened);
156 assert(bar <= 5);
157 loff_t virtualOffset = PCIEUNI_BAR_OFFSETS[bar] + address;
158
159 if(pread(_deviceID, data, sizeInBytes, virtualOffset) != static_cast<int>(sizeInBytes)) {
160 throw ChimeraTK::runtime_error(createErrorStringWithErrnoText("Cannot read data from device: "));
161 }
162 }
163
164 void PcieBackend::writeInternal(uint8_t bar, uint32_t address, int32_t const* data) {
165 device_rw l_RW;
166 assert(_opened);
167 l_RW.barx_rw = bar;
168 l_RW.mode_rw = RW_D32;
169 l_RW.offset_rw = address;
170 l_RW.data_rw = *data;
171 l_RW.rsrvd_rw = 0;
172 l_RW.size_rw = 0;
173
174 if(::write(_deviceID, &l_RW, sizeof(device_rw)) != sizeof(device_rw)) {
175 throw ChimeraTK::runtime_error(createErrorStringWithErrnoText("Cannot write data to device: "));
176 }
177 }
178
179 // direct write allows to read areas directly, without a loop in user space
180 void PcieBackend::directWrite(uint8_t bar, uint32_t address, int32_t const* data, size_t sizeInBytes) {
181 assert(_opened);
182 assert(bar <= 5);
183 loff_t virtualOffset = PCIEUNI_BAR_OFFSETS[bar] + address;
184
185 if(pwrite(_deviceID, data, sizeInBytes, virtualOffset) != static_cast<int>(sizeInBytes)) {
186 throw ChimeraTK::runtime_error(createErrorStringWithErrnoText("Cannot write data to device: "));
187 }
188 }
189
190 void PcieBackend::readWithStruct(uint8_t bar, uint32_t address, int32_t* data, size_t sizeInBytes) {
191 assert(_opened);
192 assert(sizeInBytes % 4 == 0);
193 for(uint32_t i = 0; i < sizeInBytes / 4; i++) {
194 readInternal(bar, address + i * 4, data + i);
195 }
196 }
197
198 void PcieBackend::read(uint8_t bar, uint32_t address, int32_t* data, size_t sizeInBytes) {
200
201 if(bar != 0xD) {
202 _readFunction(bar, address, data, sizeInBytes);
203 }
204 else {
205 _readDMAFunction(bar, address, data, sizeInBytes);
206 }
207 }
208
209 void PcieBackend::writeWithStruct(uint8_t bar, uint32_t address, int32_t const* data, size_t sizeInBytes) {
210 assert(_opened);
211 assert(sizeInBytes % 4 == 0);
212 for(uint32_t i = 0; i < sizeInBytes / 4; i++) {
213 writeInternal(bar, address + i * 4, (data + i));
214 }
215 }
216
217 void PcieBackend::write(uint8_t bar, uint32_t address, int32_t const* data, size_t sizeInBytes) {
219
220 _writeFunction(bar, address, data, sizeInBytes);
221 }
222
223 void PcieBackend::readDMAViaStruct(uint8_t /*bar*/, uint32_t address, int32_t* data, size_t sizeInBytes) {
224 ssize_t ret;
225 device_rw l_RW;
226 device_rw* pl_RW;
227
228 assert(_opened);
229
230 if(sizeInBytes < sizeof(device_rw)) {
231 pl_RW = &l_RW;
232 }
233 else {
234 // We are reaching down into the C-interface for the ioctl. There is not we can do about reinterpret-casting into
235 // the correct type. NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
236 pl_RW = reinterpret_cast<device_rw*>(data);
237 }
238
239 pl_RW->data_rw = 0;
240 pl_RW->barx_rw = 0;
241 pl_RW->size_rw = sizeInBytes;
242 pl_RW->mode_rw = RW_DMA;
243 pl_RW->offset_rw = address;
244 pl_RW->rsrvd_rw = 0;
245
246 ret = ::read(_deviceID, pl_RW, sizeof(device_rw));
247 if(ret != (ssize_t)sizeInBytes) {
248 throw ChimeraTK::runtime_error(createErrorStringWithErrnoText("Cannot read data from device: "));
249 }
250 if(sizeInBytes < sizeof(device_rw)) {
251 memcpy(data, pl_RW, sizeInBytes);
252 }
253 }
254
255 void PcieBackend::readDMAViaIoctl(uint8_t /*bar*/, uint32_t address, int32_t* data, size_t sizeInBytes) {
256 assert(_opened);
257
258 // prepare the struct
259 device_ioctrl_dma DMA_RW;
260 DMA_RW.dma_cmd = 0; // FIXME: Why is it 0? => read driver code
261 DMA_RW.dma_pattern = 0; // FIXME: Why is it 0? => read driver code
262 DMA_RW.dma_size = sizeInBytes;
263 DMA_RW.dma_offset = address;
264 DMA_RW.dma_reserved1 = 0; // FIXME: is this a correct value?
265 DMA_RW.dma_reserved2 = 0; // FIXME: is this a correct value?
266
267 if(sizeInBytes >= sizeof(device_ioctrl_dma)) {
268 // the ioctrl_dma struct is copied to the beginning of the data buffer,
269 // so the information about size and offset are passed to the driver.
270 memcpy((void*)data, &DMA_RW, sizeof(device_ioctrl_dma));
271 int ret = ioctl(_deviceID, _ioctlDMA, (void*)data);
272 if(ret) {
273 throw ChimeraTK::runtime_error(createErrorStringWithErrnoText("Cannot read data from device "));
274 }
275 }
276 else {
277 // the ioctrl_dma struct is used as a dma buffer and the read data is later copied out
278 int ret = ioctl(_deviceID, _ioctlDMA, (void*)&DMA_RW);
279 if(ret) {
280 throw ChimeraTK::runtime_error(createErrorStringWithErrnoText("Cannot read data from device "));
281 }
282 memcpy(&DMA_RW, (void*)data, sizeInBytes);
283 }
284 }
285
287 if(!_opened) throw ChimeraTK::logic_error("Device not opened.");
288 std::ostringstream os;
289 device_ioctrl_data ioctlData = {0, 0, 0, 0};
290 if(ioctl(_deviceID, _ioctlPhysicalSlot, &ioctlData) < 0) {
291 throw ChimeraTK::runtime_error(createErrorStringWithErrnoText("Cannot read device info: "));
292 }
293 os << "SLOT: " << ioctlData.data;
294 if(ioctl(_deviceID, _ioctlDriverVersion, &ioctlData) < 0) {
295 throw ChimeraTK::runtime_error(createErrorStringWithErrnoText("Cannot read device info: "));
296 }
297 // major and minor version are in data and offset, respectively
298 os << " DRV VER: " << ioctlData.data << "." << ioctlData.offset;
299 return os.str();
300 }
301
302 std::string PcieBackend::createErrorStringWithErrnoText(std::string const& startText) {
303 char errorBuffer[255];
304 return startText + _deviceNodeName + ": " + strerror_r(errno, errorBuffer, sizeof(errorBuffer));
305 }
306
307 boost::shared_ptr<DeviceBackend> PcieBackend::createInstance(
308 // FIXME #11279 Implement API breaking changes from linter warnings
309 // NOLINTNEXTLINE(performance-unnecessary-value-param)
310 std::string address, std::map<std::string, std::string> parameters) {
311 if(address.empty()) {
312 throw ChimeraTK::logic_error("Device address not specified.");
313 }
314
315 return boost::shared_ptr<DeviceBackend>(new PcieBackend("/dev/" + address, parameters["map"]));
316 }
317
318} // namespace ChimeraTK
void setOpenedAndClearException() noexcept
Backends should call this function at the end of a (successful) open() call.
void checkActiveException() final
Function to be called by backends when needing to check for an active exception.
std::atomic< bool > _opened
flag if backend is opened
Base class for address-based device backends (e.g.
void close() final
Deactivates all asynchronous accessors and calls closeImpl().
A class to provide the Pcie device functionality.
Definition PcieBackend.h:17
static boost::shared_ptr< DeviceBackend > createInstance(std::string address, std::map< std::string, std::string > parameters)
void read(uint8_t bar, uint32_t address, int32_t *data, size_t sizeInBytes) override
Deprecated read function using 32bit address for backwards compatibility.
PcieBackend(std::string deviceNodeName, const std::string &mapFileName="")
constructor called through createInstance to create device object
void write(uint8_t bar, uint32_t address, int32_t const *data, size_t sizeInBytes) override
Deprecated write function using 32bit address for backwards compatibility.
void open() override
Open the device.
std::string readDeviceInfo() override
Return a device information string containing hardware details like the firmware version number or th...
void closeImpl() override
All backends derrived from NumericAddressedBackend must implement closeImpl() instead of close.
Exception thrown when a logic error has occured.
Definition Exception.h:51
Exception thrown when a runtime error has occured.
Definition Exception.h:18
#define LLRFDRV_DRIVER_VERSION
#define LLRFDRV_PHYSICAL_SLOT
STL namespace.
#define PCIEDEV_PHYSICAL_SLOT
#define PCIEDEV_READ_DMA
#define PCIEDEV_DRIVER_VERSION
#define RW_D8
#define RW_DMA
#define RW_D32
#define PCIEUNI_READ_DMA
#define PCIEUNI_DRIVER_VERSION
#define PCIEUNI_PHYSICAL_SLOT
unsigned int dma_reserved2
unsigned int dma_pattern
unsigned int dma_reserved1
unsigned int dma_offset
unsigned int mode_rw
unsigned int offset_rw
unsigned int size_rw
unsigned int barx_rw
unsigned int rsrvd_rw
unsigned int data_rw