Click here to Skip to main content
15,885,890 members
Articles / Programming Languages / C#

SCSI Library in C# - Burn CDs and DVDs, Access Hard Disks, etc.

Rate me:
Please Sign up or sign in to vote.
4.77/5 (48 votes)
19 Jun 2017Ms-PL6 min read 145.3K   8.1K   146  
Ever wonder how programs like Nero work? They make their own SCSI libraries... like this!
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Helper;
using System.ComponentModel;
namespace Ata
{
	/// <summary>Represents a generic ATA device. This class should not be instantiated directly unless no subclasses exist that are appropriate.</summary>
	[Description("Represents a generic ATA device. This class should not be instantiated directly unless no subclasses exist that are appropriate.")]
	public class AtaDevice : IDisposable
	{
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private uint _LogicalSectorSize;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private int _Is48LBA = -1; //Boolean... but we need 3 states
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private int _DmaSupported = -1; //Boolean... but we need 3 states
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private bool leaveOpen;

		/// <summary>Initializes a new instance of the <see cref="AtaDevice"/> class.</summary>
		/// <param name="interface">The pass-through interface to use.</param>
		/// <param name="leaveOpen"><c>true</c> to leave the interface open, or <c>false</c> to dispose along with this object.</param>
		public AtaDevice(IAtaPassThrough @interface, bool leaveOpen) { this.leaveOpen = leaveOpen; this.Interface = @interface; }

		public byte? AdvancedPowerManagementLevel { get { var id = this.IdentifyDevice(); return id.AdvancedPowerManagementFeatureSetEnabled ? id.AdvancedPowerManagementLevelCurrent : (byte?)null; } set { if (value != null) { this.SetFeatures(0x05, value.Value, 0); } else { this.SetFeatures(0x85, 0, 0); } } }

		public byte? AutomaticAcousticManagementLevel { get { var id = this.IdentifyDevice(); return id.AutomaticAcousticManagementFeatureSetEnabled ? id.AutomaticAcousticManagementLevelCurrent : (byte?)null; } set { if (value != null) { this.SetFeatures(0x42, value.Value, 0); } else { this.SetFeatures(0xC2, 0, 0); } } }

		public void DeviceReset() { var task = new AtaTaskFile(AtaCommand.DeviceReset); this.ExecuteCommand28(ref task, BufferWithSize.Zero, AtaFlags.None, true); }

