windowswinapidscsideviceiocontrol

Windows SCSI ReadCapacity16 in D


I'm attempting to send a scsi ReadCapacity16 (0x9E) to a volume on Windows using D. The CDBs are to spec and my ReadCapacity16 works on Linux and scsi Inquiries work on Windows. Only the not-inquiry calls on Windows fail to work with an "incorrect function" from the windows kernel.

Since only inquiries work, is there a trick to sending not-inquiries through the Windows kernel? Any tips on getting this to work? I've researched a couple weeks and haven't solved this.

This is an example of the CDB:

\\.\physicaldrive0 CDB buffer contents: 9e 10 00 00 00 00 00 00 - 00 00 00 00 00 20 00 00 sgio.exceptions.IoctlFailException@sgio\exceptions.d(13): ioctl error code is 1. Incorrect function.

Here is where the CDB is copied to a buffer for the DeviceIoControl call, and this is the same code path which successfully sends the Inquiry commands (but fails for readcap). Code in github pasted below:

void sgio_execute(ubyte[] cdb_buf, ubyte[] dataout_buf, ubyte[] datain_buf, ubyte[] sense_buf)
   version (Windows)
   {
      const uint SENSE_LENGTH = 196;
      ubyte[512] iobuffer = 0;
      DWORD amountTransferred = -1;
      SCSI_PASS_THROUGH_DIRECT scsiPassThrough = {0};
      scsiPassThrough.Cdb[] = 0;
      uint size = cast(uint)((cdb_buf.length <= scsiPassThrough.Cdb.length ?
                        cdb_buf.length : scsiPassThrough.Cdb.length));

      scsiPassThrough.Cdb[0..size] = cdb_buf[0..size];
      scsiPassThrough.Length             = SCSI_PASS_THROUGH_DIRECT.sizeof;
      scsiPassThrough.ScsiStatus         = 0x00;
      scsiPassThrough.TimeOutValue       = 0x40;
      scsiPassThrough.CdbLength          = cast(ubyte)(size);
      scsiPassThrough.SenseInfoOffset    = SCSI_PASS_THROUGH_DIRECT.sizeof;
      scsiPassThrough.SenseInfoLength    = SENSE_LENGTH;
      scsiPassThrough.DataIn             = SCSI_IOCTL_DATA_IN;
      scsiPassThrough.DataBuffer         = datain_buf.ptr;
      scsiPassThrough.DataTransferLength = bigEndianToNative!ushort(cast(ubyte[2]) cdb_buf[3..5]);

      int status = DeviceIoControl( m_device,
                                    IOCTL_SCSI_PASS_THROUGH_DIRECT,
                                    &scsiPassThrough,
                                    iobuffer.length, //scsiPassThrough.sizeof,
                                    &iobuffer,
                                    iobuffer.length,
                                    &amountTransferred,
                                    null);
      if (status == 0)
      {
         int errorCode = GetLastError();
         // build error message ...
         throw new IoctlFailException(exceptionMessage);
      }
   }
}

Solution

  • Reading the Windows SCSI_PASS_THROUGH_DIRECT structure documentation very closely I noticed this:

    DataTransferLength: Indicates the size in bytes of the data buffer. Many devices transfer chunks of data of predefined length. The value in DataTransferLength must be an integral multiple of this predefined, minimum length that is specified by the device. If an underrun occurs, the miniport driver must update this member to the number of bytes actually transferred.

    I changed the code to use 512 bytes for DataTransferLength, by increasing the size of datain_buffer, and the code now works just fine.