• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wirenboard / wb-mqtt-gpio / 1

22 Aug 2025 09:07AM UTC coverage: 39.772%. First build
1

push

github

web-flow
Fix counters rising and falling edge interrupt bug

355 of 948 branches covered (37.45%)

1 of 14 new or added lines in 2 files covered. (7.14%)

698 of 1755 relevant lines covered (39.77%)

2.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

12.59
/src/gpio_chip_driver.cpp
1
#include "gpio_chip_driver.h"
2
#include "exceptions.h"
3
#include "gpio_chip.h"
4
#include "gpio_counter.h"
5
#include "gpio_line.h"
6
#include "interruption_context.h"
7
#include "log.h"
8
#include "utils.h"
9

10
#include <wblib/utils.h>
11

12
#include <algorithm>
13
#include <cassert>
14
#include <fstream>
15
#include <string.h>
16
#include <sys/epoll.h>
17
#include <sys/ioctl.h>
18
#include <sys/timerfd.h>
19
#include <unistd.h>
20

21
#define LOG(logger) ::logger.Log() << "[gpio chip driver] "
22

23
using namespace std;
24

25
const auto CONSUMER = "wb-mqtt-gpio";
26

27
namespace
28
{
29
    uint32_t GetFlagsFromConfig(const TGpioLineConfig& config, bool asIs = false)
×
30
    {
31
        uint32_t flags = 0;
×
32

33
        if (!asIs) {
×
34
            if (config.Direction == EGpioDirection::Input)
×
35
                flags |= GPIOHANDLE_REQUEST_INPUT;
×
36
            else if (config.Direction == EGpioDirection::Output)
×
37
                flags |= GPIOHANDLE_REQUEST_OUTPUT;
×
38
        }
39
        if (config.IsOpenDrain)
×
40
            flags |= GPIOHANDLE_REQUEST_OPEN_DRAIN;
×
41
        if (config.IsOpenSource)
×
42
            flags |= GPIOHANDLE_REQUEST_OPEN_SOURCE;
×
43
        if (config.IsActiveLow)
×
44
            flags |= GPIOHANDLE_REQUEST_ACTIVE_LOW;
×
45

46
        return flags;
×
47
    }
48
} // namespace
49

50
TGpioChipDriver::TGpioChipDriver(const TGpioChipConfig& config): AddedToEpoll(false)
1✔
51
{
52
    Chip = make_shared<TGpioChip>(config.Path);
1✔
53

54
    unordered_map<uint32_t, vector<vector<PGpioLine>>> pollLines;
1✔
55
    auto addToPoll = [&pollLines](const PGpioLine& line) {
×
56
        auto& lineBulks = pollLines[GetFlagsFromConfig(*line->GetConfig())];
×
57

58
        if (lineBulks.empty() || lineBulks.back().size() == GPIOHANDLES_MAX) {
×
59
            lineBulks.emplace_back();
×
60
        }
61

62
        lineBulks.back().push_back(line);
×
63
    };
×
64

65
    if (!Chip->IsValid()) {
1✔
66
        for (const auto& lineConfig: config.Lines) {
2✔
67
            auto line = make_shared<TGpioLine>(Chip, lineConfig);
1✔
68
            LOG(Error) << "Add " << line->DescribeShort() << " as initially disconnected";
1✔
69
            InitiallyDisconnectedLines[line->GetOffset()] = line;
1✔
70
        }
71
        return;
1✔
72
    }
73

74
    for (const auto& line: Chip->LoadLines(config.Lines)) {
×
75
        if (!ReleaseLineIfUsed(line)) {
×
76
            LOG(Error) << "Skipping " << line->DescribeShort();
×
77
        }
78
        switch (line->GetConfig()->Direction) {
×
79
            case EGpioDirection::Input: {
×
80
                if (!InitInputInterrupts(line)) {
×
81
                    addToPoll(line);
×
82
                }
83
                break;
×
84
            }
85
            case EGpioDirection::Output: {
×
86
                if (!InitOutput(line, line->GetConfig()->InitialState)) {
×
87
                    LOG(Error) << "Failed to init output " << line->DescribeShort()
×
88
                               << ". Treating as initially disconnected";
×
89
                    InitiallyDisconnectedLines[line->GetOffset()] = line;
×
90
                }
91
                break;
×
92
            }
93
        }
94
    }
95

96
    /* Initialize polling if line doesn't support interrupts */
97
    for (const auto& flagsLines: pollLines) {
×
98
        const auto flags = flagsLines.first;
×
99
        const auto& lineBulks = flagsLines.second;
×
100

101
        for (const auto& lines: lineBulks) {
×
102
            if (!InitLinesPolling(flags, lines)) {
×
103
                for (const auto& line: lines) {
×
104
                    LOG(Error) << "Failed to init polling " << line->DescribeShort()
×
105
                               << ". Treating as initially disconnected";
×
106
                    InitiallyDisconnectedLines[line->GetOffset()] = line;
×
107
                }
108
            }
109
        }
110
    }
111

112
    if (Lines.empty() && InitiallyDisconnectedLines.empty()) {
×
113
        wb_throw(TGpioDriverException, "Failed to initialize any line");
×
114
    }
115

116
    AutoDetectInterruptEdges();
×
117
    ReadInputValues();
×
118
}
1✔
119

