OpenBIOS 심화

IEEE 1275 Open Firmware 표준을 구현한 오픈소스 펌웨어 OpenBIOS의 내부 구조를 분석합니다. Forth 인터프리터, 디바이스 노드/트리, FCode 바이트코드, 부트 로더 체인, QEMU SPARC/PPC 연동, 빌드 및 커스터마이징을 다룹니다.

전제 조건: 부트 프로세스디바이스 트리 문서를 먼저 읽으세요. 펌웨어가 커널 부팅 과정에서 어떤 역할을 하는지, 디바이스 트리가 무엇인지 기본적인 이해가 필요합니다.
일상 비유: OpenBIOS는 컴퓨터의 "통역사"와 같습니다. 하드웨어(외국어)를 OS(한국어 사용자)가 이해할 수 있는 형태로 번역해주는 역할입니다. 특히 Forth 인터프리터는 통역사가 사용하는 "대화 도구"이고, 디바이스 트리는 하드웨어 목록을 정리한 "명부"이며, FCode는 어떤 언어(CPU 아키텍처)에서든 읽을 수 있는 "만국 공통 설명서"입니다.

핵심 요약

  • 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()가 이 인터페이스를 통해 부팅 정보를 획득

단계별 이해

  1. 전원 인가 → Forth 인터프리터 시작
    시스템에 전원이 들어오면 OpenBIOS가 로드되고, Forth 인터프리터가 초기화됩니다. ok 프롬프트가 표시되면 사용자가 직접 명령을 입력하거나, 자동 부팅 스크립트가 실행됩니다.
  2. 하드웨어 열거 → 디바이스 트리 구축
    버스를 스캔하여 연결된 장치를 발견하고, 각 장치의 FCode를 실행하여 디바이스 트리 노드를 생성합니다. 트리 완성 후 show-devs로 전체 하드웨어 구성을 확인할 수 있습니다.
  3. OS 로드 → Client Interface로 정보 전달
    boot 명령으로 커널을 메모리에 로드합니다. 커널은 Client Interface를 통해 디바이스 트리를 순회하며 메모리 맵, 콘솔 장치 등 부팅에 필요한 정보를 가져갑니다. 이후 펌웨어 제어권을 넘기고 OS가 시작됩니다.

개요

OpenBIOSIEEE 1275-1994 (Open Firmware) 표준의 오픈소스 구현체입니다. Sun SPARC 워크스테이션, Apple PowerPC Macintosh, IBM CHRP 시스템 등에서 사용되었던 Open Firmware를 GPLv2 라이선스로 재구현한 프로젝트로, 현재는 QEMU에서 SPARC(32/64비트)와 PowerPC 에뮬레이션의 기본 펌웨어로 사용됩니다.

Open Firmware vs BIOS/UEFI: x86 세계의 BIOS/UEFI와 달리, Open Firmware는 Forth 언어 기반 인터프리터플랫폼 독립적인 디바이스 트리를 핵심으로 합니다. Linux 커널의 Device Tree(of_* API)는 바로 이 "OF"(Open Firmware)에서 유래했습니다.

Open Firmware의 설계 철학

Open Firmware는 세 가지 핵심 철학 위에 설계되었습니다:

OpenBIOS의 주요 특징

특징설명
IEEE 1275 준수Open Firmware 표준 인터페이스를 구현하여 OS에 독립적인 부팅 환경 제공
Forth 인터프리터대화형 Forth 환경으로 하드웨어 탐색, 디버깅, 부팅 스크립트 실행
FCode 지원플랫폼 독립적인 바이트코드로 드라이버 이식성 확보
디바이스 트리하드웨어 토폴로지를 트리 구조로 표현, Linux DT의 직접적 기원
Client InterfaceOS가 펌웨어 서비스를 호출하는 표준 ABI
QEMU 통합SPARC32/64, PowerPC(Mac/CHRP) 에뮬레이션의 기본 펌웨어

Open Firmware 생태계 포지셔닝

Open Firmware는 다양한 펌웨어 생태계 속에서 독특한 위치를 차지합니다. 아래 다이어그램은 주요 펌웨어들의 시대적·아키텍처적 관계를 보여줍니다:

펌웨어 생태계 포지셔닝 맵 1980s 1990s 2000s 2010s~현재 Legacy BIOS x86 전용 | INT 서비스 | Option ROM (x86 코드) → 레거시 UEFI / EFI x86/ARM64 | ACPI | EFI 드라이버 | Secure Boot Open Firmware (IEEE 1275) SPARC/PPC | Forth | FCode | Device Tree → Linux of_* API Coreboot x86/ARM | 최소 초기화 → payload (UEFI, SeaBIOS, Linux) U-Boot (Das U-Boot) ARM/MIPS/PPC | FDT (OF 유산) | SPL → TPL → 커널 OpenBIOS OF의 오픈소스 구현 (QEMU) DT 영향 구현 FDT 채택 대체 실선: 직접 관계 | 점선: 기술적 영향 | 굵은 테두리: 이 문서의 주제

Open Firmware 구현체 상세 비교

Open Firmware 표준을 구현한 주요 구현체들의 기술적 차이점입니다:

