// /*************************************************************************** // 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; using System.Collections.Generic; namespace DiscImageChef.Decoders.SCSI { public static class Modes { public struct BlockDescriptor { public DensityType Density; public ulong Blocks; public uint 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 += (uint)(modeResponse[5 + i * 8 + 4] << 16); header.BlockDescriptors[i].BlockLength += (uint)(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 Sense Header:"); switch (deviceType) { case PeripheralDeviceTypes.DirectAccess: { if (header.Value.MediumType != MediumTypes.Default) { sb.Append("\tMedium 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; case MediumTypes.Type3Floppy: sb.AppendLine("3.5-inch, 135 tpi, 12362 bits/radian, double-sided MFM (aka 1.25Mb)"); break; case MediumTypes.HDFloppy: sb.AppendLine("3.5-inch, 135 tpi, 15916 bits/radian, double-sided MFM (aka 1.44Mb)"); break; default: sb.AppendFormat("Unknown medium type 0x{0:X2}", (byte)header.Value.MediumType).AppendLine(); break; } } if (header.Value.WriteProtected) sb.AppendLine("\tMedium is write protected"); if (header.Value.DPOFUA) sb.AppendLine("\tDrive supports DPO and FUA bits"); if (header.Value.BlockDescriptors != null) { 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}", (byte)descriptor.Density); break; } if (density != "") { if (descriptor.Blocks == 0) sb.AppendFormat("\tAll remaining blocks have {0} and are {1} bytes each", density, descriptor.BlockLength).AppendLine(); else sb.AppendFormat("\t{0} blocks have {1} and are {2} bytes each", descriptor.Blocks, density, descriptor.BlockLength).AppendLine(); } else { if (descriptor.Blocks == 0) sb.AppendFormat("\tAll remaining blocks are {0} bytes each", descriptor.BlockLength).AppendLine(); else sb.AppendFormat("\t{0} blocks are {1} bytes each", descriptor.Blocks, descriptor.BlockLength).AppendLine(); } } } break; } case PeripheralDeviceTypes.SequentialAccess: { switch (header.Value.BufferedMode) { case 0: sb.AppendLine("\tDevice writes directly to media"); break; case 1: sb.AppendLine("\tDevice uses a write cache"); break; case 2: sb.AppendLine("\tDevice uses a write cache but doesn't return until cache is flushed"); break; default: sb.AppendFormat("\tUnknown buffered mode code 0x{0:X2}", (byte)header.Value.BufferedMode).AppendLine(); break; } if (header.Value.Speed == 0) sb.AppendLine("\tDevice uses default speed"); else sb.AppendFormat("\tDevice uses speed {0}", header.Value.Speed).AppendLine(); if (header.Value.WriteProtected) sb.AppendLine("\tMedium is write protected"); if (header.Value.MediumType != MediumTypes.Default) { sb.Append("\tMedium is "); switch (header.Value.MediumType) { 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}", (byte)header.Value.MediumType).AppendLine(); break; } } if (header.Value.BlockDescriptors != null) { 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("\tAll remaining blocks conform to {0} and have a variable length", density).AppendLine(); else sb.AppendFormat("\tAll remaining blocks conform to {0} and are {1} bytes each", density, descriptor.BlockLength).AppendLine(); } else { if (descriptor.BlockLength == 0) sb.AppendFormat("\t{0} blocks conform to {1} and have a variable length", descriptor.Blocks, density).AppendLine(); else sb.AppendFormat("\t{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("\tAll remaining blocks have a variable length").AppendLine(); else sb.AppendFormat("\tAll remaining blocks are {0} bytes each", descriptor.BlockLength).AppendLine(); } else { if (descriptor.BlockLength == 0) sb.AppendFormat("\t{0} blocks have a variable length", descriptor.Blocks).AppendLine(); else sb.AppendFormat("\t{0} blocks are {1} bytes each", descriptor.Blocks, descriptor.BlockLength).AppendLine(); } } } } break; } case PeripheralDeviceTypes.PrinterDevice: { switch (header.Value.BufferedMode) { case 0: sb.AppendLine("\tDevice prints directly"); break; case 1: sb.AppendLine("\tDevice uses a print cache"); break; default: sb.AppendFormat("\tUnknown buffered mode code 0x{0:X2}", header.Value.BufferedMode).AppendLine(); break; } break; } case PeripheralDeviceTypes.OpticalDevice: { if (header.Value.MediumType != MediumTypes.Default) { sb.Append("\tMedium 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("\tMedium is write protected"); if (header.Value.EBC) sb.AppendLine("\tBlank checking during write is enabled"); if (header.Value.DPOFUA) sb.AppendLine("\tDrive supports DPO and FUA bits"); if (header.Value.BlockDescriptors != null) { 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("\tAll remaining blocks are {0} and have a variable length", density).AppendLine(); else sb.AppendFormat("\tAll remaining blocks are {0} and are {1} bytes each", density, descriptor.BlockLength).AppendLine(); } else { if (descriptor.BlockLength == 0) sb.AppendFormat("\t{0} blocks are {1} and have a variable length", descriptor.Blocks, density).AppendLine(); else sb.AppendFormat("\t{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("\tAll remaining blocks have a variable length").AppendLine(); else sb.AppendFormat("\tAll remaining blocks are {0} bytes each", descriptor.BlockLength).AppendLine(); } else { if (descriptor.BlockLength == 0) sb.AppendFormat("\t{0} blocks have a variable length", descriptor.Blocks).AppendLine(); else sb.AppendFormat("\t{0} blocks are {1} bytes each", descriptor.Blocks, descriptor.BlockLength).AppendLine(); } } } } break; } case PeripheralDeviceTypes.MultiMediaDevice: { sb.Append("\tMedium 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; case MediumTypes.Unknown_CD: sb.AppendLine("Unknown medium type"); break; case MediumTypes.HybridCD: sb.AppendLine("120 mm Hybrid disc (Photo CD)"); break; case MediumTypes.Unknown_CDR: sb.AppendLine("Unknown size CD-R"); break; case MediumTypes.CDR: sb.AppendLine("120 mm CD-R with data only"); break; case MediumTypes.CDR_DA: sb.AppendLine("120 mm CD-R with audio only"); break; case MediumTypes.CDR_Mixed: sb.AppendLine("120 mm CD-R with data and audio"); break; case MediumTypes.HybridCDR: sb.AppendLine("120 mm Hybrid CD-R (Photo CD)"); break; case MediumTypes.CDR_80: sb.AppendLine("80 mm CD-R with data only"); break; case MediumTypes.CDR_DA_80: sb.AppendLine("80 mm CD-R with audio only"); break; case MediumTypes.CDR_Mixed_80: sb.AppendLine("80 mm CD-R with data and audio"); break; case MediumTypes.HybridCDR_80: sb.AppendLine("80 mm Hybrid CD-R (Photo CD)"); break; case MediumTypes.Unknown_CDRW: sb.AppendLine("Unknown size CD-RW"); break; case MediumTypes.CDRW: sb.AppendLine("120 mm CD-RW with data only"); break; case MediumTypes.CDRW_DA: sb.AppendLine("120 mm CD-RW with audio only"); break; case MediumTypes.CDRW_Mixed: sb.AppendLine("120 mm CD-RW with data and audio"); break; case MediumTypes.HybridCDRW: sb.AppendLine("120 mm Hybrid CD-RW (Photo CD)"); break; case MediumTypes.CDRW_80: sb.AppendLine("80 mm CD-RW with data only"); break; case MediumTypes.CDRW_DA_80: sb.AppendLine("80 mm CD-RW with audio only"); break; case MediumTypes.CDRW_Mixed_80: sb.AppendLine("80 mm CD-RW with data and audio"); break; case MediumTypes.HybridCDRW_80: sb.AppendLine("80 mm Hybrid CD-RW (Photo CD)"); break; case MediumTypes.Unknown_HD: sb.AppendLine("Unknown size HD disc"); break; case MediumTypes.HD: sb.AppendLine("120 mm HD disc"); break; case MediumTypes.HD_80: sb.AppendLine("80 mm HD disc"); break; case MediumTypes.NoDisc: sb.AppendLine("No disc inserted, tray closed or caddy inserted"); break; case MediumTypes.TrayOpen: sb.AppendLine("Tray open or no caddy inserted"); break; case MediumTypes.MediumError: sb.AppendLine("Tray closed or caddy inserted but medium error"); break; case MediumTypes.UnknownBlockDevice: sb.AppendLine("Unknown block device"); break; case MediumTypes.ReadOnlyBlockDevice: sb.AppendLine("Read-only block device"); break; case MediumTypes.ReadWriteBlockDevice: sb.AppendLine("Read/Write block device"); break; default: sb.AppendFormat("Unknown medium type 0x{0:X2}", header.Value.MediumType).AppendLine(); break; } if (header.Value.WriteProtected) sb.AppendLine("\tMedium is write protected"); if (header.Value.DPOFUA) sb.AppendLine("\tDrive supports DPO and FUA bits"); if (header.Value.BlockDescriptors != null) { 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("\tAll remaining blocks have {0} and are {1} bytes each", density, descriptor.BlockLength).AppendLine(); else sb.AppendFormat("\t{0} blocks have {1} and are {2} bytes each", descriptor.Blocks, density, descriptor.BlockLength).AppendLine(); } else { if (descriptor.Blocks == 0) sb.AppendFormat("\tAll remaining blocks are {0} bytes each", descriptor.BlockLength).AppendLine(); else sb.AppendFormat("\t{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]; bool longLBA = (modeResponse[4] & 0x01) == 0x01; if (blockDescLength > 0) { if (longLBA) { header.BlockDescriptors = new BlockDescriptor[blockDescLength / 16]; for (int i = 0; i < header.BlockDescriptors.Length; i++) { header.BlockDescriptors[i] = new BlockDescriptor(); header.BlockDescriptors[i].Density = DensityType.Default; byte[] temp = new byte[8]; temp[0] = modeResponse[7 + i * 16 + 8]; temp[1] = modeResponse[6 + i * 16 + 8]; temp[2] = modeResponse[5 + i * 16 + 8]; temp[3] = modeResponse[4 + i * 16 + 8]; temp[4] = modeResponse[3 + i * 16 + 8]; temp[5] = modeResponse[2 + i * 16 + 8]; temp[6] = modeResponse[1 + i * 16 + 8]; temp[7] = modeResponse[0 + i * 16 + 8]; header.BlockDescriptors[i].Blocks = BitConverter.ToUInt64(temp, 0); header.BlockDescriptors[i].BlockLength += (uint)(modeResponse[15 + i * 16 + 8] << 24); header.BlockDescriptors[i].BlockLength += (uint)(modeResponse[14 + i * 16 + 8] << 16); header.BlockDescriptors[i].BlockLength += (uint)(modeResponse[13 + i * 16 + 8] << 8); header.BlockDescriptors[i].BlockLength += modeResponse[12 + i * 16 + 8]; } } else { header.BlockDescriptors = new BlockDescriptor[blockDescLength / 8]; for (int i = 0; i < header.BlockDescriptors.Length; i++) { header.BlockDescriptors[i] = new BlockDescriptor(); if (deviceType != PeripheralDeviceTypes.DirectAccess) { header.BlockDescriptors[i].Density = (DensityType)modeResponse[0 + i * 8 + 8]; } else { header.BlockDescriptors[i].Density = DensityType.Default; header.BlockDescriptors[i].Blocks += (ulong)(modeResponse[0 + i * 8 + 8] << 24); } 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 += (uint)(modeResponse[5 + i * 8 + 8] << 16); header.BlockDescriptors[i].BlockLength += (uint)(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, SPC-3, SPC-4, SPC-5 /// 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] & 0x40) == 0x40) 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, SPC-1, SPC-2, SPC-3, SPC-4, SPC-5 /// 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] & 0x40) == 0x40) 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 /// 20 bytes in SBC-1, SBC-2, SBC-3 /// 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; /// /// Manual cache controlling /// public bool IC; /// /// Abort pre-fetch /// public bool ABPF; /// /// Caching analysis permitted /// public bool CAP; /// /// Pre-fetch over discontinuities /// public bool Disc; /// /// is to be used to control caching segmentation /// public bool Size; /// /// Force sequential write /// public bool FSW; /// /// Logical block cache segment size /// public bool LBCSS; /// /// Disable read-ahead /// public bool DRA; /// /// How many segments should the cache be divided upon /// public byte CacheSegments; /// /// How many bytes should the cache be divided upon /// public ushort CacheSegmentSize; /// /// How many bytes should be used as a buffer when all other cached data cannot be evicted /// public uint NonCacheSegmentSize; public bool NV_DIS; } public static ModePage_08? DecodeModePage_08(byte[] pageResponse) { if (pageResponse == null) return null; if ((pageResponse[0] & 0x40) == 0x40) 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]); if (pageResponse.Length < 20) return decoded; decoded.IC |= (pageResponse[2] & 0x80) == 0x80; decoded.ABPF |= (pageResponse[2] & 0x40) == 0x40; decoded.CAP |= (pageResponse[2] & 0x20) == 0x20; decoded.Disc |= (pageResponse[2] & 0x10) == 0x10; decoded.Size |= (pageResponse[2] & 0x08) == 0x08; decoded.FSW |= (pageResponse[12] & 0x80) == 0x80; decoded.LBCSS |= (pageResponse[12] & 0x40) == 0x40; decoded.DRA |= (pageResponse[12] & 0x20) == 0x20; decoded.CacheSegments = pageResponse[13]; decoded.CacheSegmentSize = (ushort)((pageResponse[14] << 8) + pageResponse[15]); decoded.NonCacheSegmentSize = (uint)((pageResponse[17] << 16) + (pageResponse[18] << 8) + pageResponse[19]); decoded.NV_DIS |= (pageResponse[12] & 0x01) == 0x01; 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.DRA) sb.AppendLine("\tRead-ahead is disabled"); else { 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(); if (page.IC) sb.AppendLine("\tDevice should use number of cache segments or cache segment size for caching"); if (page.ABPF) sb.AppendLine("\tPre-fetch should be aborted upong receiving a new command"); if (page.CAP) sb.AppendLine("\tCaching analysis is permitted"); if (page.Disc) sb.AppendLine("\tPre-fetch can continue across discontinuities (such as cylinders or tracks)"); } } if (page.FSW) sb.AppendLine("\tDrive should not reorder the sequence of write commands to be faster"); if (page.Size) { if (page.CacheSegmentSize > 0) { if (page.LBCSS) sb.AppendFormat("\tDrive cache segments should be {0} blocks long", page.CacheSegmentSize).AppendLine(); else sb.AppendFormat("\tDrive cache segments should be {0} bytes long", page.CacheSegmentSize).AppendLine(); } } else { if (page.CacheSegments > 0) sb.AppendFormat("\tDrive should have {0} cache segments", page.CacheSegments).AppendLine(); } if (page.NonCacheSegmentSize > 0) sb.AppendFormat("\tDrive shall allocate {0} bytes to buffer even when all cached data cannot be evicted", page.NonCacheSegmentSize).AppendLine(); if (page.NV_DIS) sb.AppendLine("\tNon-Volatile cache is disabled"); 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, SBC-1 /// 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] & 0x40) == 0x40) 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, SBC-1 /// 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] & 0x40) == 0x40) 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] & 0x40) == 0x40) 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 #region Mode Page 0x0C: Notch page // TODO: Implement this page #endregion Mode Page 0x0C: Notch page #region Mode Page 0x01: Read-write error recovery page /// /// Disconnect-reconnect page /// Page code 0x01 /// 12 bytes in SCSI-2, SBC-1, SBC-2 /// public struct ModePage_01 { /// /// Parameters can be saved /// public bool PS; /// /// Automatic Write Reallocation Enabled /// public bool AWRE; /// /// Automatic Read Reallocation Enabled /// public bool ARRE; /// /// Transfer block /// public bool TB; /// /// Read continuous /// public bool RC; /// /// Enable early recovery /// public bool EER; /// /// Post error reporting /// public bool PER; /// /// Disable transfer on error /// public bool DTE; /// /// Disable correction /// public bool DCR; /// /// How many times to retry a read operation /// public byte ReadRetryCount; /// /// How many bits of largest data burst error is maximum to apply error correction on it /// public byte CorrectionSpan; /// /// Offset to move the heads /// public sbyte HeadOffsetCount; /// /// Incremental position to which the recovered data strobe shall be adjusted /// public sbyte DataStrobeOffsetCount; /// /// How many times to retry a write operation /// public byte WriteRetryCount; /// /// Maximum time in ms to use in data error recovery procedures /// public ushort RecoveryTimeLimit; /// /// Logical block provisioning error reporting is enabled /// public bool LBPERE; } public static ModePage_01? DecodeModePage_01(byte[] pageResponse) { if (pageResponse == null) return null; if ((pageResponse[0] & 0x40) == 0x40) return null; if ((pageResponse[0] & 0x3F) != 0x01) return null; if (pageResponse[1] + 2 != pageResponse.Length) return null; if (pageResponse.Length < 8) return null; ModePage_01 decoded = new ModePage_01(); decoded.PS |= (pageResponse[0] & 0x80) == 0x80; decoded.AWRE |= (pageResponse[2] & 0x80) == 0x80; decoded.ARRE |= (pageResponse[2] & 0x40) == 0x40; decoded.TB |= (pageResponse[2] & 0x20) == 0x20; decoded.RC |= (pageResponse[2] & 0x10) == 0x10; decoded.EER |= (pageResponse[2] & 0x08) == 0x08; decoded.PER |= (pageResponse[2] & 0x04) == 0x04; decoded.DTE |= (pageResponse[2] & 0x02) == 0x02; decoded.DCR |= (pageResponse[2] & 0x01) == 0x01; decoded.ReadRetryCount = pageResponse[3]; decoded.CorrectionSpan = pageResponse[4]; decoded.HeadOffsetCount = (sbyte)pageResponse[5]; decoded.DataStrobeOffsetCount = (sbyte)pageResponse[6]; if (pageResponse.Length < 12) return decoded; decoded.WriteRetryCount = pageResponse[8]; decoded.RecoveryTimeLimit = (ushort)((pageResponse[10] << 8) + pageResponse[11]); decoded.LBPERE |= (pageResponse[7] & 0x80) == 0x80; return decoded; } public static string PrettifyModePage_01(byte[] pageResponse) { return PrettifyModePage_01(DecodeModePage_01(pageResponse)); } public static string PrettifyModePage_01(ModePage_01? modePage) { if (!modePage.HasValue) return null; ModePage_01 page = modePage.Value; StringBuilder sb = new StringBuilder(); sb.AppendLine("SCSI Read-write error recovery page:"); if (page.PS) sb.AppendLine("\tParameters can be saved"); if (page.AWRE) sb.AppendLine("\tAutomatic write reallocation is enabled"); if (page.ARRE) sb.AppendLine("\tAutomatic read reallocation is enabled"); if (page.TB) sb.AppendLine("\tData not recovered within limits shall be transferred back before a CHECK CONDITION"); if (page.RC) sb.AppendLine("\tDrive will transfer the entire requested length without delaying to perform error recovery"); if (page.EER) sb.AppendLine("\tDrive will use the most expedient form of error recovery first"); if (page.PER) sb.AppendLine("\tDrive shall report recovered errors"); if (page.DTE) sb.AppendLine("\tTransfer will be terminated upon error detection"); if (page.DCR) sb.AppendLine("\tError correction is disabled"); if (page.ReadRetryCount > 0) sb.AppendFormat("\tDrive will repeat read operations {0} times", page.ReadRetryCount).AppendLine(); if (page.WriteRetryCount > 0) sb.AppendFormat("\tDrive will repeat write operations {0} times", page.WriteRetryCount).AppendLine(); if (page.RecoveryTimeLimit > 0) sb.AppendFormat("\tDrive will employ a maximum of {0} ms to recover data", page.RecoveryTimeLimit).AppendLine(); if (page.LBPERE) sb.AppendLine("Logical block provisioning error reporting is enabled"); return sb.ToString(); } #endregion Mode Page 0x01: Read-write error recovery page #region Mode Page 0x04: Rigid disk drive geometry page /// /// Disconnect-reconnect page /// Page code 0x04 /// 24 bytes in SCSI-2, SBC-1 /// public struct ModePage_04 { /// /// Parameters can be saved /// public bool PS; /// /// Cylinders used for data storage /// public uint Cylinders; /// /// Heads for reading and/or writing /// public byte Heads; /// /// Cylinder where write precompensation starts /// public uint WritePrecompCylinder; /// /// Cylinder where write current reduction starts /// public uint WriteReduceCylinder; /// /// Step rate in 100 ns units /// public ushort DriveStepRate; /// /// Cylinder where the heads park /// public int LandingCylinder; /// /// Rotational position locking /// public byte RPL; /// /// Rotational skew to apply when synchronized /// public byte RotationalOffset; /// /// Medium speed in rpm /// public ushort MediumRotationRate; } public static ModePage_04? DecodeModePage_04(byte[] pageResponse) { if (pageResponse == null) return null; if ((pageResponse[0] & 0x40) == 0x40) return null; if ((pageResponse[0] & 0x3F) != 0x04) return null; if (pageResponse[1] + 2 != pageResponse.Length) return null; if (pageResponse.Length < 24) return null; ModePage_04 decoded = new ModePage_04(); decoded.PS |= (pageResponse[0] & 0x80) == 0x80; decoded.Cylinders = (uint)((pageResponse[2] << 16) + (pageResponse[3] << 8) + pageResponse[4]); decoded.Heads = pageResponse[5]; decoded.WritePrecompCylinder = (uint)((pageResponse[6] << 16) + (pageResponse[7] << 8) + pageResponse[8]); decoded.WriteReduceCylinder = (uint)((pageResponse[9] << 16) + (pageResponse[10] << 8) + pageResponse[11]); decoded.DriveStepRate = (ushort)((pageResponse[12] << 8) + pageResponse[13]); decoded.LandingCylinder = ((pageResponse[14] << 16) + (pageResponse[15] << 8) + pageResponse[16]); decoded.RPL = (byte)(pageResponse[17] & 0x03); decoded.RotationalOffset = pageResponse[18]; decoded.MediumRotationRate = (ushort)((pageResponse[20] << 8) + pageResponse[21]); return decoded; } public static string PrettifyModePage_04(byte[] pageResponse) { return PrettifyModePage_04(DecodeModePage_04(pageResponse)); } public static string PrettifyModePage_04(ModePage_04? modePage) { if (!modePage.HasValue) return null; ModePage_04 page = modePage.Value; StringBuilder sb = new StringBuilder(); sb.AppendLine("SCSI Rigid disk drive geometry page:"); if (page.PS) sb.AppendLine("\tParameters can be saved"); sb.AppendFormat("\t{0} heads", page.Heads).AppendLine(); sb.AppendFormat("\t{0} cylinders", page.Cylinders).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} ns", (uint)page.DriveStepRate * 100).AppendLine(); sb.AppendFormat("\tHeads park in cylinder {0}", page.LandingCylinder).AppendLine(); if (page.MediumRotationRate > 0) sb.AppendFormat("\tMedium rotates at {0} rpm", page.MediumRotationRate).AppendLine(); switch (page.RPL) { case 0: sb.AppendLine("\tSpindle synchronization is disable or unsupported"); break; case 1: sb.AppendLine("\tTarget operates as a synchronized-spindle slave"); break; case 2: sb.AppendLine("\tTarget operates as a synchronized-spindle master"); break; case 3: sb.AppendLine("\tTarget operates as a synchronized-spindle master control"); break; } return sb.ToString(); } #endregion Mode Page 0x04: Rigid disk drive geometry page #region Mode Page 0x07: Verify error recovery page /// /// Disconnect-reconnect page /// Page code 0x07 /// 12 bytes in SCSI-2, SBC-1, SBC-2 /// public struct ModePage_07 { /// /// Parameters can be saved /// public bool PS; /// /// Enable early recovery /// public bool EER; /// /// Post error reporting /// public bool PER; /// /// Disable transfer on error /// public bool DTE; /// /// Disable correction /// public bool DCR; /// /// How many times to retry a verify operation /// public byte VerifyRetryCount; /// /// How many bits of largest data burst error is maximum to apply error correction on it /// public byte CorrectionSpan; /// /// Maximum time in ms to use in data error recovery procedures /// public ushort RecoveryTimeLimit; } public static ModePage_07? DecodeModePage_07(byte[] pageResponse) { if (pageResponse == null) return null; if ((pageResponse[0] & 0x40) == 0x40) return null; if ((pageResponse[0] & 0x3F) != 0x07) return null; if (pageResponse[1] + 2 != pageResponse.Length) return null; if (pageResponse.Length < 12) return null; ModePage_07 decoded = new ModePage_07(); decoded.PS |= (pageResponse[0] & 0x80) == 0x80; decoded.EER |= (pageResponse[2] & 0x08) == 0x08; decoded.PER |= (pageResponse[2] & 0x04) == 0x04; decoded.DTE |= (pageResponse[2] & 0x02) == 0x02; decoded.DCR |= (pageResponse[2] & 0x01) == 0x01; decoded.VerifyRetryCount = pageResponse[3]; decoded.CorrectionSpan = pageResponse[4]; decoded.RecoveryTimeLimit = (ushort)((pageResponse[10] << 8) + pageResponse[11]); return decoded; } public static string PrettifyModePage_07(byte[] pageResponse) { return PrettifyModePage_07(DecodeModePage_07(pageResponse)); } public static string PrettifyModePage_07(ModePage_07? modePage) { if (!modePage.HasValue) return null; ModePage_07 page = modePage.Value; StringBuilder sb = new StringBuilder(); sb.AppendLine("SCSI Verify error recovery page:"); if (page.PS) sb.AppendLine("\tParameters can be saved"); if (page.EER) sb.AppendLine("\tDrive will use the most expedient form of error recovery first"); if (page.PER) sb.AppendLine("\tDrive shall report recovered errors"); if (page.DTE) sb.AppendLine("\tTransfer will be terminated upon error detection"); if (page.DCR) sb.AppendLine("\tError correction is disabled"); if (page.VerifyRetryCount > 0) sb.AppendFormat("\tDrive will repeat verify operations {0} times", page.VerifyRetryCount).AppendLine(); if (page.RecoveryTimeLimit > 0) sb.AppendFormat("\tDrive will employ a maximum of {0} ms to recover data", page.RecoveryTimeLimit).AppendLine(); return sb.ToString(); } #endregion Mode Page 0x07: Verify error recovery page #region Mode Page 0x10: Device configuration page /// /// Device configuration page /// Page code 0x10 /// 16 bytes in SCSI-2, SSC-1, SSC-2, SSC-3 /// public struct ModePage_10_SSC { /// /// Parameters can be saved /// public bool PS; /// /// Used in mode select to change partition to one specified in /// public bool CAP; /// /// Used in mode select to change format to one specified in /// public bool CAF; /// /// Active format, vendor-specific /// public byte ActiveFormat; /// /// Current logical partition /// public byte ActivePartition; /// /// How full the buffer shall be before writing to medium /// public byte WriteBufferFullRatio; /// /// How empty the buffer shall be before reading more data from the medium /// public byte ReadBufferEmptyRatio; /// /// Delay in 100 ms before buffered data is forcefully written to the medium even before buffer is full /// public ushort WriteDelayTime; /// /// Drive supports recovering data from buffer /// public bool DBR; /// /// Medium has block IDs /// public bool BIS; /// /// Drive recognizes and reports setmarks /// public bool RSmk; /// /// Drive selects best speed /// public bool AVC; /// /// If drive should stop pre-reading on filemarks /// public byte SOCF; /// /// If set, recovered buffer data is LIFO, otherwise, FIFO /// public bool RBO; /// /// Report early warnings /// public bool REW; /// /// Inter-block gap /// public byte GapSize; /// /// End-of-Data format /// public byte EODDefined; /// /// EOD generation enabled /// public bool EEG; /// /// Synchronize data to medium on early warning /// public bool SEW; /// /// Bytes to reduce buffer size on early warning /// public uint BufferSizeEarlyWarning; /// /// Selected data compression algorithm /// public byte SelectedCompression; /// /// Soft write protect /// public bool SWP; /// /// Associated write protect /// public bool ASOCWP; /// /// Persistent write protect /// public bool PERSWP; /// /// Permanent write protect /// public bool PRMWP; public bool BAML; public bool BAM; public byte RewindOnReset; /// /// How drive shall respond to detection of compromised WORM medium integrity /// public byte WTRE; /// /// Respond to commands only if a reservation exists /// public bool OIR; } public static ModePage_10_SSC? DecodeModePage_10_SSC(byte[] pageResponse) { if (pageResponse == null) return null; if ((pageResponse[0] & 0x40) == 0x40) return null; if ((pageResponse[0] & 0x3F) != 0x10) return null; if (pageResponse[1] + 2 != pageResponse.Length) return null; if (pageResponse.Length < 16) return null; ModePage_10_SSC decoded = new ModePage_10_SSC(); decoded.PS |= (pageResponse[0] & 0x80) == 0x80; decoded.CAP |= (pageResponse[2] & 0x40) == 0x40; decoded.CAF |= (pageResponse[2] & 0x20) == 0x20; decoded.ActiveFormat = (byte)(pageResponse[2] & 0x1F); decoded.ActivePartition = pageResponse[3]; decoded.WriteBufferFullRatio = pageResponse[4]; decoded.ReadBufferEmptyRatio = pageResponse[5]; decoded.WriteDelayTime = (ushort)((pageResponse[6] << 8) + pageResponse[7]); decoded.DBR |= (pageResponse[8] & 0x80) == 0x80; decoded.BIS |= (pageResponse[8] & 0x40) == 0x40; decoded.RSmk |= (pageResponse[8] & 0x20) == 0x20; decoded.AVC |= (pageResponse[8] & 0x10) == 0x10; decoded.RBO |= (pageResponse[8] & 0x02) == 0x02; decoded.REW |= (pageResponse[8] & 0x01) == 0x01; decoded.EEG |= (pageResponse[10] & 0x10) == 0x10; decoded.SEW |= (pageResponse[10] & 0x08) == 0x08; decoded.SOCF = (byte)((pageResponse[8] & 0x0C) >> 2); decoded.BufferSizeEarlyWarning = (uint)((pageResponse[11] << 16) + (pageResponse[12] << 8) + pageResponse[13]); decoded.SelectedCompression = pageResponse[14]; decoded.SWP |= (pageResponse[10] & 0x04) == 0x04; decoded.ASOCWP |= (pageResponse[15] & 0x04) == 0x04; decoded.PERSWP |= (pageResponse[15] & 0x02) == 0x02; decoded.PRMWP |= (pageResponse[15] & 0x01) == 0x01; decoded.BAML |= (pageResponse[10] & 0x02) == 0x02; decoded.BAM |= (pageResponse[10] & 0x01) == 0x01; decoded.RewindOnReset = (byte)((pageResponse[15] & 0x18) >> 3); decoded.OIR |= (pageResponse[15] & 0x20) == 0x20; decoded.WTRE = (byte)((pageResponse[15] & 0xC0) >> 6); return decoded; } public static string PrettifyModePage_10_SSC(byte[] pageResponse) { return PrettifyModePage_10_SSC(DecodeModePage_10_SSC(pageResponse)); } public static string PrettifyModePage_10_SSC(ModePage_10_SSC? modePage) { if (!modePage.HasValue) return null; ModePage_10_SSC page = modePage.Value; StringBuilder sb = new StringBuilder(); sb.AppendLine("SCSI Device configuration page:"); if (page.PS) sb.AppendLine("\tParameters can be saved"); sb.AppendFormat("\tActive format: {0}", page.ActiveFormat).AppendLine(); sb.AppendFormat("\tActive partition: {0}", page.ActivePartition).AppendLine(); sb.AppendFormat("\tWrite buffer shall have a full ratio of {0} before being flushed to medium", page.WriteBufferFullRatio).AppendLine(); sb.AppendFormat("\tRead buffer shall have an empty ratio of {0} before more data is read from medium", page.ReadBufferEmptyRatio).AppendLine(); sb.AppendFormat("\tDrive will delay {0} ms before buffered data is forcefully written to the medium even before buffer is full", (int)page.WriteDelayTime * 100).AppendLine(); if (page.DBR) { sb.AppendLine("\tDrive supports recovering data from buffer"); if (page.RBO) sb.AppendLine("\tRecovered buffer data comes in LIFO order"); else sb.AppendLine("\tRecovered buffer data comes in FIFO order"); } if (page.BIS) sb.AppendLine("\tMedium supports block IDs"); if (page.RSmk) sb.AppendLine("\tDrive reports setmarks"); switch (page.SOCF) { case 0: sb.AppendLine("\tDrive will pre-read until buffer is full"); break; case 1: sb.AppendLine("\tDrive will pre-read until one filemark is detected"); break; case 2: sb.AppendLine("\tDrive will pre-read until two filemark is detected"); break; case 3: sb.AppendLine("\tDrive will pre-read until three filemark is detected"); break; } if (page.REW) { sb.AppendLine("\tDrive reports early warnings"); if (page.SEW) sb.AppendLine("\tDrive will synchronize buffer to medium on early warnings"); } switch (page.GapSize) { case 0: break; case 1: sb.AppendLine("\tInter-block gap is long enough to support update in place"); break; case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: case 10: case 11: case 12: case 13: case 14: case 15: sb.AppendFormat("\tInter-block gap is {0} times the device's defined gap size", page.GapSize).AppendLine(); break; default: sb.AppendFormat("\tInter-block gap is unknown value {0}", page.GapSize).AppendLine(); break; } if (page.EEG) sb.AppendLine("\tDrive generates end-of-data"); switch (page.SelectedCompression) { case 0: sb.AppendLine("\tDrive does not use compression"); break; case 1: sb.AppendLine("\tDrive uses default compression"); break; default: sb.AppendFormat("\tDrive uses unknown compression {0}", page.SelectedCompression).AppendLine(); break; } if (page.SWP) sb.AppendLine("\tSoftware write protect is enabled"); if (page.ASOCWP) sb.AppendLine("\tAssociated write protect is enabled"); if (page.PERSWP) sb.AppendLine("\tPersistent write protect is enabled"); if (page.PRMWP) sb.AppendLine("\tPermanent write protect is enabled"); if (page.BAML) { if (page.BAM) sb.AppendLine("\tDrive operates using explicit address mode"); else sb.AppendLine("\tDrive operates using implicit address mode"); } switch (page.RewindOnReset) { case 1: sb.AppendLine("\tDrive shall position to beginning of default data partition on reset"); break; case 2: sb.AppendLine("\tDrive shall maintain its position on reset"); break; } switch (page.WTRE) { case 1: sb.AppendLine("\tDrive will do nothing on WORM tampered medium"); break; case 2: sb.AppendLine("\tDrive will return CHECK CONDITION on WORM tampered medium"); break; } if (page.OIR) sb.AppendLine("\tDrive will only respond to commands if it has received a reservation"); return sb.ToString(); } #endregion Mode Page 0x10: Device configuration page #region Mode Page 0x0E: CD-ROM audio control parameters page /// /// CD-ROM audio control parameters /// Page code 0x0E /// 16 bytes in SCSI-2, MMC-1, MMC-2, MMC-3 /// public struct ModePage_0E { /// /// Parameters can be saved /// public bool PS; /// /// Return status as soon as playback operation starts /// public bool Immed; /// /// Stop on track crossing /// public bool SOTC; /// /// Indicates is valid /// public bool APRVal; /// /// Multiplier for /// public byte LBAFormat; /// /// LBAs per second of audio /// public ushort BlocksPerSecondOfAudio; /// /// Channels output on this port /// public byte OutputPort0ChannelSelection; /// /// Volume level for this port /// public byte OutputPort0Volume; /// /// Channels output on this port /// public byte OutputPort1ChannelSelection; /// /// Volume level for this port /// public byte OutputPort1Volume; /// /// Channels output on this port /// public byte OutputPort2ChannelSelection; /// /// Volume level for this port /// public byte OutputPort2Volume; /// /// Channels output on this port /// public byte OutputPort3ChannelSelection; /// /// Volume level for this port /// public byte OutputPort3Volume; } public static ModePage_0E? DecodeModePage_0E(byte[] pageResponse) { if (pageResponse == null) return null; if ((pageResponse[0] & 0x40) == 0x40) return null; if ((pageResponse[0] & 0x3F) != 0x0E) return null; if (pageResponse[1] + 2 != pageResponse.Length) return null; if (pageResponse.Length < 16) return null; ModePage_0E decoded = new ModePage_0E(); decoded.PS |= (pageResponse[0] & 0x80) == 0x80; decoded.Immed |= (pageResponse[2] & 0x04) == 0x04; decoded.SOTC |= (pageResponse[2] & 0x02) == 0x02; decoded.APRVal |= (pageResponse[5] & 0x80) == 0x80; decoded.LBAFormat = (byte)(pageResponse[5] & 0x0F); decoded.BlocksPerSecondOfAudio = (ushort)((pageResponse[6] << 8) + pageResponse[7]); decoded.OutputPort0ChannelSelection = (byte)(pageResponse[8] & 0x0F); decoded.OutputPort0Volume = pageResponse[9]; decoded.OutputPort1ChannelSelection = (byte)(pageResponse[10] & 0x0F); decoded.OutputPort1Volume = pageResponse[11]; decoded.OutputPort2ChannelSelection = (byte)(pageResponse[12] & 0x0F); decoded.OutputPort2Volume = pageResponse[13]; decoded.OutputPort3ChannelSelection = (byte)(pageResponse[14] & 0x0F); decoded.OutputPort3Volume = pageResponse[15]; return decoded; } public static string PrettifyModePage_0E(byte[] pageResponse) { return PrettifyModePage_0E(DecodeModePage_0E(pageResponse)); } public static string PrettifyModePage_0E(ModePage_0E? modePage) { if (!modePage.HasValue) return null; ModePage_0E page = modePage.Value; StringBuilder sb = new StringBuilder(); sb.AppendLine("SCSI CD-ROM audio control parameters page:"); if (page.PS) sb.AppendLine("\tParameters can be saved"); if (page.Immed) sb.AppendLine("\tDrive will return from playback command immediately"); else sb.AppendLine("\tDrive will return from playback command when playback ends"); if (page.SOTC) sb.AppendLine("\tDrive will stop playback on track end"); if (page.APRVal) { double blocks; if (page.LBAFormat == 8) blocks = page.BlocksPerSecondOfAudio * (1 / 256); else blocks = page.BlocksPerSecondOfAudio; sb.AppendFormat("\tThere are {0} blocks per each second of audio", blocks).AppendLine(); } if (page.OutputPort0ChannelSelection > 0) { sb.Append("\tOutput port 0 has channels "); if ((page.OutputPort0ChannelSelection & 0x01) == 0x01) sb.Append("0 "); if ((page.OutputPort0ChannelSelection & 0x02) == 0x02) sb.Append("1 "); if ((page.OutputPort0ChannelSelection & 0x04) == 0x04) sb.Append("2 "); if ((page.OutputPort0ChannelSelection & 0x08) == 0x08) sb.Append("3 "); switch (page.OutputPort0Volume) { case 0: sb.AppendLine("muted"); break; case 0xFF: sb.AppendLine("at maximum volume"); break; default: sb.AppendFormat("at volume {0}", page.OutputPort0Volume).AppendLine(); break; } } if (page.OutputPort1ChannelSelection > 0) { sb.Append("\tOutput port 1 has channels "); if ((page.OutputPort1ChannelSelection & 0x01) == 0x01) sb.Append("0 "); if ((page.OutputPort1ChannelSelection & 0x02) == 0x02) sb.Append("1 "); if ((page.OutputPort1ChannelSelection & 0x04) == 0x04) sb.Append("2 "); if ((page.OutputPort1ChannelSelection & 0x08) == 0x08) sb.Append("3 "); switch (page.OutputPort1Volume) { case 0: sb.AppendLine("muted"); break; case 0xFF: sb.AppendLine("at maximum volume"); break; default: sb.AppendFormat("at volume {0}", page.OutputPort1Volume).AppendLine(); break; } } if (page.OutputPort2ChannelSelection > 0) { sb.Append("\tOutput port 2 has channels "); if ((page.OutputPort2ChannelSelection & 0x01) == 0x01) sb.Append("0 "); if ((page.OutputPort2ChannelSelection & 0x02) == 0x02) sb.Append("1 "); if ((page.OutputPort2ChannelSelection & 0x04) == 0x04) sb.Append("2 "); if ((page.OutputPort2ChannelSelection & 0x08) == 0x08) sb.Append("3 "); switch (page.OutputPort2Volume) { case 0: sb.AppendLine("muted"); break; case 0xFF: sb.AppendLine("at maximum volume"); break; default: sb.AppendFormat("at volume {0}", page.OutputPort2Volume).AppendLine(); break; } } if (page.OutputPort3ChannelSelection > 0) { sb.Append("\tOutput port 3 has channels "); if ((page.OutputPort3ChannelSelection & 0x01) == 0x01) sb.Append("0 "); if ((page.OutputPort3ChannelSelection & 0x02) == 0x02) sb.Append("1 "); if ((page.OutputPort3ChannelSelection & 0x04) == 0x04) sb.Append("2 "); if ((page.OutputPort3ChannelSelection & 0x08) == 0x08) sb.Append("3 "); switch (page.OutputPort3Volume) { case 0: sb.AppendLine("muted"); break; case 0xFF: sb.AppendLine("at maximum volume"); break; default: sb.AppendFormat("at volume {0}", page.OutputPort3Volume).AppendLine(); break; } } return sb.ToString(); } #endregion Mode Page 0x0E: CD-ROM audio control parameters page #region Mode Page 0x0D: CD-ROM parameteres page /// /// CD-ROM parameteres page /// Page code 0x0D /// 8 bytes in SCSI-2, MMC-1, MMC-2, MMC-3 /// public struct ModePage_0D { /// /// Parameters can be saved /// public bool PS; /// /// Time the drive shall remain in hold track state after seek or read /// public byte InactivityTimerMultiplier; /// /// Seconds per Minute /// public ushort SecondsPerMinute; /// /// Frames per Second /// public ushort FramesPerSecond; } public static ModePage_0D? DecodeModePage_0D(byte[] pageResponse) { if (pageResponse == null) return null; if ((pageResponse[0] & 0x40) == 0x40) return null; if ((pageResponse[0] & 0x3F) != 0x0D) return null; if (pageResponse[1] + 2 != pageResponse.Length) return null; if (pageResponse.Length < 8) return null; ModePage_0D decoded = new ModePage_0D(); decoded.PS |= (pageResponse[0] & 0x80) == 0x80; decoded.InactivityTimerMultiplier = (byte)(pageResponse[3] & 0xF); decoded.SecondsPerMinute = (ushort)((pageResponse[4] << 8) + pageResponse[5]); decoded.FramesPerSecond = (ushort)((pageResponse[6] << 8) + pageResponse[7]); return decoded; } public static string PrettifyModePage_0D(byte[] pageResponse) { return PrettifyModePage_0D(DecodeModePage_0D(pageResponse)); } public static string PrettifyModePage_0D(ModePage_0D? modePage) { if (!modePage.HasValue) return null; ModePage_0D page = modePage.Value; StringBuilder sb = new StringBuilder(); sb.AppendLine("SCSI CD-ROM parameters page:"); if (page.PS) sb.AppendLine("\tParameters can be saved"); switch (page.InactivityTimerMultiplier) { case 0: sb.AppendLine("\tDrive will remain in track hold state a vendor-specified time after a seek or read"); break; case 1: sb.AppendLine("\tDrive will remain in track hold state 125 ms after a seek or read"); break; case 2: sb.AppendLine("\tDrive will remain in track hold state 250 ms after a seek or read"); break; case 3: sb.AppendLine("\tDrive will remain in track hold state 500 ms after a seek or read"); break; case 4: sb.AppendLine("\tDrive will remain in track hold state 1 second after a seek or read"); break; case 5: sb.AppendLine("\tDrive will remain in track hold state 2 seconds after a seek or read"); break; case 6: sb.AppendLine("\tDrive will remain in track hold state 4 seconds after a seek or read"); break; case 7: sb.AppendLine("\tDrive will remain in track hold state 8 seconds after a seek or read"); break; case 8: sb.AppendLine("\tDrive will remain in track hold state 16 seconds after a seek or read"); break; case 9: sb.AppendLine("\tDrive will remain in track hold state 32 seconds after a seek or read"); break; case 10: sb.AppendLine("\tDrive will remain in track hold state 1 minute after a seek or read"); break; case 11: sb.AppendLine("\tDrive will remain in track hold state 2 minutes after a seek or read"); break; case 12: sb.AppendLine("\tDrive will remain in track hold state 4 minutes after a seek or read"); break; case 13: sb.AppendLine("\tDrive will remain in track hold state 8 minutes after a seek or read"); break; case 14: sb.AppendLine("\tDrive will remain in track hold state 16 minutes after a seek or read"); break; case 15: sb.AppendLine("\tDrive will remain in track hold state 32 minutes after a seek or read"); break; } if (page.SecondsPerMinute > 0) sb.AppendFormat("\tEach minute has {0} seconds", page.SecondsPerMinute).AppendLine(); if (page.FramesPerSecond > 0) sb.AppendFormat("\tEach second has {0} frames", page.FramesPerSecond).AppendLine(); return sb.ToString(); } #endregion Mode Page 0x0D: CD-ROM parameteres page #region Mode Page 0x01: Read error recovery page for MultiMedia Devices /// /// Read error recovery page for MultiMedia Devices /// Page code 0x01 /// 8 bytes in SCSI-2, MMC-1 /// 12 bytes in MMC-2, MMC-3 /// public struct ModePage_01_MMC { /// /// Parameters can be saved /// public bool PS; /// /// Error recovery parameter /// public byte Parameter; /// /// How many times to retry a read operation /// public byte ReadRetryCount; /// /// How many times to retry a write operation /// public byte WriteRetryCount; /// /// Maximum time in ms to use in data error recovery procedures /// public ushort RecoveryTimeLimit; } public static ModePage_01_MMC? DecodeModePage_01_MMC(byte[] pageResponse) { if (pageResponse == null) return null; if ((pageResponse[0] & 0x40) == 0x40) return null; if ((pageResponse[0] & 0x3F) != 0x01) return null; if (pageResponse[1] + 2 != pageResponse.Length) return null; if (pageResponse.Length < 8) return null; ModePage_01_MMC decoded = new ModePage_01_MMC(); decoded.PS |= (pageResponse[0] & 0x80) == 0x80; decoded.Parameter = pageResponse[2]; decoded.ReadRetryCount = pageResponse[3]; if (pageResponse.Length < 12) return decoded; decoded.WriteRetryCount = pageResponse[8]; decoded.RecoveryTimeLimit = (ushort)((pageResponse[10] << 8) + pageResponse[11]); return decoded; } public static string PrettifyModePage_01_MMC(byte[] pageResponse) { return PrettifyModePage_01_MMC(DecodeModePage_01_MMC(pageResponse)); } public static string PrettifyModePage_01_MMC(ModePage_01_MMC? modePage) { if (!modePage.HasValue) return null; ModePage_01_MMC page = modePage.Value; StringBuilder sb = new StringBuilder(); sb.AppendLine("SCSI Read error recovery page for MultiMedia Devices:"); if (page.PS) sb.AppendLine("\tParameters can be saved"); if (page.ReadRetryCount > 0) sb.AppendFormat("\tDrive will repeat read operations {0} times", page.ReadRetryCount).AppendLine(); string AllUsed = "\tAll available recovery procedures will be used.\n"; string CIRCRetriesUsed = "\tOnly retries and CIRC are used.\n"; string RetriesUsed = "\tOnly retries are used.\n"; string RecoveredNotReported = "\tRecovered errors will not be reported.\n"; string RecoveredReported = "\tRecovered errors will be reported.\n"; string RecoveredAbort = "\tRecovered errors will be reported and aborted with CHECK CONDITION.\n"; string UnrecECCAbort = "\tUnrecovered ECC errors will return CHECK CONDITION."; string UnrecCIRCAbort = "\tUnrecovered CIRC errors will return CHECK CONDITION."; string UnrecECCNotAbort = "\tUnrecovered ECC errors will not abort the transfer."; string UnrecCIRCNotAbort = "\tUnrecovered CIRC errors will not abort the transfer."; string UnrecECCAbortData = "\tUnrecovered ECC errors will return CHECK CONDITION and the uncorrected data."; string UnrecCIRCAbortData = "\tUnrecovered CIRC errors will return CHECK CONDITION and the uncorrected data."; switch (page.Parameter) { case 0x00: sb.AppendLine(AllUsed + RecoveredNotReported + UnrecECCAbort); break; case 0x01: sb.AppendLine(CIRCRetriesUsed + RecoveredNotReported + UnrecCIRCAbort); break; case 0x04: sb.AppendLine(AllUsed + RecoveredReported + UnrecECCAbort); break; case 0x05: sb.AppendLine(CIRCRetriesUsed + RecoveredReported + UnrecCIRCAbort); break; case 0x06: sb.AppendLine(AllUsed + RecoveredAbort + UnrecECCAbort); break; case 0x07: sb.AppendLine(RetriesUsed + RecoveredAbort + UnrecCIRCAbort); break; case 0x10: sb.AppendLine(AllUsed + RecoveredNotReported + UnrecECCNotAbort); break; case 0x11: sb.AppendLine(CIRCRetriesUsed + RecoveredNotReported + UnrecCIRCNotAbort); break; case 0x14: sb.AppendLine(AllUsed + RecoveredReported + UnrecECCNotAbort); break; case 0x15: sb.AppendLine(CIRCRetriesUsed + RecoveredReported + UnrecCIRCNotAbort); break; case 0x20: sb.AppendLine(AllUsed + RecoveredNotReported + UnrecECCAbortData); break; case 0x21: sb.AppendLine(CIRCRetriesUsed + RecoveredNotReported + UnrecCIRCAbortData); break; case 0x24: sb.AppendLine(AllUsed + RecoveredReported + UnrecECCAbortData); break; case 0x25: sb.AppendLine(CIRCRetriesUsed + RecoveredReported + UnrecCIRCAbortData); break; case 0x26: sb.AppendLine(AllUsed + RecoveredAbort + UnrecECCAbortData); break; case 0x27: sb.AppendLine(RetriesUsed + RecoveredAbort + UnrecCIRCAbortData); break; case 0x30: goto case 0x10; case 0x31: goto case 0x11; case 0x34: goto case 0x14; case 0x35: goto case 0x15; default: sb.AppendFormat("Unknown recovery parameter 0x{0:X2}", page.Parameter).AppendLine(); break; } if (page.WriteRetryCount > 0) sb.AppendFormat("\tDrive will repeat write operations {0} times", page.WriteRetryCount).AppendLine(); if (page.RecoveryTimeLimit > 0) sb.AppendFormat("\tDrive will employ a maximum of {0} ms to recover data", page.RecoveryTimeLimit).AppendLine(); return sb.ToString(); } #endregion Mode Page 0x01: Read error recovery page for MultiMedia Devices #region Mode Page 0x07: Verify error recovery page for MultiMedia Devices /// /// Verify error recovery page for MultiMedia Devices /// Page code 0x07 /// 8 bytes in SCSI-2, MMC-1 /// public struct ModePage_07_MMC { /// /// Parameters can be saved /// public bool PS; /// /// Error recovery parameter /// public byte Parameter; /// /// How many times to retry a verify operation /// public byte VerifyRetryCount; } public static ModePage_07_MMC? DecodeModePage_07_MMC(byte[] pageResponse) { if (pageResponse == null) return null; if ((pageResponse[0] & 0x40) == 0x40) return null; if ((pageResponse[0] & 0x3F) != 0x07) return null; if (pageResponse[1] + 2 != pageResponse.Length) return null; if (pageResponse.Length < 8) return null; ModePage_07_MMC decoded = new ModePage_07_MMC(); decoded.PS |= (pageResponse[0] & 0x80) == 0x80; decoded.Parameter = pageResponse[2]; decoded.VerifyRetryCount = pageResponse[3]; return decoded; } public static string PrettifyModePage_07_MMC(byte[] pageResponse) { return PrettifyModePage_07_MMC(DecodeModePage_07_MMC(pageResponse)); } public static string PrettifyModePage_07_MMC(ModePage_07_MMC? modePage) { if (!modePage.HasValue) return null; ModePage_07_MMC page = modePage.Value; StringBuilder sb = new StringBuilder(); sb.AppendLine("SCSI Verify error recovery page for MultiMedia Devices:"); if (page.PS) sb.AppendLine("\tParameters can be saved"); if (page.VerifyRetryCount > 0) sb.AppendFormat("\tDrive will repeat verify operations {0} times", page.VerifyRetryCount).AppendLine(); string AllUsed = "\tAll available recovery procedures will be used.\n"; string CIRCRetriesUsed = "\tOnly retries and CIRC are used.\n"; string RetriesUsed = "\tOnly retries are used.\n"; string RecoveredNotReported = "\tRecovered errors will not be reported.\n"; string RecoveredReported = "\tRecovered errors will be reported.\n"; string RecoveredAbort = "\tRecovered errors will be reported and aborted with CHECK CONDITION.\n"; string UnrecECCAbort = "\tUnrecovered ECC errors will return CHECK CONDITION."; string UnrecCIRCAbort = "\tUnrecovered CIRC errors will return CHECK CONDITION."; string UnrecECCNotAbort = "\tUnrecovered ECC errors will not abort the transfer."; string UnrecCIRCNotAbort = "\tUnrecovered CIRC errors will not abort the transfer."; string UnrecECCAbortData = "\tUnrecovered ECC errors will return CHECK CONDITION and the uncorrected data."; string UnrecCIRCAbortData = "\tUnrecovered CIRC errors will return CHECK CONDITION and the uncorrected data."; switch (page.Parameter) { case 0x00: sb.AppendLine(AllUsed + RecoveredNotReported + UnrecECCAbort); break; case 0x01: sb.AppendLine(CIRCRetriesUsed + RecoveredNotReported + UnrecCIRCAbort); break; case 0x04: sb.AppendLine(AllUsed + RecoveredReported + UnrecECCAbort); break; case 0x05: sb.AppendLine(CIRCRetriesUsed + RecoveredReported + UnrecCIRCAbort); break; case 0x06: sb.AppendLine(AllUsed + RecoveredAbort + UnrecECCAbort); break; case 0x07: sb.AppendLine(RetriesUsed + RecoveredAbort + UnrecCIRCAbort); break; case 0x10: sb.AppendLine(AllUsed + RecoveredNotReported + UnrecECCNotAbort); break; case 0x11: sb.AppendLine(CIRCRetriesUsed + RecoveredNotReported + UnrecCIRCNotAbort); break; case 0x14: sb.AppendLine(AllUsed + RecoveredReported + UnrecECCNotAbort); break; case 0x15: sb.AppendLine(CIRCRetriesUsed + RecoveredReported + UnrecCIRCNotAbort); break; case 0x20: sb.AppendLine(AllUsed + RecoveredNotReported + UnrecECCAbortData); break; case 0x21: sb.AppendLine(CIRCRetriesUsed + RecoveredNotReported + UnrecCIRCAbortData); break; case 0x24: sb.AppendLine(AllUsed + RecoveredReported + UnrecECCAbortData); break; case 0x25: sb.AppendLine(CIRCRetriesUsed + RecoveredReported + UnrecCIRCAbortData); break; case 0x26: sb.AppendLine(AllUsed + RecoveredAbort + UnrecECCAbortData); break; case 0x27: sb.AppendLine(RetriesUsed + RecoveredAbort + UnrecCIRCAbortData); break; case 0x30: goto case 0x10; case 0x31: goto case 0x11; case 0x34: goto case 0x14; case 0x35: goto case 0x15; default: sb.AppendFormat("Unknown recovery parameter 0x{0:X2}", page.Parameter).AppendLine(); break; } return sb.ToString(); } #endregion Mode Page 0x07: Verify error recovery page for MultiMedia Devices #region Mode Page 0x06: Optical memory page /// /// Optical memory page /// Page code 0x06 /// 4 bytes in SCSI-2 /// public struct ModePage_06 { /// /// Parameters can be saved /// public bool PS; /// /// Report updated block read /// public bool RUBR; } public static ModePage_06? DecodeModePage_06(byte[] pageResponse) { if (pageResponse == null) return null; if ((pageResponse[0] & 0x40) == 0x40) return null; if ((pageResponse[0] & 0x3F) != 0x06) return null; if (pageResponse[1] + 2 != pageResponse.Length) return null; if (pageResponse.Length < 4) return null; ModePage_06 decoded = new ModePage_06(); decoded.PS |= (pageResponse[0] & 0x80) == 0x80; decoded.RUBR |= (pageResponse[2] & 0x01) == 0x01; return decoded; } public static string PrettifyModePage_06(byte[] pageResponse) { return PrettifyModePage_06(DecodeModePage_06(pageResponse)); } public static string PrettifyModePage_06(ModePage_06? modePage) { if (!modePage.HasValue) return null; ModePage_06 page = modePage.Value; StringBuilder sb = new StringBuilder(); sb.AppendLine("SCSI optical memory:"); if (page.PS) sb.AppendLine("\tParameters can be saved"); if (page.RUBR) sb.AppendLine("\tOn reading an updated block drive will return RECOVERED ERROR"); return sb.ToString(); } #endregion Mode Page 0x06: Optical memory page #region Mode Page 0x2A: CD-ROM capabilities page /// /// CD-ROM capabilities page /// Page code 0x2A /// 16 bytes in OB-U0077C /// 20 bytes in SFF-8020i /// 22 bytes in MMC-1 /// 26 bytes in MMC-2 /// Variable bytes in MMC-3 /// public struct ModePage_2A { /// /// Parameters can be saved /// public bool PS; /// /// Drive supports multi-session and/or Photo-CD /// public bool MultiSession; /// /// Drive is capable of reading sectors in Mode 2 Form 2 format /// public bool Mode2Form2; /// /// Drive is capable of reading sectors in Mode 2 Form 1 format /// public bool Mode2Form1; /// /// Drive is capable of playing audio /// public bool AudioPlay; /// /// Drive can return the ISRC /// public bool ISRC; /// /// Drive can return the media catalogue number /// public bool UPC; /// /// Drive can return C2 pointers /// public bool C2Pointer; /// /// Drive can read, deinterlave and correct R-W subchannels /// public bool DeinterlaveSubchannel; /// /// Drive can read interleaved and uncorrected R-W subchannels /// public bool Subchannel; /// /// Drive can continue from a loss of streaming on audio reading /// public bool AccurateCDDA; /// /// Audio can be read as digital data /// public bool CDDACommand; /// /// Loading Mechanism Type /// public byte LoadingMechanism; /// /// Drive can eject discs /// public bool Eject; /// /// Drive's optional prevent jumper status /// public bool PreventJumper; /// /// Current lock status /// public bool LockState; /// /// Drive can lock media /// public bool Lock; /// /// Each channel can be muted independently /// public bool SeparateChannelMute; /// /// Each channel's volume can be controlled independently /// public bool SeparateChannelVolume; /// /// Maximum drive speed in Kbytes/second /// public ushort MaximumSpeed; /// /// Supported volume levels /// public ushort SupportedVolumeLevels; /// /// Buffer size in Kbytes /// public ushort BufferSize; /// /// Current drive speed in Kbytes/second /// public ushort CurrentSpeed; public bool Method2; public bool ReadCDRW; public bool ReadCDR; public bool WriteCDRW; public bool WriteCDR; public bool DigitalPort2; public bool DigitalPort1; public bool Composite; public bool SSS; public bool SDP; public byte Length; public bool LSBF; public bool RCK; public bool BCK; public bool TestWrite; public ushort MaxWriteSpeed; public ushort CurrentWriteSpeed; public bool ReadBarcode; public bool ReadDVDRAM; public bool ReadDVDR; public bool ReadDVDROM; public bool WriteDVDRAM; public bool WriteDVDR; public bool LeadInPW; public bool SCC; public ushort CMRSupported; public bool BUF; public byte RotationControlSelected; public ushort CurrentWriteSpeedSelected; public ModePage_2A_WriteDescriptor[] WriteSpeedPerformanceDescriptors; } public struct ModePage_2A_WriteDescriptor { public byte RotationControl; public ushort WriteSpeed; } public static ModePage_2A? DecodeModePage_2A(byte[] pageResponse) { if (pageResponse == null) return null; if ((pageResponse[0] & 0x40) == 0x40) return null; if ((pageResponse[0] & 0x3F) != 0x2A) return null; if (pageResponse[1] + 2 != pageResponse.Length) return null; if (pageResponse.Length < 16) return null; ModePage_2A decoded = new ModePage_2A(); decoded.PS |= (pageResponse[0] & 0x80) == 0x80; decoded.AudioPlay |= (pageResponse[4] & 0x01) == 0x01; decoded.Mode2Form1 |= (pageResponse[4] & 0x10) == 0x10; decoded.Mode2Form2 |= (pageResponse[4] & 0x20) == 0x20; decoded.MultiSession |= (pageResponse[4] & 0x40) == 0x40; decoded.CDDACommand |= (pageResponse[5] & 0x01) == 0x01; decoded.AccurateCDDA |= (pageResponse[5] & 0x02) == 0x02; decoded.Subchannel |= (pageResponse[5] & 0x04) == 0x04; decoded.DeinterlaveSubchannel |= (pageResponse[5] & 0x08) == 0x08; decoded.C2Pointer |= (pageResponse[5] & 0x10) == 0x10; decoded.UPC |= (pageResponse[5] & 0x20) == 0x20; decoded.ISRC |= (pageResponse[5] & 0x40) == 0x40; decoded.LoadingMechanism = (byte)((pageResponse[6] & 0xE0) >> 5); decoded.Lock |= (pageResponse[6] & 0x01) == 0x01; decoded.LockState |= (pageResponse[6] & 0x02) == 0x02; decoded.PreventJumper |= (pageResponse[6] & 0x04) == 0x04; decoded.Eject |= (pageResponse[6] & 0x08) == 0x08; decoded.SeparateChannelVolume |= (pageResponse[7] & 0x01) == 0x01; decoded.SeparateChannelMute |= (pageResponse[7] & 0x02) == 0x02; decoded.MaximumSpeed = (ushort)((pageResponse[8] << 8) + pageResponse[9]); decoded.SupportedVolumeLevels = (ushort)((pageResponse[10] << 8) + pageResponse[11]); decoded.BufferSize = (ushort)((pageResponse[12] << 8) + pageResponse[13]); decoded.CurrentSpeed = (ushort)((pageResponse[14] << 8) + pageResponse[15]); if (pageResponse.Length < 20) return decoded; decoded.Method2 |= (pageResponse[2] & 0x04) == 0x04; decoded.ReadCDRW |= (pageResponse[2] & 0x02) == 0x02; decoded.ReadCDR |= (pageResponse[2] & 0x01) == 0x01; decoded.WriteCDRW |= (pageResponse[3] & 0x02) == 0x02; decoded.WriteCDR |= (pageResponse[3] & 0x01) == 0x01; decoded.Composite |= (pageResponse[4] & 0x02) == 0x02; decoded.DigitalPort1 |= (pageResponse[4] & 0x04) == 0x04; decoded.DigitalPort2 |= (pageResponse[4] & 0x08) == 0x08; decoded.SDP |= (pageResponse[7] & 0x04) == 0x04; decoded.SSS |= (pageResponse[7] & 0x08) == 0x08; decoded.Length = (byte)((pageResponse[17] & 0x30) >> 4); decoded.LSBF |= (pageResponse[17] & 0x08) == 0x08; decoded.RCK |= (pageResponse[17] & 0x04) == 0x04; decoded.BCK |= (pageResponse[17] & 0x02) == 0x02; if (pageResponse.Length < 22) return decoded; decoded.TestWrite |= (pageResponse[3] & 0x04) == 0x04; decoded.MaxWriteSpeed = (ushort)((pageResponse[18] << 8) + pageResponse[19]); decoded.CurrentWriteSpeed = (ushort)((pageResponse[20] << 8) + pageResponse[21]); decoded.ReadBarcode |= (pageResponse[5] & 0x80) == 0x80; if (pageResponse.Length < 26) return decoded; decoded.ReadDVDRAM |= (pageResponse[2] & 0x20) == 0x20; decoded.ReadDVDR |= (pageResponse[2] & 0x10) == 0x10; decoded.ReadDVDROM |= (pageResponse[2] & 0x08) == 0x08; decoded.WriteDVDRAM |= (pageResponse[3] & 0x20) == 0x20; decoded.WriteDVDR |= (pageResponse[3] & 0x10) == 0x10; decoded.LeadInPW |= (pageResponse[3] & 0x20) == 0x20; decoded.SCC |= (pageResponse[3] & 0x10) == 0x10; decoded.CMRSupported = (ushort)((pageResponse[22] << 8) + pageResponse[23]); if (pageResponse.Length < 32) return decoded; decoded.BUF |= (pageResponse[4] & 0x80) == 0x80; decoded.RotationControlSelected = (byte)(pageResponse[27] & 0x03); decoded.CurrentWriteSpeedSelected = (ushort)((pageResponse[28] << 8) + pageResponse[29]); ushort descriptors = (ushort)((pageResponse[30] << 8) + pageResponse[31]); decoded.WriteSpeedPerformanceDescriptors = new ModePage_2A_WriteDescriptor[descriptors]; for (int i = 0; i < descriptors; i++) { decoded.WriteSpeedPerformanceDescriptors[i] = new ModePage_2A_WriteDescriptor(); decoded.WriteSpeedPerformanceDescriptors[i].RotationControl = (byte)(pageResponse[1 + 32 + i * 4] & 0x07); decoded.WriteSpeedPerformanceDescriptors[i].WriteSpeed = (ushort)((pageResponse[2 + 32 + i * 4] << 8) + pageResponse[3 + 32 + i * 4]); } return decoded; } public static string PrettifyModePage_2A(byte[] pageResponse) { return PrettifyModePage_2A(DecodeModePage_2A(pageResponse)); } public static string PrettifyModePage_2A(ModePage_2A? modePage) { if (!modePage.HasValue) return null; ModePage_2A page = modePage.Value; StringBuilder sb = new StringBuilder(); sb.AppendLine("SCSI CD-ROM capabilities page:"); if (page.PS) sb.AppendLine("\tParameters can be saved"); if (page.AudioPlay) sb.AppendLine("\tDrive can play audio"); if (page.Mode2Form1) sb.AppendLine("\tDrive can read sectors in Mode 2 Form 1 format"); if (page.Mode2Form2) sb.AppendLine("\tDrive can read sectors in Mode 2 Form 2 format"); if (page.MultiSession) sb.AppendLine("\tDrive supports multi-session discs and/or Photo-CD"); if (page.CDDACommand) sb.AppendLine("\tDrive can read digital audio"); if (page.AccurateCDDA) sb.AppendLine("\tDrive can continue from streaming loss"); if (page.Subchannel) sb.AppendLine("\tDrive can read uncorrected and interleaved R-W subchannels"); if (page.DeinterlaveSubchannel) sb.AppendLine("\tDrive can read, deinterleave and correct R-W subchannels"); if (page.C2Pointer) sb.AppendLine("\tDrive supports C2 pointers"); if (page.UPC) sb.AppendLine("\tDrive can read Media Catalogue Number"); if (page.ISRC) sb.AppendLine("\tDrive can read ISRC"); switch (page.LoadingMechanism) { case 0: sb.AppendLine("\tDrive uses media caddy"); break; case 1: sb.AppendLine("\tDrive uses a tray"); break; case 2: sb.AppendLine("\tDrive is pop-up"); break; case 4: sb.AppendLine("\tDrive is a changer with individually changeable discs"); break; case 5: sb.AppendLine("\tDrive is a changer using cartridges"); break; default: sb.AppendFormat("\tDrive uses unknown loading mechanism type {0}", page.LoadingMechanism).AppendLine(); break; } if (page.Lock) sb.AppendLine("\tDrive can lock media"); if (page.PreventJumper) { sb.AppendLine("\tDrive power ups locked"); if (page.LockState) sb.AppendLine("\tDrive is locked, media cannot be ejected or inserted"); else sb.AppendLine("\tDrive is not locked, media can be ejected and inserted"); } else { if (page.LockState) sb.AppendLine("\tDrive is locked, media cannot be ejected, but if empty, can be inserted"); else sb.AppendLine("\tDrive is not locked, media can be ejected and inserted"); } if (page.Eject) sb.AppendLine("\tDrive can eject media"); if (page.SeparateChannelMute) sb.AppendLine("\tEach channel can be muted independently"); if (page.SeparateChannelVolume) sb.AppendLine("\tEach channel's volume can be controlled independently"); if (page.SupportedVolumeLevels > 0) sb.AppendFormat("\tDrive supports {0} volume levels", page.SupportedVolumeLevels).AppendLine(); if (page.BufferSize > 0) sb.AppendFormat("\tDrive has {0} Kbyte of buffer", page.BufferSize).AppendLine(); if (page.MaximumSpeed > 0) sb.AppendFormat("\tDrive's maximum reading speed is {0} Kbyte/sec.", page.MaximumSpeed).AppendLine(); if (page.CurrentSpeed > 0) sb.AppendFormat("\tDrive's current reading speed is {0} Kbyte/sec.", page.CurrentSpeed).AppendLine(); if (page.ReadCDR) { if (page.WriteCDR) sb.AppendLine("\tDrive can read and write CD-R"); else sb.AppendLine("\tDrive can read CD-R"); if (page.Method2) sb.AppendLine("\tDrive supports reading CD-R packet media"); } if (page.ReadCDRW) { if (page.WriteCDRW) sb.AppendLine("\tDrive can read and write CD-RW"); else sb.AppendLine("\tDrive can read CD-RW"); } if (page.ReadDVDROM) sb.AppendLine("\tDrive can read DVD-ROM"); if (page.ReadDVDR) { if (page.WriteDVDR) sb.AppendLine("\tDrive can read and write DVD-R"); else sb.AppendLine("\tDrive can read DVD-R"); } if (page.ReadDVDRAM) { if (page.WriteDVDRAM) sb.AppendLine("\tDrive can read and write DVD-RAM"); else sb.AppendLine("\tDrive can read DVD-RAM"); } if (page.Composite) sb.AppendLine("\tDrive can deliver a compositve audio and video data stream"); if (page.DigitalPort1) sb.AppendLine("\tDrive supports IEC-958 digital output on port 1"); if (page.DigitalPort2) sb.AppendLine("\tDrive supports IEC-958 digital output on port 2"); if (page.SDP) sb.AppendLine("\tDrive contains a changer that can report the exact contents of the slots"); if (page.CurrentWriteSpeedSelected > 0) { if (page.RotationControlSelected == 0) sb.AppendFormat("\tDrive's current writing speed is {0} Kbyte/sec. in CLV mode", page.CurrentWriteSpeedSelected).AppendLine(); else if (page.RotationControlSelected == 1) sb.AppendFormat("\tDrive's current writing speed is {0} Kbyte/sec. in pure CAV mode", page.CurrentWriteSpeedSelected).AppendLine(); } else { if (page.MaxWriteSpeed > 0) sb.AppendFormat("\tDrive's maximum writing speed is {0} Kbyte/sec.", page.MaxWriteSpeed).AppendLine(); if (page.CurrentWriteSpeed > 0) sb.AppendFormat("\tDrive's current writing speed is {0} Kbyte/sec.", page.CurrentWriteSpeed).AppendLine(); } foreach (ModePage_2A_WriteDescriptor descriptor in page.WriteSpeedPerformanceDescriptors) { if (descriptor.WriteSpeed > 0) { if (descriptor.RotationControl == 0) sb.AppendFormat("\tDrive supports writing at {0} Kbyte/sec. in CLV mode", descriptor.WriteSpeed).AppendLine(); else if (descriptor.RotationControl == 1) sb.AppendFormat("\tDrive supports writing at is {0} Kbyte/sec. in pure CAV mode", descriptor.WriteSpeed).AppendLine(); } } if (page.TestWrite) sb.AppendLine("\tDrive supports test writing"); if (page.ReadBarcode) sb.AppendLine("\tDrive can read barcode"); if (page.SCC) sb.AppendLine("\tDrive can read both sides of a disc"); if (page.LeadInPW) sb.AppendLine("\tDrive an read raw R-W subchannel from the Lead-In"); if (page.CMRSupported == 1) sb.AppendLine("\tDrive supports DVD CSS and/or DVD CPPM"); if (page.BUF) sb.AppendLine("\tDrive supports buffer under-run free recording"); return sb.ToString(); } #endregion Mode Page 0x2A: CD-ROM capabilities page #region Mode Page 0x1C: Informational exceptions control page /// /// Informational exceptions control page /// Page code 0x1C /// 12 bytes in SPC-1, SPC-2, SPC-3, SPC-4 /// public struct ModePage_1C { /// /// Parameters can be saved /// public bool PS; /// /// Informational exception operations should not affect performance /// public bool Perf; /// /// Disable informational exception operations /// public bool DExcpt; /// /// Create a test device failure at next interval time /// public bool Test; /// /// Log informational exception conditions /// public bool LogErr; /// /// Method of reporting informational exceptions /// public byte MRIE; /// /// 100 ms period to report an informational exception condition /// public uint IntervalTimer; /// /// How many times to report informational exceptions /// public uint ReportCount; /// /// Enable background functions /// public bool EBF; /// /// Warning reporting enabled /// public bool EWasc; /// /// Enable reporting of background self-test errors /// public bool EBACKERR; } public static ModePage_1C? DecodeModePage_1C(byte[] pageResponse) { if (pageResponse == null) return null; if ((pageResponse[0] & 0x40) == 0x40) return null; if ((pageResponse[0] & 0x3F) != 0x1C) return null; if (pageResponse[1] + 2 != pageResponse.Length) return null; if (pageResponse.Length < 12) return null; ModePage_1C decoded = new ModePage_1C(); decoded.PS |= (pageResponse[0] & 0x80) == 0x80; decoded.Perf |= (pageResponse[2] & 0x80) == 0x80; decoded.DExcpt |= (pageResponse[2] & 0x08) == 0x08; decoded.Test |= (pageResponse[2] & 0x04) == 0x04; decoded.LogErr |= (pageResponse[2] & 0x01) == 0x01; decoded.MRIE = (byte)(pageResponse[3] & 0x0F); decoded.IntervalTimer = (uint)((pageResponse[4] << 24) + (pageResponse[5] << 16) + (pageResponse[6] << 8) + pageResponse[7]); decoded.ReportCount = (uint)((pageResponse[8] << 24) + (pageResponse[9] << 16) + (pageResponse[10] << 8) + pageResponse[11]); decoded.EBF |= (pageResponse[2] & 0x20) == 0x20; decoded.EWasc |= (pageResponse[2] & 0x10) == 0x10; decoded.EBACKERR |= (pageResponse[2] & 0x02) == 0x02; return decoded; } public static string PrettifyModePage_1C(byte[] pageResponse) { return PrettifyModePage_1C(DecodeModePage_1C(pageResponse)); } public static string PrettifyModePage_1C(ModePage_1C? modePage) { if (!modePage.HasValue) return null; ModePage_1C page = modePage.Value; StringBuilder sb = new StringBuilder(); sb.AppendLine("SCSI Informational exceptions control page:"); if (page.PS) sb.AppendLine("\tParameters can be saved"); if (page.DExcpt) sb.AppendLine("\tInformational exceptions are disabled"); else { sb.AppendLine("\tInformational exceptions are enabled"); switch (page.MRIE) { case 0: sb.AppendLine("\tNo reporting of informational exception condition"); break; case 1: sb.AppendLine("\tAsynchronous event reporting of informational exceptions"); break; case 2: sb.AppendLine("\tGenerate unit attention on informational exceptions"); break; case 3: sb.AppendLine("\tConditionally generate recovered error on informational exceptions"); break; case 4: sb.AppendLine("\tUnconditionally generate recovered error on informational exceptions"); break; case 5: sb.AppendLine("\tGenerate no sense on informational exceptions"); break; case 6: sb.AppendLine("\tOnly report informational exception condition on request"); break; default: sb.AppendFormat("\tUnknown method of reporting {0}", page.MRIE).AppendLine(); break; } if (page.Perf) sb.AppendLine("\tInformational exceptions reporting should not affect drive performance"); if (page.Test) sb.AppendLine("\tA test informational exception will raise on next timer"); if (page.LogErr) sb.AppendLine("\tDrive shall log informational exception conditions"); if (page.IntervalTimer > 0) { if (page.IntervalTimer == 0xFFFFFFFF) sb.AppendLine("\tTimer interval is vendor-specific"); else sb.AppendFormat("\tTimer interval is {0} ms", page.IntervalTimer * 100).AppendLine(); } if (page.ReportCount > 0) sb.AppendFormat("\tInformational exception conditions will be reported a maximum of {0} times", page.ReportCount); } if (page.EWasc) sb.AppendLine("\tWarning reporting is enabled"); if (page.EBF) sb.AppendLine("\tBackground functions are enabled"); if (page.EBACKERR) sb.AppendLine("\tDrive will report background self-test errors"); return sb.ToString(); } #endregion Mode Page 0x1C: Informational exceptions control page #region Mode Page 0x1A: Power condition page /// /// Power condition page /// Page code 0x1A /// 12 bytes in SPC-1, SPC-2, SPC-3, SPC-4 /// 40 bytes in SPC-5 /// public struct ModePage_1A { /// /// Parameters can be saved /// public bool PS; /// /// Idle timer activated /// public bool Idle; /// /// Standby timer activated /// public bool Standby; /// /// Idle timer /// public uint IdleTimer; /// /// Standby timer /// public uint StandbyTimer; /// /// Interactions between background functions and power management /// public byte PM_BG_Precedence; /// /// Standby timer Y activated /// public bool Standby_Y; /// /// Idle timer B activated /// public bool Idle_B; /// /// Idle timer C activated /// public bool Idle_C; /// /// Idle timer B /// public uint IdleTimer_B; /// /// Idle timer C /// public uint IdleTimer_C; /// /// Standby timer Y /// public uint StandbyTimer_Y; public byte CCF_Idle; public byte CCF_Standby; public byte CCF_Stopped; } public static ModePage_1A? DecodeModePage_1A(byte[] pageResponse) { if (pageResponse == null) return null; if ((pageResponse[0] & 0x40) == 0x40) return null; if ((pageResponse[0] & 0x3F) != 0x1A) return null; if (pageResponse[1] + 2 != pageResponse.Length) return null; if (pageResponse.Length < 12) return null; ModePage_1A decoded = new ModePage_1A(); decoded.PS |= (pageResponse[0] & 0x80) == 0x80; decoded.Standby |= (pageResponse[3] & 0x01) == 0x01; decoded.Idle |= (pageResponse[3] & 0x02) == 0x02; decoded.IdleTimer = (uint)((pageResponse[4] << 24) + (pageResponse[5] << 16) + (pageResponse[6] << 8) + pageResponse[7]); decoded.StandbyTimer = (uint)((pageResponse[8] << 24) + (pageResponse[9] << 16) + (pageResponse[10] << 8) + pageResponse[11]); if (pageResponse.Length < 40) return decoded; decoded.PM_BG_Precedence = (byte)((pageResponse[2] & 0xC0) >> 6); decoded.Standby_Y |= (pageResponse[2] & 0x01) == 0x01; decoded.Idle_B |= (pageResponse[3] & 0x04) == 0x04; decoded.Idle_C |= (pageResponse[3] & 0x08) == 0x08; decoded.IdleTimer_B = (uint)((pageResponse[12] << 24) + (pageResponse[13] << 16) + (pageResponse[14] << 8) + pageResponse[15]); decoded.IdleTimer_C = (uint)((pageResponse[16] << 24) + (pageResponse[17] << 16) + (pageResponse[18] << 8) + pageResponse[19]); decoded.StandbyTimer_Y = (uint)((pageResponse[20] << 24) + (pageResponse[21] << 16) + (pageResponse[22] << 8) + pageResponse[23]); decoded.CCF_Idle = (byte)((pageResponse[39] & 0xC0) >> 6); decoded.CCF_Standby = (byte)((pageResponse[39] & 0x30) >> 4); decoded.CCF_Stopped = (byte)((pageResponse[39] & 0x0C) >> 2); return decoded; } public static string PrettifyModePage_1A(byte[] pageResponse) { return PrettifyModePage_1A(DecodeModePage_1A(pageResponse)); } public static string PrettifyModePage_1A(ModePage_1A? modePage) { if (!modePage.HasValue) return null; ModePage_1A page = modePage.Value; StringBuilder sb = new StringBuilder(); sb.AppendLine("SCSI Power condition page:"); if (page.PS) sb.AppendLine("\tParameters can be saved"); if ((page.Standby && page.StandbyTimer > 0) || (page.Standby_Y && page.StandbyTimer_Y > 0)) { if (page.Standby && page.StandbyTimer > 0) sb.AppendFormat("\tStandby timer Z is set to {0} ms", page.StandbyTimer * 100).AppendLine(); if (page.Standby_Y && page.StandbyTimer_Y > 0) sb.AppendFormat("\tStandby timer Y is set to {0} ms", page.StandbyTimer_Y * 100).AppendLine(); } else sb.AppendLine("\tDrive will not enter standy mode"); if ((page.Idle && page.IdleTimer > 0) || (page.Idle_B && page.IdleTimer_B > 0) || (page.Idle_C && page.IdleTimer_C > 0)) { if (page.Idle && page.IdleTimer > 0) sb.AppendFormat("\tIdle timer A is set to {0} ms", page.IdleTimer * 100).AppendLine(); if (page.Idle_B && page.IdleTimer_B > 0) sb.AppendFormat("\tIdle timer B is set to {0} ms", page.IdleTimer_B * 100).AppendLine(); if (page.Idle_C && page.IdleTimer_C > 0) sb.AppendFormat("\tIdle timer C is set to {0} ms", page.IdleTimer_C * 100).AppendLine(); } else sb.AppendLine("\tDrive will not enter idle mode"); switch (page.PM_BG_Precedence) { case 0: break; case 1: sb.AppendLine("\tPerforming background functions take precedence over maintaining low power conditions"); break; case 2: sb.AppendLine("\tMaintaining low power conditions take precedence over performing background functions"); break; } return sb.ToString(); } #endregion Mode Page 0x1A: Power condition page #region Mode Page 0x0A subpage 0x01: Control Extension mode page /// /// Control Extension mode page /// Page code 0x0A /// Subpage code 0x01 /// 32 bytes in SPC-3, SPC-4, SPC-5 /// public struct ModePage_0A_S01 { /// /// Parameters can be saved /// public bool PS; /// /// Timestamp outside this standard /// public bool TCMOS; /// /// SCSI precedence /// public bool SCSIP; /// /// Implicit Asymmetric Logical Unit Access Enabled /// public bool IALUAE; /// /// Initial task priority /// public byte InitialPriority; /// /// Device life control disabled /// public bool DLC; /// /// Maximum size of SENSE data in bytes /// public byte MaximumSenseLength; } public static ModePage_0A_S01? DecodeModePage_0A_S01(byte[] pageResponse) { if (pageResponse == null) return null; if ((pageResponse[0] & 0x40) != 0x40) return null; if ((pageResponse[0] & 0x3F) != 0x0A) return null; if (pageResponse[1] != 0x01) return null; if (((pageResponse[2] << 8) + pageResponse[3] + 4) != pageResponse.Length) return null; if (pageResponse.Length < 32) return null; ModePage_0A_S01 decoded = new ModePage_0A_S01(); decoded.PS |= (pageResponse[0] & 0x80) == 0x80; decoded.IALUAE |= (pageResponse[4] & 0x01) == 0x01; decoded.SCSIP |= (pageResponse[4] & 0x02) == 0x02; decoded.TCMOS |= (pageResponse[4] & 0x04) == 0x04; decoded.InitialPriority = (byte)(pageResponse[5] & 0x0F); return decoded; } public static string PrettifyModePage_0A_S01(byte[] pageResponse) { return PrettifyModePage_0A_S01(DecodeModePage_0A_S01(pageResponse)); } public static string PrettifyModePage_0A_S01(ModePage_0A_S01? modePage) { if (!modePage.HasValue) return null; ModePage_0A_S01 page = modePage.Value; StringBuilder sb = new StringBuilder(); sb.AppendLine("SCSI Control extension page:"); if (page.PS) sb.AppendLine("\tParameters can be saved"); if (page.TCMOS) { sb.Append("\tTimestamp can be initialized by methods outside of the SCSI standards"); if (page.SCSIP) sb.Append(", but SCSI's SET TIMESTAMP shall take precedence over them"); sb.AppendLine(); } if (page.IALUAE) sb.AppendLine("\tImplicit Asymmetric Logical Unit Access is enabled"); sb.AppendFormat("\tInitial priority is {0}", page.InitialPriority).AppendLine(); if (page.DLC) sb.AppendLine("\tDevice will not degrade performance to extend its life"); if (page.MaximumSenseLength > 0) sb.AppendFormat("\tMaximum sense data would be {0} bytes", page.MaximumSenseLength).AppendLine(); return sb.ToString(); } #endregion Mode Page 0x0A subpage 0x01: Control Extension mode page #region Mode Page 0x1A subpage 0x01: Power Consumption mode page /// /// Power Consumption mode page /// Page code 0x1A /// Subpage code 0x01 /// 16 bytes in SPC-5 /// public struct ModePage_1A_S01 { /// /// Parameters can be saved /// public bool PS; /// /// Active power level /// public byte ActiveLevel; /// /// Power Consumption VPD identifier in use /// public byte PowerConsumptionIdentifier; } public static ModePage_1A_S01? DecodeModePage_1A_S01(byte[] pageResponse) { if (pageResponse == null) return null; if ((pageResponse[0] & 0x40) != 0x40) return null; if ((pageResponse[0] & 0x3F) != 0x1A) return null; if (pageResponse[1] != 0x01) return null; if (((pageResponse[2] << 8) + pageResponse[3] + 4) != pageResponse.Length) return null; if (pageResponse.Length < 16) return null; ModePage_1A_S01 decoded = new ModePage_1A_S01(); decoded.PS |= (pageResponse[0] & 0x80) == 0x80; decoded.ActiveLevel = (byte)(pageResponse[6] & 0x03); decoded.PowerConsumptionIdentifier = pageResponse[7]; return decoded; } public static string PrettifyModePage_1A_S01(byte[] pageResponse) { return PrettifyModePage_1A_S01(DecodeModePage_1A_S01(pageResponse)); } public static string PrettifyModePage_1A_S01(ModePage_1A_S01? modePage) { if (!modePage.HasValue) return null; ModePage_1A_S01 page = modePage.Value; StringBuilder sb = new StringBuilder(); sb.AppendLine("SCSI Power Consumption page:"); if (page.PS) sb.AppendLine("\tParameters can be saved"); switch (page.ActiveLevel) { case 0: sb.AppendFormat("\tDevice power consumption is dictated by identifier {0} of Power Consumption VPD", page.PowerConsumptionIdentifier).AppendLine(); break; case 1: sb.AppendLine("\tDevice is in highest relative power consumption level"); break; case 2: sb.AppendLine("\tDevice is in intermediate relative power consumption level"); break; case 3: sb.AppendLine("\tDevice is in lowest relative power consumption level"); break; } return sb.ToString(); } #endregion Mode Page 0x1A subpage 0x01: Power Consumption mode page #region Mode Page 0x10: XOR control mode page /// /// XOR control mode page /// Page code 0x10 /// 24 bytes in SBC-1, SBC-2 /// public struct ModePage_10 { /// /// Parameters can be saved /// public bool PS; /// /// Disables XOR operations /// public bool XORDIS; /// /// Maximum transfer length in blocks for a XOR command /// public uint MaxXorWrite; /// /// Maximum regenerate length in blocks /// public uint MaxRegenSize; /// /// Maximum transfer length in blocks for READ during a rebuild /// public uint MaxRebuildRead; /// /// Minimum time in ms between READs during a rebuild /// public ushort RebuildDelay; } public static ModePage_10? DecodeModePage_10(byte[] pageResponse) { if (pageResponse == null) return null; if ((pageResponse[0] & 0x40) == 0x40) return null; if ((pageResponse[0] & 0x3F) != 0x10) return null; if (pageResponse[1] + 2 != pageResponse.Length) return null; if (pageResponse.Length < 24) return null; ModePage_10 decoded = new ModePage_10(); decoded.PS |= (pageResponse[0] & 0x80) == 0x80; decoded.XORDIS |= (pageResponse[2] & 0x02) == 0x02; decoded.MaxXorWrite = (uint)((pageResponse[4] << 24) + (pageResponse[5] << 16) + (pageResponse[6] << 8) + pageResponse[7]); decoded.MaxRegenSize = (uint)((pageResponse[12] << 24) + (pageResponse[13] << 16) + (pageResponse[14] << 8) + pageResponse[15]); decoded.MaxRebuildRead = (uint)((pageResponse[16] << 24) + (pageResponse[17] << 16) + (pageResponse[18] << 8) + pageResponse[19]); decoded.RebuildDelay = (ushort)((pageResponse[22] << 8) + pageResponse[23]); return decoded; } public static string PrettifyModePage_10(byte[] pageResponse) { return PrettifyModePage_10(DecodeModePage_10(pageResponse)); } public static string PrettifyModePage_10(ModePage_10? modePage) { if (!modePage.HasValue) return null; ModePage_10 page = modePage.Value; StringBuilder sb = new StringBuilder(); sb.AppendLine("SCSI XOR control mode page:"); if (page.PS) sb.AppendLine("\tParameters can be saved"); if (page.XORDIS) sb.AppendLine("\tXOR operations are disabled"); else { if (page.MaxXorWrite > 0) sb.AppendFormat("\tDrive accepts a maximum of {0} blocks in a single XOR WRITE command", page.MaxXorWrite).AppendLine(); if (page.MaxRegenSize > 0) sb.AppendFormat("\tDrive accepts a maximum of {0} blocks in a REGENERATE command", page.MaxRegenSize).AppendLine(); if (page.MaxRebuildRead > 0) sb.AppendFormat("\tDrive accepts a maximum of {0} blocks in a READ command during rebuild", page.MaxRebuildRead).AppendLine(); if (page.RebuildDelay > 0) sb.AppendFormat("\tDrive needs a minimum of {0} ms between READ commands during rebuild", page.RebuildDelay).AppendLine(); } return sb.ToString(); } #endregion Mode Page 0x10: XOR control mode page #region Mode Page 0x1C subpage 0x01: Background Control mode page /// /// Background Control mode page /// Page code 0x1A /// Subpage code 0x01 /// 16 bytes in SPC-5 /// public struct ModePage_1C_S01 { /// /// Parameters can be saved /// public bool PS; /// /// Suspend on log full /// public bool S_L_Full; /// /// Log only when intervention required /// public bool LOWIR; /// /// Enable background medium scan /// public bool En_Bms; /// /// Enable background pre-scan /// public bool En_Ps; /// /// Time in hours between background medium scans /// public ushort BackgroundScanInterval; /// /// Maximum time in hours for a background pre-scan to complete /// public ushort BackgroundPrescanTimeLimit; /// /// Minimum time in ms being idle before resuming a background scan /// public ushort MinIdleBeforeBgScan; /// /// Maximum time in ms to start processing commands while performing a background scan /// public ushort MaxTimeSuspendBgScan; } public static ModePage_1C_S01? DecodeModePage_1C_S01(byte[] pageResponse) { if (pageResponse == null) return null; if ((pageResponse[0] & 0x40) != 0x40) return null; if ((pageResponse[0] & 0x3F) != 0x1C) return null; if (pageResponse[1] != 0x01) return null; if (((pageResponse[2] << 8) + pageResponse[3] + 4) != pageResponse.Length) return null; if (pageResponse.Length < 16) return null; ModePage_1C_S01 decoded = new ModePage_1C_S01(); decoded.PS |= (pageResponse[0] & 0x80) == 0x80; decoded.S_L_Full |= (pageResponse[4] & 0x04) == 0x04; decoded.LOWIR |= (pageResponse[4] & 0x02) == 0x02; decoded.En_Bms |= (pageResponse[4] & 0x01) == 0x01; decoded.En_Ps |= (pageResponse[5] & 0x01) == 0x01; decoded.BackgroundScanInterval = (ushort)((pageResponse[6] << 8) + pageResponse[7]); decoded.BackgroundPrescanTimeLimit = (ushort)((pageResponse[8] << 8) + pageResponse[9]); decoded.MinIdleBeforeBgScan = (ushort)((pageResponse[10] << 8) + pageResponse[11]); decoded.MaxTimeSuspendBgScan = (ushort)((pageResponse[12] << 8) + pageResponse[13]); return decoded; } public static string PrettifyModePage_1C_S01(byte[] pageResponse) { return PrettifyModePage_1C_S01(DecodeModePage_1C_S01(pageResponse)); } public static string PrettifyModePage_1C_S01(ModePage_1C_S01? modePage) { if (!modePage.HasValue) return null; ModePage_1C_S01 page = modePage.Value; StringBuilder sb = new StringBuilder(); sb.AppendLine("SCSI Background Control page:"); if (page.PS) sb.AppendLine("\tParameters can be saved"); if (page.S_L_Full) sb.AppendLine("\tBackground scans will be halted if log is full"); if (page.LOWIR) sb.AppendLine("\tBackground scans will only be logged if they require intervention"); if (page.En_Bms) sb.AppendLine("\tBackground medium scans are enabled"); if (page.En_Ps) sb.AppendLine("\tBackground pre-scans are enabled"); if (page.BackgroundScanInterval > 0) sb.AppendFormat("\t{0} hours shall be between the start of a background scan operation and the next", page.BackgroundScanInterval).AppendLine(); if (page.BackgroundPrescanTimeLimit > 0) sb.AppendFormat("\tBackgroun pre-scan operations can take a maximum of {0} hours", page.BackgroundPrescanTimeLimit).AppendLine(); if (page.MinIdleBeforeBgScan > 0) sb.AppendFormat("\tAt least {0} ms must be idle before resuming a suspended background scan operation", page.MinIdleBeforeBgScan).AppendLine(); if (page.MaxTimeSuspendBgScan > 0) sb.AppendFormat("\tAt most {0} ms must be before suspending a background scan operation and processing received commands", page.MaxTimeSuspendBgScan).AppendLine(); return sb.ToString(); } #endregion Mode Page 0x1C subpage 0x01: Background Control mode page #region Mode Page 0x0F: Data compression page /// /// Data compression page /// Page code 0x0F /// 16 bytes in SSC-1, SSC-2, SSC-3 /// public struct ModePage_0F { /// /// Parameters can be saved /// public bool PS; /// /// Data compression enabled /// public bool DCE; /// /// Data compression capable /// public bool DCC; /// /// Data decompression enabled /// public bool DDE; /// /// Report exception on decompression /// public byte RED; /// /// Compression algorithm /// public uint CompressionAlgo; /// /// Decompression algorithm /// public uint DecompressionAlgo; } public static ModePage_0F? DecodeModePage_0F(byte[] pageResponse) { if (pageResponse == null) return null; if ((pageResponse[0] & 0x40) == 0x40) return null; if ((pageResponse[0] & 0x3F) != 0x0F) return null; if (pageResponse[1] + 2 != pageResponse.Length) return null; if (pageResponse.Length < 16) return null; ModePage_0F decoded = new ModePage_0F(); decoded.PS |= (pageResponse[0] & 0x80) == 0x80; decoded.DCE |= (pageResponse[2] & 0x80) == 0x80; decoded.DCC |= (pageResponse[2] & 0x40) == 0x40; decoded.DDE |= (pageResponse[3] & 0x80) == 0x80; decoded.RED = (byte)((pageResponse[3] & 0x60) >> 5); decoded.CompressionAlgo = (uint)((pageResponse[4] << 24) + (pageResponse[5] << 16) + (pageResponse[6] << 8) + pageResponse[7]); decoded.DecompressionAlgo = (uint)((pageResponse[8] << 24) + (pageResponse[9] << 16) + (pageResponse[10] << 8) + pageResponse[11]); return decoded; } public static string PrettifyModePage_0F(byte[] pageResponse) { return PrettifyModePage_0F(DecodeModePage_0F(pageResponse)); } public static string PrettifyModePage_0F(ModePage_0F? modePage) { if (!modePage.HasValue) return null; ModePage_0F page = modePage.Value; StringBuilder sb = new StringBuilder(); sb.AppendLine("SCSI Data compression page:"); if (page.PS) sb.AppendLine("\tParameters can be saved"); if (page.DCC) { sb.AppendLine("\tDrive supports data compression"); if (page.DCE) { sb.Append("\tData compression is enabled with "); switch (page.CompressionAlgo) { case 3: sb.AppendLine("IBM ALDC with 512 byte buffer"); break; case 4: sb.AppendLine("IBM ALDC with 1024 byte buffer"); break; case 5: sb.AppendLine("IBM ALDC with 2048 byte buffer"); break; case 0x10: sb.AppendLine("IBM IDRC"); break; case 0x20: sb.AppendLine("DCLZ"); break; case 0xFF: sb.AppendLine("an unregistered compression algorithm"); break; default: sb.AppendFormat("an unknown algorithm coded {0}", page.CompressionAlgo).AppendLine(); break; } } if (page.DDE) { sb.AppendLine("\tData decompression is enabled"); if (page.DecompressionAlgo == 0) sb.AppendLine("\tLast data read was uncompressed"); else { sb.Append("\tLast data read was compressed with "); switch (page.CompressionAlgo) { case 3: sb.AppendLine("IBM ALDC with 512 byte buffer"); break; case 4: sb.AppendLine("IBM ALDC with 1024 byte buffer"); break; case 5: sb.AppendLine("IBM ALDC with 2048 byte buffer"); break; case 0x10: sb.AppendLine("IBM IDRC"); break; case 0x20: sb.AppendLine("DCLZ"); break; case 0xFF: sb.AppendLine("an unregistered compression algorithm"); break; default: sb.AppendFormat("an unknown algorithm coded {0}", page.CompressionAlgo).AppendLine(); break; } } } sb.AppendFormat("\tReport exception on compression is set to {0}", page.RED).AppendLine(); } else sb.AppendLine("\tDrive does not support data compression"); return sb.ToString(); } #endregion Mode Page 0x0F: Data compression page #region Mode Page 0x1B: Removable Block Access Capabilities page /// /// Removable Block Access Capabilities page /// Page code 0x1B /// 12 bytes in INF-8070 /// public struct ModePage_1B { /// /// Parameters can be saved /// public bool PS; /// /// Supports reporting progress of format /// public bool SRFP; /// /// Non-CD Optical Device /// public bool NCD; /// /// Phase change dual device supporting a CD and a Non-CD Optical devices /// public bool SML; /// /// Total number of LUNs /// public byte TLUN; /// /// System Floppy Type device /// public bool SFLP; } public static ModePage_1B? DecodeModePage_1B(byte[] pageResponse) { if (pageResponse == null) return null; if ((pageResponse[0] & 0x40) == 0x40) return null; if ((pageResponse[0] & 0x3F) != 0x1B) return null; if (pageResponse[1] + 2 != pageResponse.Length) return null; if (pageResponse.Length < 12) return null; ModePage_1B decoded = new ModePage_1B(); decoded.PS |= (pageResponse[0] & 0x80) == 0x80; decoded.SFLP |= (pageResponse[2] & 0x80) == 0x80; decoded.SRFP |= (pageResponse[2] & 0x40) == 0x40; decoded.NCD |= (pageResponse[3] & 0x80) == 0x80; decoded.SML |= (pageResponse[3] & 0x40) == 0x40; decoded.TLUN = (byte)(pageResponse[3] & 0x07); return decoded; } public static string PrettifyModePage_1B(byte[] pageResponse) { return PrettifyModePage_1B(DecodeModePage_1B(pageResponse)); } public static string PrettifyModePage_1B(ModePage_1B? modePage) { if (!modePage.HasValue) return null; ModePage_1B page = modePage.Value; StringBuilder sb = new StringBuilder(); sb.AppendLine("SCSI Removable Block Access Capabilities page:"); if (page.PS) sb.AppendLine("\tParameters can be saved"); if (page.SFLP) sb.AppendLine("\tDrive can be used as a system floppy device"); if (page.SRFP) sb.AppendLine("\tDrive supports reporting progress of format"); if (page.NCD) sb.AppendLine("\tDrive is a Non-CD Optical Device"); if (page.SML) sb.AppendLine("\tDevice is a dual device supporting CD and Non-CD Optical"); if (page.TLUN > 0) sb.AppendFormat("\tDrive supports {0} LUNs", page.TLUN).AppendLine(); return sb.ToString(); } #endregion Mode Page 0x1B: Removable Block Access Capabilities page #region Mode Page 0x1C: Timer & Protect page /// /// Timer & Protect page /// Page code 0x1C /// 8 bytes in INF-8070 /// public struct ModePage_1C_SFF { /// /// Parameters can be saved /// public bool PS; /// /// Time the device shall remain in the current state after seek, read or write operation /// public byte InactivityTimeMultiplier; /// /// Disabled until power cycle /// public bool DISP; /// /// Software Write Protect until Power-down /// public bool SWPP; } public static ModePage_1C_SFF? DecodeModePage_1C_SFF(byte[] pageResponse) { if (pageResponse == null) return null; if ((pageResponse[0] & 0x40) == 0x40) return null; if ((pageResponse[0] & 0x3F) != 0x1C) return null; if (pageResponse[1] + 2 != pageResponse.Length) return null; if (pageResponse.Length < 8) return null; ModePage_1C_SFF decoded = new ModePage_1C_SFF(); decoded.PS |= (pageResponse[0] & 0x80) == 0x80; decoded.DISP |= (pageResponse[2] & 0x02) == 0x02; decoded.SWPP |= (pageResponse[3] & 0x01) == 0x01; decoded.InactivityTimeMultiplier = (byte)(pageResponse[3] & 0x0F); return decoded; } public static string PrettifyModePage_1C_SFF(byte[] pageResponse) { return PrettifyModePage_1C_SFF(DecodeModePage_1C_SFF(pageResponse)); } public static string PrettifyModePage_1C_SFF(ModePage_1C_SFF? modePage) { if (!modePage.HasValue) return null; ModePage_1C_SFF page = modePage.Value; StringBuilder sb = new StringBuilder(); sb.AppendLine("SCSI Timer & Protect page:"); if (page.PS) sb.AppendLine("\tParameters can be saved"); if (page.DISP) sb.AppendLine("\tDrive is disabled until power is cycled"); if (page.SWPP) sb.AppendLine("\tDrive is software write-protected until powered down"); switch (page.InactivityTimeMultiplier) { case 0: sb.AppendLine("\tDrive will remain in same status a vendor-specified time after a seek, read or write operation"); break; case 1: sb.AppendLine("\tDrive will remain in same status 125 ms after a seek, read or write operation"); break; case 2: sb.AppendLine("\tDrive will remain in same status 250 ms after a seek, read or write operation"); break; case 3: sb.AppendLine("\tDrive will remain in same status 500 ms after a seek, read or write operation"); break; case 4: sb.AppendLine("\tDrive will remain in same status 1 second after a seek, read or write operation"); break; case 5: sb.AppendLine("\tDrive will remain in same status 2 seconds after a seek, read or write operation"); break; case 6: sb.AppendLine("\tDrive will remain in same status 4 seconds after a seek, read or write operation"); break; case 7: sb.AppendLine("\tDrive will remain in same status 8 seconds after a seek, read or write operation"); break; case 8: sb.AppendLine("\tDrive will remain in same status 16 seconds after a seek, read or write operation"); break; case 9: sb.AppendLine("\tDrive will remain in same status 32 seconds after a seek, read or write operation"); break; case 10: sb.AppendLine("\tDrive will remain in same status 1 minute after a seek, read or write operation"); break; case 11: sb.AppendLine("\tDrive will remain in same status 2 minutes after a seek, read or write operation"); break; case 12: sb.AppendLine("\tDrive will remain in same status 4 minutes after a seek, read or write operation"); break; case 13: sb.AppendLine("\tDrive will remain in same status 8 minutes after a seek, read or write operation"); break; case 14: sb.AppendLine("\tDrive will remain in same status 16 minutes after a seek, read or write operation"); break; case 15: sb.AppendLine("\tDrive will remain in same status 32 minutes after a seek, read or write operation"); break; } return sb.ToString(); } #endregion Mode Page 0x1C: Timer & Protect page #region Mode Page 0x00: Drive Operation Mode page /// /// Drive Operation Mode page /// Page code 0x00 /// 4 bytes in INF-8070 /// public struct ModePage_00_SFF { /// /// Parameters can be saved /// public bool PS; /// /// Select LUN Mode /// public bool SLM; /// /// Select LUN for rewritable /// public bool SLR; /// /// Disable verify for WRITE /// public bool DVW; /// /// Disable deferred error /// public bool DDE; } public static ModePage_00_SFF? DecodeModePage_00_SFF(byte[] pageResponse) { if (pageResponse == null) return null; if ((pageResponse[0] & 0x40) == 0x40) return null; if ((pageResponse[0] & 0x3F) != 0x00) return null; if (pageResponse[1] + 2 != pageResponse.Length) return null; if (pageResponse.Length < 4) return null; ModePage_00_SFF decoded = new ModePage_00_SFF(); decoded.PS |= (pageResponse[0] & 0x80) == 0x80; decoded.SLM |= (pageResponse[2] & 0x80) == 0x80; decoded.SLR |= (pageResponse[2] & 0x40) == 0x40; decoded.DVW |= (pageResponse[2] & 0x20) == 0x20; decoded.DDE |= (pageResponse[3] & 0x10) == 0x10; return decoded; } public static string PrettifyModePage_00_SFF(byte[] pageResponse) { return PrettifyModePage_00_SFF(DecodeModePage_00_SFF(pageResponse)); } public static string PrettifyModePage_00_SFF(ModePage_00_SFF? modePage) { if (!modePage.HasValue) return null; ModePage_00_SFF page = modePage.Value; StringBuilder sb = new StringBuilder(); sb.AppendLine("SCSI Drive Operation Mode page:"); if (page.PS) sb.AppendLine("\tParameters can be saved"); if (page.DVW) sb.AppendLine("\tVerifying after writing is disabled"); if (page.DDE) sb.AppendLine("\tDrive will abort when a writing error is detected"); if (page.SLM) { sb.Append("\tDrive has two LUNs with rewritable being "); if (page.SLM) sb.AppendLine("LUN 1"); else sb.AppendLine("LUN 0"); } return sb.ToString(); } #endregion Mode Page 0x00: Drive Operation Mode page public struct ModePage { public byte Page; public byte Subpage; public byte[] PageResponse; } public struct DecodedMode { public ModeHeader Header; public ModePage[] Pages; } public static DecodedMode? DecodeMode6(byte[] modeResponse, PeripheralDeviceTypes deviceType) { ModeHeader? hdr = DecodeModeHeader6(modeResponse, deviceType); if (!hdr.HasValue) return null; DecodedMode decoded = new DecodedMode(); decoded.Header = hdr.Value; int blkDrLength = 0; if (decoded.Header.BlockDescriptors != null) blkDrLength = decoded.Header.BlockDescriptors.Length; int offset = 4 + blkDrLength * 8; int length = modeResponse[0] + 1; if (length != modeResponse.Length) return decoded; List listpages = new List(); while (offset < modeResponse.Length) { bool isSubpage = (modeResponse[offset] & 0x40) == 0x40; ModePage pg = new ModePage(); if (isSubpage) { pg.PageResponse = new byte[(modeResponse[offset + 2] << 8) + modeResponse[offset + 3] + 4]; Array.Copy(modeResponse, offset, pg.PageResponse, 0, pg.PageResponse.Length); pg.Page = (byte)(modeResponse[offset] & 0x3F); pg.Subpage = modeResponse[offset + 1]; offset += pg.PageResponse.Length; } else { pg.PageResponse = new byte[modeResponse[offset + 1] + 2]; Array.Copy(modeResponse, offset, pg.PageResponse, 0, pg.PageResponse.Length); pg.Page = (byte)(modeResponse[offset] & 0x3F); pg.Subpage = 0; offset += pg.PageResponse.Length; } listpages.Add(pg); } decoded.Pages = listpages.ToArray(); return decoded; } public static DecodedMode? DecodeMode10(byte[] modeResponse, PeripheralDeviceTypes deviceType) { ModeHeader? hdr = DecodeModeHeader10(modeResponse, deviceType); if (!hdr.HasValue) return null; DecodedMode decoded = new DecodedMode(); decoded.Header = hdr.Value; bool longlba = (modeResponse[4] & 0x01) == 0x01; int offset; int blkDrLength = 0; if (decoded.Header.BlockDescriptors != null) blkDrLength = decoded.Header.BlockDescriptors.Length; if (longlba) offset = 8 + blkDrLength * 16; else offset = 8 + blkDrLength * 8; int length = (modeResponse[0] << 8); length += modeResponse[1]; length += 2; if (length != modeResponse.Length) return decoded; List listpages = new List(); while (offset < modeResponse.Length) { bool isSubpage = (modeResponse[offset] & 0x40) == 0x40; ModePage pg = new ModePage(); if (isSubpage) { pg.PageResponse = new byte[(modeResponse[offset + 2] << 8) + modeResponse[offset + 3] + 4]; if((pg.PageResponse.Length + offset) > modeResponse.Length) return decoded; Array.Copy(modeResponse, offset, pg.PageResponse, 0, pg.PageResponse.Length); pg.Page = (byte)(modeResponse[offset] & 0x3F); pg.Subpage = modeResponse[offset + 1]; offset += pg.PageResponse.Length; } else { pg.PageResponse = new byte[modeResponse[offset + 1] + 2]; if((pg.PageResponse.Length + offset) > modeResponse.Length) return decoded; Array.Copy(modeResponse, offset, pg.PageResponse, 0, pg.PageResponse.Length); pg.Page = (byte)(modeResponse[offset] & 0x3F); pg.Subpage = 0; offset += pg.PageResponse.Length; } listpages.Add(pg); } decoded.Pages = listpages.ToArray(); return decoded; } } }