MacBook 12インチ + Ubuntu 26.04でサスペンドから復帰できない


退役したMacBook 12インチ(MacBook10,1、2017年モデル)にUbuntu 26.04を入れたところ、フタを閉じて開くと画面に「認証エラー」と表示され、キーボード入力も受け付けなくなった。電源ボタン長押し以外に復帰する手段がない。

結論から言うと、原因はApple製NVMe SSDとそのPCIeルートポートをD3coldに落とすことだった。suspendのnoirqフェーズでハングし、そのまま復帰しない。udevルールでd3cold_allowed=0にすれば直る。

「認証エラー」もキーボードが効かないのも症状であって原因ではなかった。最初はキーボードドライバを疑って時間を溶かしたので、切り分けの過程も含めて残しておく。

環境

項目内容
機種MacBook10,1(12インチ, 2017)
OSUbuntu 26.04 LTS
カーネル7.0.0-28-generic
SSDAPPLE SSD AP0512J(Apple S3X NVMe Controller, 106b:2003
デスクトップGNOME / Wayland

症状

フタを閉じるとサスペンドし、開くと画面は復帰する。しかしそこに「認証エラー」と表示され、パスワードを入力しようにもキーボードが反応しない。トラックパッドも同様。電源ボタン長押しで強制終了するしかなかった。

最初の見立ては外れた

MacBook 12インチのキーボードとトラックパッドはUSBではなくSPI接続で、applespiというドライバが使われている。

Terminal window
lsmod | grep -E 'applespi|spi_pxa'
applespi 57344 0
spi_pxa2xx_platform 12288 0
spi_pxa2xx_core 32768 1 spi_pxa2xx_platform

「復帰後にapplespiのキーボードが死ぬ」という報告は検索するといくらでも出てくる。定番の回避策はサスペンド前後でモジュールを載せ替えるというものだ。/usr/lib/systemd/system-sleep/にフックを置けば、サスペンド前にpre、復帰後にpostという引数で呼ばれる。

/usr/lib/systemd/system-sleep/applespi-reload
#!/bin/sh
case "$1" in
pre) modprobe -r applespi ;;
post) modprobe applespi ;;
esac

これは効かなかった。SPIコントローラ本体(spi_pxa2xx_platformspi_pxa2xx_core)まで含めて外しても同じ。後から分かるのだが、applespiはそもそも無関係だった。

ログが1行も残らない

まともに調べようとして、最初にぶつかったのがこれだった。journalctlで過去のブートを見ると、どのブートも例外なく同じ行で途切れている

Terminal window
journalctl -b -1 --no-pager -n 3
7月 27 17:47:20 systemd-sleep[6306]: Performing sleep operation 'suspend'...
7月 27 17:47:20 kernel: PM: suspend entry (deep)

復帰後のログが1行も無い。5回分のブートを見て全部これだった。

つまり復帰後にディスクへの書き込みが一切通っていない。画面は出ているのにログが残らないという状況で、この時点で「キーボードの問題」という見立てはかなり怪しくなる。

ハングする系の問題を調べるときは、ログを書くたびにsyncする必要がある。ページキャッシュに残ったまま電源を切れば消えてしまうからだ。以降のスクリプトはすべてこうしている。

Terminal window
say() { echo "$(date '+%F %T') $*" >> "$LOG"; sync; }

pm_testで段階を切り分ける

カーネルにCONFIG_PM_DEBUG=yが入っていれば/sys/power/pm_testが使える。これは実際には眠らずに、サスペンド処理を途中まで実行して5秒後に戻ってくるという機能で、まさにこういう時のためにある。

Terminal window
cat /sys/power/pm_test
[none] core processors platform devices freezer

浅い段階から順に試す。

Terminal window
for stage in freezer devices platform processors core; do
echo "$stage" | sudo tee /sys/power/pm_test
echo freeze | sudo tee /sys/power/state
done

結果はこうなった。

<<< stage=freezer 成功
<<< stage=devices 成功
>>> stage=platform 開始 ← ここから戻ってこない

2回繰り返して同じ結果。これで一気に絞れた。

各段階の意味は次の通り。

段階内容
freezerプロセスを凍結するだけ
devices+ 各ドライバの通常フェーズのsuspend/resume
platform+ late/noirqフェーズとプラットフォーム(ACPI)処理
processors+ 起動CPU以外を停止
core+ カーネルコアのsuspend

devicesが通ったということは、applespiもNVMeも無線もi915も、通常フェーズのsuspend/resumeは全部正常ということだ。キーボードドライバ説はここで完全に否定された。

そしてplatformに含まれるのはlate/noirqフェーズ。ここはPCIデバイスを低電力状態(D3)に落とす処理が走る場所である。