120
TGpioChipDriver::TGpioChipDriver(): AddedToEpoll(false)
5✔
121
{}
5✔
122

123
TGpioChipDriver::~TGpioChipDriver()
6✔
124
{
125
    for (const auto& fdLines: Lines) {
11✔
126
        auto fd = fdLines.first;
5✔
127
        const auto& lines = fdLines.second;
5✔
128

129
        {
130
            auto logDebug = move(LOG(Debug) << "Close fd for:");
10✔
131
            for (const auto& line: lines) {
10✔
132
                logDebug << "\n\t" << line->DescribeShort();
5✔
133
            }
134
        }
135

136
        close(fd);
5✔
137
    }
138
}
6✔
139

140
int TGpioChipDriver::CreateIntervalTimer()
×
141
{
142
    int tfd = timerfd_create(CLOCK_MONOTONIC, 0);
×
143
    if (tfd == -1) {
×
144
        LOG(Error) << "timerfd_create failed: " << strerror(errno);
×
145
        wb_throw(TGpioDriverException, "unable to create timer: timerfd_create failed with " + string(strerror(errno)));
×
146
    }
147
    return tfd;
×
148
}
149

150
void TGpioChipDriver::SetIntervalTimer(int tfd, std::chrono::microseconds intervalUs)
×
151
{
152
    auto sec = std::chrono::floor<std::chrono::seconds>(intervalUs);
×
153
    auto nsec = std::chrono::duration_cast<std::chrono::nanoseconds>(intervalUs - sec);
×
154

155
    struct itimerspec ts;
156
    ts.it_value.tv_sec = sec.count();
×
157
    ts.it_value.tv_nsec = nsec.count();
×
158
    ts.it_interval.tv_sec = 0;
×
159
    ts.it_interval.tv_nsec = 0;
×
160

161
    if (timerfd_settime(tfd, 0, &ts, NULL) < 0) {
×
162
        LOG(Error) << "timerfd_settime failed: " << strerror(errno);
×
163
        close(tfd);
×
164
        wb_throw(TGpioDriverException, "unable to setup timer: timerfd_settime failed with " + string(strerror(errno)));
×
165
    }
166
}
167

168
TGpioChipDriver::TGpioLinesByOffsetMap TGpioChipDriver::MapLinesByOffset() const
×
169
{
170
    TGpioLinesByOffsetMap linesByOffset;
×
171

172
    FOR_EACH_LINE(this, line)
×
173
    {
174
        linesByOffset[line->GetOffset()] = line;
×
175
    });
×
176

177
    return linesByOffset;
×
178
}
179

180
const TGpioChipDriver::TGpioLinesByOffsetMap& TGpioChipDriver::MapInitiallyDisconnectedLinesByOffset() const
1✔
181
{
182
    return InitiallyDisconnectedLines;
1✔
183
}
184

