// /*************************************************************************** // The Disc Image Chef // ---------------------------------------------------------------------------- // // Filename : Modes.cs // Version : 1.0 // Author(s) : Natalia Portillo // // Component : Component // // Revision : $Revision$ // Last change by : $Author$ // Date : $Date$ // // --[ Description ] ---------------------------------------------------------- // // Description // // --[ License ] -------------------------------------------------------------- // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program 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 General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // // ---------------------------------------------------------------------------- // Copyright (C) 2011-2015 Claunia.com // ****************************************************************************/ // //$Id$ using System; using System.Text; namespace DiscImageChef.Decoders.SCSI { public static class Modes { public enum MediumTypes : byte { Default = 0x00, #region Medium Types defined in ECMA-111 for Direct-Access devices /// /// ECMA-54: 200 mm Flexible Disk Cartridge using Two-Frequency Recording at 13262 ftprad on One Side /// ECMA54 = 0x09, /// /// ECMA-59 & ANSI X3.121-1984: 200 mm Flexible Disk Cartridge using Two-Frequency Recording at 13262 ftprad on Both Sides /// ECMA59 = 0x0A, /// /// ECMA-69: 200 mm Flexible Disk Cartridge using MFM Recording at 13262 ftprad on Both Sides /// ECMA69 = 0x0B, /// /// ECMA-66: 130 mm Flexible Disk Cartridge using Two-Frequency Recording at 7958 ftprad on One Side /// ECMA66 = 0x0E, /// /// ECMA-70 & ANSI X3.125-1985: 130 mm Flexible Disk Cartridge using MFM Recording at 7958 ftprad on Both Sides; 1,9 Tracks per mm /// ECMA70 = 0x12, /// /// ECMA-78 & ANSI X3.126-1986: 130 mm Flexible Disk Cartridge using MFM Recording at 7958 ftprad on Both Sides; 3,8 Tracks per mm /// ECMA78 = 0x16, /// /// ECMA-99 & ISO 8630-1985: 130 mm Flexible Disk Cartridge using MFM Recording at 13262 ftprad on Both Sides; 3,8 Tracks per mm /// ECMA99 = 0x1A, /// /// ECMA-100 & ANSI X3.137: 90 mm Flexible Disk Cartridge using MFM Recording at 7859 ftprad on Both Sides; 5,3 Tracks per mm /// ECMA100 = 0x1E, #endregion Medium Types defined in ECMA-111 for Direct-Access devices #region Medium Types defined in SCSI-2 for Direct-Access devices /// /// Unspecified single sided flexible disk /// Unspecified_SS = 0x01, /// /// Unspecified double sided flexible disk /// Unspecified_DS = 0x02, /// /// ANSI X3.73-1980: 200 mm, 6631 ftprad, 1,9 Tracks per mm, 1 side /// X3_73 = 0x05, /// /// ANSI X3.73-1980: 200 mm, 6631 ftprad, 1,9 Tracks per mm, 2 sides /// X3_73_DS = 0x06, /// /// ANSI X3.80-1980: 130 mm, 3979 ftprad, 1,9 Tracks per mm, 1 side /// X3_82 = 0x0D, /// /// 6,3 mm tape with 12 tracks at 394 ftpmm /// Tape12 = 0x40, /// /// 6,3 mm tape with 24 tracks at 394 ftpmm /// Tape24 = 0x44, #endregion Medium Types defined in SCSI-2 for Direct-Access devices #region Medium Types defined in SCSI-3 SBC-1 for Optical devices /// /// Read-only medium /// ReadOnly = 0x01, /// /// Write-once Read-many medium /// WORM = 0x02, /// /// Erasable medium /// Erasable = 0x03, /// /// Combination of read-only and write-once medium /// RO_WORM = 0x04, /// /// Combination of read-only and erasable medium /// RO_RW = 0x05, /// /// Combination of write-once and erasable medium /// WORM_RW = 0x06, #endregion Medium Types defined in SCSI-3 SBC-1 for Optical devices #region Medium Types defined in SCSI-2 for MultiMedia devices /// /// 120 mm CD-ROM /// CDROM = 0x01, /// /// 120 mm Compact Disc Digital Audio /// CDDA = 0x02, /// /// 120 mm Compact Disc with data and audio /// MixedCD = 0x03, /// /// 80 mm CD-ROM /// CDROM_80 = 0x05, /// /// 80 mm Compact Disc Digital Audio /// CDDA_80 = 0x06, /// /// 80 mm Compact Disc with data and audio /// MixedCD_80 = 0x07 #endregion Medium Types defined in SCSI-2 for MultiMedia devices } public enum DensityType : byte { Default = 0x00, #region Density Types defined in ECMA-111 for Direct-Access devices /// /// 7958 flux transitions per radian /// Flux7958 = 0x01, /// /// 13262 flux transitions per radian /// Flux13262 = 0x02, /// /// 15916 flux transitions per radian /// Flux15916 = 0x03, #endregion Density Types defined in ECMA-111 for Direct-Access devices #region Density Types defined in ECMA-111 for Sequential-Access devices /// /// ECMA-62 & ANSI X3.22-1983: 12,7 mm 9-Track Magnetic Tape, 32 ftpmm, NRZI, 32 cpmm /// ECMA62 = 0x01, /// /// ECMA-62 & ANSI X3.39-1986: 12,7 mm 9-Track Magnetic Tape, 126 ftpmm, Phase Encoding, 63 cpmm /// ECMA62_Phase = 0x02, /// /// ECMA-62 & ANSI X3.54-1986: 12,7 mm 9-Track Magnetic Tape, 356 ftpmm, NRZI, 245 cpmm GCR /// ECMA62_GCR = 0x03, /// /// ECMA-79 & ANSI X3.116-1986: 6,30 mm Magnetic Tape Cartridge using MFM Recording at 252 ftpmm /// ECMA79 = 0x07, /// /// Draft ECMA & ANSI X3B5/87-099: 12,7 mm Magnetic Tape Cartridge using IFM Recording on 18 Tracks at 1944 ftpmm, GCR /// ECMADraft = 0x09, /// /// ECMA-46 & ANSI X3.56-1986: 6,30 mm Magnetic Tape Cartridge, Phase Encoding, 63 bpmm /// ECMA46 = 0x0B, /// /// ECMA-98: 6,30 mm Magnetic Tape Cartridge, NRZI Recording, 394 ftpmm /// ECMA98 = 0x0E, #endregion Density Types defined in ECMA-111 for Sequential-Access devices #region Density Types defined in SCSI-2 for Sequential-Access devices /// /// ANXI X3.136-1986: 6,3 mm 4 or 9-Track Magnetic Tape Cartridge, 315 bpmm, GCR /// X3_136 = 0x05, /// /// ANXI X3.157-1987: 12,7 mm 9-Track Magnetic Tape, 126 bpmm, Phase Encoding /// X3_157 = 0x06, /// /// ANXI X3.158-1987: 3,81 mm 4-Track Magnetic Tape Cassette, 315 bpmm, GCR /// X3_158 = 0x08, /// /// ANXI X3B5/86-199: 12,7 mm 22-Track Magnetic Tape Cartridge, 262 bpmm, MFM /// X3B5_86 = 0x0A, /// /// HI-TC1: 12,7 mm 24-Track Magnetic Tape Cartridge, 500 bpmm, GCR /// HiTC1 = 0x0C, /// /// HI-TC2: 12,7 mm 24-Track Magnetic Tape Cartridge, 999 bpmm, GCR /// HiTC2 = 0x0D, /// /// QIC-120: 6,3 mm 15-Track Magnetic Tape Cartridge, 394 bpmm, GCR /// QIC120 = 0x0F, /// /// QIC-150: 6,3 mm 18-Track Magnetic Tape Cartridge, 394 bpmm, GCR /// QIC150 = 0x10, /// /// QIC-320: 6,3 mm 26-Track Magnetic Tape Cartridge, 630 bpmm, GCR /// QIC320 = 0x11, /// /// QIC-1350: 6,3 mm 30-Track Magnetic Tape Cartridge, 2034 bpmm, RLL /// QIC1350 = 0x12, /// /// ANXI X3B5/88-185A: 3,81 mm Magnetic Tape Cassette, 2400 bpmm, DDS /// X3B5_88 = 0x13, /// /// ANXI X3.202-1991: 8 mm Magnetic Tape Cassette, 1703 bpmm, RLL /// X3_202 = 0x14, /// /// ECMA TC17: 8 mm Magnetic Tape Cassette, 1789 bpmm, RLL /// ECMA_TC17 = 0x15, /// /// ANXI X3.193-1990: 12,7 mm 48-Track Magnetic Tape Cartridge, 394 bpmm, MFM /// X3_193 = 0x16, /// /// ANXI X3B5/97-174: 12,7 mm 48-Track Magnetic Tape Cartridge, 1673 bpmm, MFM /// X3B5_91 = 0x17, #endregion Density Types defined in SCSI-2 for Sequential-Access devices #region Density Types defined in SCSI-2 for MultiMedia devices /// /// User data only /// User = 0x01, /// /// User data plus auxiliary data field /// UserAuxiliary = 0x02, /// /// 4-byt tag field, user data plus auxiliary data /// UserAuxiliaryTag = 0x03, /// /// Audio information only /// Audio = 0x04, #endregion Density Types defined in SCSI-2 for MultiMedia devices #region Density Types defined in SCSI-2 for Optical devices /// /// ISO/IEC 10090: 86 mm Read/Write single-sided optical disc with 12500 tracks /// ISO10090 = 0x01, /// /// 89 mm Read/Write double-sided optical disc with 12500 tracks /// D581 = 0x02, /// /// ANSI X3.212: 130 mm Read/Write double-sided optical disc with 18750 tracks /// X3_212 = 0x03, /// /// ANSI X3.191: 130 mm Write-Once double-sided optical disc with 30000 tracks /// X3_191 = 0x04, /// /// ANSI X3.214: 130 mm Write-Once double-sided optical disc with 20000 tracks /// X3_214 = 0x05, /// /// ANSI X3.211: 130 mm Write-Once double-sided optical disc with 18750 tracks /// X3_211 = 0x06, /// /// 200 mm optical disc /// D407 = 0x07, /// /// ISO/IEC 13614: 300 mm double-sided optical disc /// ISO13614 = 0x08, /// /// ANSI X3.200: 356 mm double-sided optical disc with 56350 tracks /// X3_200 = 0x09 #endregion Density Types defined in SCSI-2 for Optical devices } public struct BlockDescriptor { public DensityType Density; public ulong Blocks; public ulong BlockLength; } public struct ModeHeader { public MediumTypes MediumType; public bool WriteProtected; public BlockDescriptor[] BlockDescriptors; public byte Speed; public byte BufferedMode; public bool EBC; public bool DPOFUA; } public static ModeHeader? DecodeModeHeader6(byte[] modeResponse, PeripheralDeviceTypes deviceType) { if (modeResponse == null || modeResponse.Length < 4 || modeResponse.Length < modeResponse[0] + 1) return null; ModeHeader header = new ModeHeader(); header.MediumType = (MediumTypes)modeResponse[1]; if (modeResponse[3] > 0) { header.BlockDescriptors = new BlockDescriptor[modeResponse[3] / 8]; for (int i = 0; i < header.BlockDescriptors.Length; i++) { header.BlockDescriptors[i].Density = (DensityType)modeResponse[0 + i * 8 + 4]; header.BlockDescriptors[i].Blocks += (ulong)(modeResponse[1 + i * 8 + 4] << 16); header.BlockDescriptors[i].Blocks += (ulong)(modeResponse[2 + i * 8 + 4] << 8); header.BlockDescriptors[i].Blocks += modeResponse[3 + i * 8 + 4]; header.BlockDescriptors[i].BlockLength += (ulong)(modeResponse[5 + i * 8 + 4] << 16); header.BlockDescriptors[i].BlockLength += (ulong)(modeResponse[6 + i * 8 + 4] << 8); header.BlockDescriptors[i].BlockLength += modeResponse[7 + i * 8 + 4]; } } if (deviceType == PeripheralDeviceTypes.DirectAccess || deviceType == PeripheralDeviceTypes.MultiMediaDevice) { header.WriteProtected = ((modeResponse[2] & 0x80) == 0x80); header.DPOFUA = ((modeResponse[2] & 0x10) == 0x10); } if (deviceType == PeripheralDeviceTypes.SequentialAccess) { header.WriteProtected = ((modeResponse[2] & 0x80) == 0x80); header.Speed = (byte)(modeResponse[2] & 0x0F); header.BufferedMode = (byte)((modeResponse[2] & 0x70) >> 4); } if (deviceType == PeripheralDeviceTypes.PrinterDevice) header.BufferedMode = (byte)((modeResponse[2] & 0x70) >> 4); if (deviceType == PeripheralDeviceTypes.OpticalDevice) { header.WriteProtected = ((modeResponse[2] & 0x80) == 0x80); header.EBC = ((modeResponse[2] & 0x01) == 0x01); header.DPOFUA = ((modeResponse[2] & 0x10) == 0x10); } return header; } public static string PrettifyModeHeader6(byte[] modeResponse, PeripheralDeviceTypes deviceType) { return PrettifyModeHeader(DecodeModeHeader6(modeResponse, deviceType), deviceType); } public static string PrettifyModeHeader(ModeHeader? header, PeripheralDeviceTypes deviceType) { if (!header.HasValue) return null; StringBuilder sb = new StringBuilder(); sb.AppendLine("SCSI Mode Page 0:"); switch (deviceType) { case PeripheralDeviceTypes.DirectAccess: { if (header.Value.MediumType != MediumTypes.Default) { sb.Append("Medium is "); switch (header.Value.MediumType) { case MediumTypes.ECMA54: sb.AppendLine("ECMA-54: 200 mm Flexible Disk Cartridge using Two-Frequency Recording at 13262 ftprad on One Side"); break; case MediumTypes.ECMA59: sb.AppendLine("ECMA-59 & ANSI X3.121-1984: 200 mm Flexible Disk Cartridge using Two-Frequency Recording at 13262 ftprad on Both Sides"); break; case MediumTypes.ECMA69: sb.AppendLine("ECMA-69: 200 mm Flexible Disk Cartridge using MFM Recording at 13262 ftprad on Both Sides"); break; case MediumTypes.ECMA66: sb.AppendLine("ECMA-66: 130 mm Flexible Disk Cartridge using Two-Frequency Recording at 7958 ftprad on One Side"); break; case MediumTypes.ECMA70: sb.AppendLine("ECMA-70 & ANSI X3.125-1985: 130 mm Flexible Disk Cartridge using MFM Recording at 7958 ftprad on Both Sides; 1,9 Tracks per mm"); break; case MediumTypes.ECMA78: sb.AppendLine("ECMA-78 & ANSI X3.126-1986: 130 mm Flexible Disk Cartridge using MFM Recording at 7958 ftprad on Both Sides; 3,8 Tracks per mm"); break; case MediumTypes.ECMA99: sb.AppendLine("ECMA-99 & ISO 8630-1985: 130 mm Flexible Disk Cartridge using MFM Recording at 13262 ftprad on Both Sides; 3,8 Tracks per mm"); break; case MediumTypes.ECMA100: sb.AppendLine("ECMA-100 & ANSI X3.137: 90 mm Flexible Disk Cartridge using MFM Recording at 7859 ftprad on Both Sides; 5,3 Tracks per mm"); break; case MediumTypes.Unspecified_SS: sb.AppendLine("Unspecified single sided flexible disk"); break; case MediumTypes.Unspecified_DS: sb.AppendLine("Unspecified double sided flexible disk"); break; case MediumTypes.X3_73: sb.AppendLine("ANSI X3.73-1980: 200 mm, 6631 ftprad, 1,9 Tracks per mm, 1 side"); break; case MediumTypes.X3_73_DS: sb.AppendLine("ANSI X3.73-1980: 200 mm, 6631 ftprad, 1,9 Tracks per mm, 2 sides"); break; case MediumTypes.X3_82: sb.AppendLine("ANSI X3.80-1980: 130 mm, 3979 ftprad, 1,9 Tracks per mm, 1 side"); break; case MediumTypes.Tape12: sb.AppendLine("6,3 mm tape with 12 tracks at 394 ftpmm"); break; case MediumTypes.Tape24: sb.AppendLine("6,3 mm tape with 24 tracks at 394 ftpmm"); break; default: sb.AppendFormat("Unknown medium type 0x{0:X2}", header.Value.MediumType).AppendLine(); break; } } if (header.Value.WriteProtected) sb.AppendLine("Medium is write protected"); if (header.Value.DPOFUA) sb.AppendLine("Drive supports DPO and FUA bits"); foreach (BlockDescriptor descriptor in header.Value.BlockDescriptors) { string density = ""; switch (descriptor.Density) { case DensityType.Default: break; case DensityType.Flux7958: density = "7958 flux transitions per radian"; break; case DensityType.Flux13262: density = "13262 flux transitions per radian"; break; case DensityType.Flux15916: density = "15916 flux transitions per radian"; break; default: density = String.Format("with unknown density code 0x{0:X2}", descriptor.Density); break; } if (density != "") { if (descriptor.Blocks == 0) sb.AppendFormat("All remaining blocks have {0} and are {1} bytes each", density, descriptor.BlockLength).AppendLine(); else sb.AppendFormat("{0} blocks have {1} and are {2} bytes each", descriptor.Blocks, density, descriptor.BlockLength).AppendLine(); } else { if (descriptor.Blocks == 0) sb.AppendFormat("All remaining blocks are {0} bytes each", descriptor.BlockLength).AppendLine(); else sb.AppendFormat("{0} blocks are {1} bytes each", descriptor.Blocks, descriptor.BlockLength).AppendLine(); } } break; } case PeripheralDeviceTypes.SequentialAccess: { switch (header.Value.BufferedMode) { case 0: sb.AppendLine("Device writes directly to media"); break; case 1: sb.AppendLine("Device uses a write cache"); break; case 2: sb.AppendLine("Device uses a write cache but doesn't return until cache is flushed"); break; default: sb.AppendFormat("Unknown buffered mode code 0x{0:X2}", header.Value.BufferedMode).AppendLine(); break; } if (header.Value.Speed == 0) sb.AppendLine("Device uses default speed"); else sb.AppendFormat("Device uses speed {0}", header.Value.Speed).AppendLine(); if (header.Value.WriteProtected) sb.AppendLine("Medium is write protected"); foreach (BlockDescriptor descriptor in header.Value.BlockDescriptors) { string density = ""; switch (descriptor.Density) { case DensityType.Default: break; case DensityType.ECMA62: density = "ECMA-62 & ANSI X3.22-1983: 12,7 mm 9-Track Magnetic Tape, 32 ftpmm, NRZI, 32 cpmm"; break; case DensityType.ECMA62_Phase: density = "ECMA-62 & ANSI X3.39-1986: 12,7 mm 9-Track Magnetic Tape, 126 ftpmm, Phase Encoding, 63 cpmm"; break; case DensityType.ECMA62_GCR: density = "ECMA-62 & ANSI X3.54-1986: 12,7 mm 9-Track Magnetic Tape, 356 ftpmm, NRZI, 245 cpmm GCR"; break; case DensityType.ECMA79: density = "ECMA-79 & ANSI X3.116-1986: 6,30 mm Magnetic Tape Cartridge, 252 ftpmm, MFM"; break; case DensityType.ECMADraft: density = "Draft ECMA & ANSI X3B5/87-099: 12,7 mm 18-Track Magnetic Tape Cartridge, 1944 ftpmm, IFM, GCR"; break; case DensityType.ECMA46: density = "ECMA-46 & ANSI X3.56-1986: 6,30 mm Magnetic Tape Cartridge, Phase Encoding, 63 bpmm"; break; case DensityType.ECMA98: density = "ECMA-98: 6,30 mm Magnetic Tape Cartridge, NRZI, 394 ftpmm"; break; case DensityType.X3_136: density = "ANXI X3.136-1986: 6,3 mm 4 or 9-Track Magnetic Tape Cartridge, 315 bpmm, GCR"; break; case DensityType.X3_157: density = "ANXI X3.157-1987: 12,7 mm 9-Track Magnetic Tape, 126 bpmm, Phase Encoding"; break; case DensityType.X3_158: density = "ANXI X3.158-1987: 3,81 mm 4-Track Magnetic Tape Cassette, 315 bpmm, GCR"; break; case DensityType.X3B5_86: density = "ANXI X3B5/86-199: 12,7 mm 22-Track Magnetic Tape Cartridge, 262 bpmm, MFM"; break; case DensityType.HiTC1: density = "HI-TC1: 12,7 mm 24-Track Magnetic Tape Cartridge, 500 bpmm, GCR"; break; case DensityType.HiTC2: density = "HI-TC2: 12,7 mm 24-Track Magnetic Tape Cartridge, 999 bpmm, GCR"; break; case DensityType.QIC120: density = "QIC-120: 6,3 mm 15-Track Magnetic Tape Cartridge, 394 bpmm, GCR"; break; case DensityType.QIC150: density = "QIC-150: 6,3 mm 18-Track Magnetic Tape Cartridge, 394 bpmm, GCR"; break; case DensityType.QIC320: density = "QIC-320: 6,3 mm 26-Track Magnetic Tape Cartridge, 630 bpmm, GCR"; break; case DensityType.QIC1350: density = "QIC-1350: 6,3 mm 30-Track Magnetic Tape Cartridge, 2034 bpmm, RLL"; break; case DensityType.X3B5_88: density = "ANXI X3B5/88-185A: 3,81 mm Magnetic Tape Cassette, 2400 bpmm, DDS"; break; case DensityType.X3_202: density = "ANXI X3.202-1991: 8 mm Magnetic Tape Cassette, 1703 bpmm, RLL"; break; case DensityType.ECMA_TC17: density = "ECMA TC17: 8 mm Magnetic Tape Cassette, 1789 bpmm, RLL"; break; case DensityType.X3_193: density = "ANXI X3.193-1990: 12,7 mm 48-Track Magnetic Tape Cartridge, 394 bpmm, MFM"; break; case DensityType.X3B5_91: density = "ANXI X3B5/97-174: 12,7 mm 48-Track Magnetic Tape Cartridge, 1673 bpmm, MFM"; break; default: density = String.Format("Unknown density code 0x{0:X2}", descriptor.Density); break; } if (density != "") { if (descriptor.Blocks == 0) { if (descriptor.BlockLength == 0) sb.AppendFormat("All remaining blocks conform to {0} and have a variable length", density).AppendLine(); else sb.AppendFormat("All remaining blocks conform to {0} and are {1} bytes each", density, descriptor.BlockLength).AppendLine(); } else { if (descriptor.BlockLength == 0) sb.AppendFormat("{0} blocks conform to {1} and have a variable length", descriptor.Blocks, density).AppendLine(); else sb.AppendFormat("{0} blocks conform to {1} and are {2} bytes each", descriptor.Blocks, density, descriptor.BlockLength).AppendLine(); } } else { if (descriptor.Blocks == 0) { if (descriptor.BlockLength == 0) sb.AppendFormat("All remaining blocks have a variable length").AppendLine(); else sb.AppendFormat("All remaining blocks are {0} bytes each", descriptor.BlockLength).AppendLine(); } else { if (descriptor.BlockLength == 0) sb.AppendFormat("{0} blocks have a variable length", descriptor.Blocks).AppendLine(); else sb.AppendFormat("{0} blocks are {1} bytes each", descriptor.Blocks, descriptor.BlockLength).AppendLine(); } } } break; } case PeripheralDeviceTypes.PrinterDevice: { switch (header.Value.BufferedMode) { case 0: sb.AppendLine("Device prints directly"); break; case 1: sb.AppendLine("Device uses a print cache"); break; default: sb.AppendFormat("Unknown buffered mode code 0x{0:X2}", header.Value.BufferedMode).AppendLine(); break; } break; } case PeripheralDeviceTypes.OpticalDevice: { if (header.Value.MediumType != MediumTypes.Default) { sb.Append("Medium is "); switch (header.Value.MediumType) { case MediumTypes.ReadOnly: sb.AppendLine("a Read-only optical"); break; case MediumTypes.WORM: sb.AppendLine("a Write-once Read-many optical"); break; case MediumTypes.Erasable: sb.AppendLine("a Erasable optical"); break; case MediumTypes.RO_WORM: sb.AppendLine("a combination of read-only and write-once optical"); break; case MediumTypes.RO_RW: sb.AppendLine("a combination of read-only and erasable optical"); break; case MediumTypes.WORM_RW: sb.AppendLine("a combination of write-once and erasable optical"); break; default: sb.AppendFormat("an unknown medium type 0x{0:X2}", header.Value.MediumType).AppendLine(); break; } } if (header.Value.WriteProtected) sb.AppendLine("Medium is write protected"); if (header.Value.EBC) sb.AppendLine("Blank checking during write is enabled"); if (header.Value.DPOFUA) sb.AppendLine("Drive supports DPO and FUA bits"); foreach (BlockDescriptor descriptor in header.Value.BlockDescriptors) { string density = ""; switch (descriptor.Density) { case DensityType.Default: break; case DensityType.ISO10090: density = "ISO/IEC 10090: 86 mm Read/Write single-sided optical disc with 12500 tracks"; break; case DensityType.D581: density = "89 mm Read/Write double-sided optical disc with 12500 tracks"; break; case DensityType.X3_212: density = "ANSI X3.212: 130 mm Read/Write double-sided optical disc with 18750 tracks"; break; case DensityType.X3_191: density = "ANSI X3.191: 130 mm Write-Once double-sided optical disc with 30000 tracks"; break; case DensityType.X3_214: density = "ANSI X3.214: 130 mm Write-Once double-sided optical disc with 20000 tracks"; break; case DensityType.X3_211: density = "ANSI X3.211: 130 mm Write-Once double-sided optical disc with 18750 tracks"; break; case DensityType.D407: density = "200 mm optical disc"; break; case DensityType.ISO13614: density = "ISO/IEC 13614: 300 mm double-sided optical disc"; break; case DensityType.X3_200: density = "ANSI X3.200: 356 mm double-sided optical disc with 56350 tracks"; break; default: density = String.Format("Unknown density code 0x{0:X2}", descriptor.Density); break; } if (density != "") { if (descriptor.Blocks == 0) { if (descriptor.BlockLength == 0) sb.AppendFormat("All remaining blocks are {0} and have a variable length", density).AppendLine(); else sb.AppendFormat("All remaining blocks are {0} and are {1} bytes each", density, descriptor.BlockLength).AppendLine(); } else { if (descriptor.BlockLength == 0) sb.AppendFormat("{0} blocks are {1} and have a variable length", descriptor.Blocks, density).AppendLine(); else sb.AppendFormat("{0} blocks are {1} and are {2} bytes each", descriptor.Blocks, density, descriptor.BlockLength).AppendLine(); } } else { if (descriptor.Blocks == 0) { if (descriptor.BlockLength == 0) sb.AppendFormat("All remaining blocks have a variable length").AppendLine(); else sb.AppendFormat("All remaining blocks are {0} bytes each", descriptor.BlockLength).AppendLine(); } else { if (descriptor.BlockLength == 0) sb.AppendFormat("{0} blocks have a variable length", descriptor.Blocks).AppendLine(); else sb.AppendFormat("{0} blocks are {1} bytes each", descriptor.Blocks, descriptor.BlockLength).AppendLine(); } } } break; } case PeripheralDeviceTypes.MultiMediaDevice: { sb.Append("Medium is "); switch (header.Value.MediumType) { case MediumTypes.CDROM: sb.AppendLine("120 mm CD-ROM"); break; case MediumTypes.CDDA: sb.AppendLine("120 mm Compact Disc Digital Audio"); break; case MediumTypes.MixedCD: sb.AppendLine("120 mm Compact Disc with data and audio"); break; case MediumTypes.CDROM_80: sb.AppendLine("80 mm CD-ROM"); break; case MediumTypes.CDDA_80: sb.AppendLine("80 mm Compact Disc Digital Audio"); break; case MediumTypes.MixedCD_80: sb.AppendLine("80 mm Compact Disc with data and audio"); break; default: sb.AppendFormat("Unknown medium type 0x{0:X2}", header.Value.MediumType).AppendLine(); break; } if (header.Value.WriteProtected) sb.AppendLine("Medium is write protected"); if (header.Value.DPOFUA) sb.AppendLine("Drive supports DPO and FUA bits"); foreach (BlockDescriptor descriptor in header.Value.BlockDescriptors) { string density = ""; switch (descriptor.Density) { case DensityType.Default: break; case DensityType.User: density = "user data only"; break; case DensityType.UserAuxiliary: density = "user data plus auxiliary data"; break; case DensityType.UserAuxiliaryTag: density = "4-byte tag, user data plus auxiliary data"; break; case DensityType.Audio: density = "audio information only"; break; default: density = String.Format("with unknown density code 0x{0:X2}", descriptor.Density); break; } if (density != "") { if (descriptor.Blocks == 0) sb.AppendFormat("All remaining blocks have {0} and are {1} bytes each", density, descriptor.BlockLength).AppendLine(); else sb.AppendFormat("{0} blocks have {1} and are {2} bytes each", descriptor.Blocks, density, descriptor.BlockLength).AppendLine(); } else { if (descriptor.Blocks == 0) sb.AppendFormat("All remaining blocks are {0} bytes each", descriptor.BlockLength).AppendLine(); else sb.AppendFormat("{0} blocks are {1} bytes each", descriptor.Blocks, descriptor.BlockLength).AppendLine(); } } break; } default: break; } return sb.ToString(); } public static ModeHeader? DecodeModeHeader10(byte[] modeResponse, PeripheralDeviceTypes deviceType) { if (modeResponse == null || modeResponse.Length < 8) return null; ushort modeLength; ushort blockDescLength; modeLength = (ushort)((modeResponse[0] << 8) + modeResponse[1]); blockDescLength = (ushort)((modeResponse[6] << 8) + modeResponse[7]); if (modeResponse.Length < modeLength) return null; ModeHeader header = new ModeHeader(); header.MediumType = (MediumTypes)modeResponse[2]; if (blockDescLength > 0) { header.BlockDescriptors = new BlockDescriptor[blockDescLength / 8]; for (int i = 0; i < header.BlockDescriptors.Length; i++) { header.BlockDescriptors[i].Density = (DensityType)modeResponse[0 + i * 8 + 8]; header.BlockDescriptors[i].Blocks += (ulong)(modeResponse[1 + i * 8 + 8] << 16); header.BlockDescriptors[i].Blocks += (ulong)(modeResponse[2 + i * 8 + 8] << 8); header.BlockDescriptors[i].Blocks += modeResponse[3 + i * 8 + 8]; header.BlockDescriptors[i].BlockLength += (ulong)(modeResponse[5 + i * 8 + 8] << 16); header.BlockDescriptors[i].BlockLength += (ulong)(modeResponse[6 + i * 8 + 8] << 8); header.BlockDescriptors[i].BlockLength += modeResponse[7 + i * 8 + 8]; } } if (deviceType == PeripheralDeviceTypes.DirectAccess || deviceType == PeripheralDeviceTypes.MultiMediaDevice) { header.WriteProtected = ((modeResponse[3] & 0x80) == 0x80); header.DPOFUA = ((modeResponse[3] & 0x10) == 0x10); } if (deviceType == PeripheralDeviceTypes.SequentialAccess) { header.WriteProtected = ((modeResponse[3] & 0x80) == 0x80); header.Speed = (byte)(modeResponse[3] & 0x0F); header.BufferedMode = (byte)((modeResponse[3] & 0x70) >> 4); } if (deviceType == PeripheralDeviceTypes.PrinterDevice) header.BufferedMode = (byte)((modeResponse[3] & 0x70) >> 4); if (deviceType == PeripheralDeviceTypes.OpticalDevice) { header.WriteProtected = ((modeResponse[3] & 0x80) == 0x80); header.EBC = ((modeResponse[3] & 0x01) == 0x01); header.DPOFUA = ((modeResponse[3] & 0x10) == 0x10); } return header; } public static string PrettifyModeHeader10(byte[] modeResponse, PeripheralDeviceTypes deviceType) { return PrettifyModeHeader(DecodeModeHeader10(modeResponse, deviceType), deviceType); } #region Mode Page 0x0A: Control mode page /// /// Control mode page /// Page code 0x0A /// 8 bytes in SCSI-2 /// 12 bytes in SPC-1, SPC-2 /// public struct ModePage_0A { /// /// Parameters can be saved /// public bool PS; /// /// If set, target shall report log exception conditions /// public bool RLEC; /// /// Queue algorithm modifier /// public byte QueueAlgorithm; /// /// If set all remaining suspended I/O processes shall be aborted after the contingent allegiance condition or extended contingent allegiance condition /// public byte QErr; /// /// Tagged queuing is disabled /// public bool DQue; /// /// Extended Contingent Allegiance is enabled /// public bool EECA; /// /// Target may issue an asynchronous event notification upon completing its initialization /// public bool RAENP; /// /// Target may issue an asynchronous event notification instead of a unit attention condition /// public bool UAAENP; /// /// Target may issue an asynchronous event notification instead of a deferred error /// public bool EAENP; /// /// Minimum time in ms after initialization before attempting asynchronous event notifications /// public ushort ReadyAENHoldOffPeriod; /// /// Global logging target save disabled /// public bool GLTSD; /// /// CHECK CONDITION should be reported rather than a long busy condition /// public bool RAC; /// /// Software write protect is active /// public bool SWP; /// /// Maximum time in 100 ms units allowed to remain busy. 0xFFFF == unlimited. /// public ushort BusyTimeoutPeriod; /// /// Task set type /// public byte TST; /// /// Tasks aborted by other initiator's actions should be terminated with TASK ABORTED /// public bool TAS; /// /// Action to be taken when a medium is inserted /// public byte AutoloadMode; /// /// Time in seconds to complete an extended self-test /// public byte ExtendedSelfTestCompletionTime; /// /// All tasks received in nexus with ACA ACTIVE is set and an ACA condition is established shall terminate /// public bool TMF_ONLY; /// /// Device shall return descriptor format sense data when returning sense data in the same transactions as a CHECK CONDITION /// public bool D_SENSE; /// /// Unit attention interlocks control /// public byte UA_INTLCK_CTRL; /// /// LOGICAL BLOCK APPLICATION TAG should not be modified /// public bool ATO; /// /// Protector information checking is disabled /// public bool DPICZ; /// /// No unit attention on release /// public bool NUAR; /// /// Application Tag mode page is enabled /// public bool ATMPE; /// /// Abort any write command without protection information /// public bool RWWP; /// /// Supportes block lengths and protection information /// public bool SBLP; } public static ModePage_0A? DecodeModePage_0A(byte[] pageResponse) { if (pageResponse == null) return null; if ((pageResponse[0] & 0x3F) != 0x0A) return null; if (pageResponse[1] + 2 != pageResponse.Length) return null; if (pageResponse.Length < 8) return null; ModePage_0A decoded = new ModePage_0A(); decoded.PS |= (pageResponse[0] & 0x80) == 0x80; decoded.RLEC |= (pageResponse[2] & 0x01) == 0x01; decoded.QueueAlgorithm = (byte)((pageResponse[3] & 0xF0) >> 4); decoded.QErr = (byte)((pageResponse[3] & 0x06) >> 1); decoded.DQue |= (pageResponse[3] & 0x01) == 0x01; decoded.EECA |= (pageResponse[4] & 0x80) == 0x80; decoded.RAENP |= (pageResponse[4] & 0x04) == 0x04; decoded.UAAENP |= (pageResponse[4] & 0x02) == 0x02; decoded.EAENP |= (pageResponse[4] & 0x01) == 0x01; decoded.ReadyAENHoldOffPeriod = (ushort)((pageResponse[6] << 8) + pageResponse[7]); if (pageResponse.Length < 10) return decoded; // SPC-1 decoded.GLTSD |= (pageResponse[2] & 0x02) == 0x02; decoded.RAC |= (pageResponse[4] & 0x40) == 0x40; decoded.SWP |= (pageResponse[4] & 0x08) == 0x08; decoded.BusyTimeoutPeriod = (ushort)((pageResponse[8] << 8) + pageResponse[9]); // SPC-2 decoded.TST = (byte)((pageResponse[2] & 0xE0) >> 5); decoded.TAS |= (pageResponse[4] & 0x80) == 0x80; decoded.AutoloadMode = (byte)(pageResponse[5] & 0x07); decoded.BusyTimeoutPeriod = (ushort)((pageResponse[10] << 8) + pageResponse[11]); // SPC-3 decoded.TMF_ONLY |= (pageResponse[2] & 0x10) == 0x10; decoded.D_SENSE |= (pageResponse[2] & 0x04) == 0x04; decoded.UA_INTLCK_CTRL = (byte)((pageResponse[4] & 0x30) >> 4); decoded.TAS |= (pageResponse[5] & 0x40) == 0x40; decoded.ATO |= (pageResponse[5] & 0x80) == 0x80; // SPC-5 decoded.DPICZ |= (pageResponse[2] & 0x08) == 0x08; decoded.NUAR |= (pageResponse[3] & 0x08) == 0x08; decoded.ATMPE |= (pageResponse[5] & 0x20) == 0x20; decoded.RWWP |= (pageResponse[5] & 0x10) == 0x10; decoded.SBLP |= (pageResponse[5] & 0x08) == 0x08; return decoded; } public static string PrettifyModePage_0A(byte[] pageResponse) { return PrettifyModePage_0A(DecodeModePage_0A(pageResponse)); } public static string PrettifyModePage_0A(ModePage_0A? modePage) { if (!modePage.HasValue) return null; ModePage_0A page = modePage.Value; StringBuilder sb = new StringBuilder(); sb.AppendLine("SCSI Control mode page:"); if (page.PS) sb.AppendLine("\tParameters can be saved"); if (page.RLEC) sb.AppendLine("\tIf set, target shall report log exception conditions"); if (page.DQue) sb.AppendLine("\tTagged queuing is disabled"); if (page.EECA) sb.AppendLine("\tExtended Contingent Allegiance is enabled"); if (page.RAENP) sb.AppendLine("\tTarget may issue an asynchronous event notification upon completing its initialization"); if (page.UAAENP) sb.AppendLine("\tTarget may issue an asynchronous event notification instead of a unit attention condition"); if (page.EAENP) sb.AppendLine("\tTarget may issue an asynchronous event notification instead of a deferred error"); if (page.GLTSD) sb.AppendLine("\tGlobal logging target save disabled"); if (page.RAC) sb.AppendLine("\tCHECK CONDITION should be reported rather than a long busy condition"); if (page.SWP) sb.AppendLine("\tSoftware write protect is active"); if (page.TAS) sb.AppendLine("\tTasks aborted by other initiator's actions should be terminated with TASK ABORTED"); if (page.TMF_ONLY) sb.AppendLine("\tAll tasks received in nexus with ACA ACTIVE is set and an ACA condition is established shall terminate"); if (page.D_SENSE) sb.AppendLine("\tDevice shall return descriptor format sense data when returning sense data in the same transactions as a CHECK CONDITION"); if (page.ATO) sb.AppendLine("\tLOGICAL BLOCK APPLICATION TAG should not be modified"); if (page.DPICZ) sb.AppendLine("\tProtector information checking is disabled"); if (page.NUAR) sb.AppendLine("\tNo unit attention on release"); if (page.ATMPE) sb.AppendLine("\tApplication Tag mode page is enabled"); if (page.RWWP) sb.AppendLine("\tAbort any write command without protection information"); if (page.SBLP) sb.AppendLine("\tSupportes block lengths and protection information"); switch (page.TST) { case 0: sb.AppendLine("\tThe logical unit maintains one task set for all nexuses"); break; case 1: sb.AppendLine("\tThe logical unit maintains separate task sets for each nexus"); break; default: sb.AppendFormat("\tUnknown Task set type {0}", page.TST).AppendLine(); break; } switch (page.QueueAlgorithm) { case 0: sb.AppendLine("\tCommands should be sent strictly ordered"); break; case 1: sb.AppendLine("\tCommands can be reordered in any manner"); break; default: sb.AppendFormat("\tUnknown Queue Algorithm Modifier {0}", page.QueueAlgorithm).AppendLine(); break; } switch (page.QErr) { case 0: sb.AppendLine("\tIf ACA is established, the task set commands shall resume after it is cleared, otherwise they shall terminate with CHECK CONDITION"); break; case 1: sb.AppendLine("\tAll the affected commands in the task set shall be aborted when CHECK CONDITION is returned"); break; case 3: sb.AppendLine("\tAffected commands in the task set belonging with the CHECK CONDITION nexus shall be aborted"); break; default: sb.AppendLine("\tReserved QErr value 2 is set"); break; } switch (page.UA_INTLCK_CTRL) { case 0: sb.AppendLine("\tLUN shall clear unit attention condition reported in the same nexus"); break; case 2: sb.AppendLine("\tLUN shall not clear unit attention condition reported in the same nexus"); break; case 3: sb.AppendLine("\tLUN shall not clear unit attention condition reported in the same nexus and shall establish a unit attention condition for the initiator"); break; default: sb.AppendLine("\tReserved UA_INTLCK_CTRL value 1 is set"); break; } switch (page.AutoloadMode) { case 0: sb.AppendLine("\tOn medium insertion, it shall be loaded for full access"); break; case 1: sb.AppendLine("\tOn medium insertion, it shall be loaded for auxiliary memory access only"); break; case 2: sb.AppendLine("\tOn medium insertion, it shall not be loaded"); break; default: sb.AppendFormat("\tReserved autoload mode {0} set", page.AutoloadMode).AppendLine(); break; } if (page.ReadyAENHoldOffPeriod > 0) sb.AppendFormat("\t{0} ms before attempting asynchronous event notifications after initialization", page.ReadyAENHoldOffPeriod).AppendLine(); if (page.BusyTimeoutPeriod > 0) { if (page.BusyTimeoutPeriod == 0xFFFF) sb.AppendLine("\tThere is no limit on the maximum time that is allowed to remain busy"); else sb.AppendFormat("\tA maximum of {0} ms are allowed to remain busy", (int)page.BusyTimeoutPeriod * 100).AppendLine(); } if (page.ExtendedSelfTestCompletionTime > 0) sb.AppendFormat("\t{0} seconds to complete extended self-test", page.ExtendedSelfTestCompletionTime); return sb.ToString(); } #endregion Mode Page 0x0A: Control mode page #region Mode Page 0x02: Disconnect-reconnect page /// /// Disconnect-reconnect page /// Page code 0x02 /// 16 bytes in SCSI-2 /// public struct ModePage_02 { /// /// Parameters can be saved /// public bool PS; /// /// How full should be the buffer prior to attempting a reselection /// public byte BufferFullRatio; /// /// How empty should be the buffer prior to attempting a reselection /// public byte BufferEmptyRatio; /// /// Max. time in 100 µs increments that the target is permitted to assert BSY without a REQ/ACK /// public ushort BusInactivityLimit; /// /// Min. time in 100 µs increments to wait after releasing the bus before attempting reselection /// public ushort DisconnectTimeLimit; /// /// Max. time in 100 µs increments allowed to use the bus before disconnecting, if granted the privilege and not restricted by /// public ushort ConnectTimeLimit; /// /// Maximum amount of data before disconnecting in 512 bytes increments /// public ushort MaxBurstSize; /// /// Data transfer disconnect control /// public byte DTDC; /// /// Target shall not transfer data for a command during the same interconnect tenancy /// public bool DIMM; /// /// Wether to use fair or unfair arbitration when requesting an interconnect tenancy /// public byte FairArbitration; /// /// Max. ammount of data in 512 bytes increments that may be transferred for a command along with the command /// public ushort FirstBurstSize; /// /// Target is allowed to re-order the data transfer /// public bool EMDP; } public static ModePage_02? DecodeModePage_02(byte[] pageResponse) { if (pageResponse == null) return null; if ((pageResponse[0] & 0x3F) != 0x02) return null; if (pageResponse[1] + 2 != pageResponse.Length) return null; if (pageResponse.Length < 16) return null; ModePage_02 decoded = new ModePage_02(); decoded.PS |= (pageResponse[0] & 0x80) == 0x80; decoded.BufferFullRatio = pageResponse[2]; decoded.BufferEmptyRatio = pageResponse[3]; decoded.BusInactivityLimit = (ushort)((pageResponse[4] << 8) + pageResponse[5]); decoded.DisconnectTimeLimit = (ushort)((pageResponse[6] << 8) + pageResponse[7]); decoded.ConnectTimeLimit = (ushort)((pageResponse[8] << 8) + pageResponse[9]); decoded.MaxBurstSize = (ushort)((pageResponse[10] << 8) + pageResponse[11]); decoded.FirstBurstSize = (ushort)((pageResponse[14] << 8) + pageResponse[15]); decoded.EMDP |= (pageResponse[12] & 0x80) == 0x80; decoded.DIMM |= (pageResponse[12] & 0x08) == 0x08; decoded.FairArbitration = (byte)((pageResponse[12] & 0x70) >> 4); decoded.DTDC = (byte)(pageResponse[12] & 0x07); return decoded; } public static string PrettifyModePage_02(byte[] pageResponse) { return PrettifyModePage_02(DecodeModePage_02(pageResponse)); } public static string PrettifyModePage_02(ModePage_02? modePage) { if (!modePage.HasValue) return null; ModePage_02 page = modePage.Value; StringBuilder sb = new StringBuilder(); sb.AppendLine("SCSI Disconnect-Reconnect mode page:"); if (page.PS) sb.AppendLine("\tParameters can be saved"); if (page.BufferFullRatio > 0) sb.AppendFormat("\t{0} ratio of buffer that shall be full prior to attempting a reselection", page.BufferFullRatio).AppendLine(); if (page.BufferEmptyRatio > 0) sb.AppendFormat("\t{0} ratio of buffer that shall be empty prior to attempting a reselection", page.BufferEmptyRatio).AppendLine(); if (page.BusInactivityLimit > 0) sb.AppendFormat("\t{0} µs maximum permitted to assert BSY without a REQ/ACK handshake", (int)page.BusInactivityLimit * 100).AppendLine(); if (page.DisconnectTimeLimit > 0) sb.AppendFormat("\t{0} µs maximum permitted wait after releasing the bus before attempting reselection", (int)page.DisconnectTimeLimit * 100).AppendLine(); if (page.ConnectTimeLimit > 0) sb.AppendFormat("\t{0} µs allowed to use the bus before disconnecting, if granted the privilege and not restricted", (int)page.ConnectTimeLimit * 100).AppendLine(); if (page.MaxBurstSize > 0) sb.AppendFormat("\t{0} bytes maximum can be transferred before disconnecting", (int)page.MaxBurstSize * 512).AppendLine(); if (page.FirstBurstSize > 0) sb.AppendFormat("\t{0} bytes maximum can be transferred for a command along with the disconnect command", (int)page.FirstBurstSize * 512).AppendLine(); if (page.DIMM) sb.AppendLine("\tTarget shall not transfer data for a command during the same interconnect tenancy"); if (page.EMDP) sb.AppendLine("\tTarget is allowed to re-order the data transfer"); switch (page.DTDC) { case 0: sb.AppendLine("\tData transfer disconnect control is not used"); break; case 1: sb.AppendLine("\tAll data for a command shall be transferred within a single interconnect tenancy"); break; case 3: sb.AppendLine("\tAll data and the response for a command shall be transferred within a single interconnect tenancy"); break; default: sb.AppendFormat("\tReserved data transfer disconnect control value {0}", page.DTDC).AppendLine(); break; } return sb.ToString(); } #endregion Mode Page 0x02: Disconnect-reconnect page #region Mode Page 0x08: Caching page /// /// Disconnect-reconnect page /// Page code 0x08 /// 12 bytes in SCSI-2 /// public struct ModePage_08 { /// /// Parameters can be saved /// public bool PS; /// /// true if write cache is enabled /// public bool WCE; /// /// Multiplication factor /// public bool MF; /// /// true if read cache is enabled /// public bool RCD; /// /// Advices on reading-cache retention priority /// public byte DemandReadRetentionPrio; /// /// Advices on writing-cache retention priority /// public byte WriteRetentionPriority; /// /// If requested read blocks are more than this, no pre-fetch is done /// public ushort DisablePreFetch; /// /// Minimum pre-fetch /// public ushort MinimumPreFetch; /// /// Maximum pre-fetch /// public ushort MaximumPreFetch; /// /// Upper limit on maximum pre-fetch value /// public ushort MaximumPreFetchCeiling; } public static ModePage_08? DecodeModePage_08(byte[] pageResponse) { if (pageResponse == null) return null; if ((pageResponse[0] & 0x3F) != 0x08) return null; if (pageResponse[1] + 2 != pageResponse.Length) return null; if (pageResponse.Length < 12) return null; ModePage_08 decoded = new ModePage_08(); decoded.PS |= (pageResponse[0] & 0x80) == 0x80; decoded.WCE |= (pageResponse[2] & 0x04) == 0x04; decoded.MF |= (pageResponse[2] & 0x02) == 0x02; decoded.RCD |= (pageResponse[2] & 0x01) == 0x01; decoded.DemandReadRetentionPrio = (byte)((pageResponse[3] & 0xF0) >> 4); decoded.WriteRetentionPriority = (byte)(pageResponse[3] & 0x0F); decoded.DisablePreFetch = (ushort)((pageResponse[4] << 8) + pageResponse[5]); decoded.MinimumPreFetch = (ushort)((pageResponse[6] << 8) + pageResponse[7]); decoded.MaximumPreFetch = (ushort)((pageResponse[8] << 8) + pageResponse[9]); decoded.MaximumPreFetchCeiling = (ushort)((pageResponse[10] << 8) + pageResponse[11]); return decoded; } public static string PrettifyModePage_08(byte[] pageResponse) { return PrettifyModePage_08(DecodeModePage_08(pageResponse)); } public static string PrettifyModePage_08(ModePage_08? modePage) { if (!modePage.HasValue) return null; ModePage_08 page = modePage.Value; StringBuilder sb = new StringBuilder(); sb.AppendLine("SCSI Caching mode page:"); if (page.PS) sb.AppendLine("\tParameters can be saved"); if (page.RCD) sb.AppendLine("\tRead-cache is enabled"); if (page.WCE) sb.AppendLine("\tWrite-cache is enabled"); switch (page.DemandReadRetentionPrio) { case 0: sb.AppendLine("\tDrive does not distinguish between cached read data"); break; case 1: sb.AppendLine("\tData put by READ commands should be evicted from cache sooner than data put in read cache by other means"); break; case 0xF: sb.AppendLine("\tData put by READ commands should not be evicted if there is data cached by other means that can be evicted"); break; default: sb.AppendFormat("\tUnknown demand read retention priority value {0}", page.DemandReadRetentionPrio).AppendLine(); break; } switch (page.WriteRetentionPriority) { case 0: sb.AppendLine("\tDrive does not distinguish between cached write data"); break; case 1: sb.AppendLine("\tData put by WRITE commands should be evicted from cache sooner than data put in write cache by other means"); break; case 0xF: sb.AppendLine("\tData put by WRITE commands should not be evicted if there is data cached by other means that can be evicted"); break; default: sb.AppendFormat("\tUnknown demand write retention priority value {0}", page.DemandReadRetentionPrio).AppendLine(); break; } if (page.MF) sb.AppendLine("\tPre-fetch values indicate a block multiplier"); if (page.DisablePreFetch == 0) sb.AppendLine("\tNo pre-fetch will be done"); else { sb.AppendFormat("\tPre-fetch will be done for READ commands of {0} blocks or less", page.DisablePreFetch).AppendLine(); if (page.MinimumPreFetch > 0) sb.AppendFormat("At least {0} blocks will be always pre-fetched", page.MinimumPreFetch).AppendLine(); if(page.MaximumPreFetch > 0) sb.AppendFormat("\tA maximum of {0} blocks will be pre-fetched", page.MaximumPreFetch).AppendLine(); if(page.MaximumPreFetchCeiling > 0) sb.AppendFormat("\tA maximum of {0} blocks will be pre-fetched even if it is commanded to pre-fetch more", page.MaximumPreFetchCeiling).AppendLine(); } return sb.ToString(); } #endregion Mode Page 0x08: Caching page #region Mode Page 0x05: Flexible disk page /// /// Disconnect-reconnect page /// Page code 0x05 /// 32 bytes in SCSI-2 /// public struct ModePage_05 { /// /// Parameters can be saved /// public bool PS; /// /// Data rate of peripheral device on kbit/s /// public ushort TransferRate; /// /// Heads for reading and/or writing /// public byte Heads; /// /// Sectors per revolution per head /// public byte SectorsPerTrack; /// /// Bytes of data per sector /// public ushort BytesPerSector; /// /// Cylinders used for data storage /// public ushort Cylinders; /// /// Cylinder where write precompensation starts /// public ushort WritePrecompCylinder; /// /// Cylinder where write current reduction starts /// public ushort WriteReduceCylinder; /// /// Step rate in 100 μs units /// public ushort DriveStepRate; /// /// Width of step pulse in μs /// public byte DriveStepPulse; /// /// Head settle time in 100 μs units /// public ushort HeadSettleDelay; /// /// If is true, specified in 1/10s of a /// second the time waiting for read status before aborting medium /// access. Otherwise, indicates time to way before medimum access /// after motor on signal is asserted. /// public byte MotorOnDelay; /// /// Time in 1/10s of a second to wait before releasing the motor on /// signal after an idle condition. 0xFF means to never release the /// signal /// public byte MotorOffDelay; /// /// Specifies if a signal indicates that the medium is ready to be accessed /// public bool TRDY; /// /// If true sectors start with one. Otherwise, they start with zero. /// public bool SSN; /// /// If true specifies that motor on shall remain released. /// public bool MO; /// /// Number of additional step pulses per cylinder. /// public byte SPC; /// /// Write compensation value /// public byte WriteCompensation; /// /// Head loading time in ms. /// public byte HeadLoadDelay; /// /// Head unloading time in ms. /// public byte HeadUnloadDelay; /// /// Description of shugart's bus pin 34 usage /// public byte Pin34; /// /// Description of shugart's bus pin 2 usage /// public byte Pin2; /// /// Description of shugart's bus pin 4 usage /// public byte Pin4; /// /// Description of shugart's bus pin 1 usage /// public byte Pin1; /// /// Medium speed in rpm /// public ushort MediumRotationRate; } public static ModePage_05? DecodeModePage_05(byte[] pageResponse) { if (pageResponse == null) return null; if ((pageResponse[0] & 0x3F) != 0x05) return null; if (pageResponse[1] + 2 != pageResponse.Length) return null; if (pageResponse.Length < 32) return null; ModePage_05 decoded = new ModePage_05(); decoded.PS |= (pageResponse[0] & 0x80) == 0x80; decoded.TransferRate = (ushort)((pageResponse[2] << 8) + pageResponse[3]); decoded.Heads = pageResponse[4]; decoded.SectorsPerTrack = pageResponse[5]; decoded.BytesPerSector = (ushort)((pageResponse[6] << 8) + pageResponse[7]); decoded.Cylinders = (ushort)((pageResponse[8] << 8) + pageResponse[9]); decoded.WritePrecompCylinder = (ushort)((pageResponse[10] << 8) + pageResponse[11]); decoded.WriteReduceCylinder = (ushort)((pageResponse[12] << 8) + pageResponse[13]); decoded.DriveStepRate = (ushort)((pageResponse[14] << 8) + pageResponse[15]); decoded.DriveStepPulse = pageResponse[16]; decoded.HeadSettleDelay = (ushort)((pageResponse[17] << 8) + pageResponse[18]); decoded.MotorOnDelay = pageResponse[19]; decoded.MotorOffDelay = pageResponse[20]; decoded.TRDY |= (pageResponse[21] & 0x80) == 0x80; decoded.SSN |= (pageResponse[21] & 0x40) == 0x40; decoded.MO |= (pageResponse[21] & 0x20) == 0x20; decoded.SPC = (byte)(pageResponse[22] & 0x0F); decoded.WriteCompensation = pageResponse[23]; decoded.HeadLoadDelay = pageResponse[24]; decoded.HeadUnloadDelay = pageResponse[25]; decoded.Pin34 = (byte)((pageResponse[26] & 0xF0) >> 4); decoded.Pin2 = (byte)(pageResponse[26] & 0x0F); decoded.Pin4 = (byte)((pageResponse[27] & 0xF0) >> 4); decoded.Pin1 = (byte)(pageResponse[27] & 0x0F); decoded.MediumRotationRate = (ushort)((pageResponse[28] << 8) + pageResponse[29]); return decoded; } public static string PrettifyModePage_05(byte[] pageResponse) { return PrettifyModePage_05(DecodeModePage_05(pageResponse)); } public static string PrettifyModePage_05(ModePage_05? modePage) { if (!modePage.HasValue) return null; ModePage_05 page = modePage.Value; StringBuilder sb = new StringBuilder(); sb.AppendLine("SCSI Flexible disk page:"); if (page.PS) sb.AppendLine("\tParameters can be saved"); sb.AppendFormat("\tTransfer rate: {0} kbit/s", page.TransferRate).AppendLine(); sb.AppendFormat("\t{0} heads", page.Heads).AppendLine(); sb.AppendFormat("\t{0} cylinders", page.Cylinders).AppendLine(); sb.AppendFormat("\t{0} sectors per track", page.SectorsPerTrack).AppendLine(); sb.AppendFormat("\t{0} bytes per sector", page.BytesPerSector).AppendLine(); if(page.WritePrecompCylinder < page.Cylinders) sb.AppendFormat("\tWrite pre-compensation starts at cylinder {0}", page.WritePrecompCylinder).AppendLine(); if(page.WriteReduceCylinder < page.Cylinders) sb.AppendFormat("\tWrite current reduction starts at cylinder {0}", page.WriteReduceCylinder).AppendLine(); if (page.DriveStepRate > 0) sb.AppendFormat("\tDrive steps in {0} μs", (uint)page.DriveStepRate * 100).AppendLine(); if (page.DriveStepPulse > 0) sb.AppendFormat("\tEach step pulse is {0} ms", page.DriveStepPulse).AppendLine(); if (page.HeadSettleDelay > 0) sb.AppendFormat("\tHeads settles in {0} μs", (uint)page.HeadSettleDelay * 100).AppendLine(); if(!page.TRDY) sb.AppendFormat("\tTarget shall wait {0} seconds before attempting to access the medium after motor on is asserted", (double)page.MotorOnDelay * 10).AppendLine(); else sb.AppendFormat("\tTarget shall wait {0} seconds after drive is ready before aborting medium access attemps", (double)page.MotorOnDelay * 10).AppendLine(); if (page.MotorOffDelay != 0xFF) sb.AppendFormat("\tTarget shall wait {0} seconds before releasing the motor on signal after becoming idle", (double)page.MotorOffDelay * 10).AppendLine(); else sb.AppendLine("\tTarget shall never release the motor on signal"); if (page.TRDY) sb.AppendLine("\tThere is a drive ready signal"); if (page.SSN) sb.AppendLine("\tSectors start at 1"); if (page.MO) sb.AppendLine("\tThe motor on signal shall remain released"); sb.AppendFormat("\tDrive needs to do {0} step pulses per cylinder", page.SPC + 1).AppendLine(); if (page.WriteCompensation > 0) sb.AppendFormat("\tWrite pre-compensation is {0}", page.WriteCompensation).AppendLine(); if (page.HeadLoadDelay > 0) sb.AppendFormat("\tHead takes {0} ms to load", page.HeadLoadDelay).AppendLine(); if (page.HeadUnloadDelay > 0) sb.AppendFormat("\tHead takes {0} ms to unload", page.HeadUnloadDelay).AppendLine(); if (page.MediumRotationRate > 0) sb.AppendFormat("\tMedium rotates at {0} rpm", page.MediumRotationRate).AppendLine(); switch (page.Pin34 & 0x07) { case 0: sb.AppendLine("\tPin 34 is unconnected"); break; case 1: sb.Append("\tPin 34 indicates drive is ready when active "); if ((page.Pin34 & 0x08) == 0x08) sb.Append("high"); else sb.Append("low"); break; case 2: sb.Append("\tPin 34 indicates disk has changed when active "); if ((page.Pin34 & 0x08) == 0x08) sb.Append("high"); else sb.Append("low"); break; default: sb.AppendFormat("\tPin 34 indicates unknown function {0} when active ", page.Pin34 & 0x07); if ((page.Pin34 & 0x08) == 0x08) sb.Append("high"); else sb.Append("low"); break; } switch (page.Pin4 & 0x07) { case 0: sb.AppendLine("\tPin 4 is unconnected"); break; case 1: sb.Append("\tPin 4 indicates drive is in use when active "); if ((page.Pin4 & 0x08) == 0x08) sb.Append("high"); else sb.Append("low"); break; case 2: sb.Append("\tPin 4 indicates eject when active "); if ((page.Pin4 & 0x08) == 0x08) sb.Append("high"); else sb.Append("low"); break; case 3: sb.Append("\tPin 4 indicates head load when active "); if ((page.Pin4 & 0x08) == 0x08) sb.Append("high"); else sb.Append("low"); break; default: sb.AppendFormat("\tPin 4 indicates unknown function {0} when active ", page.Pin4 & 0x07); if ((page.Pin4 & 0x08) == 0x08) sb.Append("high"); else sb.Append("low"); break; } switch (page.Pin2 & 0x07) { case 0: sb.AppendLine("\tPin 2 is unconnected"); break; default: sb.AppendFormat("\tPin 2 indicates unknown function {0} when active ", page.Pin2 & 0x07); if ((page.Pin2 & 0x08) == 0x08) sb.Append("high"); else sb.Append("low"); break; } switch (page.Pin1 & 0x07) { case 0: sb.AppendLine("\tPin 1 is unconnected"); break; case 1: sb.Append("\tPin 1 indicates disk change reset when active "); if ((page.Pin1 & 0x08) == 0x08) sb.Append("high"); else sb.Append("low"); break; default: sb.AppendFormat("\tPin 1 indicates unknown function {0} when active ", page.Pin1 & 0x07); if ((page.Pin1 & 0x08) == 0x08) sb.Append("high"); else sb.Append("low"); break; } return sb.ToString(); } #endregion Mode Page 0x05: Flexible disk page #region Mode Page 0x03: Format device page /// /// Disconnect-reconnect page /// Page code 0x03 /// 24 bytes in SCSI-2 /// public struct ModePage_03 { /// /// Parameters can be saved /// public bool PS; /// /// Tracks per zone to use in dividing the capacity for the purpose of allocating alternate sectors /// public ushort TracksPerZone; /// /// Number of sectors per zone that shall be reserved for defect handling /// public ushort AltSectorsPerZone; /// /// Number of tracks per zone that shall be reserved for defect handling /// public ushort AltTracksPerZone; /// /// Number of tracks per LUN that shall be reserved for defect handling /// public ushort AltTracksPerLun; /// /// Number of physical sectors per track /// public ushort SectorsPerTrack; /// /// Bytes per physical sector /// public ushort BytesPerSector; /// /// Interleave value, target dependent /// public ushort Interleave; /// /// Sectors between last block of one track and first block of the next /// public ushort TrackSkew; /// /// Sectors between last block of a cylinder and first block of the next one /// public ushort CylinderSkew; /// /// Soft-sectored /// public bool SSEC; /// /// Hard-sectored /// public bool HSEC; /// /// Removable /// public bool RMB; /// /// If set, address are allocated progressively in a surface before going to the next. /// Otherwise, it goes by cylinders /// public bool SURF; } public static ModePage_03? DecodeModePage_03(byte[] pageResponse) { if (pageResponse == null) return null; if ((pageResponse[0] & 0x3F) != 0x03) return null; if (pageResponse[1] + 2 != pageResponse.Length) return null; if (pageResponse.Length < 24) return null; ModePage_03 decoded = new ModePage_03(); decoded.PS |= (pageResponse[0] & 0x80) == 0x80; decoded.TracksPerZone = (ushort)((pageResponse[2] << 8) + pageResponse[3]); decoded.AltSectorsPerZone = (ushort)((pageResponse[4] << 8) + pageResponse[5]); decoded.AltTracksPerZone = (ushort)((pageResponse[6] << 8) + pageResponse[7]); decoded.AltTracksPerLun = (ushort)((pageResponse[8] << 8) + pageResponse[9]); decoded.SectorsPerTrack = (ushort)((pageResponse[10] << 8) + pageResponse[11]); decoded.BytesPerSector = (ushort)((pageResponse[12] << 8) + pageResponse[13]); decoded.Interleave = (ushort)((pageResponse[14] << 8) + pageResponse[15]); decoded.TrackSkew = (ushort)((pageResponse[16] << 8) + pageResponse[17]); decoded.CylinderSkew = (ushort)((pageResponse[18] << 8) + pageResponse[19]); decoded.SSEC |= (pageResponse[20] & 0x80) == 0x80; decoded.HSEC |= (pageResponse[20] & 0x40) == 0x40; decoded.RMB |= (pageResponse[20] & 0x20) == 0x20; decoded.SURF |= (pageResponse[20] & 0x10) == 0x10; return decoded; } public static string PrettifyModePage_03(byte[] pageResponse) { return PrettifyModePage_03(DecodeModePage_03(pageResponse)); } public static string PrettifyModePage_03(ModePage_03? modePage) { if (!modePage.HasValue) return null; ModePage_03 page = modePage.Value; StringBuilder sb = new StringBuilder(); sb.AppendLine("SCSI Format device page:"); if (page.PS) sb.AppendLine("\tParameters can be saved"); sb.AppendFormat("\t{0} tracks per zone to use in dividing the capacity for the purpose of allocating alternate sectors", page.TracksPerZone).AppendLine(); sb.AppendFormat("\t{0} sectors per zone that shall be reserved for defect handling", page.AltSectorsPerZone).AppendLine(); sb.AppendFormat("\t{0} tracks per zone that shall be reserved for defect handling", page.AltTracksPerZone).AppendLine(); sb.AppendFormat("\t{0} tracks per LUN that shall be reserved for defect handling", page.AltTracksPerLun).AppendLine(); sb.AppendFormat("\t{0} physical sectors per track", page.SectorsPerTrack).AppendLine(); sb.AppendFormat("\t{0} Bytes per physical sector", page.BytesPerSector).AppendLine(); sb.AppendFormat("\tTarget-dependent interleave value is {0}", page.Interleave).AppendLine(); sb.AppendFormat("\t{0} sectors between last block of one track and first block of the next", page.TrackSkew).AppendLine(); sb.AppendFormat("\t{0} sectors between last block of a cylinder and first block of the next one", page.CylinderSkew).AppendLine(); if (page.SSEC) sb.AppendLine("\tDrive supports soft-sectoring format"); if (page.HSEC) sb.AppendLine("\tDrive supports hard-sectoring format"); if (page.RMB) sb.AppendLine("\tDrive media is removable"); if (page.SURF) sb.AppendLine("\tSector addressing is progressively incremented in one surface before going to the next"); else sb.AppendLine("\tSector addressing is progressively incremented in one cylinder before going to the next"); return sb.ToString(); } #endregion Mode Page 0x03: Format device page #region Mode Page 0x0B: Medium types supported page /// /// Disconnect-reconnect page /// Page code 0x0B /// 8 bytes in SCSI-2 /// public struct ModePage_0B { /// /// Parameters can be saved /// public bool PS; public byte MediumType1; public byte MediumType2; public byte MediumType3; public byte MediumType4; } public static ModePage_0B? DecodeModePage_0B(byte[] pageResponse) { if (pageResponse == null) return null; if ((pageResponse[0] & 0x3F) != 0x0B) return null; if (pageResponse[1] + 2 != pageResponse.Length) return null; if (pageResponse.Length < 8) return null; ModePage_0B decoded = new ModePage_0B(); decoded.PS |= (pageResponse[0] & 0x80) == 0x80; decoded.MediumType1 = pageResponse[4]; decoded.MediumType2 = pageResponse[5]; decoded.MediumType3 = pageResponse[6]; decoded.MediumType4 = pageResponse[7]; return decoded; } public static string PrettifyModePage_0B(byte[] pageResponse) { return PrettifyModePage_0B(DecodeModePage_0B(pageResponse)); } public static string PrettifyModePage_0B(ModePage_0B? modePage) { if (!modePage.HasValue) return null; ModePage_0B page = modePage.Value; StringBuilder sb = new StringBuilder(); sb.AppendLine("SCSI Medium types supported page:"); if (page.PS) sb.AppendLine("\tParameters can be saved"); // TODO: Implement it when all known medium types are supported sb.AppendLine("Not yet implemented"); return sb.ToString(); } #endregion Mode Page 0x0B: Medium types supported page } }