2015-04-26 5 views
3

Я работаю над приложением, которое должно выдавать необработанные команды SCSI на CD-ROM. В настоящее время я борюсь с отправкой команды READ CD (0xBE) на диск и возвратом данных из данного сектора компакт-диска.Как выполнить команду READ CD на CD-ROM в Windows?

Рассмотрим следующий код:

#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; 
} 

ЦКБ был собран в соответствии с MMC-6 Revision 2g, и должен передать 1 сектор из LBA 1. Так как я работаю только с CD-DA дисков, каждый сектор 2352 байтов , что объясняет, почему sizeof(buf) является 2352.

Проверка ошибок была опущена для краткости. Отладчик показывает, что 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 к стандартной команде ЗАПРОСА как это:

const UCHAR cdb[] = { 0x12, 0, 0, 0, 36, 0 }; 

Буфер правильно заполнен данными дознания, и я могу прочитать имя диска, поставщик и все остальное.

Я уже пытался совместив целевой буфер, в соответствии с Microsoft's documentation for SCSI_PASS_THROUGH_DIRECT, в котором говорится, что член DataBuffer из 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 

Который приводит меня к мысли, что выравнивание не требуется, так как AlignmentMask равен 1, и, похоже, это не является причиной проблемы. Интересно, что AdapterUsesPio - 1, хотя диспетчер устройств говорит иначе.

Для записи приведенный ниже код работает правильно в Linux, а целевой буфер заполняется данными с компакт-диска. То же, что и в 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 компилируется с Visual Studio C++ 2010 Express и WinDDK 7600.16385.1, на Windows XP. Он также запускается в Windows XP.

+1

Я понятия не имею, но вы можете посмотреть, как это делает драйвер libscg из проекта [cdrecord] (http://cdrecord.org/). Парень, который разрабатывает cdrecord, также любит отвечать на вопросы, связанные с SCSI. – fuz

ответ

0

Проблема заключается в неправильно сформированном CDB, хотя и действительна с точки зрения синтаксиса. То, что я не смог увидеть в спецификации MMC было таково:

enter image description here

9-й байты должны содержать биты, используемые для выбора типа данных привода предполагается вернуться. В коде в вопросе я устанавливаю его в 0, а это значит, что я попросил «Нет полей» с диска.Изменение этого байта на 0x10 (Данные пользователя) приводит к тому, что в обеих версиях Linux и Windows возвращаются одни и те же данные для данного сектора. Я до сих пор не знаю, почему Linux возвратил некоторые данные в буфер даже с оригинальной формой CDB.

Надлежащий CDB для команды CD READ, при чтении одного сектора CD-DA в LBA 1, должны поэтому выглядеть следующим образом:

const unsigned char cdb[] = { 0xBE, 0, 0, 0, 0, 1, 0, 0, 1, 0x10, 0, 0 }; 
2

Ваш код BTW всегда происходит сбой в Windows 7 из-за ознакомьтесь с ограничениями безопасности. Вы можете отправлять большинство команд SCSI с помощью API DeviceIOControl, но когда дело доходит до данных или необработанных чтений, вы должны использовать предписанный метод SPTI для чтения сектора или Windows 7 будет блокировать его с правами администратора или без них, поэтому FYI, вы можете больше не выполняйте этот путь SCSI, если вы хотите больше совместимости!

Вот как будет выглядеть прописанный SPTI способ, и, к счастью, это намного меньше кода, чем создание пакета команд SCSI с использованием OxBE или READ10 (это то, что вы должны были использовать, если вам просто нужны данные сектора данных поскольку это команда SCSI-1, а не 0xBE, который менее совместимый):

RAW_READ_INFO rawRead; 

if (ghCDRom) { 
    rawRead.TrackMode = CDDA; 
    rawRead.SectorCount = nSectors; 
// Must use standard CDROM data sector size of 2048, and not 2352 as one would expect 
// while buffer must be able to hold the raw size, 2352 * nSectors, as you *would* expect! 
    rawRead.DiskOffset.QuadPart = LBA * CDROM_SECTOR_SIZE; 
// Call DeviceIoControl, and trap both possible errors: a return value of FALSE 
// and the number of bytes returned not matching expectations! 
    return (
     DeviceIoControl(ghCDRom, IOCTL_CDROM_RAW_READ, &rawRead, sizeof(RAW_READ_INFO), gAlignedSCSIBuffer, SCSI_BUFFER_SIZE, (PDWORD)&gnNumberOfBytes, NULL) 
     && 
     gnNumberOfBytes == (nSectors * RAW_SECTOR_SIZE) 
    ); 

Короче говоря, Google вокруг команды IOCTL_CDROM_RAW_READ. Вышеприведенный фрагмент кода будет работать для аудио сектора и вернуть 2352 байта. Это может вернуться к Windows NT4.0, если ваш вызов CreateFile() правильный. Но да, если вы используете IOCTL_SCSI_PASS_THROUGH_DIRECT и попытаетесь создать свой собственный командный пакет SCSI 0xBE, Windows 7 заблокирует его! Microsoft хочет, чтобы вы использовали IOCTL_CDROM_RAW_READ для сырых чтений. Вы можете создать другие пакеты команд SCSI для чтения TOC, получить возможности диска, но команда чтения будет заблокирована, а DeviceIoControl вызовет ошибку «Недопустимая функция». По-видимому, по крайней мере, для Windows 10 мое программное обеспечение снова работало, и ограничение было удалено, но поскольку Windows 7 имеет большую базу для установки пользователем, вам нужно будет сделать это в соответствии с назначенным SPTI способом ANYWAY, а IOCTL_CDROM_RAW_READ знает еще несколько менее распространенных прочитайте команды, чем универсальный 0xBE, один для старых накопителей oddball, поэтому лучше использовать его в любом случае!