185
void TGpioChipDriver::AddToEpoll(int epfd)
×
186
{
187
    AddedToEpoll = true;
×
188

189
    FOR_EACH_LINE(this, line)
×
190
    {
191
        if (line->IsOutput() || line->GetInterruptSupport() != EInterruptSupport::YES) {
×
192
            return;
×
193
        }
194

195
        struct epoll_event ep_event
×
196
        {};
197

198
        ep_event.events = EPOLLIN | EPOLLPRI;
×
199
        ep_event.data.fd = line->GetFd();
×
200

201
        if (epoll_ctl(epfd, EPOLL_CTL_ADD, line->GetFd(), &ep_event) < 0) {
×
202
            LOG(Error) << "epoll_ctl error: '" << strerror(errno) << "' at " << line->DescribeShort();
×
203
        }
204

205
        auto timerFd = line->GetTimerFd();
×
206
        ep_event.events = EPOLLIN;
×
207
        ep_event.data.fd = timerFd;
×
208
        if (epoll_ctl(epfd, EPOLL_CTL_ADD, timerFd, &ep_event) < 0) {
×
209
            LOG(Error) << "epoll_ctl error: '" << strerror(errno);
×
210
            wb_throw(TGpioDriverException,
×
211
                     "unable to add timer to epoll: epoll_ctl failed with " + string(strerror(errno)));
212
        }
213
    });
×
214
}
215

216
bool TGpioChipDriver::HandleGpioInterrupt(const PGpioLine& line, const TInterruptionContext& ctx)
×
217
{
218
    bool isHandled = false;
×
219
    auto fd = line->GetFd();
×
220

221
    fd_set rfds;
222
    FD_ZERO(&rfds);
×
223
    FD_SET(fd, &rfds);
×
224
    struct timeval tv
×
225
    {
226
        0
227
    }; // do not block
228

229
    while (auto retVal = select(fd + 1, &rfds, nullptr, nullptr, &tv)) {
×
230
        if (retVal < 0) {
×
231
            LOG(Error) << "select failed: " << strerror(errno);
×
232
            wb_throw(TGpioDriverException,
×
233
                     "unable to read line event data: select failed with " + string(strerror(errno)));
234
        }
235

236
        gpioevent_data data{};
×
237
        if (read(fd, &data, sizeof(data)) < 0) {
×
238
            LOG(Error) << "Read gpioevent_data failed: " << strerror(errno);
×
239
            wb_throw(TGpioDriverException,
×
240
                     "unable to read line event data: gpioevent_data failed with " + string(strerror(errno)));
241
        }
242

243
        auto edge = data.id == GPIOEVENT_EVENT_RISING_EDGE ? EGpioEdge::RISING : EGpioEdge::FALLING;
×
244
        auto time = ctx.ToSteadyClock(data.timestamp);
×
245

246
        if (line->HandleInterrupt(edge, time) == EInterruptStatus::Handled) { // update gpioline's last interruption ts
×
247
            gpiohandle_data data;
248
            if (ioctl(fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data) < 0) {
×
249
                LOG(Error) << "GPIOHANDLE_GET_LINE_VALUES_IOCTL failed: " << strerror(errno);
×
250
                line->SetError("r");
×
251
                return false;
×
252
            }
253

254
            // false interrupt generated on every service startup
255
            // ignore it to prevent incorrect counter updates
256
            bool oldValue = line->GetValueUnfiltered();
×
257
            bool newValue = data.values[0];
×
NEW
258
            if ((line->GetInterruptEdge() == EGpioEdge::BOTH && oldValue == newValue) ||
×
NEW
259
                (line->GetSkipInterrupt() && !newValue))
×
260
            {
NEW
261
                line->ClearSkipInterrupt();
×
NEW
262
                return false;
×
263
            }
264

NEW
265
            line->SetCachedValueUnfiltered(data.values[0]); // all interrupt events
×
NEW
266
            SetIntervalTimer(line->GetTimerFd(), line->GetConfig()->DebounceTimeout);
×
NEW
267
            isHandled = true;
×
268
        }
269
    }
270
    return isHandled;
×
271
}
272

273
bool TGpioChipDriver::HandleTimerInterrupt(const PGpioLine& line)
×
274
{
275
    bool isHandled = false;
×
276

277
    if (line->UpdateIfStable(chrono::steady_clock::now())) {
×
278
        isHandled = true;
×
279
        SetIntervalTimer(line->GetTimerFd(),
×
280
                         std::chrono::microseconds(0)); // disarm timer
×
281
    }
282
    return isHandled;
×
283
}
284

