GCC Code Coverage Report


Directory: ./
File: boot/FPPRTC.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 95 148 64.2%
Functions: 12 12 100.0%
Branches: 101 247 40.9%

Line Branch Exec Source
1 /*
2 * This file is part of the Falcon Player (FPP) and is Copyright (C)
3 * 2013-2024 by the Falcon Player Developers.
4 *
5 * The Falcon Player (FPP) is free software, and is covered under
6 * multiple Open Source licenses. Please see the included 'LICENSES'
7 * file for descriptions of what files are covered by each license.
8 *
9 * This source file is covered under the LGPL v2.1 as described in the
10 * included LICENSE.LGPL file.
11 */
12
13 #include <sys/file.h>
14 #include <sys/stat.h>
15 #include <sys/wait.h>
16 #include <signal.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <string>
21 #include <unistd.h>
22
23 #include <array>
24 #include <iomanip>
25 #include <thread>
26
27 #include "common_mini.h"
28
29 14 static int getBus() {
30 #if defined(PLATFORM_BBB) || defined(PLATFORM_BB64)
31 8 return 2;
32 #elif defined(PLATFORM_PI)
33 6 return 1;
34 #else
35 return 0;
36 #endif
37 }
38
39 10 static void exec(const std::string& cmd) {
40 std::array<char, 128> buffer;
41
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 struct PipeCloser { void operator()(FILE* f) const { if (f) pclose(f); } };
42
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 std::unique_ptr<FILE, PipeCloser> pipe(popen(cmd.c_str(), "r"));
43
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (!pipe) {
44 ✗ throw std::runtime_error("popen() failed!");
45 }
46
3/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 10 times.
34 while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
47 // not printf(): the command's output is not a format string
48
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 fputs(buffer.data(), stdout);
49 }
50 10 }
51 4 static std::string execAndReturn(const std::string& cmd) {
52 std::array<char, 128> buffer;
53
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 struct PipeCloser { void operator()(FILE* f) const { if (f) pclose(f); } };
54
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 std::unique_ptr<FILE, PipeCloser> pipe(popen(cmd.c_str(), "r"));
55
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (!pipe) {
56 ✗ throw std::runtime_error("popen() failed!");
57 }
58 4 std::string ret;
59
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
12 while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
60 ✗ ret += std::string(buffer.data());
61 }
62 4 return ret;
63 4 }
64
65 8 static void modprobe(const char* mod) {
66
2/4
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
8 std::string cmd = std::string("/usr/sbin/modprobe ") + mod;
67
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 exec(cmd);
68 8 }
69 4 static std::string hwclock(const char* action, const char* device) {
70
5/10
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 4 times.
✗ Branch 9 not taken.
4 std::string cmd = std::string("/usr/sbin/hwclock ") + action + " -f " + device + " 2>&1";
71
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
8 return execAndReturn(cmd);
72 4 }
73 6 static void addI2CDevice(const char* dev, int bus) {
74 char buf[256];
75 6 snprintf(buf, sizeof(buf), "/sys/bus/i2c/devices/i2c-%d/new_device", bus);
76
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 FILE* fd = fopen(buf, "w");
77
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (fd != nullptr) {
78 ✗ fwrite(dev, strlen(dev), 1, fd);
79 ✗ fclose(fd);
80 }
81 6 }
82 6 static void removeI2CDevice(const std::string& dev, int bus) {
83
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (!dev.empty()) {
84
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 printf("Failed to setup RTC, removing device\n");
85 char buf[256];
86 6 snprintf(buf, sizeof(buf), "/sys/bus/i2c/devices/i2c-%d/delete_device", bus);
87
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 FILE* fd = fopen(buf, "w");
88
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (fd != nullptr) {
89 ✗ fwrite(dev.c_str(), dev.size(), 1, fd);
90 ✗ fclose(fd);
91 }
92 }
93 6 }
94
95 8 static bool setupRTC(std::string& devAd, bool newDev) {
96 // common_mini's parser handles both the quoted and unquoted forms of
97 // the settings file; the old local copy only handled quoted values and
98 // returned garbage for `piRTC = 5`, so no RTC was ever configured.
99
2/4
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
8 int ledType = getRawSettingInt("piRTC", 0);
100
101 8 int bus = getBus();
102
1/7
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
8 switch (ledType) {
103 ✗ case 1:
104 ✗ printf("FPP - Configuring RTC, Setting to rasClock/%d\n", bus);
105 ✗ modprobe("rtc-pcf2127");
106 ✗ devAd = "0x51";
107 ✗ if (newDev) {
108 ✗ addI2CDevice("pcf2127 0x51", bus);
109 }
110 ✗ break;
111 8 case 2:
112 8 printf("FPP - Configuring RTC, Setting to DS1307/%d\n", bus);
113 8 modprobe("rtc-ds1307");
114 8 devAd = "0x68";
115
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
8 if (newDev) {
116 6 addI2CDevice("ds1307 0x68", bus);
117 }
118 8 break;
119 ✗ case 3:
120 ✗ printf("FPP - Configuring RTC, Setting to mcp7941x/%d\n", bus);
121 // modprobe("");
122 ✗ devAd = "0x6f";
123 ✗ if (newDev) {
124 ✗ addI2CDevice("mcp7941x 0x6f", bus);
125 }
126 ✗ break;
127 ✗ case 4:
128 ✗ printf("FPP - Configuring RTC, Setting to pcf8523/%d\n", bus);
129 ✗ modprobe("rtc-pcf8523");
130 ✗ devAd = "0x68";
131 ✗ if (newDev) {
132 ✗ addI2CDevice("pcf8523 0x68", bus);
133 }
134 ✗ break;
135 ✗ case 5:
136 ✗ printf("FPP - Configuring RTC, Setting to pcf85363/%d\n", bus);
137 ✗ modprobe("rtc-pcf85363");
138 ✗ devAd = "0x51";
139 ✗ if (newDev) {
140 ✗ addI2CDevice("pcf85363 0x51", bus);
141 }
142 ✗ break;
143 ✗ case 6:
144 ✗ printf("FPP - Configuring RTC, Setting to pcf8563/%d\n", bus);
145 ✗ modprobe("rtc-pcf8563");
146 ✗ devAd = "0x51";
147 ✗ if (newDev) {
148 ✗ addI2CDevice("pcf8563 0x51", bus);
149 }
150 ✗ break;
151 ✗ default:
152 ✗ printf("FPP - Configuring RTC, None Selected");
153 ✗ return false;
154 }
155 8 return true;
156 }
157 8 static std::string getRTCDev() {
158
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 std::string rtc = "/dev/rtc0";
159
160 #ifdef PLATFORM_BBB
161
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 if (FileExists("/sys/class/rtc/rtc0/name")) {
162
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
4 std::string drv = GetFileContents("/sys/class/rtc/rtc0/name");
163
3/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
4 if (contains(drv, "omap_rtc")) {
164 // built in OMAP RTC, (not battery backed, kind of useless)
165
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 rtc = "/dev/rtc1";
166 ✗ } else if (contains(drv, " 0-00")) {
167 // Beaglebone Green Gateway has a ds1307 on i2c0
168 // but no battery (by default) so cape would
169 // be rtc1 and that's what we should use
170 ✗ rtc = "/dev/rtc1";
171 }
172 2 }
173 #elif defined(PLATFORM_BB64)
174
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 if (FileExists("/sys/class/rtc/rtc0/name")) {
175
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
6 std::string drv = GetFileContents("/sys/class/rtc/rtc0/name");
176
3/6
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 3 times.
6 if (contains(drv, "rtc-ti-k3")) {
177 // onboard non-battery-backed RTC, we'll be configuring rtc1
178 ✗ rtc = "/dev/rtc1";
179 }
180 3 }
181 #elif defined(PLATFORM_PI)
182
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 if (FileExists("/sys/class/rtc/rtc0/name")) {
183
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 std::string drv = GetFileContents("/sys/class/rtc/rtc0/name");
184
6/20
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✓ Branch 10 taken 3 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 3 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✓ Branch 15 taken 3 times.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
9 if (contains(drv, ":rpi_rtc") && FileExists("/dev/rtc1")) {
185 // Raspberry Pi 5 RTC, we are configuring the cape clock which would be rtc1
186 ✗ rtc = "/dev/rtc1";
187 }
188 3 }
189 #endif
190 8 return rtc;
191 ✗ }
192
193 8 int main(int argc, char* argv[]) {
194 // Which RTC was detected, which driver was loaded, and what time came back
195 // are exactly what a "clock is wrong after a power cycle" report needs, and
196 // journald on these boxes is RAM-only -- so tee it into fppd.log alongside
197 // fppinit's boot output, in time order, while still printing to the console
198 // for `journalctl -u fpprtc` and for a hand-run `fpprtc`.
199 // (the media dir is hardcoded here as it is throughout this file - FPPINIT.h
200 // has the same constant, but including it collides with this file's own static
201 // exec()/modprobe() helpers.)
202
5/10
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 8 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 8 times.
✗ Branch 9 not taken.
40 teeOutput(getRawMediaDir() + "/logs/fppd.log", "fpprtc", "RTC", getpid());
203
204
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 std::string rtc = getRTCDev();
205 8 std::string dev;
206
3/6
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 8 times.
8 if (!setupRTC(dev, !FileExists(rtc))) {
207 ✗ return 0;
208 }
209 8 int count = 0;
210 // Bounded at 1 s: with an RTC selected but no chip answering, the device
211 // never appears, and this oneshot unit holds up sysinit (and with it fppd
212 // and apache) for as long as it runs.
213
7/8
✓ Branch 0 taken 608 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 606 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 600 times.
✓ Branch 5 taken 6 times.
✓ Branch 6 taken 600 times.
✓ Branch 7 taken 8 times.
608 while (!FileExists(rtc) && count < 100) {
214
1/2
✓ Branch 0 taken 600 times.
✗ Branch 1 not taken.
600 std::this_thread::sleep_for(std::chrono::milliseconds(10));
215 600 count++;
216 }
217
3/4
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 6 times.
8 if (FileExists(rtc)) {
218
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 std::string action = "get";
219
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (argc > 1) {
220 ✗ action = argv[1];
221 }
222
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if (action == "set") {
223 ✗ hwclock("-w", rtc.c_str());
224 } else {
225
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 std::string ret = hwclock("-s", rtc.c_str());
226 // printf("%s\n", ret.c_str());
227
3/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 2 times.
4 if (contains(ret, "Remote I/O error")) {
228 ✗ removeI2CDevice(dev, getBus());
229 } else {
230 #if defined(PLATFORM_BBB) || defined(PLATFORM_BB64)
231 // Set the built-in RTC (rtc0, not battery backed) to the time
232 // just read from the cape's. This wrote `rtc` -- the cape's
233 // own clock -- back to itself, and so never set the built-in
234 // one. Only after a clean read (hwclock prints nothing then):
235 // otherwise the time copied would be the early-boot guess.
236
6/12
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
2 if (rtc != "/dev/rtc0" && ret.empty() && FileExists("/dev/rtc0")) {
237
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 hwclock("-w", "/dev/rtc0");
238 }
239 #endif
240
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
4 exec("cp /opt/fpp/etc/update-RTC /etc/cron.daily/");
241 }
242 2 }
243
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
8 } else if (!dev.empty()) {
244
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 removeI2CDevice(dev, getBus());
245 }
246 8 auto t = std::time(nullptr);
247 struct tm tm;
248 8 localtime_r(&t, &tm);
249
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 std::stringstream sstr;
250
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 sstr << std::put_time(&tm, "%a %b %d %H:%M:%S %Z %Y");
251
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 std::string str = sstr.str();
252
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 printf("%s\n", str.c_str());
253 8 return 0;
254 8 }
255