กระบวนการติดตามและแก้ไขอาการ Linux ค้างเมื่อสลีปแล้วปลุกกลับบน AMD GPU
(nyanpasu64.gitlab.io)- บนเดสก์ท็อป 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 อุปกรณ์อื่น
- เดิม: การสำรอง VRAM ทำที่จังหวะ
- การเปลี่ยนแปลงนี้ดีกว่าเดิม แต่เพราะ
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 ที่ addressfffffffffffffffc
- สาเหตุอยู่ที่ 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 กลับไม่ได้
- hybrid sleep บันทึก system image แล้วเรียก
- ในการทดสอบ ความถี่ของความล้มเหลวหรือ crash ลดลง แต่ไม่ได้แก้หมด และเป็นการเปลี่ยนแปลงที่เปราะบางเพราะแตะ core power management จึงไม่ได้พยายามส่ง upstream
ทางเลี่ยงฝั่ง userspace: amdgpu-sleep
- ใน 2024-10 หลังปิด SuperTuxKart แล้วสลีป เมื่อ resume กลับเกิดหน้าจอดำ
- จาก log ความพยายามสลีปครั้งแรกเกิด OOM ใน
dpm_prepare()และครั้งถัดไปนำไปสู่ crash จาก allocation หน่วยความจำในbw_calcs()ของ amdgpu ระหว่าง resume
- จาก log ความพยายามสลีปครั้งแรกเกิด OOM ใน
- นำแนวทางสำรอง VRAM จาก userspace ของ NVIDIA มาอ้างอิง
- NVIDIA ใช้ service ที่รันก่อน/หลัง systemd เรียก kernel suspend เขียนไปยัง
/proc/driver/nvidia/suspendเพื่อสำรอง/กู้คืน VRAM
- NVIDIA ใช้ service ที่รันก่อน/หลัง systemd เรียก kernel suspend เขียนไปยัง
- จากแนวทางนี้จึงสร้างแพ็กเกจ 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 จะสำเร็จ
- สถานะนี้ดำเนินอยู่นานกว่า 70 วินาทีก่อนที่
- สคริปต์นี้ยังถูกใช้ต่อไป เพราะความถี่ที่เกิด 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และการสลีปจะถูกหยุด
- ไดรเวอร์ที่มี notifier callback จะได้รับ
- หาก evict VRAM ที่ตำแหน่งนี้ จะเป็นช่วงก่อนที่
pm_restrict_gfp_mask()จะปิดใช้งาน swap และดิสก์ก็ยังไม่ถูก freeze - flow หลังแก้ไขเป็นดังนี้
suspend_prepare()PM_SUSPEND_PREPAREamdgpu_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()” ที่ทดลองไว้ก่อนหน้า
- แนวทางนี้สอดคล้องกับทิศทาง “อนุญาต swap ระหว่าง
- ไม่มีแผนจะ implement และส่งเอง
- เพราะย้าย AMD GPU ไปยังคอมพิวเตอร์เก่าที่มี CPU ช้ากว่าและหน่วยความจำ 8GB แล้ว และไม่อยากรัน build เคอร์เนลในสภาพแวดล้อมนั้น
- เครื่องหลักอัปเกรดเป็น Intel Arc B570 แล้ว แต่ยังเกิดปัญหาประเภทเดียวกันแม้มี VRAM 10GB
- ปัญหานั้นถูกรายงานไปยัง drm/xe kernel bug tracker
1 ความคิดเห็น
ความคิดเห็นบน 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"เพื่อบล็อกไว้echo GPP0 >> /proc/acpi/wakeupตอนบูตแต่การพักครั้งแรกหลังบูตจะปลุกขึ้นมาทันทีเสมอ พอใช้ กฎ udev ข้างต้นแล้วดูเหมือนว่าปัญหานั้นก็หายไปด้วย
น่าจะทำได้ในลักษณะส่งคำสั่งพักไปยังอุปกรณ์ PCIe แล้วค่อยทำให้บัสเองพัก และตอนปลุกก็คืนชีพบัสก่อน จากนั้นค่อยปลุกอุปกรณ์
ในกรณีของผม สาเหตุการปลุกดูเหมือนจะมาจาก
.../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...
ครั้งแรกที่เจอปัญหาแบบนี้น่าจะราว ๆ 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
น่าขันที่แรงต้านแบบเดียวกันนี้ทำให้นักพัฒนา 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 เลย
ถ้าอุปกรณ์ต่อพ่วงทุกตัวไม่มีสถานะ ก็คงไม่เป็นปัญหา
บน 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
DOS จงเจริญ
วิดีโอ TTM ไว้มีเวลาจะดู
ไม่ค่อยแน่ใจว่ามีวิธีจัดการ OOM สมัยใหม่ที่ดีกว่าสิ่งที่ Linux ทำอยู่หรือเปล่า และถ้ามีเอกสารที่น่าอ่านเกี่ยวกับเรื่องนี้ก็อยากรู้
Linux จัดการ สถานการณ์ OOM ได้ไม่ดี
รู้ว่าสามารถตั้ง guardrail ด้วย cgroups ได้ ติดตั้ง earlyoom ได้ เพิ่ม swap หรือใช้ zram ได้
แต่สุดท้ายทั้งหมดก็เป็นแค่ hack สกปรก ๆ ที่อาจช่วยชีวิตได้เป็นครั้งคราวเท่านั้น และไม่ได้แก้วิธีที่สถานการณ์แบบนี้ถูกจัดการ
ไม่อยากให้เสนอสิ่งพวกนี้เป็นคำตอบ
เคยเห็นเคอร์เนลจัดสรรหน่วยความจำใน dm_crypt ไม่ได้จน LUKS volume เมานต์ตัวเองเป็น read-only คือได้โปรด แค่ฆ่าโปรเซสใน user space สักตัวก็พอ
สภาพปัจจุบันยอมรับไม่ได้จริง ๆ และเบื่อข้อแก้ตัวแล้ว
ถ้าใช้ 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
แต่เป็นโน้ตบุ๊ก เลยมีการตั้งค่าไดรเวอร์ต่างกันมาก และไม่มี PCIe GPU ด้วย
ส่วนที่บันทึกและดึงโมดูลเคอร์เนล
amdgpu.koออกมา แล้ว decompile ด้วย Ghidra จากนั้น map ตำแหน่ง crash ของdm_resumeไปยังบรรทัดที่ตรงกันในซอร์สเคอร์เนล เป็นฉากที่ผมชอบที่สุดในการ debug เสมอ