GCC Code Coverage Report


Directory: ./
File: LinuxGpioDriver.cpp
Date: 2026-09-03 22:12:42
Exec Total Coverage
Lines: 0 273 0.0%
Functions: 0 13 0.0%
Branches: 0 161 0.0%

Line Branch Exec Source
1 // ======================================================================
2 // \title LinuxGpioDriverImpl.cpp
3 // \author tcanham
4 // \brief cpp file for LinuxGpioDriver component implementation class
5 //
6 // \copyright
7 // Copyright 2009-2015, by the California Institute of Technology.
8 // ALL RIGHTS RESERVED. United States Government Sponsorship
9 // acknowledged.
10 //
11 // ======================================================================
12 #include <Drv/LinuxGpioDriver/LinuxGpioDriver.hpp>
13 #include <Fw/FPrimeBasicTypes.hpp>
14 #include <Fw/Types/String.hpp>
15 #include <Fw/Types/StringUtils.hpp>
16 #include <Os/Posix/File.hpp>
17
18 #include <linux/gpio.h>
19 #include <poll.h>
20 #include <sys/ioctl.h>
21 #include <unistd.h>
22 #include <cerrno>
23 #include <cstring>
24 #include <type_traits>
25
26 namespace Drv {
27
28 Os::File::Status errno_to_file_status(int errno_input) {
29 Os::File::Status status = Os::File::Status::OTHER_ERROR;
30 switch (errno_input) {
31 case 0:
32 status = Os::File::Status::OP_OK;
33 break;
34 case EBADF:
35 status = Os::File::Status::NOT_OPENED;
36 break;
37 case EINVAL:
38 status = Os::File::Status::INVALID_ARGUMENT;
39 break;
40 case ENODEV:
41 status = Os::File::Status::DOESNT_EXIST;
42 break;
43 case ENOMEM:
44 status = Os::File::Status::NO_SPACE;
45 break;
46 case EPERM:
47 status = Os::File::Status::NO_PERMISSION;
48 break;
49 case ENXIO:
50 status = Os::File::Status::INVALID_MODE;
51 break;
52 // Cascades intended
53 case EFAULT:
54 case EWOULDBLOCK:
55 case EBUSY:
56 case EIO:
57 default:
58 status = Os::File::Status::OTHER_ERROR;
59 break;
60 }
61 return status;
62 }
63
64 Drv::GpioStatus errno_to_gpio_status(int errno_input) {
65 Drv::GpioStatus status = Drv::GpioStatus::T::UNKNOWN_ERROR;
66 switch (errno_input) {
67 case EBADF:
68 status = Drv::GpioStatus::T::NOT_OPENED;
69 break;
70 case ENXIO:
71 status = Drv::GpioStatus::INVALID_MODE;
72 break;
73 // Cascades intended
74 case EFAULT:
75 case EINVAL:
76 case EWOULDBLOCK:
77 case EBUSY:
78 case EIO:
79 default:
80 status = Drv::GpioStatus::T::UNKNOWN_ERROR;
81 break;
82 }
83 return status;
84 }
85
86 U32 configuration_to_handler_flags(Drv::LinuxGpioDriver::GpioConfiguration configuration) {
87 U32 flags = 0;
88 switch (configuration) {
89 case LinuxGpioDriver::GPIO_OUTPUT:
90 flags = GPIOHANDLE_REQUEST_OUTPUT;
91 break;
92 // Cascade intended
93 case LinuxGpioDriver::GPIO_INPUT:
94 case LinuxGpioDriver::GPIO_INTERRUPT_RISING_EDGE:
95 case LinuxGpioDriver::GPIO_INTERRUPT_FALLING_EDGE:
96 case LinuxGpioDriver::GPIO_INTERRUPT_BOTH_RISING_AND_FALLING_EDGES:
97 flags = GPIOHANDLE_REQUEST_INPUT;
98 break;
99 default:
100 FW_ASSERT(false, static_cast<FwAssertArgType>(configuration));
101 break;
102 }
103 return flags;
104 }
105
106 #ifdef GPIO_V2_GET_LINE_IOCTL
107 U64 configuration_to_line_flags_v2(Drv::LinuxGpioDriver::GpioConfiguration configuration) {
108 U64 flags = 0;
109 switch (configuration) {
110 case LinuxGpioDriver::GPIO_OUTPUT:
111 flags = GPIO_V2_LINE_FLAG_OUTPUT;
112 break;
113 case LinuxGpioDriver::GPIO_INPUT:
114 flags = GPIO_V2_LINE_FLAG_INPUT;
115 break;
116 case LinuxGpioDriver::GPIO_INTERRUPT_RISING_EDGE:
117 flags = GPIO_V2_LINE_FLAG_INPUT | GPIO_V2_LINE_FLAG_EDGE_RISING;
118 break;
119 case LinuxGpioDriver::GPIO_INTERRUPT_FALLING_EDGE:
120 flags = GPIO_V2_LINE_FLAG_INPUT | GPIO_V2_LINE_FLAG_EDGE_FALLING;
121 break;
122 case LinuxGpioDriver::GPIO_INTERRUPT_BOTH_RISING_AND_FALLING_EDGES:
123 flags = GPIO_V2_LINE_FLAG_INPUT | GPIO_V2_LINE_FLAG_EDGE_RISING | GPIO_V2_LINE_FLAG_EDGE_FALLING;
124 break;
125 default:
126 FW_ASSERT(false, static_cast<FwAssertArgType>(configuration));
127 break;
128 }
129 return flags;
130 }
131 #endif
132
133 U32 configuration_to_event_flags(Drv::LinuxGpioDriver::GpioConfiguration configuration) {
134 U32 flags = 0;
135 switch (configuration) {
136 case LinuxGpioDriver::GPIO_INTERRUPT_RISING_EDGE:
137 flags = GPIOEVENT_REQUEST_RISING_EDGE;
138 break;
139 case LinuxGpioDriver::GPIO_INTERRUPT_FALLING_EDGE:
140 flags = GPIOEVENT_REQUEST_FALLING_EDGE;
141 break;
142 case LinuxGpioDriver::GPIO_INTERRUPT_BOTH_RISING_AND_FALLING_EDGES:
143 flags = GPIOEVENT_REQUEST_RISING_EDGE | GPIOEVENT_REQUEST_FALLING_EDGE;
144 break;
145 default:
146 FW_ASSERT(false, static_cast<FwAssertArgType>(configuration));
147 break;
148 }
149 return flags;
150 }
151
152 LinuxGpioDriver ::~LinuxGpioDriver() {
153 if (this->m_fd >= 0) {
154 (void)::close(this->m_fd);
155 }
156 }
157
158 // ----------------------------------------------------------------------
159 // Handler implementations for user-defined typed input ports
160 // ----------------------------------------------------------------------
161
162 Os::File::Status LinuxGpioDriver ::setupLineRequestV2(const int chip_descriptor,
163 const U32 gpio,
164 const GpioConfiguration& configuration,
165 const Fw::Logic& default_state,
166 int& fd) {
167 fd = -1;
168 #ifdef GPIO_V2_GET_LINE_IOCTL
169 Os::File::Status status = Os::File::OP_OK;
170 // Set up the GPIO v2 line request
171 struct gpio_v2_line_request request;
172 (void)::memset(&request, 0, sizeof request);
173 request.offsets[0] = gpio;
174 (void)Fw::StringUtils::string_copy(request.consumer, FW_OPTIONAL_NAME(this->getObjName()),
175 static_cast<FwSizeType>(sizeof request.consumer));
176 request.num_lines = 1;
177 request.fd = -1;
178 request.config.flags = configuration_to_line_flags_v2(configuration);
179 // For outputs, set the default state via an output-values config attribute
180 if (configuration == GPIO_OUTPUT) {
181 request.config.num_attrs = 1;
182 request.config.attrs[0].attr.id = GPIO_V2_LINE_ATTR_ID_OUTPUT_VALUES;
183 request.config.attrs[0].attr.values = (default_state == Fw::Logic::HIGH) ? 1 : 0;
184 request.config.attrs[0].mask = 1;
185 }
186
187 errno = 0;
188 int return_value = ioctl(chip_descriptor, GPIO_V2_GET_LINE_IOCTL, &request);
189 fd = request.fd;
190 if (return_value != 0) {
191 status = errno_to_file_status(errno);
192 fd = -1;
193 }
194 return status;
195 #else
196 // Headers do not provide the v2 uAPI: report unsupported (v2 is never selected in this build)
197 return Os::File::Status::NOT_SUPPORTED;
198 #endif
199 }
200
201 Os::File::Status LinuxGpioDriver ::setupLineHandle(const int chip_descriptor,
202 const U32 gpio,
203 const GpioConfiguration& configuration,
204 const Fw::Logic& default_state,
205 int& fd) {
206 Os::File::Status status = Os::File::OP_OK;
207 // Set up the GPIO request
208 struct gpiohandle_request request;
209 (void)::memset(&request, 0, sizeof request);
210 request.lineoffsets[0] = gpio;
211 (void)Fw::StringUtils::string_copy(request.consumer_label, FW_OPTIONAL_NAME(this->getObjName()),
212 static_cast<FwSizeType>(sizeof request.consumer_label));
213 request.default_values[0] = (default_state == Fw::Logic::HIGH) ? 1 : 0;
214 request.fd = -1;
215 request.lines = 1;
216 request.flags = configuration_to_handler_flags(configuration);
217
218 errno = 0;
219 int return_value = ioctl(chip_descriptor, GPIO_GET_LINEHANDLE_IOCTL, &request);
220 fd = request.fd;
221 if (return_value != 0) {
222 status = errno_to_file_status(errno);
223 fd = -1;
224 }
225 return status;
226 }
227
228 Os::File::Status LinuxGpioDriver ::setupLineEvent(const int chip_descriptor,
229 const U32 gpio,
230 const GpioConfiguration& configuration,
231 int& fd) {
232 Os::File::Status status = Os::File::OP_OK;
233 // Set up the GPIO request
234 struct gpioevent_request event;
235 (void)::memset(&event, 0, sizeof event);
236 event.lineoffset = gpio;
237 (void)Fw::StringUtils::string_copy(event.consumer_label, FW_OPTIONAL_NAME(this->getObjName()),
238 static_cast<FwSizeType>(sizeof event.consumer_label));
239 event.fd = -1;
240 event.handleflags = configuration_to_handler_flags(configuration);
241 event.eventflags = configuration_to_event_flags(configuration);
242 errno = 0;
243 int return_value = ioctl(chip_descriptor, GPIO_GET_LINEEVENT_IOCTL, &event);
244 fd = event.fd;
245 if (return_value != 0) {
246 status = errno_to_file_status(errno);
247 fd = -1;
248 }
249 return status;
250 }
251
252 Os::File::Status LinuxGpioDriver ::open(const char* device,
253 const U32 gpio,
254 const GpioConfiguration& configuration,
255 const Fw::Logic& default_state) {
256 Os::File::Status status = Os::File::OP_OK;
257 Os::File chip_file;
258 FW_ASSERT(device != nullptr);
259 FW_ASSERT(configuration < MAX_GPIO_CONFIGURATION and configuration >= 0,
260 static_cast<FwAssertArgType>(configuration));
261
262 // Open chip file and check for success
263 status = chip_file.open(device, Os::File::Mode::OPEN_WRITE);
264 if (status != Os::File::OP_OK) {
265 this->log_WARNING_HI_OpenChipError(Fw::String(device), Os::FileStatus(static_cast<Os::FileStatus::T>(status)));
266 return status;
267 }
268 // Read chip information and check for correctness
269 int chip_descriptor = reinterpret_cast<Os::Posix::File::PosixFileHandle*>(chip_file.getHandle())->m_file_descriptor;
270 struct gpiochip_info chip_info;
271 (void)::memset(&chip_info, 0, sizeof chip_info);
272 int return_value = ioctl(chip_descriptor, GPIO_GET_CHIPINFO_IOCTL, &chip_info);
273 if (return_value != 0) {
274 status = errno_to_file_status(errno);
275 this->log_WARNING_HI_OpenChipError(Fw::String(device), Os::FileStatus(static_cast<Os::FileStatus::T>(status)));
276 return status;
277 }
278 // Check if the GPIO line exists
279 if (gpio >= chip_info.lines) {
280 status = Os::File::Status::DOESNT_EXIST;
281 this->log_WARNING_HI_OpenPinError(Fw::String(device), gpio, Fw::String("Does Not Exist"),
282 Os::FileStatus(static_cast<Os::FileStatus::T>(status)));
283 return status;
284 }
285 Fw::String pin_message("Unknown");
286 // The v2 line-info ioctl doubles as a probe for v2 uAPI support: it only succeeds when the kernel
287 // (and this chip) support the v2 uAPI, so its result selects which uAPI version to request below.
288 bool supports_v2 = false;
289 #ifdef GPIO_V2_GET_LINEINFO_IOCTL
290 struct gpio_v2_line_info pin_info_v2;
291 (void)::memset(&pin_info_v2, 0, sizeof pin_info_v2);
292 pin_info_v2.offset = gpio;
293 return_value = ioctl(chip_descriptor, GPIO_V2_GET_LINEINFO_IOCTL, &pin_info_v2);
294 if (return_value == 0) {
295 const bool has_consumer = pin_info_v2.consumer[0] != '\0';
296 (void)pin_message.format("%s%s%s", pin_info_v2.name, has_consumer ? " with current consumer " : "",
297 has_consumer ? pin_info_v2.consumer : "");
298 supports_v2 = true;
299 }
300 #endif
301 if (not supports_v2) {
302 struct gpioline_info pin_info;
303 (void)::memset(&pin_info, 0, sizeof pin_info);
304 pin_info.line_offset = gpio;
305 return_value = ioctl(chip_descriptor, GPIO_GET_LINEINFO_IOCTL, &pin_info);
306 if (return_value == 0) {
307 const bool has_consumer = pin_info.consumer[0] != '\0';
308 (void)pin_message.format("%s%s%s", pin_info.name, has_consumer ? " with current consumer " : "",
309 has_consumer ? pin_info.consumer : "");
310 }
311 }
312
313 // Set up pin and set file descriptor for it. The v2 uAPI is used when the probe above detected support;
314 // otherwise the deprecated v1 uAPI is used. Real v2 request errors are reported, not retried on v1.
315 int pin_fd = -1;
316 ApiVersion api_version = ApiVersion::API_V1;
317 if (supports_v2) {
318 api_version = ApiVersion::API_V2;
319 status = this->setupLineRequestV2(chip_descriptor, gpio, configuration, default_state, pin_fd);
320 } else {
321 switch (configuration) {
322 // Cascade intended
323 case GPIO_OUTPUT:
324 case GPIO_INPUT:
325 status = this->setupLineHandle(chip_descriptor, gpio, configuration, default_state, pin_fd);
326 break;
327 // Cascade intended
328 case GPIO_INTERRUPT_RISING_EDGE:
329 case GPIO_INTERRUPT_FALLING_EDGE:
330 case GPIO_INTERRUPT_BOTH_RISING_AND_FALLING_EDGES:
331 status = this->setupLineEvent(chip_descriptor, gpio, configuration, pin_fd);
332 break;
333 default:
334 FW_ASSERT(false);
335 break;
336 }
337 }
338 // Final status check
339 if (status != Os::File::Status::OP_OK) {
340 this->log_WARNING_HI_OpenPinError(Fw::String(device), gpio, pin_message,
341 Os::FileStatus(static_cast<Os::FileStatus::T>(status)));
342 } else {
343 this->log_DIAGNOSTIC_OpenChip(Fw::String(chip_info.name), Fw::String(chip_info.label), gpio, pin_message);
344 this->m_fd = pin_fd;
345 this->m_configuration = configuration;
346 this->m_apiVersion = api_version;
347 }
348 return status;
349 }
350
351 Drv::GpioStatus LinuxGpioDriver ::gpioRead_handler(const FwIndexType portNum, Fw::Logic& state) {
352 Drv::GpioStatus status = Drv::GpioStatus::INVALID_MODE;
353 if (this->m_configuration == GpioConfiguration::GPIO_INPUT) {
354 #ifdef GPIO_V2_GET_LINE_IOCTL
355 if (this->m_apiVersion == ApiVersion::API_V2) {
356 struct gpio_v2_line_values values;
357 (void)::memset(&values, 0, sizeof values);
358 values.mask = 1;
359 int return_value = ioctl(this->m_fd, GPIO_V2_LINE_GET_VALUES_IOCTL, &values);
360 if (return_value != 0) {
361 status = errno_to_gpio_status(errno);
362 } else {
363 state = (values.bits & 1) ? Fw::Logic::HIGH : Fw::Logic::LOW;
364 status = Drv::GpioStatus::OP_OK;
365 }
366 return status;
367 }
368 #endif
369 struct gpiohandle_data values;
370 (void)::memset(&values, 0, sizeof values);
371 int return_value = ioctl(this->m_fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &values);
372 if (return_value != 0) {
373 status = errno_to_gpio_status(errno);
374 } else {
375 state = values.values[0] ? Fw::Logic::HIGH : Fw::Logic::LOW;
376 status = Drv::GpioStatus::OP_OK;
377 }
378 }
379 return status;
380 }
381
382 Drv::GpioStatus LinuxGpioDriver ::gpioWrite_handler(const FwIndexType portNum, const Fw::Logic& state) {
383 Drv::GpioStatus status = Drv::GpioStatus::INVALID_MODE;
384 if (this->m_configuration == GpioConfiguration::GPIO_OUTPUT) {
385 #ifdef GPIO_V2_GET_LINE_IOCTL
386 if (this->m_apiVersion == ApiVersion::API_V2) {
387 struct gpio_v2_line_values values;
388 (void)::memset(&values, 0, sizeof values);
389 values.mask = 1;
390 values.bits = (state == Fw::Logic::HIGH) ? 1 : 0;
391 int return_value = ioctl(this->m_fd, GPIO_V2_LINE_SET_VALUES_IOCTL, &values);
392 if (return_value != 0) {
393 status = errno_to_gpio_status(errno);
394 } else {
395 status = Drv::GpioStatus::OP_OK;
396 }
397 return status;
398 }
399 #endif
400 struct gpiohandle_data values;
401 (void)::memset(&values, 0, sizeof values);
402 values.values[0] = (state == Fw::Logic::HIGH) ? 1 : 0;
403 int return_value = ioctl(this->m_fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &values);
404 if (return_value != 0) {
405 status = errno_to_gpio_status(errno);
406 } else {
407 status = Drv::GpioStatus::OP_OK;
408 }
409 }
410 return status;
411 }
412
413 void LinuxGpioDriver ::pollLoop() {
414 // Ensure size of FwSizeType is large enough to fit the necessary ranges
415 // NOTE: casts to unsigned types for int and ssize_t are made to avoid sign-compare warning;
416 // in both cases the cast is safe because max() returns nonnegative value.
417 static_assert(GPIO_POLL_TIMEOUT < static_cast<unsigned int>(std::numeric_limits<int>::max()),
418 "Poll timeout would overflow");
419 static_assert(sizeof(struct gpioevent_data) < std::numeric_limits<FwSizeType>::max(), "FwSizeType too small");
420 #ifdef GPIO_V2_GET_LINE_IOCTL
421 static_assert(sizeof(struct gpio_v2_line_event) < std::numeric_limits<FwSizeType>::max(), "FwSizeType too small");
422 #endif
423 using unsigned_ssize_t = std::make_unsigned<ssize_t>::type;
424 static_assert(
425 static_cast<unsigned_ssize_t>(std::numeric_limits<ssize_t>::max()) <= std::numeric_limits<FwSizeType>::max(),
426 "FwSizeType too small");
427 // Setup poll information
428 pollfd file_descriptors[1];
429 // Loop forever
430 // @non-terminating@: polling thread runs until stopped
431 while (this->getRunning()) {
432 // Setup polling
433 (void)::memset(file_descriptors, 0, sizeof file_descriptors);
434 file_descriptors[0].fd = this->m_fd;
435 file_descriptors[0].events = POLLIN; // Ask for read data available
436 // Poll for fd bing ready
437 int status = ::poll(file_descriptors, 1, static_cast<int>(GPIO_POLL_TIMEOUT));
438 // Check for some file descriptor to be ready
439 if (status > 0) {
440 union {
441 struct gpioevent_data v1;
442 #ifdef GPIO_V2_GET_LINE_IOCTL
443 struct gpio_v2_line_event v2;
444 #endif
445 } event_data;
446 FwSizeType expected_bytes = sizeof event_data.v1;
447 #ifdef GPIO_V2_GET_LINE_IOCTL
448 if (this->m_apiVersion == ApiVersion::API_V2) {
449 expected_bytes = sizeof event_data.v2;
450 }
451 #endif
452 FwSizeType read_bytes = static_cast<FwSizeType>(::read(this->m_fd, &event_data, expected_bytes));
453 if (read_bytes == expected_bytes) {
454 Os::RawTime timestamp;
455 Os::RawTime::Status timeStatus = timestamp.now();
456 if (timeStatus != Os::RawTime::Status::OP_OK) {
457 this->log_WARNING_HI_InterruptTimeError(
458 Os::RawTimeStatus(static_cast<Os::RawTimeStatus::T>(timeStatus)));
459 }
460 this->gpioInterrupt_out(0, timestamp);
461 }
462 // A read error occurred
463 else {
464 this->log_WARNING_HI_InterruptReadError(static_cast<U32>(expected_bytes), static_cast<U32>(read_bytes));
465 }
466 }
467 // An error of some kind occurred
468 else if (status < 0) {
469 this->log_WARNING_HI_PollingError(static_cast<I32>(errno));
470 }
471 }
472 }
473
474 } // end namespace Drv
475