INT 13h
"INT 13h"はBIOSが提供している割り込み命令で、ディスクの読み込みや書き込みを行うためのものです。
AHレジスタの値により読み込みや書き込みといった各種操作を分岐します。 例えば"AH=02h"ならディスクからセクタを読み込む操作を指定したことになります。 ただしこの場合CHS方式でのセクタ指定となり、大容量化している最近のディスクを扱うには適していません。 そこでLBA方式での指定も可能なように拡張されており、AHレジスタに指定できる値の種類が増えています。 先ほどのセクタ読み込みの例だと、LBA方式でセクタ指定したい場合は"AH=42H"となります。
今回はこの"INT 13h AH=42h: Extended Read Sectors From Drive"についてまとめておきます。
実際にディスクを読み込むためにはいくつかパラメータを指定しなければいけません。 その指定方法は以下のとおり。
レジスタ
- AH 42H (= Extended Read Sectors From Drive)
- DL ドライブのインデックス。1番目のドライブから順に80h、81hと指定するようです。
- DS:SI segment:offset pointer (後述のDAPへのポインタを指定)
DAP (Disk Address Packet)
読み込むセクタ数や読み込んだ内容を格納するバッファのポインタなどを指定する構造体です。
- 00h [1 byte] DAPのサイズ。(10h or 18h)
- 01h [1 byte] reserved, "0"を指定
- 02h [2 bytes] 読み込むセクタ数
- 04h [4 bytes] segment:offset pointer (読み込み先バッファ)
- 08h [8 bytes] 読み込む先頭のセクタ
- 10h [8 bytes] 読み込み先バッファ(64bit), offset=04hの値が0xFFFFFFFFの場合に利用される
私の環境はx64なのでoffset=10hの方を利用すればよさそうです。
FreeBSDのソースコードにも"INT 13h"を利用した部分があったので参考までに引用しておきます。
http://svnweb.freebsd.org/base/release/9.0.0/sys/boot/i386/pmbr/pmbr.s?view=markup
read: pushl 0x4(%si) # Set the LBA
pushl 0x0(%si) # address
pushw %es # Set the address of
pushw %bx # the transfer buffer
pushw $0x1 # Read 1 sector
pushw $0x10 # Packet length
movw %sp,%si # Packer pointer
movw $0x4200,%ax # BIOS: LBA Read from disk
int $0x13 # Call the BIOS
add $0x10,%sp # Restore stack
jc err_rd # If error
ret
さてここまで調べてきて気づいたのですが、DS:SIレジスタにDAPへのポインタをどう渡せば良いかわかりません。 前述のとおりx64環境なのでアドレスは64bitなのですがDSとSIはそれぞれ16bitのレジスタなのでサイズが足りない。 アセンブリコードも書いて試してみましたが"Segmentation fault"で動きませんでした。
さらに調べてみるとどうやらEFIにはディスク読み込み/書き込み用のプロトコルが用意されているようです。 EDKの"MdePkg/Include/Protocol/DiskIo.h"をのぞいてみると定義がありました。
typedef
EFI_STATUS
(EFIAPI *EFI_DISK_READ)(
IN EFI_DISK_IO_PROTOCOL *This,
IN UINT32 MediaId,
IN UINT64 Offset,
IN UINTN BufferSize,
OUT VOID *Buffer
);
"UEFI and Windows"というドキュメントによれば "INT 13h"が一度に64KBを読み込むのに対し、このプロトコルは1MB読み込むので効率的だそうです。
次はこれを試してみようと思います。
(結局x64環境での"INT 13h"の利用方法がわからずじまいで残念。)