// /*************************************************************************** // Aaru Data Preservation Suite // ---------------------------------------------------------------------------- // // Filename : Command.cs // Author(s) : Natalia Portillo // // Component : Windows direct device access. // // --[ Description ] ---------------------------------------------------------- // // Contains a high level representation of the Windows syscalls used to // directly interface devices. // // --[ License ] -------------------------------------------------------------- // // This library is free software; you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation; either version 2.1 of the // License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, see . // // ---------------------------------------------------------------------------- // Copyright © 2011-2021 Natalia Portillo // ****************************************************************************/ using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using Aaru.Decoders.ATA; using Microsoft.Win32.SafeHandles; namespace Aaru.Devices.Windows { [SuppressMessage("ReSharper", "UnusedParameter.Global")] internal static class Command { /// Sends a SCSI command /// 0 if no error occurred, otherwise, errno /// File handle /// SCSI CDB /// Buffer for SCSI command response /// Buffer with the SCSI sense /// Timeout in seconds /// SCSI command transfer direction /// Time it took to execute the command in milliseconds /// /// True if SCSI error returned non-OK status and contains SCSI /// sense /// internal static int SendScsiCommand(SafeFileHandle fd, byte[] cdb, ref byte[] buffer, out byte[] senseBuffer, uint timeout, ScsiIoctlDirection direction, out double duration, out bool sense) { senseBuffer = null; duration = 0; sense = false; if(buffer == null) return -1; var sptdSb = new ScsiPassThroughDirectAndSenseBuffer { SenseBuf = new byte[32], sptd = new ScsiPassThroughDirect { Cdb = new byte[16], CdbLength = (byte)cdb.Length, SenseInfoLength = 32, DataIn = direction, DataTransferLength = (uint)buffer.Length, TimeOutValue = timeout, DataBuffer = Marshal.AllocHGlobal(buffer.Length) } }; sptdSb.sptd.Length = (ushort)Marshal.SizeOf(sptdSb.sptd); sptdSb.sptd.SenseInfoOffset = (uint)Marshal.SizeOf(sptdSb.sptd); Array.Copy(cdb, sptdSb.sptd.Cdb, cdb.Length); uint k = 0; int error = 0; Marshal.Copy(buffer, 0, sptdSb.sptd.DataBuffer, buffer.Length); DateTime start = DateTime.Now; bool hasError = !Extern.DeviceIoControlScsi(fd, WindowsIoctl.IoctlScsiPassThroughDirect, ref sptdSb, (uint)Marshal.SizeOf(sptdSb), ref sptdSb, (uint)Marshal.SizeOf(sptdSb), ref k, IntPtr.Zero); DateTime end = DateTime.Now; if(hasError) error = Marshal.GetLastWin32Error(); Marshal.Copy(sptdSb.sptd.DataBuffer, buffer, 0, buffer.Length); sense |= sptdSb.sptd.ScsiStatus != 0; senseBuffer = new byte[64]; Array.Copy(sptdSb.SenseBuf, senseBuffer, 32); duration = (end - start).TotalMilliseconds; Marshal.FreeHGlobal(sptdSb.sptd.DataBuffer); return error; } /// Sends an ATA command in CHS mode /// 0 if no error occurred, otherwise, errno /// File handle /// Buffer for SCSI command response /// Timeout in seconds /// Time it took to execute the command in milliseconds /// True if ATA error returned non-OK status /// Registers to send to drive /// Registers returned by drive /// ATA protocol to use internal static int SendAtaCommand(SafeFileHandle fd, AtaRegistersChs registers, out AtaErrorRegistersChs errorRegisters, AtaProtocol protocol, ref byte[] buffer, uint timeout, out double duration, out bool sense) { duration = 0; sense = false; errorRegisters = new AtaErrorRegistersChs(); if(buffer == null) return -1; var aptd = new AtaPassThroughDirect { TimeOutValue = timeout, DataBuffer = Marshal.AllocHGlobal(buffer.Length), Length = (ushort)Marshal.SizeOf(typeof(AtaPassThroughDirect)), DataTransferLength = (uint)buffer.Length, PreviousTaskFile = new AtaTaskFile(), CurrentTaskFile = new AtaTaskFile { Command = registers.Command, CylinderHigh = registers.CylinderHigh, CylinderLow = registers.CylinderLow, DeviceHead = registers.DeviceHead, Features = registers.Feature, SectorCount = registers.SectorCount, SectorNumber = registers.Sector } }; switch(protocol) { case AtaProtocol.PioIn: case AtaProtocol.UDmaIn: case AtaProtocol.Dma: aptd.AtaFlags = AtaFlags.DataIn; break; case AtaProtocol.PioOut: case AtaProtocol.UDmaOut: aptd.AtaFlags = AtaFlags.DataOut; break; } switch(protocol) { case AtaProtocol.Dma: case AtaProtocol.DmaQueued: case AtaProtocol.FpDma: case AtaProtocol.UDmaIn: case AtaProtocol.UDmaOut: aptd.AtaFlags |= AtaFlags.Dma; break; } // Unknown if needed aptd.AtaFlags |= AtaFlags.DrdyRequired; uint k = 0; int error = 0; Marshal.Copy(buffer, 0, aptd.DataBuffer, buffer.Length); DateTime start = DateTime.Now; sense = !Extern.DeviceIoControlAta(fd, WindowsIoctl.IoctlAtaPassThroughDirect, ref aptd, (uint)Marshal.SizeOf(aptd), ref aptd, (uint)Marshal.SizeOf(aptd), ref k, IntPtr.Zero); DateTime end = DateTime.Now; if(sense) error = Marshal.GetLastWin32Error(); Marshal.Copy(aptd.DataBuffer, buffer, 0, buffer.Length); duration = (end - start).TotalMilliseconds; errorRegisters.CylinderHigh = aptd.CurrentTaskFile.CylinderHigh; errorRegisters.CylinderLow = aptd.CurrentTaskFile.CylinderLow; errorRegisters.DeviceHead = aptd.CurrentTaskFile.DeviceHead; errorRegisters.Error = aptd.CurrentTaskFile.Error; errorRegisters.Sector = aptd.CurrentTaskFile.SectorNumber; errorRegisters.SectorCount = aptd.CurrentTaskFile.SectorCount; errorRegisters.Status = aptd.CurrentTaskFile.Status; sense = errorRegisters.Error != 0 || (errorRegisters.Status & 0xA5) != 0; Marshal.FreeHGlobal(aptd.DataBuffer); return error; } /// Sends an ATA command in 28-bit LBA mode /// 0 if no error occurred, otherwise, errno /// File handle /// Buffer for SCSI command response /// Timeout in seconds /// Time it took to execute the command in milliseconds /// True if ATA error returned non-OK status /// Registers to send to drive /// Registers returned by drive /// ATA protocol to use internal static int SendAtaCommand(SafeFileHandle fd, AtaRegistersLba28 registers, out AtaErrorRegistersLba28 errorRegisters, AtaProtocol protocol, ref byte[] buffer, uint timeout, out double duration, out bool sense) { duration = 0; sense = false; errorRegisters = new AtaErrorRegistersLba28(); if(buffer == null) return -1; var aptd = new AtaPassThroughDirect { TimeOutValue = timeout, DataBuffer = Marshal.AllocHGlobal(buffer.Length), Length = (ushort)Marshal.SizeOf(typeof(AtaPassThroughDirect)), DataTransferLength = (uint)buffer.Length, PreviousTaskFile = new AtaTaskFile(), CurrentTaskFile = new AtaTaskFile { Command = registers.Command, CylinderHigh = registers.LbaHigh, CylinderLow = registers.LbaMid, DeviceHead = registers.DeviceHead, Features = registers.Feature, SectorCount = registers.SectorCount, SectorNumber = registers.LbaLow } }; switch(protocol) { case AtaProtocol.PioIn: case AtaProtocol.UDmaIn: case AtaProtocol.Dma: aptd.AtaFlags = AtaFlags.DataIn; break; case AtaProtocol.PioOut: case AtaProtocol.UDmaOut: aptd.AtaFlags = AtaFlags.DataOut; break; } switch(protocol) { case AtaProtocol.Dma: case AtaProtocol.DmaQueued: case AtaProtocol.FpDma: case AtaProtocol.UDmaIn: case AtaProtocol.UDmaOut: aptd.AtaFlags |= AtaFlags.Dma; break; } // Unknown if needed aptd.AtaFlags |= AtaFlags.DrdyRequired; uint k = 0; int error = 0; Marshal.Copy(buffer, 0, aptd.DataBuffer, buffer.Length); DateTime start = DateTime.Now; sense = !Extern.DeviceIoControlAta(fd, WindowsIoctl.IoctlAtaPassThroughDirect, ref aptd, (uint)Marshal.SizeOf(aptd), ref aptd, (uint)Marshal.SizeOf(aptd), ref k, IntPtr.Zero); DateTime end = DateTime.Now; if(sense) error = Marshal.GetLastWin32Error(); Marshal.Copy(aptd.DataBuffer, buffer, 0, buffer.Length); duration = (end - start).TotalMilliseconds; errorRegisters.LbaHigh = aptd.CurrentTaskFile.CylinderHigh; errorRegisters.LbaMid = aptd.CurrentTaskFile.CylinderLow; errorRegisters.DeviceHead = aptd.CurrentTaskFile.DeviceHead; errorRegisters.Error = aptd.CurrentTaskFile.Error; errorRegisters.LbaLow = aptd.CurrentTaskFile.SectorNumber; errorRegisters.SectorCount = aptd.CurrentTaskFile.SectorCount; errorRegisters.Status = aptd.CurrentTaskFile.Status; sense = errorRegisters.Error != 0 || (errorRegisters.Status & 0xA5) != 0; Marshal.FreeHGlobal(aptd.DataBuffer); return error; } /// Sends an ATA command in 48-bit LBA mode /// 0 if no error occurred, otherwise, errno /// File handle /// Buffer for SCSI command response /// Timeout in seconds /// Time it took to execute the command in milliseconds /// True if ATA error returned non-OK status /// Registers to send to drive /// Registers returned by drive /// ATA protocol to use internal static int SendAtaCommand(SafeFileHandle fd, AtaRegistersLba48 registers, out AtaErrorRegistersLba48 errorRegisters, AtaProtocol protocol, ref byte[] buffer, uint timeout, out double duration, out bool sense) { duration = 0; sense = false; errorRegisters = new AtaErrorRegistersLba48(); if(buffer == null) return -1; var aptd = new AtaPassThroughDirect { TimeOutValue = timeout, DataBuffer = Marshal.AllocHGlobal(buffer.Length), Length = (ushort)Marshal.SizeOf(typeof(AtaPassThroughDirect)), DataTransferLength = (uint)buffer.Length, PreviousTaskFile = new AtaTaskFile { CylinderHigh = registers.LbaHighPrevious, CylinderLow = registers.LbaMidPrevious, Features = (byte)((registers.Feature & 0xFF00) >> 8), SectorCount = (byte)((registers.SectorCount & 0xFF00) >> 8), SectorNumber = registers.LbaLowPrevious }, CurrentTaskFile = new AtaTaskFile { Command = registers.Command, CylinderHigh = registers.LbaHighCurrent, CylinderLow = registers.LbaMidCurrent, DeviceHead = registers.DeviceHead, Features = (byte)(registers.Feature & 0xFF), SectorCount = (byte)(registers.SectorCount & 0xFF), SectorNumber = registers.LbaLowCurrent } }; switch(protocol) { case AtaProtocol.PioIn: case AtaProtocol.UDmaIn: case AtaProtocol.Dma: aptd.AtaFlags = AtaFlags.DataIn; break; case AtaProtocol.PioOut: case AtaProtocol.UDmaOut: aptd.AtaFlags = AtaFlags.DataOut; break; } switch(protocol) { case AtaProtocol.Dma: case AtaProtocol.DmaQueued: case AtaProtocol.FpDma: case AtaProtocol.UDmaIn: case AtaProtocol.UDmaOut: aptd.AtaFlags |= AtaFlags.Dma; break; } aptd.AtaFlags |= AtaFlags.ExtendedCommand; // Unknown if needed aptd.AtaFlags |= AtaFlags.DrdyRequired; uint k = 0; int error = 0; Marshal.Copy(buffer, 0, aptd.DataBuffer, buffer.Length); DateTime start = DateTime.Now; sense = !Extern.DeviceIoControlAta(fd, WindowsIoctl.IoctlAtaPassThroughDirect, ref aptd, (uint)Marshal.SizeOf(aptd), ref aptd, (uint)Marshal.SizeOf(aptd), ref k, IntPtr.Zero); DateTime end = DateTime.Now; if(sense) error = Marshal.GetLastWin32Error(); Marshal.Copy(aptd.DataBuffer, buffer, 0, buffer.Length); duration = (end - start).TotalMilliseconds; errorRegisters.SectorCount = (ushort)((aptd.PreviousTaskFile.SectorCount << 8) + aptd.CurrentTaskFile.SectorCount); errorRegisters.LbaLowPrevious = aptd.PreviousTaskFile.SectorNumber; errorRegisters.LbaMidPrevious = aptd.PreviousTaskFile.CylinderLow; errorRegisters.LbaHighPrevious = aptd.PreviousTaskFile.CylinderHigh; errorRegisters.LbaLowCurrent = aptd.CurrentTaskFile.SectorNumber; errorRegisters.LbaMidCurrent = aptd.CurrentTaskFile.CylinderLow; errorRegisters.LbaHighCurrent = aptd.CurrentTaskFile.CylinderHigh; errorRegisters.DeviceHead = aptd.CurrentTaskFile.DeviceHead; errorRegisters.Error = aptd.CurrentTaskFile.Error; errorRegisters.Status = aptd.CurrentTaskFile.Status; sense = errorRegisters.Error != 0 || (errorRegisters.Status & 0xA5) != 0; Marshal.FreeHGlobal(aptd.DataBuffer); return error; } /// Returns true if the specified handle is controlled by a SFFDISK (aka SDHCI) driver /// Device handle /// true if SDHCI, false otherwise internal static bool IsSdhci(SafeFileHandle fd) { var queryData1 = new SffdiskQueryDeviceProtocolData(); queryData1.size = (ushort)Marshal.SizeOf(queryData1); Extern.DeviceIoControl(fd, WindowsIoctl.IoctlSffdiskQueryDeviceProtocol, IntPtr.Zero, 0, ref queryData1, queryData1.size, out _, IntPtr.Zero); return queryData1.protocolGuid.Equals(Consts.GuidSffProtocolSd) || queryData1.protocolGuid.Equals(Consts.GuidSffProtocolMmc); } /// Sends a MMC/SD command /// The result of the command. /// File handle /// MMC/SD opcode /// Buffer for MMC/SD command response /// Timeout in seconds /// Time it took to execute the command in milliseconds /// True if MMC/SD returned non-OK status /// True if data is sent from host to card /// True if command should be preceded with CMD55 /// Flags indicating kind and place of response /// How many blocks to transfer /// Command argument /// Response registers /// Size of block in bytes internal static int SendMmcCommand(SafeFileHandle fd, MmcCommands command, bool write, bool isApplication, MmcFlags flags, uint argument, uint blockSize, uint blocks, ref byte[] buffer, out uint[] response, out double duration, out bool sense, uint timeout = 0) { var commandData = new SffdiskDeviceCommandData(); var commandDescriptor = new SdCmdDescriptor(); commandData.size = (ushort)Marshal.SizeOf(commandData); commandData.command = SffdiskDcmd.DeviceCommand; commandData.protocolArgumentSize = (ushort)Marshal.SizeOf(commandDescriptor); commandData.deviceDataBufferSize = blockSize * blocks; commandDescriptor.commandCode = (byte)command; commandDescriptor.cmdClass = isApplication ? SdCommandClass.AppCmd : SdCommandClass.Standard; commandDescriptor.transferDirection = write ? SdTransferDirection.Write : SdTransferDirection.Read; commandDescriptor.transferType = flags.HasFlag(MmcFlags.CommandAdtc) ? command == MmcCommands.ReadMultipleBlock ? SdTransferType.MultiBlock : SdTransferType.SingleBlock : SdTransferType.CmdOnly; commandDescriptor.responseType = 0; if(flags.HasFlag(MmcFlags.ResponseR1) || flags.HasFlag(MmcFlags.ResponseSpiR1)) commandDescriptor.responseType = SdResponseType.R1; if(flags.HasFlag(MmcFlags.ResponseR1B) || flags.HasFlag(MmcFlags.ResponseSpiR1B)) commandDescriptor.responseType = SdResponseType.R1b; if(flags.HasFlag(MmcFlags.ResponseR2) || flags.HasFlag(MmcFlags.ResponseSpiR2)) commandDescriptor.responseType = SdResponseType.R2; if(flags.HasFlag(MmcFlags.ResponseR3) || flags.HasFlag(MmcFlags.ResponseSpiR3)) commandDescriptor.responseType = SdResponseType.R3; if(flags.HasFlag(MmcFlags.ResponseR4) || flags.HasFlag(MmcFlags.ResponseSpiR4)) commandDescriptor.responseType = SdResponseType.R4; if(flags.HasFlag(MmcFlags.ResponseR5) || flags.HasFlag(MmcFlags.ResponseSpiR5)) commandDescriptor.responseType = SdResponseType.R5; if(flags.HasFlag(MmcFlags.ResponseR6)) commandDescriptor.responseType = SdResponseType.R6; byte[] commandB = new byte[commandData.size + commandData.protocolArgumentSize + commandData.deviceDataBufferSize]; Array.Copy(buffer, 0, commandB, commandData.size + commandData.protocolArgumentSize, buffer.Length); IntPtr hBuf = Marshal.AllocHGlobal(commandB.Length); Marshal.StructureToPtr(commandData, hBuf, true); var descriptorOffset = IntPtr.Add(hBuf, commandData.size); Marshal.StructureToPtr(commandDescriptor, descriptorOffset, true); Marshal.Copy(hBuf, commandB, 0, commandB.Length); Marshal.FreeHGlobal(hBuf); int error = 0; DateTime start = DateTime.Now; sense = !Extern.DeviceIoControl(fd, WindowsIoctl.IoctlSffdiskDeviceCommand, commandB, (uint)commandB.Length, commandB, (uint)commandB.Length, out _, IntPtr.Zero); DateTime end = DateTime.Now; if(sense) error = Marshal.GetLastWin32Error(); buffer = new byte[blockSize * blocks]; Buffer.BlockCopy(commandB, commandB.Length - buffer.Length, buffer, 0, buffer.Length); response = new uint[4]; duration = (end - start).TotalMilliseconds; return error; } internal static int SendMultipleMmcCommands(SafeFileHandle fd, Device.MmcSingleCommand[] commands, out double duration, out bool sense, uint timeout = 0) { int error = 0; duration = 0; sense = false; if(commands.Length == 3 && commands[0].command == MmcCommands.SetBlocklen && commands[1].command == MmcCommands.ReadMultipleBlock && commands[2].command == MmcCommands.StopTransmission) return SendMmcCommand(fd, commands[1].command, commands[1].write, commands[1].isApplication, commands[1].flags, commands[1].argument, commands[1].blockSize, commands[1].blocks, ref commands[1].buffer, out commands[1].response, out duration, out sense, timeout); foreach(Device.MmcSingleCommand command in commands) { int singleError = SendMmcCommand(fd, command.command, command.write, command.isApplication, command.flags, command.argument, command.blockSize, command.blocks, ref command.buffer, out command.response, out double cmdDuration, out bool cmdSense, timeout); if(error == 0 && singleError != 0) error = singleError; duration += cmdDuration; if(cmdSense) sense = true; } return error; } internal static int ReOpen(string devicePath, SafeFileHandle fd, out object newFd) { Extern.CloseHandle(fd); newFd = Extern.CreateFile(devicePath, FileAccess.GenericRead | FileAccess.GenericWrite, FileShare.Read | FileShare.Write, IntPtr.Zero, FileMode.OpenExisting, FileAttributes.Normal, IntPtr.Zero); return ((SafeFileHandle)newFd).IsInvalid ? Marshal.GetLastWin32Error() : 0; } internal static int BufferedOsRead(SafeFileHandle fd, out byte[] buffer, long offset, uint length, out double duration) { buffer = new byte[length]; DateTime start = DateTime.Now; bool sense = !Extern.SetFilePointerEx(fd, offset, out _, MoveMethod.Begin); DateTime end = DateTime.Now; if(sense) { duration = (end - start).TotalMilliseconds; return Marshal.GetLastWin32Error(); } sense = !Extern.ReadFile(fd, buffer, length, out _, IntPtr.Zero); end = DateTime.Now; duration = (end - start).TotalMilliseconds; return sense ? Marshal.GetLastWin32Error() : 0; } } }