구현체개발Forth 엔진타겟 HWFCode특수 기능현재 상태
Sun OBPSun Microsystems네이티브 (원조)SPARC 전 세대완전 지원POST 진단, watchdog, 보안 모드Oracle 인수, 단종
Apple BootROMApple Computer커스텀PPC Mac (G3~G5)완전 지원HFS+ 부팅, BootX, Mac 파티션Intel Mac 전환으로 중단
IBM SLOFIBMpaflof (POWER Forth)POWER 서버완전 지원RTAS, CAS, VIO, LPARQEMU pseries 펌웨어
SmartFirmwareCodeGen독자 엔진PPC (Pegasos)완전 지원USB 부팅, PCI 핫플러그상용, 소스 일부 공개
OFWFirmWorks (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에서 개발한 것이 시초이며, 이후 여러 플랫폼에 채택되었습니다.

타임라인

시기사건
1988Sun Microsystems의 OBP(OpenBoot PROM) 1.x 출시 — SPARC 워크스테이션용
1991OBP 2.x — Forth 기반 디바이스 트리, FCode 도입
1994IEEE 1275-1994 표준 제정
1995Apple이 PowerPC Macintosh에 Open Firmware 채택
1996IBM CHRP(Common Hardware Reference Platform) 사양에 OF 포함
1998IEEE 1275.1 (ISA 바인딩), 1275.2 (PCI 바인딩) 등 부속 표준
2002Stefan Reinauer가 OpenBIOS 프로젝트 시작 (GPLv2)
2004Segher Boessenkool이 QEMU SPARC 포팅 기여
2006Apple이 Intel Mac으로 전환하며 EFI 채택, OF 시대 종료
2010+QEMU의 SPARC/PPC 에뮬레이션 기본 펌웨어로 정착

IEEE 1275 표준 구성

IEEE 1275는 핵심 표준과 다수의 버스 바인딩(Bus Binding) 부속 문서로 구성됩니다:

표준 번호이름주요 내용
IEEE 1275-1994Core StandardForth 인터프리터, 디바이스 트리, Client Interface, FCode 사양 (핵심 문서)
IEEE 1275.1ISA Bus BindingISA 버스 디바이스의 reg, interrupts 프로퍼티 인코딩 규칙
IEEE 1275.2PCI Bus BindingPCI Configuration Space, BAR, 버스 번호 인코딩, PCI FCode 실행 규칙
IEEE 1275.3VME Bus BindingVMEbus 디바이스 노드 프로퍼티, 주소 공간 인코딩
IEEE 1275.4FutureBus+ BindingFutureBus+ 어드레스 맵
IEEE 1275.664-bit Extension64비트 주소 공간, 64비트 셀 크기 확장 (UltraSPARC 등)
(비공식)SBus BindingSun SBus 슬롯 주소, DMA 프로퍼티 (Sun 자체 정의)
(비공식)USB BindingUSB 디바이스 트리 표현 (Apple이 Mac OF에 사용)
바인딩(Binding)의 역할: 각 바인딩 문서는 해당 버스에서 reg, ranges, interrupts 등의 프로퍼티를 어떻게 인코딩하는지 정의합니다. 예를 들어 PCI Binding에서는 reg의 phys.hi 셀에 버스 번호, 디바이스 번호, 기능 번호, BAR 인덱스가 비트 필드로 인코딩됩니다. 이 규칙은 Linux 커널의 of_pci_range_parser()에 그대로 반영되어 있습니다.

Open Firmware 진화 타임라인

Open Firmware 기술이 현대 Linux Device Tree까지 진화한 30년의 여정을 시각화합니다:

Open Firmware → Device Tree 진화 타임라인 1988 Sun OBP 1.x SPARC 워크스테이션 최초의 OF 개념 1994 IEEE 1275 표준 제정 Apple PPC 채택 2002 OpenBIOS 오픈소스 구현 QEMU 통합 시작 2005 FDT 탄생 PPC Linux에서 DTB 형식 도입 2012 ARM DT 의무화 수천 .dts 파일 ATAG 폐지 현재 DT 유비쿼터스 ARM, RISC-V, MIPS of_* 6,000+ 호출 1995: Apple PPC Mac 1996: IBM CHRP 2004: QEMU SPARC 포팅 2006: Apple → EFI 전환 2017: RISC-V FDT 채택 2020+: Zephyr, Xen 채택

Open Firmware를 채택한 주요 플랫폼

플랫폼별 Open Firmware 구현 상세

플랫폼구현체프롬프트버스부트 미디어특이점
Sun SPARCstationOBP 2.x/3.xokSBus, SCSI디스크, net, CDPOST 진단 모드, watchdog
Sun Ultra (sun4u)OBP 3.x/4.xokUPA, PCI, SCSI디스크, net, CD64비트 확장, UPA 그래픽
Apple OldWorld MacToolbox ROM + OF0 >PCI, NuBus디스크, CDMac OS ROM 의존성
Apple NewWorld MacBootROM진입 어려움PCI, AGP, USBHFS+, CD, netApple 파티션 맵, BootX
IBM pSeries (POWER)SLOF / 독점 OF0 >PCI/PCI-X/PCIe디스크, SAN, netRTAS, CAS 프로토콜
QEMU SPARC/PPCOpenBIOSok에뮬레이트됨가상 디스크/netfw_cfg 인터페이스
OldWorld vs NewWorld Mac: Apple은 1998년 iMac G3부터 "NewWorld" ROM 아키텍처를 도입했습니다. OldWorld Mac은 Toolbox ROM에 크게 의존하여 OF 프롬프트 접근이 쉬웠지만, NewWorld Mac은 Cmd+Opt+O+F 키 조합으로 진입해야 했습니다. NewWorld에서는 HFS+ 파티션의 System/Library/CoreServices/BootX 파일이 2차 부트 로더 역할을 했습니다.

OpenBIOS 아키텍처

OpenBIOS는 크게 플랫폼 독립 계층플랫폼 종속 계층으로 나뉩니다. Forth 인터프리터가 중심에 위치하며, 디바이스 트리를 통해 하드웨어를 추상화합니다.

OpenBIOS 아키텍처 계층 구조 운영체제 (Linux / Solaris / NetBSD) Client Interface 호출 → prom_init(), of_* API Client Interface (IEEE 1275 §6) OpenBIOS 코어 (플랫폼 독립) Forth 인터프리터 워드 딕셔너리 스택 머신 FCode 토크나이저 대화형 콘솔 디바이스 트리 노드 계층 구조 프로퍼티 관리 패키지 메서드 인스턴스 핸들 부트 서비스 boot-device 탐색 ELF/a.out 로더 디바이스 별칭 NVRAM 설정 플랫폼 종속 계층 SPARC32 (sun4m) SPARC64 (sun4u) PowerPC (Mac) PowerPC (CHRP) 하드웨어 / QEMU 에뮬레이션

소스 트리 구조

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 가상 머신은 두 개의 스택을 사용합니다:

Forth 스택 머신 실행 흐름: 3 4 + 5 * . → 35 출력 입력 토큰 스트림 3 4 + 5 * . Step 1: 3 3 push Step 2: 4 4 3 push Step 3: + 7 3+4=7 Step 4: 5 5 7 push Step 5: * 35 7*5=35 Step 6: . (빈 스택) 35 출력 스택 효과 표기법 (Stack Effect Notation) Forth에서는 워드의 입출력을 ( 입력 -- 출력 ) 형식으로 문서화합니다 + ( n1 n2 -- n3 ) \ n3 = n1 + n2 dup ( x -- x x ) \ 스택 top 복제 swap ( x1 x2 -- x2 x1 ) \ 상위 2개 교환 drop ( x -- ) \ 스택 top 제거

스택 조작 워드 레퍼런스

워드스택 효과설명
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 딕셔너리 엔트리 구조 (링크드 리스트) 워드 "square" Link Field Flags Name: "square" Code Field dup * ; 워드 "abs" Link Field Flags Name: "abs" Code Field dup 0< if negate then ; 프리미티브 "+" Link Field Flags Name: "+" C 함수 ptr → C primitive (기계어) 딕셔너리 검색: 인터프리터가 토큰을 읽으면 딕셔너리를 최신 엔트리부터 역방향으로 탐색합니다. 숫자이면 스택에 push, 워드이면 해당 Code Field를 실행합니다. 컴파일 모드에서는 실행 대신 현재 워드에 컴파일합니다.

제어 흐름 구조

Forth의 제어 흐름은 컴파일 타임에 분기 주소가 결정되는 구조적 방식입니다:

구조문법설명
조건... if ... thenflag가 true이면 if~then 사이 실행
조건 분기... if ... else ... thenflag에 따라 분기
카운트 루프limit start do ... loopstart부터 limit-1까지 반복
+LOOPlimit start do ... n +loop증분값 n으로 루프
조건 루프begin ... flag untilflag가 true가 될 때까지 반복
무한 루프begin ... flag while ... repeatflag가 true인 동안 반복
CASEcase n1 of ... endof n2 of ... endof endcase다중 분기
재귀recurse현재 정의 중인 워드를 재귀 호출
종료exit현재 워드에서 즉시 반환
루프 탈출leavedo...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에 대해서는 디바이스 트리 심화 문서를 참고하세요.

SPARC sun4m 디바이스 트리 예시 / (root) compatible="sun4m" model="SPARCstation 5" memory device_type="memory" reg=<0 0x8000000> obio (on-board I/O) #address-cells=<2> sbus device_type="hierarchical" counter-timer reg=<...> interrupts=<...> interrupt-controller device_type="interrupt- controller" zs (serial) device_type="serial" reg=<...> le (Lance Ethernet) device_type="network" local-mac-address=... esp (SCSI) device_type="scsi" clock-frequency=... sd (SCSI disk) device_type="block" 루트 노드 버스 노드 디바이스 노드

패키지(Package)와 인스턴스(Instance)

Open Firmware에서 가장 중요한 개념 구분은 패키지인스턴스입니다:

Package (phandle) vs Instance (ihandle) Package (phandle) — 정적 정보 프로퍼티: name, device_type, reg, compatible, ... 메서드: open, close, read, write, seek, ... 자식 노드 링크, 형제 노드 링크 클래스에 비유 디바이스 트리에 항상 존재 하드웨어 1개에 대해 1개의 패키지 finddevice("/sbus/esp") → phandle Instance (ihandle) — 동적 상태 인스턴스 변수: 파일 위치, 버퍼, 상태 플래그 my-self: 현재 인스턴스 핸들 참조 상위 인스턴스 체인 (디바이스 경로) 객체에 비유 open 시 생성, close 시 소멸 같은 디바이스에 여러 인스턴스 가능 open("/sbus/esp/sd@3,0:a") → ihandle open close
개념핸들 타입생명주기비유Linux 커널 대응
패키지 (Package)phandle부팅 시 생성, 영구 존재클래스 정의struct device_node
인스턴스 (Instance)ihandleopen 시 생성, 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-address6 바이트네트워크 디바이스의 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의 핵심 가치: x86 세계에서는 확장 카드마다 BIOS ROM에 x86 코드를 넣어야 했지만, Open Firmware 세계에서는 FCode 하나로 SPARC, PowerPC, x86 등 모든 아키텍처에서 동작합니다.

FCode 구조

필드크기설명
start-code1 byte0xF0 (FCode start1) 또는 0xF1 (start2)
format1 byteFCode 형식 버전 (보통 0x08)
checksum2 bytes본문 체크섬
length4 bytesFCode 이미지 전체 길이
body가변토큰화된 Forth 워드 시퀀스
end-code1 byte0x00 (end0)
FCode ROM 바이너리 레이아웃 (PCI Expansion ROM) PCI Expansion ROM Header (28 bytes) Signature: 0x55AA | PCI Data Offset | Code Type: 0x01 (OF) Start 0xF1 Format 0x08 Checksum 2 bytes Length 4 bytes (BE) FCode Body (토큰 스트림) b(lit), b(:), b(;), b(if), ... End 0x00 FCode 토큰 구분: 1-byte 토큰 (0x00~0x0F): end0, b(lit), b('), b("), bbranch, b(?branch), b(loop), ... 1-byte 토큰 (0x10~0xFF): 표준 Forth/OF 워드 — +, -, dup, swap, property, new-device, ... 2-byte 토큰 (0x01xx~0x07xx): 사용자 정의 워드, vendor-specific, 확장 토큰

주요 FCode 토큰 번호

토큰FCode #설명
end00x00FCode 이미지 종료
b(lit)0x10다음 4바이트를 리터럴 정수로 push
b(')0x11다음 토큰의 실행 토큰(xt)을 push
b(")0x12인라인 문자열 리터럴
bbranch0x13무조건 분기 (오프셋 따름)
b(?branch)0x14조건부 분기 (flag에 따라)
b(loop)0x15loop 종료 체크
b(+loop)0x16+loop 종료 체크
b(do)0x17do 루프 시작
new-token0xB5새 토큰 번호에 워드 할당
named-token0xB6이름 있는 새 토큰
external-token0xCA외부에서 접근 가능한 토큰
property0x110프로퍼티 설정 (2바이트 토큰)
new-device0x11F새 디바이스 노드 생성
finish-device0x127디바이스 노드 완료

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-device0x11F( -- )현재 노드 아래 새 자식 노드 생성, 컨텍스트 이동
finish-device0x127( -- )현재 노드 확정, 부모 노드로 컨텍스트 복귀
device-name0x201( str len -- )현재 노드의 name 프로퍼티 설정
device-type0x11A( str len -- )현재 노드의 device_type 프로퍼티 설정
property0x110( val-addr val-len name-str name-len -- )임의의 프로퍼티 생성
encode-int0x111( n -- addr len )정수를 빅엔디안 4바이트로 인코딩
encode-string0x114( str len -- addr len )문자열을 null-terminated로 인코딩
encode+0x112( addr1 len1 addr2 len2 -- addr3 len3 )두 인코딩 값 연결
model0x119( str len -- )model 프로퍼티 설정
my-address0x102( -- phys.lo ... )현재 디바이스의 버스 주소 (부모 bus-binding에 따라 셀 수 다름)
my-space0x103( -- 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 종료
FCode ROM의 실전 사례: Sun의 SBus/PCI 확장 카드(예: hme 이더넷, cgsix 그래픽)는 모두 FCode ROM을 탑재했습니다. 사용자는 ok show-devs로 카드가 정상 등록되었는지 확인하고, ok test /sbus/SUNW,hme로 카드 자체 진단을 실행할 수 있었습니다.

부트 로더 체인

OpenBIOS의 부팅 과정은 하드웨어 초기화부터 OS 커널 진입까지 여러 단계를 거칩니다.

OpenBIOS 부트 체인 (SPARC) 1. CPU Reset 트랩 테이블 설정 MMU 초기화 2. 플랫폼 초기화 메모리 감지 콘솔 초기화 3. Forth 엔진 시작 딕셔너리 로드 기본 워드 등록 4. 디바이스 열거 버스 스캔 FCode 실행 5. 디바이스 트리 구축 노드/프로퍼티 생성 별칭 등록 6. 배너 / ok 프롬프트 auto-boot? 확인 NVRAM 설정 읽기 7. 부트 디바이스 선택 boot-device 경로 해석 파티션 테이블 파싱 8. 커널 로드 ELF/a.out 파싱 load-base에 적재 9. 커널 진입 go (엔트리 포인트 호출) Client Interface 제공 10. OS 실행 prom_init() 호출 DT → FDT 변환 auto-boot?=true이면 6→7 자동 진행, false이면 ok 프롬프트에서 대기

부트 단계별 상세

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
FCode 자동 실행: PCI 버스 열거 시, 각 PCI 디바이스의 Expansion ROM에서 Code Type이 0x01(Open Firmware)인 이미지를 찾으면 자동으로 FCode를 실행합니다. FCode 내부에서 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단계: 커널 로드와 진입

부트 디바이스가 결정되면 해당 디바이스의 openread 체인을 통해 커널을 로드합니다:

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-devicedisk부트 디바이스 별칭 또는 경로
boot-file(빈 문자열)커널 파일 경로 또는 부트 인자
boot-commandboot자동 부팅 시 실행할 명령
diag-switch?false진단 모드 활성화
input-devicekeyboard콘솔 입력 디바이스
output-devicescreen콘솔 출력 디바이스
\ 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 위치0xFFD000000xFFF000000xFFF00000
CPU 모드32비트 슈퍼바이저64비트 가상 주소32비트 실주소
디바이스 버스SBus, OBIOPCI, UPAPCI, AGP
파티션Sun VTOC (sun-parts)Sun VTOC, EFI GPTApple 파티션 맵 (mac-parts)
파일시스템UFS (SunOS/Solaris)UFS, ext2HFS+, 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 파일 타입: Apple의 Open Firmware는 HFS+ 파일의 "타입 코드"를 인식합니다. tbxi(Toolbox Image)는 부팅 가능한 이미지를 의미하며, "Blessed Folder"(축복받은 폴더)에 있는 tbxi 파일이 자동으로 부트 로더로 선택됩니다. 이 메커니즘은 Mac OS 특유의 "System Folder" 개념과 밀접하게 연관됩니다.

Client Interface

Client Interface는 OS가 부팅 후에도 펌웨어 서비스를 호출할 수 있는 표준 ABI입니다. IEEE 1275 §6에 정의되어 있으며, 단일 진입점(cif_handler)을 통해 다양한 서비스를 제공합니다.

Client Interface 호출 흐름 (OS → 펌웨어) OS (Linux 커널) prom_finddevice("/") args 구조체 구성: service="finddevice" call(cif_handler, &args) phandle = args.result 호출 Client Interface Handler 1. service 문자열 매칭 2. 인자 검증 (nargs/nreturns) 3. 내부 함수 디스패치 4. 결과값 기록 OpenBIOS Forth 코어 디바이스 트리 검색 프로퍼티 읽기/쓰기 메모리 할당/해제 디바이스 I/O 콘솔 입출력 반환

호출 규약

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 서비스

서비스인자설명
finddevicepath경로로 디바이스 노드 핸들(phandle) 검색
getpropphandle, name, buf, len프로퍼티 값 읽기
setpropphandle, name, buf, len프로퍼티 값 쓰기
getproplenphandle, name프로퍼티 값 길이 조회
nextpropphandle, previous, buf다음 프로퍼티 이름 열거
childphandle첫 번째 자식 노드
peerphandle형제 노드
parentphandle부모 노드
opendevice-specifier디바이스 인스턴스 열기
closeihandle디바이스 인스턴스 닫기
readihandle, buf, len디바이스에서 읽기
writeihandle, buf, len디바이스에 쓰기
claimvirt, size, align메모리 할당
releasevirt, 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);
    }
}
Client Interface 재진입성(Reentrancy) 주의: IEEE 1275 표준은 Client Interface가 재진입 불가(non-reentrant)임을 명시합니다. 즉, Client Interface 호출 중에 인터럽트 핸들러나 다른 콜백에서 다시 Client Interface를 호출하면 동작이 정의되지 않습니다. 이 때문에 Linux 커널의 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
mmuMMU 패키지 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에서 직접 유래했습니다:

커널 APIOF 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 생성
DT의 진화: Open Firmware의 디바이스 트리는 원래 SPARC/PPC 전용이었지만, ARM에서 .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() 등이 모두 이 전통을 이어갑니다.
Open Firmware Device Tree → Flattened Device Tree 진화 OF Device Tree (Live) 1988~2005 펌웨어가 런타임에 트리 구축 Client Interface로 순회 SPARC, PowerPC Flattened DT (DTB) 2005~현재 바이너리 블롭(DTB) 형태 .dts 소스 → dtc → .dtb ARM, MIPS, RISC-V, PPC 커널 내부 DT 커널 부팅 시 unflatten_device_tree() struct device_node 트리 of_* API로 접근 핵심 전환점 2005 PowerPC 리눅스가 OF DT를 FDT 바이너리(DTB)로 평탄화하여 커널에 전달하기 시작 2008 ARM에서 ATAG 대신 DTB 채택 시작 → 보드별 하드코딩 제거 운동 2012 ARM Device Tree 의무화 — arch/arm/boot/dts/ 에 수천 개 .dts 파일 추가 2017 RISC-V도 FDT를 공식 하드웨어 기술 방식으로 채택 현재 OF의 of_* API가 커널 전체에 6,000+ 호출 — 'O'pen 'F'irmware의 유산이 모든 아키텍처에 존재

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);
}
quiesce 호출의 중요성: 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/v3sun4m, sun4darch/sparc/prom/tree_32.c디바이스 트리 지원, 풍부한 API
OBP v4 (sun4u)UltraSPARCarch/sparc/prom/p1275.cIEEE 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 심화 문서를 참고하세요.

