问题描述
我正在开发一个需要向 CD-ROM 驱动器发出原始 SCSI 命令的应用程序.目前,我正在努力向驱动器发送 READ CD (0xBE
) 命令并从 CD 的给定扇区取回数据.
I'm working on an application that needs to issue raw SCSI commands to a CD-ROM drive. Currently, I'm struggling with sending a READ CD (0xBE
) command to the drive and getting back the data from a given sector of the 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.
The CDB was assembled according to MMC-6 Revision 2g, and should transfer 1 sector from LBA 1. Since I'm working with CD-DA discs only, each sector is 2352 bytes, which explains why sizeof(buf)
is 2352.
为简洁起见,省略了错误检查.调试器显示DeviceIoControl
调用成功返回,ioctl_bytes
为0x2c
,而sptd.s
中的值为如下:
Error-checking was omitted for brevity. The debugger shows that the DeviceIoControl
call returns successfully and ioctl_bytes
is 0x2c
, while the values inside sptd.s
are as follows :
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
),没有返回任何sense数据.但是,数据缓冲区没有写入,因为调试器显示它填充了 0xcc
,因为应用程序是在调试模式下编译的.
This shows that the command has been executed successfully by the drive, as ScsiStatus
is 0 (SCSI_STATUS_GOOD
), and no sense data was returned. However, the buffer for the data is not written to, since the debugger shows that it is filled with 0xcc
, as the application is compiled in debug mode.
但是,当我将 CDB 更改为这样的标准 INQUIRY 命令时:
However, when I change the CDB to the standard INQUIRY command like this :
const UCHAR cdb[] = { 0x12, 0, 0, 0, 36, 0 };
缓冲区已正确填充查询数据,我可以读取驱动器名称、供应商和其他所有内容.
The buffer is properly filled with inquiry data, and I am able to read the name of the drive, vendor, and everything else.
我已经尝试根据 Microsoft 的 SCSI_PASS_THROUGH_DIRECT 文档,其中说SCSI_PASS_THROUGH_DIRECT 的 DataBuffer 成员是指向此适配器设备对齐缓冲区的指针.实验性地将缓冲区对齐到 64 字节不起作用,并且发出一个 IOCTL_SCSI_GET_CAPABILITIES
,它应该返回所需的对齐,给了我以下信息:
I've already tried aligning the target buffer, according to Microsoft's documentation for SCSI_PASS_THROUGH_DIRECT, which says that The DataBuffer member of SCSI_PASS_THROUGH_DIRECT is a pointer to this adapter device aligned buffer. Experimentally aligning the buffer to 64 bytes did not work, and issuing a IOCTL_SCSI_GET_CAPABILITIES
, which is supposed to return the required alignment, gave me the following information :
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,尽管设备管理器另有说明.
Which leads me to believe that alignment is not required since AlignmentMask
is 1, and thus it does not seem like this is the cause of the problem. Interestingly, AdapterUsesPio
is 1, although the Device Manager says otherwise.
为了记录,下面的代码在 Linux 上运行正常,目标缓冲区填充了来自 CD 的数据.和Windows一样,返回的SCSI状态为0,不返回sense数据.
For the record, the code below works properly on Linux, and the target buffer is filled with data from the CD. Same as on Windows, the returned SCSI status is 0, and no sense data is returned.
#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 上运行.
The Windows code is compiled with Visual Studio C++ 2010 Express and WinDDK 7600.16385.1, on Windows XP. It is run on Windows XP as well.
推荐答案
问题出在结构不正确的 CDB 中,尽管在语法方面是有效的.我在 MMC 规范中没有看到的是:
The issue lies within an improperly formed CDB, although valid in terms of syntax. What I failed to see in the MMC specification was this :
第 9 个字节应该包含用于选择驱动器应该返回的数据类型的位.在问题的代码中,我将其设置为 0,这意味着我从驱动器请求了无字段".将此字节更改为 0x10
(用户数据)会导致 Linux 和 Windows 版本为给定扇区返回相同的数据.我还是不明白为什么Linux会在缓冲区中返回一些数据,即使是CDB的原始形式.
The 9th byte is supposed to contain bits used for selecting the kind of data the drive is supposed to return. In the code in the question, I set it to 0, which means that I requested "No fields" from the drive. Changing this byte to 0x10
(User Data) results in both the Linux and Windows versions returning the same data for a given sector. I still don't know why Linux returned some data in the buffer even with the original form of the CDB.
READ CD 命令的正确 CDB,因此在 LBA 1 读取一个 CD-DA 扇区时,应如下所示:
The proper CDB for the READ CD command, when reading one CD-DA sector at LBA 1, should therefore look like this :
const unsigned char cdb[] = { 0xBE, 0, 0, 0, 0, 1, 0, 0, 1, 0x10, 0, 0 };
这篇关于如何在 Windows 中向 CD-ROM 驱动器发出 READ CD 命令?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!