Bug Summary

File:/shared/playproj/i2c/src/dw/dw_suspend_resume.c
Warning:line 254, column 11
Value stored to 'result' during its initialization is never read

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-pc-linux-gnu -O2 -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name dw_suspend_resume.c -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 2 -fhalf-no-semantic-interposition -mframe-pointer=none -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/shared/playproj/i2c/src/dw -fcoverage-compilation-dir=/shared/playproj/i2c/src/dw -resource-dir /usr/lib/llvm-21/lib/clang/21 -D HAVE_CONFIG_H -I . -I ../.. -I /usr/include/libdrm -I /usr/include/glib-2.0 -I /usr/lib/x86_64-linux-gnu/glib-2.0/include -I /usr/include/sysprof-6 -I ../../src -I ../../src/public -D _GLIBCXX_ASSERTIONS -D _GNU_SOURCE -D PIC -internal-isystem /usr/lib/llvm-21/lib/clang/21/include -internal-isystem /usr/local/include -internal-isystem /usr/lib64/gcc/x86_64-linux-gnu/15/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -Wno-compound-token-split-by-macro -std=c11 -ferror-limit 19 -fgnuc-version=4.2.1 -fskip-odr-check-in-gmf -vectorize-loops -vectorize-slp -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o dw_suspend_resume.plist -x c dw_suspend_resume.c
1/** @file dw_suspend_resume.c
2 * Detection of system resume from sleep, combining every available method
3 *
4 * Consults the logind PrepareForSleep signals recorded by dbus_util.c together
5 * with the clock comparison in util/suspend_resume_util.c. The clock half is
6 * separate because it needs no dbus and has a util-layer consumer; this half
7 * does need dbus and belongs with the display watch code that drives it.
8 */
9
10// Copyright (C) 2026 Sanford Rockowitz <rockowitz@minsoft.com>
11// SPDX-License-Identifier: GPL-2.0-or-later
12
13#include "config.h"
14#include <inttypes.h>
15#include <stdbool.h>
16#include <stdint.h>
17#include <time.h>
18#include <syslog.h>
19#include "util/debug_util.h"
20#include "util/dbus_util.h"
21#include "util/report_util.h"
22#include "util/string_util.h"
23#include "util/syslog_util.h"
24#include "util/timestamp.h"
25#include "util/suspend_resume_util.h"
26
27#include "dw_suspend_resume.h"
28
29//
30// Combined CLOCKTIME/BOOTTIME and dbus algorithm
31//
32
33/** Determines whether the system recently resumed from sleep, consulting
34 * every available detection method.
35 *
36 * @param within_ms interval that defines "recently"
37 * @param millisec_since_loc if non-NULL, where to return the number of
38 * milliseconds since the resume, UINT64_MAX if
39 * no recent resume
40 * @param detection_loc if non-NULL, where to return which method
41 * answered. A caller that reports the pause it
42 * takes needs this: only two of the three
43 * methods establish that the system has actually
44 * slept. See resume_detection_description().
45 * @return true if a resume from sleep occurred within the past **within_ms**
46 *
47 * @remark
48 * Callers pause for the time remaining in their own interval, rather than
49 * this function pausing, because each has its own sleep and logging needs.
50 *
51 * ddcutil detects a resume from sleep three ways. They are complementary,
52 * not redundant, and none alone is sufficient.
53 *
54 * The **dbus** method (dbus_util.c) records when the logind
55 * **PrepareForSleep(false)** signal is received. It is precise, it is
56 * process wide, and it covers three cases the clock method cannot:
57 * - **Program start.** ldbus_elapsed_since_resume_from_sleep_mark_start()
58 * deliberately treats the start of the sleep watch thread like a resume,
59 * because the window just after boot or login has the same transient
60 * EACCES race on /dev/i2c opens that the window after resume does. The
61 * clock method reports nothing at startup: no sleep has accumulated.
62 * - **Short suspends.** The clock method needs more than 1 second of
63 * accumulated sleep before it reports a resume at all.
64 * - **Shared timing.** last_resume_from_sleep_ns is a single process wide
65 * value, so every thread computes the same elapsed time and pauses only
66 * the remainder of the interval. The clock method's state is per thread,
67 * so threads detect independently and each starts its own pause from its
68 * own first look, which can multiply the pauses taken.
69 * Its weakness is latency. The signal is delivered asynchronously, and the
70 * display watch thread can be woken by udev and begin reopening buses before
71 * the dbus thread has processed it. That interval is exactly when udev has
72 * not yet reapplied the /dev/i2c ACLs, so relying on dbus alone mistakes a
73 * transient permission failure for a permanent one.
74 *
75 * The **clock** method (recently_resumed_from_sleep_by_clocktime()) compares
76 * CLOCK_BOOTTIME against CLOCK_MONOTONIC. It cannot be late: the divergence
77 * is already present the instant the thread runs again, and no signal
78 * delivery is involved. Its weaknesses mirror the strengths above. It is
79 * coarse, per thread, silent at startup, and its reference point is when a
80 * thread happened to look, not when the resume occurred.
81 *
82 * dbus is therefore preferred where it is trustworthy, and the clock method
83 * covers the case it cannot: the signal not yet delivered.
84 *
85 * The **open sleep cycle** method uses the other logind signal,
86 * **PrepareForSleep(true)**, whose timestamp dbus_util.c records alongside
87 * the resume timestamp. A cycle opened by that signal and not yet closed by
88 * its counterpart means this process is somewhere inside a suspend, and any
89 * thread running there is treated as having just resumed. It covers what
90 * neither of the others can: a suspend that stopped this process without
91 * accumulating sleep the clock method can see. Its weakness is that it
92 * depends on the closing signal to know the cycle is over; the sleep watch
93 * thread's own running time bounds what happens when that signal never
94 * arrives. See the body and ldbus_in_open_sleep_cycle().
95 *
96 * Neither elapsed time can simply be trusted over the other:
97 * - Taking whichever is smaller prefers the clock method systematically,
98 * since its reference point is the later one whenever detection lags the
99 * resume, and pauses for the full interval well after the resume: resume
100 * at T, dbus signal at T+50 ms, first call on this thread at T+3000 ms,
101 * elapsed reported as 0 rather than 3000.
102 * - Taking dbus whenever it is within the interval lets a timestamp that
103 * predates the suspend, from an earlier resume or from
104 * ldbus_elapsed_since_resume_from_sleep_mark_start(), mask a detection
105 * that is genuinely fresh, and pauses too little.
106 *
107 * The tie is broken on whether the clock method detected the resume on this
108 * very call, which it reports through its out parameter. A detection now
109 * means this thread had not yet accounted for the sleep, so dbus may not
110 * have processed the signal either and its timestamp may predate the
111 * suspend; the clock is preferred. Inside the grace window the resume has
112 * already been observed once, dbus has had time to catch up, and it is the
113 * more accurate of the two. See the body for why the two errors are not
114 * symmetric. Note that the decision deliberately does not depend on
115 * within_ms, which is tunable and must not determine correctness.
116 */
117bool_Bool recently_resumed_from_sleep(int within_ms, uint64_t * millisec_since_loc,
118 Resume_Detection * detection_loc)
119{
120 bool_Bool debug = false0;
121 bool_Bool resumed = false0;
122 uint64_t millisec_since = UINT64_MAX(18446744073709551615UL);
123 Resume_Detection detection = RESUME_DETECTED_NONE;
124
125 // Called on every invocation, whatever dbus reports, so that this thread's
126 // baseline stays current and its grace window opens when the resume is
127 // first observed here. Otherwise the first call on a thread where dbus
128 // always won the race would report a resume that was long since handled.
129 bool_Bool clock_detected_now = false0;
130 bool_Bool resumed_by_clocktime = recently_resumed_from_sleep_by_clocktime(&clock_detected_now);
131
132#ifdef USE_DBUS1
133 // An open sleep cycle, i.e. a PrepareForSleep(true) not yet matched by a
134 // PrepareForSleep(false), is reported as a resume whatever the clocks say.
135 //
136 // Once the kernel has frozen user space this process cannot run again until
137 // it is thawed, so a thread executing inside an open cycle has either been
138 // thawed already or is in the interval between the signal and the freeze.
139 // Neither other source covers the first case:
140 // - dbus has not yet dispatched PrepareForSleep(false). That is the
141 // latency described above, and the resume timestamp it would report
142 // still predates the suspend.
143 // - the clock method needs more than a second of accumulated sleep, which
144 // the cycle need never have produced. Freezing user space precedes
145 // timekeeping_suspend() and thawing follows timekeeping_resume(), so a
146 // suspend that is aborted, or whose device callbacks are slow (hybrid
147 // graphics, notably), can stop this process for ten seconds while
148 // BOOTTIME and MONOTONIC stay in lockstep and nothing is detected. The
149 // /dev/i2c ACLs are dropped and reapplied around the whole cycle, not
150 // around its sleeping part, so the EACCES window is there regardless.
151 //
152 // Reported as elapsed 0, so the caller pauses its full interval. While the
153 // cycle is open there is no better reference point: the prepare timestamp
154 // marks the start of the cycle, not the resume, and measuring from it would
155 // count the entire suspend as already elapsed and pause not at all.
156 //
157 // Before the freeze this reports a resume that has not occurred. That
158 // costs one interval per call over a window logind bounds by
159 // InhibitDelayMaxSec, 5 seconds by default, and ddcutil holds no delay
160 // inhibitor, so the freeze normally follows the signal promptly. It also
161 // keeps the watch thread from opening buses while the GPU is being torn
162 // down, which is no worse a place to be idle.
163 //
164 // The rule holds until the matching signal arrives, or, should it never
165 // arrive, until the sleep watch thread has run long enough since the
166 // prepare signal to conclude it is not coming. See
167 // ldbus_in_open_sleep_cycle().
168 bool_Bool sleep_cycle_open = ldbus_in_open_sleep_cycle(NULL((void*)0));
169
170 // within_ms 0 asks whether a resume occurred within no time at all, and the
171 // answer must remain no: callers subtract millisec_since from within_ms.
172 if (sleep_cycle_open && within_ms > 0) {
173 resumed = true1;
174 millisec_since = 0;
175 detection = RESUME_DETECTED_IN_SLEEP_CYCLE;
176 }
177
178 uint64_t dbus_elapsed_ms = NANOS2MILLIS(ldbus_elapsed_since_resume_from_sleep_ns());
179
180 // A detection on THIS call means sleep accumulated that this thread had
181 // not yet accounted for, so dbus may not have processed the corresponding
182 // signal either; its timestamp can predate the suspend, and measuring
183 // from it would pause far too little. The clock is preferred in that
184 // case. Within the grace window the resume has been observed at least
185 // once already, dbus has had time to catch up, and its timestamp is the
186 // more accurate of the two, so it is preferred there.
187 //
188 // The two errors are not symmetric, which is what settles the direction.
189 // Preferring the clock when dbus was in fact current costs one interval
190 // of extra pause, latency and nothing more. Preferring dbus when its
191 // timestamp is stale reopens the buses while udev has not yet reapplied
192 // the ACLs, which costs the whole EACCES retry ladder, up to
193 // max_eacces_retry_ms, plus a diagnostic dump in the system log.
194 //
195 // Deliberately not decided by comparing the two elapsed times, nor by
196 // comparing either against within_ms: within_ms is tunable and must not
197 // be what determines correctness.
198 //
199 // Not consulted when the cycle is open: its timestamp is then known to
200 // predate the suspend, and 0 is already the conservative answer.
201 if (!resumed && !clock_detected_now && dbus_elapsed_ms < (uint64_t) within_ms) {
202 resumed = true1;
203 millisec_since = dbus_elapsed_ms;
204 detection = RESUME_DETECTED_BY_DBUS;
205 }
206#endif
207
208 // Fallback, for the case dbus cannot cover: the signal has not yet been
209 // delivered, or the build has no dbus support.
210 //
211 // This branch is the one that reports a resume when the clock method is
212 // wrong, and it is reached whatever dbus says, so a false detection here
213 // is not masked by a correct dbus answer. It surfaced as
214 // test_dw_suspend_resume failing on the aarch64 worker at
215 // build.opensuse.org while passing on x86_64: the two assertions in that
216 // test expecting false are the only ones this branch can reach. The
217 // cause was in get_accumulated_sleep_ns(), not here or in the test --
218 // nothing about it is specific to aarch64, only to a machine whose
219 // BOOTTIME/MONOTONIC offset sits near zero, which is every worker that
220 // has never suspended.
221 if (!resumed && resumed_by_clocktime) {
222 uint64_t clock_elapsed_ms = millisec_since_resume_detected_by_clocktime();
223 if (clock_elapsed_ms < (uint64_t) within_ms) {
224 resumed = true1;
225 millisec_since = clock_elapsed_ms;
226 detection = RESUME_DETECTED_BY_CLOCKTIME;
227 }
228 }
229
230 if (millisec_since_loc)
231 *millisec_since_loc = millisec_since;
232 if (detection_loc)
233 *detection_loc = detection;
234 DBGF(debug, "within_ms=%d, millisec_since=%"PRIu64", detection=%s, returning %s",do { if (debug) simple_dbgmsg(debug, __func__, 236, "dw_suspend_resume.c"
, "within_ms=%d, millisec_since=%""l" "u"", detection=%s, returning %s"
, within_ms, millisec_since, resume_detection_description(detection
), sbool(resumed)); } while(0)
235 within_ms, millisec_since, resume_detection_description(detection),do { if (debug) simple_dbgmsg(debug, __func__, 236, "dw_suspend_resume.c"
, "within_ms=%d, millisec_since=%""l" "u"", detection=%s, returning %s"
, within_ms, millisec_since, resume_detection_description(detection
), sbool(resumed)); } while(0)
236 sbool(resumed))do { if (debug) simple_dbgmsg(debug, __func__, 236, "dw_suspend_resume.c"
, "within_ms=%d, millisec_since=%""l" "u"", detection=%s, returning %s"
, within_ms, millisec_since, resume_detection_description(detection
), sbool(resumed)); } while(0)
;
237 return resumed;
238}
239
240
241/** Returns a description of how a resume from sleep was detected, phrased as
242 * the opening clause of a message reporting it.
243 *
244 * An open sleep cycle is deliberately not described as a resume. The pause
245 * it causes may be taken before the system has slept at all, in the interval
246 * between PrepareForSleep(true) and the freeze, and a message claiming a
247 * resume there would contradict the machine's state in the system log at
248 * exactly the point where this subsystem is diagnosed from it.
249 *
250 * @param detection value reported by #recently_resumed_from_sleep()
251 * @return description, valid for the life of the program
252 */
253const char * resume_detection_description(Resume_Detection detection) {
254 char * result = "Unrecognized resume detection";
Value stored to 'result' during its initialization is never read
255 switch(detection) {
256 case RESUME_DETECTED_NONE:
257 result = "No recent resume from sleep"; break;
258 case RESUME_DETECTED_BY_DBUS:
259 result = "Recently resumed from sleep, per dbus signal"; break;
260 case RESUME_DETECTED_BY_CLOCKTIME:
261 result = "Recently resumed from sleep, per clock comparison"; break;
262 case RESUME_DETECTED_IN_SLEEP_CYCLE:
263 result = "Sleep cycle in progress"; break;
264 }
265 return result;
266}