---
title: "RK3588 GPU 안에 숨어있는 Cortex-M7 MCU를 뜯어보고 Rust/gdb/MicroPython까지 올려본 이야기"
published: 2026-03-21T22:43:49.000Z
canonical: https://jeff.news/article/875
---
# RK3588 GPU 안에 숨어있는 Cortex-M7 MCU를 뜯어보고 Rust/gdb/MicroPython까지 올려본 이야기

RK3588의 Mali-G610 GPU 내부에 990MHz로 동작하는 Cortex-M7 MCU가 있으며, 이 펌웨어가 커널 대신 GPU 작업을 관리함. 메모리 접근의 3단 계층 구조, 인터럽트 시스템, 펌웨어 이미지 포맷을 분석하고, 실제로 Rust 정적 라이브러리, gdb 디버깅, MicroPython까지 MCU 위에서 구동하는 과정을 상세히 다룸.

## 펌웨어가 뭐하는 물건인고

- 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의 도구들이 큰 도움이 되었다고 함

## 핵심 포인트

- GPU 내장 Cortex-M7 MCU가 최대 990MHz로 동작하며 기존 커널의 GPU 관리 역할을 대체함
- 메모리 접근은 MPU → MCU 매핑 → GPU MMU 3단 계층을 거치며, MCU는 앞 2개만 제어 가능해 보안상 이점이 있음
- 5가지 인터럽트 경로(커널↔MCU, 유저스페이스→MCU, GPU→MCU, 타이머)로 시스템 간 통신함
- mali_csffw.bin은 GPL-2 라이선스의 20바이트 헤더 기반 펌웨어 포맷임
- Rust(정적 라이브러리), gdb(MRI 기반 셀프호스트 디버그), MicroPython을 MCU에서 실행하는 데 성공함

## 인사이트

GPU 내부 MCU의 동작 원리를 이 정도로 상세하게 공개한 글은 드묾. 특히 MicroPython으로 프로토타이핑 후 Rust로 재작성하는 전략은 임베디드 펌웨어 개발에서 참고할 만한 접근법임.
