- UEFIRC는 운영체제가 뜨기 전 메인보드 펌웨어의 UEFI 사전 부팅 환경에서 실행되는 그래픽 IRC 클라이언트로, 부트로더용 환경에서도 일반 앱에 가까운 UI와 네트워크 기능을 구현할 수 있음을 보여줌
- 구현은 UEFI가 네트워크 부팅을 위해 제공하는 NIC 드라이버와 TCP 스택을 활용하며, QEMU용
vmnet네트워크 백엔드가 개발을 가능하게 함 - 가장 까다로운 지점은 Rust에서 UEFI TCP 프로토콜을 다루는 일이었고, 전역 상태, 재진입 콜백, scatter-gather 버퍼, 이벤트·토큰·핸들·프로토콜이 복잡하게 얽힘
- GUI는 axle의 Rust GUI 툴킷과 TrueType 렌더러를 UEFI로 가져온 형태이며, 마우스 입력·스크롤바·스크롤 뷰 텍스트 렌더링을 위해 libgui 개선도 함께 이뤄짐
- 결과물은 실용적 IRC 클라이언트라기보다 정교한 농담성 프로젝트에 가깝지만, UEFI TCP/IP 스택에 대한 불만을 UEFI 안에서 IRC로 말할 수 있는 도구가 됨
UEFIRC가 하는 일
- UEFIRC는 UEFI에서 실행되는 그래픽 IRC 클라이언트임
- Rust로 작성됐고, axle 사용자 공간용으로 만든 GUI 툴킷과 TrueType 렌더러를 활용함
- IRC 서버에 접속해 채팅하고 메시지를 읽을 수 있음
- 개발에는 QEMU용
vmnet네트워크 백엔드가 사용됨
UEFI라는 실행 무대
- 운영체제의 부트로더는 메인보드 ROM에 저장된 펌웨어의 도움으로 로드됨
- 과거 BIOS는 여러 제약을 가졌고, 이를 대체하기 위해 UEFI 표준이 만들어짐
- BIOS는 부트로더가 16-bit 모드에서 시작하도록 요구함
- 첫 단계 로더가 512바이트 안에 들어가야 하는 요구도 있음
- UEFI는 부트로더를 처음부터 64-bit 환경에 두고, VESA 디스플레이 해상도 전환, 메모리 할당, EFI 파일시스템 접근 같은 API를 제공함
- BIOS보다 큰 진전이지만, 과도하게 설계됐다는 평가도 함께 받음
네트워크 부팅 기능을 IRC에 재사용
- 일부 부트로더는 로컬 블록 디바이스가 아니라 네트워크를 통해 운영체제를 로드할 수 있음
- 이 사용 사례를 지원하려면 UEFI 펌웨어가 네트워크 스택을 포함해야 함
- NIC 드라이버
- TCP 구현
- 사전 부팅 환경에서 실행되는 애플리케이션이 이 스택에 접근하는 API
- 부트로더가 반드시 운영체제를 로드해야 하는 것은 아니므로, 같은 환경에서 IRC 클라이언트도 실행할 수 있음
Rust에서 UEFI TCP를 다루는 어려움
- 프로젝트에서 가장 까다로운 부분은 UEFI TCP 프로토콜 클라이언트를 Rust로 구현하는 일이었음
- UEFI TCP 프로토콜은 Rust에 설명하기 어려운 데이터 수명과 상호작용을 요구함
- 전역 상태
- 재진입 콜백
- scatter-gather 버퍼
- 이벤트, 토큰, 핸들, 프로토콜
- 메모리 누수와 TCP 수신 버퍼의 use-after-free를 없애기 위해 Rust 코드를 여러 날 테스트함
NOTIFY_SIGNAL과 NOTIFY_WAIT의 혼란
- UEFI 이벤트 API는 이름만 보고 동작을 예측하기 어려움
NOTIFY_SIGNAL을 지정하면 이벤트 발생 시 콜백이 호출되고,wait()사용은 오류가 됨NOTIFY_WAIT을 지정하고wait()를 호출하면, UEFI는 이벤트가 발생하기 전까지 콜백을 여러 번 호출할 수 있고 이벤트가 발생하면wait()가 풀림- 두 모드는 같은 콜백이라도 완전히 다른 의미를 갖게 함
NOTIFY_SIGNAL: 이벤트가 발생했으니 다음 작업을 할 시점NOTIFY_WAIT: 이벤트가 아직 발생하지 않았으니 진행을 유도할 시점
- 수신 패킷 데이터를 비동기 버퍼링하기 위해 최종적으로
NOTIFY_WAIT루프와 짧은 타임아웃 타이머를 함께 사용함
마우스와 커서 지원
- IRC 클라이언트에 마우스가 필수는 아니지만, 앱을 더 상호작용적으로 느끼게 함
- UEFI의 Simple Pointer Protocol을 사용해 마우스 이동과 버튼 입력을 읽고, GUI에 커서 위치 피드백을 넣음
- Simple Pointer Protocol은 스크롤 휠을 지원하지 않음
- UEFIRC에서는 화살표 키를 쓰거나 커서로 스크롤바를 드래그해야 함
- 표준 OVMF UEFI 펌웨어에서는 마우스 이벤트를 얻을 수 없어,
UsbMouseDxe등 필요한 드라이버와 프로토콜을 포함한 커스텀 UEFI 펌웨어를 빌드함 - QEMU에서 UEFIRC를 시험할 수 있도록 해당 UEFI 펌웨어도 릴리스에 업로드됨
마우스 이동 스케일링
- 마우스 드라이버는 절대 위치가 아니라 위치 변화량을 보고함
- 단순히
delta_x,delta_y를 그대로 더하는 선형 스케일링은 둔하게 느껴짐 - 운영체제는 빠른 이동과 세밀한 조정을 함께 가능하게 하는 방식에 가까운 스케일링을 사용함
- 예시 구현에서는 이동량의 절댓값 합에
log2()를 적용한 값을 곱해 커서 이동을 키움 - 선형 이동 커서는 전체 환경이 느리고 반응이 나쁘다고 느끼게 만들기 쉬움
IRC 메시지 모델링
- IRC 메시지 모델링은 비교적 간단하고 쾌적했음
- IRC는 텍스트 기반 라인 포맷을 사용해 파싱하기 쉬움
- 다만 수십 년에 걸친 확장으로 인해 일부만 표준화된 부담도 있음
UEFI에서 libgui 사용
- axle의 Rust GUI 툴킷은 이미 axle 외부 컨텍스트에서도 쓸 수 있도록 상당 부분 작업돼 있어, UEFI에서 실행하는 일이 크게 어렵지는 않았음
- 핵심 작업은 UEFI 안에서 사용할 수 있는
AwmWindow구현을 제공하는 일이었음 - 이후 libgui의 여러 기능을 그대로 활용함
- 이벤트 관리
- 폰트 렌더링
- 레이어 합성
- 뷰 장식
- 스크롤 뷰 같은 복잡한 컴포넌트
스크롤바와 스크롤 뷰 텍스트 렌더링
- axle의 C 기반 libgui에는 스크롤바 기능이 있었지만, Rust 버전에는 아직 일부 기능이 부족했음
- UEFIRC의 주요 상호작용은 텍스트로 채워진 스크롤 뷰에서 이뤄지므로, Rust libgui에 스크롤바 기능을 다시 구현함
- 스크롤 뷰는 고정 크기 뷰보다 픽셀 렌더링 비용이 큼
- 고정 크기 뷰는
width * height크기의 RGB 버퍼를 생각하면 됨 - 스크롤 뷰는 무한히 확장 가능한 캔버스를 다뤄야 함
- 고정 크기 뷰는
- axle의 Rust GUI 툴킷은 스크롤 뷰를 타일 기반으로 처리함
- 각 타일은 수백 픽셀 너비의 정사각형 픽셀 버퍼
- 콘텐츠가 실제로 렌더링되는 영역에 필요한 타일만 할당함
- 보이는 타일을 계산해 최종 이미지로 이어 붙임
- TrueType 렌더러가 글리프의 픽셀마다
putpixel()을 호출하면, 스크롤 뷰가 전체 렌더링 영역을 미리 알 수 없어 비효율적임 - 이를 해결하기 위해 polygon stack을 선, 원, 사각형 같은 기본 드로잉 단위에 추가함
- 스크롤 뷰가 큰 폴리곤을 그린다는 사실을 알고 필요한 타일을 미리 할당할 수 있음
- 임의 폴리곤 채우기를 기본 프리미티브로 둔 점은 마음에 들지 않지만, 실용적으로 잘 동작함
UEFIRC를 만들며 개선된 libgui
- 새 그래픽 애플리케이션을 만들 때마다 GUI 툴킷, IPC, 드라이버 인터페이스, 커널 기능의 작은 제약과 불편이 드러남
- UEFIRC 개발 과정에서 libgui에 여러 수정이 들어감
완전히 불필요한 결과물
- IRC 클라이언트 자체는 정교한 농담 프로젝트라 실사용성이 높지 않음
- UEFI TCP/IP 스택에 화가 날 때, 그 불만을 이야기할 도구로 쓸 수 있음
- 마지막으로 UEFI 안에서 UEFI
#edk2개발 IRC 채널에 접속해 인사를 남김