QEMU ↔ OpenBIOS 상호작용 아키텍처 QEMU (호스트) CPU 에뮬레이션 TCG / KVM SPARC/PPC 명령어 실행 디바이스 에뮬레이션 MMIO 트랩 → C 핸들러 SBus, PCI, IDE, NIC... fw_cfg 인터페이스 QEMU→펌웨어 정보 전달 채널 RAM 크기, CPU 수 머신 ID, 커널 주소 게스트 RAM OpenBIOS 바이너리 로드됨 OpenBIOS ROM 0xFFD00000 (SPARC) QEMU 모니터: info mtree, info qtree, x/fmt addr, gdb stub OpenBIOS (게스트) Forth 인터프리터 fw_cfg에서 머신 정보 읽어 디바이스 트리 구축 arch/sparc32/openbios.c 디바이스 트리 에뮬레이트된 HW에 맞춰 노드/프로퍼티 생성 /obio, /sbus, /pci 등 부트 로직 ELF 로더, 파티션 파싱, 파일시스템 콘솔 ok 프롬프트 Forth REPL Client Interface → OS Linux/Solaris/NetBSD가 DT 탐색, 메모리 확보, 콘솔 출력 MMIO 실행

fw_cfg 인터페이스

QEMU의 fw_cfg는 호스트(QEMU)가 게스트(OpenBIOS)에게 머신 구성 정보를 전달하는 특수 MMIO/PIO 인터페이스입니다. OpenBIOS는 이를 통해 하드코딩 없이 동적으로 머신 구성을 파악합니다:

