tsutsuiの作業記録置き場

NetBSDとかPC-6001とかの作業記録のうち、Twitterの140字では収まらない内容や記事としてまとめるべき内容をとりあえず置いてみる予定

Power Macintosh OpenFirmware 2.x機種のHDD起動仕様

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) のソースを見るのが話は通じやすいかと思います。

github.com

ただ、上記 pdisk の定義だと構造体が struct Block0struct dpme という名称で直感的でないので、説明の名称としては NetBSDのソースコメントで使われている「Driver map」と「Apple partition map entry」の語でそれぞれ置き換えて説明します。

このため以下の NetBSDのソースsrc/sys/sys/bootblock.hsrc/sys/arch/mac68k/include/disklabel.h の記述も参照してください。

github.com

github.com

そもそも上記 pdisk の dpme.hNetBSD/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) コマンドの実装を見るのが最速です。

github.com

ただ、これだけだどれが必要十分条件なのかがわからず試行錯誤が必要になるので、私自身の経験則で「これくらいは書かないとわかりづらいだろう」というところを適当に書いてみます。

ディスク上の配置

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);

構造体定義は前述の NetBSDsys/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 が見ているかの検証はしていませんが、書かれていないと他のツールで上書きされてしまうというケースが想定されるので実際は必要になるかと。

pmMapBlkCntnumber 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 が見ているのかは不明。

pmPartNamepme.pmPartType は pdisk 等のユーティリティでも入力を求められるパーティション名称とタイプで、 Apple partition map を含むいくつかのタイプはデフォルトで定義されているものだと思います。

pmPartStatus0x37 などというマジックナンバーではなくマクロ定義で書きましょうというやつですが、

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 が見ていたとしても「指定されたブートローダがこの範囲に入っているかのチェック」くらいではないかと思われます。

pmPartNamepme.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 は、本エントリで書いているような情報からの推測に基づいた以下の手順を行うことで MacOSNetBSD/macppc をどちらも起動可能な状態にできています。

  1. NetBSD/macppc install CD で起動する
  2. pdisk /dev/rwd0c などでインストール先ディスクを指定して pdisk(8) 実行
  3. i コマンドで apple partition map 初期化を実行
  4. C コマンドで Apple_HFS を 64セクタ目からサイズ (4194304 - 64) の 4194240 セクタ等のサイズで確保
    (上記の例では次の NetBSDパーティションが先頭から 2GB オフセットの位置から始まるようにしている)
    ここで 4GBを超えるサイズを設定すると MacOS 8 のツールでは認識できないことがあるため注意。Mac OS 8 は 2GB あれば十分インストール可能 (Apus2000 の内蔵HDDは 1.2GB や 2.1GB だったはず)
  5. C コマンドで Apple_UNIX_SVR2/NetBSD/macppc をインストールできるサイズで確保
  6. C コマンドで Apple_UNIX_SVR2(swap)NetBSD/macppc の swap用として必要なサイズで確保
  7. P コマンドで設定を確認して w コマンドでパーティションマップを書き込んで pdisk(8) を終了
  8. 本体を再起動し、 MacOS のインストールCDで起動して、前述の Apple_HFS で確保した領域に MacOS をインストールする
  9. MacOSインストール時に HDDドライバもインストールする
    (HFS領域の末尾 512セクタ分が削られて入る)
  10. 本体を再起動して NetBSD/macppc install CD で起動する
  11. 前述の操作で設定した Apple_UNIX_SVR2/disklabel(8) コマンド出力の a パーティションとして見えるので、 newfs /dev/rwd0a などとして当該パーティションnewfs(8) する
  12. NetBSD/macppc のバイナリセットの kern-GENERIC.tgz, base.tgz, etc.tgz 他を tar -zxp base.tgz 等で手動展開する
  13. /etc/fstab, /etc/rc.conf 等の、本来インストーラが設定するファイルを手動設定する
  14. /dev 以下で MAKEDEV all してデバイスファイルを作成する等、手動インストール時の操作を実行する
    (ここで installboot(8) は実行せず、後述の手動操作でブートローダを設定する)
  15. ディスク上の Apple_UNIX_SVR2/Apple partition map エントリの pmPartStatus0x3b に書き換える
  16. ディスク上の Apple_UNIX_SVR2/Apple partition map エントリの pmProcessor[]PowerPC の ASCII文字を書き込む
    ここで、 pdisk の dpme.h で定義されている bzb_magic の値は pdisk が設定する 0xABADBABE のままにしておく必要がある。Open Firmware は当該パーティションApple_UNIX_SVR2 の場合のみ この値をチェックしているようである
  17. ディスク上の Apple_UNIX_SVR2/Apple partition map エントリの pmBootSize に、プライマリブートローダ/usr/mdec/bootxx のサイズをセクタサイズ (512) で切り上げた値を書く
  18. ディスク上の Apple_UNIX_SVR2/Apple partition map エントリの pmBootLoadpmBootEntry にプライマリブートローダ/usr/mdec/bootxx のエントリアドレスである 0x4000 を書く
  19. cp /usr/mdec/ofwboot / としてセカンダリブートローダofwbootNetBSD root パーティションに置く
  20. installboot -v -n /dev/rwd0a /usr/mdec/bootxx /ofwboot として「実際の書き込みをしない」の -n オプションで intallboot(8) を実行して表示される /ofwboot のブロックサイズ、ブロック数、ブロック数分のブロック番号をそれぞれメモする
  21. /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) を加算した番号をブロック数の分だけ書く
  22. 前項でバイナリ編集した bootxxApple_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 としては以下の動作をしていると思われます。
「これを結論として最初に書けよ」と言われそうですが、あくまでも観測結果からの推測なのでそれなしでは書きづらい……

  1. boot コマンドで指定されたデバイスの指定されたパーティションApple partition map entry をチェックして有効なエントリであることを確認する
    (ここでパーティション番号で「デフォルト」の 0 を指定した場合は前から順番にチェックされていくのではないかと思われるが未確認)
  2. pmPartStatus (pdisk の dpme.h 定義だと dpme_flags)の「起動可能」のビットがセットされているのを確認する
  3. pmProcessor[](pdisk の dpme.h 定義だと dpme_process_id[])の文字列が PowerPC であることを確認する
  4. 当該パーティションApple_UNIX_SVR2/ である場合は、追加で pmBootArgs[](pdisk の dpme.h 定義だと dpme_boot_args[])内の A/UX 用マジックナンバー(pdisk の dpme.h 定義の struct bzbbzb_magic) が 0xABADBABE であることを確認する

    上記 1〜4 を満たした場合に当該パーティションは起動可能と判断して次項以降のブートローダ処理を実行する

  5. 当該の 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) に書かれたアドレスにロードする
  6. ロード後、 pmBootEntry (pdisk の dpme.h 定義だと dpme_goto_addr) に書かれたアドレスにジャンプする

最後に

これで一通り頭にあったことは書いたと思いますが、サーベイしたり思い出したりするのを含めてざっと 13000字で 5時間近くかかりました。少しでも需要がある、というのが見えていないとなかなか書けない分量ですね……。

訂正や追加情報その他があれば今後も都度書き足していこうと思います。