1 คะแนน โดย GN⁺ 2025-02-18 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • บนเดสก์ท็อป Linux ที่ใช้ AMD RX 570 เมื่อสั่งสลีปในสภาพที่ ใช้ RAM สูง มักเกิดหน้าจอดำหรือรับอินพุตไม่ได้หลังปลุกกลับ จึงต้องไล่ตรวจไปไกลกว่าแค่ปัญหาหน้าจอ ไปจนถึง flow การจัดการพลังงานของเคอร์เนล
  • สาเหตุหลักอยู่ที่ flow ซึ่ง amdgpu ต้อง สำรอง VRAM ไปยัง system RAM ก่อน S3 sleep แต่เมื่อ RAM ว่างไม่พอ กลับใช้ disk swap ได้ไม่ถูกต้องและล้มด้วย OOM
  • แพตช์แรกที่เลื่อน VRAM eviction จาก dpm_suspend() มาเป็น dpm_prepare() ช่วยลดการแข่งกับการตัดไฟ SSD แต่ pm_restrict_gfp_mask() ได้ปิดใช้ swap ไปแล้ว ทำให้ยังอาจเกิด หน่วยความจำต่อเนื่องไม่พอ ได้อยู่
  • ต่อมาเมื่อเปลี่ยนให้เรียก amdgpu_device_evict_resources() ณ จังหวะ PM_SUSPEND_PREPARE ผ่าน register_pm_notifier() ก็สามารถสำรอง VRAM ได้ขณะที่ swap และดิสก์ยังทำงานอยู่ ในสภาพแวดล้อมทดสอบจึงสลีปสำเร็จแม้ใช้ RAM และ VRAM สูง
  • การเปลี่ยนแปลงนี้ถูก merge เข้า tree ของ amdgpu แล้ว แต่ถูก revert ใน 2025-06 เพราะ มีความเป็นไปได้ที่จะเกิด deadlock และในเส้นทางสลีปที่ไม่ได้ freeze โปรเซสผู้ใช้ก่อน แอป 3D อาจดึง VRAM กลับไปที่ GPU อีกจนขัดขวาง eviction ได้

ความล้มเหลวในการสลีปซ้ำ ๆ และการวินิจฉัยผิดในช่วงแรก

  • เดสก์ท็อปเป็นเครื่อง dual-boot Windows กับ Linux และเมื่อพยายามสลีปบน Linux ตอนใช้ RAM สูง ระบบมักเสียหาย
    • หลังปลุกกลับเห็นเพียงหน้าจอดำกับเคอร์เซอร์ที่ขยับได้ หรือไม่มีสัญญาณภาพและตอบสนองเฉพาะ magic SysRq หรือ hard reset
    • บางกรณีนาฬิกาบนหน้าจอล็อกของ KDE ยังอัปเดตแบบเรียลไทม์ แต่จะค้างเมื่อพยายามล็อกอินหรือโต้ตอบ
  • สภาพแวดล้อมทดสอบคือ Gigabyte B550M DS3H, GPU AMD RX 570, SSD NVMe Kingston A2000 1TB, Arch Linux, systemd-boot, Linux 6.4
  • เมื่อตรวจ log ของการบูตครั้งก่อนด้วย journalctl --system -b -1 พบ ข้อผิดพลาด OOM ในโค้ดเคอร์เนลใต้ amdgpu_device_suspend ในความพยายามสลีปบางครั้ง
  • เคสล้มเหลวจำนวนมากจบที่ log ต่อไปนี้ และไม่มีบันทึกว่าระบบที่เสียหายปลุกกลับมาได้อีก
    • systemd-sleep: Entering sleep state 'suspend'...
    • kernel: PM: suspend entry (deep)
  • ตอนแรกสงสัยโหมดประหยัดพลังงาน NVMe APST จึงลอง nvme_core.default_ps_max_latency_us=0, iommu=soft, อัปเกรดเฟิร์มแวร์ SSD และเปลี่ยน SSD บูตเป็น 2TB แต่ปัญหาไม่หาย

เครื่องมือดีบักที่ช่วยจำกัดตำแหน่งความล้มเหลว

  • พฤติกรรมที่ systemd ทดลองโหมดสลีปหลายแบบต่อเนื่องกันอาจสร้าง noise ใน log และทำให้สถานะเคอร์เนลแย่ลง จึงเพิ่ม SuspendState=mem ใน /etc/systemd/sleep.conf
    • การดีบักง่ายขึ้น แต่สาเหตุรากยังคงอยู่
  • ใช้ echo 1 > /sys/power/pm_trace เพื่อติดตามตำแหน่งที่สลีปล้มเหลว
    • pm_trace บันทึกสถานะความคืบหน้าของ sleep-resume ไว้ในเวลาระบบ
    • จากผลข้างเคียงที่ทำให้ asynchronous suspend ถูกปิดใช้งาน ยังพบรูปแบบที่ระบบกู้กลับได้แทนที่จะ hang ทั้งระบบหลัง amdgpu suspend ล้มเหลว
  • เพิ่มพารามิเตอร์เคอร์เนล systemd.debug_shell เพื่อให้เปิด root shell ที่ Ctrl-Alt-F9 ได้โดยไม่ต้องล็อกอินผ่าน KDE หรือ TTY
  • ใช้คีย์บอร์ด PS/2 เผื่อกรณี USB controller และคีย์บอร์ดเสียหาย และภายหลังได้ตั้งค่า serial console
    • พารามิเตอร์เคอร์เนล: no_console_suspend console=tty0 console=ttyS0,115200 loglevel=8
    • จับ log ต่อเนื่องจากแล็ปท็อปด้วย sudo minicom --device /dev/ttyUSB0 --baudrate 115200
    • serial console ช่วยให้รันคำสั่งและเก็บ log ได้แม้จอแสดงผลกับเครือข่ายเสียไปแล้ว แต่มีข้อจำกัดด้านความเร็วส่งข้อมูลและการจัดการสี/ขนาดหน้าจอ

