Disabling CPU before suspend and enabling it after wake up

Check for any entries in the journal log after the sleep/suspend command was issued. There must me something in the log

Here’s one snippet of the log with relevant stuff:

May 10 00:07:14 fedora systemd-sleep[3063]: Waking up from suspend…
May 10 00:07:14 fedora systemd[1]: packagekit.service: Deactivated successfully.
May 10 00:07:14 fedora systemd-sleep[3067]: sh: echo 1 > /sys/devices/system/cpu/cpu5/online: No such file or directory
May 10 00:07:14 fedora systemd-sleep[3068]: /usr/lib/systemd/system-sleep/fix-macbook-wakeup: line 10: required: command not found
May 10 00:07:14 fedora (sd-executor)[3062]: /usr/lib/systemd/system-sleep/fix-macbook-wakeup failed with exit status 127.

Weird that it cant find /cpu5/online . just to check i ran:

[sblfedora@fedora ~]$ cat /sys/devices/system/cpu/cpu5/online
1

also a longer sleep/wakeup but missing the error i’ve shown above:

Can you paste the above script? What is there on line 10 ?

Thanks for the tip.

Ii inspired me to write this gist:

apple_sleep 2023-11-15

Explanation

Fedora Linux on Apple MacBook might not be able to wake from sleep.
(Or it might eventually wake up, but it can take several minutes.)

This packageapple_sleep”" is a workaround using “isolation” of CPUs, that allows the laptop to wake up from
sleep within a normal timeframe.

I have no idea why it works, but I found the trick online - and polished the implementation as far as my Linux-fu
allows. And it helps my Fedora-ized MacBook wake up from sleep.

There was some uncertainty about which cpu-num to isolate, so I made a script that isolates all but the first cpu;
cpu0, and this seems to work … speedily.

The package consists of a Bash script and a systemd service file.

Usage

Put the files where you prefer, and edit the service file to point to the script.
Enable the service, and sleep.target will trigger the service to source the script when the laptop goes to sleep, and
again when it wakes up.

In my case I simply “link-enable” like so:

[root@sofa /]# systemctl enable /opt/optulation/mixed/apple_sleep/apple_sleep.service
Created symlink /etc/systemd/system/apple_sleep.service → /opt/optulation/mixed/apple_sleep/apple_sleep.service.
Created symlink /etc/systemd/system/sleep.target.wants/apple_sleep.service → /opt/optulation/mixed/apple_sleep/apple_sleep.service.

The Bash script does not need to be executable.

The script logs to ‘/tmp/apple_sleep.log’.
The log shows the status of the CPUs before and after the script runs.

21:24:17 START. cpu_statuses: 1:0 2:0 3:0 4:0 5:0 6:0 7:0 
21:24:23 STOP. cpu_statuses: 1:1 2:1 3:1 4:1 5:1 6:1 7:1 

“START” is when sleep.target is triggered -ie. when the laptop goes to sleep.

References

tested on:

  • Fedora 39 on MacBook Pro (Retina, 15-inch, Late 2013), MacBookPro11,2
  • Fedora 38 on MacBook Pro (Retina, 13-inch, Late 2013), MacBookPro11,1

apple_sleep.service:

[Unit]
Description=Isolate cpu cores before sleep and restore after wake
Documentation=https://gist.github.com/jakob-hede/66e9f3439d0891f090fe99daef45cf0d
PartOf=sleep.target

[Service]
Type=simple
RemainAfterExit=yes
# Edit to customize script location:
Environment="APPLE_SLEEP_SCRIPT=/opt/optulation/mixed/apple_sleep/apple_sleep.sh"
ExecStart=/bin/bash -c '. ${APPLE_SLEEP_SCRIPT} START'
ExecStop=/bin/bash -c '. ${APPLE_SLEEP_SCRIPT} STOP'

[Install]
WantedBy=sleep.target
#

apple_sleep.sh:

#!/bin/bash

# Documentation=https://gist.github.com/jakob-hede/66e9f3439d0891f090fe99daef45cf0d
# You must be root to run this script.
execute() {
  debug() {
    # Change to 'false' to disable debug output
    true && printf "\e[35m%s\e[0m\n" "$*"
  }

  fail() {
    printf "\e[31m%s\e[0m\n" "$*"
    exit 1
  }

  is_apple() {
    declare system_vendor txt
    declare -i response
    system_vendor=$(cat /sys/class/dmi/id/sys_vendor 2>/dev/null)
    if [[ "$system_vendor" == *Apple* ]]; then
      response=0
      txt="Running on Apple hardware"
    else
      response=1
      txt="NOT running on Apple hardware"
    fi
    debug "${txt}"
    return "${response}"
  }

  is_root() {
    [[ $UID -eq 0 ]]
  }

  check_circumstances() {
    is_root || fail "You must be root to run this script."
    is_apple || fail "You must be running on Apple hardware to run this script."
  }

  argparse() {
    indx=-99
    if [[ $1 == 'START' ]]; then
      indx=0
      num=0
    elif [[ $1 == 'STOP' ]]; then
      indx=0
      num=1
    else
      fail "Usage: $0 [START|STOP]"
    fi
  }

  logify() {
    declare log_file timestamp
    timestamp=$(date +%H:%M:%S)
    log_file="/tmp/${name}.log"
    echo "${timestamp} ${*}. cpu_statuses: ${cpu_statuses}" >>"${log_file}"
    debug "${name} ${timestamp} ${log_file} $* ${cpu_statuses}"
  }

  isolate() {
    declare directory file
    while ((indx > -1)); do
      ((indx++)) # We do not isolate CPU0, so we start at 1.
      directory="/sys/devices/system/cpu/cpu${indx}"
      [[ ! -d $directory ]] && indx=-99 && break
      file="${directory}/online"
      is_apple >/dev/null && echo "${num}" >"${file}"
      cpu_statuses+="${indx}:$(cat ${file}) "
    done
  }

  flow() (# <- subshell !!!
    # Subshell ensures not to break some system stuff by doing 'exit'.
    declare -i num indx
    declare name cpu_statuses
    name="$(basename "${BASH_SOURCE[0]%.*}")"
    debug "${name} FLOW $*"
    check_circumstances # Optionally disable while testing
    argparse "$@"
    #    debug "solitude: ${num}"
    isolate
    logify "$@"
  )
  flow "$@"
}

execute "$@"
printf '\e[34mapple_sleep DONE %s\e[0m\n' "$?"
#