OpenBIOS 심화
IEEE 1275 Open Firmware 표준을 구현한 오픈소스 펌웨어 OpenBIOS의 내부 구조를 분석합니다. Forth 인터프리터, 디바이스 노드/트리, FCode 바이트코드, 부트 로더 체인, QEMU SPARC/PPC 연동, 빌드 및 커스터마이징을 다룹니다.
핵심 요약
- Open Firmware (OF) — IEEE 1275 표준으로 정의된 펌웨어 인터페이스. SPARC, PowerPC 시스템에서 사용되었으며, Linux Device Tree의 직접적 기원
- Forth — 스택 기반 프로그래밍 언어. OF의 대화형 콘솔과 부팅 스크립트에 사용.
ok프롬프트에서 하드웨어를 직접 탐색 가능 - FCode — 플랫폼 독립적인 바이트코드. 확장 카드 ROM에 탑재되어 SPARC/PPC/x86 어디서든 드라이버로 동작
- 디바이스 트리 — 하드웨어 토폴로지를
/pci/ethernet@1같은 경로로 기술하는 트리 구조. 현대 ARM/RISC-V의 FDT가 여기서 유래 - Client Interface — OS가 펌웨어의 서비스를 호출하는 표준 ABI. Linux의
prom_init()가 이 인터페이스를 통해 부팅 정보를 획득
단계별 이해
- 전원 인가 → Forth 인터프리터 시작
시스템에 전원이 들어오면 OpenBIOS가 로드되고, Forth 인터프리터가 초기화됩니다.ok프롬프트가 표시되면 사용자가 직접 명령을 입력하거나, 자동 부팅 스크립트가 실행됩니다. - 하드웨어 열거 → 디바이스 트리 구축
버스를 스캔하여 연결된 장치를 발견하고, 각 장치의 FCode를 실행하여 디바이스 트리 노드를 생성합니다. 트리 완성 후show-devs로 전체 하드웨어 구성을 확인할 수 있습니다. - OS 로드 → Client Interface로 정보 전달
boot명령으로 커널을 메모리에 로드합니다. 커널은 Client Interface를 통해 디바이스 트리를 순회하며 메모리 맵, 콘솔 장치 등 부팅에 필요한 정보를 가져갑니다. 이후 펌웨어 제어권을 넘기고 OS가 시작됩니다.
개요
OpenBIOS는 IEEE 1275-1994 (Open Firmware) 표준의 오픈소스 구현체입니다. Sun SPARC 워크스테이션, Apple PowerPC Macintosh, IBM CHRP 시스템 등에서 사용되었던 Open Firmware를 GPLv2 라이선스로 재구현한 프로젝트로, 현재는 QEMU에서 SPARC(32/64비트)와 PowerPC 에뮬레이션의 기본 펌웨어로 사용됩니다.
of_* API)는 바로 이 "OF"(Open Firmware)에서 유래했습니다.
Open Firmware의 설계 철학
Open Firmware는 세 가지 핵심 철학 위에 설계되었습니다:
- 플러그 앤 플레이 (Plug and Play): 확장 카드가 자신의 드라이버를 FCode 바이트코드로 ROM에 탑재합니다. 호스트 CPU 아키텍처와 무관하게 동작하므로, 같은 그래픽 카드가 SPARC, PowerPC, x86 어디서든 초기화됩니다. x86 Option ROM이 x86 머신 코드를 포함하는 것과 근본적으로 다릅니다.
- 대화형 디버깅 (Interactive Debugging): Forth 인터프리터가 내장되어 있어 부팅 전에도 하드웨어를 직접 탐색·테스트할 수 있습니다. 레지스터 읽기, 메모리 덤프, 디바이스 I/O를 별도의 디버그 장비 없이 펌웨어 프롬프트에서 수행합니다.
- 자기 기술적 하드웨어 (Self-Describing Hardware): 디바이스 트리가 하드웨어 토폴로지를 구조적으로 기술합니다. OS는 하드코딩된 하드웨어 정보 대신 펌웨어가 구축한 트리를 읽어 시스템 구성을 파악합니다. 이 개념이 ARM 생태계의 FDT(Flattened Device Tree)로 이어집니다.
OpenBIOS의 주요 특징
| 특징 | 설명 |
|---|---|
| IEEE 1275 준수 | Open Firmware 표준 인터페이스를 구현하여 OS에 독립적인 부팅 환경 제공 |
| Forth 인터프리터 | 대화형 Forth 환경으로 하드웨어 탐색, 디버깅, 부팅 스크립트 실행 |
| FCode 지원 | 플랫폼 독립적인 바이트코드로 드라이버 이식성 확보 |
| 디바이스 트리 | 하드웨어 토폴로지를 트리 구조로 표현, Linux DT의 직접적 기원 |
| Client Interface | OS가 펌웨어 서비스를 호출하는 표준 ABI |
| QEMU 통합 | SPARC32/64, PowerPC(Mac/CHRP) 에뮬레이션의 기본 펌웨어 |
Open Firmware 생태계 포지셔닝
Open Firmware는 다양한 펌웨어 생태계 속에서 독특한 위치를 차지합니다. 아래 다이어그램은 주요 펌웨어들의 시대적·아키텍처적 관계를 보여줍니다:
Open Firmware 구현체 상세 비교
Open Firmware 표준을 구현한 주요 구현체들의 기술적 차이점입니다:
| 구현체 | 개발 | Forth 엔진 | 타겟 HW | FCode | 특수 기능 | 현재 상태 |
|---|---|---|---|---|---|---|
| Sun OBP | Sun Microsystems | 네이티브 (원조) | SPARC 전 세대 | 완전 지원 | POST 진단, watchdog, 보안 모드 | Oracle 인수, 단종 |
| Apple BootROM | Apple Computer | 커스텀 | PPC Mac (G3~G5) | 완전 지원 | HFS+ 부팅, BootX, Mac 파티션 | Intel Mac 전환으로 중단 |
| IBM SLOF | IBM | paflof (POWER Forth) | POWER 서버 | 완전 지원 | RTAS, CAS, VIO, LPAR | QEMU pseries 펌웨어 |
| SmartFirmware | CodeGen | 독자 엔진 | PPC (Pegasos) | 완전 지원 | USB 부팅, PCI 핫플러그 | 상용, 소스 일부 공개 |
| OFW | FirmWorks (Mitch Bradley) | 원저자 엔진 | x86/ARM (OLPC) | 완전 지원 | 무선 네트워크, 보안, 다국어 | OLPC 전용 유지 |
| OpenBIOS | 커뮤니티 (GPLv2) | C 기반 커스텀 | SPARC/PPC (QEMU) | 완전 지원 | fw_cfg, 여러 머신 타입 | QEMU 기본 펌웨어 |
IEEE 1275 Open Firmware 표준과 역사
Open Firmware는 1994년 IEEE 1275로 표준화된 펌웨어 인터페이스입니다. Mitch Bradley가 Sun Microsystems에서 개발한 것이 시초이며, 이후 여러 플랫폼에 채택되었습니다.
타임라인
| 시기 | 사건 |
|---|---|
| 1988 | Sun Microsystems의 OBP(OpenBoot PROM) 1.x 출시 — SPARC 워크스테이션용 |
| 1991 | OBP 2.x — Forth 기반 디바이스 트리, FCode 도입 |
| 1994 | IEEE 1275-1994 표준 제정 |
| 1995 | Apple이 PowerPC Macintosh에 Open Firmware 채택 |
| 1996 | IBM CHRP(Common Hardware Reference Platform) 사양에 OF 포함 |
| 1998 | IEEE 1275.1 (ISA 바인딩), 1275.2 (PCI 바인딩) 등 부속 표준 |
| 2002 | Stefan Reinauer가 OpenBIOS 프로젝트 시작 (GPLv2) |
| 2004 | Segher Boessenkool이 QEMU SPARC 포팅 기여 |
| 2006 | Apple이 Intel Mac으로 전환하며 EFI 채택, OF 시대 종료 |
| 2010+ | QEMU의 SPARC/PPC 에뮬레이션 기본 펌웨어로 정착 |
IEEE 1275 표준 구성
IEEE 1275는 핵심 표준과 다수의 버스 바인딩(Bus Binding) 부속 문서로 구성됩니다:
| 표준 번호 | 이름 | 주요 내용 |
|---|---|---|
IEEE 1275-1994 | Core Standard | Forth 인터프리터, 디바이스 트리, Client Interface, FCode 사양 (핵심 문서) |
IEEE 1275.1 | ISA Bus Binding | ISA 버스 디바이스의 reg, interrupts 프로퍼티 인코딩 규칙 |
IEEE 1275.2 | PCI Bus Binding | PCI Configuration Space, BAR, 버스 번호 인코딩, PCI FCode 실행 규칙 |
IEEE 1275.3 | VME Bus Binding | VMEbus 디바이스 노드 프로퍼티, 주소 공간 인코딩 |
IEEE 1275.4 | FutureBus+ Binding | FutureBus+ 어드레스 맵 |
IEEE 1275.6 | 64-bit Extension | 64비트 주소 공간, 64비트 셀 크기 확장 (UltraSPARC 등) |
| (비공식) | SBus Binding | Sun SBus 슬롯 주소, DMA 프로퍼티 (Sun 자체 정의) |
| (비공식) | USB Binding | USB 디바이스 트리 표현 (Apple이 Mac OF에 사용) |
reg, ranges, interrupts 등의 프로퍼티를 어떻게 인코딩하는지 정의합니다. 예를 들어 PCI Binding에서는 reg의 phys.hi 셀에 버스 번호, 디바이스 번호, 기능 번호, BAR 인덱스가 비트 필드로 인코딩됩니다. 이 규칙은 Linux 커널의 of_pci_range_parser()에 그대로 반영되어 있습니다.
Open Firmware 진화 타임라인
Open Firmware 기술이 현대 Linux Device Tree까지 진화한 30년의 여정을 시각화합니다:
Open Firmware를 채택한 주요 플랫폼
- Sun SPARC: OpenBoot PROM (OBP) — SBus, UPA, PCI 버스 지원
- Apple PowerPC: BootROM — NewWorld Mac (iMac G3 ~ PowerMac G5)
- IBM pSeries/CHRP: POWER 서버 — RTAS(Run-Time Abstraction Services) 확장
- OLPC XO-1: OFW (Open Firmware Works) — ARM/x86 겸용 구현
- Genesi Pegasos: SmartFirmware — PowerPC 데스크톱
플랫폼별 Open Firmware 구현 상세
| 플랫폼 | 구현체 | 프롬프트 | 버스 | 부트 미디어 | 특이점 |
|---|---|---|---|---|---|
| Sun SPARCstation | OBP 2.x/3.x | ok | SBus, SCSI | 디스크, net, CD | POST 진단 모드, watchdog |
| Sun Ultra (sun4u) | OBP 3.x/4.x | ok | UPA, PCI, SCSI | 디스크, net, CD | 64비트 확장, UPA 그래픽 |
| Apple OldWorld Mac | Toolbox ROM + OF | 0 > | PCI, NuBus | 디스크, CD | Mac OS ROM 의존성 |
| Apple NewWorld Mac | BootROM | 진입 어려움 | PCI, AGP, USB | HFS+, CD, net | Apple 파티션 맵, BootX |
| IBM pSeries (POWER) | SLOF / 독점 OF | 0 > | PCI/PCI-X/PCIe | 디스크, SAN, net | RTAS, CAS 프로토콜 |
| QEMU SPARC/PPC | OpenBIOS | ok | 에뮬레이트됨 | 가상 디스크/net | fw_cfg 인터페이스 |
Cmd+Opt+O+F 키 조합으로 진입해야 했습니다. NewWorld에서는 HFS+ 파티션의 System/Library/CoreServices/BootX 파일이 2차 부트 로더 역할을 했습니다.
OpenBIOS 아키텍처
OpenBIOS는 크게 플랫폼 독립 계층과 플랫폼 종속 계층으로 나뉩니다. Forth 인터프리터가 중심에 위치하며, 디바이스 트리를 통해 하드웨어를 추상화합니다.
소스 트리 구조
openbios/
├── kernel/ # Forth 인터프리터 코어 (C 구현)
│ ├── dict.c # 딕셔너리 관리
│ ├── forth.c # Forth 가상 머신
│ ├── stack.c # 데이터/리턴 스택
│ └── bootstrap.c # 부트스트랩 워드 정의
├── libopenbios/ # 플랫폼 독립 라이브러리
│ ├── bootinfo_load.c # bootinfo 형식 로더
│ ├── elf_load.c # ELF 바이너리 로더
│ ├── feval.c # Forth 평가기
│ ├── initprogram.c # 프로그램 초기화
│ └── ofmem_common.c # OF 메모리 관리
├── packages/ # Open Firmware 패키지
│ ├── disk-label.c # 디스크 레이블 처리
│ ├── mac-parts.c # Apple 파티션 맵
│ ├── sun-parts.c # Sun VTOC 파티션
│ └── pc-parts.c # MBR/GPT 파티션
├── drivers/ # 디바이스 드라이버 (Forth + C)
│ ├── pci.c # PCI 버스 열거
│ ├── ide.c # IDE/ATA 컨트롤러
│ ├── vga.c # VGA 프레임버퍼
│ └── timer.c # 타이머
├── forth/ # Forth 소스 워드 정의
│ ├── device/ # 디바이스 트리 조작
│ ├── admin/ # 관리 명령어
│ └── debugging/ # 디버깅 유틸리티
├── arch/ # 플랫폼 종속 코드
│ ├── sparc32/ # SPARC32 (sun4m) 초기화
│ ├── sparc64/ # SPARC64 (sun4u) 초기화
│ ├── ppc/ # PowerPC 초기화
│ └── x86/ # x86 (실험적)
├── include/ # 공용 헤더
│ ├── config.h # 빌드 구성
│ ├── kernel/kernel.h # 코어 데이터 구조
│ └── arch/ # 아키텍처별 헤더
└── config/ # 빌드 설정
Forth 인터프리터
Open Firmware의 핵심은 Forth 프로그래밍 언어입니다. Forth는 스택 기반 언어로, 최소한의 리소스로 대화형 환경을 제공합니다. OpenBIOS는 ANS Forth의 서브셋에 Open Firmware 확장을 추가한 형태입니다.
스택 머신 구조
Forth 가상 머신은 두 개의 스택을 사용합니다:
- 데이터 스택 (Parameter Stack): 피연산자와 결과 값이 오가는 주 스택
- 리턴 스택 (Return Stack): 서브루틴 반환 주소 및 루프 카운터 저장
스택 조작 워드 레퍼런스
| 워드 | 스택 효과 | 설명 |
|---|---|---|
dup | ( x -- x x ) | top을 복제 |
drop | ( x -- ) | top을 제거 |
swap | ( x1 x2 -- x2 x1 ) | 상위 2개 교환 |
over | ( x1 x2 -- x1 x2 x1 ) | 2번째를 top에 복사 |
rot | ( x1 x2 x3 -- x2 x3 x1 ) | 3번째를 top으로 회전 |
-rot | ( x1 x2 x3 -- x3 x1 x2 ) | 역방향 회전 |
?dup | ( x -- x x | 0 ) | 0이 아니면 복제 |
2dup | ( x1 x2 -- x1 x2 x1 x2 ) | 상위 2개 쌍 복제 |
2drop | ( x1 x2 -- ) | 상위 2개 제거 |
2swap | ( x1 x2 x3 x4 -- x3 x4 x1 x2 ) | 상위 2쌍 교환 |
nip | ( x1 x2 -- x2 ) | 2번째를 제거 |
tuck | ( x1 x2 -- x2 x1 x2 ) | top을 2번째 아래로 삽입 |
pick | ( xn...x0 n -- xn...x0 xn ) | n번째 값을 top에 복사 |
>r | ( x -- ) R:( -- x ) | 데이터→리턴 스택 이동 |
r> | ( -- x ) R:( x -- ) | 리턴→데이터 스택 이동 |
r@ | ( -- x ) R:( x -- x ) | 리턴 스택 top 복사 |
depth | ( -- n ) | 현재 스택 깊이 |
산술·논리·비교 워드
| 카테고리 | 워드 | 스택 효과 | 설명 |
|---|---|---|---|
| 산술 | + - * / | ( n1 n2 -- n3 ) | 사칙 연산 |
mod | ( n1 n2 -- rem ) | 나머지 | |
/mod | ( n1 n2 -- rem quot ) | 나머지와 몫 | |
negate | ( n -- -n ) | 부호 반전 | |
abs | ( n -- |n| ) | 절대값 | |
min max | ( n1 n2 -- n ) | 최소/최대 | |
| 논리 | and or xor | ( x1 x2 -- x3 ) | 비트 논리 연산 |
invert | ( x -- ~x ) | 비트 반전 | |
lshift rshift | ( x n -- x' ) | 비트 시프트 | |
| 비교 | = <> | ( x1 x2 -- flag ) | 같음/다름 |
< > <= >= | ( n1 n2 -- flag ) | 크기 비교 (부호 있음) | |
u< u> | ( u1 u2 -- flag ) | 부호 없는 비교 | |
0= 0< 0> | ( n -- flag ) | 0 비교 | |
| 메모리 | @ ! | ( addr -- x ) / ( x addr -- ) | 셀 읽기/쓰기 |
c@ c! | ( addr -- byte ) / ( byte addr -- ) | 바이트 읽기/쓰기 | |
w@ w! | ( addr -- u16 ) / ( u16 addr -- ) | 16비트 읽기/쓰기 | |
l@ l! | ( addr -- u32 ) / ( u32 addr -- ) | 32비트 읽기/쓰기 |
\ Forth 기본 스택 조작 예시
ok 42 . \ 42 출력 (스택에서 pop)
ok 10 20 + . \ 30 출력 (10+20)
ok 3 dup * . \ 9 출력 (3을 복제 후 곱셈)
ok 1 2 swap .s \ 스택: 2 1 (swap으로 교환)
워드와 딕셔너리
Forth에서 함수는 워드(word)라 부르며, 이름-실행 주소 쌍이 링크드 리스트로 연결된 딕셔너리에 저장됩니다.
제어 흐름 구조
Forth의 제어 흐름은 컴파일 타임에 분기 주소가 결정되는 구조적 방식입니다:
| 구조 | 문법 | 설명 |
|---|---|---|
| 조건 | ... if ... then | flag가 true이면 if~then 사이 실행 |
| 조건 분기 | ... if ... else ... then | flag에 따라 분기 |
| 카운트 루프 | limit start do ... loop | start부터 limit-1까지 반복 |
| +LOOP | limit start do ... n +loop | 증분값 n으로 루프 |
| 조건 루프 | begin ... flag until | flag가 true가 될 때까지 반복 |
| 무한 루프 | begin ... flag while ... repeat | flag가 true인 동안 반복 |
| CASE | case n1 of ... endof n2 of ... endof endcase | 다중 분기 |
| 재귀 | recurse | 현재 정의 중인 워드를 재귀 호출 |
| 종료 | exit | 현재 워드에서 즉시 반환 |
| 루프 탈출 | leave | do...loop에서 즉시 탈출 |
\ 새 워드 정의
: square ( n -- n*n ) dup * ;
ok 7 square . \ 49 출력
\ 조건 분기
: abs ( n -- |n| )
dup 0< if negate then
;
\ 반복 (do...loop)
: stars ( n -- )
0 do [char] * emit loop cr
;
ok 5 stars \ ***** 출력
Open Firmware 확장 워드
| 카테고리 | 워드 | 설명 |
|---|---|---|
| 디바이스 트리 | new-device | 현재 노드 아래 새 자식 노드 생성 |
finish-device | 현재 디바이스 노드 확정 | |
device-name | 현재 노드 이름 설정 | |
| 프로퍼티 | property | 프로퍼티 이름-값 쌍 추가 |
get-my-property | 현재 인스턴스의 프로퍼티 읽기 | |
encode-int | 정수를 프로퍼티 인코딩 형식으로 변환 | |
| 메모리 | alloc-mem | 메모리 할당 |
free-mem | 메모리 해제 | |
map-in | 디바이스 주소를 가상 주소에 매핑 | |
| 부팅 | boot | 기본 부트 디바이스에서 부팅 |
open / close | 디바이스 인스턴스 열기/닫기 | |
load | 부트 이미지를 메모리로 로드 | |
| 디버깅 | see | 워드 역어셈블 |
words | 딕셔너리의 모든 워드 출력 | |
show-devs | 전체 디바이스 트리 출력 |
OpenBIOS Forth 엔진 내부 구조
OpenBIOS의 Forth 엔진은 C로 구현된 내부 인터프리터입니다. 핵심 구조체:
/* kernel/kernel.h */
typedef struct {
cell *dstack; /* 데이터 스택 포인터 */
cell *rstack; /* 리턴 스택 포인터 */
cell *dstack_min; /* 데이터 스택 하한 */
cell *rstack_min; /* 리턴 스택 하한 */
ucell IP; /* Instruction Pointer */
ucell state; /* 0=인터프리트, 1=컴파일 모드 */
ucell *catch_frame; /* 예외 처리 프레임 */
} forth_context_t;
인터프리트 모드 vs 컴파일 모드
Forth 엔진은 두 가지 모드로 동작합니다:
| 모드 | state 값 | 워드를 만나면 | 숫자를 만나면 |
|---|---|---|---|
| 인터프리트 | 0 | 즉시 실행 | 스택에 push |
| 컴파일 | 1 (`: ... ;` 사이) | 현재 워드 정의에 컴파일 | 리터럴로 컴파일 |
Immediate 워드: immediate 플래그가 설정된 워드(예: if, then, do, loop)는 컴파일 모드에서도 실행됩니다. 이들은 컴파일 타임에 분기 주소를 계산하는 역할을 합니다.
\ 인터프리트 모드 — 즉시 실행
ok 3 4 + . \ → 7
\ 컴파일 모드 — 새 워드 정의
: add-and-print ( n1 n2 -- )
+ \ '+' 워드가 컴파일됨
. \ '.' 워드가 컴파일됨
;
ok 3 4 add-and-print \ → 7
\ immediate 워드 예시 — ['] 는 컴파일 타임에 실행됨
: greet ['] cr execute ." Hello from OpenBIOS!" cr ;
\ postpone — 컴파일 타임 동작 지연
: my-if postpone if ; immediate
문자열 처리
Open Firmware Forth에서 문자열은 ( addr len ) 쌍으로 표현됩니다:
\ 문자열 출력 워드
ok ." Hello World" cr \ 즉시 출력
ok " test string" type cr \ addr len을 스택에 push 후 type으로 출력
\ 문자 단위 조작
ok [char] A emit \ 'A' 문자 출력
ok h# 41 emit \ 0x41 = 'A' 출력
ok 10 emit \ newline (LF)
\ 문자열 비교
: same? ( addr1 len1 addr2 len2 -- flag )
rot over <> if 2drop drop false exit then
comp 0=
;
\ 16진수 숫자 출력
ok h# deadbeef . \ 3735928559 (10진)
ok hex deadbeef . \ DEADBEEF (16진 모드)
ok decimal \ 10진 모드 복귀
디바이스 노드와 트리
Open Firmware 디바이스 트리는 하드웨어 토폴로지를 계층적 트리로 표현합니다. 이 구조가 직접적으로 Linux Flattened Device Tree (FDT)의 기원입니다. 현대 ARM/RISC-V 시스템의 Device Tree 사양과 커널 API에 대해서는 디바이스 트리 심화 문서를 참고하세요.
패키지(Package)와 인스턴스(Instance)
Open Firmware에서 가장 중요한 개념 구분은 패키지와 인스턴스입니다:
| 개념 | 핸들 타입 | 생명주기 | 비유 | Linux 커널 대응 |
|---|---|---|---|---|
| 패키지 (Package) | phandle | 부팅 시 생성, 영구 존재 | 클래스 정의 | struct device_node |
| 인스턴스 (Instance) | ihandle | open 시 생성, close 시 소멸 | 객체 인스턴스 | struct file (유사) |
/sbus/esp/sd@3,0:a를 open하면, sbus, esp, sd 각각의 인스턴스가 체인으로 연결됩니다. I/O 요청은 이 체인을 따라 전파됩니다. 이는 Linux의 VFS 레이어가 파일 → 파일시스템 → 블록 디바이스로 요청을 전달하는 것과 유사한 구조입니다.
노드와 프로퍼티
각 디바이스 트리 노드는 패키지(package)라 불리며, 프로퍼티와 메서드를 포함합니다:
| 구성요소 | 설명 | 예시 |
|---|---|---|
| 이름 | 노드의 고유 식별자 | /sbus/esp@0,800000 |
| 프로퍼티 | 이름-값 쌍 (바이트 배열) | device_type="block" |
| 메서드 | Forth 워드로 정의된 실행 가능 코드 | open, close, read, write |
| unit-address | 부모 버스 내 주소 | @0,800000 |
디바이스 경로 (Device Path)
Open Firmware는 파일시스템과 유사한 경로 표기법으로 디바이스를 참조합니다:
\ 디바이스 경로 예시
/sbus@1f,0/esp@0,800000/sd@3,0:a \ SCSI 디스크 파티션 a
/pci@1f,4000/network@1,1 \ PCI 네트워크 카드
/memory@0,0 \ 물리 메모리
\ 별칭 (alias)
devalias disk /sbus/esp/sd@3,0
devalias net /sbus/le@0,c00000
\ 디바이스 트리 탐색 명령
ok show-devs \ 전체 디바이스 트리 출력
ok cd /sbus \ 현재 노드 변경
ok .properties \ 현재 노드의 프로퍼티 출력
ok ls \ 자식 노드 목록
프로퍼티 인코딩 규칙
Open Firmware 프로퍼티 값은 바이트 배열입니다. 인코딩 워드를 사용하여 다양한 타입을 바이트 배열로 변환합니다:
| 인코딩 워드 | 스택 효과 | 설명 |
|---|---|---|
encode-int | ( n -- prop-addr prop-len ) | 32비트 정수를 빅엔디안 4바이트로 인코딩 |
encode-string | ( str-addr str-len -- prop-addr prop-len ) | 문자열을 null-terminated로 인코딩 |
encode-bytes | ( data-addr data-len -- prop-addr prop-len ) | 바이트 배열을 그대로 인코딩 |
encode+ | ( p1-addr p1-len p2-addr p2-len -- p3-addr p3-len ) | 두 인코딩 값을 연결(concatenate) |
encode-phys | ( phys.lo phys.hi -- prop-addr prop-len ) | 물리 주소 인코딩 (버스 종속) |
\ 프로퍼티 인코딩 예시
\ 단일 정수 프로퍼티
h# 1000 encode-int " size" property
\ 문자열 프로퍼티
" ACME,widget-3000" encode-string " compatible" property
\ reg 프로퍼티 (주소, 크기 쌍)
my-address my-space encode-phys
0 encode-int encode+ \ 크기 상위
h# 100 encode-int encode+ \ 크기 하위
" reg" property
\ 다중 문자열 프로퍼티 (compatible 리스트)
" ACME,widget-3000" encode-string
" generic,widget" encode-string encode+
" compatible" property
표준 프로퍼티 레퍼런스
| 프로퍼티 | 타입 | 설명 |
|---|---|---|
name | 문자열 | 노드 이름 (주로 device-name 워드로 설정) |
device_type | 문자열 | 디바이스 타입: "block", "network", "display", "serial", "memory" 등 |
compatible | 문자열 리스트 | 호환 디바이스 식별자 (가장 구체적 → 일반적 순서) |
model | 문자열 | 디바이스 모델 이름 (사람이 읽을 수 있는) |
reg | 정수 배열 | 부모 버스 내 주소-크기 쌍 (Bus Binding에 따라 인코딩) |
ranges | 정수 배열 | 자식→부모 주소 변환 테이블 (빈 값이면 1:1 매핑) |
interrupts | 정수 배열 | 인터럽트 사양 |
#address-cells | 정수 | 자식 노드 reg에서 주소 부분의 셀 수 |
#size-cells | 정수 | 자식 노드 reg에서 크기 부분의 셀 수 |
local-mac-address | 6 바이트 | 네트워크 디바이스의 MAC 주소 |
assigned-addresses | 정수 배열 | PCI BAR에 할당된 주소 |
clock-frequency | 정수 | 디바이스 클럭 주파수 (Hz) |
Forth로 디바이스 노드 생성
\ 새 디바이스 노드 추가 예시
new-device
" my-device" device-name
" block" device-type
my-address my-space encode-phys
0 encode-int encode+
h# 100 encode-int encode+
" reg" property
: open ( -- ok? ) true ;
: close ( -- ) ;
: read ( addr len -- actual )
\ ... 읽기 구현 ...
;
finish-device
FCode 바이트코드
FCode는 Open Firmware의 플랫폼 독립적인 바이트코드 형식입니다. Forth 소스를 토크나이즈(tokenize)하여 컴팩트한 바이너리로 변환하며, 확장 카드의 ROM에 저장하여 어떤 Open Firmware 시스템에서든 드라이버를 로드할 수 있게 합니다.
FCode 구조
| 필드 | 크기 | 설명 |
|---|---|---|
start-code | 1 byte | 0xF0 (FCode start1) 또는 0xF1 (start2) |
format | 1 byte | FCode 형식 버전 (보통 0x08) |
checksum | 2 bytes | 본문 체크섬 |
length | 4 bytes | FCode 이미지 전체 길이 |
body | 가변 | 토큰화된 Forth 워드 시퀀스 |
end-code | 1 byte | 0x00 (end0) |
주요 FCode 토큰 번호
| 토큰 | FCode # | 설명 |
|---|---|---|
end0 | 0x00 | FCode 이미지 종료 |
b(lit) | 0x10 | 다음 4바이트를 리터럴 정수로 push |
b(') | 0x11 | 다음 토큰의 실행 토큰(xt)을 push |
b(") | 0x12 | 인라인 문자열 리터럴 |
bbranch | 0x13 | 무조건 분기 (오프셋 따름) |
b(?branch) | 0x14 | 조건부 분기 (flag에 따라) |
b(loop) | 0x15 | loop 종료 체크 |
b(+loop) | 0x16 | +loop 종료 체크 |
b(do) | 0x17 | do 루프 시작 |
new-token | 0xB5 | 새 토큰 번호에 워드 할당 |
named-token | 0xB6 | 이름 있는 새 토큰 |
external-token | 0xCA | 외부에서 접근 가능한 토큰 |
property | 0x110 | 프로퍼티 설정 (2바이트 토큰) |
new-device | 0x11F | 새 디바이스 노드 생성 |
finish-device | 0x127 | 디바이스 노드 완료 |
FCode 토큰 예시
\ Forth 소스
: probe-self ( -- )
my-address my-space " reg" property
" network" device-type
;
\ 토크나이즈 후 FCode 바이트코드 (개념적)
\ 0xF1 0x08 checksum length
\ b(lit) 0x... \ my-address
\ b(lit) 0x... \ my-space
\ 0x110 \ "reg" property 토큰
\ 0x11A \ "network" device-type 토큰
\ 0x00 \ end0
FCode 토크나이저
OpenBIOS에는 toke라는 FCode 토크나이저가 포함되어 있습니다:
# Forth 소스를 FCode로 변환
toke my-driver.fs -o my-driver.fc
# FCode를 디토크나이즈(디컴파일)
detok my-driver.fc
FCode 개발 엔드투엔드 워크플로
FCode 드라이버의 전체 개발-빌드-배포-검증 과정입니다:
# 1. Forth 소스 작성
cat > my-nic.fs <<'EOF'
fcode-version2
" network" device-type
" ACME,nic-1000" encode-string " compatible" property
: open ( -- ok? ) true ;
: close ( -- ) ;
: read ( buf len -- actual ) 2drop -2 ;
: write ( buf len -- actual ) nip ;
fcode-end
EOF
# 2. FCode로 토크나이즈 (컴파일)
toke my-nic.fs -o my-nic.fc
# → my-nic.fc 바이너리 생성
# 3. 디토크나이즈로 검증 (역어셈블)
detok my-nic.fc
# start1 (0xF1) format: 0x08
# checksum: 0x1234 length: 0x0080
# named-token: open ...
# named-token: close ...
# end0
# 4. 16진수 덤프로 바이너리 구조 확인
hexdump -C my-nic.fc | head -8
# 00000000 f1 08 xx xx 00 00 00 80 ...
# 00000008 b6 04 6f 70 65 6e ... (named-token "open")
# 5. PCI Expansion ROM 이미지 생성 (실제 하드웨어용)
# ROM 헤더(0x55AA) + PCI Data Structure + FCode 이미지 연결
# 도구: romheader (fcode-utils 포함)
# 6. QEMU에서 테스트
qemu-system-sparc -machine SS-5 -m 128M -nographic \
-device pci-testdev,romfile=my-nic.fc
디바이스 트리 조작 FCode 토큰 레퍼런스
FCode에서 디바이스 트리를 구성하는 데 사용되는 핵심 토큰들입니다:
| 토큰 | FCode # | 스택 효과 | 설명 |
|---|---|---|---|
new-device | 0x11F | ( -- ) | 현재 노드 아래 새 자식 노드 생성, 컨텍스트 이동 |
finish-device | 0x127 | ( -- ) | 현재 노드 확정, 부모 노드로 컨텍스트 복귀 |
device-name | 0x201 | ( str len -- ) | 현재 노드의 name 프로퍼티 설정 |
device-type | 0x11A | ( str len -- ) | 현재 노드의 device_type 프로퍼티 설정 |
property | 0x110 | ( val-addr val-len name-str name-len -- ) | 임의의 프로퍼티 생성 |
encode-int | 0x111 | ( n -- addr len ) | 정수를 빅엔디안 4바이트로 인코딩 |
encode-string | 0x114 | ( str len -- addr len ) | 문자열을 null-terminated로 인코딩 |
encode+ | 0x112 | ( addr1 len1 addr2 len2 -- addr3 len3 ) | 두 인코딩 값 연결 |
model | 0x119 | ( str len -- ) | model 프로퍼티 설정 |
my-address | 0x102 | ( -- phys.lo ... ) | 현재 디바이스의 버스 주소 (부모 bus-binding에 따라 셀 수 다름) |
my-space | 0x103 | ( -- phys.hi ) | 현재 디바이스의 주소 공간 식별자 |
FCode 드라이버 예시 — 간단한 네트워크 카드
PCI 확장 카드의 FCode ROM에 들어갈 수 있는 드라이버 예시입니다. 이 FCode는 카드가 장착된 어떤 Open Firmware 시스템에서든 자동으로 실행됩니다:
\ PCI 네트워크 카드 FCode 드라이버 예시
\ 실제 하드웨어 레지스터 접근은 생략한 구조적 예시
fcode-version2 \ FCode 시작 선언
\ ---- 프로퍼티 설정 ----
" network" device-type \ 네트워크 디바이스임을 선언
" ACME,net-100" encode-string
" generic,ethernet" encode-string encode+
" compatible" property \ 호환성 문자열
\ ---- 인스턴스 변수 ----
instance variable tx-buffer \ 송신 버퍼 주소
instance variable rx-buffer \ 수신 버퍼 주소
instance variable mac-buf \ MAC 주소 버퍼
\ ---- 하드웨어 레지스터 접근 ----
my-address my-space \ PCI BAR 주소
h# 10 + \ BAR0 오프셋
h# 1000 " map-in" $call-parent \ MMIO 매핑
constant reg-base
: reg@ ( offset -- value ) reg-base + rl@ ;
: reg! ( value offset -- ) reg-base + rl! ;
\ ---- 디바이스 메서드 ----
: open ( -- ok? )
h# 1000 alloc-mem tx-buffer !
h# 1000 alloc-mem rx-buffer !
\ 하드웨어 초기화 (레지스터 설정)
h# 01 0 reg! \ 리셋 레지스터
true \ 성공
;
: close ( -- )
tx-buffer @ h# 1000 free-mem
rx-buffer @ h# 1000 free-mem
\ 하드웨어 정지
h# 00 0 reg!
;
: read ( buf len -- actual )
\ 수신 대기, 데이터 복사
4 reg@ h# 01 and 0= if \ 수신 데이터 없으면
drop drop -2 exit \ -2 = 데이터 없음
then
\ ... 실제 수신 구현 ...
;
: write ( buf len -- actual )
\ 송신 버퍼에 복사, 전송 시작
\ ... 실제 송신 구현 ...
;
: load ( addr -- len )
\ TFTP 부팅용 — 네트워크에서 파일 로드
" obp-tftp" find-package if
open-package \ TFTP 패키지 열기
\ ... TFTP 프로토콜 처리 ...
then
;
fcode-end \ FCode 종료
ok show-devs로 카드가 정상 등록되었는지 확인하고, ok test /sbus/SUNW,hme로 카드 자체 진단을 실행할 수 있었습니다.
부트 로더 체인
OpenBIOS의 부팅 과정은 하드웨어 초기화부터 OS 커널 진입까지 여러 단계를 거칩니다.
부트 단계별 상세
1~3단계: 하드웨어 초기화와 Forth 엔진 시작
CPU 리셋 벡터에서 시작하여, 플랫폼 종속 어셈블리 코드(arch/sparc32/entry.S)가 최소한의 하드웨어 초기화를 수행합니다:
CPU Reset Vector (플랫폼별)
└→ 트랩 테이블 설정 (SPARC: TBR, SPARC64: TBA)
└→ MMU 초기화 (페이지 테이블, 컨텍스트 레지스터)
└→ 스택 영역 할당
└→ BSS 클리어
└→ C 코드 진입: arch_init()
└→ 메모리 컨트롤러 감지 (QEMU에서는 fw_cfg로 획득)
└→ 콘솔 디바이스 초기화 (시리얼 or 프레임버퍼)
└→ Forth 딕셔너리 영역 할당
└→ 기본 워드 등록 (bootstrap.c)
└→ Forth REPL(Read-Eval-Print Loop) 시작
4~5단계: 디바이스 열거와 트리 구축
Forth 엔진이 시작되면, 플랫폼별 디바이스 열거 코드가 실행됩니다:
\ SPARC sun4m 디바이스 열거 (개념적)
" /" find-device
\ 메모리 노드 생성
new-device
" memory" device-name
" memory" device-type
0 encode-int mem-size encode-int encode+
" reg" property
finish-device
\ SBus 열거
new-device
" sbus" device-name
\ SBus 슬롯을 순회하며 FCode ROM이 있으면 실행
sbus-slots 0 do
i sbus-probe-slot \ 슬롯에 카드가 있으면
if
new-device
i sbus-slot-fcode \ FCode ROM 주소 획득
1 byte-load \ FCode 실행 → 카드 스스로 노드 구성
finish-device
then
loop
finish-device
new-device/finish-device를 호출하여 디바이스 노드를 스스로 등록합니다.
6~7단계: 배너 표시와 부트 디바이스 선택
디바이스 트리 구축이 완료되면 시스템 배너가 표시됩니다:
Sun Ultra 5/10 UPA/PCI (UltraSPARC-IIi 400MHz), Keyboard Present
OpenBIOS 1.1 [Oct 15 2023 12:00]
cpu0: TI,UltraSparc-IIi @ 400,000,000 Hz
0 > (또는 ok 프롬프트)
auto-boot?가 true이면 즉시 boot-command(기본값: boot)를 실행합니다. false이면 ok 프롬프트에서 사용자 입력을 기다립니다.
8~10단계: 커널 로드와 진입
부트 디바이스가 결정되면 해당 디바이스의 open→read 체인을 통해 커널을 로드합니다:
boot 명령 처리:
1. boot-device 별칭 해석: "disk" → "/pci@1f,0/ide@d/disk@0,0"
2. 파티션 인자 파싱: ":a" → 첫 번째 파티션
3. 파티션 레이블 패키지 실행 (sun-parts / mac-parts / pc-parts)
4. 파일시스템 패키지 실행 (UFS / ext2 / HFS+)
5. boot-file이 있으면 해당 파일, 없으면 기본 커널 로드
6. ELF 헤더 파싱: e_entry, PT_LOAD 세그먼트 확인
7. PT_LOAD 세그먼트를 load-base 주소에 복사
8. ELF 엔트리 포인트로 점프 (go 명령)
9. Client Interface 콜백 주소를 레지스터로 전달
- SPARC: %o0~%o3에 romvec/Client Interface 포인터
- PPC: r3~r5에 DT 포인터, OF entry, 0
NVRAM 부트 변수
Open Firmware는 NVRAM에 부팅 설정을 저장합니다:
| 변수 | 기본값 | 설명 |
|---|---|---|
auto-boot? | true | 자동 부팅 여부 |
boot-device | disk | 부트 디바이스 별칭 또는 경로 |
boot-file | (빈 문자열) | 커널 파일 경로 또는 부트 인자 |
boot-command | boot | 자동 부팅 시 실행할 명령 |
diag-switch? | false | 진단 모드 활성화 |
input-device | keyboard | 콘솔 입력 디바이스 |
output-device | screen | 콘솔 출력 디바이스 |
\ NVRAM 변수 조회/설정
ok printenv \ 모든 변수 출력
ok printenv boot-device \ 특정 변수 조회
ok setenv boot-device disk:a \ 부트 디바이스 변경
ok setenv auto-boot? false \ 자동 부팅 비활성화
\ 수동 부팅
ok boot disk:a \ 디스크에서 부팅
ok boot net \ 네트워크(TFTP)에서 부팅
ok boot cdrom \ CD-ROM에서 부팅
플랫폼별 부트 체인 차이점
동일한 Open Firmware 표준이라도 플랫폼마다 부트 체인이 크게 다릅니다:
| 단계 | SPARC32 (sun4m) | SPARC64 (sun4u) | PowerPC (Mac) |
|---|---|---|---|
| ROM 위치 | 0xFFD00000 | 0xFFF00000 | 0xFFF00000 |
| CPU 모드 | 32비트 슈퍼바이저 | 64비트 가상 주소 | 32비트 실주소 |
| 디바이스 버스 | SBus, OBIO | PCI, UPA | PCI, AGP |
| 파티션 | Sun VTOC (sun-parts) | Sun VTOC, EFI GPT | Apple 파티션 맵 (mac-parts) |
| 파일시스템 | UFS (SunOS/Solaris) | UFS, ext2 | HFS+, ext2 |
| 커널 진입 | %o0=romvec | %o0=CIF 포인터 | r5=CIF 포인터 |
| 2차 부트로더 | 없음 (직접 커널 로드) | SILO (선택적) | BootX / yaboot |
| 초기 콘솔 | 시리얼 (Zilog 8530) | 시리얼 / PCI VGA | 시리얼 / ATI 프레임버퍼 |
Apple Mac Open Firmware 부팅 체인 상세
NewWorld Macintosh(iMac G3~PowerMac G5)의 Open Firmware 부팅 체인은 가장 복잡한 형태입니다:
Apple NewWorld Mac 부팅 체인:
1. CPU Reset → BootROM (0xFFF00000)
└→ POST (Power-On Self Test)
└→ 키보드 스캔: Cmd+Opt+O+F → OF 프롬프트 진입
2. Open Firmware 초기화
└→ PCI/AGP 버스 열거 → FCode 실행
└→ USB 컨트롤러 초기화 (OHCI)
└→ ATA/ATAPI 컨트롤러 열거
└→ PMU/CUDA 전력관리 초기화
3. 부트 디바이스 탐색
└→ boot-device = "hd:,\\:tbxi" (Apple 규약)
└→ Apple 파티션 맵 파싱 (mac-parts 패키지)
└→ HFS+ 파일시스템 탐색
└→ "Blessed Folder"에서 tbxi 타입 파일 검색
4. 2차 부트로더 로드
├→ Mac OS 9: System Folder/Mac OS ROM
├→ Mac OS X: /System/Library/CoreServices/BootX
└→ Linux: /boot/yaboot (또는 /etc/yaboot.conf)
5. yaboot (Linux 부트로더) 실행 시:
└→ yaboot.conf 파싱 → 커널/initrd 경로 결정
└→ OF 서비스로 HFS+ 파티션에서 vmlinux 로드
└→ initrd 로드 (OF claim으로 메모리 할당)
└→ r3=DT base, r4=kernel, r5=OF entry 설정
└→ 커널 엔트리 호출
6. Linux prom_init() 실행
└→ OF DT 순회 → FDT 블롭 생성
└→ quiesce → start_kernel()
tbxi(Toolbox Image)는 부팅 가능한 이미지를 의미하며, "Blessed Folder"(축복받은 폴더)에 있는 tbxi 파일이 자동으로 부트 로더로 선택됩니다. 이 메커니즘은 Mac OS 특유의 "System Folder" 개념과 밀접하게 연관됩니다.
Client Interface
Client Interface는 OS가 부팅 후에도 펌웨어 서비스를 호출할 수 있는 표준 ABI입니다. IEEE 1275 §6에 정의되어 있으며, 단일 진입점(cif_handler)을 통해 다양한 서비스를 제공합니다.
호출 규약
Client Interface는 하나의 C 호출 규약 함수 포인터로 제공됩니다:
/* Client Interface 호출 구조 */
typedef struct {
const char *service; /* 서비스 이름 문자열 */
int nargs; /* 입력 인자 수 */
int nreturns; /* 반환 값 수 */
/* 이하 가변 인자... */
} client_interface_args_t;
/* 호출 예: getprop */
struct {
const char *service; /* "getprop" */
int nargs; /* 4 */
int nreturns; /* 1 */
phandle node; /* 디바이스 노드 핸들 */
const char *name; /* 프로퍼티 이름 */
void *buf; /* 결과 버퍼 */
int buflen; /* 버퍼 크기 */
int actual; /* [반환] 실제 크기 */
} args;
주요 Client Interface 서비스
| 서비스 | 인자 | 설명 |
|---|---|---|
finddevice | path | 경로로 디바이스 노드 핸들(phandle) 검색 |
getprop | phandle, name, buf, len | 프로퍼티 값 읽기 |
setprop | phandle, name, buf, len | 프로퍼티 값 쓰기 |
getproplen | phandle, name | 프로퍼티 값 길이 조회 |
nextprop | phandle, previous, buf | 다음 프로퍼티 이름 열거 |
child | phandle | 첫 번째 자식 노드 |
peer | phandle | 형제 노드 |
parent | phandle | 부모 노드 |
open | device-specifier | 디바이스 인스턴스 열기 |
close | ihandle | 디바이스 인스턴스 닫기 |
read | ihandle, buf, len | 디바이스에서 읽기 |
write | ihandle, buf, len | 디바이스에 쓰기 |
claim | virt, size, align | 메모리 할당 |
release | virt, size | 메모리 해제 |
milliseconds | (없음) | 부팅 이후 경과 밀리초 |
exit | (없음) | 펌웨어로 복귀 |
Client Interface 실전 호출 패턴
OS 커널이 Client Interface를 사용하여 부팅 정보를 획득하는 대표적 패턴들입니다:
/* 패턴 1: finddevice + getprop — 디바이스 트리 탐색의 기본 */
phandle root = prom_finddevice("/");
char compatible[64];
int len = prom_getprop(root, "compatible", compatible, sizeof(compatible));
/* → compatible = "sun4m" 또는 "PowerMac10,1" 등 */
/* 패턴 2: open → read → close — 디바이스 I/O */
ihandle disk = prom_open("/pci/ide/disk@0,0:a");
if (disk != (ihandle)-1) {
char buf[512];
int actual = prom_read(disk, buf, 512); /* 첫 512바이트 읽기 */
prom_close(disk);
}
/* 패턴 3: claim + release — 메모리 할당 */
void *mem = prom_claim(0, 0x100000, 0x1000); /* 1MB, 4K 정렬 */
/* ... 메모리 사용 ... */
prom_release(mem, 0x100000);
/* 패턴 4: write — 콘솔 출력 (prom_printf 내부) */
ihandle stdout = prom_get_chosen_ihandle("stdout");
const char *msg = "Booting Linux...\r\n";
prom_write(stdout, msg, strlen(msg));
/* 패턴 5: milliseconds — 타이밍/대기 */
unsigned long start = prom_milliseconds();
while (prom_milliseconds() - start < 1000) {
/* 1초 대기 */
}
/* 패턴 6: DT 트리 순회 (재귀적) */
void walk_tree(phandle node, int depth) {
phandle child;
char name[32];
prom_getprop(node, "name", name, sizeof(name));
/* name 처리 ... */
child = prom_child(node);
while (child != 0 && child != (phandle)-1) {
walk_tree(child, depth + 1);
child = prom_peer(child);
}
}
prom_init()은 인터럽트를 비활성화한 상태에서 모든 OF 호출을 수행하며, SPARC64에서는 p1275_cmd_direct()가 스핀락으로 직렬화합니다. quiesce 호출 후에는 어떤 CI 호출도 해서는 안 됩니다.
/chosen 노드 — 부팅 정보 전달
Open Firmware는 /chosen이라는 특별한 노드를 통해 부팅 관련 정보를 OS에 전달합니다:
| 프로퍼티 | 설명 | 예시 |
|---|---|---|
stdin | 표준 입력 디바이스 ihandle | 키보드 또는 시리얼 포트 |
stdout | 표준 출력 디바이스 ihandle | 프레임버퍼 또는 시리얼 포트 |
bootpath | 부트 디바이스 전체 경로 | /pci@1f,0/ide@d/disk@0,0:a |
bootargs | 커널 부트 인자 | root=/dev/sda1 console=ttyS0 |
mmu | MMU 패키지 ihandle | 가상-물리 주소 변환 서비스 |
memory | 메모리 패키지 ihandle | 물리 메모리 할당 서비스 |
\ /chosen 노드 확인
ok dev /chosen .properties
stdin ff07e638
stdout ff07e400
bootpath /pci@1f,0/ide@d/disk@0,0:a
bootargs
mmu ff0b1428
memory ff0b1308
아키텍처별 커널 진입 규약
| 아키텍처 | Client Interface 전달 방식 | 엔트리 프로토콜 |
|---|---|---|
| SPARC32 | %o0 = romvec 포인터 (OBP v0/v2 호환) | ELF 엔트리 직접 호출, 슈퍼바이저 모드 |
| SPARC64 | %o0 = Client Interface 함수 포인터, %o1 = 선택 인자 | 64비트 가상 주소 모드 |
| PowerPC (Mac) | r5 = Client Interface 함수 포인터 | r3 = DT base, r4 = kernel image, 실주소 모드 |
| PowerPC (CHRP) | r5 = OF entry, r6 = MSR 설정 | r3 = initrd, r4 = initrd size |
Linux 커널과의 상호작용
Linux 커널은 Open Firmware 디바이스 트리에 깊이 의존합니다. 특히 SPARC와 PowerPC 포트에서 OF Client Interface는 부팅 초기에 핵심적인 역할을 합니다. 커널의 of_* API와 디바이스 트리 전반에 대한 상세는 해당 문서를 참고하세요.
prom_init() — 커널 진입 시점
Linux 커널이 Open Firmware 시스템에서 부팅될 때, 가장 먼저 실행되는 코드 중 하나가 prom_init()입니다:
/* arch/sparc/kernel/prom_common.c (개념적) */
void __init prom_init(void)
{
/* 1. Client Interface를 통해 디바이스 트리 순회 */
phandle root = prom_finddevice("/");
/* 2. 메모리 레이아웃 파악 */
prom_getprop(root, "available", ...);
/* 3. 디바이스 트리를 커널 내부 구조로 복사 */
of_pdt_build_devicetree(root, ...);
/* 4. 콘솔 초기화 */
prom_getprop(prom_stdout, "device_type", ...);
}
of_* API — Open Firmware에서 유래한 커널 API
Linux 커널의 of_* (Open Firmware) API는 이름 그대로 Open Firmware에서 직접 유래했습니다:
| 커널 API | OF Client Interface 대응 | 설명 |
|---|---|---|
of_find_node_by_path() | finddevice | 경로로 노드 검색 |
of_get_property() | getprop | 프로퍼티 읽기 |
of_property_read_u32() | getprop + 파싱 | 정수 프로퍼티 읽기 |
of_get_child_count() | child + peer | 자식 노드 수 |
of_find_compatible_node() | getprop("compatible") | 호환성 문자열로 검색 |
of_platform_populate() | 디바이스 열거 | DT에서 platform_device 생성 |
.dts (Device Tree Source) 형식의 Flattened Device Tree (FDT)로 발전했습니다. 현재 ARM, RISC-V, MIPS 등 다양한 아키텍처에서 표준으로 사용됩니다.
OF Device Tree → FDT 변환 과정 상세
PowerPC Linux에서 OF의 라이브 디바이스 트리를 FDT 바이너리 블롭으로 변환하는 과정은 다음과 같습니다:
/* arch/powerpc/kernel/prom_init.c — OF DT → FDT 변환 (개념적) */
/* FDT 블롭 헤더 구성 */
struct boot_param_header {
uint32_t magic; /* 0xd00dfeed */
uint32_t totalsize;
uint32_t off_dt_struct; /* DT 구조체 시작 오프셋 */
uint32_t off_dt_strings; /* 문자열 블록 오프셋 */
uint32_t off_mem_rsvmap;
uint32_t version; /* 17 */
/* ... */
};
/* 변환 핵심 루프: OF DT 재귀 순회 → FDT 직렬화 */
static void __init scan_dt_node(phandle node)
{
char name[256];
phandle child;
/* 1. FDT_BEGIN_NODE 토큰 기록 */
prom_getprop(node, "name", name, sizeof(name));
dt_push_token(FDT_BEGIN_NODE);
dt_push_string(name);
/* 2. 모든 프로퍼티를 FDT_PROP으로 기록 */
char prev[64] = "";
while (prom_nextprop(node, prev, prev) == 1) {
int len = prom_getproplen(node, prev);
char *val = prom_claim(0, len, 0);
prom_getprop(node, prev, val, len);
dt_push_token(FDT_PROP);
dt_push_u32(len);
dt_push_u32(dt_string_offset(prev)); /* 문자열 테이블 참조 */
dt_push_bytes(val, len);
prom_release(val, len);
}
/* 3. 자식 노드 재귀 처리 */
child = prom_child(node);
while (child != 0 && child != (phandle)-1) {
scan_dt_node(child); /* 재귀 */
child = prom_peer(child); /* 형제로 이동 */
}
/* 4. FDT_END_NODE 토큰 */
dt_push_token(FDT_END_NODE);
}
pdt 네이밍의 유래: Linux 커널 소스에서 SPARC OF 관련 코드에는 of_pdt_build_devicetree(), pdt_node_to_num() 등 pdt 접두사가 자주 등장합니다. 이 pdt는 "Prom Device Tree"의 약자입니다. "Prom"은 SPARC 세계에서 Open Firmware/OBP를 지칭하는 전통적 이름으로, Sun의 PROM(Programmable Read-Only Memory)에서 유래했습니다. 비록 실제로는 Flash나 EEPROM이지만, 역사적 이유로 "prom"이라는 이름이 SPARC Linux 코드 전체에 남아 있습니다. arch/sparc/kernel/prom_64.c, prom_finddevice(), prom_getprop() 등이 모두 이 전통을 이어갑니다.
PowerPC prom_init() 상세
PowerPC Linux의 prom_init()는 Open Firmware와의 상호작용이 가장 복잡합니다. 커널이 실주소 모드에서 시작하여 OF 서비스를 통해 메모리를 확보하고, 디바이스 트리를 FDT 블롭으로 변환하는 과정을 거칩니다:
/* arch/powerpc/kernel/prom_init.c (개념적 흐름) */
unsigned long __init prom_init(unsigned long r3, unsigned long r4,
unsigned long pp, unsigned long r6, unsigned long r7)
{
/* r5 = OF Client Interface entry point */
prom_entry = r5;
/* 1단계: 기본 콘솔 확보 */
prom_init_stdout(); /* /chosen/stdout → 콘솔 ihandle */
/* 2단계: CPU 정보 수집 */
prom_check_platform(); /* /cpus 노드에서 CPU 타입 파악 */
/* 3단계: Client Architecture Support (CAS) — pseries만 해당 */
prom_negotiate_cas(); /* 하이퍼바이저와 기능 협상 */
/* 4단계: 메모리 확보 */
alloc = prom_claim(base, size, 0); /* OF claim으로 메모리 할당 */
/* 5단계: 디바이스 트리 순회 → FDT 변환 */
prom_scan_dt(root); /* OF DT를 재귀 순회 */
/* finddevice → child → peer → getprop 반복 */
/* 결과를 FDT(Flattened Device Tree) 바이너리로 직렬화 */
/* 6단계: OF 서비스 종료 */
prom_call("quiesce"); /* OF에게 종료 통보 */
/* 7단계: 리얼 커널로 점프 */
/* FDT 블롭 주소를 r3에 넣고 커널 엔트리로 점프 */
__start(fdt_blob, 0, 0);
}
prom_call("quiesce")는 OF에게 "이제 더 이상 당신의 서비스를 사용하지 않겠다"고 알리는 것입니다. 이 호출 이후에는 Client Interface를 사용할 수 없습니다. OF가 사용하던 메모리와 하드웨어 리소스를 OS가 자유롭게 사용할 수 있게 됩니다.
SPARC prom 함수 상세
SPARC Linux는 두 가지 OF 인터페이스 버전을 지원합니다:
| 인터페이스 | SPARC 세대 | 커널 코드 | 특징 |
|---|---|---|---|
| OBP v0 (romvec) | sun4c (초기) | arch/sparc/prom/console_32.c | 함수 포인터 테이블, 제한적 API |
| OBP v2/v3 | sun4m, sun4d | arch/sparc/prom/tree_32.c | 디바이스 트리 지원, 풍부한 API |
| OBP v4 (sun4u) | UltraSPARC | arch/sparc/prom/p1275.c | IEEE 1275 완전 준수, 64비트 |
/* SPARC32 romvec 구조 (OBP v0) — 가장 원시적인 형태 */
struct linux_romvec {
char *pv_initstr;
unsigned int pv_v0mem; /* v0 메모리 디스크립터 */
struct node *pv_v0devops; /* v0 디바이스 ops */
/* ... */
int (*pv_nbgetchar)(void); /* 콘솔 문자 읽기 */
int (*pv_nbputchar)(int); /* 콘솔 문자 쓰기 */
/* ... */
struct linux_nodeops *pv_nodeops; /* v2+ 디바이스 트리 ops */
int (*pv_v2devops)(int, ...); /* v2 디바이스 ops */
};
/* SPARC64 — IEEE 1275 Client Interface 직접 사용 */
/* arch/sparc/prom/p1275.c */
int prom_finddevice(const char *name)
{
unsigned long args[5];
args[0] = (unsigned long)"finddevice";
args[1] = 1; /* nargs */
args[2] = 1; /* nreturns */
args[3] = (unsigned long)name;
p1275_cmd_direct(args);
return (int)args[4]; /* phandle */
}
OF에서 유래한 Linux DT 데이터 구조
/* include/linux/of.h — OF에서 직접 유래한 구조체 */
struct device_node {
const char *name; /* OF name 프로퍼티 */
phandle phandle; /* OF phandle — 그대로 계승 */
const char *full_name; /* OF 경로 (예: /sbus/esp@0,800000) */
struct property *properties; /* OF 프로퍼티 리스트 */
struct device_node *parent; /* 부모 노드 */
struct device_node *child; /* 첫 자식 — OF child 서비스 대응 */
struct device_node *sibling; /* 형제 — OF peer 서비스 대응 */
/* ... */
};
struct property {
char *name; /* 프로퍼티 이름 */
int length; /* 값 길이 (바이트) */
void *value; /* 프로퍼티 값 (바이트 배열) */
struct property *next; /* 다음 프로퍼티 */
};
SPARC Linux 부팅 시퀀스
OpenBIOS ok → boot disk:a
↓
ELF 헤더 파싱 → vmlinux 로드
↓
head_32.S / head_64.S 진입
↓
prom_init() — Client Interface로 OF DT 순회
↓
of_pdt_build_devicetree() — 내부 device_node 트리 구축
↓
prom_printf() 콘솔 출력 (OF write 서비스 사용)
↓
start_kernel() → 일반 Linux 초기화
↓
OF 서비스 더 이상 사용하지 않음 (own drivers 사용)
QEMU SPARC/PPC 연동
OpenBIOS는 QEMU에서 SPARC 및 PowerPC 에뮬레이션의 기본 펌웨어로 사용됩니다. QEMU가 에뮬레이트하는 하드웨어에 맞춰 OpenBIOS가 디바이스 트리를 구축합니다. QEMU의 전체 아키텍처와 KVM 연동에 대해서는 QEMU 심화 문서를 참고하세요.
fw_cfg 인터페이스
QEMU의 fw_cfg는 호스트(QEMU)가 게스트(OpenBIOS)에게 머신 구성 정보를 전달하는 특수 MMIO/PIO 인터페이스입니다. OpenBIOS는 이를 통해 하드코딩 없이 동적으로 머신 구성을 파악합니다:
| fw_cfg 키 | 설명 | OpenBIOS 용도 |
|---|---|---|
FW_CFG_RAM_SIZE | 게스트 RAM 크기 | /memory 노드 reg 프로퍼티 |
FW_CFG_NB_CPUS | CPU 수 | /cpus 아래 CPU 노드 생성 수 |
FW_CFG_MACHINE_ID | 머신 타입 ID | 플랫폼별 디바이스 트리 구성 선택 |
FW_CFG_KERNEL_ADDR | -kernel로 지정된 커널 주소 | 커널 직접 로드 시 사용 |
FW_CFG_KERNEL_SIZE | 커널 이미지 크기 | 메모리 영역 예약 |
FW_CFG_KERNEL_CMDLINE | 커널 커맨드라인 | /chosen/bootargs에 설정 |
FW_CFG_INITRD_ADDR | initrd 주소 | /chosen/linux,initrd-start |
FW_CFG_SETUP_ADDR | 추가 설정 데이터 주소 | 플랫폼별 활용 |
/* OpenBIOS에서 fw_cfg 읽기 (arch/sparc32/lib.c 개념적) */
/* SPARC32 fw_cfg MMIO 주소 */
#define FW_CFG_ADDR 0xd00000510ULL /* selector 레지스터 */
#define FW_CFG_DATA 0xd00000511ULL /* data 레지스터 */
static uint16_t fw_cfg_read_i16(uint16_t key)
{
/* 1. selector에 키 쓰기 */
*(volatile uint16_t *)FW_CFG_ADDR = key;
/* 2. data에서 값 읽기 */
return *(volatile uint16_t *)FW_CFG_DATA;
}
/* 사용 예: RAM 크기 획득 */
uint64_t ram_size = fw_cfg_read_i64(FW_CFG_RAM_SIZE);
/* → /memory 노드의 reg 프로퍼티에 반영 */
-m, -smp, -kernel 등의 옵션이 펌웨어에 도달하는 경로입니다.
QEMU 머신 타입과 OpenBIOS 지원
| 아키텍처 | 머신 타입 | 에뮬레이션 대상 | 주요 버스 | 특이사항 |
|---|---|---|---|---|
| SPARC32 | SS-4 | SPARCstation 4 | SBus | microSPARC-II |
SS-5 | SPARCstation 5 | SBus | 가장 많이 테스트됨 | |
SS-10 | SPARCstation 10 | SBus, MBus | SMP 지원 | |
SS-20 | SPARCstation 20 | SBus, MBus | SMP 지원 | |
SS-600MP | SPARCserver 600MP | SBus | 서버 모델 | |
Voyager | SPARCstation Voyager | SBus | 노트북 모델 | |
| SPARC64 | sun4u | Sun Ultra 5/10 | PCI | UltraSPARC-IIi |
| PowerPC | g3beige | Power Mac G3 (Beige) | PCI | OldWorld ROM 호환 |
mac99 | PowerMac G4 | PCI, AGP | NewWorld, PMU/CUDA |
QEMU SPARC32 (sun4m)
# SPARCstation 5 에뮬레이션
qemu-system-sparc \
-machine SS-5 \
-m 128M \
-drive file=solaris7.img,format=raw \
-nographic
# OpenBIOS 프롬프트로 진입
qemu-system-sparc -machine SS-5 -m 128M -nographic
# Ctrl+A, C 로 QEMU 모니터 전환 가능
# Linux SPARC32 부팅
qemu-system-sparc \
-machine SS-5 \
-m 256M \
-kernel vmlinux-sparc32 \
-append "root=/dev/sda1 console=ttyS0" \
-drive file=rootfs.img,format=raw \
-nographic
QEMU SPARC64 (sun4u)
# Sun Ultra 5 에뮬레이션
qemu-system-sparc64 \
-machine sun4u \
-m 512M \
-drive file=debian-sparc64.img,format=qcow2 \
-cdrom debian-sparc64-netinst.iso \
-nographic \
-boot d
QEMU PowerPC (Mac)
# Power Macintosh G3 (Beige) 에뮬레이션
qemu-system-ppc \
-machine g3beige \
-m 256M \
-drive file=macos9.img,format=raw \
-boot c
# PowerMac G4 (Mac99)
qemu-system-ppc \
-machine mac99,via=pmu \
-m 512M \
-drive file=debian-powerpc.img,format=qcow2 \
-cdrom debian-ppc-netinst.iso \
-nographic
QEMU에서 OpenBIOS 프롬프트 사용
\ QEMU SPARC에서 OpenBIOS 프롬프트
ok banner \ 시스템 정보 출력
ok show-devs \ 에뮬레이트된 디바이스 목록
ok dev /pci \ PCI 버스 노드로 이동
ok .properties \ PCI 버스 프로퍼티 확인
ok dev / \ 루트로 복귀
ok devalias \ 모든 디바이스 별칭
\ 디스크 정보 확인
ok probe-scsi-all \ SCSI 디바이스 스캔
ok probe-ide-all \ IDE 디바이스 스캔
\ 네트워크 부팅
ok boot net:dhcp \ DHCP + TFTP 부팅
\ 메모리 확인
ok .registers \ CPU 레지스터 덤프
ok show-memmap \ 메모리 맵
QEMU + OpenBIOS로 OS 부팅 실전
Solaris 7/8/9 SPARC 부팅
# Solaris SPARC — CD-ROM 설치
qemu-system-sparc \
-machine SS-5 \
-m 256M \
-cdrom sol-8-u7-sparc.iso \
-drive file=solaris8.img,format=raw \
-nographic \
-boot d
# Solaris 부팅 시 OpenBIOS가 수행하는 작업:
# 1. fw_cfg로 RAM 256MB 감지 → /memory 노드 생성
# 2. IDE 컨트롤러 열거 → /pci/ide 노드
# 3. CD-ROM에서 UFS 파티션 탐색
# 4. ELF 포맷 커널(genunix) 로드
# 5. romvec/Client Interface 전달 → 부팅
NetBSD SPARC64 부팅
# NetBSD/sparc64 — 디스크에서 부팅
qemu-system-sparc64 \
-machine sun4u \
-m 512M \
-drive file=netbsd-sparc64.img,format=qcow2 \
-nographic
# OpenBIOS 프롬프트에서 수동 부팅
# ok boot disk:a /netbsd
# → 디스크의 Sun 파티션 맵(sun-parts) 파싱
# → a 파티션의 FFS에서 /netbsd ELF 로드
Linux 커널 직접 부팅 (-kernel 옵션)
# QEMU -kernel 옵션 사용 시 부팅 흐름:
# 1. QEMU가 vmlinux를 게스트 메모리에 직접 로드
# 2. fw_cfg에 커널 주소/크기/cmdline 설정
# 3. OpenBIOS가 fw_cfg에서 감지 → 디스크 부팅 건너뜀
# 4. 커널 엔트리 포인트로 직접 점프
qemu-system-sparc \
-machine SS-5 \
-m 256M \
-kernel vmlinux-sparc32 \
-initrd initramfs-sparc32.cpio.gz \
-append "root=/dev/ram0 console=ttyS0 rdinit=/bin/sh" \
-nographic
커스텀 OpenBIOS 사용
# QEMU에 커스텀 빌드한 OpenBIOS 지정
qemu-system-sparc \
-machine SS-5 \
-bios /path/to/openbios-sparc32 \
-m 128M \
-nographic
qemu-system-sparc64 \
-machine sun4u \
-bios /path/to/openbios-sparc64 \
-m 512M \
-nographic
qemu-system-ppc \
-machine mac99 \
-bios /path/to/openbios-ppc \
-m 256M \
-nographic
QEMU 모니터를 활용한 OpenBIOS 분석
# QEMU 모니터 진입: Ctrl+A, C (nographic 모드)
# 또는 -monitor stdio 옵션
# 메모리 맵 확인 — OpenBIOS가 매핑한 영역 포함
(qemu) info mtree
# 디바이스 트리(QOM) 확인 — 에뮬레이트 디바이스
(qemu) info qtree
# 게스트 메모리 읽기 — OpenBIOS Forth 딕셔너리 탐색
(qemu) x/16wx 0xffd00000
# CPU 레지스터 확인
(qemu) info registers
# OpenBIOS 심볼 주소에 브레이크포인트 설정
(qemu) gdbstub tcp::1234
# 별도 터미널에서 GDB 연결
빌드와 커스터마이징
OpenBIOS는 GNU 툴체인으로 빌드하며, 크로스 컴파일을 지원합니다.
빌드 환경 구축
# 필수 패키지 (Ubuntu/Debian)
sudo apt install gcc-sparc64-linux-gnu gcc-powerpc-linux-gnu \
gcc-sparc64-linux-gnu binutils-sparc64-linux-gnu \
xsltproc fcode-utils
# 소스 받기
git clone https://github.com/openbios/openbios.git
cd openbios
빌드 과정
# 설정 — 타겟 아키텍처 선택
./config/scripts/switch-arch sparc32
# 또는
./config/scripts/switch-arch sparc64
./config/scripts/switch-arch ppc
# 빌드
make
# 결과물
# obj-sparc32/openbios-builtin.elf — QEMU용 SPARC32 이미지
# obj-sparc64/openbios-builtin.elf — QEMU용 SPARC64 이미지
# obj-ppc/openbios-builtin.elf — QEMU용 PPC 이미지
빌드 파이프라인
OpenBIOS 빌드의 전체 과정을 단계별로 시각화합니다:
- SPARC32:
sparc64-linux-gnu-gcc로 빌드하되,-m32플래그가 자동 적용됩니다. 별도의sparc-linux-gnu-gcc가 없어도 됩니다. - multilib 문제: 일부 배포판의 크로스 컴파일러는 32비트 라이브러리가 빠져 있을 수 있습니다. 빌드 실패 시
gcc-multilib패키지를 확인하세요. - fcode-utils: Forth 워드를 FCode로 변환하는
toke가 빌드 중 사용됩니다. 호스트(x86)용으로 설치해야 하며, 크로스 컴파일러와 혼동하지 마세요. - xsltproc: Forth 소스 파일을 C 배열로 변환하는 XML 처리에 필요합니다. 누락 시 빌드가 조용히 실패할 수 있습니다.
커스터마이징 포인트
| 영역 | 파일 | 설명 |
|---|---|---|
| 디바이스 드라이버 추가 | drivers/ | 새 하드웨어 드라이버를 C 또는 Forth로 작성 |
| Forth 워드 추가 | forth/ | 대화형 명령어나 유틸리티 추가 |
| 파티션 지원 | packages/ | 새로운 파티션 형식 지원 추가 |
| 부트 로더 형식 | libopenbios/ | ELF 외 바이너리 형식 지원 |
| 플랫폼 포팅 | arch/ | 새 머신 타입에 대한 초기화 코드 |
| 메모리 레이아웃 | arch/*/lib.c | 물리/가상 메모리 맵 변경 |
빌드 설정 옵션
OpenBIOS는 config/scripts/switch-arch로 타겟을 선택하며, 세부 설정은 .config 파일에서 관리됩니다:
| 설정 변수 | 기본값 | 설명 |
|---|---|---|
CONFIG_DEBUG_BOOT | off | 부팅 과정 상세 로그 출력 |
CONFIG_DEBUG_DICT | off | 딕셔너리 조작 디버그 메시지 |
CONFIG_DEBUG_OFMEM | off | OF 메모리 할당/해제 추적 |
CONFIG_PCI | 아키텍처 의존 | PCI 버스 지원 포함 |
CONFIG_IDE | on | IDE/ATA 디바이스 드라이버 |
CONFIG_LOADER_ELF | on | ELF 바이너리 로더 |
CONFIG_LOADER_AOUT | on | a.out 바이너리 로더 (SPARC) |
CONFIG_LOADER_FCODE | on | FCode 해석기 |
CONFIG_FONT_8X16 | on | 콘솔 폰트 (프레임버퍼용) |
크로스 컴파일 환경 상세
# 각 타겟에 필요한 크로스 컴파일러
# SPARC32: sparc-linux-gnu-gcc 또는 sparc64-linux-gnu-gcc
# SPARC64: sparc64-linux-gnu-gcc
# PowerPC: powerpc-linux-gnu-gcc
# Ubuntu/Debian에서 전체 설치
sudo apt install \
gcc-sparc64-linux-gnu \
gcc-powerpc-linux-gnu \
binutils-sparc64-linux-gnu \
binutils-powerpc-linux-gnu \
xsltproc \
fcode-utils
# 여러 타겟 동시 빌드
for arch in sparc32 sparc64 ppc; do
./config/scripts/switch-arch $arch
make -j$(nproc)
cp obj-${arch}/openbios-builtin.elf openbios-${arch}
make clean
done
# QEMU 소스 트리에 통합 (QEMU 빌드 시 자동 포함)
# QEMU는 pc-bios/ 디렉토리에 사전 빌드된 OpenBIOS를 포함합니다
ls qemu/pc-bios/openbios-*
# openbios-sparc32 openbios-sparc64 openbios-ppc
C 드라이버 추가 예
새로운 하드웨어 드라이버를 C로 작성하는 예시입니다:
/* drivers/my-device.c — 커스텀 디바이스 드라이버 */
#include "config.h"
#include "libopenbios/bindings.h"
#include "kernel/kernel.h"
/* 디바이스 레지스터 오프셋 */
#define REG_STATUS 0x00
#define REG_DATA 0x04
#define REG_CTRL 0x08
static void mydev_open(void)
{
/* open 메서드: 인스턴스 초기화 */
phys_addr_t base = get_parent_address();
/* MMIO 매핑 */
unsigned long virt = (unsigned long)map_io(base, 0x1000);
/* 하드웨어 리셋 */
*(volatile uint32_t *)(virt + REG_CTRL) = 1;
/* 성공 반환 */
PUSH(-1); /* true */
}
static void mydev_close(void)
{
/* close 메서드: 정리 */
}
static void mydev_read(void)
{
/* read 메서드: ( buf len -- actual ) */
int len = POP();
char *buf = (char *)POP();
/* ... 읽기 구현 ... */
PUSH(len);
}
/* 디바이스 노드 등록 */
void mydev_init(phys_addr_t base)
{
/* 디바이스 트리에 노드 생성 */
push_str("/my-bus");
feval("find-device");
feval("new-device");
push_str("my-device");
feval("device-name");
push_str("block");
feval("device-type");
/* 메서드 바인딩 */
BIND_NODE_METHODS(mydev, open, close, read);
feval("finish-device");
}
커스텀 Forth 워드 추가 예
\ forth/debugging/custom.fs — 커스텀 디버깅 워드
\ 모든 디바이스의 compatible 프로퍼티 출력
: show-compatible ( -- )
" /" find-device
begin
" compatible" get-my-property 0= if
decode-string type cr 2drop
then
next-device
0= until
;
\ 메모리 맵 요약
: meminfo ( -- )
" /memory" find-device
" reg" get-my-property 0= if
begin dup while
decode-int . ." : "
decode-int . ." bytes" cr
repeat 2drop
then
;
\ 디바이스 트리를 들여쓰기로 출력 (트리 뷰)
: .tree ( depth -- )
recursive
dup 0 do ." " loop \ 들여쓰기
" name" get-my-property 0= if
decode-string type 2drop
else
." (unnamed)"
then cr
child begin ?dup while \ 자식 순회
dup select-dev
over 1+ .tree \ 재귀
peer \ 형제로 이동
repeat
;
\ 전체 트리 출력
: tree ( -- )
" /" find-device 0 .tree
;
OpenBIOS 메모리 관리
Open Firmware는 OS가 시작되기 전에도 메모리 할당/해제를 수행하는 간단한 메모리 관리 시스템을 갖추고 있습니다. OpenBIOS의 메모리 관리는 libopenbios/ofmem_common.c에 구현되어 있습니다.
메모리 영역 분류
| 영역 | 관리 주체 | 설명 |
|---|---|---|
| 물리 메모리 (Physical) | /memory 노드 | 물리 RAM 전체, reg와 available 프로퍼티로 기술 |
| 가상 메모리 (Virtual) | /memory 또는 MMU 패키지 | MMU 페이지 테이블을 통한 매핑 관리 |
| OF 예약 영역 | OpenBIOS 자체 | Forth 딕셔너리, 스택, 디바이스 트리 데이터 |
| 클라이언트 영역 | OS (claim/release) | OS가 Client Interface로 할당받은 영역 |
\ OpenBIOS 메모리 조작 명령어
\ 물리 메모리 정보 확인
ok dev /memory .properties
reg 00000000 08000000 \ 0~128MB
available 00004000 07e00000 \ 사용 가능 영역
\ 가상 메모리 정보 확인
ok dev /virtual-memory .properties
available ... \ 가상 주소 가용 범위
translations ... \ 현재 매핑 테이블
\ 메모리 할당/해제 (Client Interface: claim/release)
ok 0 10000 1000 claim . \ 4K 정렬, 64K 할당 → 할당된 주소 출력
ok 40000 10000 release \ 주소 0x40000에서 64K 해제
\ 메모리 테스트
ok test /memory \ 기본 메모리 테스트 실행
\ 메모리 내용 확인
ok ffd00000 40 dump \ OpenBIOS ROM 시작 부분 덤프
ok 4000 20 dump \ load-base 시작 부분 덤프
\ MMU 매핑 확인
ok .tlb \ TLB 내용 출력 (SPARC)
ok dev /virtual-memory
ok " translations" get-my-property \ 현재 매핑
/memory 노드의 available 프로퍼티는 OS가 자유롭게 사용할 수 있는 물리 메모리 영역을 나열합니다. OpenBIOS 자체가 사용하는 영역(딕셔너리, 스택, 디바이스 트리)은 이 목록에서 제외됩니다. Linux의 prom_init()은 이 프로퍼티를 읽어 memblock에 등록합니다.
가상 ↔ 물리 주소 매핑
Open Firmware는 MMU를 통해 가상 주소와 물리 주소를 매핑합니다. /virtual-memory 노드의 translations 프로퍼티가 현재 활성 매핑을 기술합니다:
MMU 매핑 조작 Forth 예제
Open Firmware 프롬프트에서 MMU 매핑을 직접 조작하는 방법입니다:
\ ========== 현재 매핑 확인 ==========
\ TLB 내용 덤프 (SPARC)
ok .tlb
VA PA Size Mode
FFD00000 FFD00000 300K --C-V \ ROM (1:1 매핑)
F0000000 10000000 64K --C-V \ SBus MMIO
00004000 00004000 4MB --C-V \ 커널 텍스트
\ translations 프로퍼티로 매핑 목록 읽기
ok dev /virtual-memory
ok " translations" get-my-property 0= if
ok begin dup while
ok decode-int ." virt=" . ." → "
ok decode-int ." size=" .
ok decode-int ." phys=" .
ok decode-int ." mode=" . cr
ok repeat 2drop
ok then
\ ========== 매핑 생성/제거 ==========
\ 물리 주소 0x30000000를 가상 주소 0xE0000000에 4K 매핑
\ mode: 0x36 = Cacheable, Supervisor, Writable (SPARC)
ok 30000000 E0000000 1000 36 map
ok E0000000 l@ \ 매핑된 주소로 읽기 가능
\ 매핑 제거
ok E0000000 1000 unmap
\ ========== 디바이스 MMIO 매핑 ==========
\ PCI 디바이스의 BAR을 가상 주소에 매핑
ok dev /pci/ethernet@1
ok " assigned-addresses" get-my-property 0= if
ok decode-int drop \ phys.hi (PCI 인코딩)
ok decode-int swap decode-int swap \ phys.mid, phys.lo
ok 2drop \ 나머지 버림
ok \ map-in: 물리 주소를 가상 주소에 매핑
ok my-space swap h# 10000 " map-in" $call-parent
ok constant my-reg-base
ok then
\ ========== 메모리 테스트 패턴 ==========
\ claim → map → 테스트 → unmap → release 전체 사이클
ok 0 1000 1000 claim constant test-va \ 가상 주소 할당
ok 0 1000 1000 " claim" $call-memory \ 물리 주소 할당
ok constant test-pa
ok test-pa test-va 1000 36 map \ 매핑 생성
ok DEADBEEF test-va l! \ 패턴 쓰기
ok test-va l@ . cr \ 읽기 검증 → DEADBEEF
ok test-va 1000 unmap \ 매핑 제거
ok test-va 1000 release \ 가상 주소 해제
prom_init()이 OF의 translations 프로퍼티를 읽어 커널의 초기 페이지 테이블에 동일한 매핑을 재현한 뒤, OF의 페이지 테이블에서 커널 자체 페이지 테이블로 전환합니다. 이 "매핑 계승" 과정이 실패하면 커널은 즉시 크래시합니다. SPARC64에서는 inherit_prom_mappings() 함수가 이 역할을 담당하며, OF TLB 엔트리를 커널 TSB(Translation Storage Buffer)에 복사합니다. quiesce 호출 이후에야 OF의 페이지 테이블 메모리를 해제할 수 있습니다.
네트워크 부팅
Open Firmware는 네트워크 부팅을 기본 기능으로 지원합니다. RARP/BOOTP/DHCP로 IP 주소를 획득하고 TFTP로 커널을 다운로드하는 과정이 펌웨어 레벨에서 수행됩니다.
네트워크 부팅 흐름
ok boot net
1. boot-device 별칭 해석
"net" → "/sbus/le@0,c00000" (또는 /pci/.../network)
2. 네트워크 디바이스 open
NIC 드라이버 초기화, 인터럽트 설정
3. obp-tftp 패키지 로드
Open Firmware 내장 네트워크 스택
4. RARP/BOOTP/DHCP 요청
┌─────────┐ DHCPDISCOVER ┌──────────┐
│ OpenBIOS │ ──────────────────→ │ DHCP │
│ (게스트) │ ←────────────────── │ 서버 │
└─────────┘ DHCPOFFER └──────────┘
(IP, TFTP 서버, 파일명)
5. TFTP 요청
┌─────────┐ RRQ: vmlinux ┌──────────┐
│ OpenBIOS │ ──────────────────→ │ TFTP │
│ │ ←────────────────── │ 서버 │
└─────────┘ DATA 블록들 └──────────┘
6. 커널 이미지를 load-base에 적재
7. ELF 파싱 → 엔트리 포인트로 점프
네트워크 부팅 명령어
\ 기본 네트워크 부팅
ok boot net \ RARP + TFTP
\ DHCP 사용
ok boot net:dhcp \ DHCP + TFTP
\ 특정 서버/파일 지정
ok boot net:192.168.1.100,vmlinux-sparc,192.168.1.50
\ 네트워크 디바이스 테스트
ok test net \ NIC 자체 진단
ok watch-net \ 네트워크 패킷 모니터링
\ MAC 주소 확인
ok dev /sbus/le .properties
local-mac-address 08 00 20 xx xx xx
\ 네트워크 부팅 기본값 설정
ok setenv boot-device net:dhcp
ok setenv auto-boot? true
QEMU에서 네트워크 부팅 실습
# QEMU SPARC + TFTP 네트워크 부팅
# 1단계: TFTP 서버 준비
mkdir -p /tmp/tftpboot
cp vmlinux-sparc32 /tmp/tftpboot/
# 2단계: QEMU 실행 (user-mode 네트워크 + TFTP)
qemu-system-sparc \
-machine SS-5 \
-m 256M \
-nographic \
-netdev user,id=net0,tftp=/tmp/tftpboot,bootfile=vmlinux-sparc32 \
-device lance,netdev=net0
# OpenBIOS 프롬프트에서:
# ok boot net
# 3단계: QEMU 내장 DHCP가 응답 → TFTP로 커널 전송 → 부팅
# TAP 네트워크로 실제 DHCP/TFTP 서버 사용
qemu-system-sparc \
-machine SS-5 \
-m 256M \
-nographic \
-netdev tap,id=net0,ifname=tap0,script=no \
-device lance,netdev=net0
obp-tftp 패키지
OpenBIOS의 네트워크 스택은 obp-tftp 패키지로 구현됩니다. 이 패키지는 ARP, RARP, BOOTP, DHCP, TFTP 프로토콜을 Forth와 C의 조합으로 구현합니다:
| 기능 | 구현 파일 | 설명 |
|---|---|---|
| 이더넷 프레임 | packages/ethernet.c | 이더넷 II 프레임 송수신, MAC 주소 처리 |
| ARP | packages/arp.c | IP → MAC 주소 해석, ARP 캐시 |
| IP | packages/ip.c | IPv4 패킷 송수신, 체크섬 계산 |
| UDP | packages/udp.c | UDP 데이터그램 처리 |
| BOOTP/DHCP | packages/bootp.c | IP 주소 획득, TFTP 서버/파일명 수신 |
| TFTP | packages/tftp.c | 파일 다운로드, 512바이트 블록 전송 |
boot net 한 줄로 시작되었습니다. RARP로 IP를 받고, TFTP로 커널을 받고, NFS로 루트 파일시스템을 마운트하는 것이 SPARC 서버 관리의 기본이었습니다.
RARP vs BOOTP vs DHCP 비교
Open Firmware가 지원하는 세 가지 IP 주소 획득 프로토콜의 차이점입니다:
| 특성 | RARP | BOOTP | DHCP |
|---|---|---|---|
| 프로토콜 계층 | L2 (이더넷) | L4 (UDP/IP) | L4 (UDP/IP) |
| 요청 방식 | MAC 주소 → IP | MAC + UDP 브로드캐스트 | MAC + UDP 브로드캐스트 |
| 제공 정보 | IP 주소만 | IP, 게이트웨이, 서브넷, 파일명 | BOOTP + DHCP 옵션 (DNS, NTP 등) |
| TFTP 서버 | 별도 설정 필요 | siaddr 필드 | option 66 (TFTP server) |
| 파일명 | hex-IP 규칙 | file 필드 (128B) | option 67 (bootfile) |
| OF 명령 | boot net | boot net:bootp | boot net:dhcp |
| 시대 | 1980s~1990s | 1990s | 현재 표준 |
네트워크 부팅 디바이스 경로 상세
Open Firmware의 네트워크 부팅 디바이스 경로는 다양한 형식을 지원합니다:
\ 기본 네트워크 부팅 경로 형식
\ /bus/nic:protocol,server-ip,filename,client-ip,gateway-ip,subnet-mask,tftp-retries
\ RARP 기본 (전통적 Sun 방식)
ok boot /sbus/le@0,c00000
\ → RARP로 IP 획득, hex-IP 파일명 (예: C0A80164)
\ DHCP 명시
ok boot /pci/ethernet@1:dhcp
\ → DHCP로 IP + TFTP 서버 + 파일명 모두 획득
\ 서버와 파일 수동 지정
ok boot /sbus/le:rarp,192.168.1.1,vmlinux-sparc32
\ → RARP로 IP 획득, 192.168.1.1 TFTP 서버에서 vmlinux-sparc32 다운로드
\ 모든 파라미터 수동 지정 (RARP/DHCP 없이)
ok boot /pci/ethernet@1,1:,192.168.1.1,vmlinux,192.168.1.100,192.168.1.254,255.255.255.0
\ → 고정 IP 192.168.1.100, 게이트웨이 192.168.1.254, 서버 192.168.1.1
\ PowerPC Mac 네트워크 부팅
ok boot enet:192.168.1.1,yaboot
\ → "enet"은 Mac OF의 네트워크 별칭
- IP
192.168.1.100→ 16진수C0A80164 - TFTP 서버에서
/tftpboot/C0A80164파일을 요청 - 서버 측에서는
ln -s vmlinux-sparc32 /tftpboot/C0A80164심볼릭 링크를 생성 - IP가 바뀌면 링크도 갱신해야 하는 불편함 → BOOTP/DHCP로 대체됨
디버깅과 트러블슈팅
OpenBIOS와 Open Firmware 환경의 디버깅은 문제 유형에 따라 접근 방식이 달라집니다. 아래 플로차트는 증상별 디버깅 경로를 보여줍니다:
OpenBIOS 디버깅 명령어
디바이스 트리 탐색
\ 전체 디바이스 트리 출력
ok show-devs
\ 특정 노드로 이동하여 프로퍼티 확인
ok dev /chosen .properties
ok dev / ls \ 루트 자식 노드 목록
\ 디바이스 별칭 확인
ok devalias \ 모든 별칭
ok devalias disk \ 특정 별칭
\ 프로퍼티 값 상세 확인 (16진수 덤프)
ok dev /sbus/le
ok " local-mac-address" get-my-property
ok if 2drop ." not found" else dump then
\ 노드 순회 (자식→형제)
ok dev /
ok child . \ 첫 자식의 phandle
ok peer . \ 형제 노드의 phandle
메모리 및 레지스터 진단
\ CPU 레지스터 확인
ok .registers \ 전체 레지스터 덤프
ok .fregisters \ 부동소수점 레지스터 (SPARC)
\ 메모리 읽기/쓰기
ok 1000 c@ \ 주소 0x1000의 바이트 읽기
ok 42 1000 c! \ 주소 0x1000에 0x42 쓰기
ok 1000 l@ \ 32비트 읽기
ok 1000 40 dump \ 64바이트 메모리 덤프
\ 메모리 테스트
ok test /memory \ 기본 메모리 테스트
\ 메모리 검색
ok 1000 10000 " OpenBIOS" search \ 문자열 검색
Forth 디버깅
\ 워드 역어셈블
ok see boot \ boot 워드의 내부 코드 확인
ok see go \ go 워드 확인
\ 스택 디버깅
ok .s \ 데이터 스택 출력 (비파괴)
ok showstack \ 자동 스택 표시 모드 ON
ok noshowstack \ 자동 스택 표시 모드 OFF
ok .rs \ 리턴 스택 출력
\ 워드 검색
ok words \ 모든 워드 목록
ok sifting boot \ "boot" 포함한 워드 검색
\ 실행 추적
ok ctrace \ 호출 스택 추적 (예외 발생 시)
\ 디버거 (워드 단위 스테핑)
ok debug boot \ boot 워드에 디버그 모드 설정
ok boot \ → 워드 단위로 실행/스테핑
\ [s]tep, [c]ontinue, [q]uit 선택
QEMU 디버깅 통합
# GDB 스텁으로 OpenBIOS 소스 레벨 디버깅
qemu-system-sparc -machine SS-5 -m 128M -nographic \
-S -gdb tcp::1234
# 별도 터미널에서 GDB 연결
sparc-linux-gnu-gdb obj-sparc32/openbios-builtin.elf
(gdb) target remote :1234
(gdb) break entry # 엔트리 포인트
(gdb) break __do_boot # 부팅 시작점
(gdb) break feval # Forth 평가기
(gdb) continue
# 유용한 GDB 명령
(gdb) info registers # SPARC 레지스터
(gdb) x/20i $pc # 현재 PC 주변 디스어셈블
(gdb) watch *(uint32_t*)0x1234 # 메모리 워치포인트
(gdb) bt # 백트레이스
# QEMU 로그 옵션들
qemu-system-sparc -machine SS-5 -m 128M -nographic \
-d guest_errors,unimp \ # 게스트 오류, 미구현 기능
-D /tmp/qemu-sparc.log
# 상세 로그 (주의: 매우 큰 파일 생성)
qemu-system-sparc -machine SS-5 -m 128M -nographic \
-d in_asm,cpu \ # 실행된 모든 어셈블리 + CPU 상태
-D /tmp/qemu-trace.log
# 특정 주소 범위만 추적
qemu-system-sparc -machine SS-5 -m 128M -nographic \
-d exec,nochain \
-dfilter 0xffd00000-0xffffffff \ # OpenBIOS ROM 영역만
-D /tmp/qemu-rom.log
일반적인 문제와 해결
| 증상 | 원인 | 해결 |
|---|---|---|
| ok 프롬프트 없이 멈춤 | 콘솔 디바이스 초기화 실패 | -nographic 옵션 확인, 시리얼 출력 확인 |
Can't open boot device | boot-device 경로 오류 | show-devs로 올바른 경로 확인, devalias 점검 |
Unhandled Exception 0x29 | Data Access Exception (SPARC) | .registers로 트랩 주소 확인, 잘못된 포인터 추적 |
Bad FCode start byte | FCode 이미지 손상 또는 잘못된 주소 | detok으로 FCode 검증, ROM 매핑 확인 |
| Linux 부팅 중 OF 패닉 | Client Interface 호출 오류 | 커널 prom_init() 로그, QEMU -d 로그 확인 |
out of memory | OF 메모리 풀 소진 | -m 옵션으로 RAM 증가, 불필요한 claim 제거 |
Fast Data Access MMU Miss | TLB 미스, 매핑되지 않은 주소 접근 | .tlb으로 TLB 확인, map으로 매핑 추가 |
| 부팅 후 콘솔 깨짐 | 프레임버퍼/시리얼 설정 불일치 | setenv output-device 확인, reset-all 시도 |
고급 트러블슈팅 시나리오
| 시나리오 | 증상 | 진단 방법 | 해결 |
|---|---|---|---|
| FCode 무한 루프 | PCI 열거 중 QEMU 무응답 | QEMU -d exec로 PC 추적, Ctrl+A, C로 모니터 진입 후 info registers | FCode 소스에서 begin...until 종료 조건 확인, detok으로 바이트코드 검증 |
| DT 프로퍼티 누락 | Linux of_get_property() 반환 NULL | OpenBIOS 프롬프트에서 해당 노드 .properties 확인 | FCode/Forth 드라이버에서 property 워드 호출 누락 확인, 인코딩 형식 검증 |
| claim 실패 | prom_claim()이 -1 반환 | dev /memory .properties에서 available 확인, 가용 연속 영역 부족 | -m 옵션으로 RAM 증가, 또는 alignment 값 변경 |
| MMU 매핑 충돌 | Fast Data Access MMU Miss 반복 | .tlb로 TLB 엔트리 확인, translations 프로퍼티 검사 | 충돌하는 매핑 unmap 후 재매핑, context 레지스터 값 확인 |
| 네트워크 부팅 타임아웃 | Timeout waiting for ARP/RARP packet | QEMU -netdev 설정 확인, tcpdump로 호스트 측 패킷 캡처 | -netdev user,tftp=... 경로 확인, DHCP 서버 응답 여부 점검 |
-d 옵션 외에도 환경변수를 활용할 수 있습니다:
QEMU_LOG=guest_errors,unimp— 게스트 오류와 미구현 기능만 로깅QEMU_LOG_FILE=/tmp/qemu-ob.log— 로그 파일 경로 지정-d trace:openbios*— OpenBIOS 관련 QEMU 트레이스 이벤트만 활성화 (QEMU 빌드 시 trace 백엔드 필요)-dfilter 0xffd00000-0xffffffff— OpenBIOS ROM 영역 실행만 추적하여 로그 크기를 대폭 줄일 수 있음
디버깅 시나리오: 새 디바이스 드라이버가 등록되지 않을 때
\ 1. 디바이스 트리에서 노드 존재 확인
ok show-devs
\ → 목록에 내 디바이스가 없다면 new-device/finish-device 확인
\ 2. 부모 노드로 이동하여 자식 확인
ok dev /pci ls
\ 3. FCode가 실행되었는지 확인 (PCI 카드)
ok dev /pci/my-card
ok .properties
\ → 프로퍼티가 없으면 FCode 실행 실패
\ 4. FCode를 수동으로 실행
ok 0 0 " my-card" begin-package
ok h# FE000000 1 byte-load \ FCode ROM 주소에서 수동 로드
ok end-package
\ 5. 드라이버 메서드 테스트
ok dev /pci/my-card
ok " open" $find if execute then
\ → open이 실패하면 하드웨어 초기화 문제
\ 6. 레지스터 직접 확인
ok dev /pci/my-card
ok " assigned-addresses" get-my-property
ok if ." no BAR" cr else
ok begin dup while
ok decode-int ." BAR: " . cr
ok repeat 2drop
ok then
다른 펌웨어와 비교
| 특성 | OpenBIOS / OF | Legacy BIOS | UEFI | Coreboot | U-Boot |
|---|---|---|---|---|---|
| 주요 플랫폼 | SPARC, PPC | x86 | x86, ARM64 | x86, ARM | ARM, MIPS, PPC |
| 인터프리터 | Forth | 없음 | 없음 (EFI Shell) | 없음 | Shell (Hush) |
| 드라이버 형식 | FCode (이식성) | x86 Option ROM | EFI 드라이버 | payload 의존 | C 드라이버 |
| 디바이스 트리 | OF DT (원조) | 없음 | ACPI | ACPI/DT | FDT |
| OS 인터페이스 | Client Interface | INT 서비스 | Boot/Runtime | payload 의존 | SPL → OS |
| 대화형 디버깅 | Forth 프롬프트 | 제한적 | EFI Shell | 제한적 | U-Boot Shell |
| 오픈소스 | GPLv2 | 대부분 독점 | TianoCore (BSD) | GPLv2 | GPLv2 |
| 현재 상태 | QEMU 유지보수 | 레거시 | 현재 주류 | 활발 | 활발 |
각 펌웨어의 디바이스 발견 메커니즘 비교
| 펌웨어 | 하드웨어 발견 | 드라이버 매칭 | OS 전달 방식 |
|---|---|---|---|
| Open Firmware | 버스 프로빙 + FCode 자동 실행 | compatible 프로퍼티 | Client Interface → DT |
| UEFI | PCI 열거 + EFI 드라이버 바인딩 | Protocol GUID 매칭 | EFI System Table, ACPI |
| Legacy BIOS | 고정 I/O 포트 스캔 | Option ROM (x86 코드) | INT 15h 메모리 맵 |
| U-Boot | 보드 코드 + DT 기반 | DT compatible + driver model | FDT 블롭 전달 |
| Coreboot | 하드웨어 초기화 후 payload 위임 | payload 의존 | coreboot 테이블 |
of_* 접두사의 커널 API는 수천 개에 달하며, "OF" 없이는 현대 임베디드 Linux를 논할 수 없습니다. UEFI/ACPI 기반 시스템과의 차이점은 UEFI 심화와 ACPI 심화 문서에서 확인할 수 있습니다.
펌웨어 아키텍처 계층 비교
5대 펌웨어의 내부 계층 구조를 나란히 비교합니다:
펌웨어별 동등 명령어 비교
같은 작업을 각 펌웨어에서 수행하는 방법 비교입니다:
═══════════════════════════════════════════════════════════
작업: 디바이스 목록 확인
═══════════════════════════════════════════════════════════
OF: ok show-devs
UEFI: Shell> devtree
U-Boot: => dm tree
BIOS: (불가 — POST 중 화면 출력만)
Coreboot: (payload 의존)
═══════════════════════════════════════════════════════════
작업: 메모리 맵 확인
═══════════════════════════════════════════════════════════
OF: ok dev /memory .properties
UEFI: Shell> memmap
U-Boot: => bdinfo
BIOS: INT 15h, AH=E820h
Coreboot: coreboot 테이블 → payload가 파싱
═══════════════════════════════════════════════════════════
작업: 네트워크 부팅
═══════════════════════════════════════════════════════════
OF: ok boot net:dhcp
UEFI: Shell> PXE 부팅 매니저
U-Boot: => dhcp; tftp ${loadaddr} vmlinuz; bootz
BIOS: PXE ROM (Option ROM)
Coreboot: payload에 네트워크 스택 필요
═══════════════════════════════════════════════════════════
작업: 메모리 주소 읽기
═══════════════════════════════════════════════════════════
OF: ok FFD00000 l@ .
UEFI: Shell> mem FFD00000
U-Boot: => md.l FFD00000 1
BIOS: DEBUG.EXE D FFF0:0000
Coreboot: (payload 의존)
of_* API 6,000여 개 호출은 OF가 남긴 가장 광범위한 기술적 유산입니다.
OF vs UEFI 심층 비교
Open Firmware와 UEFI는 둘 다 "플랫폼 독립 펌웨어"를 지향하지만, 접근 방식이 근본적으로 다릅니다:
| 관점 | Open Firmware | UEFI |
|---|---|---|
| 설계 철학 | 대화형 환경 — 엔지니어가 프롬프트에서 하드웨어 직접 탐색 | 자동화 환경 — GUI Setup, 부팅 매니저 |
| 프로그래밍 모델 | Forth 스택 머신 (최소 리소스) | C 기반 (풍부한 라이브러리) |
| 드라이버 이식성 | FCode — 진정한 아키텍처 독립 (SPARC↔PPC↔x86) | EFI 바이트코드 — 사실상 x86/ARM64만 |
| 하드웨어 기술 | Device Tree — 계층적, 프로퍼티 기반 | ACPI — 테이블 기반, AML 바이트코드 |
| 런타임 서비스 | Client Interface (부팅 초기만) | Boot Services + Runtime Services (OS 실행 중에도) |
| 보안 부팅 | 기본 미지원 (표준에 없음) | Secure Boot — 서명 검증 체인 |
| 현재 상태 | 레거시 (QEMU 에뮬레이션용 유지) | 현재 주류 — 모든 신규 x86/ARM64 시스템 |
관련 프로젝트
OpenBIOS 생태계
| 프로젝트 | 라이선스 | 주요 용도 | 설명 |
|---|---|---|---|
| OpenBIOS | GPLv2 | QEMU SPARC/PPC | IEEE 1275 구현, 이 문서의 주제 |
| SLOF | BSD | QEMU pseries (POWER) | IBM의 Slimline OF — RTAS, CAS 확장 포함 |
| SmartFirmware | 상용 | Pegasos PPC | CodeGen의 상용 구현, 소스 일부 공개 |
| Open Firmware Works (OFW) | 독점/공개 | OLPC XO-1 | Mitch Bradley 원저자의 구현 |
| fcode-utils | GPLv2 | FCode 개발 도구 | 토크나이저(toke), 디토크나이저(detok) |
| dtc (Device Tree Compiler) | GPLv2 | DT 개발 도구 | .dts ↔ .dtb 변환, libfdt 라이브러리 포함 (디바이스 트리 심화 참고) |
| libfdt | BSD/GPLv2 | FDT 파싱 | 커널/부트로더에서 DTB를 파싱하는 라이브러리 |
| QEMU | GPLv2 | 시스템 에뮬레이션 | OpenBIOS/SLOF의 주요 소비자, fw_cfg 제공 |
OpenBIOS에서 SLOF까지
QEMU의 pseries (IBM POWER) 머신 타입은 OpenBIOS 대신 SLOF(Slimline Open Firmware)를 사용합니다. SLOF도 IEEE 1275 호환이지만, IBM POWER 아키텍처에 특화된 확장(RTAS, CAS 등)을 포함합니다.
| 특성 | OpenBIOS | SLOF |
|---|---|---|
| 타겟 | SPARC, PPC (Mac/CHRP) | POWER (pseries) |
| Forth 구현 | C 기반 커스텀 엔진 | paflof (POWER Arch Forth) |
| 특수 기능 | sun4m/sun4u, Mac ROM 호환 | RTAS, CAS, VIO (가상 I/O) |
| 유지보수 | QEMU 커뮤니티 | IBM + QEMU 커뮤니티 |
| 네트워크 스택 | obp-tftp (기본) | 고급 (IPv6 일부 지원) |
# QEMU pseries (SLOF 사용)
qemu-system-ppc64 \
-machine pseries \
-m 2G \
-cdrom debian-ppc64el-netinst.iso \
-nographic
# SLOF 프롬프트 (OpenBIOS와 유사)
# 0 > dev / ls
# 0 > show-devs
# 0 > boot cdrom
Open Firmware → Device Tree → 현대 임베디드 Linux
Open Firmware가 남긴 기술적 유산의 전파 경로:
Open Firmware (1988, Sun SPARC)
│
├── IEEE 1275 표준 (1994)
│ ├── Device Tree 개념 정립
│ ├── Client Interface 표준화
│ └── FCode 바이트코드
│
├── Linux 커널 통합 (1996~)
│ ├── arch/sparc/prom/ — SPARC OF 인터페이스
│ ├── arch/powerpc/kernel/prom_init.c — PPC OF 인터페이스
│ └── drivers/of/ — 공용 OF API (of_*)
│
├── Flattened Device Tree (2005, PPC → ARM)
│ ├── dtc (Device Tree Compiler) 개발
│ ├── .dts/.dtb 형식 정의
│ └── libfdt 라이브러리
│
├── ARM Device Tree 의무화 (2012)
│ ├── arch/arm/boot/dts/ — 수천 개 DTS 파일
│ └── ATAG 방식 폐지
│
├── RISC-V 채택 (2017)
│ └── SBI + FDT = RISC-V 부팅 표준
│
└── 현재 (2025+)
├── of_* API: 커널 전체에 6,000+ 호출
├── devicetree.org — DT 스펙 관리
└── Zephyr, U-Boot, Xen 등 광범위 채택
정리
| 주제 | 핵심 내용 |
|---|---|
| IEEE 1275 | 플랫폼 독립적 펌웨어 표준, Forth 언어 기반, 1994년 제정. 핵심 표준 + 버스 바인딩(PCI, ISA, SBus 등) 부속 문서 |
| Forth 인터프리터 | 스택 머신, 워드/딕셔너리 구조, 대화형 콘솔, 인터프리트/컴파일 2모드, immediate 워드로 컴파일 타임 제어 |
| 디바이스 트리 | 하드웨어 토폴로지의 계층적 표현. 패키지(phandle)=정적 정보, 인스턴스(ihandle)=동적 상태. Linux DT(of_* API)의 직접적 기원 |
| FCode | 플랫폼 독립 바이트코드. 확장 카드 ROM에 탑재되어 SPARC/PPC/x86 어디서든 동작. PCI Code Type 0x01 |
| Client Interface | OS→펌웨어 표준 ABI. 단일 진입점 cif_handler, args 구조체 기반. /chosen 노드로 부팅 정보 전달 |
| 부트 체인 | Reset → 플랫폼 초기화 → Forth → 디바이스 열거(FCode 자동 실행) → ELF 로드 → 커널 진입. NVRAM으로 설정 관리 |
| Linux 연계 | prom_init()→DT 순회→FDT 변환→quiesce→커널 시작. of_* API 6,000+ 호출. ARM/RISC-V까지 확산 |
| QEMU 연동 | fw_cfg로 머신 구성 전달. SPARC32(6개 머신), SPARC64(sun4u), PPC(g3beige/mac99) 지원 |
| 메모리 관리 | claim/release로 OS에 메모리 할당. /memory/available로 가용 영역 기술. map/unmap으로 MMU 매핑 |
| 네트워크 부팅 | RARP/BOOTP/DHCP + TFTP. obp-tftp 패키지. 디스크리스 워크스테이션의 기본 부팅 방식 |