0
RK3588 GPU 안에 숨어있는 Cortex-M7 MCU를 뜯어보고 Rust/gdb/MicroPython까지 올려본 이야기
open-source
요약
기사 전체 정리
펌웨어가 뭐하는 물건인고
- RK3588의 Mali-G610 GPU 내부에는 Cortex-M7 (r1p2) MCU가 들어있으며, GPU 클럭을 공유해 최대 990MHz로 동작함. FPU, ECC 캐시, TCM은 없는 구성임
- 이 펌웨어(
mali_csffw.bin)는 기존에 커널이 처리하던 GPU 관련 작업들을 MCU가 대신 처리하도록 넘겨받은 것임. 커맨드 스트림 관리, 전력 관리, 컨텍스트 스위칭 등이 포함됨 - MCU는 32비트인데 GPU는 48비트 가상 주소 공간 8개를 지원함. 해결책은 MCU가 자기 페이지 테이블을 직접 제어하는 것임
메모리 접근의 3단 구조
- 메모리 접근 시 MPU → MCU 매핑 → GPU MMU 세 계층을 순서대로 통과해야 함. 각 계층의 역할이 다름
- MPU: 권한과 L1 캐시 동작을 설정하며, 거의 임의 크기의 리전 16개를 지원함. 폴트 시 MCU 예외가 발생함
- MCU 매핑: 순수 매핑 전용으로 권한 설정은 불가능하며, 128MiB 크기의 리전 8개를 64MiB 정렬로 제공함. 폴트 없음
- GPU MMU: 권한, 매핑, L2 캐시, 코히런시를 모두 제어하며, 4KB 페이지 단위로 48비트 주소 공간을 관리함. 폴트는 커널이 처리하고, MCU 주소 공간에서 폴트 발생 시 리셋됨
- 보안 관점에서 MCU가 제어하는 건 앞의 두 계층뿐이라, 익스플로잇으로도 GPU가 매핑하지 않은 메모리에는 접근 불가함
인터럽트 시스템
- 커널 → MCU: doorbell을 울려 MCU를 깨움. 펌웨어 초기화, 커맨드 스트림 설정, 전력 관리, protected mode 진입 등에 사용됨
- MCU → 커널: 커널 요청 처리 완료 알림이나 GPU 작업 완료 시 유저스페이스 스레드를 깨우는 데 사용됨. 에러 시그널링도 이 경로임
- 유저스페이스 → MCU: 매핑된 doorbell 페이지에 쓰기를 하면 MCU 레지스터가 설정되고 인터럽트가 발생함. GPU에 커맨드 스트림 처리 시작을 알리는 용도임
- GPU → MCU: 커맨드 스트림 처리 완료 시나, "event add" 같은 에뮬레이션이 필요한 인스트럭션 실행 시 발생함
- MCU 타이머 인터럽트: 일정 시간 후 코드 실행에 활용됨. 예를 들어 fragment job을 soft-stop해서 다른 컨텍스트에 렌더링 기회를 주는 식임
MMIO와 주요 메모리 영역
0x04000000에 64MiB 영역: 커널과 MCU 간 공유 버퍼로, 상태 변경 요청(커맨드 스트림 그룹 온라인, 레지스터 서스펜드 등)과 tracebuffer 디버그 출력에 사용됨0x40000000에 256KiB MMIO 영역: GPU 하드웨어 제어용으로, 인터럽트 시그널링, MCU 메모리 매핑 설정, GPU 상태 조회, 커맨드 스트림 프로세서 실행 등을 처리함
펌웨어 이미지 포맷
mali_csffw.bin은 20바이트 헤더(버전 정보 + 엔트리 크기)로 시작하고, 그 뒤에 다양한 엔트리가 이어지는 구조임- Interface 엔트리: 메모리 섹션을 설정하며, GPU 페이지 테이블용 권한을 지정함. 첫 번째 인터페이스에는 "This firmware image is GPL-2"라는 라벨이 붙어 있어 라이선스 확인이 가능함
- Tracebuffer 엔트리: MCU가 CPU로 트레이스 데이터를 쓰는 버퍼를 설정함. fwlog(로그 메시지), fwin/fwout(gdb용) 등이 있음
- 그 외에 Configuration 엔트리(sysfs를 통한 설정 변경), Timeline metadata 엔트리(트레이스 이벤트 정보), Build info metadata 엔트리(git SHA 저장) 등이 존재함
- ELF에서 변환하는 건 간단하지만, 역방향 변환은 Ghidra로 Arm 펌웨어를 로드하고 싶은 유혹 때문에 일부러 구현하지 않았다고 함. GPU 리버스 엔지니어링 윤리를 지키려는 의도임
MCU에서 이것저것 돌려보기
- Rust: 언어 자체는 괜찮은데 빌드 툴링이 고통스러웠음. 결국 순수 Rust 펌웨어는 포기하고, Meson에서 Cargo를 호출해 C에서 불러오는 정적 라이브러리로 타협함
- gdb: Adam Green이 만든 MRI(Armv7-M 셀프 호스트 디버그 기반 gdb 서버)를 활용해 SoC 내장 MCU에서도 gdb 디버깅이 가능함. 다만 패치된 커널이 필요하고, debugfs의 fw_io 파일을 통해 tracebuffer로 통신함
- watchpoint: Cortex-M의 하드웨어 워치포인트가 비동기적으로 동작해서, 실제 접근 시점보다 몇 인스트럭션 뒤에 예외가 발생함. 사실상 쓸모없음. 대안으로 MPU 리전 접근 차단이나 싱글스테핑을 사용해야 함
- 크리티컬 섹션: 원자적 연산 대신, ISR이 온스택 컨텍스트 연결 리스트를 순회하며 하위 우선순위 컨텍스트의 일관성을 보장하는 방식을 채택함. Linux의 RSEQ(restartable sequences)와 비슷한 아이디어임
- MicroPython: MCU에서 동작하며, GPU 컨텍스트 메모리의 tracebuffer를 I/O로 사용함. 실제 Python보다 훨씬 빠르게 시작되고, root 권한이나 패치 커널 없이도 접근 가능함. "일단 Python으로 구현하고 나중에 Rust로 재작성" 전략임
- 전체 코드는 panFWost git 저장소에 공개되어 있으며, Alyssa Rosenzweig의 도구들이 큰 도움이 되었다고 함
핵심 포인트
인사이트
관련 기사
open-source
Microsoft가 공개한 Rust 트레이닝 자료 — 초급부터 전문가까지 7개 코스
open-source
오픈소스가 전부를 줬고, 줄 것이 남지 않을 때까지 — Requests 창시자의 고백
open-source
IBM Z/LinuxONE 오픈소스 2월 리포트 — Cassandra부터 Terraform까지 27개 검증
open-source
1년간 OS를 밑바닥부터 만든 개발자 — 부트로더에서 Doom 포팅까지
open-source
2026년인데 아직도 VNC + 프로프라이어터리 EDA — 반도체 업계의 도구 현실
댓글
댓글
댓글을 불러오는 중...