ความเชื่อมโยงระหว่าง amdgpu VRAM eviction กับ OOM

  • log crash ส่วนใหญ่เกิดในเส้นทาง TTM buffer eviction ของ amdgpu
    • amdgpu_device_evict_resources()
    • amdgpu_ttm_evict_resources()
  • จาก bug report ที่เกี่ยวข้อง ยืนยันว่า “evict” เกี่ยวข้องกับการคัดลอก VRAM ไปยัง system RAM หรือการย้าย system RAM ไปยัง swap
  • เมื่อเดสก์ท็อปเข้าสู่ S3 sleep ไฟของ PCIe GPU จะถูกตัดและข้อมูลในชิป VRAM จะหายไป
    • ไดรเวอร์ GPU ต้องคัดลอก VRAM ที่ใช้งานอยู่ไปยัง system RAM ก่อนสลีป
    • หลัง resume ต้องกู้ข้อมูลที่เก็บไว้ใน RAM กลับมา
  • ไดรเวอร์ Linux amdgpu มีปัญหาว่าเมื่อไม่มี RAM ว่างพอจะเก็บ VRAM ที่ใช้งานอยู่ทั้งหมด แทนที่จะย้าย system RAM ไปยัง disk-based swap กลับ ชนเพราะหน่วยความจำไม่พอ
  • เมื่อ Linux เจอ OOM ระหว่าง suspend จะพยายามยกเลิกการสลีปและเริ่มอุปกรณ์ใหม่ แต่เพราะ suspend ถูกหยุดไปบางส่วนแล้ว ไดรเวอร์บางตัวอาจเสียหาย หรือเกิด OOM ซ้ำระหว่าง suspend/resume ได้
    • หากเปิด asynchronous suspend ความเสี่ยงจะยิ่งมากขึ้น
    • ตัวสลีปเองอาจสำเร็จแล้ว แต่เกิด OOM ระหว่างกระบวนการเริ่มอุปกรณ์ตอน resume ก็ได้

ความพยายามแก้ครั้งแรก: เลื่อนจังหวะสำรอง VRAM ให้เร็วขึ้น

  • Mario Limonciello แนะนำให้เปิด /sys/power/pm_print_times และ /sys/power/pm_debug_messages แล้วตรวจ log การสลีป
  • log แสดงว่าไดรเวอร์ NVMe และ amdgpu เข้าสู่ pci_pm_suspend แบบขนานกัน ระหว่างสลีป
  • ตอนแรกจึงมองหาวิธีให้ GPU suspend ก่อน SSD suspend และตรวจสอบกลไก device suspend ordering ของ Linux ด้วย
    • กลไกนี้ดูใกล้เคียงกับการซิงก์อุปกรณ์ต่อพ่วงที่เชื่อมกันอย่างใกล้ชิด มากกว่าจะมีไว้ให้ suspend GPU ทุกตัวก่อนดิสก์ระบบทุกลูก
  • Mario เสนอวิธี evict VRAM ในขั้น prepare ของ Linux suspend และเขียนแพตช์เคอร์เนลที่ย้าย VRAM eviction จาก dpm_suspend() ไปยัง dpm_prepare()
  • flow ก่อนและหลังเปลี่ยนเป็นดังนี้
    • เดิม: การสำรอง VRAM ทำที่จังหวะ dpm_suspend() ซึ่งอาจทับซ้อนกับ flow ที่ SSD กำลังปิด
    • เปลี่ยนแล้ว: พยายามสำรอง VRAM ก่อนใน dpm_prepare() และถ้าล้มเหลวให้หยุดสลีปก่อน suspend อุปกรณ์อื่น
  • การเปลี่ยนแปลงนี้ดีกว่าเดิม แต่เพราะ pm_restrict_gfp_mask() ปิดใช้งาน swap ก่อน dpm_prepare() จึงยังล้มเหลวเมื่อใช้ RAM สูง
    • amdgpu_ttm_evict_resources() อาจล้มเหลวเพราะหน่วยความจำต่อเนื่องไม่พอ
    • แม้จะยัด VRAM ทั้งหมดลง RAM ได้ หากไม่มีพื้นที่พอสำหรับ allocation ของไดรเวอร์ภายหลัง ก็อาจเกิด OOM ระหว่าง sleep หรือ wake ได้