fw_cfg 키설명OpenBIOS 용도
FW_CFG_RAM_SIZE게스트 RAM 크기/memory 노드 reg 프로퍼티
FW_CFG_NB_CPUSCPU 수/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_ADDRinitrd 주소/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 프로퍼티에 반영 */
fw_cfg의 역할: fw_cfg가 없다면 OpenBIOS는 하드웨어를 직접 프로빙하여 RAM 크기, CPU 수 등을 감지해야 합니다. 물리 하드웨어에서는 실제로 그렇게 동작하지만, QEMU에서는 fw_cfg를 통해 정확하고 빠르게 정보를 전달합니다. 이는 QEMU의 -m, -smp, -kernel 등의 옵션이 펌웨어에 도달하는 경로입니다.

QEMU 머신 타입과 OpenBIOS 지원

아키텍처머신 타입에뮬레이션 대상주요 버스특이사항
SPARC32SS-4SPARCstation 4SBusmicroSPARC-II
SS-5SPARCstation 5SBus가장 많이 테스트됨
SS-10SPARCstation 10SBus, MBusSMP 지원
SS-20SPARCstation 20SBus, MBusSMP 지원
SS-600MPSPARCserver 600MPSBus서버 모델
VoyagerSPARCstation VoyagerSBus노트북 모델
SPARC64sun4uSun Ultra 5/10PCIUltraSPARC-IIi
PowerPCg3beigePower Mac G3 (Beige)PCIOldWorld ROM 호환
mac99PowerMac G4PCI, AGPNewWorld, 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 빌드의 전체 과정을 단계별로 시각화합니다:

OpenBIOS 빌드 파이프라인 1. 소스코드 C 코드 (kernel/) Forth 워드 (forth/) drivers/, packages/ 2. 아키텍처 선택 switch-arch sparc32 | sparc64 | ppc 3. 크로스 컴파일 sparc64-linux-gnu- gcc / powerpc-gnu-gcc .c → .o 오브젝트 4. Forth 임베드 + 링크 Forth .fs → 바이너리 딕셔너리 인라인 ld → ELF 5. 결과물 openbios- builtin.elf QEMU -bios 옵션 QEMU 통합 pc-bios/openbios-sparc32 qemu-system-sparc -bios ... Forth 딕셔너리 빌드 상세 forth/device/*.fs, forth/admin/*.fs → xsltproc으로 XML 변환 → C 배열로 인코딩 → 바이너리에 포함 빌드 시 생성되는 주요 컴포넌트 Forth 엔진 (C) kernel/*.o 디바이스 드라이버 drivers/*.o 부트 패키지 packages/*.o 플랫폼 코드 arch/*/*.o Forth 사전 dict.bin
크로스 컴파일러 주의사항: 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_BOOToff부팅 과정 상세 로그 출력
CONFIG_DEBUG_DICToff딕셔너리 조작 디버그 메시지
CONFIG_DEBUG_OFMEMoffOF 메모리 할당/해제 추적
CONFIG_PCI아키텍처 의존PCI 버스 지원 포함
CONFIG_IDEonIDE/ATA 디바이스 드라이버
CONFIG_LOADER_ELFonELF 바이너리 로더
CONFIG_LOADER_AOUTona.out 바이너리 로더 (SPARC)
CONFIG_LOADER_FCODEonFCode 해석기
CONFIG_FONT_8X16on콘솔 폰트 (프레임버퍼용)