285
bool TGpioChipDriver::HandleInterrupt(const TInterruptionContext& ctx)
×
286
{
287
    bool isHandled = false;
×
288

289
    for (int i = 0; i < ctx.Count; i++) {
×
290
        auto fd = ctx.Events[i].data.fd;
×
291

292
        // gpio interrupt event fired: set stable-val-check timer
293
        auto itFdLines = Lines.find(fd);
×
294
        if (itFdLines != Lines.end()) {
×
295
            const auto& lines = itFdLines->second;
×
296
            assert(lines.size() == 1);
×
297
            const auto& line = lines.front();
×
298
            HandleGpioInterrupt(line, ctx);
×
299

300
            // timer event fired: check, is value stable or bouncing
301
        } else {
302
            auto itFdTimers = Timers.find(fd);
×
303
            if (itFdTimers != Timers.end()) {
×
304
                const auto& line = itFdTimers->second.front();
×
305
                isHandled |= HandleTimerInterrupt(line);
×
306
            }
307
        }
308
    }
309
    return isHandled;
×
310
}
311

312
bool TGpioChipDriver::PollLines()
×
313
{
314
    bool isHandled = false;
×
315

316
    for (const auto& fdLines: Lines) {
×
317
        const auto& lines = fdLines.second;
×
318
        assert(!lines.empty());
×
319

320
        isHandled = true;
×
321

322
        PollLinesValues(lines);
×
323
    }
324

325
    return isHandled;
×
326
}
327

328
void TGpioChipDriver::ForEachLine(const TGpioLineHandler& handler) const
×
329
{
330
    for (const auto& fdLines: Lines) {
×
331
        const auto& lines = fdLines.second;
×
332
        assert(!lines.empty());
×
333

334
        for (const auto& line: lines) {
×
335
            handler(line);
×
336
        }
337
    }
338
}
339

340
bool TGpioChipDriver::ReleaseLineIfUsed(const PGpioLine& line)
×
341
{
342
    if (!line->IsUsed())
×
343
        return true;
×
344

345
    LOG(Debug) << line->Describe() << " is used by '" << line->GetConsumer() << "'.";
×
346
    if (line->GetConsumer() == "sysfs") {
×
347
        ofstream unexportGpio("/sys/class/gpio/unexport");
×
348
        if (unexportGpio.is_open()) {
×
349
            LOG(Debug) << "Trying to unexport...";
×
350
            try {
351
                unexportGpio << Utils::ToSysfsGpio(line);
×
352
            } catch (const TGpioDriverException& e) {
×
353
                LOG(Error) << line->Describe() << " is used by '" << line->GetConsumer() << "',"
×
354
                           << " during unexport: " << e.what();
×
355
            }
356
        }
357
    }
358

359
    line->UpdateInfo();
×
360

361
    if (line->IsUsed()) {
×
362
        LOG(Error) << "Failed to release " << line->DescribeShort();
×
363
        return false;
×
364
    }
365

366
    LOG(Debug) << line->DescribeShort() << " successfully released";
×
367
    return true;
×
368
}
369

370
bool TGpioChipDriver::TryListenLine(const PGpioLine& line)
×
371
{
372
    const auto& config = line->GetConfig();
×
373
    assert(config->Direction == EGpioDirection::Input);
×
374

375
    gpioevent_request req{};
×
376

377
    strcpy(req.consumer_label, CONSUMER);
×
378
    req.lineoffset = line->GetOffset();
×
379
    req.handleflags = GetFlagsFromConfig(*config);
×
380

381
    req.eventflags = 0;
×
382

383
    switch (config->InterruptEdge) {
×
384
        case EGpioEdge::RISING:
×
385
            req.eventflags |= GPIOEVENT_REQUEST_RISING_EDGE;
×
386
            break;
×
387
        case EGpioEdge::FALLING:
×
388
            req.eventflags |= GPIOEVENT_REQUEST_FALLING_EDGE;
×
389
            break;
×
390
        case EGpioEdge::BOTH:
×
391
            req.eventflags |= GPIOEVENT_REQUEST_BOTH_EDGES;
×
392
            break;
×
393
        case EGpioEdge::AUTO:
×
394
            req.eventflags |= GPIOEVENT_REQUEST_BOTH_EDGES;
×
395
            break;
×
396
        default:
×
397
            wb_throw(TGpioDriverException, "Unknown interrupt edge in config");
×
398
    }
399

400
    errno = 0;
×
401
    if (ioctl(Chip->GetFd(), GPIO_GET_LINEEVENT_IOCTL, &req) < 0) {
×
402
        auto error = errno;
×
403
        LOG(Warn) << "GPIO_GET_LINEEVENT_IOCTL failed: " << strerror(error) << " at " << line->DescribeShort();
×
404
        return false;
×
405
    }
406

407
    Lines[req.fd].push_back(line);
×
408
    assert(Lines[req.fd].size() == 1);
×
409
    line->SetFd(req.fd);
×
410

411
    auto timerFd = CreateIntervalTimer();
×
412
    line->SetTimerFd(timerFd);
×
413
    Timers[timerFd].push_back(line);
×
414

415
    LOG(Debug) << "Listening to " << line->DescribeShort();
×
416
    return true;
×
417
}
418

