00 Linux PM states — S0'dan S5'e
Linux, ACPI standardından esinlenen bir güç durum modeli kullanır. Her state farklı bir enerji/uyanma gecikmesi tradeoff'u sunar.
mem state'inin gerçek davranışı
# mem state hangi derinliği kullanıyor?
cat /sys/power/mem_sleep
# s2idle [deep] → köşeli parantez içindeki aktif
# ya da:
# [s2idle] deep
# deep (S2RAM) yerine s2idle (Connected Standby) seç
echo s2idle > /sys/power/mem_sleep
# gerçek S2RAM için
echo deep > /sys/power/mem_sleep
01 /sys/power/state — geçiş mekanizması
/sys/power/state dosyasına uyku modu adı yazmak, o moda geçişi başlatır. Komut döndüğünde sistem zaten uyanmış demektir.
# desteklenen state'leri görüntüle
cat /sys/power/state
# freeze mem disk
# freeze (s2idle / connected standby)
echo freeze > /sys/power/state
# suspend-to-RAM (S2RAM)
echo mem > /sys/power/state
# standby (hafif uyku, bazı platformlarda)
echo standby > /sys/power/state
# hibernation (swap'a yaz)
echo disk > /sys/power/state
Sistem uyku döngüsü (internal)
echo mem > /sys/power/state
│
├─ 1. senkronize_filesystems() — FS flush
├─ 2. freeze_processes() — kullanıcı proc'ları dondur
├─ 3. freeze_kernel_threads() — kernel thread'leri dondur
├─ 4. dpm_suspend_start() — driver .prepare() hook'ları
├─ 5. dpm_suspend() — driver .suspend() hook'ları
├─ 6. dpm_suspend_late() — driver .suspend_late()
├─ 7. dpm_suspend_noirq() — driver .suspend_noirq()
├─ 8. disable_nonboot_cpus() — CPU hotplug off
├─ 9. syscore_suspend() — son sistem çekirdek uyku
└─10. arch_suspend() — donanım uyku (PSCI/WFI)
... DONANIM UYKU ...
└─10. arch_resume()
├─ 9. syscore_resume()
├─ 8. enable_nonboot_cpus()
├─ 7. dpm_resume_noirq()
├─ 6. dpm_resume_early()
├─ 5. dpm_resume()
├─ 4. dpm_resume_end()
├─ 3. thaw_kernel_threads()
└─ 2. thaw_processes()
echo mem > /sys/power/state komutu başarıyla döndüğünde sistem artık uyanmış demektir — komut bloke olur, uyanınca devam eder. Hata olursa negatif errno döner ve dmesg'de hangi driver'ın başarısız olduğu görülür.
02 Wakeup sources — kayıt ve yönetim
Sistem suspend halindeyken onu uyandırabilecek kaynaklar "wakeup source" olarak kayıt edilmelidir. Kayıtsız bir interrupt sistemi uyandıramaz.
Kernel API
#include <linux/pm_wakeup.h>
/* Cihazı wakeup özellikli yap */
device_init_wakeup(&pdev->dev, true);
/* Wakeup event bildirimi (interrupt handler içinde) */
pm_wakeup_event(&pdev->dev, 500); /* 500ms wakeup lock */
/* Manuel wakeup source oluştur */
struct wakeup_source *ws;
ws = wakeup_source_register(NULL, "my-wakeup-source");
__pm_stay_awake(ws); /* wakeup işlemi devam ederken */
__pm_relax(ws); /* wakeup tamamlandığında */
wakeup_source_unregister(ws);
sysfs wakeup source yönetimi
# tüm wakeup source'ları listele
cat /sys/kernel/debug/wakeup_sources
# name active_count event_count wakeup_count expire_count
# rtc0 1 42 42 0
# gpio-keys 0 5 5 0
# bir cihazın wakeup'ını etkinleştir
cat /sys/bus/platform/devices/rtc0/power/wakeup
# enabled
echo enabled > /sys/bus/platform/devices/soc:gpio-wakeup/power/wakeup
echo disabled > /sys/bus/platform/devices/soc:gpio-wakeup/power/wakeup
# wakeup count — suspend öncesi ve sonrası karşılaştır (race condition önleme)
cat /sys/power/wakeup_count
# 7
# wakeup_count ile yarış koşulsuz suspend
WC=$(cat /sys/power/wakeup_count)
echo "$WC" > /sys/power/wakeup_count && echo mem > /sys/power/state
RTC alarm wakeup
# rtcwake ile belirli süre sonra uyanma
rtcwake -m mem -s 60
# Sistem 60 saniye sonra otomatik uyanacak
# belirli bir saat için
rtcwake -m mem -t $(date +%s -d "tomorrow 07:00")
# test: sistemi uyutmadan sadece RTC alarm kur
rtcwake -m no -s 30 # alarm kur, uyutma
cat /proc/driver/rtc # alarm time'ı kontrol et
03 pm_test — suspend adımlarını test et
pm_test, gerçekten uyumadan suspend/resume sırasını kısmen ya da tamamen simüle etmeni sağlar. Hangi sürücünün başarısız olduğunu bulmak için temel araçtır.
# mevcut pm_test seçenekleri
cat /sys/power/pm_test
# [none] core processors platform devices freezer
# pm_test seviyeleri (her biri öncekini içerir):
# none - normal suspend (test yok)
# core - syscore suspend'e kadar git, geri dön
# processors- CPU suspend dahil
# platform - platform firmware suspend dahil
# devices - tüm cihaz driver'larını test et
# freezer - process freeze'i test et
# devices seviyesinde test: driver'ları suspend et, uyanma olmadan dön
echo devices > /sys/power/pm_test
echo mem > /sys/power/state
# Sistem 5 saniye bekler, otomatik geri döner
# Başarısız driver dmesg'e yazılır
# testi bitir, normal moda dön
echo none > /sys/power/pm_test
# dmesg'de hata ara
dmesg | grep -E "(suspend|resume|PM:|failed)" | tail -30
Gerçek echo mem başarısız oluyorsa önce pm_test=freezer dene, sorun yoksa devices'a yükselt. Bu şekilde sorunlu katmanı izole edersin. Çoğu zaman bir USB host controller veya ağ sürücüsünün eksik .suspend() callback'i suçludur.
04 Driver suspend/resume hook'ları
Her platform driver, struct dev_pm_ops yapısı üzerinden suspend/resume callback'lerini kaydeder. Bu callback'lerin doğru sırayla çalışması tüm suspend/resume süreci için kritiktir.
dev_pm_ops callback'leri
#include <linux/platform_device.h>
#include <linux/pm.h>
static int mydev_suspend(struct device *dev)
{
struct mydev_priv *priv = dev_get_drvdata(dev);
/* donanımı uyku moduna al */
mydev_hw_suspend(priv);
/* wakeup etkin ise interrupt kaynağını koru */
if (device_may_wakeup(dev))
enable_irq_wake(priv->irq);
dev_dbg(dev, "suspended\n");
return 0;
}
static int mydev_resume(struct device *dev)
{
struct mydev_priv *priv = dev_get_drvdata(dev);
if (device_may_wakeup(dev))
disable_irq_wake(priv->irq);
/* donanımı yeniden başlat */
mydev_hw_resume(priv);
dev_dbg(dev, "resumed\n");
return 0;
}
/* noirq hook: interrupt'lar devre dışıyken çalışır */
static int mydev_suspend_noirq(struct device *dev)
{
/* interrupt gerektirmeyen son temizlik işlemleri */
return 0;
}
static const struct dev_pm_ops mydev_pm_ops = {
.suspend = mydev_suspend,
.resume = mydev_resume,
.suspend_noirq = mydev_suspend_noirq,
.resume_noirq = mydev_resume_noirq,
/* Runtime PM için */
.runtime_suspend = mydev_runtime_suspend,
.runtime_resume = mydev_runtime_resume,
};
static struct platform_driver mydev_driver = {
.driver = {
.name = "mydev",
.pm = &mydev_pm_ops,
},
.probe = mydev_probe,
.remove = mydev_remove,
};
Power domain
/* Power domain tanımı */
pd_vpu: power-domain@0 {
compatible = "fsl,imx8m-blk-ctrl";
#power-domain-cells = <1>;
};
/* Cihaz, power domain'e bağlı */
vpu: vpu@38300000 {
compatible = "nxp,imx8mq-vpu";
power-domains = <&pd_vpu 0>;
/* Suspend sırasında PM core bu domain'i otomatik kapatır */
};
Birçok gömülü SoC'ta simple-pm-bus driver'ı, clock ve power domain yönetimini otomatik yapar. Driver yazarken bu mekanizmayı kullanmak, manuel suspend/resume yerine daha az hata içerir.
05 Freezer — process dondurma
Suspend öncesinde tüm kullanıcı alanı süreçleri ve çoğu kernel thread dondurulur. Bu adım, suspend sırasında I/O'ları yarım bırakmaktan kaynaklanan tutarsızlıkları önler.
Freeze mekanizması
#include <linux/freezer.h>
#include <linux/kthread.h>
static int my_worker_thread(void *data)
{
/* Thread'i dondurulabilir yap */
set_freezable();
while (!kthread_should_stop()) {
/* Freeze isteği gelirse burada donur */
try_to_freeze();
/* ... asıl iş ... */
msleep_interruptible(100);
}
return 0;
}
Freeze sorunlarını tespit et
# hangi süreçler dondurulamıyor? (suspend başarısız mesajı)
dmesg | grep -i "freezing of tasks"
# PM: Freezing user space processes
# PM: Freezing of user space processes failed (elapsed 20.003 seconds)
# dondurulamamış süreçleri bul
dmesg | grep "Task refused to freeze"
# Task refused to freeze (pid:1234, name:myapp)
# process'in freeze durumunu kontrol et (/proc/PID/status)
grep "^Flags" /proc/1234/status
# Flags: 00400002 (PF_NOFREEZE bayrağı set ise dondurulamaz)
# dynamic debug ile freeze istatistiklerini izle
echo "file kernel/freezer.c +p" > /sys/kernel/debug/dynamic_debug/control
Kullanıcı alanı süreci freeze sinyaline 20 saniye içinde yanıt vermezse suspend iptal edilir. Uzun süre bloklanan sistem çağrılarında olan D-state (uninterruptible sleep) süreçler bu duruma yol açabilir. Uygulamada SA_RESTART olmayan signal handler'lar veya yanlış konfigüre edilmiş NFS mount'ları yaygın sebeplerdir.
06 Platform-specific: i.MX6, RPi, ACPI S3
Her platform suspend/resume için farklı firmware/hardware desteği gerektirir. Üç yaygın platform için özel notlar.
i.MX6 — SNVS ile RTC wakeup
/* i.MX6 Secure Non-Volatile Storage — RTC ve wakeup kontrolü */
snvs: snvs@020cc000 {
compatible = "fsl,sec-v4.0-mon", "syscon", "simple-mfd";
snvs_rtc: snvs-rtc-lp {
compatible = "fsl,sec-v4.0-mon-rtc-lp";
regmap = <&snvs>;
offset = <0x34>;
clocks = <&clks IMX6QDL_CLK_SNVS_LP>;
clock-names = "snvs-rtc";
interrupts = <GIC_SPI 19 IRQ_TYPE_LEVEL_HIGH>;
};
snvs_poweroff: snvs-poweroff {
compatible = "fsl,sec-v4.0-poweroff";
regmap = <&snvs>;
};
};
# i.MX6'da S2RAM için "mem" = "deep"
echo deep > /sys/power/mem_sleep
# RTC alarm 30 saniye sonraya ayarla
rtcwake -d rtc0 -m mem -s 30
# Wakeup kaynağını kontrol et
dmesg | grep -i "wakeup\|snvs"
Raspberry Pi — PMIC wakeup
# RPi 4/5'te suspend desteği (kernel 6.1+)
cat /sys/power/state
# freeze mem
# GPIO wakeup etkinleştir (örn. GPIO3 = pin 5)
echo enabled > /sys/class/gpio/gpio3/power/wakeup
# ya da /boot/config.txt ile:
# dtparam=wakeup-gpio=3
# suspend
echo mem > /sys/power/state
# GPIO3'ü GND'ye çekmek sistemi uyandırır
# RPi 5'te suspend LED göstergesi
echo heartbeat > /sys/class/leds/PWR/trigger # uyku sırasında yanıp söner
ACPI S3 (x86/x86-64)
# ACPI S3 desteğini kontrol et
cat /sys/power/state
# freeze mem disk (mem = S3)
# S3 destekleniyor mu? (ACPI tablo)
dmesg | grep -i "ACPI.*S3\|suspend.*s3"
# USB wakeup'ı yönet
for usb in /sys/bus/usb/devices/*/power/wakeup; do
echo disabled > "$usb" # USB wakeup'ı devre dışı bırak
done
# ağ kartının wakeup'ını aç (Wake-on-LAN)
ethtool -s eth0 wol g
echo enabled > /sys/class/net/eth0/device/power/wakeup
07 Hata ayıklama — suspend başarısız olunca
Suspend başarısız olduğunda sistematik bir yaklaşım gerekir. En yaygın sebep: eksik/hatalı driver suspend callback'i veya dondurulamamış bir süreç.
Adım adım hata ayıklama
# 1. dmesg'de suspend hatası ara
dmesg | grep -E "(PM:|suspend|resume|failed|error)" | tail -50
# Örnek hata çıktısı:
# PM: Suspending system (mem)
# PM: suspend of devices failed
# PM: Some devices failed to suspend, or early wake event detected
# e1000e 0000:00:19.0: Device driver failed to support suspend
# 2. pm_test ile driver isolation
echo devices > /sys/power/pm_test
echo mem > /sys/power/state
dmesg | grep -E "(PM:|failed)" | tail -20
echo none > /sys/power/pm_test
# 3. dynamic_debug ile suspend callback izle
echo "file drivers/base/power/main.c +p" \
> /sys/kernel/debug/dynamic_debug/control
echo "file drivers/base/power/wakeup.c +p" \
>> /sys/kernel/debug/dynamic_debug/control
# 4. Belirli bir cihazı debug et
echo "module e1000e +p" > /sys/kernel/debug/dynamic_debug/control
# 5. suspend_stats — suspend istatistikleri
cat /sys/kernel/debug/suspend_stats
# success: 15
# fail: 3
# failed_freeze: 0
# failed_prepare: 0
# failed_suspend: 3
# failed_suspend_noirq: 0
# failed_resume: 0
# last_failed_dev: e1000e
Wakeup kaynaklarını izole et
# Sistem anında uyanıyorsa (spurious wakeup)
# wakeup_count yöntemini kullan
cat /sys/power/wakeup_count
# 42
echo 42 > /sys/power/wakeup_count
echo $?
# 0: başarılı (wakeup event yok, devam et)
# yazma hatası: yeni wakeup event geldi, suspend iptal
# tüm wakeup source'ları debug
cat /sys/kernel/debug/wakeup_sources | sort -k3 -rn | head -10
08 Pratik: RTC DS3231 ve GPIO button wakeup
Raspberry Pi üzerinde iki wakeup senaryosu: DS3231 RTC modülü ile zamanlı uyanma ve GPIO button ile manuel uyanma.
1 — DS3231 RTC ile Wakeup
/* /boot/overlays/i2c-rtc.dts benzeri */
&i2c1 {
status = "okay";
rtc: ds3231@68 {
compatible = "maxim,ds3231";
reg = <0x68>;
/* SQW/INT pinini GPIO wakeup olarak kullan */
interrupt-parent = <&gpio>;
interrupts = <17 IRQ_TYPE_EDGE_FALLING>;
};
};
# DS3231 /dev/rtc1 olarak görünür (RPi'nin kendi RTC'si rtc0)
ls -la /dev/rtc*
# crw------- 1 root root 253, 0 /dev/rtc0
# crw------- 1 root root 253, 1 /dev/rtc1
# DS3231'i sistem RTC'si yap
hwclock --hctosys --rtc=/dev/rtc1
# 2 dakika sonra uyan (mem = S2RAM)
rtcwake -d /dev/rtc1 -m mem -s 120
# uyanma sonrası log
dmesg | tail -20
# PM: resume of devices complete after 423.127 msecs
# rtc-ds3231 1-0068: setting system clock to 2026-04-12T09:30:05
# periyodik wakeup scripti (her 5 dakikada bir)
cat <<'EOF' > /usr/local/bin/periodic_wake.sh
#!/bin/bash
INTERVAL=300 # saniye
while true; do
# iş yap
/usr/local/bin/collect_sensor_data.sh
# uyku
rtcwake -d /dev/rtc1 -m mem -s $INTERVAL
done
EOF
chmod +x /usr/local/bin/periodic_wake.sh
2 — GPIO Button ile Wakeup
/* /boot/overlays/ altında gpio-wakeup.dts */
/ {
gpio_wakeup: gpio-keys-wakeup {
compatible = "gpio-keys";
wakeup-button {
label = "Wakeup Button";
gpios = <&gpio 3 GPIO_ACTIVE_LOW>; /* GPIO3 = Pin 5 */
linux,code = <KEY_WAKEUP>;
wakeup-source; /* bu butonu wakeup source yap */
debounce-interval = <50>;
};
};
};
# gpio-keys cihazının wakeup etkin olduğunu kontrol et
cat /sys/devices/platform/gpio-keys-wakeup/power/wakeup
# enabled
# suspend'e gir (GPIO3'e GND bağlamak sistemi uyandırır)
echo mem > /sys/power/state
# uyanma kaynağını öğren
dmesg | grep -i "wakeup\|gpio"
# gpio-keys: wakeup key event (KEY_WAKEUP)
3 — Suspend sonrası periferal yeniden başlatma
# /lib/systemd/system-sleep/ altına script koy
cat <<'EOF' > /lib/systemd/system-sleep/reinit-peripherals.sh
#!/bin/bash
case "$1" in
pre)
echo "Suspend öncesi: sensörleri durdur"
systemctl stop sensor-daemon.service
;;
post)
echo "Resume sonrası: sensörleri yeniden başlat"
# I2C sensör yeniden başlat
i2cset -y 1 0x76 0xF3 0xFE # BME280 reset
sleep 0.1
systemctl start sensor-daemon.service
# GPS seri portu yeniden aç
systemctl restart gpsd.service
;;
esac
EOF
chmod +x /lib/systemd/system-sleep/reinit-peripherals.sh
RTC tabanlı periyodik wakeup, pil ömrü kritik IoT sensör düğümlerinde en yaygın kullanılan senaryodur. Raspberry Pi + DS3231 kombinasyonunda uyku sırasında tüketim ~2 mA seviyesine inerken, aktif ölçüm anında ~500 mA'ye çıkabilir. %5 aktif süre hedeflendiğinde ortalama tüketim ~27 mA olur.