我正在开发一个需要向 CD-ROM 驱动器发出原始 SCSI 命令的应用程序。目前,我正在努力向0xBE
驱动器发送 READ CD ( ) 命令并从 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 字节,这解释了为什么sizeof(buf)
是 2352。
为简洁起见,省略了错误检查。调试器显示DeviceIoControl
调用成功返回ioctl_bytes
is 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 文档,我已经尝试对齐目标缓冲区,该文档说SCSI_PASS_THROUGH_DIRECT的 DataBuffer 成员是指向此适配器设备对齐缓冲区的指针。实验性地将缓冲区对齐到 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
这让我相信,因为AlignmentMask
是 1,所以不需要对齐,因此这似乎不是问题的原因。有趣的是,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 上运行。