crash ของ amdgpu อีกตัวที่หาเจอด้วย Ghidra

  • ระหว่างทดสอบเกิดข้อผิดพลาด BUG: unable to handle page fault for address: fffffffffffffffc ซึ่งดูเหมือน dereference pointer ที่เกือบเป็น null
  • log crash ชี้ไปที่ตำแหน่ง dm_resume+0x200 แต่ไม่ให้หมายเลขบรรทัดของซอร์สโค้ด
  • หลังบันทึกและ extract โมดูลเคอร์เนล amdgpu.ko แล้ว ใช้ Ghidra decompile เพื่อ map ตำแหน่ง crash ใน dm_resume กับบรรทัดในซอร์สเคอร์เนล
  • ปัญหาเกิดใน macro for_each_new_crtc_in_state(dm->cached_state, crtc, new_crtc_state, i)
    • dm->cached_state ไม่ใช่ pointer ที่ถูกต้อง แต่เป็น fffffffffffffff4
    • ต่อมาเมื่ออ่านฟิลด์ [RSI + 0x8] จึงเกิด page fault ที่ address fffffffffffffffc
  • สาเหตุอยู่ที่ flow ซึ่ง dm_suspend() เก็บค่าที่ drm_atomic_helper_suspend() คืนมาเป็น pointer โดยตรง
    • drm_atomic_helper_suspend() อาจคืน pointer ที่ถูกต้อง หรือ ERR_PTR(err)
    • เมื่อเกิด OOM จึงคืน -ENOMEM (-12) และคาดว่าโค้ด suspend ของ amdgpu dereference ค่านี้เหมือนเป็น pointer
  • Mario แก้ปัญหานี้โดยเพิ่มโค้ดตรวจสอบค่าล้มเหลวที่คืนมาและหยุด suspend

แนวทางที่เลิกทำ: อนุญาต swap ระหว่าง prepare()

  • เหตุที่สลีปยังล้มเหลวเมื่อใช้ RAM สูง คือ amdgpu สำรอง VRAM ใน dpm_prepare() แต่ ณ จังหวะนี้ pm_restrict_gfp_mask() ได้ปิดใช้งาน disk swap แล้ว
  • ความพยายามหนึ่งคือโครงสร้างที่อนุญาต swap ใน dpm_prepare() แล้วค่อยปิดใช้ swap ตอน dpm_suspend() ก่อนดิสก์ถูกปิด
  • เพื่อทำเช่นนั้น มีการทดลองย้ายการเรียก pm_restrict_gfp_mask() จาก enter_state() เข้าไปลึกขึ้นใน dpm_suspend_start()
  • มีข้อจำกัดเชิงปฏิบัติค่อนข้างมาก
    • pm_restrict_gfp_mask() ประกาศไว้ใน kernel/power/power.h และถูกเรียกจาก kernel/power/suspend.c
    • ตำแหน่งที่ต้องการเรียกคือ drivers/base/power/main.c ซึ่งโดยทั่วไปไม่ได้ include header ใน kernel/power/
    • ใช้วิธี hack ชั่วคราวโดยเพิ่ม #include <../kernel/power/power.h> แต่เป็นโครงสร้างที่ต้องโน้มน้าว upstream
  • ใน hybrid sleep ก็มีปัญหาด้านความถูกต้องเช่นกัน
    • hybrid sleep บันทึก system image แล้วเรียก pm_restrict_gfp_mask() จากนั้นเรียก suspend_devices_and_enter() ในสภาพที่ swap ถูกปิดแล้ว
    • หากเรียกฟังก์ชันเดียวกันซ้ำจะเกิด warning และ pm_restore_gfp_mask() อาจเปิด swap กลับไม่ได้
  • ในการทดสอบ ความถี่ของความล้มเหลวหรือ crash ลดลง แต่ไม่ได้แก้หมด และเป็นการเปลี่ยนแปลงที่เปราะบางเพราะแตะ core power management จึงไม่ได้พยายามส่ง upstream