ちなみに、この段階ではコンソールも既に落ちているため、no_console_suspendを付けて画面を見張っても何も出ない。ここでだいぶ回り道をした。

D3coldを疑う

noirqフェーズでの電源遷移が怪しいので、PCIデバイスを完全に電源断(D3cold)にする処理を止めて試す。sysfsにd3cold_allowedというノブがある。

Terminal window
for d in /sys/bus/pci/devices/*/d3cold_allowed; do echo 0 | sudo tee "$d"; done

この状態でplatform段階を実行すると——通った

2026-07-28 10:43:08 === d3cold_allowed=0 を 19 個のデバイスに設定
2026-07-28 10:43:09 >>> platform 段階を実行 (条件: no-d3cold)
2026-07-28 10:43:17 <<< ★通過★ 条件: no-d3cold

原因はnoirqフェーズでのPCIe D3cold遷移で確定。

犯人を特定する

全デバイスで禁止すれば動くが、消費電力の面では無駄が多い。どのデバイスかを特定する。PCIツリーはこうなっている。

Terminal window
lspci -t -v
-[0000:00]-+-02.0 Intel Kaby Lake-Y GT2 [HD Graphics 615]
+-1c.0-[01]----00.0 Apple Inc. S3X NVMe Controller
+-1d.0-[02]----00.0 Broadcom BCM4350 802.11ac Wireless
+-1d.1-[03]----00.0 Broadcom 720p FaceTime HD Camera
...

ここで効率のいいやり方がある。「1グループだけD3coldを許可して残りは全部禁止」を順に試すのだ。無実のグループは通過するのでマシンは生きたまま次に進める。固まった時点のグループが犯人になる。

Terminal window
hunt_group() {
label="$1"; shift
for d in /sys/bus/pci/devices/*/d3cold_allowed; do echo 0 > "$d"; done
for a in "$@"; do echo 1 > "/sys/bus/pci/devices/$a/d3cold_allowed"; done
say ">>>>>> グループ [$label] のみ D3cold 許可"
echo platform > /sys/power/pm_test
sync
echo freeze > /sys/power/state && say "<<< 通過"
}
hunt_group "NVMe" 0000:00:1c.0 0000:01:00.0
hunt_group "無線" 0000:00:1d.0 0000:02:00.0
hunt_group "カメラ" 0000:00:1d.1 0000:03:00.0

最初のグループで止まった。

2026-07-28 10:51:02 >>>>>> グループ [NVMe] のみ D3cold 許可 (0000:00:1c.0 0000:01:00.0)
2026-07-28 10:51:02 >>> platform 段階を実行 (条件: only:NVMe)
(以降なし)

犯人はApple S3X NVMe(01:00.0)とそのPCIeルートポート(00:1c.0。強制終了して再起動し、残り3グループも確認したが、無線・カメラ・その他はすべて通過した。

<<< ★通過★ 条件: only:無線
<<< ★通過★ 条件: only:カメラ
<<< ★通過★ 条件: only:その他

なぜ「認証エラー」だったのか

これで最初の症状の説明がつく。

  1. 復帰時にNVMeが戻らない
  2. ディスクI/Oが全滅する
  3. PAMが/etc/shadowを読めず、ロック画面が「認証エラー」を出す
  4. journaldも書けないので、ログがPM: suspend entryで途切れる
  5. ロック画面が固まっているので、キーボードが効かないように見える

画面(GNOME Shell)はメモリ上にあるので描画はされる。だから「画面は出るのに何もできない」という、いかにも入力デバイスの問題に見える症状になっていた。

対策

3つ入れた。

サスペンド方式をs2idleにする

Apple のEFIはmacOS向けに作られていて、S3(deep)からの復帰パスがLinuxでは動かないという報告が多い。ファームウェアを経由しないs2idleに変更する。

/etc/systemd/sleep.conf.d/10-macbook10-s2idle.conf
[Sleep]
MemorySleepMode=s2idle

なお、これは切り分けの初期に入れたもので、D3coldを直した後にdeepでも動くかは確認していない。s2idleで問題なく動いているのでそのままにしてある。

NVMe経路のD3coldを禁止する

これが本命。udevルールで起動時に設定する。

/etc/udev/rules.d/60-macbook-no-d3cold.rules
ACTION=="add", SUBSYSTEM=="pci", KERNEL=="0000:00:1c.0", ATTR{d3cold_allowed}="0"
ACTION=="add", SUBSYSTEM=="pci", KERNEL=="0000:01:00.0", ATTR{d3cold_allowed}="0"

反映はsudo udevadm control --reloadと再起動。

保険とログ

udevが取りこぼした場合に備えて、サスペンド直前に再適用するフックも置いた。ついでに復帰の記録も残す。

/usr/lib/systemd/system-sleep/macbook-no-d3cold
#!/bin/sh
LOG=/var/log/macbook-sleep.log
NVME_PATH="0000:00:1c.0 0000:01:00.0"
case "$1" in
pre)
echo s2idle > /sys/power/mem_sleep 2>/dev/null
for a in $NVME_PATH; do
echo 0 > "/sys/bus/pci/devices/$a/d3cold_allowed" 2>/dev/null
done
echo "$(date '+%F %T') pre op=$2" >> "$LOG"; sync
;;
post)
echo "$(date '+%F %T') post op=$2 復帰成功" >> "$LOG"; sync
;;
esac

