
RISC-V에 심장을 달다: 커널 배관공이 되어 GPU를 깨운 이야기
RISC-V 기반 TH1520 SoC에서 오픈소스 PowerVR 드라이버를 활성화하기 위해 메인라인 커널의 전원, 클럭, 리셋 인프라를 구축한 여정을 소개합니다.
김영태
테크리드

안녕하세요. 풀링포레스트 테크리드 김영태입니다.
개발자로서 가장 짜릿한 순간 중 하나는 죽어있던 하드웨어에 생명을 불어넣을 때라고 생각합니다. 오늘은 최근 리눅스 커널 커뮤니티에서 꽤 화제가 되었던, RISC-V 기반 SoC인 TH1520에서 3D 가속을 활성화한 과정을 소개해드리려 합니다. 단순히 "드라이버를 설치했다" 수준이 아니라, 전원부터 클럭, 리셋까지 말 그대로 바닥부터 배관(Plumbing) 공사를 다시 한 이야기입니다.
솔직히 말해, 처음 이 소식을 접했을 때 저는 적잖은 충격을 받았습니다. 그동안 임베디드 세상에서 PowerVR GPU는 악명 높은 '블랙박스'였습니다. 벤더가 던져주는 바이너리 덩어리(out-of-tree 드라이버)에 의존해야 했고, 그마저도 커널 버전이 바뀌면 깨지기 일쑤였으니까요. 그런데 Imagination Technologies가 오픈소스 드라이버(drm/imagination)를 내놓았고, 드디어 RISC-V 진영에서도 메인라인 커널로 3D 그래픽을 돌릴 기회가 열린 겁니다.
하지만 현실은 녹록지 않았습니다. "드라이버가 나왔으니 바로 쓰면 되겠지?"라는 생각은 오산이었습니다. TH1520 SoC의 GPU는 단순히 소프트웨어만 올린다고 작동하는 게 아니었습니다. GPU가 깨어나기 전에 전원, 클럭, 리셋 컨트롤러 같은 주변 장치들이 아주 정교한 순서대로 준비 운동을 마쳐야 했거든요. 마치 복잡한 기계를 시동 걸기 위해 수십 개의 스위치를 정확한 순서로 켜야 하는 것과 같았습니다.
가장 먼저 부딪힌 벽은 '의존성 체인'이었습니다. GPU 드라이버를 로드하기 훨씬 전 단계인, 가장 밑바닥 인프라가 비어 있었던 거죠.
Mailbox (mailbox-th1520): 전원 관리를 담당하는 별도 코프로세서(E902)와 통신할 물리적 채널이 필요했습니다.
Firmware Protocol (thead-aon-protocol): 통신 채널 위에, 실제로 "전원 켜줘"라고 말할 수 있는 프로토콜을 구현해야 했습니다.
Power Domains (pmdomain-thead): 그제야 비로소 리눅스 표준인 GenPD를 통해 GPU 전원 레일을 제어할 수 있게 되었습니다.
이 '배관 공사'를 끝내고 나니, 더 큰 문제가 기다리고 있었습니다. 바로 전원 시퀀싱(Power Sequencing)입니다.
TH1520 GPU는 전원을 켤 때 타이밍에 매우 민감합니다. 전원을 넣고, 전압이 안정화되길 기다렸다가, 특정 순서대로 리셋을 해제해야만 작동합니다. 예전 같으면 드라이버 코드 안에 `sleep` 함수와 GPIO 제어 코드를 덕지덕지 붙여서 해결했을 겁니다. 하지만 우리는 메인라인 입성을 목표로 하잖아요? 그렇게 지저분한 코드는 받아들여지지 않습니다.
여기서 등장한 구원투수가 바로 리눅스 커널의 새로운 서브시스템인 입니다. 커널 유지관리자들의 조언을 받아, 저는 GPU를 위한 전용 시퀀서 드라이버()를 구현했습니다. 이 방식이 흥미로운 건, 시퀀서가 GPU의 리소스를 '가로채서(adopt)' 관리한다는 점입니다.
아래 코드를 보시죠. 제가 구현한 match 함수의 일부입니다.
static int pwrseq_thead_gpu_match(struct pwrseq_device *pwrseq, struct device *dev) {
struct pwrseq_thead_gpu_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
/* 1. TH1520 GPU 호환성 확인 */
if (!of_device_is_compatible(dev->of_node, "thead,th1520-gpu"))
return 0;
/* ... 검증 로직 생략 ... */
/* 2. GPU 노드에 정의된 클럭 리소스를 시퀀서가 대신 가져옵니다 */
ctx->num_clks = ARRAY_SIZE(clk_names);
/* ... 메모리 할당 ... */
/* 여기서 핵심! GPU가 쓸 클럭을 시퀀서가 제어권을 가져갑니다 */
ret = clk_bulk_get(dev, ctx->num_clks, ctx->clks);
if (ret)
goto err_free_clks;
ctx->gpu_reset = reset_control_get_shared(dev, NULL);
return 1;
}이 코드가 의미하는 바는 큽니다. GPU 드라이버 본체()는 하드웨어 특화된 복잡한 리셋 순서를 알 필요가 없어집니다. 그저 "전원 켜줘"라고 요청하면, 이 시퀀서가 중간에서 가로채서 아주 정교한 타이밍으로 클럭과 리셋을 제어해 주는 것이죠. 이를 '제어의 역전(Inversion of Control)'이라고 부르는데, 덕분에 핵심 드라이버 코드를 아주 깔끔하게 유지할 수 있었습니다.
결국 이 모든 노력 끝에, TH1520은 오픈소스 드라이버로 하드웨어 가속 3D 그래픽을 지원하는 최초의 RISC-V SoC가 되었습니다. 리눅스 6.18 커널에 정식으로 병합되면서 말이죠.
물론 이게 끝은 아닙니다. 렌더링 된 화면을 실제로 모니터에 뿌리기 위해서는 디스플레이 컨트롤러(DC8200) 쪽의 파이프라인도 연결해야 합니다. 여전히 가야 할 길이 멀지만, 캄캄했던 터미널에 첫 그래픽이 떴을 때의 그 희열은 개발자만이 알 수 있는 특권이 아닐까 싶습니다.
여러분도 당장 눈앞의 비즈니스 로직 구현에 치여 막막할 때가 있겠지만, 가끔은 이렇게 시스템의 가장 깊은 곳, 배관을 들여다보는 시간을 가져보셨으면 좋겠습니다. 그 복잡한 의존성 너머에, 시스템이 살아 움직이는 진짜 원리가 숨어있으니까요.
오늘도 보이지 않는 곳에서 묵묵히 배관을 연결하고 계신 모든 개발자분들을 응원합니다.


