Enabling real s2idle suspend on the RG34XX SP with ROCKNIX

I managed to get real Linux suspend working on the Anbernic RG34XX SP running ROCKNIX.

By default, ROCKNIX disables hardware suspend on H700 devices and uses its own fake-suspend implementation. The kernel does expose s2idle, but enabling it directly caused the console to hang during resume.

The problem turned out to be asynchronous device power management. Disabling it allows the kernel to complete suspend and resume normally.

Tested configuration

This may also work on other H700 devices, but I have only tested it on the RG34XX SP.

The problem

The kernel reports:

cat /sys/power/state
freeze mem disk

cat /sys/power/mem_sleep
[s2idle]

This means that the kernel supports suspend-to-idle.

However, enabling it directly:

echo freeze > /sys/power/state

caused the console to hang during resume.

The screen backlight came back and the previous EmulationStation frame was visible, but the system itself was still frozen. The suspend operation never returned to userspace.

The fix

The default value of:

/sys/power/pm_async

is:

1

This allows Linux to suspend and resume independent devices in parallel.

On the tested H700 configuration, that appears to cause a race or incorrect dependency ordering during resume.

Disabling asynchronous device power management fixes the issue:

echo 0 > /sys/power/pm_async

After applying this setting, the console successfully completed multiple suspend and resume cycles.

A successful kernel log ends with:

PM: suspend entry (s2idle)
...
Restarting tasks: Done
PM: suspend exit

Installation

Connect to the console over SSH as root.

It is a good idea to keep SSH enabled while testing, in case the device needs to be force-rebooted.

1. Create the autostart script

mkdir -p /storage/.config/autostart

cat > /storage/.config/autostart/999-hardware-suspend <<'EOF'
#!/bin/sh

# H700 may hang when device suspend/resume callbacks run asynchronously.
echo 0 > /sys/power/pm_async

# Enable real Linux s2idle instead of ROCKNIX fake suspend.
/usr/bin/suspendmode freeze

# Reload logind after the configuration has been applied.
systemctl restart systemd-logind
EOF

chmod +x /storage/.config/autostart/999-hardware-suspend

2. Override the ROCKNIX power-button configuration

ROCKNIX creates this file during boot:

/storage/.config/logind.conf.d/login.conf

It contains:

[Login]
HandlePowerKey=ignore
HandleSuspendKey=ignore

Do not edit this file directly, because ROCKNIX may recreate it during boot.

Instead, create another configuration file whose name sorts after login.conf:

mkdir -p /storage/.config/logind.conf.d

cat > /storage/.config/logind.conf.d/zz-hardware-suspend.conf <<'EOF'
[Login]
HandlePowerKey=suspend
HandleSuspendKey=suspend
HandleLidSwitch=suspend
HandleLidSwitchExternalPower=suspend
EOF

The zz- prefix is important.

A name such as:

99-hardware-suspend.conf

is not sufficient, because login.conf sorts later and its HandlePowerKey=ignore setting takes precedence.

3. Remove stale fake-suspend state

rm -f /var/run/power-fake-suspend-active.flag
rm -f /var/run/lid-closed.flag
rm -f /var/run/shutdown-delay.flag.*

4. Reboot

reboot

The settings can also be applied without rebooting:

echo 0 > /sys/power/pm_async
/usr/bin/suspendmode freeze
systemctl restart systemd-logind

Verification

Check that asynchronous device PM is disabled:

cat /sys/power/pm_async

Expected result:

0

Check the selected suspend mode:

get_setting system.suspendmode

Expected result:

freeze

Inspect the merged systemd-logind configuration:

systemd-analyze cat-config systemd/logind.conf \
    | grep -E 'HandlePowerKey|HandleSuspendKey|HandleLidSwitch'

The final active values should be:

HandlePowerKey=suspend
HandleSuspendKey=suspend
HandleLidSwitch=suspend
HandleLidSwitchExternalPower=suspend

There may be older conflicting values earlier in the output. What matters is the last occurrence of each option.

Testing

First, test suspend manually over SSH:

sync
echo freeze > /sys/power/state

Press the Power button to wake the console.

Then test the normal controls:

Result

Success: closing the lid now enters real Linux suspend rather than ROCKNIX fake suspend.

Opening the lid resumes the console normally. EmulationStation continues running, the buttons respond, Wi-Fi reconnects, and Bluetooth reinitializes.

Repeated suspend and resume cycles also work correctly.

Checking the kernel log

After a successful suspend and resume cycle:

dmesg | tail -n 100

Look for:

PM: suspend entry (s2idle)
...
PM: suspend exit

The important line is:

PM: suspend exit

It confirms that the kernel completed resume.

Wi-Fi will usually disconnect before suspend and reconnect afterward.

Bluetooth firmware may also be loaded again:

Bluetooth: hci0: RTL: loading rtl_bt/rtl8821cs_fw.bin

USB messages such as these may appear:

usb usb2: root hub lost power or was reset
usb usb3: root hub lost power or was reset

They are not necessarily errors if the resume process completes successfully.

What kind of suspend is this?

This enables Linux s2idle.

It is not deep suspend-to-RAM.

The current H700 kernel exposes only:

[s2idle]

and does not provide a deep option.

During s2idle:

It will probably consume more power than true deep suspend, but it is still a real kernel-managed suspend state rather than a userspace simulation.

Why does pm_async=0 help?

With the default value:

pm_async = 1

Linux may run power-management callbacks for several devices in parallel.

This is normally faster, but it depends on correct device dependency information.

On the tested H700 setup, parallel resume causes the kernel to hang before userspace is unfrozen.

Setting:

echo 0 > /sys/power/pm_async

forces the kernel to process device suspend and resume callbacks sequentially.

This may make suspend and resume slightly slower, but it does not affect performance during normal gameplay.

Rollback

To restore the default ROCKNIX behavior:

rm -f /storage/.config/autostart/999-hardware-suspend
rm -f /storage/.config/logind.conf.d/zz-hardware-suspend.conf

/usr/bin/suspendmode off
echo 1 > /sys/power/pm_async

reboot

Debugging a failed resume

A visible EmulationStation frame does not necessarily mean that Linux resumed successfully.

It may only be the old framebuffer image with the backlight switched on.

To check whether the kernel actually returns from suspend:

rm -f /storage/suspend-stage.log

sh -c '
echo "before: $(date)" > /storage/suspend-stage.log
sync

echo freeze > /sys/power/state

echo "after: $(date)" >> /storage/suspend-stage.log
sync
'

After a forced reboot:

cat /storage/suspend-stage.log

If the file contains only:

before: ...

the kernel never completed resume.

If it contains both:

before: ...
after: ...

the kernel resumed successfully, and the remaining issue is likely in userspace or the input stack.