크로스 컴파일 환경 상세

# 각 타겟에 필요한 크로스 컴파일러
# 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 전체, regavailable 프로퍼티로 기술
가상 메모리 (Virtual)/memory 또는 MMU 패키지MMU 페이지 테이블을 통한 매핑 관리
OF 예약 영역OpenBIOS 자체Forth 딕셔너리, 스택, 디바이스 트리 데이터
클라이언트 영역OS (claim/release)OS가 Client Interface로 할당받은 영역
OpenBIOS SPARC32 (sun4m) 메모리 레이아웃 물리 주소 공간 0x00000000 트랩 테이블 / 초기 코드 0x00000000 ~ 0x00003FFF 0x00004000 커널 로드 영역 (load-base) 0x00004000 ~ ... ELF vmlinux가 여기 로드됨 OS 가용 영역 (available) claim/release로 관리 OpenBIOS 데이터 영역 Forth 딕셔너리, 스택 디바이스 트리, 프로퍼티 데이터 ... 프레임버퍼 매핑 (가변) 0xF0000000+ 디바이스 MMIO 영역 SBus, OBIO, 인터럽트 컨트롤러 0xFFD00000 OpenBIOS ROM 0xFFD00000 ~ 0xFFFFFFFF 메모리 관리 연산 claim (할당) OS가 펌웨어에게 메모리 예약 요청 claim ( virt size align -- base ) align≠0이면 펌웨어가 위치 결정 align=0이면 virt 주소 고정 할당 release (해제) 이전에 claim한 영역 반환 release ( virt size -- ) map / unmap MMU 페이지 테이블 매핑 관리 map ( phys virt size mode -- ) unmap ( virt size -- ) available 프로퍼티 /memory와 /virtual-memory 의 available 프로퍼티가 사용 가능 영역을 기술
\ 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의 의미: /memory 노드의 available 프로퍼티는 OS가 자유롭게 사용할 수 있는 물리 메모리 영역을 나열합니다. OpenBIOS 자체가 사용하는 영역(딕셔너리, 스택, 디바이스 트리)은 이 목록에서 제외됩니다. Linux의 prom_init()은 이 프로퍼티를 읽어 memblock에 등록합니다.

가상 ↔ 물리 주소 매핑

Open Firmware는 MMU를 통해 가상 주소와 물리 주소를 매핑합니다. /virtual-memory 노드의 translations 프로퍼티가 현재 활성 매핑을 기술합니다:

Open Firmware MMU — 가상 ↔ 물리 주소 매핑 (SPARC) 가상 주소 공간 0x00000000 커널 텍스트/데이터 load-base OS 힙 (claim 영역) ... 가용 가상 공간 ... 0xF0000000 MMIO 디바이스 매핑 map-in 으로 생성 0xFFD00000 OpenBIOS ROM 1:1 매핑 0xFFE00000 Forth 딕셔너리/스택 OF 내부 사용 MMU 페이지 테이블 TLB 캐시 translations 프로퍼티에 기록 map / unmap 명령으로 조작 물리 주소 공간 0x00000000 RAM (물리 메모리) reg=<0 0x8000000> ... 할당되지 않음 ... 0x10000000 SBus 슬롯 MMIO 물리 디바이스 레지스터 0x71000000 OBIO 디바이스 0xFFD00000 Boot PROM (ROM) 핵심 개념 translations 프로퍼티: 현재 활성된 가상→물리 매핑 목록 (virt, size, phys, mode 4-튜플) 커널 부팅 시: prom_init()가 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                       \ 가상 주소 해제
OF 매핑과 커널 페이지 테이블 전환: Linux 커널이 Open Firmware 시스템에서 부팅될 때, 초기에는 OF가 설정한 MMU 매핑을 그대로 사용합니다. prom_init()이 OF의 translations 프로퍼티를 읽어 커널의 초기 페이지 테이블에 동일한 매핑을 재현한 뒤, OF의 페이지 테이블에서 커널 자체 페이지 테이블로 전환합니다. 이 "매핑 계승" 과정이 실패하면 커널은 즉시 크래시합니다. SPARC64에서는 inherit_prom_mappings() 함수가 이 역할을 담당하며, OF TLB 엔트리를 커널 TSB(Translation Storage Buffer)에 복사합니다. quiesce 호출 이후에야 OF의 페이지 테이블 메모리를 해제할 수 있습니다.

네트워크 부팅

Open Firmware는 네트워크 부팅을 기본 기능으로 지원합니다. RARP/BOOTP/DHCP로 IP 주소를 획득하고 TFTP로 커널을 다운로드하는 과정이 펌웨어 레벨에서 수행됩니다.

네트워크 부팅 시퀀스 다이어그램 OpenBIOS DHCP 서버 TFTP 서버 NIC open → MAC 취득 DHCPDISCOVER (브로드캐스트) DHCPOFFER (IP, TFTP서버, 파일명) DHCPREQUEST DHCPACK (확정) IP: 192.168.1.100 TFTP RRQ: vmlinux-sparc DATA block #1 (512B) ACK #1 DATA block #2...#N ELF 파싱 → 커널 진입

