2006年07月13日

LBA と MSF

CD上のデータの位置は、単純に0から増えてゆくLBA (Logical Block Address、論理ブロックアドレス) とMSF (Minute Second Frame、分秒フレーム) という時間で表示する2種類のアドレスで表されます。CDのサブチャネルでは時間表記されており、LBAはもっぱらドライバやプログラム側の都合で使用されます。

MSFでは、1分=60秒、1秒=75フレームです。

このLBAとMSFの変換がややこしいです。LBAの基点は最初のトラックの INDEX 1 が始まる位置であり、データCDおよびCD-Rへの書き込み時では 00:02:00 (=75*2) です。そしてその変換式は、次のようになります。

BYTE M; //分
BYTE S; //秒
BYTE F; //フレーム
long LBA; //論理ブロックアドレス

条件変換式
-150≦LBA≦404849 M = (LBA + 150)/75/60
S = ((LBA + 150)/75)%60
F = (LBA + 150)%75
-45150≦LBA≦-151 M = (LBA + 450150)/75/60
S = ((LBA + 450150)/75)%60
F = (LBA + 450150)%75
00:00:00≦LBA≦89:59:74 LBA = M*60*75 + S*60 + F - 150
90:00:00≦LBA≦99:59:74 LBA = M*60*75 + S*60 + F - 450150
   LBA    MSF
 -45150 ┬ 90:00:00
    │
    │
  -151 ┼ 99:59:74 
  -150 ┼ 00:00:00
    │
   0 ┼ 00:02:00
    │
    │
    │
 404849 ┼ 89:59:74
 -45150 ┼ 90:00:00
    │
    │
  -151 ┴ 99:59:74 
先頭のリードインでは、なんと 100:00:00 からのマイナスになります。


蛇足
「せんべえ焼き」では、
MSF 00:00:00 = 0 LBA
とする表現を主に使用しており、
コマンドを送る直前に -150 しています。
このため、ソースコードがよけいややこしくなっています。

posted by 七癖 at 17:53| Comment(0) | TrackBack(0) | Multimedia Commands | このブログの読者になる | 更新情報をチェックする

2006年07月11日

Compact Disc Sector/Frame

ASPI によるCDデータの読み書きの基本単位はセクタです。
リードイン、トラック、リードアウト共に、多数のセクタが集まって出来ています。

1セクタは 1/75 秒 (=1フレーム) です。

一つのセクタは、2352バイトのメインチャネルと、 98バイトのサブチャネルよりなります。
他に同期パターンやパリティなどが記録面に書かれていますが、
おそらくこれらはファームウェアやドライバーなどが自動的にやってくれるので
ASPIで制御する限り気にする必要はありません。
メインデータとサブデータの配置も考えなくていいです。
また、サブチャネル98バイトの内、先頭の2バイトは同期信号で、
これまた考えなくて結構です。
よって、読み取りでも書き込みでも、最大 (RAW96) で、
1セクタは 2448 (= 2352+96) となります。
ただし、読み書きの方法をサブチャネルを除くCD-DAデータのみとしたり、
CD-ROM のエラー訂正コードなどを除くユーザーデータのみとすることができます。
これらの場合、1セクタ = 2352 とか、1セクタ = 2048 のように考えます。

メインチャネルにはデータ本体が入っており、
サブチャネルにはCDプレーヤーで表示される経過時間などの情報が入っています。
1バイトは当然8ビットで、1ビットは0か1のどちらかをとります。

posted by 七癖 at 19:45| Comment(0) | TrackBack(0) | Multimedia Commands | このブログの読者になる | 更新情報をチェックする

2006年07月09日

Compact Disc Session/Track/Index

CD-Rへ記録するプログラムを作る場合、 多くをドライバに任せることも出来ますが、 ある程度細かく指定することも出来るので、 CDの規格を知っておくほど面白いです。

オーディオCDはSONYとPhilipsによって開発されました。 その規格を眺めると、LPレコードの影響を受けており、 高性能で安価なコンピューターがない時代の工夫が偲ばれます。

CD-ROMはオーディオCDの音楽データのかわりに 位置情報とデジタルデータとエラー検出、訂正コードを乗せた規格です。 ですから基本は音楽CDです。

CDのデータはLPレコードのように渦巻状に配置されています。 そして、CDのデータは一つ以上のセッションよりなります。 一つのセッションは、先頭にリードイン、最後にリードアウト、 そして途中は音楽やデータを記録した複数のトラックよりなります。 このトラックが、音楽CDの一曲に相当します。 一つのセッションは複数のインデックスで分けられます。 このうち、Index 0 プレギャップとも呼ばれ、 曲の経過表示のマイナスの部分になります。 普通、Index 1 は曲の本体ですが、Index 2、Index 3、・・・がある曲もあります。

Session 1 Session 2 ・・・
LeadIn
(〜449999)
Track
1
Track
2
・・・ Track
n
LeadOut
(90s)
LeadIn
Track
n+1
・・・ Track
m
LeadOut
(30s)
Index
0
(-150〜-1)
Index
1
(0〜)
・・・ Index
0
Index
1
・・・

多くのCDでは、セッションは一つです。 データCDでは、トラックが1つのCD-ROMも多いです。 インデックスは、トラック1を除き、一つしかない (Index 1 のみ) 場合があります。

posted by 七癖 at 19:42| Comment(0) | TrackBack(0) | Multimedia Commands | このブログの読者になる | 更新情報をチェックする

2006年07月05日

GET CONFIGURATION その3 Profile List

ドライブがサポートしているメディアの種類(のみ)を入手するには、 GET CONFIGURATION コマンドパケットのRTに2を、SFNに0x0000をセットします。
受け取るデータは、コマンドに共通のヘッダの後に、次のように続きます。

