| 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 |