2015-04-26 2 views
3

Je travaille sur une application qui doit émettre des commandes SCSI brutes sur un lecteur de CD-ROM. Actuellement, je n'arrive pas à envoyer une commande READ CD (0xBE) au lecteur et à récupérer les données d'un secteur donné du CD.Comment émettre une commande READ CD sur un lecteur de CD-ROM sous Windows?

Consultez le code suivant:

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

La PEH a été assemblé selon MMC-6 Revision 2g, et devrait transférer 1 secteur de 1. Depuis que je LBA je travaille avec des disques CD-DA seulement, chaque secteur est 2352 octets , ce qui explique pourquoi sizeof(buf) est 2352.

La vérification d'erreur a été omise par souci de brièveté. Le débogueur montre que le tandis que les valeurs à l'intérieur de sptd.s appel retourne avec succès DeviceIoControl et ioctl_bytes est 0x2c, sont les suivantes:

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 

Cela montre que la commande a été exécutée avec succès par le lecteur, comme ScsiStatus est égal à 0 (SCSI_STATUS_GOOD) et aucune donnée de détection n'a été renvoyée. Toutefois, le tampon pour les données n'est pas écrit, car le débogueur montre qu'il est rempli avec 0xcc, que l'application est compilée en mode débogage.

Cependant, quand je change la PEH à la commande standard ENQUETE comme ceci:

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

Le tampon est correctement rempli avec des données d'enquête, et je suis capable de lire le nom du lecteur, le vendeur, et tout le reste.

je l'ai déjà essayé aligner le tampon de cible, selon la Microsoft's documentation for SCSI_PASS_THROUGH_DIRECT, qui dit que Le membre DataBuffer de SCSI_PASS_THROUGH_DIRECT est un pointeur sur ce dispositif adaptateur tampon aligné. l'alignement Expérimentalement le tampon à 64 octets ne fonctionnait pas, et l'émission d'un IOCTL_SCSI_GET_CAPABILITIES, qui est censé revenir l'alignement nécessaire, m'a donné les informations suivantes:

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 

Ce qui me porte à croire que l'alignement n'est pas nécessaire puisque AlignmentMask est 1, et donc il ne semble pas que ce soit la cause du problème. Fait intéressant, AdapterUsesPio est 1, bien que le Gestionnaire de périphériques dise le contraire. Pour l'anecdote, le code ci-dessous fonctionne correctement sous Linux et le tampon cible est rempli avec les données du CD. Identique à Windows, l'état SCSI renvoyé est 0 et aucune donnée de détection n'est renvoyée.

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

Le code Windows est compilé avec Visual Studio C++ 2010 Express et winddk 7600.16385.1, sous Windows XP. Il est également exécuté sous Windows XP.

+1

Je n'en ai aucune idée, mais vous pourriez vouloir regarder comment le pilote libscg du projet [cdrecord] (http://cdrecord.org/) le fait. Le gars qui développe cdrecord aime aussi répondre aux questions liées à SCSI. – fuz

Répondre

0

Le problème réside dans un CDB incorrectement formé, bien que valable en termes de syntaxe. Ce que je ne voyais pas dans la spécification MMC était le suivant:

enter image description here

L'octet 9 est censé contenir des bits utilisés pour sélectionner le type de données que le lecteur est censé revenir. Dans le code dans la question, je l'ai mis à 0, ce qui signifie que j'ai demandé "No fields" du lecteur.La modification de cet octet en 0x10 (Données utilisateur) génère à la fois les versions Linux et Windows renvoyant les mêmes données pour un secteur donné. Je ne sais toujours pas pourquoi Linux a retourné des données dans le tampon même avec la forme originale de la BDC.

Le bon PEH pour la commande CD READ, lors de la lecture d'un secteur CD-DA à LBA 1, devrait donc ressembler à ceci:

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

Votre code est BTW va toujours à l'échec dans Windows 7 en raison de lire les restrictions de sécurité. Vous pouvez envoyer la plupart des commandes SCSI avec l'API DeviceIOControl, mais quand il s'agit de données ou de lectures brutes, vous devez utiliser la méthode SPTI prescrite pour lire un secteur ou Windows 7 le bloquera, avec ou sans privilèges d'administrateur, donc FYI, vous pouvez Ne faites plus cela de la manière SCSI si vous voulez plus de compatibilité! Voici à quoi ressemble le SPTI prescrit, et heureusement, c'est beaucoup moins de code que de construire un paquet de commande SCSI en utilisant OxBE ou READ10 (ce que vous auriez dû utiliser si vous vouliez simplement les données d'un secteur de données comme il est une commande SCSI-1, et non 0xBE qui est moins compatible):

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) 
    ); 

donc en bref, google autour de la commande IOCTL_CDROM_RAW_READ. L'extrait de code ci-dessus fonctionne pour un secteur audio et renvoie 2352 octets. Cela peut fonctionner jusqu'à Windows NT4.0 si votre appel CreateFile() est correct. Mais oui, si vous utilisez IOCTL_SCSI_PASS_THROUGH_DIRECT et essayez de construire votre propre paquet de commande SCSI 0xBE, Windows 7 le bloquera! Microsoft vous demande d'utiliser le fichier IOCTL_CDROM_RAW_READ pour les lectures brutes. Vous pouvez créer d'autres paquets de commande SCSI pour lire la table des matières, obtenir des capacités de lecteur, mais une commande de lecture sera bloquée et DeviceIoControl déclenchera une erreur "Invalid function". Apparemment, au moins pour Windows 10, mon logiciel a fonctionné à nouveau et la restriction a été supprimée, mais comme Windows 7 a une base d'installation importante, vous voudrez le faire de la manière prescrite par le SPTI ANYWAY, plus IOCTL_CDROM_RAW_READ en connaît un peu moins lire les commandes que le 0xBE universel pour les anciens lecteurs de badminton, il est donc préférable de l'utiliser quand même!