2024年振り返りの記事で以下のようなことを書いていたのですが、
これも「Apple Partition Mapの仕様」とか「OpenFirmware 1.x と 2.x のマシンが Apple Partition Map 情報をどう解釈してどこからブートローダーを読み込むのか」とか「HFS/ISO9660ハイブリッド形式とは」とか語りだすといろいろあるのですが、Appleというメジャーマシンの仕様であってもマイナーネタすぎる(だから20年かかったとも言える)ので、需要があればまたいずれ。
オープンソースカンファレンス2025 大阪でおーぷんここんのしまださんから「Old MacのHDD起動仕様が文書になっていればそれはうれしい」という話が挙がったので、いままで脳内で雑多な情報として持っていたものを一通り書き下ろしてみようと思います。
概要
ざっくりは以下:
- Open Firmware (1.x および 2.x) は HDD上の Apple partition map の情報を解釈する
- Apple Partition Map内の情報として、起動可能ステータスになっている、かつ、プロセッサやマジックナンバーなどの条件を満たしている場合、Apple Partition Map内にあるブートローダのブロック位置、長さ、ロードアドレス、開始アドレスなどの情報に従ってブートローダを読み込んで、そこで指定されたアドレスに処理を移す
観測結果(※)としては Open Firmware 3.x の機種でも Apple partition map の解釈はしているようですが、ここで記載している 1.x や 2.x の仕様での起動はできない、というのが通説のようです。 ※ こちらのトゥートを参照
Apple Partition Map仕様
NetBSDのソースコード内でも複数箇所に定義が散らばっていてわかりづらかったりするのですが、Linuxでも使われていると思われる汎用の pdisk(8) のソースを見るのが話は通じやすいかと思います。
ただ、上記 pdisk の定義だと構造体が struct Block0
と struct dpme
という名称で直感的でないので、説明の名称としては NetBSDのソースコメントで使われている「Driver map」と「Apple partition map entry」の語でそれぞれ置き換えて説明します。
このため以下の NetBSDのソースの src/sys/sys/bootblock.h
や src/sys/arch/mac68k/include/disklabel.h
の記述も参照してください。
そもそも上記 pdisk の dpme.h
や NetBSD/mac68k の disklabel.h
のコメントとして以下の記載があるので一次情報としてこれら Inside Macintosh の書籍を探して読むべきですが、そこまでは確認していません。
// For more information see:
// "Inside Macintosh: Devices" pages 3-12 to 3-15.
// "Inside Macintosh - Volume V" pages V-576 to V-582
// "Inside Macintosh - Volume IV" page IV-292
* Driver Descriptor Map, from Inside Macintosh: Devices, SCSI Manager
* pp 12-13. The driver descriptor map always resides on physical block 0.
* Partition map structure from Inside Macintosh: Devices, SCSI Manager
* pp. 13-14. The partition map always begins on physical block 1.
ハードディスク起動要件実例
「日本語の説明はいいから起動に必要な最低限の操作はなんなのか」という場合は、以下の NetBSD/macppc 用の installboot(8)
コマンドの実装を見るのが最速です。
ただ、これだけだどれが必要十分条件なのかがわからず試行錯誤が必要になるので、私自身の経験則で「これくらいは書かないとわかりづらいだろう」というところを適当に書いてみます。
ディスク上の配置
Apple partition mapの「パーティションの情報」としての仕様と、「MacOSとしてのディスク管理用情報」の定義とがはっきり分かれていないような気もするのですが、ショートカットで前述の installboot の実装を見ると、ディスク上の先頭からのデータ配置としては以下になっていると解釈できます。
- block 0
Driver map - block 1
「Apple partition map 自身が書かれている領域」を定義した Apple partition map entry - block 2以降
「ユーザーが使用する領域(実際にOSとしてパーティションが切られている領域)」の Apple partition map entry
Mac OS のパーティションツールでは「Apple partition map 自身が書かれている領域」として64ブロック分を定義するようです。
1ブロックで1領域の定義ができるので、64ブロックということは Driver map分と Apple partition map 自身を除く 62パーティション分のエントリが定義可能ということになると思います。
ただ、仕様としては Apple partition map のサイズは可変で、前述 installboot のダミーエントリ作成実装では2ブロック分(≒実質 Apple partition mapと NetBSD分の2つのみ)のみを割り当てています。
block 0: Driver Map
これはパーティションの仕様ではなくハードディスクの仕様の情報を持つ領域っぽいです。
MacOSではデバイスドライバ的なものをパーティション末尾に配置するようで、そういった情報もここに含まれていて pdiskソースでの struct DDMap
および NetBSDソースでの「Driver Descriptor Map」に相当するようです。
pdisk ユーティリティや MacOSのツールでディスクを初期化した場合は、そこでこれら Driver Map の情報やドライバも書き込まれることになります。
が、Open Firmwareとしてはそれらの情報は見ておらず、 NetBSD/macppc の installboot で設定しているのは以下のみです。
dm.sbSig = htobe16(APPLE_DRVR_MAP_MAGIC);
dm.sbBlockSize = htobe16(512);
dm.sbBlkCount = htobe32(0);
htobe16()
や htobe32()
は「指定された値をビッグエンディアンで書き込む」なので仕様解釈では無視するとして、実質マジックナンバー(定義としては 0x4552
) とブロックサイズ (512) を書いているだけです。
ディスク全体のサイズとして 0 を書いていますが、おそらく Open Firmwareとしては実際のディスクサイズは参照していないということかと思います。
block 1: Apple partition map 領域の Apple partition map entry
これも、pdisk ユーティリティや MacOSのツールでディスクを初期化した場合はそこで自動的に作成されるはずです。
そもそも「Apple partition map の領域情報が Apple partition map の中に書いてある」という鶏卵状態なので、開始アドレスは事実上 block 1で固定であり、サイズだけに値の意味があるものと思います。
NetBSD/macppc の installboot で設定しているのは以下:
/* block 1: Apple Partition Map */
memset(&pme, 0, sizeof(pme));
pme.pmSig = htobe16(APPLE_PART_MAP_ENTRY_MAGIC);
pme.pmMapBlkCnt = htobe32(2);
pme.pmPyPartStart = htobe32(1);
pme.pmPartBlkCnt = htobe32(2);
pme.pmDataCnt = htobe32(2);
strlcpy(pme.pmPartName, "Apple", sizeof(pme.pmPartName));
strlcpy(pme.pmPartType, "Apple_partition_map", sizeof(pme.pmPartType));
pme.pmPartStatus = htobe32(0x37);
構造体定義は前述の NetBSDの sys/bootblock.h
にあって以下:
struct apple_part_map_entry {
uint16_t pmSig; /* partition signature */
uint16_t pmSigPad; /* (reserved) */
uint32_t pmMapBlkCnt; /* number of blocks in partition map */
uint32_t pmPyPartStart; /* first physical block of partition */
uint32_t pmPartBlkCnt; /* number of blocks in partition */
uint8_t pmPartName[32]; /* partition name */
uint8_t pmPartType[32]; /* partition type */
uint32_t pmLgDataStart; /* first logical block of data area */
uint32_t pmDataCnt; /* number of blocks in data area */
uint32_t pmPartStatus; /* partition status information */
/*
* Partition Status Information from Apple Tech Note 1189
*/
#define APPLE_PS_VALID 0x00000001 /* Entry is valid */
#define APPLE_PS_ALLOCATED 0x00000002 /* Entry is allocated */
#define APPLE_PS_IN_USE 0x00000004 /* Entry in use */
#define APPLE_PS_BOOT_INFO 0x00000008 /* Entry contains boot info */
#define APPLE_PS_READABLE 0x00000010 /* Entry is readable */
#define APPLE_PS_WRITABLE 0x00000020 /* Entry is writable */
[中略]
uint32_t pmLgBootStart; /* first logical block of boot code */
uint32_t pmBootSize; /* size of boot code, in bytes */
uint32_t pmBootLoad; /* boot code load address */
uint32_t pmBootLoad2; /* (reserved) */
uint32_t pmBootEntry; /* boot code entry point */
uint32_t pmBootEntry2; /* (reserved) */
uint32_t pmBootCksum; /* boot code checksum */
int8_t pmProcessor[16]; /* processor type (e.g. "68020") */
uint8_t pmBootArgs[128]; /* A/UX boot arguments */
uint8_t pad[248]; /* pad to end of block */
};
pmSig
はマジックナンバーで実体は 0x504d
という値。これを Open Firmware が見ているかの検証はしていませんが、書かれていないと他のツールで上書きされてしまうというケースが想定されるので実際は必要になるかと。
pmMapBlkCnt
は number of blocks in partition map
とあるものの、 pdisk の dpme.h
定義だと dpme_map_entries
になっていて、実際は「何番目のエントリか」を示す値ではないかなと推測しています。が、いずれにせよ Open Firmwareはあまりちゃんと見ていないということではないかと思います。
pmPyPartStart
は開始ブロック番号で、 Apple partition map は前述のとおり事実上 block 1 固定と思われるのでその値。似たような定義の pmLgDataStart
は設定されていませんが、これも Open Firmware は見ない、ということかと。
pmPartBlkCnt
はコメントのとおりパーティションサイズで、 installboot が作るダミーは前述のとおり 2ブロックのみなのでその値。ただ、後述の通り Open Firmware がこの値を見ているかは不明。
pmDataCnt
は上記で「設定されていない」と書いた pmLgDataStart
と対になる "number of blocks in data area"
ですが、なぜかこちらは設定されています。これも Open Firmware が見ているのかは不明。
pmPartName
と pme.pmPartType
は pdisk 等のユーティリティでも入力を求められるパーティション名称とタイプで、 Apple partition map を含むいくつかのタイプはデフォルトで定義されているものだと思います。
pmPartStatus
は 0x37
などというマジックナンバーではなくマクロ定義で書きましょうというやつですが、
APPLE_PS_WRITABLE
APPLE_PS_READABLE
APPLE_PS_IN_USE
APPLE_PS_ALLOCATED
APPLE_PS_VALID
が定義されていることになります。後述しますがこの値は Open Firmware も見ているようです。
block2: NetBSD領域(起動可能領域)の Apple partition map entry
これも pdisk ユーティリティや MacOSのツールでディスクを初期化した場合はすでにある程度の設定がされているはずですが、 NetBSD/macppc の installboot で設定しているのは以下:
/* block 2: NetBSD partition */
memset(&pme, 0, sizeof(pme));
pme.pmSig = htobe16(APPLE_PART_MAP_ENTRY_MAGIC);
pme.pmMapBlkCnt = htobe32(2);
pme.pmPyPartStart = htobe32(4);
pme.pmPartBlkCnt = htobe32(0x7fffffff);
pme.pmDataCnt = htobe32(0x7fffffff);
strlcpy(pme.pmPartName, "NetBSD", sizeof(pme.pmPartName));
strlcpy(pme.pmPartType, "NetBSD/macppc", sizeof(pme.pmPartType));
pme.pmPartStatus = htobe32(0x3b);
pme.pmBootSize = htobe32(roundup(params->s1stat.st_size, 512));
pme.pmBootLoad = htobe32(0x4000);
pme.pmBootEntry = htobe32(0x4000);
strlcpy(pme.pmProcessor, "PowerPC", sizeof(pme.pmProcessor));
pmSig
は前述の通りマジックナンバーです。
pmMapBlkCnt
も前述の通り「何番目のエントリか」ではないかと思いますが、 pdiskで作成した場合はすでに設定されていると思います。いずれにせよ、 Open Firmware 的にはあまり重要ではなさそうです。
pmPyPartStart
は本来開始ブロック番号ですが、ここでの 4 という数字は sys/bootblock.h
に別で定義のある
#define MACPPC_BOOT_BLOCK_OFFSET 2048
に対応しています。「なんだそれは」という感じですが、 NetBSD/macppc では「(ここで記載しているように block 0〜2 に書かれている)ダミーの Driver map と Apple partition map を避けた上でブートローダをパーティション先頭に置くという設計にした」ということかと思います。Open Firmware側の仕様としては、この pmPyPartStart
と(ここでは設定されていないので 0 のままになっているはずの) pmLgBootStart
を加算したブロック番号からブートローダを読む、ということではないかと思われます(が、本当に両方の値を見ているかの検証は未)。
pmPartBlkCnt
と pmDataCnt
は前述の通りブロックサイズですが、「十分大きければいい」という値なので、 Open Firmware が見ていたとしても「指定されたブートローダがこの範囲に入っているかのチェック」くらいではないかと思われます。
pmPartName
と pme.pmPartType
はここでは NetBSDの名称を入れていますが、これも Open Firmwareは見ておらず、 pdisk 等の他ツールでの表示のための設定と思われます。
pmPartStatus
は前述の 0x37
から 0x3b
になっていますが、 APPLE_PS_IN_USE
の代わりに APPLE_PS_BOOT_INFO
が定義された形です。 Open Firmware としてはこの APPLE_PS_BOOT_INFO
の有無でブート可能を判断しているだけではないかと思われます(これ以外のビットを見ているかどうかの検証は未)。
pmProcessor
には PowerPC
の文字列を書いていますが、 68k Macのディスクをつなげた場合もそのまま読んでしまわないようにバイナリの判別をするためのエントリと思われます。ここは Open Firmware も読んで判定しているようです。
既存 Apple partition map 中のパーティションを起動可能とした例
OSC大阪で展示した Apus2000/200 は、本エントリで書いているような情報からの推測に基づいた以下の手順を行うことで MacOS と NetBSD/macppc をどちらも起動可能な状態にできています。
- NetBSD/macppc install CD で起動する
pdisk /dev/rwd0c
などでインストール先ディスクを指定してpdisk(8)
実行i
コマンドで apple partition map 初期化を実行C
コマンドでApple_HFS
を 64セクタ目からサイズ (4194304 - 64) の 4194240 セクタ等のサイズで確保
(上記の例では次の NetBSDパーティションが先頭から 2GB オフセットの位置から始まるようにしている)
ここで 4GBを超えるサイズを設定すると MacOS 8 のツールでは認識できないことがあるため注意。Mac OS 8 は 2GB あれば十分インストール可能 (Apus2000 の内蔵HDDは 1.2GB や 2.1GB だったはず)C
コマンドでApple_UNIX_SVR2
の/
を NetBSD/macppc をインストールできるサイズで確保C
コマンドでApple_UNIX_SVR2
の(swap)
を NetBSD/macppc の swap用として必要なサイズで確保P
コマンドで設定を確認してw
コマンドでパーティションマップを書き込んでpdisk(8)
を終了- 本体を再起動し、 MacOS のインストールCDで起動して、前述の
Apple_HFS
で確保した領域に MacOS をインストールする - MacOSインストール時に HDDドライバもインストールする
(HFS領域の末尾 512セクタ分が削られて入る) - 本体を再起動して NetBSD/macppc install CD で起動する
- 前述の操作で設定した
Apple_UNIX_SVR2
の/
がdisklabel(8)
コマンド出力のa
パーティションとして見えるので、newfs /dev/rwd0a
などとして当該パーティションをnewfs(8)
する - NetBSD/macppc のバイナリセットの
kern-GENERIC.tgz, base.tgz, etc.tgz
他をtar -zxp base.tgz
等で手動展開する /etc/fstab
,/etc/rc.conf
等の、本来インストーラが設定するファイルを手動設定する/dev
以下でMAKEDEV all
してデバイスファイルを作成する等、手動インストール時の操作を実行する
(ここでinstallboot(8)
は実行せず、後述の手動操作でブートローダを設定する)- ディスク上の
Apple_UNIX_SVR2
の/
の Apple partition map エントリのpmPartStatus
を0x3b
に書き換える - ディスク上の
Apple_UNIX_SVR2
の/
の Apple partition map エントリのpmProcessor[]
にPowerPC
の ASCII文字を書き込む
ここで、 pdisk のdpme.h
で定義されているbzb_magic
の値は pdisk が設定する0xABADBABE
のままにしておく必要がある。Open Firmware は当該パーティションがApple_UNIX_SVR2
の場合のみ この値をチェックしているようである - ディスク上の
Apple_UNIX_SVR2
の/
の Apple partition map エントリのpmBootSize
に、プライマリブートローダの/usr/mdec/bootxx
のサイズをセクタサイズ (512) で切り上げた値を書く - ディスク上の
Apple_UNIX_SVR2
の/
の Apple partition map エントリのpmBootLoad
とpmBootEntry
にプライマリブートローダの/usr/mdec/bootxx
のエントリアドレスである0x4000
を書く cp /usr/mdec/ofwboot /
としてセカンダリブートローダのofwboot
を NetBSD root パーティションに置くinstallboot -v -n /dev/rwd0a /usr/mdec/bootxx /ofwboot
として「実際の書き込みをしない」の-n
オプションでintallboot(8)
を実行して表示される/ofwboot
のブロックサイズ、ブロック数、ブロック数分のブロック番号をそれぞれメモする/usr/mdec/bootxx
を任意の作業ディレクトリにコピーして以下のようにバイナリ編集するsys/bootblock.h
に定義のあるstruct shared_bbinfo
の領域をバイナリ編集する必要があるので、bootxx
内からbbi_magic[32]
の文字列 (macppc だとNetBSD/macppc bootxx 20020515
) をキーにして探すstruct shared_bbinfo
のbbi_block_size
に前項のinstallboot -v -n
でメモった「ブロックサイズ」を書く- 同
bbi_block_count
にメモした「ブロック数」を書く - 同
bbi_block_table[]
にメモしたブロック数分の「ブロック番号」に Apple Partition map のApple_UNIX_SVR2
の/
のオフセット (ここでは 2GBで0x00400000
) を加算した番号をブロック数の分だけ書く
- 前項でバイナリ編集した
bootxx
をApple_UNIX_SVR2
の/
、つまり NetBSD/macppc のa
パーティションである/dev/rwd0a
の先頭(FFSの先頭未使用領域部分)に書き込む
Open Firmware HDD起動仕様
仕様書を読んで書いているわけではなく、既存の実装と自身の試行錯誤だけなので「これだ」と断言することはできませんが、観測結果としては Open Firmware のディスク起動の要件は以下と思われます。
- 対象のディスクに Apple partition map が作成されている(Open Firmware が参照する部分だけのダミーでも可)
- 起動するパーティションの Apple partition map entry の
pmPartStatus
(pdisk のdpme.h
定義だとdpme_flags
)の「起動可能」のビットがセットされている - 起動するパーティションの Apple partition map entry の
pmProcessor[]
(pdisk のdpme.h
定義だとdpme_process_id[]
)の文字列がPowerPC
である - 起動するパーティションが
Apple_UNIX_SVR2
の/
である場合は、pmBootArgs[]
(pdisk のdpme.h
定義だとdpme_boot_args[]
)内に A/UX 用マジックナンバーが書かれている - 起動するパーティション内にプライマリブートローダが連続ブロック領域に配置されている
- 起動するパーティションの Apple partition map entry の
pmPyPartStart
(pdisk のdpme.h
定義だとdpme_pblock_start
) とpmLgBootStart
(pdisk のdpme.h
定義だとdpme_boot_block
) を加算した値のブロック番号からプライマリブートローダが置かれている - 起動するパーティションの Apple partition map entry の
pmBootSize
(pdisk のdpme.h
定義だとdpme_boot_bytes
) にプライマリブートローダのサイズが書かれている - 起動するパーティションの Apple partition map entry の
pmBootLoad
(pdisk のdpme.h
定義だとdpme_load_addr
) にプライマリブートローダのロードアドレスが書かれている - 起動するパーティションの Apple partition map entry の
pmBootEntry
(pdisk のdpme.h
定義だとdpme_goto_addr
) にプライマリブートローダの実行開始ジャンプ先アドレスが書かれている
つまり、 Open Firmware としては以下の動作をしていると思われます。
「これを結論として最初に書けよ」と言われそうですが、あくまでも観測結果からの推測なのでそれなしでは書きづらい……
boot
コマンドで指定されたデバイスの指定されたパーティションの Apple partition map entry をチェックして有効なエントリであることを確認する
(ここでパーティション番号で「デフォルト」の 0 を指定した場合は前から順番にチェックされていくのではないかと思われるが未確認)pmPartStatus
(pdisk のdpme.h
定義だとdpme_flags
)の「起動可能」のビットがセットされているのを確認するpmProcessor[]
(pdisk のdpme.h
定義だとdpme_process_id[]
)の文字列がPowerPC
であることを確認する- 当該パーティションが
Apple_UNIX_SVR2
の/
である場合は、追加でpmBootArgs[]
(pdisk のdpme.h
定義だとdpme_boot_args[]
)内の A/UX 用マジックナンバー(pdisk のdpme.h
定義のstruct bzb
のbzb_magic
) が0xABADBABE
であることを確認する
上記 1〜4 を満たした場合に当該パーティションは起動可能と判断して次項以降のブートローダ処理を実行する - 当該の Apple partition map entry の
pmPyPartStart
(pdisk のdpme.h
定義だとdpme_pblock_start
) とpmLgBootStart
(pdisk のdpme.h
定義だとdpme_boot_block
) を加算した値のブロック番号から、pmBootSize
(pdisk のdpme.h
定義だとdpme_boot_bytes
) に書かれたサイズ分、pmBootLoad
(pdisk のdpme.h
定義だとdpme_load_addr
) に書かれたアドレスにロードする - ロード後、
pmBootEntry
(pdisk のdpme.h
定義だとdpme_goto_addr
) に書かれたアドレスにジャンプする
最後に
これで一通り頭にあったことは書いたと思いますが、サーベイしたり思い出したりするのを含めてざっと 13000字で 5時間近くかかりました。少しでも需要がある、というのが見えていないとなかなか書けない分量ですね……。
訂正や追加情報その他があれば今後も都度書き足していこうと思います。