네트워크 부팅 흐름

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 주소 처리
ARPpackages/arp.cIP → MAC 주소 해석, ARP 캐시
IPpackages/ip.cIPv4 패킷 송수신, 체크섬 계산
UDPpackages/udp.cUDP 데이터그램 처리
BOOTP/DHCPpackages/bootp.cIP 주소 획득, TFTP 서버/파일명 수신
TFTPpackages/tftp.c파일 다운로드, 512바이트 블록 전송
디스크리스 워크스테이션: Sun SPARC 시대에 네트워크 부팅은 일상적이었습니다. 데이터센터의 디스크리스 클라이언트가 NIS/NFS 서버에서 OS를 부팅했으며, 이 과정 전체가 Open Firmware의 boot net 한 줄로 시작되었습니다. RARP로 IP를 받고, TFTP로 커널을 받고, NFS로 루트 파일시스템을 마운트하는 것이 SPARC 서버 관리의 기본이었습니다.

RARP vs BOOTP vs DHCP 비교

Open Firmware가 지원하는 세 가지 IP 주소 획득 프로토콜의 차이점입니다:

특성RARPBOOTPDHCP
프로토콜 계층L2 (이더넷)L4 (UDP/IP)L4 (UDP/IP)
요청 방식MAC 주소 → IPMAC + UDP 브로드캐스트MAC + UDP 브로드캐스트
제공 정보IP 주소만IP, 게이트웨이, 서브넷, 파일명BOOTP + DHCP 옵션 (DNS, NTP 등)
TFTP 서버별도 설정 필요siaddr 필드option 66 (TFTP server)
파일명hex-IP 규칙file 필드 (128B)option 67 (bootfile)
OF 명령boot netboot net:bootpboot net:dhcp
시대1980s~1990s1990s현재 표준

네트워크 부팅 디바이스 경로 상세

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의 네트워크 별칭
Hex-IP 파일명 규칙 (RARP 부팅): RARP 프로토콜은 TFTP 파일명을 전달하지 않습니다. 이 때문에 Sun 워크스테이션은 클라이언트 IP 주소를 16진수로 변환하여 TFTP 파일명으로 사용하는 규칙을 정했습니다. 예를 들어:
  • IP 192.168.1.100 → 16진수 C0A80164
  • TFTP 서버에서 /tftpboot/C0A80164 파일을 요청
  • 서버 측에서는 ln -s vmlinux-sparc32 /tftpboot/C0A80164 심볼릭 링크를 생성
  • IP가 바뀌면 링크도 갱신해야 하는 불편함 → BOOTP/DHCP로 대체됨

디버깅과 트러블슈팅

OpenBIOS와 Open Firmware 환경의 디버깅은 문제 유형에 따라 접근 방식이 달라집니다. 아래 플로차트는 증상별 디버깅 경로를 보여줍니다:

OpenBIOS 디버그 워크플로 — 문제 유형별 분기 문제 발생! ok 프롬프트 도달? No QEMU 디버깅 -d guest_errors GDB 스텁 연결 -S -gdb tcp::1234 QEMU 로그 분석 -d in_asm,cpu Yes 문제 유형? 디바이스 show-devs .properties FCode 수동 실행 byte-load 부팅 devalias probe-scsi-all 파티션 확인 dir disk:a 메모리/Forth .s / .registers dump / see / debug MMU 매핑 확인 .tlb / translations 해결 도구 요약 OF 프롬프트: show-devs, .properties, devalias, .s, dump, see, debug QEMU: -d guest_errors, -gdb tcp::1234, info mtree, info registers

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 deviceboot-device 경로 오류show-devs로 올바른 경로 확인, devalias 점검
Unhandled Exception 0x29Data Access Exception (SPARC).registers로 트랩 주소 확인, 잘못된 포인터 추적
Bad FCode start byteFCode 이미지 손상 또는 잘못된 주소detok으로 FCode 검증, ROM 매핑 확인
Linux 부팅 중 OF 패닉Client Interface 호출 오류커널 prom_init() 로그, QEMU -d 로그 확인
out of memoryOF 메모리 풀 소진-m 옵션으로 RAM 증가, 불필요한 claim 제거
Fast Data Access MMU MissTLB 미스, 매핑되지 않은 주소 접근.tlb으로 TLB 확인, map으로 매핑 추가
부팅 후 콘솔 깨짐프레임버퍼/시리얼 설정 불일치setenv output-device 확인, reset-all 시도

고급 트러블슈팅 시나리오

시나리오증상진단 방법해결
FCode 무한 루프PCI 열거 중 QEMU 무응답QEMU -d exec로 PC 추적, Ctrl+A, C로 모니터 진입 후 info registersFCode 소스에서 begin...until 종료 조건 확인, detok으로 바이트코드 검증
DT 프로퍼티 누락Linux of_get_property() 반환 NULLOpenBIOS 프롬프트에서 해당 노드 .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 packetQEMU -netdev 설정 확인, tcpdump로 호스트 측 패킷 캡처-netdev user,tftp=... 경로 확인, DHCP 서버 응답 여부 점검
QEMU 디버그 로그 환경변수: QEMU의 디버그 출력을 세밀하게 제어하려면 -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 / OFLegacy BIOSUEFICorebootU-Boot
주요 플랫폼SPARC, PPCx86x86, ARM64x86, ARMARM, MIPS, PPC
인터프리터Forth없음없음 (EFI Shell)없음Shell (Hush)
드라이버 형식FCode (이식성)x86 Option ROMEFI 드라이버payload 의존C 드라이버
디바이스 트리OF DT (원조)없음ACPIACPI/DTFDT
OS 인터페이스Client InterfaceINT 서비스Boot/Runtimepayload 의존SPL → OS
대화형 디버깅Forth 프롬프트제한적EFI Shell제한적U-Boot Shell
오픈소스GPLv2대부분 독점TianoCore (BSD)GPLv2GPLv2
현재 상태QEMU 유지보수레거시현재 주류활발활발

각 펌웨어의 디바이스 발견 메커니즘 비교