		public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); }

		public bool DmaSupported { get { if (this._DmaSupported != -1) { this._DmaSupported = this.IdentifyDevice().DmaSupported ? 1 : 0; } return this._DmaSupported != 0; } }

		protected virtual void Dispose(bool disposing) { if (disposing) { try { if (!this.leaveOpen) { this.Interface.Dispose(); } } finally { this.Interface = null; } } }

		protected void ExecuteCommand28(ref AtaTaskFile task, BufferWithSize buffer, AtaFlags flags, bool checkError) { flags &= ~AtaFlags.Command48Bit; var high = new AtaTaskFile(); this.ExecuteCommandCore(ref task, ref high, buffer, flags, checkError); }

		protected void ExecuteCommand48(ref AtaTaskFile low, ref AtaTaskFile high, BufferWithSize buffer, AtaFlags flags, bool checkError) { flags |= AtaFlags.Command48Bit; this.ExecuteCommandCore(ref low, ref high, buffer, flags, checkError); }

		protected virtual void ExecuteCommandCore(ref AtaTaskFile low, ref AtaTaskFile high, BufferWithSize buffer, AtaFlags flags, bool checkError)
		{
			this.Interface.ExecuteCommand(ref low, ref high, buffer.Address, buffer.LengthU32, this.TimeoutSeconds, flags);
			if (checkError)
			{
				if (low.Error != AtaError.None) { throw AtaException.CreateException(low.Error); }
				//if (high.Error != AtaError.None) { throw AtaException.CreateException(high.Error); }
			}
		}

		public void FlushCache() { var task = new AtaTaskFile(AtaCommand.FlushCache); this.ExecuteCommand28(ref task, BufferWithSize.Zero, AtaFlags.None, true); }

		public void FlushCacheExt() { AtaTaskFile low = new AtaTaskFile(AtaCommand.FlushCache), high = low; this.ExecuteCommand48(ref low, ref high, BufferWithSize.Zero, AtaFlags.Command48Bit, true); }

		public byte? FreeFallSensitivityLevel { get { var id = this.IdentifyDevice(); return id.FreeFallControlFeatureSetEnabled ? id.FreeFallControlSensitivityLevelCurrent : (byte?)null; } set { if (value != null) { this.SetFeatures(0x41, value.Value, 0); } else { this.SetFeatures(0xC1, 0, 0); } } }

		public DeviceIdentifier IdentifyDevice()
		{
			var task = new AtaTaskFile(AtaCommand.IdentifyDevice);
			var id = new DeviceIdentifier();
			BufferWithSize buffer;
			unsafe { buffer = new BufferWithSize((IntPtr)(&id), Marshaler.DefaultSizeOf<DeviceIdentifier>()); }
			this.ExecuteCommand28(ref task, buffer, AtaFlags.ReceiveData, true);
			return id;
		}

		[Obsolete("Do not use. This is only for viewing the data in the debugger, because manipulating the returned data has no effect. Use the appropriate Get and Set methods instead.", true)]
		private DeviceIdentifier Identifier { get { return this.IdentifyDevice(); } }

		public IAtaPassThrough Interface { get; private set; }

		public uint LogicalSectorSize { get { if (this._LogicalSectorSize == 0) { var id = this.IdentifyDevice(); this._LogicalSectorSize = id.LogicalSectorSize; } return this._LogicalSectorSize; } }

		/// <summary>The highest addressable logical block address. Note that this is one less than the number of logical blocks on the disk.</summary>
		public int NativeMaximumAddress { get { var task = new AtaTaskFile(0, 0, 0, 1 << 6, AtaCommand.ReadNativeMaxAddress, 0); this.ExecuteCommand28(ref task, BufferWithSize.Zero, AtaFlags.NoMultiple, true); return checked((int)task.LogicalBlockAddress); } }

		/// <summary>The highest addressable logical block address, in 48-bit form. Note that this is one less than the number of logical blocks on the disk.</summary>
		public long NativeMaximumAddressExt { get { var low = new AtaTaskFile(0, 0, 0, 1 << 6, AtaCommand.ReadNativeMaxAddressExt, 0); var high = new AtaTaskFile(0, 0, 0, 1 << 6, AtaCommand.ReadNativeMaxAddressExt, 0); this.ExecuteCommand48(ref low, ref high, BufferWithSize.Zero, AtaFlags.Command48Bit, true); return low.LogicalBlockAddress; } }

		public byte[] ReadDma(int lba, byte sectorCount) { var data = new byte[this.LogicalSectorSize * sectorCount]; this.ReadDma(lba, sectorCount, data, 0); return data; }

		/// <param name="sectorCount">The number of sectors to read. IMPORTANT NOTE: If zero, then this value specifies <c>0x100</c> sectors.</param>
		public void ReadDma(int lba, byte sectorCount, byte[] buffer, int bufferOffset) { unsafe { fixed (byte* pBuffer = &buffer[bufferOffset]) { this.ReadDma(lba, sectorCount, new BufferWithSize(pBuffer, buffer.Length - bufferOffset)); } } }

		/// <param name="sectorCount">The number of sectors to read. IMPORTANT NOTE: If zero, then this value specifies <c>0x100</c> sectors.</param>
		public void ReadDma(int lba, byte sectorCount, BufferWithSize buffer)
		{
			var sectorsToRead = (uint)(sectorCount == 0 ? 256 : sectorCount);
			if (sectorsToRead * this.LogicalSectorSize > buffer.LengthU32) { throw new ArgumentOutOfRangeException("buffer", buffer, "Buffer was too small."); }
			var task = new AtaTaskFile(0, sectorCount, checked((uint)lba), 1 << 6, AtaCommand.ReadDma, 0);
			this.ExecuteCommand28(ref task, buffer, AtaFlags.ReceiveData | AtaFlags.UseDma, true);
		}

		public byte[] ReadDmaExt(long lba, ushort sectorCount) { var data = new byte[this.LogicalSectorSize * sectorCount]; this.ReadDmaExt(lba, sectorCount, data, 0); return data; }

		/// <param name="sectorCount">The number of sectors to read. IMPORTANT NOTE: If zero, then this value specifies <c>0x10000</c> sectors.</param>
		public void ReadDmaExt(long lba, ushort sectorCount, byte[] buffer, int bufferOffset) { unsafe { fixed (byte* pBuffer = &buffer[bufferOffset]) { this.ReadDmaExt(lba, sectorCount, new BufferWithSize(pBuffer, buffer.Length - bufferOffset)); } } }

		/// <param name="sectorCount">The number of sectors to read. IMPORTANT NOTE: If zero, then this value specifies <c>0x10000</c> sectors.</param>
		public void ReadDmaExt(long lba, ushort sectorCount, BufferWithSize buffer)
		{
			var sectorsToRead = (uint)(sectorCount == 0 ? 256 : sectorCount);
			if (sectorsToRead * this.LogicalSectorSize > buffer.LengthU32) { throw new ArgumentOutOfRangeException("buffer", buffer, "Buffer was too small."); }
			var low = new AtaTaskFile(0, unchecked((byte)sectorCount), unchecked((uint)lba & 0x00FFFFFFU), 1 << 6, AtaCommand.ReadDmaExt, 0);
			var high = new AtaTaskFile(0, checked((byte)(sectorCount >> 8)), checked((uint)(lba >> 24)), 1 << 6, AtaCommand.ReadDmaExt, 0);
			this.ExecuteCommand48(ref low, ref high, buffer, AtaFlags.ReceiveData | AtaFlags.UseDma | AtaFlags.Command48Bit, true);
		}

		public bool ReadLookAhead { get { return this.IdentifyDevice().ReadLookAheadEnabled; } set { this.SetFeatures(value ? (byte)0xAA : (byte)0x55, 0, 0); } }

		public byte[] ReadSectors(int lba, byte sectorCount) { var data = new byte[this.LogicalSectorSize * sectorCount]; this.ReadSectors(lba, sectorCount, data, 0); return data; }

		/// <param name="sectorCount">The number of sectors to read. IMPORTANT NOTE: If zero, then this value specifies <c>0x100</c> sectors.</param>
		public void ReadSectors(int lba, byte sectorCount, byte[] buffer, int bufferOffset) { unsafe { fixed (byte* pBuffer = &buffer[bufferOffset]) { this.ReadSectors(lba, sectorCount, new BufferWithSize(pBuffer, buffer.Length - bufferOffset)); } } }

		/// <param name="sectorCount">The number of sectors to read. IMPORTANT NOTE: If zero, then this value specifies <c>0x100</c> sectors.</param>
		public void ReadSectors(int lba, byte sectorCount, BufferWithSize buffer)
		{
			var sectorsToRead = (uint)(sectorCount == 0 ? 256 : sectorCount);
			if (sectorsToRead * this.LogicalSectorSize > buffer.LengthU32) { throw new ArgumentOutOfRangeException("buffer", buffer, "Buffer was too small."); }
			var task = new AtaTaskFile(0, sectorCount, checked((uint)lba), 1 << 6, AtaCommand.ReadSectors, 0);
			this.ExecuteCommand28(ref task, buffer, AtaFlags.ReceiveData, true);
		}

		public byte[] ReadSectorsExt(long lba, ushort sectorCount) { var data = new byte[this.LogicalSectorSize * sectorCount]; this.ReadSectorsExt(lba, sectorCount, data, 0); return data; }

		/// <param name="sectorCount">The number of sectors to read. IMPORTANT NOTE: If zero, then this value specifies <c>0x10000</c> sectors.</param>
		public void ReadSectorsExt(long lba, ushort sectorCount, byte[] buffer, int bufferOffset) { unsafe { fixed (byte* pBuffer = &buffer[bufferOffset]) { this.ReadSectorsExt(lba, sectorCount, new BufferWithSize(pBuffer, buffer.Length - bufferOffset)); } } }

		/// <param name="sectorCount">The number of sectors to read. IMPORTANT NOTE: If zero, then this value specifies <c>0x10000</c> sectors.</param>
		public void ReadSectorsExt(long lba, ushort sectorCount, BufferWithSize buffer)
		{
			var sectorsToRead = (uint)(sectorCount == 0 ? 256 : sectorCount);
			if (sectorsToRead * this.LogicalSectorSize > buffer.LengthU32) { throw new ArgumentOutOfRangeException("buffer", buffer, "Buffer was too small."); }
			var low = new AtaTaskFile(0, unchecked((byte)sectorCount), unchecked((uint)lba & 0x00FFFFFFU), 1 << 6, AtaCommand.ReadSectorsExt, 0);
			var high = new AtaTaskFile(0, checked((byte)(sectorCount >> 8)), checked((uint)(lba >> 24)), 1 << 6, AtaCommand.ReadSectorsExt, 0);
			this.ExecuteCommand48(ref low, ref high, buffer, AtaFlags.ReceiveData | AtaFlags.Command48Bit, true);
		}

		private void SetFeatures(byte feature, byte count, int lba) { var task = new AtaTaskFile(feature, count, checked((uint)lba), 0, AtaCommand.SetFeatures, 0); this.ExecuteCommand28(ref task, BufferWithSize.Zero, AtaFlags.None, true); }

		public bool Supports48BitLogicalBlockAddressing { get { if (this._Is48LBA != -1) { this._Is48LBA = this.IdentifyDevice().Command48BitAddressFeatureSetEnabled ? 1 : 0; } return this._Is48LBA != 0; } }

		[CLSCompliant(false)]
		public virtual uint TimeoutSeconds { get { return 10; } }
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions