Skip to content

Commit 3efa99b

Browse files
committed
sleep/resume: Set fan control to auto before sleep
On some systems, like the X1 Carbon Gen 12 and T14s Gen 5, unless the fan is set to auto before suspension it continues running at the set fan speed during suspend. See #36. Introduce a mechanism to disable zcfan's fan setting and set the fan level to auto when sleep is imminent, and reenable it when we have resumed. systemd users can do this transparently using the supplied service files, others can manually signal with SIGPWR and SIGUSR2 at their convenience.
1 parent 1cc39cf commit 3efa99b

File tree

5 files changed

+76
-4
lines changed

5 files changed

+76
-4
lines changed

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ install: all
4848
mkdir -p $(DESTDIR)$(bindir)/
4949
$(INSTALL) -pt $(DESTDIR)$(bindir)/ $(EXECUTABLES)
5050
$(INSTALL) -Dp -m 644 $(SERVICE) $(DESTDIR)$(prefix)/lib/systemd/system/$(SERVICE)
51+
$(INSTALL) -Dp -m 644 zcfan-sleep.service $(DESTDIR)$(prefix)/lib/systemd/system/zcfan-sleep.service
52+
$(INSTALL) -Dp -m 644 zcfan-resume.service $(DESTDIR)$(prefix)/lib/systemd/system/zcfan-resume.service
5153
$(INSTALL) -Dp -m 644 zcfan.1 $(DESTDIR)$(mandir)/man1/zcfan.1
5254

5355
lint:

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ Run `make`.
6969
- At runtime: `rmmod thinkpad_acpi && modprobe thinkpad_acpi fan_control=1`
7070
- By default: `echo options thinkpad_acpi fan_control=1 > /etc/modprobe.d/99-fancontrol.conf`
7171
3. Run `zcfan` as root (or use the `zcfan` systemd service provided)
72+
4. If you run a laptop which keeps the fan running during suspend, you will also
73+
want to send `SIGPWR` before sleep and `SIGUSR2` before wakeup to avoid
74+
that. For systemd users, enable the `zcfan-sleep.service` and
75+
`zcfan-resume.service` units to do that automatically.
7276

7377
## Disclaimer
7478

zcfan-resume.service

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[Unit]
2+
Description=Reload zcfan after waking up from suspend
3+
After=sysinit.target
4+
After=suspend.target
5+
After=suspend-then-hibernate.target
6+
After=hybrid-sleep.target
7+
After=hibernate.target
8+
9+
[Service]
10+
Type=oneshot
11+
ExecStart=/usr/bin/pkill -x -USR2 zcfan
12+
13+
[Install]
14+
WantedBy=sleep.target

zcfan-sleep.service

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[Unit]
2+
Description=Notify zcfan of imminent sleep
3+
Before=sleep.target
4+
5+
[Service]
6+
Type=oneshot
7+
ExecStart=/usr/bin/pkill -x -PWR zcfan
8+
# Executing the signal handler races with sleep, so delay a bit
9+
ExecStart=sleep 1
10+
11+
[Install]
12+
WantedBy=sleep.target

zcfan.c

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ static const unsigned int tick_hysteresis = 3;
5858
static char output_buf[512];
5959
static const struct Rule *current_rule = NULL;
6060
static volatile sig_atomic_t run = 1;
61+
static volatile sig_atomic_t pending_sleep = 0;
62+
static volatile sig_atomic_t pending_resume = 0;
6163
static int first_tick = 1; /* Stop running if errors are immediate */
6264
static glob_t temp_files;
6365

@@ -345,6 +347,16 @@ static void stop(int sig) {
345347
run = 0;
346348
}
347349

350+
static void handle_sigpwr(int sig) {
351+
(void)sig;
352+
pending_sleep = 1;
353+
}
354+
355+
static void handle_sigusr2(int sig) {
356+
(void)sig;
357+
pending_resume = 1;
358+
}
359+
348360
int main(int argc, char *argv[]) {
349361
const struct sigaction sa_exit = {
350362
.sa_handler = stop,
@@ -363,6 +375,15 @@ int main(int argc, char *argv[]) {
363375
print_thresholds();
364376
expect(sigaction(SIGTERM, &sa_exit, NULL) == 0);
365377
expect(sigaction(SIGINT, &sa_exit, NULL) == 0);
378+
expect(
379+
sigaction(SIGPWR,
380+
&(const struct sigaction){.sa_handler = handle_sigpwr},
381+
NULL) == 0);
382+
expect(
383+
sigaction(SIGUSR2,
384+
&(const struct sigaction){.sa_handler = handle_sigusr2},
385+
NULL) == 0);
386+
366387
expect(setvbuf(stdout, output_buf, _IOLBF, sizeof(output_buf)) == 0);
367388

368389
if (!full_speed_supported()) {
@@ -374,16 +395,35 @@ int main(int argc, char *argv[]) {
374395
write_watchdog_timeout(watchdog_secs);
375396
populate_temp_files();
376397

398+
int fan_control_enabled = 1;
399+
377400
while (run) {
378-
enum set_fan_status set = set_fan_level();
379-
if (set != FAN_LEVEL_SET) {
380-
maybe_ping_watchdog();
401+
if (fan_control_enabled) {
402+
enum set_fan_status set = set_fan_level();
403+
if (set != FAN_LEVEL_SET) {
404+
maybe_ping_watchdog();
405+
}
381406
}
382-
383407
if (run) {
384408
sleep(1);
385409
first_tick = 0;
386410
}
411+
if (pending_sleep) {
412+
pending_sleep = 0;
413+
info("Fan control disabled for sleep\n");
414+
if (write_fan_level("auto") == 0)
415+
write_watchdog_timeout(0);
416+
fan_control_enabled = 0;
417+
}
418+
if (pending_resume) {
419+
pending_resume = 0;
420+
info("Fan control enabled for resume\n");
421+
fan_control_enabled = 1;
422+
expect(current_rule);
423+
write_fan_level(current_rule->tpacpi_level);
424+
write_watchdog_timeout(watchdog_secs);
425+
}
426+
387427
}
388428

389429
globfree(&temp_files);

0 commit comments

Comments
 (0)