PC-6601用3.5インチ1DディスクのLinuxでの読み取りに関する覚え書き

LinuxでPC-6601用の1Dディスクを読み取る手順についての覚え書きです。そのものズバリの情報が意外と見つからないので、自分用のメモという意味で、調べたことや試行錯誤した結果を書き連ねてみました。66本体を経由するか、Ditt!1DDITTなどのDOSツールに頼るしか方法がないのだと思っていたのですが、Linuxも結構便利じゃないですか。SR用の1DDや、その他機種の2D/2DDを読むことも可能です。

こんな人向けです。

(追記:最近のWindowsの場合、fdrawcmd.sysというデバイスドライバを入れて、NDittSAMdiskHxC Floppy Emulator Softwareなどのツールを使えば読み取れるようです。)

(追記:マイコンを用いた3.5インチ倍密度フロッピーのUSBリード/ライト実験というのもやってみました。)


目次


試した環境

一般的な内蔵型FDD、一般的なLinuxディストリビューションなら大丈夫か?USB接続のFDDではどうも無理っぽい。

FDの種類と物理フォーマットについて

フォーマットの種類は星の数ほどあるらしいが、とりあえすP6関連フォーマットや似ているものについてまとめ。FDに関する基礎知識も含めてメモ。

1D
片面倍密度。PC-6601で用いられているもの。6601の場合、35トラック16セクタ256バイトというフォーマットが標準とされている。
2D
両面倍密度。
1DD
片面倍密度倍トラック。PC-6601SRで用いられているもの。66SRの場合、80トラック16セクタ256バイトが標準。
2DD
両面倍密度倍トラック。
2HD
両面高密度倍トラック。現在普通に使われるもの。両面、1面あたり80トラック、トラックあたり18セクタ、セクタあたり512バイトで合計1440KB。PC-98で用いられた、両面77トラック8セクタ1024バイトで1232KBというのもあるが、これはFDDの回転数が異なっていて何かと話が違うらしい。

標準フォーマットの1D読み取り

続いて、具体的なLinuxでの読み取り方法について。2HDなどメジャーなフォーマットはマイナー番号やデバイスファイルが用意されていて簡単に読めるようになっているのだが、残念ながら1D用のものは用意されていない。そこで、fdutilsパッケージのsetfdprmコマンドを使い、FDのパラメータを直接指定する。

$ setfdprm /dev/fd0 dd stretch=1 cyl=35 head=1 sect=16 ssize=256
$ cp /dev/fd0 image.dsk

などとやると1Dディスク全体のベタイメージが作成される。cpコマンドのかわりに、ddコマンドで特定のセクタだけ読むこともできる。

$ dd bs=256 skip=18 count=1 if=/dev/fd0 of=image.dsk

例えば、これでトラック1セクタ3のイメージが作成される。18セクタ分スキップするので、最初のトラック(トラック0)の全16セクタ+次のトラック(トラック1)の2セクタがスキップされ、2番目のトラックの3番目のセクタ、即ちトラック1セクタ3が読まれるわけだ。ただし、実作業では全体のイメージをHDDにコピーしてしまってからの方が楽だろう。

入出力を逆にすれば書き込みも可能だ。当時6601用として売られていたディスクでは未確認だが、現在売られている2HDディスクの識別孔にテープを貼り、6601形式で物理フォーマットして書き込むと、実機でも読み出す事ができた。

$ superformat /dev/fd0 dd stretch=1 cyl=35 head=1 sect=16 ssize=256
$ cp image.dsk /dev/fd0


特殊フォーマットの1D読み取り

特殊なフォーマットのディスクは、以上の方法では読むことができない。例えば「マリオブラザースSPECIAL」では、トラック0は16セクタ×256バイト、トラック1以降は4セクタ×1024バイトと、トラックごとにセクタ構成が異なるので、setfdprmでの指定ではうまくいかない。幸い、LinuxのfdutilsパッケージにはFDC(Floppy Disk Controller)を直接叩く「fdrawcmd」というコマンドがあるので、これを使えばセクタ構成を確認して1セクタずつ読み出すことが可能だ。このコマンドはタイミング取りからデータの受け渡しまで自動的にやってくれるので、かなり気軽にFDCを触ることができる。

(※追記: fdrawcmdは、Linux5.18以降はデフォルトでは無効にされているそうです。)

$ fdrawcmd [option] FDCcommand args

という形で入力すればよい。引数の数はFDC命令によって異なる。FDCからの返り値(リザルトステータスなど)は標準エラー出力に出力される。I/Oエラーが出てどうにもならなくなった場合は、

$ floppycontrol --resetnow 2

とするといいらしい。(--reset 2でないと駄目な場合もあるみたい?)以下、fdrawcmdのオプションや、FDC命令の引数などについて。普通に読むだけならば、以下の四種類のFDC命令で大体事足りるようだ。

RECALIBRATE

ヘッドをトラック0まで戻し、位置を較正する。最初に一回やっておく。オーバートラックを使った後は二度やるのが作法らしい。初期のFDCはステップパルスを77回分しか出さないのだとか。

$ fdrawcmd recalibrate 0

引数は「ヘッド番号(0〜1)×4+ドライブ番号(0〜3)」。片面ディスク(1D/1DD)でドライブが1台なら常に0。返り値の1個目はステータス、2個目はシリンダ位置なので正常終了なら0になる。

(※実際には返り値はRECALIBRATE命令自体に対するものではなく、終了後にfdrawcmdが自動的に与えるSENSE INTERRUPT STATUSという命令に対するもの。)