chmod +xを忘れずに。

結果

フタの開閉で問題なく復帰するようになった。

/var/log/macbook-sleep.log
2026-07-28 11:04:48 pre op=suspend
2026-07-28 11:05:50 post op=suspend 復帰成功
2026-07-28 11:09:46 pre op=suspend
2026-07-28 11:09:47 post op=suspend 復帰成功

復帰後のジャーナルも当然ながら残るようになり、カーネル側の復帰は0.5秒で完了していることが確認できる。

11:05:50.071 kernel: Freezing user space processes
11:05:50.077 kernel: nvme nvme0: 1/0/0 default/read/poll queues
11:05:50.486 kernel: PM: suspend exit
11:05:50.675 systemd[1]: Finished grub2-common.service

代償は、サスペンド中の消費電力がわずかに増えること。D3coldはPCIeデバイスの電源を完全に落とす状態で、それをD3hot止まりにしているためである。

役に立たなかったもの

pm_trace

CONFIG_PM_TRACE=yがあれば/sys/power/pm_traceが使える。suspend/resume中の各デバイスコールバックのハッシュをRTC(ハードウェア時計)に書き込み、電源を切っても消えないので次回起動時に「どのデバイスで止まったか」を教えてくれる、という仕組みだ。

期待して試したが、結果はこれだった。

PM: Magic number: 8:1:0
PM: hash matches drivers/base/power/main.c:1988
memory memory48: hash matches

memory memory48はハッシュ衝突による偽陽性の典型で、/sys/power/pm_trace_dev_matchを見てもinputmemoryにしか一致しない。ハッシュ空間が狭いので、こうなると使い物にならない。

副作用としてハードウェア時計が壊れる(上のログの通り、RTCが2024-01-01などに書き潰される)ので、使うなら覚悟がいる。Ubuntu 26.04にはhwclockが標準で入っていないので、時計はNTP同期後にtimedatectlで直すことになる。

no_console_suspend

quiet splashを外してno_console_suspend ignore_loglevelを付ければ、ハングした瞬間の最後の行が画面に残るはず——と考えたが、今回止まっていたnoirqフェーズではコンソールも既に落ちているため何も出なかった。

なおignore_loglevelを付けたままにすると、全カーネルメッセージをフレームバッファに描画するので復帰が体感でわかるほど遅くなる。デバッグが終わったら必ず外すこと。

残っている問題

復帰後、パネルが点灯するまでにまだ時間がかかる。ただしこれは別問題で、

  • カーネルの復帰は0.5秒で完了しており、ジャーナルに待ち時間の空白が無い
  • つまりシステムは裏で動いていて、画面だけが暗い

という状況。バックライトデバイスがacpi_video0しか無く、起動ログに次の行が出ている。

i915 0000:00:02.0: [drm] Skipping intel_backlight registration

輝度制御をACPI(LNXVIDEO:00)が握っていて、i915のネイティブ制御が無効になっている。復帰のたびにacpi LNXVIDEO:00: Restoring backlight stateが出ているのも符合する。

定石通りacpi_backlight=nativeを試したが改善しなかったので、これは未解決のまま。とりあえず復帰はするので良しとしている。進展があれば追記する。

まとめ

  • 復帰しない系の調査では、まず/sys/power/pm_testfreezerdevicesplatformと段階を切ること。これだけで犯人の範囲が劇的に狭まる
  • ログは書くたびにsyncする。しないと消える
  • platformで止まるならnoirqフェーズ、つまりPCIの電源遷移を疑う。d3cold_allowedは再起動なしで試せる
  • 「1グループだけ許可」方式なら、無実のグループは通過するので再起動の回数を最小化できる
  • 症状(認証エラー、キーボードが効かない)から原因を推測しても当たらない

参考


Author

me

入谷 史人

Scientist (Ph.D.), Programer, Web Developer, Guitarist, Photographer

more...