ทางเลี่ยงฝั่ง userspace: amdgpu-sleep

  • ใน 2024-10 หลังปิด SuperTuxKart แล้วสลีป เมื่อ resume กลับเกิดหน้าจอดำ
    • จาก log ความพยายามสลีปครั้งแรกเกิด OOM ใน dpm_prepare() และครั้งถัดไปนำไปสู่ crash จาก allocation หน่วยความจำใน bw_calcs() ของ amdgpu ระหว่าง resume
  • นำแนวทางสำรอง VRAM จาก userspace ของ NVIDIA มาอ้างอิง
    • NVIDIA ใช้ service ที่รันก่อน/หลัง systemd เรียก kernel suspend เขียนไปยัง /proc/driver/nvidia/suspend เพื่อสำรอง/กู้คืน VRAM
  • จากแนวทางนี้จึงสร้างแพ็กเกจ amdgpu-sleep สำหรับ Arch Linux
    • ก่อนสลีปจะอ่าน /sys/kernel/debug/dri/1/amdgpu_evict_vram
    • debug endpoint นี้สั่งให้ amdgpu เก็บ VRAM ทั้งหมดลง system RAM
    • systemd รอจน GPU VRAM eviction เสร็จ แล้วจึงเริ่ม kernel suspend
  • เวลาสลีปบนเดสก์ท็อป จะคัดลอก VRAM ไปยังหน่วยความจำอย่างรวดเร็ว และหากจำเป็นก็ผลัก RAM ออกไปยัง swap จนสำเร็จ
  • เมื่อมีแอป 3D หลายตัวทำงานอยู่ แอปเหล่านั้นยัง render frame ต่อเนื่องและดึง VRAM กลับไปยัง GPU ทำให้เกิด livelock แบบชักเย่อ กับ amdgpu_evict_vram
    • สถานะนี้ดำเนินอยู่นานกว่า 70 วินาทีก่อนที่ amdgpu_evict_vram จะยอมแพ้
    • จากนั้นเมื่อ systemd เริ่ม kernel suspend และ freeze userspace แล้ว ในขั้นเคอร์เนล VRAM eviction จะสำเร็จ
  • สคริปต์นี้ยังถูกใช้ต่อไป เพราะความถี่ที่เกิด kernel-level crash เมื่อปิดสคริปต์ไม่ได้ต่ำกว่าความถี่ที่เกิด livelock

แพตช์สุดท้าย: power management notifier

  • ใน 2024-11 Mario ขอให้ทดสอบแพตช์ที่อนุญาต eviction ขณะที่ swap ยัง active อยู่
  • แพตช์มีโครงสร้างที่เรียก register_pm_notifier() และใช้ power management notifier API ของ Linux
  • callback รับข้อความ PM_HIBERNATION_PREPARE และ PM_SUSPEND_PREPARE แล้วเรียก amdgpu_device_evict_resources()
  • PM_SUSPEND_PREPARE ถูกส่งใน enter_state() → suspend_prepare() ผ่าน pm_notifier_call_chain_robust(PM_SUSPEND_PREPARE, PM_POST_SUSPEND)
    • ไดรเวอร์ที่มี notifier callback จะได้รับ PM_SUSPEND_PREPARE
    • หากเกิดความล้มเหลว ไดรเวอร์ที่เตรียมไปแล้วจะได้รับ PM_POST_SUSPEND และการสลีปจะถูกหยุด
  • หาก evict VRAM ที่ตำแหน่งนี้ จะเป็นช่วงก่อนที่ pm_restrict_gfp_mask() จะปิดใช้งาน swap และดิสก์ก็ยังไม่ถูก freeze
  • flow หลังแก้ไขเป็นดังนี้
    • suspend_prepare()
    • PM_SUSPEND_PREPARE
    • amdgpu_device_pm_notifier() → amdgpu_device_evict_resources()
    • pm_restrict_gfp_mask()
    • dpm_prepare()
    • dpm_suspend()
  • เมื่อ build custom kernel ที่มีไดรเวอร์ amdgpu ที่แก้แล้วมาทดสอบ พบว่าแม้สลีปหลายครั้งในสภาพใช้ RAM/VRAM สูงก็ไม่เกิดข้อผิดพลาด
  • อย่างไรก็ตาม เนื่องจาก amdgpu สำรอง VRAM ก่อนที่ PipeWire หรือ kernel จะ mute ลำโพง จึงเกิดอาการเสียงวนซ้ำอยู่หลายวินาที
  • หลัง code review หลายรอบ การเปลี่ยนแปลงนี้ถูก merge เข้า amdgpu tree และกลายเป็นขั้นตอนที่นำไปสู่การแก้บั๊กนี้หลังพยายามมากว่าหนึ่งปี

อัปเดต 2025-06 และการ revert

  • ตอนแรกคาดว่าการเปลี่ยนแปลงนี้จะถูกรวมใน stable Linux kernel 6.14 แต่ภายหลังถูก revert เพราะ อาจเกิด deadlock
  • ปัญหาใหม่ที่เพิ่มเข้ามาเกิดเมื่อ systemd เรียก system suspend โดยไม่ได้ freeze โปรเซส userspace ทั้งหมดก่อน
    • kernel พยายาม evict VRAM ไปยัง system RAM ก่อนที่ตัวเองจะ freeze โปรเซส
    • ในเวลาเดียวกัน หากโปรแกรม 3D ดึง VRAM กลับไปยัง GPU อีก กระบวนการ suspend eviction อาจ deadlock
  • สิ่งนี้คล้ายกับ livelock ที่เจอในทางเลี่ยง amdgpu_evict_vram ฝั่ง userspace
  • Linux PM maintainer เสนอแนวทางย้ายการเรียก pm_restrict_gfp_mask() เข้าไปด้านใน suspend_devices_and_enter()
    • แนวทางนี้สอดคล้องกับทิศทาง “อนุญาต swap ระหว่าง prepare()” ที่ทดลองไว้ก่อนหน้า
  • ไม่มีแผนจะ implement และส่งเอง
    • เพราะย้าย AMD GPU ไปยังคอมพิวเตอร์เก่าที่มี CPU ช้ากว่าและหน่วยความจำ 8GB แล้ว และไม่อยากรัน build เคอร์เนลในสภาพแวดล้อมนั้น
  • เครื่องหลักอัปเกรดเป็น Intel Arc B570 แล้ว แต่ยังเกิดปัญหาประเภทเดียวกันแม้มี VRAM 10GB
    • ปัญหานั้นถูกรายงานไปยัง drm/xe kernel bug tracker

