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