SEEK

目的のシリンダ位置までヘッドを移動する。

$ fdrawcmd seek 0 cylinder(0〜79)

引数の0はRECALIBRATE命令と同じ。2DD/2HD用ドライブの場合、cylinderは倍トラック換算なので、1Dのディスクに対しては読みたいトラック番号の倍の数字を指定することになる。返り値はrecalibrateと同じ。正常終了なら2個目は指定したのと同じトラック番号が返ることになる。

(※これもRECALIBRATEと同様、SENSE INTERRUPT STATUS命令に対する返り値。)

READ ID

ヘッドの直近にあるセクタの情報を取得する。

$ fdrawcmd rate=2 [short] [repeat=nrepeat] readid 0

倍密度のディスク(1D/2D/1DD/2DDすべて)を読み書きする場合は、(fdrawcmdコマンドのオプションである)rate=2を指定する。rate=1とすると5インチディスクが読めるらしいがドライブが無いので未確認。指定しないとrate=0となり2HD用になる。shortオプションとrepeatオプションは別にREAD ID命令専用ではないが、特にわかりやすくなるのでここに記した。shortは返り値を一行にまとめて表示するオプション。repeatは同じ命令を繰り返すオプション。(READ ID命令の)引数の0はRECALIBRATE/SEEKの場合と同じ。

返り値の1個目〜3個目はリザルトステータス。4個目〜7個目が見つかったセクタに記載してある情報で、それぞれシリンダ番号、ヘッド番号(1D/1DDなら常に0)、セクタ番号、セクタサイズ(128×2^Nバイト)に対応する。repeat=20ぐらいにしてみると、第6カラム(セクタ番号)がFDの回転に伴って順に増えていくのがわかる。

READ DATA

ヘッドが位置するトラックにおいて、指定されたセクタを探してデータを読む。読んだデータは標準出力に出力される。

$ fdrawcmd length=length rate=2 read 0 C H R N EOT GPL DTL > image.dsk

何バイト読むかをlengthオプションで指定しておく。1D標準フォーマットのディスクから1セクタずつ読むならlength=256。READ DATA命令の引数は以下の通り。

返り値はREAD ID命令と同じ7個である。


自動読み出しスクリプト

フォーマットのわからないFDを読み取ってD88形式で出力するシェルスクリプト(2013.4.30バージョン)を書いてみた。あくまで自分用に書いたもので、エラーチェックなどあまりしていないしバグがある可能性も高いので、扱いには注意されたい。

$ ./disk.sh image.d88

などとして動かす。動作条件を変える時は以下のようにスクリプト内の変数代入を書き換える。

type
1D/2D/1DD/2DD/2HDのどれか。
ncylinder
(倍トラック換算での)シリンダ数。D88形式に則りDittに倣って82シリンダ分のトラックを読むようにしてあるが、オーバートラックのデータが無いのなら減らしても構わない。6601の1Dは標準で35トラックなので、倍トラックに換算してncylinder=69とすると吸い出しが早く終わるかもしれない。
retry
エラーが検出された時に再試行する回数。retry回試しても駄目な場合は、諦めて最後の試行で読み取れたデータを使う。
drive
ドライブ番号。複数台のFDDを接続していて2台目以降を使いたい場合は変更する。
gpl
セクタ間のギャップ。このスクリプトでは1セクタずつ読んでいるので実は関係ない。
protected
ライトプロテクトの有無。0だとかかっていない状態、それ以外だとかかっている状態のイメージになる。
output
出力ファイル名をいちいち入力するのは面倒臭い、固定してしまえという場合は変更する。
seclist,img
一時ファイル名。/tmpにimg-$head-$cylinderといったファイルが大量に作られる。名前や場所を変更したい場合は変更する。
error
スクリプト内で利用する場合を除き、標準エラー出力は/dev/nullに捨てているので、見たい場合は変更する。
rm
rm=0にすると一時ファイルを消さない。
rate
rate=1にすると5インチディスクが読めるらしいが未確認。
nhead
片面ディスクなら0、両面ディスクなら1になる。なんか直感と1ずれているような気もする。
d88type
D88形式においてディスクの種類の判別に使われる値。2D(0)/2DD(0x10)/2HD(0x20)の三種類のはずだが、1DDITTで1DDを吸い出すとオリジナルの値(0x40)が書き込まれるとのこと。1DDITTで作成するイメージのフォーマット (.1dd) はD88形式とは若干異なる (表裏を交互に並べるのでなく表だけまとめて並べる) ので、それを明示するためなのだろう。このスクリプトでは出力はD88形式に従うので、1Dは2Dと、1DDは2DDと共通の値を使う事にした。もしエミュレータの対応などで不都合が出るようならば、1DDITTに合わせる必要があるかもしれない。
step
1Dの場合、倍トラック換算における奇数トラックは飛ばすようにしたが、実際には、存在しないはずのトラックでも謎のデータが読める場合がある。例えば手元のマリオブラザーズSPECIALには、倍トラック換算でのトラック1にC/H/R/N=0/0/0Eh,0Fh/1という2個の変なセクタが存在するように見える。なので正確なディスクのコピーという意味では飛ばさない方がいいのかもしれないが、たぶん隣のトラックのデータが何かの弾みで見えているだけな気がする。step=1にするとそういうのも全部読むが、エミュレータでの扱いによっては動かなくなる可能性もあるので注意。

参考文献


6601内蔵ドライブについてのメモ


トップ > 雑多なもの > LinuxでのPC-6601用3.5インチ1Dディスク読み取りに関する覚え書き