BYTE\bit 7 6 5 4 3 2 1 0
0
(MSB)
Featurn Code = 0x0000
(LSB)
1
2 予約 Version Persistent Current
3追加長さ
4〜各データ

"追加長さ"= 4×サポートしているメディアの数 です。
一つのメディアにつき4バイト分の情報があり、 それが"各データ"の部分に、メディアの数だけ続きます。
各メディアの情報は、次の通りです。

BYTE\bit 7 6 5 4 3 2 1 0
0
(MSB)
メディアの種類
(LSB)
1
2 予約 CurrentP
3予約

"メディアの種類"におけるメディアと番号の対応は、ヘッダのCurrent Profileと同じで、幾つかは前回の表で示した通りです。

このような可変長のデータを入手する場合は、予め多めにメモリを確保しておくか、 一度ヘッダ部分だけ入手した後で、 データ長などの情報を元にメモリを確保し直して、 もう一度コマンドを実行しましょう。
posted by 七癖 at 16:19| Comment(0) | TrackBack(1) | Multimedia Commands | このブログの読者になる | 更新情報をチェックする

2006年07月03日

GET CONFIGURATION その2 response data

GET CONFIGURATION コマンドで入手されるデータは、8バイトのヘッダと、 RTとSFNで指定した情報よりなります。

BYTE\bit 7 6 5 4 3 2 1 0
0〜7ヘッダ
8〜n応答データ

ヘッダの形式は、以下の通りです。

BYTE\bit 7 6 5 4 3 2 1 0
0
(MSB)
データ長
 
(LSB)
1
2
3
4予約
5予約
6
(MSB)
セットされているディスクの種類
(LSB)
7

"セットされているディスクの種類" (Current Profile) には、 メディア毎に割り振られた値が入ります。 その幾つかを挙げておきます。

番号名称
0x0000ディスクがない
0x0008CD-ROM
0x0009CD-R
0x000ACD-RW
0x0010DVD-ROM
0x0011DVD-R Sequential Recording
0x0012DVD-RAM
0x0013DVD-RW Restricted Overwrite
0x0014DVD-RW Sequential Recording
0x0015DVD-RW Dual Layer Sequential Recording
0x0016DVD-RW Dual Layer Jump Recording
0x001ADVD+RW
0x001BDVD+R
0x002ADVD+RW Dual Layer
0x002BDVD+R Dual Layer
0x0040BD-ROM (Blu-ray Disc ROM)
0x0041BD-R SRM
0x0042BD-R RRM
0x0043BD-RE
0x0050HD DVD-ROM
0x0051HD DVD-R
0x0052HD DVD-RAM
0xFFFF不明、このドライブでは使えない

他にもあります。消えたものもあります。今後追加されるメディアもあるでしょう。
posted by 七癖 at 16:21| Comment(0) | TrackBack(0) | Multimedia Commands | このブログの読者になる | 更新情報をチェックする

2006年07月01日

GET CONFIGURATION その1 CDB

最近のドライブは、コンボドライブとかマルチドライブといわれるように、 CD、DVDなど複数のメディアに対応しているものが多いです。 こういったドライブでは、 GET CONFIGURATION コマンドを用いることで、 今入っているディスクの種類や、 対応しているメディアの種類 といった情報を入手できます。 そして、昨今の規格の乱立に伴い、その機能は増え続けています。 そのため、古いドライバーではいくつかの機能に対応していない可能性もあります。
ここでは、私が使っている範囲内で説明します。 もっとも、今のところ一つの機能しか使用しておりませんが。

GET CONFIGURATION コマンドの CDB は次のとおりです。

BYTE\bit 7 6 5 4 3 2 1 0
0Operation Code = 0x46
1予約予約RT
2
(MSB)
Starting Feature Number
(LSB)
3
4予約
5予約
6予約
7
(MSB)
データ長
(LSB)
8
9予約

RT (Requested Type)とStarting Feature Number(SFN)の組み合わせで、 取得する情報を指定します。

RT内容
00Bデバイスが持つ、SFNから始まる全ての情報
01B現在のディスクに対しデバイスが持つ、SFNから始まる情報
10BSFNで指定された情報
11B予約

私の場合、RT=11Bしか使っていません。そして、SFNに 欲しい情報の Feature Number をセットして、目的の情報を入手します。

Feature Number の一例は、次の通りです。

番号名称機能
0x0000Profile Listサポートしているメディアの一覧

すみません。他にもたくさんあるのですが、これ以外は使ったことがありません。

他の多くは、どのメディアを使う場合にどのコマンドが使えるか、 といった内容です。 本当はそれらを入手してプログラムをより精密にすべきなのでしょうが、面倒なので行っていません。

posted by 七癖 at 21:52| Comment(0) | TrackBack(0) | Multimedia Commands | このブログの読者になる | 更新情報をチェックする

2006年06月29日

TEST UNIT READY

ディスクが入っていて、ドライブが使用可能かどうかを調べるには、TEST UNIT READY コマンドを使用します。

TEST UNIT READY コマンドの CDB は次のとおりです。

BYTE\bit 7 6 5 4 3 2 1 0
0Operation Code = 0x00
1LUN予約
2予約
3予約
4予約
5予約

取り敢えず全て0を入れておけばよいでしょう。

成功した場合、SK、ASC、ASCQ にセットされた値で状態を判別します。

SK ASC ASCQ エラーの内容
0x02 0x04 0x01 準備中
0x02 0x3A 0x00 ディスクが入っていません
0x06 0x28 0x00 ディスクが変更されたので、準備中
0x06 0x29 0x00 一度リセットされたので、準備中

成功した場合、上のコードがセットされていなければ、まず大丈夫でしょう。
失敗した場合には別のエラーコードがセットされます。
posted by 七癖 at 17:59| Comment(0) | TrackBack(0) | Multimedia Commands | このブログの読者になる | 更新情報をチェックする

2006年06月27日

トレイが引き出されるプログラム

ASPI を使って、ATAPI と SCSI 接続されているディスクドライブのトレイが片っ端から開かれるサンプルプログラムを作成してみました。エラー処理もせず、メッセージの一つも出ない簡単なものですが、参考になればと思います。

開発環境:Microsoft Visual C++ 6.0
実行可能条件:WINASPI32.DLL がインストールされていること。
Windows 95/98/Me では標準でインストール済。
Windows 2000/XP では別途必要。

#include "windows.h" の部分を書き換えたり、 LoadLibrary, FreeLibrary, CreateEvent, CloseHandle ResetEvent, WaitForSingleObject といったWindows特有のAPIをなんとかすれば、 他の開発環境、他のOSでも何とかなると思います。

OpenTray.lzh (プロジェクトファイル付)

#include "windows.h"

// ASPI API関数
typedef DWORD (*fGetASPI32SupportInfo)(void);  //ASPIが利用可能か
typedef DWORD (*fSendASPI32Command)(void*);    //コマンドを実行

// ASPI コマンド
#define SC_HA_INQUIRY    0x00  // ホストアダプタに関する情報を取得する
#define SC_GET_DEV_TYPE  0x01  // デバイスの情報を取得する
#define SC_EXEC_SCSI_CMD 0x02  // コマンドの実行

// コマンドの結果
#define SS_PENDING       0x00  // ASPI実行中
#define SS_COMP          0x01  // 正常終了

// デバイスの種類
#define DTYPE_CDROM      0x05  // CD-ROM device

// コマンドのフラグ
#define SRB_DIR_IN       0x08  // ドライブからデータを受け取る
#define SRB_DIR_OUT      0x10  // ドライブへデータを送る
#define SRB_EVENT_NOTIFY 0x40  // ASPIコマンド実行終了検知にイベントを使用

// MMCコマンド
#define SCSI_START_STOP_UNIT 0x1B  // Start/Stop unit

// -----------------------------------------------------------------------

// WINASPI32.dll へのハンドル
HINSTANCE m_hModule;

// イベントへのハンドル 
HANDLE m_hEvent;

// ASPI API 関数へのポインタ
fGetASPI32SupportInfo lpGetASPI32SupportInfo;  //ASPIが利用可能か
fSendASPI32Command    lpSendASPI32Command;     //コマンドを実行

// -----------------------------------------------------------------------

//構造体のメンバの幅詰をする
#pragma pack(push, 1)

// -----------------------------------------------------------------------

// SC_HA_INQUIRY ASPIコマンド
typedef struct tSRB_HAInquiry
{
    BYTE  SRB_Cmd;           // = SC_HA_INQUIRY
    BYTE  SRB_Status;
    BYTE  SRB_Ha;            // ホストアダプタ番号(入力)
    BYTE  SRB_Flags;
    DWORD Reserved1;
    BYTE  HA_Count;
    BYTE  HA_SCSI_ID;
    BYTE  HA_ManagerId[16];
    BYTE  HA_Identifier[16];
    BYTE  HA_Unique[16];     // ターゲットの数など(出力)
    WORD  Reserved2;
} SRB_HAInquiry;

// -----------------------------------------------------------------------

// SC_GET_DEV_TYPE ASPIコマンド
typedef struct tSRB_GDEVBlock
{
    BYTE  SRB_Cmd            // = SC_GET_DEV_TYPE
    BYTE  SRB_Status;
    BYTE  SRB_Ha;            // ホストアダプタ番号(入力)
    BYTE  SRB_Flags;
    DWORD Reserved1;
    BYTE  SRB_Tgt;           // ターゲット番号(入力)
    BYTE  SRB_Lun;           // ターゲットの論理ユニット番号(入力)
    BYTE  SRB_DeviceType;    // ターゲットのデバイスタイプ(出力)
    BYTE  Reserved2;
} SRB_GDEVBlock;

// -----------------------------------------------------------------------

// SC_EXEC_SCSI_CMD ASPIコマンド
typedef struct SRB_ExecSCSICmd
{
    BYTE  SRB_Cmd;           // = SC_EXEC_SCSI_CMD
    BYTE  SRB_Status;
    BYTE  SRB_Ha;            // ホストアダプタ番号(入力)
    BYTE  SRB_Flags;         // リクエストフラグ(入力)
    DWORD Reserved1;
    BYTE  SRB_Tgt;           // ターゲット番号(入力)
    BYTE  SRB_Lun;           // ターゲットの論理ユニット番号(入力)
    WORD  Reserved2;
    DWORD SRB_BufLen;        // データ用確保バッファ長
    BYTE* SRB_BufPointer;    // データバッファアドレス
    BYTE  SRB_SenseLen;      // センスデータ用確保バッファ長
    BYTE  SRB_CDBLen;        // CDBの長さ
    BYTE  SRB_HaStat;
    BYTE  SRB_TgtStat;
    VOID* SRB_PostProc;      // イベントハンドル
    BYTE  Reserved3[20];
    BYTE  CDBByte[16];       // CDBの内容
    BYTE  SenseArea[32+2];   // エラーコード(+2しているのはバウンダリ調整用)
} SRB_ExecSCSICmd;

// -----------------------------------------------------------------------

//構造体のメンバの幅詰はここまで
#pragma pack(pop)

// -----------------------------------------------------------------------

//Ha, Tgt, Lun で指定されたドライブのトレイを開く
void OpenTray(BYTE Ha, BYTE Tgt, BYTE Lun)
{
    DWORD ret;
    ::ResetEvent(m_hEvent);

    SRB_ExecSCSICmd cmd;
    memset(&cmd, 0, sizeof(cmd));

    cmd.SRB_Ha  = Ha;
    cmd.SRB_Tgt = Tgt;
    cmd.SRB_Lun = Lun;

    cmd.SRB_Cmd        = SC_EXEC_SCSI_CMD;
    cmd.SRB_Flags      = SRB_DIR_OUT | SRB_EVENT_NOTIFY;  // イベントを使用
    cmd.SRB_BufLen     = 0;
    cmd.SRB_BufPointer = NULL;
    cmd.SRB_SenseLen   = 14;
    cmd.SRB_CDBLen     = 6;
    cmd.SRB_PostProc   = (LPVOID)m_hEvent;

    cmd.CDBByte[ 0]    = SCSI_START_STOP_UNIT;  // Start/Stop unit  0x1b
    cmd.CDBByte[ 4]    = 0x02;                  //トレイを出す (0x03にするとトレイを入れる)

    ret = lpSendASPI32Command((void*)&cmd);
    if(ret == SS_PENDING)
    {
        ::WaitForSingleObject(m_hEvent, INFINITE);
    }
    // 実行結果は cmd.SRB_Status に格納される
    // エラーコードは cmd.SenseArea に格納される
}

int main(int argc, char *argv[])
{
    m_hModule = NULL;
    m_hEvent = NULL;

    // DLLを開く
    m_hModule = ::LoadLibrary("WNASPI32.DLL");
    if(m_hModule == NULL)  //DLLが開けなかったとき
    {
        return 1;
    }

    lpSendASPI32Command    = (fSendASPI32Command)::GetProcAddress(m_hModule, "SendASPI32Command");
    lpGetASPI32SupportInfo = (fGetASPI32SupportInfo)::GetProcAddress(m_hModule, "GetASPI32SupportInfo");
    if(!(lpGetASPI32SupportInfo && lpSendASPI32Command))  // 関数が使えないとき
    {
        ::FreeLibrary(m_hModule);
        return 1;
    }

    //イベントの用意
    m_hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);

    BYTE Ha, Tgt, Lun;
    int HaMax, TgtMax;
    DWORD Info;

    // ホストアダプタの数を取得
    Info = lpGetASPI32SupportInfo();  //ASPIが利用可能か
    if((Info & 0x0000ff00) != 0x00000100)
    {
        HaMax = 0;  //利用不可
    }
    else            //利用可能
    {
        HaMax = Info & 0x00ff;  //ホストアダプタの数
    }

    // 全てのホストアダプタを検索
    for(Ha = 0; Ha < HaMax; Ha++)
    {
        // ターゲットの数を取得
        SRB_HAInquiry ha_cmd;
        memset(&ha_cmd, 0, sizeof(ha_cmd));
        ha_cmd.SRB_Cmd = SC_HA_INQUIRY;
        ha_cmd.SRB_Ha  = Ha;
        Info = lpSendASPI32Command((void*)&ha_cmd);
        //一つのアダプタにあるターゲットの数
        if(ha_cmd.HA_Unique[3] == 0)
        {
            TgtMax = 8;
        }
        else
        {
            TgtMax = ha_cmd.HA_Unique[3];
        }
        //個々のアダプタに接続しているデバイスの情報を取得
        for(Tgt = 0; Tgt < TgtMax; Tgt++)
        {
            Lun = 0;

            SRB_GDEVBlock dev_cmd;
            memset(&dev_cmd, 0, sizeof(dev_cmd));
            dev_cmd.SRB_Cmd = SC_GET_DEV_TYPE;
            dev_cmd.SRB_Ha  = Ha;
            dev_cmd.SRB_Tgt = Tgt;
            dev_cmd.SRB_Lun = Lun;

            Info = lpSendASPI32Command((void*)&dev_cmd);

            if(dev_cmd.SRB_Status == SS_COMP)
            {
               	if(dev_cmd.SRB_DeviceType == DTYPE_CDROM)  //CD-ROMドライブを発見
                {
                    OpenTray(Ha, Tgt, Lun);  //Ha, Tgt, Lun で指定したドライブのトレイを開く
                }
            }
        }
    }

    //イベントを解放する
    ::CloseHandle(m_hEvent);

    // DLLを解放する
    ::FreeLibrary(m_hModule);

    return 0;
}
posted by 七癖 at 17:15| Comment(0) | TrackBack(0) | Multimedia Commands | このブログの読者になる | 更新情報をチェックする

2006年06月25日

START/STOP UNIT

ディスクドライブのトレイを出し入れするには、START/STOP UNIT コマンドを使用します。

START/STOP UNIT コマンドの CDB は次のとおりです。

BYTE\bit 7 6 5 4 3 2 1 0
0Operation Code = 0x1B
1LUN予約Immed
2予約
3予約
4予約LoEjStart
5予約

これからもですが、説明していない値には、取り合えず0を入れておいてください。

4バイト目の LoEj と Start の組み合わせで動作を決定します。

LoEj Start 動作
0 0 ディスクの回転を止める
0 1 ディスクの回転を始め、読み込みの準備を開始する
1 0 可能ならば、トレイを出す
1 1 ディスクを入れる(トレイを閉める)

トレイがロックされていると、エラーになります。

「せんべえ焼き」では、
CDBByte[4] = 0x02; // (=00000010B) トレイを出す
CDBByte[4] = 0x03; // (=00000011B) トレイを入れる
として使っています。


ASPIでドライブを制御してみたいと考えている方は、 まずこのトレイの出し入れを目指してみるのが良いかと思います。 結果が一目でわかりますし、 ウィィィン、ウィィィンパタンと動くのをみると結構感動したり、 自分がデバイスを動かしている気にはなりませんか?
posted by 七癖 at 14:13| Comment(0) | TrackBack(0) | Multimedia Commands | このブログの読者になる | 更新情報をチェックする

2006年06月21日

INQUIRY

MMC の INQUIRY コマンドは製品名やメーカー名といったデバイスの情報を取得するために使います。


INQUIRY コマンドの CDB (Command Descriptor Block) は次のとおりです。

BYTE\bit 7 6 5 4 3 2 1 0
0Operation Code = 0x12
1LUN予約
2予約
3予約
4予約
5データの最大長



INQUIRY コマンドで出力されるデータの内容は次のとおりです。

BYTE\bit 7 6 5 4 3 2 1 0
0予約デバイスの種類
1RMB予約
2ISO VersionECMA VersionANSI Version
3予約Response Data Format
4追加データ長
5予約
6予約
7予約
8
(MSB)
メーカー名 (ASCII)
(LSB)
15
16
(MSB)
製品名 (ASCII)
(LSB)
31
32
(MSB)
ファームウェアのバージョン (ASCII)
(LSB)
35
36
 
メーカー定義
 
55
56
 
予約
 
95
96
 
メーカー定義
 
n



0バイト目のデバイスの種類は、以下のようになります。(SRB_DeviceType と同じです。)

コードデバイスの種類
0x00ハードディスク、フロッピーディスクなど
0x01磁気テープなど
0x02プリンタ
0x03プロセッサ
0x05CD-ROMなど
0x06スキャナ
0x07MOなど


「せんべえ焼き、もしくはコースターメーカー」では、もっぱらドライブ名を取得するために使っています。
posted by 七癖 at 17:34| Comment(0) | TrackBack(0) | Multimedia Commands | このブログの読者になる | 更新情報をチェックする

2006年06月19日

MSB と LSB

普通のOS上で動くプログラムを作っているだけでは気にならないのですが、 マイコン用のプログラムや、 他の機器を制御するようなプログラムの場合、 流れてくるデータの内、先に来る方が高い桁なのか低い桁なのか、 後に来る方が上位の桁なのか下位の桁なのか混乱することがあります。

ビット単位で考えるとき、
最上位ビットを MSB (Most Significant Bit)
最下位ビットを LSB (Least Significant Bit)
と呼びます。

バイト単位でも同様に、
最上位バイトを MSB (Most Significant Byte)
最下位バイトを LSB (Least Significant Byte)
となります。

また、
上位のデータから並べる方法をビッグエンディアン(big endian)
下位のデータから並べる方法をリトルエンディアン(little endian)
といいます。

さて、MMCの仕様書には、下のような表がたくさん出てきます。

BYTE\bit 7 6 5 4 3 2 1 0
0
(MSB)
unsigned long data
 
(LSB)
1
2
3

(タグに慣れていないので、上手く表示されているか不安です。)
この場合、
0バイト目の7ビット目が最上位ビット
3バイト目の0ビット目が最下位ビット
となります。

このように決められたバイト列 Buf[4] に DWORD(=unsigned long)データlbaをセットするには、
Buf[0] = (BYTE)(lba >> 24);
Buf[1] = (BYTE)(lba >> 16);
Buf[2] = (BYTE)(lba >> 8);
Buf[3] = (BYTE)(lba);
とします。

また、こうして得た Buf[4] は、
lba = ((DWORD)Buf[0] << 24)
   | ((DWORD)Buf[1] << 16)
   | ((DWORD)Buf[2] << 8)
   | ((DWORD)Buf[3]);
のように結合することで、DWORD型のデータとして扱えます。

最後に、Microsoft Visual C++には、
こういった操作の手間を省くための便利なマクロがいくつか用意されています。

#define HIBYTE(w) ((BYTE)(((WORD)(w) >> 8) & 0xFF))
#define LOBYTE(w) ((BYTE)(w))
#define HIWORD(l) ((WORD)(((DWORD)(l) >> 16) & 0xFFFF))
#define LOWORD(l) ((WORD)(l))
#define MAKELONG(a, b) ((LONG)(((WORD)(a)) | ((DWORD)((WORD)(b))) << 16))
#define MAKEWORD(a, b) ((WORD)(((BYTE)(a)) | ((WORD)((BYTE)(b))) << 8))

posted by 七癖 at 17:22| Comment(0) | TrackBack(0) | Multimedia Commands | このブログの読者になる | 更新情報をチェックする

2006年06月15日

Multimedia Commands 一覧

MMC には多くのコマンドがありますが、とりあえず、これを書いている時点でせんべえ焼きで使用している、あるいは試したことのある、そして紹介していく予定のコマンドを挙げておきます。

コマンド名コード内容
BLANK0xA1メディアを消去、初期化
GET CONFIGURATION0x46現在入っているメディアの種類と、ドライブが読み書きできるメディアの種類などを取得
GET PERFORMANCE0xACメディアの読み込み、書き込み可能な速度などを取得
INQUIRY0x12ドライブの名前やメーカー名などの情報を取得
MODE SELECT (10)0x55ドライブの書き込み方法やエラーに対する対応を設定
MODE SENSE (10)0x5Aドライブの書き込み方法やエラーに対する対応を取得
PREVENT ALLOW MEDIUM REMOVAL0x1Eドア/トレイをロック/解除
READ (10)0x28ディスクのデータを読む
READ BUFFER CAPACITY0x5Cドライブのバッファの空き容量を調べる
READ CD0xBE論理アドレスで指定した位置からCDのデータを読む
READ CD MSF0xB9時間アドレスで指定した位置からCDのデータを読む
READ DISC INFORMATION0x51ディスクの容量や空か消去可能かなどの情報を取得
READ DISC STRUCTURE
(READ DVD STRUCTURE)
0xADDVD以降のディスクの種類や各層の容量を取得
READ TOC/PMA/ATIP0x43CDのトラックのリストを取得
RESERVE TRACK0x53DVDメディアのこれから書き込む容量を設定
REWIND0x01巻き戻し
SEND CUE SHEET0x5DCDを焼くときにトラックのリストを設定
SEND DISC STRUCTURE
(SEND DVD STRUCTURE)
0xBFDVD(以降)を焼くときに、1層目の容量(多層ディスク)や日時(DVD-R)を設定
SEND OPC INFORMATION0x54ディスクを焼くときにレーザーの出力を調整する
SET CD SPEED0xBBディスクの読み書き速度を設定
START STOP UNIT0x1Bトレイの出し入れを行う
SYNCHRONIZE CACHE0x35書き込みを最後まで確実に行う
TEST UNIT READY0x00デバイスが使用可能か調べる
WRITE (10)0x2Aメディアにデータを焼く


昨今の規格の乱立のため、コマンド名や内容が変わることがよくあります。なお、ドライブのメーカーがその製品専用のコマンドを用意している場合もありますが、当然汎用性はありません。


2006/10/02 修正
posted by 七癖 at 18:26| Comment(0) | TrackBack(0) | Multimedia Commands | このブログの読者になる | 更新情報をチェックする

2006年06月13日

返り値とエラーコード

SC_EXEC_SCSI_CMD の実行結果は、戻り値の他に SRB_Status メンバを確認することでも出来ます。その値は戻り値と同じで、

SS_PENDING 0x00 // 送られたコマンドを実行中
SS_COMP 0x01 // 正常終了
SS_ERR 0x04 // コマンド実行がエラーで終了した

などです。
SS_PENDING の場合は、時間がかかるということなので、待ちます。待っている間に別の (ドライブを使用しない) 処理をしておくのも良いでしょう。
SS_COMP は成功なので、次の処理に移ります。
それ以外は、とりあえず全てエラーとみなします。

さらに、より詳細なエラーの内容が SenseArea メンバの次の位置に格納されます。

BYTE SK = cmd.SenseArea[2] & 0x0f; //Sense Key
BYTE ASC = cmd.SenseArea[12]; //Additional Sense Code
BYTE ASCQ = cmd.SenseArea[13]; //Additional Sense Code Qualifier

この (SK, ASC, ASCQ) の組みが、それぞれのコマンド毎にあって、ここでは書ききれないほどの量があります。せんべえ焼きでこれらのエラーが表示された場合には、報告をお願いします。開発途中で発見された場合、MMCの仕様書にある表から探すのですが、pdfの検索が遅くてなかなか大変です。
posted by 七癖 at 17:45| Comment(0) | TrackBack(0) | Multimedia Commands | このブログの読者になる | 更新情報をチェックする

2006年06月11日

パラグラフ境界 (Paragraph Boundary)

デバイスからデータを読み書きする場合、CPUを介さないDMA(Direct Memory Access)の場合には、バッファのアドレスの下位4ビットが0から始まるようにしなければなりません。
このため、バッファを必要な量(BufferSize)より少し余分に確保し、次のように調整したアドレスからの値(pPBBuf)を使うようにします。(C++の場合)

BYTE* pBuffer = new BYTE [BufferSize + 0x0f];
BYTE* pPBBuf = (BYTE*)(((DWORD)pBuffer + 0x0f) & ~0x0f);

SRB_ExecSCSICmd 構造体の SRB_BufPointer メンバには、こうして調整したポインタ pPBBuf をセットします。

私は開発の初期段階で、これを知らなかったためエラーに悩まされました。いやもう凄かったこと。OSが落ちるわ落ちるわ。

posted by 七癖 at 16:13| Comment(0) | TrackBack(0) | Multimedia Commands | このブログの読者になる | 更新情報をチェックする

2006年06月09日

SRB_ExecSCSICmd


最も多用するASPIコマンドが、MMCコマンドパケットを送信する
SRB_ExecSCSICmd です。
その構造体は、次のとおりです。

typedef struct SRB_ExecSCSICmd
{
BYTE SRB_Cmd; // (入力)コマンドコード = SC_EXEC_SCSI_CMD = 0x02
BYTE SRB_Status;
BYTE SRB_Ha; // (入力)ホストアダプタ番号
BYTE SRB_Flags; // (入力)データの入出力方向、SRB_PostProcの種類
DWORD Reserved1;
BYTE SRB_Tgt; // (入力)ターゲット番号
BYTE SRB_Lun; // (入力)論理ユニット番号
WORD Reserved2;
DWORD SRB_BufLen; // (入力)バッファの大きさ
BYTE *SRB_BufPointer; // (入力)バッファへのポインタ
// (コマンドによって、バッファ内のデータが入出力されます。)
BYTE SRB_SenseLen; // (入力)エラーコードの長さ = 14
BYTE SRB_CDBLen; // (入力)コマンドの長さ
BYTE SRB_HaStat;
BYTE SRB_TgtStat;
VOID *SRB_PostProc; //イベントハンドルなど
// (コマンドの処理は、大抵時間がかかるので、
// OSやアプリケーションとの通信のためにあります。)
BYTE Reserved3[20];
BYTE CDBByte[16]; // (入力)コマンドの内容
BYTE SenseArea[32+2]; // (出力)エラーコード
} SRB_ExecSCSICmd;

フラグ SRB_Flags は、以下の値をORで組み合わせます。
SRB_POSTING 0x01 ASPIコマンド実行終了検知に、
コールバック関数を使います
SRB_ENABLE_RESIDUAL_COUNT 0x04 転送経過モニタ機能を有効にします。
SRB_DIR_IN 0x08 CDのデータを読むときなど、
ターゲットからデータを受け取る場合に指定します
SRB_DIR_OUT 0x10 CD-Rに書き込むときなど、
ターゲットにデータを送る場合に指定します
SRB_EVENT_NOTIFY 0x40 ASPIコマンド実行終了検知に、
イベントカーネルオブジェクトを使用します。

CDから読み取ったデータや、CD-Rに書き込むデータは、
SRB_BufPointer で指定した先のバッファに保存されたり、
用意しておいたりします。
そのデータの並び方もまた、コマンドによって異なります。

CDBByte に、MMCの個々のコマンドを入力します。
これからは、延々とそれらのコマンドについて、
だらだらと書いてゆければと思います。

まあ、大雑把に言って、
ドライブの指定 SRB_Ha, SRB_Tgt, SRB_Lun
コマンドの内容 CDBByte, SRB_CDBLen
コマンドで使用するバッファ SRB_BufPointer, SRB_BufLen
同期、終了処置関系 SRB_PostProc
エラー関係 SenseArea
その他のフラグ SRB_Flags
と考えれば、SPTIとかでも通用すると思います。


posted by 七癖 at 17:07| Comment(0) | TrackBack(0) | Multimedia Commands | このブログの読者になる | 更新情報をチェックする

2006年06月07日

ドライブを列挙する


ASPIでディスクドライブを列挙するには、以下の手順で行います。

1.GetASPI32SupportInfo ASPI-API関数で
ホストアダプタの数を取得する。
2.各ホストアダプタへ SC_HA_INQUIRY コマンドを送り、
アダプタが持つターゲットの数を取得する。
3.各ターゲットへ SC_GET_DEV_TYPE コマンドを送り、
ターゲットの種類を取得する。

C++言語で書くと、大雑把な流れはこんな感じです。


BYTE Ha, Tgt, HaMax, TgtMax;
DWORD Info;

// ホストアダプタの数を取得
Info = lpGetASPI32SupportInfo(); //ASPIが利用可能か
if(HIBYTE(LOWORD(Info)) != SS_COMP)
{
HaMax = 0; //利用不可
}
else //利用可能
{
HaMax = LOBYTE(LOWORD(Info)); //ホストアダプタの数
}

// 各バスを検索する。
for(Ha = 0; Ha < HaMax; Ha++)
{
// ターゲットの数を取得する。
SRB_HAInquiry ha_cmd;
memset(&ha_cmd, 0, sizeof(ha_cmd));
ha_cmd.SRB_Cmd = SC_HA_INQUIRY;
ha_cmd.SRB_Ha = Ha;
lpSendASPI32Command((LPSRB)&ha_cmd); //ホストアダプタの情報を取得
//そのアダプタにあるターゲットの数
if(ha_cmd.HA_Unique[3] == 0)
{
TgtMax = 8; //0の場合はデフォルトの8
}
else
{
TgtMax = ha_cmd.HA_Unique[3];
}
// ターゲットデバイスの情報を取得
for(Tgt = 0; Tgt < TgtMax; Tgt++)
{
SRB_GDEVBlock dev_cmd;
memset(&dev_cmd, 0, sizeof(dev_cmd));
dev_cmd.SRB_Cmd = SC_GET_DEV_TYPE;
dev_cmd.SRB_Ha = Ha;
dev_cmd.SRB_Tgt = Tgt;
dev_cmd.SRB_Lun = 0; // Lunの数を取得する方法がわからないので、
取り合えず0番を調べておきます。
lpSendASPI32Command((LPSRB)&dev_cmd); //デバイスの情報を取得する。
if(dev_cmd.SRB_Status == SS_COMP
&& dev_cmd.SRB_DeviceType == DTYPE_CDROM) //CD-ROMドライブを発見
{
//得られた[Ha:Tgt:Lun]の値をどこかに記憶しておく。
//この値を使って、ドライブを識別、交信する。
}
}
}


より正確には、せんべえ焼きか、CD Manipulatorのソースコードを見てください。

なお、各構造体の、ここでは説明していないメンバにセットされた値を
覗いてみると、けっこう面白かったりします。
posted by 七癖 at 18:23| Comment(0) | TrackBack(0) | Multimedia Commands | このブログの読者になる | 更新情報をチェックする

2006年06月05日

ASPIのコマンド


SendASPI32Commandで送るコマンドはいくつかありますが、私が使っているのは次の3つです。

ホストアダプタに関する情報(接続可能な機器の数など)を取得する
SC_HA_INQUIRY 0x00

接続されているデバイスの情報(種類など)を取得する
SC_GET_DEV_TYPE 0x01

デバイスにコマンドを送る
SC_EXEC_SCSI_CMD 0x02


各コマンドに対応する構造体は、次の通りです。
(今日は2つのみ)

ホストアダプタに関する情報を取得する
typedef struct tSRB_HAInquiry
{
BYTE SRB_Cmd; // (入力)コマンドコード = SC_HA_INQUIRY
BYTE SRB_Status;
BYTE SRB_Ha; // (入力)ホストアダプタ番号
BYTE SRB_Flags;
DWORD Reserved1;
BYTE HA_Count;
BYTE HA_SCSI_ID;
BYTE HA_ManagerId[16];
BYTE HA_Identifier[16];
BYTE HA_Unique[16]; // (出力)HA_Unique[3]に接続できる最大の機器数(ただし、0の時は8)
WORD Reserved2;
} SRB_HAInquiry;


typedef struct tSRB_GDEVBlock
{
BYTE SRB_Cmd; // (入力)コマンドコード = SC_GET_DEV_TYPE
BYTE SRB_Status;
BYTE SRB_Ha; // (入力)ホストアダプタ番号
BYTE SRB_Flags;
DWORD Reserved1;
BYTE SRB_Tgt; // (入力)ホストアダプタ内のターゲットの番号
BYTE SRB_Lun; // (入力)ターゲット内の論理ユニット番号
BYTE SRB_DeviceType; // (出力)デバイスの種類
BYTE Reserved2;
} SRB_GDEVBlock;


