生の SCSI コマンドを CD-ROM ドライブに発行する必要があるアプリケーションに取り組んでいます。現在、ドライブに READ CD ( 0xBE
) コマンドを送信し、CD の特定のセクターからデータを取得するのに苦労しています。
次のコードを検討してください。
#include <windows.h>
#include <winioctl.h>
#include <ntddcdrm.h>
#include <ntddscsi.h>
#include <stddef.h>
int main(void)
{
HANDLE fh;
DWORD ioctl_bytes;
BOOL ioctl_rv;
const UCHAR cdb[] = { 0xBE, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0 };
UCHAR buf[2352];
struct sptd_with_sense
{
SCSI_PASS_THROUGH_DIRECT s;
UCHAR sense[128];
} sptd;
fh = CreateFile("\\\\.\\E:", GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL);
memset(&sptd, 0, sizeof(sptd));
sptd.s.Length = sizeof(sptd.s);
sptd.s.CdbLength = sizeof(cdb);
sptd.s.DataIn = SCSI_IOCTL_DATA_IN;
sptd.s.TimeOutValue = 30;
sptd.s.DataBuffer = buf;
sptd.s.DataTransferLength = sizeof(buf);
sptd.s.SenseInfoLength = sizeof(sptd.sense);
sptd.s.SenseInfoOffset = offsetof(struct sptd_with_sense, sense);
memcpy(sptd.s.Cdb, cdb, sizeof(cdb));
ioctl_rv = DeviceIoControl(fh, IOCTL_SCSI_PASS_THROUGH_DIRECT, &sptd,
sizeof(sptd), &sptd, sizeof(sptd), &ioctl_bytes, NULL);
CloseHandle(fh);
return 0;
}
CDB はMMC-6 Revision 2gに従って組み立てられ、LBA 1 から 1 セクターを転送する必要があります。私は CD-DA ディスクのみを使用しているため、各セクターは 2352 バイトであり、2352 である理由sizeof(buf)
が説明されています。
簡潔にするために、エラーチェックは省略されています。デバッガーは、呼び出しDeviceIoControl
が正常に返され、内部の値が次のようになっていることを示しています。ioctl_bytes
0x2c
sptd.s
Length 0x002c unsigned short
ScsiStatus 0x00 unsigned char
PathId 0x00 unsigned char
TargetId 0x00 unsigned char
Lun 0x00 unsigned char
CdbLength 0x0c unsigned char
SenseInfoLength 0x00 unsigned char
DataIn 0x01 unsigned char
DataTransferLength 0x00000930 unsigned long
TimeOutValue 0x0000001e unsigned long
DataBuffer 0x0012f5f8 void *
SenseInfoOffset 0x0000002c unsigned long
これは、コマンドがドライブによって正常に実行されたことを示しており、ScsiStatus
0 ( SCSI_STATUS_GOOD
) であり、センス データは返されませんでした。0xcc
ただし、アプリケーションがデバッグ モードでコンパイルされているため、データのバッファは書き込まれません。
ただし、CDB を次のような標準の INQUIRY コマンドに変更すると、次のようになります。
const UCHAR cdb[] = { 0x12, 0, 0, 0, 36, 0 };
バッファは問い合わせデータで適切に満たされ、ドライブの名前、ベンダー、その他すべてを読み取ることができます。
Microsoft の SCSI_PASS_THROUGH_DIRECT に関するドキュメントによると、ターゲット バッファーのアライメントを既に試みました。実験的にバッファーを 64 バイトにアラインしてもうまくいきませんでしIOCTL_SCSI_GET_CAPABILITIES
た。必要なアラインメントを返すはずの を発行すると、次の情報が得られました。
Length 0x00000018 unsigned long
MaximumTransferLength 0x00020000 unsigned long
MaximumPhysicalPages 0x00000020 unsigned long
SupportedAsynchronousEvents 0x00000000 unsigned long
AlignmentMask 0x00000001 unsigned long
TaggedQueuing 0x00 unsigned char
AdapterScansDown 0x00 unsigned char
AdapterUsesPio 0x01 unsigned char
これにより、1であるためアライメントは不要でAlignmentMask
あり、これが問題の原因であるとは思えません。興味深いことに、AdapterUsesPio
デバイス マネージャーはそうではないと言っていますが、1 です。
記録のために、以下のコードは Linux で適切に動作し、ターゲット バッファーは CD からのデータで満たされます。Windows と同様に、返される SCSI ステータスは 0 であり、センス データは返されません。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <scsi/sg.h>
#include <scsi/scsi.h>
#include <linux/cdrom.h>
#include <sys/ioctl.h>
int main(void)
{
int fd = open("/dev/sr0", O_RDONLY | O_NONBLOCK);
if(fd == -1) { perror("open"); return 1; }
{
struct sg_io_hdr sgio;
unsigned char cdb[] = { 0xBE, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0 };
unsigned char buf[2352];
unsigned char sense[128];
int rv;
sgio.interface_id = 'S';
sgio.dxfer_direction = SG_DXFER_FROM_DEV;
sgio.cmd_len = sizeof(cdb);
sgio.cmdp = cdb;
sgio.dxferp = buf;
sgio.dxfer_len = sizeof(buf);
sgio.sbp = sense;
sgio.mx_sb_len = sizeof(sense);
sgio.timeout = 30000;
rv = ioctl(fd, SG_IO, &sgio);
if(rv == -1) { perror("ioctl"); return 1; }
}
close(fd);
return 0;
}
Windows コードは、Windows XP 上の Visual Studio C++ 2010 Express および WinDDK 7600.16385.1 でコンパイルされています。Windows XP でも動作します。