419
bool TGpioChipDriver::FlushMcp23xState(const PGpioLine& line)
×
420
{
421
    /*
422
        MCP's POR state is input => we need to init gpio-extender module as output on physicall reconnect.
423

424
        "pinctrl_mcp23s08" kernel driver has internal cache => once init module as input
425
        and then init as output to trigger needed i2c communication with mcp.
426
    */
427
    if (line->GetConfig()->Direction != EGpioDirection::Output) {
×
428
        wb_throw(TGpioDriverException, "Only output lines need flushing-state magic after physical reconnect");
×
429
    }
430

431
    LOG(Debug) << "Flush state of " << line->DescribeShort() << " to guarantee, it is alive after any disconnects";
×
432

433
    gpioevent_request req{};
×
434
    req.lineoffset = line->GetOffset();
×
435
    req.handleflags |= GPIOHANDLE_REQUEST_INPUT;
×
436
    req.eventflags |= GPIOEVENT_REQUEST_RISING_EDGE;
×
437
    strcpy(req.consumer_label, CONSUMER);
×
438

439
    if (ioctl(Chip->GetFd(), GPIO_GET_LINEEVENT_IOCTL, &req) < 0) {
×
440
        LOG(Error) << "Temporary init " << line->DescribeShort()
×
441
                   << " as input failed. GPIO_GET_LINEEVENT_IOCTL: " << strerror(errno);
×
442
        return false;
×
443
    }
444
    line->UpdateInfo();
×
445
    close(req.fd);
×
446
    return true;
×
447
}
448

449
bool TGpioChipDriver::InitOutput(const PGpioLine& line, uint8_t val)
×
450
{
451
    const auto& config = line->GetConfig();
×
452
    assert(config->Direction == EGpioDirection::Output);
×
453

454
    gpiohandle_request req;
455
    memset(&req, 0, sizeof(gpiohandle_request));
×
456
    req.lines = 1;
×
457
    req.lineoffsets[0] = line->GetOffset();
×
458
    req.default_values[0] = val;
×
459
    req.flags = GetFlagsFromConfig(*config, line->IsOutput());
×
460
    strcpy(req.consumer_label, CONSUMER);
×
461

462
    if (ioctl(Chip->GetFd(), GPIO_GET_LINEHANDLE_IOCTL, &req) < 0) {
×
463
        LOG(Error) << "GPIO_GET_LINEHANDLE_IOCTL failed: " << strerror(errno) << " at " << line->DescribeShort();
×
464
        return false;
×
465
    }
466

467
    Lines[req.fd].push_back(line);
×
468
    assert(Lines[req.fd].size() == 1);
×
469
    line->SetFd(req.fd);
×
470

471
    if (Debug.IsEnabled()) {
×
472
        gpiohandle_data data;
473
        if (ioctl(line->GetFd(), GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data) >= 0) {
×
474
            LOG(Debug) << "Initialized output " << line->DescribeShort() << " = " << static_cast<int>(data.values[0]);
×
475
        }
476
    } else {
477
        LOG(Info) << "Initialized output " << line->DescribeShort();
×
478
    }
479

480
    return true;
×
481
}
482