펌웨어하드웨어 발견드라이버 매칭OS 전달 방식
Open Firmware버스 프로빙 + FCode 자동 실행compatible 프로퍼티Client Interface → DT
UEFIPCI 열거 + EFI 드라이버 바인딩Protocol GUID 매칭EFI System Table, ACPI
Legacy BIOS고정 I/O 포트 스캔Option ROM (x86 코드)INT 15h 메모리 맵
U-Boot보드 코드 + DT 기반DT compatible + driver modelFDT 블롭 전달
Coreboot하드웨어 초기화 후 payload 위임payload 의존coreboot 테이블
Open Firmware의 유산: OF의 디바이스 트리 개념은 ARM 생태계의 Device Tree로 이어졌고, FDT(Flattened Device Tree)는 현재 ARM, RISC-V, MIPS 등에서 사실상 표준입니다. of_* 접두사의 커널 API는 수천 개에 달하며, "OF" 없이는 현대 임베디드 Linux를 논할 수 없습니다. UEFI/ACPI 기반 시스템과의 차이점은 UEFI 심화ACPI 심화 문서에서 확인할 수 있습니다.

펌웨어 아키텍처 계층 비교

5대 펌웨어의 내부 계층 구조를 나란히 비교합니다:

펌웨어 아키텍처 계층 비교 Open Firmware Legacy BIOS UEFI Coreboot U-Boot OS 인터페이스 런타임 드라이버 HW기술 초기화 Client Interface cif_handler() Forth 인터프리터 ok 프롬프트 FCode 바이트코드 아키텍처 독립 Device Tree 노드/프로퍼티/메서드 compatible 매칭 플랫폼 종속 초기화 arch/sparc32/ INT 서비스 INT 10h/13h/15h 실모드 16비트 640K 제한 Option ROM x86 기계어 없음 하드코딩 MP 테이블 POST + 셋업 어셈블리 코드 Boot/Runtime Services SystemTable DXE / BDS EFI Shell EFI 드라이버 PE/COFF 바이너리 ACPI 테이블 AML 바이트코드 DSDT/SSDT SEC → PEI → DXE TianoCore EDK2 payload 의존 SeaBIOS/UEFI/Linux 최소 C 환경 ramstage 네이티브 C 드라이버 chipset 특화 coreboot 테이블 + ACPI (옵션) + FDT (옵션) bootblock → romstage → ramstage → payload FDT 전달 bootz/booti Hush Shell env, script Driver Model (DM) C 드라이버 Flattened DT .dts/.dtb OF 유산 계승 TPL → SPL → U-Boot proper 핵심 차이점 요약 OF: 대화형 Forth + 이식 가능 FCode — 디버깅 최강, 하지만 Forth 진입장벽 UEFI: C 기반 + Secure Boot — 현대 표준, 하지만 복잡한 스펙 (수천 페이지) U-Boot: OF의 DT 유산 + 실용적 Shell — 임베디드 리눅스의 사실상 표준 부트로더

펌웨어별 동등 명령어 비교

같은 작업을 각 펌웨어에서 수행하는 방법 비교입니다:

═══════════════════════════════════════════════════════════
작업: 디바이스 목록 확인
═══════════════════════════════════════════════════════════
  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의 Device Tree 유산이 현대 펌웨어에 미친 영향: Open Firmware가 직접 사용되는 시스템은 이제 QEMU 에뮬레이션뿐이지만, OF의 핵심 혁신인 Device Tree는 더 널리 퍼졌습니다. U-Boot는 OF의 DT를 FDT 형태로 채택했고, Coreboot도 ARM 타겟에서 FDT를 지원합니다. 심지어 UEFI 세계에서도 ARM 서버용 SBBR(Server Base Boot Requirements) 사양은 ACPI와 함께 DT를 부팅 대안으로 인정합니다. Linux 커널의 of_* API 6,000여 개 호출은 OF가 남긴 가장 광범위한 기술적 유산입니다.

OF vs UEFI 심층 비교

Open Firmware와 UEFI는 둘 다 "플랫폼 독립 펌웨어"를 지향하지만, 접근 방식이 근본적으로 다릅니다:

관점Open FirmwareUEFI
설계 철학대화형 환경 — 엔지니어가 프롬프트에서 하드웨어 직접 탐색자동화 환경 — 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 에코시스템 관계도 OpenBIOS IEEE 1275 구현 GPLv2 QEMU SPARC/PPC 에뮬레이션 호스트 fw_cfg 제공 / 기본 펌웨어 SLOF (IBM) POWER 서버 OF 동일 표준 (IEEE 1275) Linux 커널 of_* API, prom_init() drivers/of/, arch/sparc/prom/ CI 소비자 fcode-utils toke / detok 토크나이저 FCode 빌드 도구 dtc / libfdt DT 컴파일러 / FDT 라이브러리 DT 유산 계승 U-Boot FDT 기반 임베디드 부트로더 FDT 채택 Sun OBP / Apple BootROM 원조 OF 구현체 (독점) 호환 타겟 OFW / SmartFirmware 다른 OF 구현체 대안 구현 직접 관계 기술적 영향/유산
프로젝트라이선스주요 용도설명
OpenBIOSGPLv2QEMU SPARC/PPCIEEE 1275 구현, 이 문서의 주제
SLOFBSDQEMU pseries (POWER)IBM의 Slimline OF — RTAS, CAS 확장 포함
SmartFirmware상용Pegasos PPCCodeGen의 상용 구현, 소스 일부 공개
Open Firmware Works (OFW)독점/공개OLPC XO-1Mitch Bradley 원저자의 구현
fcode-utilsGPLv2FCode 개발 도구토크나이저(toke), 디토크나이저(detok)
dtc (Device Tree Compiler)GPLv2DT 개발 도구.dts ↔ .dtb 변환, libfdt 라이브러리 포함 (디바이스 트리 심화 참고)
libfdtBSD/GPLv2FDT 파싱커널/부트로더에서 DTB를 파싱하는 라이브러리
QEMUGPLv2시스템 에뮬레이션OpenBIOS/SLOF의 주요 소비자, fw_cfg 제공

OpenBIOS에서 SLOF까지

QEMU의 pseries (IBM POWER) 머신 타입은 OpenBIOS 대신 SLOF(Slimline Open Firmware)를 사용합니다. SLOF도 IEEE 1275 호환이지만, IBM POWER 아키텍처에 특화된 확장(RTAS, CAS 등)을 포함합니다.

특성OpenBIOSSLOF
타겟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 InterfaceOS→펌웨어 표준 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 패키지. 디스크리스 워크스테이션의 기본 부팅 방식
다음 학습: