Files
Aaru/Aaru.Devices/Windows/Command.cs

693 lines
27 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Command.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// 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 <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2025 Natalia Portillo
// ****************************************************************************/
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using Aaru.Decoders.ATA;
using Microsoft.Win32.SafeHandles;
namespace Aaru.Devices.Windows;
[SuppressMessage("ReSharper", "UnusedParameter.Global")]
partial class Device
{
/// <inheritdoc />
public override unsafe int SendScsiCommand(Span<byte> cdb, ref byte[] buffer, uint timeout, ScsiDirection direction,
out double duration, out bool sense)
{
// We need a timeout
if(timeout == 0) timeout = Timeout > 0 ? Timeout : 15;
duration = 0;
sense = false;
if(buffer == null) return -1;
ScsiIoctlDirection dir = direction switch
{
ScsiDirection.In => ScsiIoctlDirection.In,
ScsiDirection.Out => ScsiIoctlDirection.Out,
_ => ScsiIoctlDirection.Unspecified
};
EnsureCapacityAligned((nuint)buffer.Length);
var sptdSb = new ScsiPassThroughDirectAndSenseBuffer
{
SenseBuf = new byte[32],
sptd = new ScsiPassThroughDirect
{
Cdb = new byte[16],
CdbLength = (byte)cdb.Length,
SenseInfoLength = 32,
DataIn = dir,
DataTransferLength = (uint)buffer.Length,
TimeOutValue = timeout,
DataBuffer = (IntPtr)_nativeBuffer
}
};
sptdSb.sptd.Length = (ushort)Marshal.SizeOf(sptdSb.sptd);
sptdSb.sptd.SenseInfoOffset = (uint)Marshal.SizeOf(sptdSb.sptd);
cdb.CopyTo(sptdSb.sptd.Cdb);
// OUT or BiDir → precopy
if(direction != ScsiDirection.In) buffer.AsSpan().CopyTo(new Span<byte>((void*)_nativeBuffer, buffer.Length));
uint k = 0;
var error = 0;
var cmdStopwatch = new Stopwatch();
cmdStopwatch.Start();
bool hasError = !Extern.DeviceIoControlScsi(_fileHandle,
WindowsIoctl.IoctlScsiPassThroughDirect,
ref sptdSb,
(uint)Marshal.SizeOf(sptdSb),
ref sptdSb,
(uint)Marshal.SizeOf(sptdSb),
ref k,
IntPtr.Zero);
cmdStopwatch.Stop();
if(hasError) error = Marshal.GetLastWin32Error();
if(direction != ScsiDirection.Out) new Span<byte>((void*)_nativeBuffer, buffer.Length).CopyTo(buffer);
sense |= sptdSb.sptd.ScsiStatus != 0;
sptdSb.SenseBuf.AsSpan().CopyTo(SenseBuffer);
duration = cmdStopwatch.Elapsed.TotalMilliseconds;
return error;
}
/// <inheritdoc />
public override int SendAtaCommand(AtaRegistersChs registers, out AtaErrorRegistersChs errorRegisters,
AtaProtocol protocol, AtaTransferRegister transferRegister, ref byte[] buffer,
uint timeout, bool transferBlocks, out double duration, out bool sense)
{
// We need a timeout
if(timeout == 0) timeout = Timeout > 0 ? Timeout : 15;
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
}
};
aptd.AtaFlags = protocol switch
{
AtaProtocol.PioIn or AtaProtocol.UDmaIn or AtaProtocol.Dma => AtaFlags.DataIn,
AtaProtocol.PioOut or AtaProtocol.UDmaOut => AtaFlags.DataOut,
_ => aptd.AtaFlags
};
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;
var error = 0;
Marshal.Copy(buffer, 0, aptd.DataBuffer, buffer.Length);
var cmdStopwatch = new Stopwatch();
cmdStopwatch.Start();
sense = !Extern.DeviceIoControlAta(_fileHandle,
WindowsIoctl.IoctlAtaPassThroughDirect,
ref aptd,
(uint)Marshal.SizeOf(aptd),
ref aptd,
(uint)Marshal.SizeOf(aptd),
ref k,
IntPtr.Zero);
cmdStopwatch.Stop();
if(sense) error = Marshal.GetLastWin32Error();
Marshal.Copy(aptd.DataBuffer, buffer, 0, buffer.Length);
duration = cmdStopwatch.Elapsed.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;
}
/// <inheritdoc />
public override int SendAtaCommand(AtaRegistersLba28 registers, out AtaErrorRegistersLba28 errorRegisters,
AtaProtocol protocol, AtaTransferRegister transferRegister, ref byte[] buffer,
uint timeout, bool transferBlocks, out double duration, out bool sense)
{
// We need a timeout
if(timeout == 0) timeout = Timeout > 0 ? Timeout : 15;
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
}
};
aptd.AtaFlags = protocol switch
{
AtaProtocol.PioIn or AtaProtocol.UDmaIn or AtaProtocol.Dma => AtaFlags.DataIn,
AtaProtocol.PioOut or AtaProtocol.UDmaOut => AtaFlags.DataOut,
_ => aptd.AtaFlags
};
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;
var error = 0;
Marshal.Copy(buffer, 0, aptd.DataBuffer, buffer.Length);
var cmdStopwatch = new Stopwatch();
cmdStopwatch.Start();
sense = !Extern.DeviceIoControlAta(_fileHandle,
WindowsIoctl.IoctlAtaPassThroughDirect,
ref aptd,
(uint)Marshal.SizeOf(aptd),
ref aptd,
(uint)Marshal.SizeOf(aptd),
ref k,
IntPtr.Zero);
cmdStopwatch.Stop();
if(sense) error = Marshal.GetLastWin32Error();
Marshal.Copy(aptd.DataBuffer, buffer, 0, buffer.Length);
duration = cmdStopwatch.Elapsed.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;
}
/// <inheritdoc />
public override int SendAtaCommand(AtaRegistersLba48 registers, out AtaErrorRegistersLba48 errorRegisters,
AtaProtocol protocol, AtaTransferRegister transferRegister, ref byte[] buffer,
uint timeout, bool transferBlocks, out double duration, out bool sense)
{
// We need a timeout
if(timeout == 0) timeout = Timeout > 0 ? Timeout : 15;
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
}
};
aptd.AtaFlags = protocol switch
{
AtaProtocol.PioIn or AtaProtocol.UDmaIn or AtaProtocol.Dma => AtaFlags.DataIn,
AtaProtocol.PioOut or AtaProtocol.UDmaOut => AtaFlags.DataOut,
_ => aptd.AtaFlags
};
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;
var error = 0;
Marshal.Copy(buffer, 0, aptd.DataBuffer, buffer.Length);
var cmdStopwatch = new Stopwatch();
cmdStopwatch.Start();
sense = !Extern.DeviceIoControlAta(_fileHandle,
WindowsIoctl.IoctlAtaPassThroughDirect,
ref aptd,
(uint)Marshal.SizeOf(aptd),
ref aptd,
(uint)Marshal.SizeOf(aptd),
ref k,
IntPtr.Zero);
cmdStopwatch.Stop();
if(sense) error = Marshal.GetLastWin32Error();
Marshal.Copy(aptd.DataBuffer, buffer, 0, buffer.Length);
duration = cmdStopwatch.Elapsed.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;
}
/// <summary>Returns true if the specified handle is controlled by a SFFDISK (aka SDHCI) driver</summary>
/// <param name="fd">Device handle</param>
/// <returns><c>true</c> if SDHCI, false otherwise</returns>
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);
}
/// <inheritdoc />
public override int SendMmcCommand(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 = 15)
{
var cmdStopwatch = new Stopwatch();
switch(command)
{
case MmcCommands.SendCid when CachedCid != null:
{
cmdStopwatch.Restart();
buffer = new byte[CachedCid.Length];
Array.Copy(CachedCid, buffer, buffer.Length);
response = new uint[4];
sense = false;
cmdStopwatch.Stop();
duration = cmdStopwatch.Elapsed.TotalMilliseconds;
return 0;
}
case MmcCommands.SendCsd when CachedCid != null:
{
cmdStopwatch.Restart();
buffer = new byte[CachedCsd.Length];
Array.Copy(CachedCsd, buffer, buffer.Length);
response = new uint[4];
sense = false;
cmdStopwatch.Stop();
duration = cmdStopwatch.Elapsed.TotalMilliseconds;
return 0;
}
case (MmcCommands)SecureDigitalCommands.SendScr when CachedScr != null:
{
cmdStopwatch.Restart();
buffer = new byte[CachedScr.Length];
Array.Copy(CachedScr, buffer, buffer.Length);
response = new uint[4];
sense = false;
cmdStopwatch.Stop();
duration = cmdStopwatch.Elapsed.TotalMilliseconds;
return 0;
}
case (MmcCommands)SecureDigitalCommands.SendOperatingCondition when CachedOcr != null:
case MmcCommands.SendOpCond when CachedOcr != null:
{
cmdStopwatch.Restart();
buffer = new byte[CachedOcr.Length];
Array.Copy(CachedOcr, buffer, buffer.Length);
response = new uint[4];
sense = false;
cmdStopwatch.Stop();
duration = cmdStopwatch.Elapsed.TotalMilliseconds;
return 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;
var 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);
var error = 0;
cmdStopwatch.Restart();
sense = !Extern.DeviceIoControl(_fileHandle,
WindowsIoctl.IoctlSffdiskDeviceCommand,
commandB,
(uint)commandB.Length,
commandB,
(uint)commandB.Length,
out _,
IntPtr.Zero);
cmdStopwatch.Stop();
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 = cmdStopwatch.Elapsed.TotalMilliseconds;
return error;
}
/// <inheritdoc />
public override int SendMultipleMmcCommands(MmcSingleCommand[] commands, out double duration, out bool sense,
uint timeout = 15)
{
// We need a timeout
if(timeout == 0) timeout = Timeout > 0 ? Timeout : 15;
var 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(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(MmcSingleCommand command in commands)
{
int singleError = SendMmcCommand(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;
}
/// <inheritdoc />
public override bool ReOpen()
{
Extern.CloseHandle(_fileHandle);
SafeFileHandle newFd = Extern.CreateFile(DevicePath,
FileAccess.GenericRead | FileAccess.GenericWrite,
FileShare.Read | FileShare.Write,
IntPtr.Zero,
FileMode.OpenExisting,
FileAttributes.Normal,
IntPtr.Zero);
if(newFd.IsInvalid)
{
LastError = Marshal.GetLastWin32Error();
Error = true;
return true;
}
_fileHandle = newFd;
return false;
}
/// <inheritdoc />
public override bool BufferedOsRead(out byte[] buffer, long offset, uint length, out double duration)
{
buffer = new byte[length];
var cmdStopwatch = new Stopwatch();
cmdStopwatch.Start();
bool sense = !Extern.SetFilePointerEx(_fileHandle, offset, out _, MoveMethod.Begin);
cmdStopwatch.Stop();
if(sense)
{
cmdStopwatch.Stop();
duration = cmdStopwatch.Elapsed.TotalMilliseconds;
LastError = Marshal.GetLastWin32Error();
Error = true;
return true;
}
sense = !Extern.ReadFile(_fileHandle, buffer, length, out _, IntPtr.Zero);
cmdStopwatch.Stop();
duration = cmdStopwatch.Elapsed.TotalMilliseconds;
if(sense)
{
Error = true;
LastError = Marshal.GetLastWin32Error();
return true;
}
Error = false;
return false;
}
}