SRB_DeviceType に出力されるデバイスの種類
DTYPE_DASD 0x00 // Disk Device
DTYPE_SEQD 0x01 // Tape Device
DTYPE_PRNT 0x02 // Printer
DTYPE_PROC 0x03 // Processor
DTYPE_WORM 0x04 // Write-once read-multiple
DTYPE_CDROM 0x05 // CD-ROM device
DTYPE_SCAN 0x06 // Scanner device
DTYPE_OPTI 0x07 // Optical memory device
DTYPE_JUKE 0x08 // Medium Changer device
DTYPE_COMM 0x09 // Communications device
DTYPE_RESL 0x0A // Reserved (low)
DTYPE_RESH 0x1E // Reserved (high)
DTYPE_UNKNOWN 0x1F // Unknown or no device type


使わない値には、0を入れてください。
これらの構造体を、SendASPI32Commandの引数として使用します。


posted by 七癖 at 19:14| Comment(0) | TrackBack(0) | Multimedia Commands | このブログの読者になる | 更新情報をチェックする

2006年06月03日

デバイスを区別する

ドライブにアクセスするには、とりあえず使おうとするドライブを他のドライブやデバイスと区別し、認識する方法を知っていなければなりません。
ASPIの場合、ATAPIとSCSIを一緒にして考えます。そして、次の3つの数値でデバイスを識別します。

Ha:ホストアダプタ (Host Adapter)
     各機器を接続しているコンピュータ側の起点。外見的にはSCSIカードの各コネクタやマザーボードのIDEコネクタ毎にHa番号が割り振られる。

Tgt:ターゲット (Target)
     コードにつながっている個別の装置。外見的には一つのコードにつながっている機器毎にTgt番号が割り振られる。

Lun:論理装置番号 (Logical Unit Number)
     装置が持つ機能。私の環境では(インストールしているドライバーソフトのせいかもしれないが)、一つのコンボドライブで、CD/DVD-ROM/R/RWの読み書きはCD-ROMデバイスとして、それとは別にDVD-RAMの読み書きはハードディスクと同じディスクデバイスとして認識され、別々のLun番号が割り振られている。


少々豪華ですが、下のような構成があったとします

パソコン(マザーボード)
├────────┬────────┬───・・・
ATAPI(Ha=0) ATAPI(Ha=1) SCSIコネクタ(Ha=2)
│ │ │
│ │ │
├ハードディスク ├ハードディスク ├プリンタ
│ Tgt=0 │ Tgt=0 │ Tgt=0
│ │ │
└ドライブ └接続なし ├スキャナ
│Tgt=1 Tgt=1 │ Tgt=2
│ │
├CD/DVDドライブ ├CD-Rドライブ
│ Lun=0 │ Tgt=3
│ │
└DVD-RAMドライブ ├ハードディスク
Lun=1 │ Tgt=5


この場合、目的のCDドライブは、[Ha:Tgt:Lun]=[0:1:0]と[2:3:0]です。
要するに、[Ha:Tgt:Lun]の値は、パーツのつなげ方に基づいた位置を表しています。
パソコンの自作をされた方であれば分かりやすいでしょう。


これに対し、SPTIではドライブレターを使った方法で各デバイスを認識しているようです。
posted by 七癖 at 19:30| Comment(0) | TrackBack(0) | Multimedia Commands | このブログの読者になる | 更新情報をチェックする

2006年06月01日

ASPI API

ASPIには幾つかの関数が用意されていますが、せんべえ焼きで使っているのは次の3つです。

DWORD GetASPI32DLLVersion();
機能 :ASPIのバージョンを返す
引数 :なし
戻り値:DLLのバージョン

DWORD GetASPI32SupportInfo();
機能 :ASPIが使えるか調べ、ホストアダプタの数を返す
引数 :なし
戻り値:ビット 0〜 7:ホストアダプタの数
ビット16〜23:SS_COMP(=0x01)ならば利用可能

DWORD SendASPI32Command(LPSRB lpSRB);
機能 :ASPIにコマンドを送る
引数 :ASPIのコマンドとそのパラメータをまとめた構造体
(Servise Request Block)へのポインタ(void*型)
戻り値:実行結果
SS_PENDING (=0x00) 送られたコマンドを実行中
SS_COMP (=0x01) 正常終了
SS_ERR (=0x04) エラー
など

GetASPI32SupportInfo は最初に接続している機器を調べるのに使います。
実際の操作ではもっぱら SendASPI32Command を使ってゆくことになります。
posted by 七癖 at 20:05| Comment(0) | TrackBack(0) | Multimedia Commands | このブログの読者になる | 更新情報をチェックする

2006年05月31日

ASPI

しばらくの間、ASPIを中心に話を進めます。
ASPIは、WNASPI32.DLLから実行します。ただし、それぞれのSCSI/ATAPIコマンド(MMC)毎に関数(API)が用意されているのではなく、コマンドの内容を一まとめにした構造体(コマンドパケット)を引数として送信するようになっています。

ASPI32 API
┌ ASPIコマンド
│GetASPI32SupportInfo ┌
│SendASPI32Command ──┤SC_HA_INQUIRY SCSI/ATAPIコマンド(MMC)
│GetASPI32DLLVersion │SC_GET_DEV_TYPE ┌
│ :        │SC_EXEC_SCSI_CMD ─┤TEST UNIT READY
: : │ : │MODE SENSE
└ : : │MODE SELECT
└ │READ(10)
│WRITE(10)
│ :
: :


SendASPI32Command 関数からASPIコマンドを実行し、SC_EXEC_SCSI_CMDコマンドから具体的な動作を行うSCSI/ATAPIコマンドを実行します。この、最後のSCSI/ATAPIコマンドが共通化されており、SPTIでも送信方法は違えどコマンドパケットはそのままで実行できると思われます。
posted by 七癖 at 18:40| Comment(0) | TrackBack(0) | Multimedia Commands | このブログの読者になる | 更新情報をチェックする