The forgetful CPU (Linux on M4)
건망증 있는 CPU — M4에서 Linux 부팅하기
M4 Mac mini에서 Linux를 부팅하는 과정에서 마주친 하드웨어 제약과 CPU 오류를 설명합니다. 특히 WFI 명령이 CPU 레지스터를 지우는 문제를 우회하는 방법이 Linux와 m1n1에 반영돼, M4 계열 기기에서 모든 코어를 켜고 부팅하는 길이 열렸습니다.
- 주제
AI 요약
M4 Mac mini에서 Linux를 처음 부팅하기까지 겪은 문제와 해결 과정을 따라갑니다. M4 세대부터 적용된 보안 기능과 잠긴 레지스터 때문에 이전 Apple Silicon에서 쓰던 분석 방식을 그대로 적용하기 어려웠습니다. 부팅 과정에서 발생한 오류를 좁혀 가며 MMU 설정, 인터럽트 초기화, 보조 코어와 WFI 명령 관련 문제를 하나씩 해결했고, 관련 변경 사항은 Linux 메인라인과 m1n1에 반영됐습니다.
M4에서 달라진 부팅 조건
작성자는 2024년 11월 M4 Mac mini를 구입하고 Asahi Linux 지원을 시도했습니다. 하지만 M4는 Secure Page Table Monitor(SPTM)를 필수로 사용합니다. 이 기능은 macOS XNU 커널의 취약점을 막기 위한 보호 장치입니다. 이전 세대 Linux 개발에서는 m1n1 하이퍼바이저로 macOS의 MMIO 접근을 기록해 하드웨어 동작을 분석하는 방식이 중요했습니다. SPTM이 들어오면서 macOS를 하이퍼바이저 안에서 실행하려면 m1n1을 크게 바꿔야 했습니다.
하이퍼바이저 작업과 별도로 Linux 부팅도 시도했습니다. macOS 복구 환경에서 보안 설정을 낮추고 m1n1을 사용자 지정 부팅 객체로 설치한 뒤, 직렬 콘솔로 부팅 로그를 확인했습니다. 처음에는 m1n1이 BRINGUP 모드에서만 시작됐습니다. GXF 초기화 과정에서 충돌이 발생했는데, M4 계열 SoC에서는 raw boot 모드의 GXF 기능이 비활성화되거나 잠겨 있었습니다. 해당 초기화를 조건부로 건너뛰자 다음 단계로 진행할 수 있었습니다.
CPU 코어별 시작 주소를 보관하는 Reset Vector Base Address Register(RVBAR)도 문제였습니다. m1n1은 커널을 부팅하거나 다른 m1n1을 실행할 때 이 레지스터에 진입 주소를 씁니다. M4에서는 쓰기 동작이 충돌을 일으켰습니다. 조사 결과 레지스터에 이미 필요한 주소가 들어 있었기 때문에, M4에서 해당 쓰기를 생략했습니다.
MMU가 켜진 뒤 사라진 직렬 출력
작성자는 CPU 코어와 AIC 인터럽트 컨트롤러만 담은 최소 장치 트리(device tree)를 만들고 Linux를 실행했습니다. 커널은 “Vectoring to next stage” 이후 아무 출력도 내지 않았습니다. 원인을 찾기 위해 커널 초기화 코드 곳곳에 한 글자를 출력하는 debug_putc를 넣었습니다. 출력이 끊기는 지점을 따라가 보니 MMU 초기화 부근이었습니다.
문제는 MMU 자체가 아니라 직렬 포트 접근 방식이었습니다. 직렬 포트는 메모리 매핑 입출력(MMIO)으로 접근합니다. MMU를 켜면 메모리 접근은 가상 주소를 사용하고, 페이지 테이블이 이를 물리 주소와 연결합니다. m1n1은 MMIO 영역을 같은 주소로 매핑했지만 Linux 초기 페이지 테이블에는 그런 매핑이 없었습니다. 그 결과 MMU가 켜진 뒤 직렬 포트 주소가 매핑되지 않은 공간을 가리켰습니다. 초기 페이지 테이블에 MMIO 1:1 매핑을 추가하자 debug_putc가 더 오랫동안 동작했습니다.
다시 출력이 멈추는 지점을 좁혀 보니 인터럽트 컨트롤러 초기화 중 CPU 시스템 레지스터 SYS_IMP_APL_VM_TMR_FIQ_ENA_EL2에 쓰는 동작이 충돌을 일으켰습니다. 이 쓰기를 주석 처리하자 커널이 셸까지 부팅됐습니다. 해당 레지스터는 가상화와 관련이 있으며, 이후 새 iBoot 버전에서 잠금이 풀려 우회 코드가 필요 없어졌습니다. 직렬 콘솔에서 초기 오류 로그를 제대로 받으려면 장치 트리에 stdout-path = "serial0"도 지정해야 했습니다.
WFI가 지우는 CPU 레지스터
보조 코어를 시작하려면 m1n1에 smp_start_offset 값이 필요했습니다. 작성자는 M1~M3 기본 모델에 쓰던 오프셋을 적용해 코어를 켰지만, Linux는 다시 충돌했습니다. 원인은 대기 명령인 WFI(Wait For Interrupt)였습니다.
이전 Apple Silicon 세대에서도 특정 chicken bit 설정에 따라 WFI 실행 후 x0부터 x31까지의 CPU 레지스터가 0으로 바뀌는 문제가 있었습니다. XNU는 WFI 전에 레지스터를 스택에 저장하고 실행 뒤 복구합니다. M1~M3에서는 m1n1이 이 동작을 비활성화했으며, Asahi 커널은 전력 절약과 코어 클러스터의 높은 클럭 동작을 위해 이후 다시 활성화했습니다.
M4에서는 해당 설정 비트가 잠겼거나 사라진 것으로 보이며, 기본 동작이 ARM64 규격을 따르지 않았습니다. 규격은 WFI가 완료되는 구성이라면 아키텍처 상태가 손실되지 않아야 한다고 명시합니다. 작성자는 2026년 4월 Linux 커널의 WFI와 WFIT(시간 제한을 둔 WFI) 명령을 NOP으로 바꿔 모든 코어를 켠 채 부팅하는 데 성공했습니다.
처음에는 Linux의 CPU 오류 대응 프레임워크를 활용하는 방안을 검토했습니다. 하지만 macOS 하이퍼바이저 아래 가상 머신도 같은 오류 감지 조건에 걸릴 수 있습니다. 가상 환경에서는 하이퍼바이저가 WFI를 가로채 게스트 스케줄링에 사용하므로 명령을 NOP으로 바꾸면 문제가 생깁니다. 중첩 가상화까지 고려해 가상 환경을 정확히 판별하는 일도 복잡했습니다.
Will Deacon은 다른 해결책을 제안했습니다. Linux에 WFI idle을 끄는 부팅 인자를 추가하고, m1n1이 알려진 오류가 있는 베어메탈 기기에서만 해당 인자를 전달하는 방식입니다. 이후 코어를 절전 상태로 보내는 기능은 별도 경로로 추가합니다. 현재는 하위 버전의 cpuidle-apple 드라이버를 쓸 수 있으며, Sven이 진행하는 PSCI EFI conduit 작업은 메인라인에 들어갈 해결책으로 언급됐습니다. WFI와 WFIT으로 Linux가 충돌하지 않도록 하는 변경은 Linux 메인라인과 m1n1에 병합됐습니다.
다음 과제
이 작업으로 M4뿐 아니라 M4 Pro, M4 Max, M5에서도 같은 WFI 우회 방식이 작동하는 것을 확인했습니다. 최신 Linux와 m1n1은 M4 Mac에서 보조 코어를 포함해 네이티브로 부팅할 수 있습니다. 다만 주변 장치 역공학은 계속 진행 중입니다. 내장 카메라, 디스플레이 컨트롤러, GPU 초기화처럼 복잡한 부분을 분석하려면 macOS를 부팅하고 동작을 추적하는 m1n1 하이퍼바이저 작업이 도움이 될 전망입니다. 작성자는 변경 사항을 각 프로젝트에 직접 제출해 upstream에 반영하고 있으며, Asahi Linux 작업 후원을 위한 LiberaPay, GitHub Sponsors와 Asahi Open Collective도 안내합니다.
원문: yuka.dev / 번역·요약: Trawling