/// <summary> /// Формирование xml-файла с данными о формате диска. /// </summary> /// <returns></returns> public string SaveFormatToXml() { StringBuilder sb = new StringBuilder(); sb.AppendLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>"); int trackCount = Tracks.Cnt; sb.AppendLine($"<DiskFormat Name=\"{Name}\" TrackCount=\"{trackCount}\" Sectors=\"{SizeSectors}\">"); for (int i = 0; i < trackCount; i++) { MList <SectorInfo> layout = Tracks[i].Layout; if (layout.Cnt != 0) { sb.AppendLine($" <Track Index=\"{i}\" SectorCount=\"{layout.Cnt}\" Name=\"{Tracks[i].FormatName}\">"); for (int j = 0; j < layout.Cnt; j++) { sb.AppendLine($" <Sector Cylinder=\"{layout[j].Cylinder}\" Head=\"{layout[j].Head}\" Number=\"{layout[j].SectorNumber}\" Size=\"{layout[j].SizeCode}\" Time=\"{GP.ToString(layout[j].TimeMs, 4)}\" />"); } sb.AppendLine(" </Track>"); } else { sb.AppendLine($" <Track Index=\"{i}\" SectorCount=\"{layout.Cnt}\" Name=\"{Tracks[i].FormatName}\" />"); } } sb.AppendLine("</DiskFormat>"); return(sb.ToString()); }
/// <summary> /// Сканирование трека. Сканировать может в двух режимах: по времени оборота диска и по зацикливанию потока секторов. /// При сканировании по времени оборота диска скорость диска должна быть в районе 300 об/мин, т.е. замедлять диск не надо, иначе будут ошибки. /// Если byLooping == true, то конец трека определяется по зацикливанию потока секторов, но сектора сканируются не менее 150 мс и не более 1000 мс. /// </summary> /// <param name="track"></param> /// <returns></returns> public unsafe bool ScanFormat(TrackFormat result, int track, bool byLooping) { int ilayoutCnt = 0; scanFormatBuffer.Layout.Cnt = 0; SectorInfo[] layout = scanFormatBuffer.Layout.Data; tagFD_READ_ID_PARAMS pars = new tagFD_READ_ID_PARAMS() { flags = Driver.FD_OPTION_MFM, head = (byte)(track & 1) }; tagFD_CMD_RESULT cmdResult = new tagFD_CMD_RESULT(); scanFormatStopwatch.Restart(); scanFormatTotalTime.Stop(); int firstIndex = 1; for (int i = 0; i < scanFormatBuffer.Layout.Capacity; i++) { if (Aborted) { return(false); } if (!Driver.ReadId(DriverHandle, pars, out cmdResult)) { int error = Marshal.GetLastWin32Error(); Log.Trace?.Out($"Функция ReadId вернула false. i={i}. LastError={error}"); if (Aborted) { return(false); } Make30HeadPositionings(error, track); if (scanFormatBuffer.Layout.Cnt == 0) { goto success; } return(false); } double timeMs = scanFormatStopwatch.ElapsedMs; scanFormatStopwatch.Restart(); if (!scanFormatTotalTime.IsRunning) { scanFormatTotalTime.Restart(); } if (!byLooping && scanFormatTotalTime.ElapsedMs > 209) { goto success; } layout[ilayoutCnt] = new SectorInfo() { Cylinder = cmdResult.cyl, Head = cmdResult.head, SectorNumber = cmdResult.sector, SizeCode = cmdResult.size, TimeMs = timeMs }; ilayoutCnt++; scanFormatBuffer.Layout.Cnt = ilayoutCnt; // Бывают случаи когда заголовок сектора читается на первом обороте и не читается на втором. // Из-за этого алгоритм, в котором повтор сектора определяется сравнением с первым прочитанным заголовком, иногда даёт сбои. // Поэтому ищем встечался ли ранее только что прочитанный сектор среди всех прочитанных заголовков, а не только сравниваем с первым. // По отношению к найденному сектору замеряем время вращения. Если оно больше 250 мс, значит этот сектор был пропущен на одном из оборотов // и такую последовательность брать нельзя. Если время меньше 150, значит на треке есть сектора с одинаковыми параметрами. if (byLooping) { for (int u = 0; u < ilayoutCnt - 1; u++) { if (layout[u].Cylinder == cmdResult.cyl && layout[u].Head == cmdResult.head && layout[u].SectorNumber == cmdResult.sector && layout[u].SizeCode == cmdResult.size) { double spinTime = 0; for (int p = u + 1; p < ilayoutCnt; p++) { spinTime += layout[p].TimeMs; } if (spinTime > 250 || spinTime < 150) { continue; } firstIndex = u + 1; goto success; } } if (scanFormatTotalTime.ElapsedMs > 1000) { Log.Trace?.Out($"Не удалось найти цикл в последовательности секторов из-за нестабильного чтения. Сканироваие прервано по таймауту."); return(false); } } } return(false); success: result.AssignLayout(scanFormatBuffer, track, cmdResult.sector, firstIndex); Log.Trace?.Out($"Время сканирования трека {track}: {GP.ToString(scanFormatTotalTime.ElapsedMs, 2)}"); return(true); }
public unsafe void ReadTrack(int track, DiskReaderParams pars) { // Массив sectors: // D0 - Признак обработанного сектора при чтении трека в одной попытке. Обнуляется перед каждой попыткой чтения трека. // D1 - Используется в блоке чтения заголовков. Отмечаются сектора заголовки которых были найдены. Вне этого блока не используется. // D2 - Запрет чтения сектора из-за того что его заголовок был прочитан SectorReadAttempts раз и не был найден. Параметр сохраняется между попытками чтения трека. int *sectors = stackalloc int[pars.SectorsOnTrack]; for (int i = 0; i < pars.SectorsOnTrack; i++) { sectors[i] = 0; } bool formatScanned = false; bool headScanned = false; bool trackScanned = false; bool wasCorrectHead = false; bool missedHeaders = false; for (int attempt = 0; attempt < pars.SectorReadAttempts; attempt++) { if (Aborted) { return; } for (int i = 0; i < pars.SectorsOnTrack; i++) { sectors[i] &= ~1; } bool wasError = false; double timeAfterSync = 0; trackTimer.Restart(); int skip = 0; int processedSectors = 0; int sectorCounter = 0; SectorInfo diskSector; while (processedSectors < pars.SectorsOnTrack) { bool sync = false; if (pars.ReadMode == ReadMode.Fast && pars.CurrentTrackFormat.IsSync) { bool gcsError; diskSector = pars.CurrentTrackFormat.GetClosestSector(track, 0, skip, out timeAfterSync, out gcsError); sync = true; // Переключение в стандартный режим чтения в случае если функция GetClosestSector отработала с ошибкой. // Такое было у одного из пользователей и причина ошибки непонятна. if (gcsError) { pars.ReadMode = ReadMode.Standard; } } else { diskSector = pars.Image.StandardFormat.Layout.Data[sectorCounter]; sectorCounter++; } int sectorIndex = pars.Image.StandardFormat.FindSectorIndex(diskSector.SectorNumber); int diskSectorNum = track * Params.SectorsOnTrack + sectorIndex; skip++; if ((sectors[sectorIndex] & 1) != 0) { continue; } sectors[sectorIndex] |= 1; processedSectors++; if ((sectors[sectorIndex] & 4) != 0) { continue; } if (diskSectorNum < pars.FirstSectorNum || diskSectorNum >= pars.LastSectorNum) { continue; } if (pars.Image.Sectors[diskSectorNum] == SectorProcessResult.Good) { continue; } bool badWritten = pars.Image.Sectors[diskSectorNum] == SectorProcessResult.Bad; skip = 0; pars.Image.Map?.ClearHighlight(MapCell.Processing); pars.Image.Map?.MarkAsProcessing(diskSectorNum); WinApi.RtlZeroMemory(memoryHandle, (UIntPtr)diskSector.SizeBytes); int error = Driver.ReadSector(DriverHandle, memoryHandle, track, diskSector.SectorNumber, pars.UpperSideHead, diskSector.SizeCode); double curTimeSinceSync = pars.CurrentTrackFormat.Timer.ElapsedMs; if (error == 0) { pars.CurrentTrackFormat.Sync(track, diskSector.SectorNumber); pars.Image.WriteGoodSectors(diskSectorNum, memoryHandle, 1); wasCorrectHead = true; if (Aborted) { return; } // Проверка несовпадения расположения секторов с заданным. Работает только для TR-DOS. if (pars.ReadMode == ReadMode.Fast && pars.TrackLayoutAutodetect && sync && !formatScanned && !missedHeaders && curTimeSinceSync < 2 * pars.CurrentTrackFormat.SpinTime) { double remainderTime = curTimeSinceSync; for (int t = 0; remainderTime > 0 && t < 20; remainderTime -= pars.CurrentTrackFormat.SpinTime, t++) { // 6 - половина времени сектора TR-DOS (около 12 мс). if (Math.Abs(remainderTime - timeAfterSync) <= 6) { goto skip; } } Log.Trace?.Out($"Несовпадение расположения секторов. Calculated Time: {GP.ToString(timeAfterSync, 2)} | Real Time: {GP.ToString(curTimeSinceSync, 2)}"); formatScanned = true; bool scanResult = ScanFormat(workTrackFormat, track, true); if (Aborted) { return; } if (scanResult) { Log.Trace?.Out($"Формат трека {track}: {workTrackFormat.FormatName} | {workTrackFormat.Layout.Cnt} sectors | {workTrackFormat.ToStringAsSectorArray()}"); if (workTrackFormat.FormatName <= TrackFormatName.TrDosGeneric) { pars.CurrentTrackFormat.Assign(workTrackFormat); } } //pars.Image.Map?.ClearHighlight(MapCell.Scanning); //findex = pars.TrackFormat.GetNextSectorIndex(track); skip :; } } else { wasError = true; bool writeBad = true; bool noHeader = error == 21 || error == 1112 || error == 1122 || (error == 27 /* && curTimeSinceSync > pars.CurrentTrackFormat.SpinTime*/); if (noHeader) { Make30HeadPositionings(error, track); // Проверка параметра Head верхней стороны. bool scanWholeTrack = true; int scanHeadResult = 0; if (track % 2 == 1 && !headScanned && !wasCorrectHead && pars.UpperSideHeadAutodetect) { headScanned = true; UpperSideHead head = pars.UpperSideHead; scanHeadResult = ScanHeadParameter(ref head, track, pars.CurrentTrackFormat); if (scanHeadResult == 0) { if (head != pars.UpperSideHead) { Log.Trace?.Out($"Параметр Head верхней стороны для трека {track} определен: {(int)head}"); pars.UpperSideHead = head; writeBad = false; sectors[sectorIndex] = 0; processedSectors--; scanWholeTrack = false; } else { Log.Trace?.Out($"Параметр Head верхней стороны для трека {track} определен и совпадает с предыдущим: {(int)head}"); } } else { Log.Trace?.Out($"Параметр Head верхней стороны для трека {track} определить не удалось."); } } // Чтение неформатных областей. // Если встретился NoHeader, то предполагается что на этом треке может быть еще много случаев NoHeader, // поэтому они проверяются сканированием трека целиком. Это происходит быстрее, чем если пытаться читать их по-отдельности. if (scanWholeTrack && !trackScanned) { int sec0Num = Math.Max(track * pars.SectorsOnTrack, pars.FirstSectorNum); int sec0NtNum = Math.Min((track + 1) * pars.SectorsOnTrack, pars.LastSectorNum); // sectorsToCheck - число секторов которые надо проверить на предмет отсутствия заголовков. // Если подозреваемый сектор только один (т.е. только что прочитанный) и число попыток чтения 1, то sectorsToCheck после цикла будет нулём. bool ignoreCurSector = pars.SectorReadAttempts == 1; writeBad = ignoreCurSector; int sectorsToCheck = ignoreCurSector ? 0 : 1; // Учет только что обработанного сектора. // Далее к sectorsToCheck добавляются только необработанные сектора. for (int sn = sec0Num, si = sn - track * pars.SectorsOnTrack; sn < sec0NtNum; sn++, si++) { if (pars.Image.Sectors[sn] != SectorProcessResult.Good && (sectors[si] & 1) == 0) { sectorsToCheck++; } } if (sectorsToCheck > 0) { trackScanned = true; int foundHeaders = 0; bool scanResult = false; int maxExtraSectors = 0; if (!ignoreCurSector) { sectors[sectorIndex] &= ~1; } int attempts = !ignoreCurSector && sectorsToCheck == 1 ? pars.SectorReadAttempts - 1 : pars.SectorReadAttempts; for (int attemptnh = 0; attemptnh < attempts; attemptnh++) { scanResult = false; if (scanHeadResult == 1 && attemptnh == 0) { workTrackFormat.Layout.Cnt = 0; } else { scanResult = ScanFormat(workTrackFormat, track, true); } if (workTrackFormat.Layout.Cnt == 0) { Log.Info?.Out($"На треке {track} заголовки не найдены."); } else { Log.Trace?.Out($"Формат трека {track}: {workTrackFormat.FormatName} | {workTrackFormat.Layout.Cnt} sectors | {workTrackFormat.ToStringAsSectorArray()}"); } for (int sn = sec0Num, si = sn - track * pars.SectorsOnTrack; sn < sec0NtNum; sn++, si++) { if (pars.Image.Sectors[sn] == SectorProcessResult.Good || (sectors[si] & 1) == 1 || (sectors[si] & 2) != 0) { continue; } int dSector = pars.Image.StandardFormat.Layout.Data[si].SectorNumber; int sindex = workTrackFormat.FindSectorIndex(dSector); int head = pars.UpperSideHead == UpperSideHead.Head1 ? track & 1 : 0; int sizeCode = pars.CurrentTrackFormat.Layout.Data[pars.CurrentTrackFormat.FindSectorIndex(dSector)].SizeCode; if (sindex >= 0 && workTrackFormat.Layout.Data[sindex].SizeCode == sizeCode) { if (workTrackFormat.Layout.Data[sindex].Cylinder == track / 2 && workTrackFormat.Layout.Data[sindex].Head == head) { sectors[si] |= 2; foundHeaders++; } else { Log.Info?.Out($"Не совпадает Cylinder или Head в заголовке сектора {dSector} на треке {track} (цилиндр {track / 2} сторона {track % 2}): {workTrackFormat.Layout.Data[sindex]}"); } } } // Поиск экстра-секторов на треке. int extraSectors = 0; for (int ii = 0; ii < workTrackFormat.Layout.Cnt; ii++) { for (int jj = 0; jj < pars.CurrentTrackFormat.Layout.Cnt; jj++) { if (pars.CurrentTrackFormat.Layout.Data[jj].SectorNumber == workTrackFormat.Layout.Data[ii].SectorNumber && pars.CurrentTrackFormat.Layout.Data[jj].SizeCode == workTrackFormat.Layout.Data[ii].SizeCode) { goto found; } } extraSectors++; found :; } if (extraSectors > maxExtraSectors) { maxExtraSectors = extraSectors; longestTrackFormat.Assign(workTrackFormat); } if (foundHeaders == sectorsToCheck) { break; } } int noHeaderCnt = 0; for (int sn = sec0Num, si = sn - track * pars.SectorsOnTrack; sn < sec0NtNum; sn++, si++) { if (pars.Image.Sectors[sn] == SectorProcessResult.Good || (sectors[si] & 1) == 1) { continue; } if ((sectors[si] & 2) == 0) { // 4 (100b) означает что заголовок не найден за нужное число попыток // и больше читать этот сектор не надо, т.к. все попытки исчерпаны. sectors[si] |= 4; if (!badWritten) { pars.Image.WriteBadSectors(sn, 1, true); } noHeaderCnt++; } } if (scanResult) { pars.CurrentTrackFormat.Sync(workTrackFormat); } Log.Trace?.Out($"На треке {track} не найдены заголовки {noHeaderCnt} секторов."); if (noHeaderCnt > 0) { missedHeaders = true; } if (maxExtraSectors > 0) { Log.Info?.Out($"Трек {track} имеет несоответствующий формат: {longestTrackFormat.GetFormatName()} | {longestTrackFormat.Layout.Cnt} sectors | {longestTrackFormat.ToStringAsSectorArray()}"); } } } } else if (error == 23) { writeBad = false; pars.Image.WriteBadSectors(diskSectorNum, memoryHandle, 1, false); } if (writeBad && !badWritten) { pars.Image.WriteBadSectors(diskSectorNum, 1, noHeader); } } if (Aborted) { return; } } Log.Trace?.Out($"Время чтения трека: {GP.ToString(trackTimer.ElapsedMs, 2)}"); trackTimer.Restart(); if (!wasError) { break; } } }
public unsafe void ReadTrack(int track, DiskReaderParams2 pars, ScanMode scanMode) { // Массив sectors: // D0 - Признак обработанного сектора при чтении трека в одной попытке. Обнуляется перед каждой попыткой чтения трека. // D1 - Используется в блоке чтения заголовков. Отмечаются сектора заголовки которых были найдены. Вне этого блока не используется. // D2 - Запрет чтения сектора из-за того что его заголовок был прочитан SectorReadAttempts раз и не был найден. Параметр сохраняется между попытками чтения трека. TrackFormat trackF = pars.Image.Tracks[track]; const int sectorArrayLen = 50; int * sectors = stackalloc int[sectorArrayLen]; for (int i = 0; i < sectorArrayLen; i++) { sectors[i] = 0; } bool trackScanned = false; for (int attempt = 0; attempt < pars.SectorReadAttempts; attempt++) { if (Aborted) { return; } // Сканирование трека. if ( ((scanMode == ScanMode.Once && !trackScanned) || (scanMode == ScanMode.UnscannedOnly && trackF.FormatName == TrackFormatName.Unscanned) || scanMode == ScanMode.EachTrackRead) && (trackF.MaxGap() > 128 + TrackFormat.MinSectorHeaderSize || trackF.ContainsCalculatedTime()) ) { // workTrackFormat и longestTrackFormat используются чтобы не плодить объекты. // Объект scanFormatBuffer используется функциями ScanFormat и CombineFormats, поэтому его брать нельзя. trackF.Scanning = true; trackF.MapModified = true; if (ScanFormat(workTrackFormat, track, true)) { for (int i = 0; i < workTrackFormat.Layout.Cnt; i++) { workTrackFormat.Layout.Data[i].MapCellValue = MapCell.Unprocessed; } if (CombineFormats(track, trackF, workTrackFormat, longestTrackFormat)) { trackF.Assign(longestTrackFormat); trackF.MapModified = true; } } trackF.Scanning = false; trackF.MapModified = true; trackScanned = true; if (Aborted) { return; } } if (trackF.Layout.Cnt > sectorArrayLen) { Log.Error?.Out($"Число секторов превышает размер рабочего массива: {trackF.Layout.Cnt}"); throw new Exception(); } for (int i = 0; i < trackF.Layout.Cnt; i++) { sectors[i] &= ~1; } bool wasError = false; trackTimer.Restart(); int skip = 0; int processedSectors = 0; int sectorCounter = 0; SectorInfo diskSector; while (processedSectors < trackF.Layout.Cnt) { diskSector = trackF.Layout.Data[sectorCounter]; int sectorIndex = sectorCounter; sectorCounter++; skip++; if ((sectors[sectorIndex] & 1) != 0) { continue; } sectors[sectorIndex] |= 1; processedSectors++; if ((sectors[sectorIndex] & 4) != 0) { continue; } if (trackF.Layout.Data[sectorIndex].ProcessResult == SectorProcessResult.Good) { continue; } skip = 0; pars.Map.MarkSectorAsProcessing(track, sectorIndex); WinApi.RtlZeroMemory(memoryHandle, (UIntPtr)diskSector.SizeBytes); int error = Driver.ReadSectorF(DriverHandle, memoryHandle, diskSector.Cylinder, diskSector.SectorNumber, diskSector.SizeCode, track & 1, diskSector.Head, 0x0a, 0xff); double curTimeSinceSync = trackF.Timer.ElapsedMs; bool badWritten = diskSector.ProcessResult == SectorProcessResult.Bad; if (error == 0) { pars.Image.WriteGoodSector(memoryHandle, trackF, sectorIndex); } else { wasError = true; // Если надо проверить CRC-Error заголовка: (error == 27 && curTimeSinceSync > trackF.SpinTime) bool noHeader = error == 21 || error == 1112 || error == 27; if (noHeader) { if (!badWritten) { pars.Image.WriteNoHeader(memoryHandle, trackF, sectorIndex); } Make30HeadPositionings(error, track); } else if (error == 23) { pars.Image.WriteBadSector(memoryHandle, trackF, sectorIndex); badWritten = true; } else { Log.Info?.Out($"Необработанная ошибка при чтении сектора: {error}"); } } if (Aborted) { return; } } Log.Trace?.Out($"Время чтения трека: {GP.ToString(trackTimer.ElapsedMs, 2)}"); trackTimer.Restart(); if (!wasError) { break; } } }
public string ToStringAsSectorArray() { StringBuilder sb = new StringBuilder(); for (int i = 0; i < Layout.Cnt; i++) { sb.Append($"(c={Layout.Data[i].Cylinder}, h={Layout.Data[i].Head}, s={Layout.Data[i].SectorNumber}, {Layout.Data[i].SizeBytes}, {GP.ToString(Layout.Data[i].TimeMs, 2)} ms)"); if (i < Layout.Cnt - 1) { sb.Append(", "); } } return(sb.ToString()); }