483
bool TGpioChipDriver::InitInputInterrupts(const PGpioLine& line)
×
484
{
485
    switch (line->GetInterruptSupport()) {
×
486
        case EInterruptSupport::UNKNOWN:
×
487
        case EInterruptSupport::YES: {
488
            if (TryListenLine(line)) {
×
489
                line->SetInterruptSupport(EInterruptSupport::YES);
×
490
                return true;
×
491
            }
492
            LOG(Info) << line->Describe() << " does not support interrupts. Polling will be used instead.";
×
493
            line->SetInterruptSupport(EInterruptSupport::NO);
×
494
            return false;
×
495
        }
496

497
        case EInterruptSupport::NO: {
×
498
            return false;
×
499
        }
500
    }
501
}
502

503
bool TGpioChipDriver::InitLinesPolling(uint32_t flags, const vector<PGpioLine>& lines)
×
504
{
505
    assert(lines.size() <= GPIOHANDLES_MAX);
×
506

507
    gpiohandle_request req;
508
    req.lines = 0;
×
509
    req.flags = flags;
×
510
    strcpy(req.consumer_label, CONSUMER);
×
511

512
    for (auto& line: lines) {
×
513
        req.lineoffsets[req.lines] = line->GetOffset();
×
514
        req.default_values[req.lines] = line->IsActiveLow();
×
515
        ++req.lines;
×
516
    }
517

518
    if (ioctl(Chip->GetFd(), GPIO_GET_LINEHANDLE_IOCTL, &req) < 0) {
×
519
        LOG(Error) << "GPIO_GET_LINEHANDLE_IOCTL failed: " << strerror(errno);
×
520
        return false;
×
521
    }
522

523
    auto& initialized = Lines[req.fd];
×
524

525
    assert(initialized.empty());
×
526
    initialized.reserve(lines.size());
×
527

528
    for (const auto& line: lines) {
×
529
        line->SetFd(req.fd);
×
530
        initialized.push_back(line);
×
531
    }
532

533
    return true;
×
534
}
535

536
void TGpioChipDriver::PollLinesValues(const TGpioLines& lines)
×
537
{
538
    assert(!lines.empty());
×
539

540
    auto fd = lines.front()->GetFd();
×
541
    gpiohandle_data data;
542
    if (ioctl(fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data) < 0) {
×
543
        if (lines.front()->GetError().empty()) {
×
544
            LOG(Error) << "GPIOHANDLE_GET_LINE_VALUES_IOCTL failed: " << strerror(errno);
×
545
            for (const auto& line: lines) {
×
546
                LOG(Error) << "Treating " << line->DescribeShort() << " as disconnected";
×
547
                line->SetError("r");
×
548
            }
549
        }
550
        return;
×
551
    }
552

553
    auto now = chrono::steady_clock::now();
×
554
    for (uint32_t i = 0; i < lines.size(); ++i) {
×
555
        const auto& line = lines[i];
×
556
        assert(line->GetFd() == fd);
×
557

558
        bool oldValue = line->GetValue();
×
559
        bool newValue = data.values[i];
×
560

561
        bool recovery = !line->GetError().empty();
×
562
        if (recovery) {
×
563
            line->ClearError();
×
564
            LOG(Info) << "Treating " << line->DescribeShort() << " as alive again";
×
565
            if (line->GetConfig()->Direction == EGpioDirection::Output) {
×
566
                ReInitOutput(line);
×
567
            }
568
        }
569

570
        LOG(Debug) << "Poll " << line->DescribeShort() << " old value: " << oldValue << " new value: " << newValue;
×
571

572
        if (!line->IsOutput()) {
×
573
            /* if value changed for input we simulate interrupt */
574
            if (recovery || oldValue != newValue) {
×
575
                auto edge = newValue ? EGpioEdge::RISING : EGpioEdge::FALLING;
×
576
                if (line->HandleInterrupt(edge, now) == EInterruptStatus::Handled) {
×
577
                    line->SetCachedValue(newValue);
×
578
                }
579
            }
580
        } else { /* for output just set value to cache: it will publish it if
581
                    changed */
582
            line->SetCachedValue(newValue);
×
583
        }
584
    }
585
}
586

587
void TGpioChipDriver::ReadLinesValues(const TGpioLines& lines)
×
588
{
589
    if (lines.empty()) {
×
590
        return;
×
591
    }
592
    auto fd = lines.front()->GetFd();
×
593

594
    gpiohandle_data data;
595
    if (ioctl(fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data) < 0) {
×
596
        LOG(Error) << "GPIOHANDLE_GET_LINE_VALUES_IOCTL failed: " << strerror(errno);
×
597
        for (const auto& line: lines) {
×
598
            line->SetError("r");
×
599
        }
600
        return;
×
601
    }
602

603
    uint32_t i = 0;
×
604
    for (const auto& line: lines) {
×
605
        assert(line->GetFd() == fd);
×
606

607
        line->SetCachedValue(data.values[i++]);
×
608
    }
609
}
610