1 ความคิดเห็น

 
GN⁺ 2025-02-18
ความคิดเห็นบน Hacker News
  • ข้อสันนิษฐานว่าเมื่อเดสก์ท็อปเข้าสู่ โหมดพัก S3 แล้วไฟของ PCIe GPU จะถูกตัดนั้นยังไม่แน่ชัด
    S3 ควรจะตัดไฟทุกอย่างยกเว้น RAM ก็จริง แต่ตัวอย่างเช่นเมนบอร์ด Gigabyte Aorus ขึ้นชื่อเรื่องบั๊กโหมดพักของ NVMe SSD ที่ทำให้ระบบไม่สามารถหลับหรือปลุกกลับมาได้อย่างถูกต้อง
    โดยทั่วไปเลี่ยงได้ด้วยกฎ udev ที่ปิดการปลุกของพอร์ต PCIe ทั้งหมด: ACTION=="offline", SUBSYSTEM=="pci", DRIVER=="pcieport", ATTR{power/wakeup}="disabled"
    หากต้องการจำกัดให้แคบลงก็ระบุเฉพาะพอร์ต PCIe ที่มีปัญหาได้ เช่น ATTR{vendor}=="0x8086", ATTR{device}=="0x43bc" และสามารถหาสาเหตุได้ด้วย /proc/acpi/wakeup, /sys/class/pci_bus/*/*/yourWakeupDevicePci/uevent | grep PCI_ID, udevadm info --attribute-walk /dev/whatever
    แทนที่จะใช้ udev จะให้ systemd service หรือสคริปต์อัตโนมัติ toggle /proc/acpi/wakeup ก็ได้ แต่เสถียรน้อยกว่า และปัญหาโหมดพักบน Linux แบบนี้น่ารำคาญจริง ๆ

    • บนเมนบอร์ดของผมต้องใส่ ACTION=="add", KERNELS=="0000:00:01.1", ATTR{power/wakeup}="disabled" ไว้ใน /etc/udev/rules.d/ เพื่อกัน การปลุกขึ้นมาเอง
      ตัวรับ Logitech Bolt ก็ปลุกคอมพิวเตอร์ Linux หลายเครื่องทันทีเหมือนกัน แต่ไม่รู้ว่าทำไมบน Windows ถึงไม่เป็น และถ้าจะลองจับ USB capture ก็ยังไม่แน่ใจว่าต้องใช้อุปกรณ์อย่าง logic analyzer หรือ Glasgow หรือเปล่า
      ตอนนี้แก้ขัดด้วยการเพิ่มกฎ ACTION=="add", SUBSYSTEM=="usb", DRIVERS=="usb", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c548", ATTR{power/wakeup}="disabled" เพื่อบล็อกไว้
    • ผมน่าจะเสียไฟไปหลาย kWh เพราะปัญหานี้บนเมนบอร์ด Aorus เลยหวังว่าวิธีนี้จะช่วยได้ แต่ในกรณีของผมไม่ได้ผล
    • ผมก็เจอปัญหาโหมดพักบน X570 Aorus Master อยู่เหมือนกัน และแก้ได้ด้วยการให้ systemd unit รัน echo GPP0 >> /proc/acpi/wakeup ตอนบูต
      แต่การพักครั้งแรกหลังบูตจะปลุกขึ้นมาทันทีเสมอ พอใช้ กฎ udev ข้างต้นแล้วดูเหมือนว่าปัญหานั้นก็หายไปด้วย
    • ทำให้คาดหวังว่าควรจะตรวจจับได้ว่าฮาร์ดแวร์หลับจริงหรือไม่ หรือทำให้การปลุกฮาร์ดแวร์ที่ไม่เคยหลับขึ้นมาอีกครั้งไม่ก่อปัญหา
      น่าจะทำได้ในลักษณะส่งคำสั่งพักไปยังอุปกรณ์ PCIe แล้วค่อยทำให้บัสเองพัก และตอนปลุกก็คืนชีพบัสก่อน จากนั้นค่อยปลุกอุปกรณ์
    • ผมติดปัญหานี้อยู่พักใหญ่ แต่วิธีนี้ก็ใช้ไม่ได้เช่นกัน สิ่งที่สรุปไว้มีอยู่ที่ https://bbs.archlinux.org/viewtopic.php?id=302440
      ในกรณีของผม สาเหตุการปลุกดูเหมือนจะมาจาก .../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:45/wakeup/wakeup6 แต่ไม่ค่อยแน่ใจว่า path นี้หมายถึงอะไร
  • ในฐานะผู้เขียน memreserver ซึ่งเป็นหนึ่งใน workaround ฝั่ง user space ที่ถูกกล่าวถึง ผมเคย debug ปัญหานี้เมื่อหลายปีก่อน
    คอมเมนต์สาธารณะที่หาได้เร็วคือ https://gitlab.freedesktop.org/drm/amd/-/issues/2125#note_17... และจำได้ว่าน่าจะมีการคุยกันใน mailing list ด้วย
    ประเด็นหลักคือ Linux ไม่มี suspend hook แบบเป็นขั้นเป็นตอนที่รันได้อย่างเชื่อถือได้ก่อนที่ดิสก์และบางส่วนของ memory subsystem จะถูก freeze และตอนนี้ดูเหมือนว่าจะทำได้แล้ว
    น่าเสียดายที่ Freedesktop Gitlab ดูเหมือนจะถูกทำดัชนีได้ไม่ดี ความรู้นี้เลยเหมือนถูกฝังกลบไป

  • เป็นงานที่ยอดเยี่ยมจริง ๆ
    ถ้าเคยสงสัยว่าทำไมการทำให้ การเข้าสู่โหมดประหยัดพลังงาน ทำงานได้ถูกต้องบน Linux ถึงยาก และดีบักก็ยาก บทความนี้เพียงบทเดียวก็แสดงให้เห็นได้ดีว่ามีจุดที่พังได้มากแค่ไหน
    ตอนนี้บน ThinkPad P1G4 ถ้าไม่ปิดพัดลมเองก่อนเข้าสู่โหมดประหยัดพลังงาน มันก็จะไม่ปิดเองโดยอัตโนมัติ และช่วงหลังเมื่อกลับมาจากโหมดประหยัดพลังงาน หูฟัง Bluetooth ก็มีเสียงรบกวน เลยต้องปิดการพักโหนดของ PipeWire ด้วย: https://wiki.archlinux.org/title/PipeWire#Noticeable_audio_d...

    • น่าทึ่งที่แม้จะเป็นปี 2025 แล้ว sleep/suspend ของแล็ปท็อป Linux ก็ยังทำงานได้ไม่เรียบร้อย
      ครั้งแรกที่เจอปัญหาแบบนี้น่าจะราว ๆ 15 ปีก่อน
    • โหมดประหยัดพลังงานนี่ขึ้นกับดวงจริง ๆ และข้อผิดพลาดแบบที่ในบทความพูดถึงก็ดีบักยากมาก
      ในบรรดาทางเลี่ยงที่ถูกเสนอสำหรับปัญหาหลาย ๆ อย่าง มีบางวิธีที่ให้ปิดโหมดประหยัดพลังงาน แต่เป้าหมายของการใช้ sleep มักคือเพื่อยืดเวลาการใช้งานแบตเตอรี่ วิธีแบบนี้จึงไม่ค่อยสมเหตุสมผลนัก เพราะอาจทำให้เวลาใช้งานจริงลดลงมาก
      ถึงอย่างนั้น การทำให้ S0ix sleep ทำงานก็ไม่ใช่เรื่องเป็นไปไม่ได้
      ผมติดตั้ง Arch Linux บนอุปกรณ์พกพาที่ใช้ AMD 7840U และ AMD 8840U ได้แก่ GPD Win Max 2, GPD Win Mini, GPD Win 4, Minisforum V3, OneXPlayer X1 Ryzen ซึ่งดูไม่น่าเป็นไปได้ที่บริษัทเหล่านี้จะออกแบบหรือทดสอบโดยคำนึงถึงการรองรับ Linux
      แต่เพียงปรับแหล่งปลุกปลอม ๆ ใน /proc/acpi/wakeup และ /sys/devices/*/*/*/power/wakeup เล็กน้อย ก็ได้การรองรับ S0ix ที่แทบสมบูรณ์แบบ ยกเว้น OneXPlayer X1 Ryzen รุ่นล่าสุด
      การรองรับจากเคอร์เนล Linux พื้นฐานก็ดีเยี่ยม หน้าจอสัมผัส, ปากกา, Wi-Fi, Bluetooth ใช้งานได้ดี และช่องโหว่เดียวที่เห็นคือการรองรับเครื่องอ่านลายนิ้วมือ
      ผู้ผลิตรายเล็กแบบนี้มักมีการปรับแต่งชิ้นส่วนแบบสุดโต่งหรือการบูรณาการอย่างประณีตน้อยกว่า ซึ่งในทางปฏิบัติก็เห็นได้จากตัวเครื่องที่หนาขึ้นทีละไม่กี่มม.
      ดังนั้นพวกเขาจึงเลือกชิ้นส่วนแบบอนุรักษนิยมมากกว่า และผลลัพธ์อาจเป็นการรองรับ Linux ที่ดีอย่างคาดไม่ถึง
    • ถ้าผู้ใช้ปลายทางอยากใช้โหมดประหยัดพลังงานได้ง่ายและดี คำตอบคือซื้อ ฮาร์ดแวร์ที่ผ่านการตรวจสอบว่าเข้ากันได้
      จากประสบการณ์ของผม ThinkPad สายธุรกิจมีความเสถียรมากมาตั้งนานแล้ว และดูเหมือนว่าผู้ใช้ Windows ของรุ่นเดียวกันจะเจอปัญหา sleep บ่อยกว่าด้วยซ้ำ
  • โดยส่วนตัวแล้วขอบคุณจริง ๆ
    แล็ปท็อปหลักของผมเป็น ThinkPad ที่ใช้ Ryzen และรัน Linux และผมใช้โหมดประหยัดพลังงานกับไฮเบอร์เนตบ่อย ๆ ซึ่งเจอปัญหานี้เป็นครั้งคราวมาตลอด
    ตั้งตารอ Linux 6.14

  • เหตุผลที่ dm->cached_state เก็บ -12 แทนที่จะเป็นพอยน์เตอร์ น่าจะเป็นเพราะระหว่าง suspend ฟังก์ชัน dm_suspend() กำหนดค่า dm.cached_state = drm_atomic_helper_suspend(adev_to_drm(adev)) ตรง ๆ
    drm_atomic_helper_suspend() ที่ถูกเรียกอาจคืนพอยน์เตอร์ที่ถูกต้อง หรือ ERR_PTR(err) ซึ่งเข้ารหัสข้อผิดพลาดไว้เป็นพอยน์เตอร์ค่าลบ แต่ตัวเรียกไม่ได้ตรวจสอบข้อผิดพลาดและใส่ลงในพอยน์เตอร์ทันที แล้วไป dereference ตอน resume
    นี่เป็นอีกเหตุผลหนึ่งที่ควรใส่ Rust เข้าไปในเคอร์เนล และถ้าบังคับให้จัดการชนิด Result เรื่องแบบนี้ก็เกิดขึ้นได้ยาก

    • แม้แต่ C preprocessor ก็สร้าง algebraic sum type ได้: https://github.com/Hirrolot/datatype99
      แต่ค่าเริ่มต้นสำคัญ และประวัติที่เคอร์เนลเลื่อนการปรับแนวปฏิบัติการเขียนโค้ดให้ทันสมัยมานานก็ส่งผลเสียต่อการปรับปรุงฝั่ง C
      น่าขันที่แรงต้านแบบเดียวกันนี้ทำให้นักพัฒนา Rust หงุดหงิดด้วย เพราะแม้แต่การจัดระเบียบ subsystem แต่ละส่วนหรือการบันทึกวิธีทำงานไว้เป็นเอกสารก็ยังไม่ค่อยได้รับการยอมรับ
      สิ่งอย่าง https://github.com/llvm/llvm-project/issues/74205 ถ้าลงมาถึงเคอร์เนลได้ก็อาจช่วยได้ แต่ถึงอย่างนั้นก็ดูเหมือนจะยังเลือกวิธี overload พอยน์เตอร์ด้วยมือ แทนที่จะใช้ type เพื่อรับประกันความปลอดภัยอยู่ดี
  • ผมใช้ดูอัลบูต Linux/Windows บน Framework AMD Laptop ที่ติดตั้งโมดูลขยาย GPU อยู่ งานนี้น่าจะช่วยได้
    อยากสนับสนุนโดยตรงหรือบริจาคให้การกุศลที่คุณชอบ ช่องทางติดต่ออยู่ในโปรไฟล์

  • เคยคิดว่าการตั้งชื่อ, cache invalidation และข้อผิดพลาด off-by-one คือสองปัญหาใหญ่ที่สุดของวิทยาการคอมพิวเตอร์ แต่พอรู้จัก ปัญหา sleep/wake แล้ว อันนี้ดูเป็น NP-complete เลย

    • ผมมองว่า sleep/wake เป็นส่วนย่อยของ cache invalidation
      ถ้าอุปกรณ์ต่อพ่วงทุกตัวไม่มีสถานะ ก็คงไม่เป็นปัญหา
    • เป็นแบบนั้นเฉพาะบน Linux ส่วนบน Windows เป็น O(n²) และบน macOS เป็น O(log n)
  • บน Linux เรื่อง การจัดการหน่วยความจำ โดยเฉพาะสถานการณ์ OOM ยังเป็นฝันร้ายที่เจ็บปวดอย่างไม่น่าเชื่อ
    ไม่ได้เจอปัญหาแบบนี้ตลอดเวลา แต่เคยพยายามดีบักปัญหาคล้าย ๆ กันแล้วล้มเหลวแน่นอน และสุดท้ายพอเกิด OOM ก็มักจะลง RAM เพิ่ม
    มันสิ้นเปลืองและแพง แต่การจัดการสถานการณ์ OOM อย่างสง่างามคงยังเป็นปัญหาที่ยากสำหรับ Linux ต่อไป
    งานครั้งนี้ยอดเยี่ยม และจะเป็นจุดอ้างอิงเวลา debug ปัญหาคล้าย ๆ กันในอนาคต
    ฟีเจอร์ debug-shell ของ systemd ก็น่าดีใจ และไม่รู้มาก่อนว่ามีฟีเจอร์แบบนั้นด้วย
    แต่บอร์ด X670E Steel Legend ของผมดูเหมือนจะไม่มี serial header เลยสงสัยว่าพอร์ต serial ในตัวสมัยนี้ทำงานอย่างไร
    มันต่ออยู่กับเลน PCIe ของชิปเซ็ตหรือเปล่า
    เวลาขุดลึกเข้าไปในเคอร์เนล Linux วิดีโอบันทึกงานนำเสนอเกี่ยวกับ subsystem ของเคอร์เนลจากงานอย่าง FOSDEM หรือ Linux Plumbers Conference ช่วยได้มาก เช่น วิดีโอเรื่อง TTM memory subsystem ที่ไดรเวอร์ DRM ของ GPU เดสก์ท็อปส่วนใหญ่ใช้ อยู่ทางนี้: https://www.youtube.com/watch?v=MG7_tUNKSt0

    • บน Windows พอร์ต serial ของเมนบอร์ดผมแสดงว่าต่อกับ Pci Bus → PCI standard ISA bridge
      DOS จงเจริญ
      วิดีโอ TTM ไว้มีเวลาจะดู
    • การกัก OOM ด้วย cgroups ทำได้ค่อนข้างดี
      ไม่ค่อยแน่ใจว่ามีวิธีจัดการ OOM สมัยใหม่ที่ดีกว่าสิ่งที่ Linux ทำอยู่หรือเปล่า และถ้ามีเอกสารที่น่าอ่านเกี่ยวกับเรื่องนี้ก็อยากรู้
    • ใช่ พูดแบบสุภาพที่สุดก็คือแย่มาก
      Linux จัดการ สถานการณ์ OOM ได้ไม่ดี
      รู้ว่าสามารถตั้ง guardrail ด้วย cgroups ได้ ติดตั้ง earlyoom ได้ เพิ่ม swap หรือใช้ zram ได้
      แต่สุดท้ายทั้งหมดก็เป็นแค่ hack สกปรก ๆ ที่อาจช่วยชีวิตได้เป็นครั้งคราวเท่านั้น และไม่ได้แก้วิธีที่สถานการณ์แบบนี้ถูกจัดการ
      ไม่อยากให้เสนอสิ่งพวกนี้เป็นคำตอบ
      เคยเห็นเคอร์เนลจัดสรรหน่วยความจำใน dm_crypt ไม่ได้จน LUKS volume เมานต์ตัวเองเป็น read-only คือได้โปรด แค่ฆ่าโปรเซสใน user space สักตัวก็พอ
      สภาพปัจจุบันยอมรับไม่ได้จริง ๆ และเบื่อข้อแก้ตัวแล้ว
    • สงสัยว่าเคยลองใช้ zswap/zram หรือยัง
      ถ้าใช้ zstd ก็ทำให้ RAM 8GB ใช้เหมือนมี ‘RAM’ 20GB หรือ 16GB ใช้เหมือน 40GB ได้โดยไม่มีปัญหาใหญ่
      ถ้ากล้ากว่านั้นก็ overcommit หน่วยความจำเกิน 100% ได้ด้วย และ Android ก็ทำแบบนี้ จึงเป็นวิธีที่ค่อนข้างเสถียร
  • ข่าวดี
    ไดรเวอร์กราฟิก Linux ของ AMD โดยรวมทำงานได้ดี แต่ปัญหานี้เป็นข้อยกเว้นที่เจอมาหลายครั้ง

    • ผมโชคดีน้อยกว่านั้นนิดหน่อย
      ปัญหาที่เจอช่วงนี้คือหลังปลุกจาก sleep แล้วไดรเวอร์คอยพ่น log "[drm] scheduler comp_1.0.n is not ready, skipping" ต่อจาก WARNING: CPU: 12 PID: 11871 at drivers/gpu/drm/amd/amdgpu/../display/dc/dc_helper.c:100 generic_reg_update_ex+0x1d2/0x290 [amdgpu] อยู่เรื่อย ๆ
      https://gitlab.freedesktop.org/drm/amd/-/issues/3911
    • โดยรวมผมก็มีประสบการณ์ที่ดีเหมือนกัน แต่ถ้าถอด Thunderbolt ที่ต่อกับจอออกตอนเครื่องหลับอยู่ จะเกิดปัญหาคล้ายกัน
      แต่เป็นโน้ตบุ๊ก เลยมีการตั้งค่าไดรเวอร์ต่างกันมาก และไม่มี PCIe GPU ด้วย
  • ส่วนที่บันทึกและดึงโมดูลเคอร์เนล amdgpu.ko ออกมา แล้ว decompile ด้วย Ghidra จากนั้น map ตำแหน่ง crash ของ dm_resume ไปยังบรรทัดที่ตรงกันในซอร์สเคอร์เนล เป็นฉากที่ผมชอบที่สุดในการ debug เสมอ