611
void TGpioChipDriver::ReListenLine(PGpioLine line)
×
612
{
613
    assert(!AddedToEpoll);
×
614

615
    auto oldFd = line->GetFd();
×
616
    auto oldTimerFd = line->GetTimerFd();
×
617

618
    assert(oldFd > -1);
×
619

620
    Lines.erase(oldFd);
×
621
    Timers.erase(oldTimerFd);
×
622
    close(oldFd);
×
623

624
    bool ok = TryListenLine(line);
×
625
    assert(ok);
×
626
    if (!ok) {
×
627
        LOG(Error) << "Unable to re-listen to " << line->DescribeShort();
×
628
    }
629
}
630

631
void TGpioChipDriver::ReInitOutput(PGpioLine line)
×
632
{
633
    auto oldfd = line->GetFd();
×
634
    Lines.erase(oldfd);
×
635
    close(oldfd);
×
636

637
    if (Chip->GetLabel() == "mcp23017" || Chip->GetLabel() == "mcp23008")
×
638
        if (!FlushMcp23xState(line)) {
×
639
            LOG(Error) << "Unable to re-init output " << line->DescribeShort();
×
640
            return;
×
641
        }
642

643
    auto lastSuccessfulVal = line->GetValue();
×
644
    if (!InitOutput(line, lastSuccessfulVal)) {
×
645
        LOG(Error) << "Unable to re-init output " << line->DescribeShort();
×
646
    }
647
}
648

649
void TGpioChipDriver::AutoDetectInterruptEdges()
5✔
650
{
651
    static auto doesNeedAutoDetect = [](const PGpioLine& line) {
25✔
652
        if (line->IsHandled() && !line->IsOutput()) {
25✔
653
            if (const auto& counter = line->GetCounter()) {
25✔
654
                return counter->GetInterruptEdge() == EGpioEdge::AUTO;
25✔
655
            }
656
        }
657

658
        return false;
×
659
    };
660

661
    vector<TGpioLines> pollingLines;
10✔
662
    unordered_map<PGpioLine, int> linesSum;
10✔
663

664
    for (const auto& fdLines: Lines) {
10✔
665
        const auto& lines = fdLines.second;
5✔
666

667
        if (any_of(lines.begin(), lines.end(), doesNeedAutoDetect)) {
5✔
668
            pollingLines.push_back(lines);
2✔
669
        }
670
    }
671

672
    const auto testCount = 10;
5✔
673

674
    for (auto i = 0; i < testCount; ++i) {
55✔
675
        for (const auto& lines: pollingLines) {
70✔
676
            ReadLinesValues(lines);
20✔
677

678
            for (const auto& line: lines) {
40✔
679
                if (doesNeedAutoDetect(line)) {
20✔
680
                    linesSum[line] += line->GetValue();
20✔
681
                }
682
            }
683
        }
684
    }
685

686
    for (auto& lineSum: linesSum) {
7✔
687
        const auto& line = lineSum.first;
2✔
688
        auto& sum = lineSum.second;
2✔
689

690
        auto edge = sum < testCount ? EGpioEdge::RISING : EGpioEdge::FALLING;
2✔
691

692
        LOG(Info) << "Auto detected edge for line: " << line->DescribeShort() << ": " << GpioEdgeToString(edge);
2✔
693

694
        line->GetCounter()->SetInterruptEdge(edge);
2✔
695
        line->GetConfig()->InterruptEdge = edge;
2✔
696

697
        ReListenLine(line);
2✔
698
    }
699
}
5✔
700

701
void TGpioChipDriver::ReadInputValues()
×
702
{
703
    for (const auto& fdLines: Lines) {
×
704
        TGpioLines linesToRead;
×
705
        for (auto line: fdLines.second) {
×
706
            if (!line->IsOutput() && !line->GetCounter()) {
×
707
                linesToRead.push_back(line);
×
708
            }
709
        }
710
        ReadLinesValues(linesToRead);
×
711
    }
712
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc