From b8887f55a29164d813e36c5551a29b9ac81ac055 Mon Sep 17 00:00:00 2001 From: Victor Antonovich Date: Wed, 28 Oct 2020 23:10:17 +0400 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=20=D1=80=D0=B5=D0=B6=D0=B8=D0=BC=20"=D1=87?= =?UTF-8?q?=D1=82=D0=B5=D0=BD=D0=B8=D0=B5/=D0=B7=D0=B0=D0=BF=D0=B8=D1=81?= =?UTF-8?q?=D1=8C"=20=D0=B4=D0=BB=D1=8F=20=D0=BE=D0=B1=D1=80=D0=B0=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=20=D1=84=D0=BB=D0=BE=D0=BF=D0=BF=D0=B8-=D0=B4?= =?UTF-8?q?=D0=B8=D1=81=D0=BA=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +- app/build.gradle | 5 +- .../su/comp/bk/arch/io/FloppyController.java | 994 ++++++++++-------- .../java/su/comp/bk/ui/BkEmuActivity.java | 73 +- app/src/main/java/su/comp/bk/util/Crc16.java | 31 +- .../main/res/layout-land/fdd_mgr_dialog.xml | 22 +- app/src/main/res/layout/fdd.xml | 61 +- app/src/main/res/layout/fdd_mgr_dialog.xml | 15 + app/src/main/res/raw-ru/changelog_data.xml | 7 +- app/src/main/res/raw/changelog_data.xml | 7 +- app/src/main/res/values-ru/strings.xml | 1 + app/src/main/res/values/dimens.xml | 5 + app/src/main/res/values/strings.xml | 1 + app/src/main/res/values/styles.xml | 6 + .../comp/bk/arch/io/FloppyControllerTest.java | 281 +++-- gradle/wrapper/gradle-wrapper.properties | 2 +- 16 files changed, 927 insertions(+), 587 deletions(-) diff --git a/README.md b/README.md index d2d0ab1..af857bf 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,7 @@ PDP-11-совместимых советских 16-разрядных дома * Системный таймер 11М (прерывание 50 Гц по вектору 100, бит 14 в регистре 0177662) * Страничная память 11М (биты 8-10, 12-14 в регистре 0177716) * Стандартный шестикнопочный джойстик на порту УП - * Контроллер накопителя на гибких магнитных дисках К1801ВП1-128 (КНГМД, - в режиме "только для чтения") + * Контроллер накопителя на гибких магнитных дисках К1801ВП1-128 (КНГМД) * Звуковой 8-битный ЦАП Covox (монофонический) * Музыкальный сопроцессор AY-3-8910 diff --git a/app/build.gradle b/app/build.gradle index afd5c6d..fe173a6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -89,7 +89,7 @@ android { dependencies { api 'androidx.legacy:legacy-support-v4:1.0.0' - implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'commons-lang:commons-lang:2.6' implementation 'com.samskivert:jmustache:1.9' @@ -98,5 +98,6 @@ dependencies { testImplementation 'junit:junit:4.12' testImplementation 'commons-io:commons-io:2.4' - testImplementation 'org.robolectric:robolectric:4.3.1' + testImplementation 'org.robolectric:robolectric:4.4' + testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1' } \ No newline at end of file diff --git a/app/src/main/java/su/comp/bk/arch/io/FloppyController.java b/app/src/main/java/su/comp/bk/arch/io/FloppyController.java index 6552f86..e57228a 100644 --- a/app/src/main/java/su/comp/bk/arch/io/FloppyController.java +++ b/app/src/main/java/su/comp/bk/arch/io/FloppyController.java @@ -20,11 +20,17 @@ package su.comp.bk.arch.io; import android.os.Bundle; +import android.util.SparseBooleanArray; + +import androidx.annotation.NonNull; import java.io.File; import java.io.RandomAccessFile; import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; +import java.util.ArrayList; import su.comp.bk.arch.Computer; import su.comp.bk.util.Crc16; @@ -92,36 +98,47 @@ public class FloppyController implements Device { /** Index hole length (in nanoseconds) */ public final static long NANOSECS_PER_INDEX_HOLE = NANOSECS_IN_MSEC; - /** Tracks per floppy disk */ - public final static int TRACKS_PER_DISK = 81; + /** Maximum tracks per floppy disk */ + public final static int MAX_TRACKS_PER_DISK = 81; /** Sectors per track */ public final static int SECTORS_PER_TRACK = 10; /** Bytes per sector */ public final static int BYTES_PER_SECTOR = 512; + /** Words per sector */ + public final static int WORDS_PER_SECTOR = BYTES_PER_SECTOR / 2; - /** Bytes per two-sided disk */ - public final static int BYTES_PER_DISK = TRACKS_PER_DISK * SECTORS_PER_TRACK * BYTES_PER_SECTOR * 2; + /** Max bytes per two-sided disk */ + public final static int MAX_BYTES_PER_DISK = MAX_TRACKS_PER_DISK * SECTORS_PER_TRACK + * BYTES_PER_SECTOR * 2; private final static int[] ADDRESSES = { CONTROL_REGISTER_ADDRESS, DATA_REGISTER_ADDRESS }; // State save/restore: Synchronous read flag state private static final String STATE_SYNCHRONOUS_READ = FloppyController.class.getName() + - "#synch_read"; + "#sync_read"; // State save/restore: Marker found flag state private static final String STATE_MARKER_FOUND = FloppyController.class.getName() + "#marker_found"; // State save/restore: Data ready flag state private static final String STATE_DATA_READY = FloppyController.class.getName() + "#data_ready"; + // State save/restore: Write operation flag state + private static final String STATE_WRITE_OPERATION = FloppyController.class.getName() + + "#write_operation"; // State save/restore: Data ready read position private static final String STATE_DATA_READY_READ_POSITION = FloppyController.class.getName() + "#data_ready_read_position"; - // State save/restore: CRC correct flag state - private static final String STATE_CRC_CORRECT = FloppyController.class.getName() + - "#crc_correct"; + // State save/restore: Last marker data position + private static final String STATE_LAST_MARKER_POSITION = FloppyController.class.getName() + + "#last_marker_position"; + // State save/restore: CRC flag state + private static final String STATE_CRC_FLAG = FloppyController.class.getName() + "#crc_flag"; // State save/restore: Last data register read time - private static final String STATE_LAST_DATA_REGISTER_READ_TIME = FloppyController.class.getName() + - "#last_data_register_read_time"; + private static final String STATE_LAST_DATA_REGISTER_READ_TIME = + FloppyController.class.getName() + "#last_data_register_read_time"; + // State save/restore: Last data register write time + private static final String STATE_LAST_DATA_REGISTER_WRITE_TIME = + FloppyController.class.getName() + "#last_data_register_write_time"; // State save/restore: Last controller access time private static final String STATE_LAST_ACCESS_TIME = FloppyController.class.getName() + "#last_access_time"; @@ -131,12 +148,22 @@ public class FloppyController implements Device { // State save/restore: Motor started flag state private static final String STATE_MOTOR_STARTED = FloppyController.class.getName() + "#motor_started"; + // State save/restore: Drive disk image file name private static final String STATE_DRIVE_IMAGE_FILE_NAME = FloppyDrive.class.getName() + - "#disk_image_file_name"; - // State save/restore: Drive disk image read only flag - private static final String STATE_DRIVE_IMAGE_READ_ONLY = FloppyDrive.class.getName() + - "#disk_image_read_only"; + "#image_file_name"; + // State save/restore: Drive current track data + private static final String STATE_DRIVE_CURRENT_TRACK_DATA = FloppyDrive.class.getName() + + "#current_track_data"; + // State save/restore: Drive current track data is modified flag + private static final String STATE_DRIVE_CURRENT_TRACK_DATA_MODIFIED = + FloppyDrive.class.getName() + "#current_track_data_modified"; + // State save/restore: Drive current track data marker positions + private static final String STATE_DRIVE_CURRENT_TRACK_DATA_MARKER_POSITIONS = + FloppyDrive.class.getName() + "#current_track_data_marker_positions"; + // State save/restore: Drive write protect flag + private static final String STATE_DRIVE_WRITE_PROTECT_MODE = FloppyDrive.class.getName() + + "#write_protect"; // State save/restore: Drive track number private static final String STATE_DRIVE_TRACK_NUMBER = FloppyDrive.class.getName() + "#track_number"; @@ -165,12 +192,21 @@ public class FloppyController implements Device { // Data ready track read position in synchronous read state private int dataReadyReadPosition; - // CRC is correct in synchronous read state flag - private boolean isCrcCorrect; + // CRC flag (set if CRC is correct in synchronous read state, or if CRC is written in write state) + private boolean isCrcFlag; // Last data register read CPU time private long lastDataRegisterReadCpuTime; + // Write operation in progress flag + private boolean isWriteOperation; + + // Last marker track position + private int lastMarkerPosition; + + // Last data register write CPU time + private long lastDataRegisterWriteCpuTime; + // Last controller access CPU time private long lastAccessCpuTime; @@ -195,22 +231,22 @@ enum FloppyDriveSide { DOWN, UP } - /** - * Floppy drive track changed event listener. - */ - interface OnFloppyDriveTrackChanged { - /** - * Notifies about floppy drive track changed. - * @param trackNumber Track number [0, TRACKS_PER_DISK] - * @param trackSide Track side [Side.Down, Side.UP] - */ - void onFloppyDriveTrackChanged(int trackNumber, FloppyDriveSide trackSide); - } - /** * Floppy drive class. */ class FloppyDrive { + public static final int SEQ_SYNC = 0x0000; + public static final int SEQ_SYNC_LENGTH = 6; + + public static final int SEQ_GAP = 0x4e4e; + public static final int SEQ_GAP1_LENGTH = 16; + public static final int SEQ_GAP2_LENGTH = 11; + public static final int SEQ_GAP3_LENGTH = 24; + + public static final int SEQ_MARK = 0xa1a1; + public static final int SEQ_MARK_ID = 0xa1fe; + public static final int SEQ_MARK_DATA = 0xa1fb; + // This floppy drive identifier private final FloppyDriveIdentifier driveIdentifier; @@ -218,371 +254,208 @@ class FloppyDrive { private File mountedDiskImageFile; private RandomAccessFile mountedDiskImageRandomAccessFile; - private ByteBuffer mountedDiskImageBuffer; + private MappedByteBuffer mountedDiskImageBuffer; + + private boolean isWriteProtectMode; - private boolean isMountedDiskImageReadOnly; + private int lastTrackNumber = MAX_TRACKS_PER_DISK; private int currentTrackNumber; private FloppyDriveSide currentTrackSide; - private final FloppyDriveTrackArea[] currentTrackData = new FloppyDriveTrackArea[WORDS_PER_TRACK]; + private final short[] currentTrackData = new short[WORDS_PER_TRACK]; + private final SparseBooleanArray currentTrackDataMarkerPositions = new SparseBooleanArray(); - protected final OnFloppyDriveTrackChanged[] trackChangedListeners = - new OnFloppyDriveTrackChanged[SECTORS_PER_TRACK * 2]; - - /** - * Abstract floppy drive track area class. - */ - abstract class FloppyDriveTrackArea { - // Track area position in track - private int startPosition; - - /** - * Abstract floppy drive track area constructor. - * @param position floppy drive track area position from track start (in words) - */ - FloppyDriveTrackArea(int position) { - this.startPosition = position; - } - - /** - * Get floppy drive track area index for given track position. - * @param position floppy drive track area position from track start (in words) - * @return this floppy drive track area index - */ - protected int getPositionIndex(int position) { - return (position - startPosition); - } - - /** - * Get floppy drive track area length. - * @return floppy drive track area length (in words) - */ - protected abstract int getLength(); - - /** - * Check given position is marker start position. - * @param position floppy drive track area position from track start (in words) - * @return true if given position is marker start position, - * false otherwise - */ - public boolean isMarkerPosition(int position) { - return isDiskImageMounted() && isMarkerPositionInternal(getPositionIndex(position)); - } - - /** - * Check given internal data index is marker start position. - * @return true if given internal data index is marker start position, - * false otherwise - */ - protected abstract boolean isMarkerPositionInternal(int index); - - /** - * Check given position is CRC position. - * @param position floppy drive track area position from track start (in words) - * @return true if given position is CRC position, - * false otherwise - */ - public boolean isCrcPosition(int position) { - return isDiskImageMounted() && isCrcPositionInternal(getPositionIndex(position)); - } - - /** - * Check given internal data index is CRC position. - * @return true if given internal data index is CRC position, - * false otherwise - */ - protected abstract boolean isCrcPositionInternal(int index); - - /** - * Read data from this floppy drive track area. - * @param position floppy drive track area read position (from track start, in words) - * @return read floppy drive track area word - */ - public int read(int position) { - return isDiskImageMounted() ? readInternal(getPositionIndex(position)) : 0; - } - - /** - * Internal data read from this floppy drive track area. - * @param index floppy drive track area read index from this area start (in words) - * @return read floppy drive track area data word - */ - protected abstract int readInternal(int index); - - /** - * Write data to this floppy drive track area. - * @param position floppy drive track area write position (from track start, in words) - * @param value word data value to write - */ - public void write(int position, int value) { - if (isDiskImageMounted()) { - writeInternal(getPositionIndex(position), value); - } - } + private boolean isCurrentTrackDataModified; - /** - * Internal data write to this floppy drive track area. - * @param index floppy drive track area write index from this area start (in words) - * @param value word data value to write - */ - protected abstract void writeInternal(int index, int value); + FloppyDrive(FloppyDriveIdentifier driveIdentifier) { + this.driveIdentifier = driveIdentifier; + setCurrentTrack(0, FloppyDriveSide.DOWN); } /** - * Floppy drive track gap/sync sequences class. + * Get current track data. + * @return current track data */ - class FloppyDriveTrackSequence extends FloppyDriveTrackArea { - public static final int SEQ_GAP = 0x4e4e; - public static final int SEQ_SYNC = 0x0000; - - public static final int SEQ_SYNC_LENGTH = 6; - public static final int SEQ_GAP1_LENGTH = 16; - public static final int SEQ_GAP2_LENGTH = 11; - public static final int SEQ_GAP3_LENGTH = 18; - - private final int sequenceValue; - private final int sequenceLength; - - FloppyDriveTrackSequence(int position, int value, int length) { - super(position); - this.sequenceValue = value; - this.sequenceLength = length; - } - - @Override - protected int getLength() { - return sequenceLength; - } - - @Override - protected int readInternal(int index) { - return sequenceValue; - } - - @Override - protected void writeInternal(int index, int value) { - // Do nothing - } - - @Override - protected boolean isMarkerPositionInternal(int index) { - return false; - } - - @Override - protected boolean isCrcPositionInternal(int index) { - return false; - } + short[] getCurrentTrackData() { + return currentTrackData; } - /** - * Floppy drive track sector header class. - */ - class FloppyDriveTrackSectorHeader extends FloppyDriveTrackArea - implements OnFloppyDriveTrackChanged { - // Sector header - track number byte index - private final static int TRACK_NUMBER_INDEX = 4; - // Sector header - head number byte index - private final static int HEAD_NUMBER_INDEX = 5; - // Sector header - sector number byte index - private final static int SECTOR_NUMBER_INDEX = 6; - // Sector header - CRC value first byte index - private final static int CRC_VALUE_INDEX = 8; - - private final byte[] data = { - (byte) 0xa1, (byte) 0xa1, (byte) 0xa1, (byte) 0xfe, // IDAM - 0, 0, 0, // track number (0-79), head number(0-1), sector number(1-10) - 2, // 512 bytes per sector - 0, 0 // CRC value (big endian) - }; - - FloppyDriveTrackSectorHeader(int position, int sectorNumber) { - super(position); - data[SECTOR_NUMBER_INDEX] = (byte) sectorNumber; - } - - @Override - public void onFloppyDriveTrackChanged(int trackNumber, FloppyDriveSide trackSide) { - data[TRACK_NUMBER_INDEX] = (byte) trackNumber; - data[HEAD_NUMBER_INDEX] = (byte) trackSide.ordinal(); - correctCrcValue(); - } - - private void correctCrcValue() { - short crcValue = Crc16.calculate(data, 0, CRC_VALUE_INDEX); - data[CRC_VALUE_INDEX] = (byte) (crcValue >> 8); - data[CRC_VALUE_INDEX + 1] = (byte) crcValue; - } - - @Override - protected int getLength() { - return (data.length >> 1); + private void loadCurrentTrackData() { + clearCurrentTrackDataMarkerPositions(); + int position = 0; + // GAP1 + position = writeCurrentTrackDataSequence(position, SEQ_GAP, SEQ_GAP1_LENGTH); + // Sectors + for (int sectorNumber = 1; sectorNumber <= SECTORS_PER_TRACK; sectorNumber++) { + // Header sync + position = writeCurrentTrackDataSequence(position, SEQ_SYNC, SEQ_SYNC_LENGTH); + // Sector header - IDAM + descriptor + CRC + position = writeCurrentTrackSectorHeader(position, sectorNumber); + // GAP2 + position = writeCurrentTrackDataSequence(position, SEQ_GAP, SEQ_GAP2_LENGTH); + // Data sync + position = writeCurrentTrackDataSequence(position, SEQ_SYNC, SEQ_SYNC_LENGTH); + // Sector data - DATA AM + Data + CRC + position = writeCurrentTrackSectorData(position, sectorNumber); + // GAP3 or GAP4B for the last sector + position = writeCurrentTrackDataSequence(position, SEQ_GAP, + (sectorNumber < SECTORS_PER_TRACK) ? SEQ_GAP3_LENGTH + : WORDS_PER_TRACK - position); } + setCurrentTrackDataModified(false); + } - @Override - protected int readInternal(int index) { - int dataIndex = (index << 1); - return ((data[dataIndex] << 8) & 0177400) | (data[dataIndex + 1] & 0377); + private int writeCurrentTrackDataSequence(int position, int value, int length) { + int dataIndex = position; + while (dataIndex < position + length) { + writeCurrentTrackData(dataIndex++, value); } + return dataIndex; + } - @Override - protected void writeInternal(int index, int value) { - // TODO Auto-generated method stub + private int writeCurrentTrackSectorHeader(int position, int sectorNumber) { + int dataIndex = position; + // ID AM + writeCurrentTrackData(dataIndex++, SEQ_MARK, true); + writeCurrentTrackData(dataIndex++, SEQ_MARK_ID); + // Track number (0-79), head number(0-1) + writeCurrentTrackData(dataIndex++, currentTrackNumber << 8 | currentTrackSide.ordinal()); + // Sector number(1-10), sector size (2 for 512 bytes per sector) + writeCurrentTrackData(dataIndex++, sectorNumber << 8 | 2); + // CRC value (big endian) + writeCurrentTrackData(dataIndex++, Crc16.calculate(currentTrackData, position, 4)); + return dataIndex; + } + private int writeCurrentTrackSectorData(int position, int sectorNumber) { + int dataIndex = position; + // DATA AM + writeCurrentTrackData(dataIndex++, SEQ_MARK, true); + writeCurrentTrackData(dataIndex++, SEQ_MARK_DATA); + // Sector data + ByteBuffer diskImageBuffer = getMountedDiskImageBuffer(); + int imageBufferOffset = getImageSectorOffset(currentTrackSide, currentTrackNumber, sectorNumber); + for (int wordIndex = 0; wordIndex < WORDS_PER_SECTOR; wordIndex++) { + writeCurrentTrackData(dataIndex++, diskImageBuffer.getShort( + imageBufferOffset + wordIndex * 2)); } + // CRC value (big endian) + int length = dataIndex - position; + writeCurrentTrackData(dataIndex++, Crc16.calculate(currentTrackData, position, length)); + return dataIndex; + } - @Override - protected boolean isMarkerPositionInternal(int index) { - return (index == 0); - } + private void saveCurrentTrackData() { + int position = 0; + short[] trackData = getCurrentTrackData(); + + // Loop by track data + Loop: + while (true) { + // Find sector header start + while (!isCurrentTrackDataMarkerPosition(position)) { + if (position >= WORDS_PER_TRACK - 5) { + break Loop; + } + position++; + } - @Override - protected boolean isCrcPositionInternal(int index) { - return ((index << 1) == CRC_VALUE_INDEX); - } - } + int headerPosition = position; - /** - * Floppy drive track sector data class. - */ - class FloppyDriveTrackSectorData extends FloppyDriveTrackArea - implements OnFloppyDriveTrackChanged { - - // Sector data address marker length (in words) - private final static int SECTOR_DATA_AM_LENGTH = 2; - // Sector CRC value index (in words) - private final static int SECTOR_CRC_INDEX = SECTOR_DATA_AM_LENGTH + - (BYTES_PER_SECTOR >> 1); - - private final short[] dataMarker = { - (short) 0xa1a1, (short) 0xa1fb, // DATA AM - }; - - private final int sectorIndex; - - private short dataMarkerCrcValue; - private short crcValue; - - FloppyDriveTrackSectorData(int position, int sectorNumber) { - super(position); - sectorIndex = sectorNumber - 1; - dataMarkerCrcValue = Crc16.INIT_VALUE; - for (short dataMarkerWord : dataMarker) { - dataMarkerCrcValue = Crc16.calculate(dataMarkerCrcValue, - (byte) (dataMarkerWord >> 8)); - dataMarkerCrcValue = Crc16.calculate(dataMarkerCrcValue, - (byte) (dataMarkerWord)); + // Check sector header marker + if (readCurrentTrackData(position++) != SEQ_MARK + || readCurrentTrackData(position++) != SEQ_MARK_ID) { + continue; } - } - @Override - public void onFloppyDriveTrackChanged(int trackNumber, FloppyDriveSide trackSide) { - // Update sector CRC value - int imageBufferStartIndex = getImageBufferStartIndex(trackNumber, trackSide); - ByteBuffer diskImageBuffer = getMountedDiskImageBuffer(); - crcValue = dataMarkerCrcValue; - for (int dataIndex = imageBufferStartIndex; dataIndex < (imageBufferStartIndex - + BYTES_PER_SECTOR); dataIndex++) { - crcValue = Crc16.calculate(crcValue, diskImageBuffer.get(dataIndex)); + // Check sector header data + int data = readCurrentTrackData(position++); + int trackNumber = (data >> 8) & 0377; + if (trackNumber != currentTrackNumber) { + Timber.w("Unexpected track number: expected: %d, found: %d", + currentTrackNumber, trackNumber); + continue; + } + int trackSide = data & 0377; + if (trackSide != currentTrackSide.ordinal()) { + Timber.w("Unexpected track side: expected: %d, found: %d", + currentTrackSide.ordinal(), trackSide); + continue; + } + data = readCurrentTrackData(position++); + int sectorNumber = (data >> 8) & 0377; + if (sectorNumber < 1 || trackNumber > SECTORS_PER_TRACK) { + Timber.w("Unexpected sector number: %d", sectorNumber); + continue; + } + int sectorSize = data & 0377; + if (sectorSize != 2) { + Timber.w("Unexpected sector size: %d", sectorSize); + continue; } - } - @Override - protected int getLength() { - return (SECTOR_CRC_INDEX + 1); // DATA AM + Data + 2 bytes of CRC - } + // Check sector header CRC + int length = position - headerPosition; + int crcValue = readCurrentTrackData(position++); + if (crcValue != (Crc16.calculate(trackData, headerPosition, length) & 0177777)) { + Timber.w("Invalid sector header CRC, sector: %d", sectorNumber); + continue; + } - @Override - protected int readInternal(int index) { - short data; - if (index < SECTOR_DATA_AM_LENGTH) { - data = dataMarker[index]; - } else if (index < SECTOR_CRC_INDEX) { - int dataIndex = (index - SECTOR_DATA_AM_LENGTH) << 1; - int imageBufferDataIndex = getImageBufferStartIndex(getCurrentTrackNumber(), - getCurrentTrackSide()) + dataIndex; - ByteBuffer diskImageBuffer = getMountedDiskImageBuffer(); - data = diskImageBuffer.getShort(imageBufferDataIndex); - } else { - data = crcValue; + // Find sector data start + while (!isCurrentTrackDataMarkerPosition(position)) { + if (position >= WORDS_PER_TRACK - (WORDS_PER_SECTOR + 3)) { + break Loop; + } + position++; } - return (data & 0177777); - } - /** - * Get this sector start index in mapped floppy drive image file. - * @return this sector start index in mapped floppy drive image file (in bytes). - */ - private int getImageBufferStartIndex(int trackNumber, FloppyDriveSide side) { - return BYTES_PER_SECTOR * (SECTORS_PER_TRACK * (trackNumber * 2 + side.ordinal()) - + sectorIndex); - } + int dataPosition = position; - @Override - protected void writeInternal(int index, int value) { - // TODO Auto-generated method stub + // Check sector data marker + if (readCurrentTrackData(position++) != SEQ_MARK + || readCurrentTrackData(position++) != SEQ_MARK_DATA) { + continue; + } - } + position += WORDS_PER_SECTOR; - @Override - protected boolean isMarkerPositionInternal(int index) { - return (index == 0); - } + // Check sector data CRC + length = position - dataPosition; + crcValue = readCurrentTrackData(position++); + if (crcValue != (Crc16.calculate(trackData, dataPosition, length) & 0177777)) { + Timber.w("Invalid sector data CRC, sector: %d", sectorNumber); + continue; + } - @Override - protected boolean isCrcPositionInternal(int index) { - return (index == SECTOR_CRC_INDEX); + // Save sector data to image + Timber.d("Saving sector data, sector number: %d", sectorNumber); + ByteBuffer diskImageBuffer = getMountedDiskImageBuffer(); + int imageBufferOffset = getImageSectorOffset(currentTrackSide, currentTrackNumber, + sectorNumber); + for (int wordIndex = 0; wordIndex < WORDS_PER_SECTOR; wordIndex++) { + data = readCurrentTrackData(dataPosition + 2 + wordIndex); + diskImageBuffer.putShort(imageBufferOffset + wordIndex * 2, (short) data); + } } + setCurrentTrackDataModified(false); } - FloppyDrive(FloppyDriveIdentifier driveIdentifier) { - this.mountedDiskImageBuffer = ByteBuffer.allocate(BYTES_PER_DISK); - this.driveIdentifier = driveIdentifier; - setCurrentTrack(0, FloppyDriveSide.DOWN); - initializeCurrentTrackData(); - } - - private void initializeCurrentTrackData() { - int position = 0; - // GAP1 - position = initializeCurrentTrackData(position, new FloppyDriveTrackSequence(position, - FloppyDriveTrackSequence.SEQ_GAP, FloppyDriveTrackSequence.SEQ_GAP1_LENGTH)); - // Sectors - for (int sectorNumber = 1; sectorNumber <= SECTORS_PER_TRACK; sectorNumber++) { - // Header sync - position = initializeCurrentTrackData(position, new FloppyDriveTrackSequence(position, - FloppyDriveTrackSequence.SEQ_SYNC, FloppyDriveTrackSequence.SEQ_SYNC_LENGTH)); - // Sector header - IDAM + descriptor + CRC - FloppyDriveTrackSectorHeader sectorHeader = new FloppyDriveTrackSectorHeader( - position, sectorNumber); - position = initializeCurrentTrackData(position, sectorHeader); - trackChangedListeners[(sectorNumber - 1) * 2] = sectorHeader; - // GAP2 - position = initializeCurrentTrackData(position, new FloppyDriveTrackSequence(position, - FloppyDriveTrackSequence.SEQ_GAP, FloppyDriveTrackSequence.SEQ_GAP2_LENGTH)); - // Data sync - position = initializeCurrentTrackData(position, new FloppyDriveTrackSequence(position, - FloppyDriveTrackSequence.SEQ_SYNC, FloppyDriveTrackSequence.SEQ_SYNC_LENGTH)); - // Sector data - DATA AM + Data + CRC - FloppyDriveTrackSectorData sectorData = new FloppyDriveTrackSectorData( - position, sectorNumber); - position = initializeCurrentTrackData(position, sectorData); - trackChangedListeners[(sectorNumber - 1) * 2 + 1] = sectorData; - // GAP3 (19 words for 512 bytes per sector) or GAP4B (to end of track) - position = initializeCurrentTrackData(position, new FloppyDriveTrackSequence(position, - FloppyDriveTrackSequence.SEQ_GAP, (sectorNumber < SECTORS_PER_TRACK) - ? FloppyDriveTrackSequence.SEQ_GAP3_LENGTH : WORDS_PER_TRACK - position)); + void flushCurrentTrackData() { + if (isDiskImageMounted() && isCurrentTrackDataModified()) { + saveCurrentTrackData(); } } - private int initializeCurrentTrackData(int position, FloppyDriveTrackArea area) { - int dataIndex = position; - while (dataIndex < position + area.getLength()) { - currentTrackData[dataIndex++] = area; - } - return dataIndex; + /** + * Get sector offset in floppy drive image file. + * @return sector offset in floppy drive image file (in bytes). + */ + private int getImageSectorOffset(FloppyDriveSide side, int trackNumber, int sectorNumber) { + return BYTES_PER_SECTOR * (SECTORS_PER_TRACK * (trackNumber * 2 + side.ordinal()) + + sectorNumber - 1); } /** @@ -591,33 +464,74 @@ private int initializeCurrentTrackData(int position, FloppyDriveTrackArea area) * @return read data word */ int readCurrentTrackData(int position) { - return currentTrackData[position].read(position); + return currentTrackData[position] & 0177777; } + /** * Write data to current track at given position. - * @param position track data position (in words from track start) + * @param position track data position (in words from the track start) + * @param value data word to write + * @param isMarker true if value is marker data + */ + void writeCurrentTrackData(int position, int value, boolean isMarker) { + currentTrackData[position] = (short) value; + setCurrentTrackDataMarkerPosition(position, isMarker); + setCurrentTrackDataModified(true); + } + + /** + * Write non-marker data to current track at given position. + * @param position track data position (in words from the track start) * @param value data word to write */ void writeCurrentTrackData(int position, int value) { - currentTrackData[position].write(position, value); + writeCurrentTrackData(position, value, false); } /** - * Check given position of current track data is marker start position. - * @return true if given position is marker start position, + * Check is current track data modified by write operations. + * @return true if track data was modified, false otherwise + */ + boolean isCurrentTrackDataModified() { + return isCurrentTrackDataModified; + } + + /** + * Set current track data modified by write operations flag state + * @param isCurrentTrackDataModified true to mark track data is modified, + * false to mark as not modified + */ + void setCurrentTrackDataModified(boolean isCurrentTrackDataModified) { + this.isCurrentTrackDataModified = isCurrentTrackDataModified; + } + + /** + * Set current track data position marker flag. + * @param position track data position (in words from the track start) + * @param isMarker true to set given position as marker position + */ + void setCurrentTrackDataMarkerPosition(int position, boolean isMarker) { + if (isMarker) { + currentTrackDataMarkerPositions.put(position, true); + } else { + currentTrackDataMarkerPositions.delete(position); + } + } + + /** + * Check given position of current track data is marker position. + * @return true if given position is marker position, * false otherwise */ boolean isCurrentTrackDataMarkerPosition(int position) { - return currentTrackData[position].isMarkerPosition(position); + return currentTrackDataMarkerPositions.get(position); } /** - * Check given position of current track data is CRC position. - * @return true if given position is CRC position, - * false otherwise + * Clear current track data marker positions. */ - boolean isCurrentTrackDataCrcPosition(int position) { - return currentTrackData[position].isCrcPosition(position); + void clearCurrentTrackDataMarkerPositions() { + currentTrackDataMarkerPositions.clear(); } /** @@ -645,13 +559,16 @@ void setCurrentTrack(int trackNumber, FloppyDriveSide trackSide) { if (isDebugEnabled) { d("set track: " + trackNumber + ", side: " + trackSide); } + + // Flush current track data + flushCurrentTrackData(); + this.currentTrackNumber = trackNumber; this.currentTrackSide = trackSide; - // OnFloppyDriveTrackChanged listeners notify if disk image mounted + + // Load current track data if disk image mounted if (isDiskImageMounted()) { - for (OnFloppyDriveTrackChanged listener : trackChangedListeners) { - listener.onFloppyDriveTrackChanged(trackNumber, trackSide); - } + loadCurrentTrackData(); } } @@ -659,11 +576,31 @@ void setCurrentTrack(int trackNumber, FloppyDriveSide trackSide) { * Get next current track number after single step to center or to edge. * @param isStepToCenter true is track changed with single step to center, * false if track changed with single step to edge - * @return next track number in range [0, TRACKS_PER_DISK - 1] + * @return next track number in range [0, maxTrackNumber] */ int getNextTrackNumber(boolean isStepToCenter) { return Math.max(Math.min((getCurrentTrackNumber() + (isStepToCenter ? 1 : -1)), - TRACKS_PER_DISK - 1), 0); + getLastTrackNumber()), 0); + } + + /** + * Get current disk image last track number. + * @return last track number in range [0, MAX_TRACKS_PER_DISK - 1] + */ + int getLastTrackNumber() { + return lastTrackNumber; + } + + /** + * Set disk image last track number. + * @param lastTrackNumber last track number in range [0, MAX_TRACKS_PER_DISK - 1] + */ + void setLastTrackNumber(int lastTrackNumber) { + if (lastTrackNumber < 0 || lastTrackNumber >= MAX_TRACKS_PER_DISK) { + throw new IllegalArgumentException("Invalid lastTrackNumber value: " + + lastTrackNumber); + } + this.lastTrackNumber = lastTrackNumber; } boolean isDiskIndexHoleActive(long cpuTime) { @@ -688,38 +625,45 @@ boolean isDiskImageMounted() { } /** - * Check is mounted disk image is read only. - * @return true if mounted disk image is read only, - * false if not mounted or mounted in read/write mode + * Check is drive in write protect mode. + * @return true if drive is in write protect mode */ - boolean isMountedDiskImageReadOnly() { - return (isDiskImageMounted() && isMountedDiskImageReadOnly); + boolean isWriteProtectMode() { + return isWriteProtectMode; + } + + /** + * Set drive write protect mode. + * @param isWriteProtectMode true to set drive write protect mode + */ + void setWriteProtectMode(boolean isWriteProtectMode) { + this.isWriteProtectMode = isWriteProtectMode; } /** * Mount disk image to this drive. * @param diskImageFile Disk image file to mount - * @param isReadOnly true to mount disk image as read only, - * false to mount disk image in read/write mode + * @param isWriteProtectMode true to mount disk image in write protect mode * @throws Exception in case of mounting error */ - void mountDiskImage(File diskImageFile, boolean isReadOnly) throws Exception { - // Check disk image size - if (diskImageFile.length() > BYTES_PER_DISK) { + void mountDiskImage(@NonNull File diskImageFile, boolean isWriteProtectMode) + throws Exception { + // Check disk image + if (diskImageFile.length() > MAX_BYTES_PER_DISK) { throw new IllegalArgumentException("Invalid disk image size: " + diskImageFile.length()); } if (isDiskImageMounted()) { unmountDiskImage(); } - isMountedDiskImageReadOnly = isReadOnly; - mountedDiskImageRandomAccessFile = new RandomAccessFile(diskImageFile, isReadOnly ? "r" : "rw"); - FileChannel mountedDiskImageFileChannel = mountedDiskImageRandomAccessFile.getChannel(); - mountedDiskImageBuffer.clear(); - mountedDiskImageFileChannel.read(mountedDiskImageBuffer); - mountedDiskImageBuffer.flip(); - mountedDiskImageBuffer.limit(BYTES_PER_DISK); - mountedDiskImageFileChannel.close(); + setWriteProtectMode(isWriteProtectMode); + int lastDiskImageTrackNumber = (int) (diskImageFile.length() + / (SECTORS_PER_TRACK * BYTES_PER_SECTOR * 2) - 1); + setLastTrackNumber(lastDiskImageTrackNumber); + mountedDiskImageRandomAccessFile = new RandomAccessFile(diskImageFile, "rw"); + mountedDiskImageBuffer = mountedDiskImageRandomAccessFile.getChannel().map( + FileChannel.MapMode.READ_WRITE,0, diskImageFile.length()); + mountedDiskImageBuffer.order(ByteOrder.BIG_ENDIAN); this.mountedDiskImageFile = diskImageFile; // Reload track data setCurrentTrack(getCurrentTrackNumber(), getCurrentTrackSide()); @@ -727,10 +671,12 @@ void mountDiskImage(File diskImageFile, boolean isReadOnly) throws Exception { /** * Unmount current mounted disk image. - * @throws Exception in case of unmounting error + * @throws Exception in case of unmount error */ void unmountDiskImage() throws Exception { + flushCurrentTrackData(); mountedDiskImageFile = null; + mountedDiskImageBuffer.force(); mountedDiskImageRandomAccessFile.close(); } @@ -775,11 +721,14 @@ public synchronized void init(long cpuTime) { public synchronized void saveState(Bundle outState) { outState.putSerializable(STATE_SELECTED_FLOPPY_DRIVE, getSelectedFloppyDriveIdentifier()); outState.putBoolean(STATE_SYNCHRONOUS_READ, isSynchronousReadState()); + outState.putBoolean(STATE_WRITE_OPERATION, isWriteOperation()); outState.putBoolean(STATE_MARKER_FOUND, isMarkerFound()); outState.putBoolean(STATE_DATA_READY, isDataReady()); outState.putInt(STATE_DATA_READY_READ_POSITION, getDataReadyReadPosition()); - outState.putBoolean(STATE_CRC_CORRECT, isCrcCorrect()); + outState.putInt(STATE_LAST_MARKER_POSITION, getLastMarkerPosition()); + outState.putBoolean(STATE_CRC_FLAG, isCrcFlag()); outState.putLong(STATE_LAST_DATA_REGISTER_READ_TIME, getLastDataRegisterReadCpuTime()); + outState.putLong(STATE_LAST_DATA_REGISTER_WRITE_TIME, getLastDataRegisterWriteCpuTime()); outState.putLong(STATE_LAST_ACCESS_TIME, getLastAccessCpuTime()); outState.putBoolean(STATE_MOTOR_STARTED, isMotorStarted()); for (FloppyDriveIdentifier driveIdentifier : FloppyDriveIdentifier.values()) { @@ -787,12 +736,25 @@ public synchronized void saveState(Bundle outState) { File mountedDiskImageFile = drive.getMountedDiskImageFile(); outState.putString(getFloppyDriveStateKey(STATE_DRIVE_IMAGE_FILE_NAME, driveIdentifier), (mountedDiskImageFile != null) ? mountedDiskImageFile.toString() : null); - outState.putBoolean(getFloppyDriveStateKey(STATE_DRIVE_IMAGE_READ_ONLY, driveIdentifier), - drive.isMountedDiskImageReadOnly()); + outState.putShortArray(getFloppyDriveStateKey(STATE_DRIVE_CURRENT_TRACK_DATA, + driveIdentifier), drive.getCurrentTrackData()); + outState.putBoolean(getFloppyDriveStateKey(STATE_DRIVE_CURRENT_TRACK_DATA_MODIFIED, + driveIdentifier), drive.isCurrentTrackDataModified()); + ArrayList markerPositions = new ArrayList<>(); + for (int position = 0; position < WORDS_PER_TRACK; position++) { + if (drive.isCurrentTrackDataMarkerPosition(position)) { + markerPositions.add(position); + } + } + outState.putIntegerArrayList(getFloppyDriveStateKey( + STATE_DRIVE_CURRENT_TRACK_DATA_MARKER_POSITIONS, driveIdentifier), + markerPositions); + outState.putBoolean(getFloppyDriveStateKey(STATE_DRIVE_WRITE_PROTECT_MODE, + driveIdentifier), drive.isWriteProtectMode()); outState.putInt(getFloppyDriveStateKey(STATE_DRIVE_TRACK_NUMBER, driveIdentifier), drive.getCurrentTrackNumber()); - outState.putSerializable(getFloppyDriveStateKey(STATE_DRIVE_TRACK_SIDE, driveIdentifier), - drive.getCurrentTrackSide()); + outState.putSerializable(getFloppyDriveStateKey(STATE_DRIVE_TRACK_SIDE, + driveIdentifier), drive.getCurrentTrackSide()); } } @@ -800,11 +762,14 @@ public synchronized void saveState(Bundle outState) { public synchronized void restoreState(Bundle inState) { selectFloppyDrive((FloppyDriveIdentifier) inState.getSerializable(STATE_SELECTED_FLOPPY_DRIVE)); setSynchronousReadState(inState.getBoolean(STATE_SYNCHRONOUS_READ)); + setWriteOperation(inState.getBoolean(STATE_WRITE_OPERATION)); setMarkerFound(inState.getBoolean(STATE_MARKER_FOUND)); setDataReady(inState.getBoolean(STATE_DATA_READY)); setDataReadyReadPosition(inState.getInt(STATE_DATA_READY_READ_POSITION)); - setCrcCorrect(inState.getBoolean(STATE_CRC_CORRECT)); + setLastMarkerPosition(inState.getInt(STATE_LAST_MARKER_POSITION)); + setCrcFlag(inState.getBoolean(STATE_CRC_FLAG)); setLastDataRegisterReadCpuTime(inState.getLong(STATE_LAST_DATA_REGISTER_READ_TIME)); + setLastDataRegisterWriteCpuTime(inState.getLong(STATE_LAST_DATA_REGISTER_WRITE_TIME)); setLastAccessCpuTime(inState.getLong(STATE_LAST_ACCESS_TIME)); setMotorStarted(inState.getBoolean(STATE_MOTOR_STARTED)); for (FloppyDriveIdentifier driveIdentifier : FloppyDriveIdentifier.values()) { @@ -814,17 +779,32 @@ public synchronized void restoreState(Bundle inState) { FloppyDriveSide driveTrackSide = (FloppyDriveSide) inState.getSerializable( getFloppyDriveStateKey(STATE_DRIVE_TRACK_SIDE, driveIdentifier)); drive.setCurrentTrack(driveTrackNumber, driveTrackSide); + drive.setWriteProtectMode(inState.getBoolean(getFloppyDriveStateKey( + STATE_DRIVE_WRITE_PROTECT_MODE, driveIdentifier))); String diskImageFileName = inState.getString(getFloppyDriveStateKey( STATE_DRIVE_IMAGE_FILE_NAME, driveIdentifier)); if (diskImageFileName != null) { try { - drive.mountDiskImage(new File(diskImageFileName), inState.getBoolean( - getFloppyDriveStateKey(STATE_DRIVE_IMAGE_READ_ONLY, driveIdentifier))); + drive.mountDiskImage(new File(diskImageFileName), drive.isWriteProtectMode()); + short[] currentTrackData = inState.getShortArray(getFloppyDriveStateKey( + STATE_DRIVE_CURRENT_TRACK_DATA, driveIdentifier)); + System.arraycopy(currentTrackData, 0, drive.getCurrentTrackData(), + 0, currentTrackData.length); + ArrayList markerPositions = inState.getIntegerArrayList( + getFloppyDriveStateKey(STATE_DRIVE_CURRENT_TRACK_DATA_MARKER_POSITIONS, + driveIdentifier)); + drive.clearCurrentTrackDataMarkerPositions(); + for (int markerPosition: markerPositions) { + drive.setCurrentTrackDataMarkerPosition(markerPosition, true); + } + drive.setCurrentTrackDataModified(inState.getBoolean(getFloppyDriveStateKey( + STATE_DRIVE_CURRENT_TRACK_DATA_MODIFIED, driveIdentifier))); } catch (Exception e) { Timber.e(e, "can't remount disk file image: %s", diskImageFileName); try { drive.unmountDiskImage(); } catch (Exception e1) { + // Do nothing } } } @@ -862,35 +842,73 @@ public synchronized boolean write(long cpuTime, boolean isByteMode, int address, } protected int readControlRegister(long cpuTime) { - int value = 0; FloppyDrive drive = getSelectedFloppyDrive(); - if (drive != null) { - // Track 0 flag - if (drive.getCurrentTrackNumber() == 0) { - value |= TR0; - } - // Floppy disk write protect flag - if (drive.isMountedDiskImageReadOnly()) { - value |= WRP; + if (drive == null) { + return 0; + } + + int result = 0; + + // Track 0 flag + if (drive.getCurrentTrackNumber() == 0) { + result |= TR0; + } + + // Floppy disk write protect flag + if (drive.isDiskImageMounted() && drive.isWriteProtectMode()) { + result |= WRP; + } + + // Floppy disk index hole activity flag + if (drive.isDiskIndexHoleActive(cpuTime)) { + result |= IND; + } + + int trackPosition = getTrackPosition(cpuTime); + + if (isWriteOperation()) { + // Handle write state + int lastDataRegisterWritePosition = getTrackPosition(getLastDataRegisterWriteCpuTime()); + int numDataWords = getNumTrackDataWords(lastDataRegisterWritePosition, trackPosition); + + // Set flags + setDataReady(numDataWords > 0); // last word written + + if (numDataWords > 1) { // CRC written + if (!isCrcFlag()) { + setCrcFlag(true); + // Write CRC value + int crcPosition = getNextTrackPosition(lastDataRegisterWritePosition); + short[] trackData = drive.getCurrentTrackData(); + int length = getNumTrackDataWords(getLastMarkerPosition(), crcPosition); + short crcValue = Crc16.calculate(trackData, getLastMarkerPosition(), length); + drive.writeCurrentTrackData(crcPosition, crcValue); + } } - // Floppy disk index hole activity flag - if (drive.isDiskIndexHoleActive(cpuTime)) { - value |= IND; + + // Check write operation end condition + if (numDataWords > 2) { // last word and CRC written, end write operation + endWriteOperation(); } - int trackPosition = getTrackPosition(cpuTime); + } + + if (!isWriteOperation()) { + // Handle read state int lastDataRegisterReadPosition = getTrackPosition(getLastDataRegisterReadCpuTime()); int numReadDataWords = getNumTrackDataWords(lastDataRegisterReadPosition, trackPosition); + // Check data is ready if (isMarkerFound() && !isDataReady()) { setDataReady(numReadDataWords > 0); if (isDataReady()) { - setDataReadyReadPosition((lastDataRegisterReadPosition + 1) % WORDS_PER_TRACK); + setDataReadyReadPosition(getNextTrackPosition(lastDataRegisterReadPosition)); // d(TAG, "isDataReady, position: " + trackPosition + // ", dataReadyReadPosition: " + getDataReadyReadPosition() + // ", readDataWords: " + numReadDataWords + ", isCrcPosition: " + // drive.isCurrentTrackDataCrcPosition(trackPosition)); } } + // Check for synchronous read state if (isSynchronousReadState()) { if (!isMarkerFound()) { @@ -900,6 +918,7 @@ protected int readControlRegister(long cpuTime) { d("marker found, position: " + trackPosition); } setMarkerFound(true); + setLastMarkerPosition(trackPosition); setDataReady(true); setDataReadyReadPosition(trackPosition); } @@ -908,25 +927,32 @@ protected int readControlRegister(long cpuTime) { if (numReadDataWords > 1) { // Data ready flag set, synchronous read completed setSynchronousReadState(false); - // Check was CRC read as last data word - setCrcCorrect(drive.isCurrentTrackDataCrcPosition(getDataReadyReadPosition())); + // Check CRC is correct + short[] trackData = drive.getCurrentTrackData(); + int crcPosition = getDataReadyReadPosition(); + int length = getNumTrackDataWords(getLastMarkerPosition(), crcPosition); + short crcValue = Crc16.calculate(trackData, getLastMarkerPosition(), length); + setCrcFlag((crcValue & 0177777) == drive.readCurrentTrackData(crcPosition)); if (isDebugEnabled) { d("synchronous read completed, position: " + trackPosition + ", dataReadyReadPosition: " + getDataReadyReadPosition() + - ", readDataWords: " + numReadDataWords + ", isCrcCorrect: " + isCrcCorrect()); + ", readDataWords: " + numReadDataWords + + ", isCrcCorrect: " + isCrcFlag()); } - } } } - if (isDataReady()) { - value |= TR; - } - if (isCrcCorrect()) { - value |= CRC; - } } - return value; + + if (isDataReady()) { + result |= TR; + } + + if (isCrcFlag()) { + result |= CRC; + } + + return result; } private static int getNumTrackDataWords(int oldPosition, int newPosition) { @@ -937,6 +963,7 @@ private static int getNumTrackDataWords(int oldPosition, int newPosition) { protected void writeControlRegister(long cpuTime, int value) { // Set floppy drives motor state setMotorStarted((value & MSW) != 0); + // Determine floppy drive to operate int driveMask = value & (DS0 | DS1 | DS2 | DS3); FloppyDriveIdentifier driveIdentifier = null; @@ -957,27 +984,39 @@ protected void writeControlRegister(long cpuTime, int value) { // Drive unselected break; } + // Select floppy drive selectFloppyDrive(driveIdentifier); + // Do operations on selected drive, if any if (driveIdentifier != null) { FloppyDrive drive = getFloppyDrive(driveIdentifier); + + // Handle Write Marker flag + if ((value & WM) != 0 && isWriteOperation()) { + int markerPosition = getTrackPosition(getLastDataRegisterWriteCpuTime()); + drive.setCurrentTrackDataMarkerPosition(markerPosition, true); + setLastMarkerPosition(markerPosition); + } + // Check is track number or side changed FloppyDriveSide trackSide = (value & HS) != 0 ? FloppyDriveSide.UP : FloppyDriveSide.DOWN; int trackNumber = (value & ST) != 0 ? drive.getNextTrackNumber((value & DIR) != 0) : drive.getCurrentTrackNumber(); if (trackSide != drive.getCurrentTrackSide() || trackNumber != drive.getCurrentTrackNumber()) { - // Track changed, cancel synchronous read mode + // Track changed, cancel synchronous read or write mode cancelSynchronousRead(); + endWriteOperation(); // Set floppy drive track number and side drive.setCurrentTrack(trackNumber, trackSide); } + + // Process GOR flag if ((value & GOR) != 0) { startSynchronousRead(); } - // TODO Process WM flag } } @@ -1005,12 +1044,12 @@ private void setDataReadyReadPosition(int position) { this.dataReadyReadPosition = position; } - private boolean isCrcCorrect() { - return isCrcCorrect; + private boolean isCrcFlag() { + return isCrcFlag; } - private void setCrcCorrect(boolean isCrcCorrect) { - this.isCrcCorrect = isCrcCorrect; + private void setCrcFlag(boolean isCrcFlag) { + this.isCrcFlag = isCrcFlag; } private long getLastDataRegisterReadCpuTime() { @@ -1021,6 +1060,14 @@ private void setLastDataRegisterReadCpuTime(long lastDataRegisterReadCpuTime) { this.lastDataRegisterReadCpuTime = lastDataRegisterReadCpuTime; } + private long getLastDataRegisterWriteCpuTime() { + return lastDataRegisterWriteCpuTime; + } + + private void setLastDataRegisterWriteCpuTime(long lastDataRegisterWriteCpuTime) { + this.lastDataRegisterWriteCpuTime = lastDataRegisterWriteCpuTime; + } + public synchronized long getLastAccessCpuTime() { return lastAccessCpuTime; } @@ -1038,6 +1085,7 @@ private void setSynchronousReadState(boolean state) { } private void startSynchronousRead() { + endWriteOperation(); cancelSynchronousRead(); setSynchronousReadState(true); } @@ -1046,10 +1094,11 @@ private void cancelSynchronousRead() { setSynchronousReadState(false); setMarkerFound(false); setDataReady(false); - setCrcCorrect(false); + setCrcFlag(false); } protected int readDataRegister(long cpuTime) { + endWriteOperation(); int value = 0; FloppyDrive selectedDrive = getSelectedFloppyDrive(); if (selectedDrive != null && isMotorStarted()) { @@ -1065,6 +1114,55 @@ protected int readDataRegister(long cpuTime) { return value; } + private boolean isWriteOperation() { + return isWriteOperation; + } + + private void setWriteOperation(boolean isWriteOperation) { + this.isWriteOperation = isWriteOperation; + } + + public int getLastMarkerPosition() { + return lastMarkerPosition; + } + + public void setLastMarkerPosition(int position) { + this.lastMarkerPosition = position; + } + + private void startWriteOperation(int position) { + cancelSynchronousRead(); + setWriteOperation(true); + } + + private void endWriteOperation() { + setCrcFlag(false); + setWriteOperation(false); + FloppyDrive floppyDrive = getSelectedFloppyDrive(); + if (floppyDrive != null) { + floppyDrive.flushCurrentTrackData(); + } + } + + protected void writeDataRegister(long cpuTime, int value) { + FloppyDrive selectedDrive = getSelectedFloppyDrive(); + if (selectedDrive != null && isMotorStarted()) { + int position = getTrackPosition(cpuTime); + if (!isWriteOperation() && !selectedDrive.isWriteProtectMode()) { + startWriteOperation(position); + } + if (isWriteOperation()) { + // Mounted disk media is in big-endian mode, and low byte is written first, + // so swap bytes before write + int writeValue = ((value << 8) & 0177400) | ((value >> 8) & 0377); + selectedDrive.writeCurrentTrackData(position, writeValue); + } + } + setDataReady(false); + setCrcFlag(false); + setLastDataRegisterWriteCpuTime(cpuTime); + } + /** * Get track position for given CPU time. * @param cpuTime CPU time to get track position @@ -1074,21 +1172,25 @@ private int getTrackPosition(long cpuTime) { return (int) ((cpuTime / clockTicksPerWord) % WORDS_PER_TRACK); } - protected void writeDataRegister(long cpuTime, int value) { - // TODO + /** + * Get next track position. + * @param position track position (in words) + * @return next track position + */ + private static int getNextTrackPosition(int position) { + return (position + 1) % WORDS_PER_TRACK; } /** * Mount floppy drive disk image to given drive. * @param diskImageFile floppy drive disk image file * @param drive {@link FloppyDriveIdentifier} of drive to mount disk image - * @param isReadOnly true to mount disk image read only, false - * to mount disk image read/write + * @param isWriteProtectMode true to mount disk image in write protect mode * @throws Exception in case of disk image mounting error */ public synchronized void mountDiskImage(File diskImageFile, FloppyDriveIdentifier drive, - boolean isReadOnly) throws Exception { - getFloppyDrive(drive).mountDiskImage(diskImageFile, isReadOnly); + boolean isWriteProtectMode) throws Exception { + getFloppyDrive(drive).mountDiskImage(diskImageFile, isWriteProtectMode); } /** @@ -1143,8 +1245,9 @@ protected void selectFloppyDrive(FloppyDriveIdentifier floppyDriveIdentifier) { d("selected drive: " + floppyDriveIdentifier); } if (selectedFloppyDriveIdentifier != floppyDriveIdentifier) { - // Cancel synchronous data read + // Cancel synchronous data read or write operation cancelSynchronousRead(); + endWriteOperation(); } this.selectedFloppyDriveIdentifier = floppyDriveIdentifier; } @@ -1187,4 +1290,23 @@ public synchronized boolean isFloppyDriveMounted(FloppyDriveIdentifier driveIden public synchronized File getFloppyDriveImageFile(FloppyDriveIdentifier driveIdentifier) { return getFloppyDrive(driveIdentifier).getMountedDiskImageFile(); } + + /** + * Check floppy drive is in write protect mode. + * @param driveIdentifier {@link FloppyDriveIdentifier} of drive to get mode + * @return true if floppy drive is in write protect mode + */ + public synchronized boolean isFloppyDriveInWriteProtectMode(FloppyDriveIdentifier driveIdentifier) { + return getFloppyDrive(driveIdentifier).isWriteProtectMode(); + } + + /** + * Set floppy drive write protect mode. + * @param driveIdentifier {@link FloppyDriveIdentifier} of drive to set mode + * @param isWriteProtectMode true to set floppy drive in write protect mode + */ + public synchronized void setFloppyDriveWriteProtectMode(FloppyDriveIdentifier driveIdentifier, + boolean isWriteProtectMode) { + getFloppyDrive(driveIdentifier).setWriteProtectMode(isWriteProtectMode); + } } diff --git a/app/src/main/java/su/comp/bk/ui/BkEmuActivity.java b/app/src/main/java/su/comp/bk/ui/BkEmuActivity.java index 0beb957..6ad1047 100644 --- a/app/src/main/java/su/comp/bk/ui/BkEmuActivity.java +++ b/app/src/main/java/su/comp/bk/ui/BkEmuActivity.java @@ -48,6 +48,7 @@ import androidx.annotation.RequiresApi; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.SwitchCompat; import androidx.appcompat.widget.Toolbar; import androidx.transition.ChangeBounds; import androidx.transition.Explode; @@ -142,8 +143,12 @@ public class BkEmuActivity extends AppCompatActivity implements View.OnSystemUiV private static final int FILE_NAME_DISPLAY_SUFFIX_LENGTH = 3; private static final String PREFS_KEY_COMPUTER_CONFIGURATION = "su.comp.bk.a.c"; + private static final String PREFS_KEY_FLOPPY_DRIVE_PREFIX = + "su.comp.bk.arch.io.FloppyController.FloppyDrive/"; private static final String PREFS_KEY_FLOPPY_DRIVE_IMAGE = - "su.comp.bk.arch.io.FloppyController.FloppyDrive/image:"; + PREFS_KEY_FLOPPY_DRIVE_PREFIX + "image:"; + private static final String PREFS_KEY_FLOPPY_DRIVE_WRITE_PROTECT_MODE = + PREFS_KEY_FLOPPY_DRIVE_PREFIX + "writeProtectMode:"; private static final String PREFS_KEY_AUDIO_VOLUME = "su.comp.bk.arch.io.audio.AudioOutput/volume"; @@ -572,8 +577,8 @@ private void mountIntentDataDiskImage() { Timber.e(e, "Can't get local file for floppy disk image %s", intentDataDiskImageUri); } - if (intentDataDiskImageFile == null - || !mountFddImage(FloppyDriveIdentifier.A, intentDataDiskImageFile)) { + if (intentDataDiskImageFile == null || !mountFddImage(FloppyDriveIdentifier.A, + intentDataDiskImageFile, true)) { intentDataDiskImageUri = null; } } @@ -976,12 +981,17 @@ protected void updateFloppyDriveView(final View fddView, TextView fddLabelView = fddView.findViewWithTag("fdd_label"); fddLabelView.setText(fddIdentifier.name()); FloppyController fddController = computer.getFloppyController(); + SwitchCompat fddWriteProtectSwitch = fddView.findViewWithTag("fdd_wp_switch"); + fddWriteProtectSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> + setFddWriteProtectMode(fddController, fddIdentifier, isChecked)); boolean isFddMounted = fddController.isFloppyDriveMounted(fddIdentifier); ImageView fddImageView = fddView.findViewWithTag("fdd_image"); fddImageView.setImageResource(isFddMounted ? R.drawable.floppy_drive_loaded : R.drawable.floppy_drive); TextView fddFileTextView = fddView.findViewWithTag("fdd_file"); if (isFddMounted) { + fddWriteProtectSwitch.setClickable(true); + fddWriteProtectSwitch.setChecked(fddController.isFloppyDriveInWriteProtectMode(fddIdentifier)); fddFileTextView.setTextColor(getResources().getColor(R.color.fdd_loaded)); String fddImageFileName = fddController.getFloppyDriveImageFile(fddIdentifier).getName(); if (fddImageFileName.length() > MAX_FILE_NAME_DISPLAY_LENGTH) { @@ -998,6 +1008,8 @@ protected void updateFloppyDriveView(final View fddView, } fddFileTextView.setText(fddImageFileName); } else { + fddWriteProtectSwitch.setClickable(false); + fddWriteProtectSwitch.setChecked(false); fddFileTextView.setTextColor(getResources().getColor(R.color.fdd_empty)); fddFileTextView.setText(R.string.fdd_empty); } @@ -1019,8 +1031,10 @@ protected void mountAvailableFddImages() { FloppyController fddController = computer.getFloppyController(); for (FloppyDriveIdentifier fddIdentifier : FloppyDriveIdentifier.values()) { String fddImagePath = readFddImagePath(fddIdentifier); + boolean fddWriteProtectMode = readFddWriteProtectMode(fddIdentifier); if (fddImagePath != null) { - doMountFddImage(fddController, fddIdentifier, new File(fddImagePath)); + doMountFddImage(fddController, fddIdentifier, new File(fddImagePath), + fddWriteProtectMode); } } } @@ -1029,12 +1043,15 @@ protected void mountAvailableFddImages() { * Try to mount disk image to given floppy drive. * @param fddIdentifier floppy drive identifier to mount image * @param fddImageFile disk image file + * @param isWriteProtectMode true to mount floppy disk image in write protect mode * @return true if image successfully mounted, false otherwise */ - protected boolean mountFddImage(FloppyDriveIdentifier fddIdentifier, File fddImageFile) { + protected boolean mountFddImage(FloppyDriveIdentifier fddIdentifier, File fddImageFile, + boolean isWriteProtectMode) { FloppyController fddController = computer.getFloppyController(); - if (doMountFddImage(fddController, fddIdentifier, fddImageFile)) { + if (doMountFddImage(fddController, fddIdentifier, fddImageFile, isWriteProtectMode)) { storeFddImagePath(fddIdentifier, fddImageFile.getPath()); + storeFddWriteProtectMode(fddIdentifier, isWriteProtectMode); return true; } return false; @@ -1042,12 +1059,12 @@ protected boolean mountFddImage(FloppyDriveIdentifier fddIdentifier, File fddIma private boolean doMountFddImage(FloppyController fddController, FloppyDriveIdentifier fddIdentifier, - File fddImageFile) { + File fddImageFile, boolean isWriteProtectMode) { try { if (fddController != null) { - fddController.mountDiskImage(fddImageFile, fddIdentifier, true); - Timber.d("Mounted floppy disk image %s to drive %s", - fddImageFile, fddIdentifier); + fddController.mountDiskImage(fddImageFile, fddIdentifier, isWriteProtectMode); + Timber.d("Mounted floppy disk image %s to drive %s in %s mode", + fddImageFile, fddIdentifier, (isWriteProtectMode ? "write protect" : "normal")); return true; } } catch (Exception e) { @@ -1203,7 +1220,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { File diskImageFile = new File(diskImageFilePath); FloppyDriveIdentifier driveIdentifier = FloppyDriveIdentifier .valueOf(data.getStringExtra(FloppyDriveIdentifier.class.getName())); - if (mountFddImage(driveIdentifier, diskImageFile)) { + if (mountFddImage(driveIdentifier, diskImageFile, false)) { lastDiskImageFilePath = diskImageFilePath; showDialog(DIALOG_DISK_MANAGER); } else { @@ -1386,6 +1403,13 @@ protected void setComputerConfiguration(Configuration configuration) { prefsEditor.apply(); } + protected void setFddWriteProtectMode(FloppyController fddController, + FloppyDriveIdentifier fddIdentifier, + boolean isWriteProtectMode) { + fddController.setFloppyDriveWriteProtectMode(fddIdentifier, isWriteProtectMode); + storeFddWriteProtectMode(fddIdentifier, isWriteProtectMode); + } + private String getPrefsFddImageKey(FloppyDriveIdentifier fddIdentifier) { return PREFS_KEY_FLOPPY_DRIVE_IMAGE + fddIdentifier.name(); } @@ -1413,6 +1437,33 @@ protected void storeFddImagePath(FloppyDriveIdentifier fddIdentifier, prefsEditor.apply(); } + private String getFddWriteProtectModePrefsKey(FloppyDriveIdentifier fddIdentifier) { + return PREFS_KEY_FLOPPY_DRIVE_WRITE_PROTECT_MODE + fddIdentifier.name(); + } + + /** + * Read floppy drive write protect mode from shared preferences. + * @param fddIdentifier floppy drive identifier + * @return true if floppy drive is in write protect mode + */ + protected boolean readFddWriteProtectMode(FloppyDriveIdentifier fddIdentifier) { + SharedPreferences prefs = getPreferences(); + return prefs.getBoolean(getFddWriteProtectModePrefsKey(fddIdentifier), false); + } + + /** + * Store floppy drive write protect mode to shared preferences. + * @param fddIdentifier floppy drive identifier + * @param isWriteProtectMode true if floppy drive is in write protect mode + */ + protected void storeFddWriteProtectMode(FloppyDriveIdentifier fddIdentifier, + boolean isWriteProtectMode) { + SharedPreferences prefs = getPreferences(); + SharedPreferences.Editor prefsEditor = prefs.edit(); + prefsEditor.putBoolean(getFddWriteProtectModePrefsKey(fddIdentifier), isWriteProtectMode); + prefsEditor.apply(); + } + private String getPrefsAudioOutputKey(String audioOutputName) { return (audioOutputName == null) ? PREFS_KEY_AUDIO_VOLUME : PREFS_KEY_AUDIO_VOLUME + ":" + audioOutputName; diff --git a/app/src/main/java/su/comp/bk/util/Crc16.java b/app/src/main/java/su/comp/bk/util/Crc16.java index 57a6f47..c3f5a08 100644 --- a/app/src/main/java/su/comp/bk/util/Crc16.java +++ b/app/src/main/java/su/comp/bk/util/Crc16.java @@ -52,11 +52,11 @@ public static short calculate(byte[] data, int offset, int length) { int index = offset; while (counter-- > 0) { crcValue = calculate(crcValue, data[index++]); + index %= data.length; } return crcValue; } - /** * Calculate CRC value for byte array. * @param data byte array to calculate CRC value @@ -65,4 +65,33 @@ public static short calculate(byte[] data, int offset, int length) { public static short calculate(byte[] data) { return calculate(data, 0, data.length); } + + /** + * Calculate next CRC value for word data. + * @param crcValue current CRC value + * @param data word data value to add to CRC + * @return next CRC value + */ + public static short calculate(short crcValue, short data) { + short x = calculate(crcValue, (byte) (data >> 8)); + return calculate(x, (byte) data); + } + + /** + * Calculate CRC value of part of data from word array. + * @param data word array + * @param offset data offset to calculate CRC value + * @param length data length to calculate CRC value + * @return calculated CRC value + */ + public static short calculate(short[] data, int offset, int length) { + short crcValue = INIT_VALUE; + int counter = length; + int index = offset; + while (counter-- > 0) { + crcValue = calculate(crcValue, data[index++]); + index %= data.length; + } + return crcValue; + } } diff --git a/app/src/main/res/layout-land/fdd_mgr_dialog.xml b/app/src/main/res/layout-land/fdd_mgr_dialog.xml index c00a9b8..f1b5aca 100644 --- a/app/src/main/res/layout-land/fdd_mgr_dialog.xml +++ b/app/src/main/res/layout-land/fdd_mgr_dialog.xml @@ -10,14 +10,23 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" - android:orientation="horizontal"> + android:orientation="horizontal" + android:baselineAligned="false" + android:layout_margin="@dimen/fdd_margin"> @@ -26,14 +35,23 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" - android:orientation="horizontal"> + android:orientation="horizontal" + android:baselineAligned="false" + android:layout_marginBottom="@dimen/fdd_margin"> diff --git a/app/src/main/res/layout/fdd.xml b/app/src/main/res/layout/fdd.xml index f291d74..d5db44d 100644 --- a/app/src/main/res/layout/fdd.xml +++ b/app/src/main/res/layout/fdd.xml @@ -1,42 +1,53 @@ - - + android:padding="@dimen/fdd_padding" + android:orientation="vertical"> + android:textAlignment="center" + android:gravity="center_horizontal" + android:layout_marginBottom="@dimen/fdd_text_margin" + android:text="@string/fdd_empty" + android:textColor="@color/theme_primary_medium" /> - + + + + + + + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/fdd_mgr_dialog.xml b/app/src/main/res/layout/fdd_mgr_dialog.xml index 4d49717..b08db3d 100644 --- a/app/src/main/res/layout/fdd_mgr_dialog.xml +++ b/app/src/main/res/layout/fdd_mgr_dialog.xml @@ -8,18 +8,33 @@ \ No newline at end of file diff --git a/app/src/main/res/raw-ru/changelog_data.xml b/app/src/main/res/raw-ru/changelog_data.xml index aa2f207..69d03f3 100644 --- a/app/src/main/res/raw-ru/changelog_data.xml +++ b/app/src/main/res/raw-ru/changelog_data.xml @@ -1,6 +1,11 @@ - + + + Реализован режим "чтение/запись" для образов флоппи-дисков. + Добавлен полноэкранный режим. + + Реализована эмуляция музыкального сопроцессора AY-3-8910. diff --git a/app/src/main/res/raw/changelog_data.xml b/app/src/main/res/raw/changelog_data.xml index 7d196aa..2f94450 100644 --- a/app/src/main/res/raw/changelog_data.xml +++ b/app/src/main/res/raw/changelog_data.xml @@ -1,6 +1,11 @@ - + + + Implemented read/write mode for floppy disk images. + Added full-screen mode. + + Implemented AY-3-8910 programmable sound generator emulation. diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index af62d05..a0f3f77 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -67,4 +67,5 @@ BkEmu Громкость Аудиовыход + Защита записи diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 0a87498..3769545 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -13,4 +13,9 @@ 20dp 5dp 10dp + + 5dp + 5dp + 3dp + 24sp \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 625af9e..9a8ae9f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -69,4 +69,5 @@ Speaker Covox AY8910 + Write Protect diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 19f6d1a..51b8afa 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -12,7 +12,13 @@ @color/theme_window_background + + #212121 + #505050 #000000 #101010 diff --git a/app/src/test/java/su/comp/bk/arch/io/FloppyControllerTest.java b/app/src/test/java/su/comp/bk/arch/io/FloppyControllerTest.java index a6c0966..2642ae6 100644 --- a/app/src/test/java/su/comp/bk/arch/io/FloppyControllerTest.java +++ b/app/src/test/java/su/comp/bk/arch/io/FloppyControllerTest.java @@ -19,44 +19,70 @@ */ package su.comp.bk.arch.io; -import static org.junit.Assert.*; +import android.util.Log; -import java.io.File; +import net.lachlanmckee.timberjunit.TimberTestRule; import org.apache.commons.io.FileUtils; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +import java.io.File; +import java.nio.ByteBuffer; +import java.util.Arrays; import su.comp.bk.ResourceFileTestBase; import su.comp.bk.arch.Computer; import su.comp.bk.arch.cpu.Cpu; import su.comp.bk.arch.io.FloppyController.FloppyDrive; -import su.comp.bk.arch.io.FloppyController.FloppyDrive.FloppyDriveTrackSequence; import su.comp.bk.arch.io.FloppyController.FloppyDriveIdentifier; import su.comp.bk.arch.io.FloppyController.FloppyDriveSide; import su.comp.bk.arch.memory.RandomAccessMemory; import su.comp.bk.arch.memory.ReadOnlyMemory; import su.comp.bk.util.Crc16; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + /** * {@link FloppyController} class unit tests. */ +@RunWith(RobolectricTestRunner.class) public class FloppyControllerTest extends ResourceFileTestBase { - private final static int TEST_DISK_NUM_TRACKS = 80; private final static String TEST_DISK_IMAGE_FILE_NAME = "test disk.img"; private final static String FDD_ROM_FILE_NAME = "disk_327.rom"; private final static String MONITOR_ROM_FILE_NAME = "monit10.rom"; + private static final int FDD_ERROR_CODE_ADDR = 052; private static final int FDD_BLOCK_START_ADDR = 02000; + private static final int FDD_BLOCK_FORMAT_BYTE = FDD_BLOCK_START_ADDR + 017; + private static final int FDD_BLOCK_FLAGS_A = FDD_BLOCK_START_ADDR + 022; + private static final int FDD_BLOCK_DISK_SIDE = FDD_BLOCK_START_ADDR + 032; + private static final int FDD_BLOCK_DISK_TRACK = FDD_BLOCK_START_ADDR + 033; private static final int FDD_BLOCK_DRIVE_NUM = FDD_BLOCK_START_ADDR + 034; + private static final int FDD_BLOCK_SECLEN = FDD_BLOCK_START_ADDR + 064; private final static int MAX_CPU_OPS = Integer.MAX_VALUE; private Computer computer; private FloppyController floppyController; + @Rule + public TimberTestRule timberTestRule = TimberTestRule.builder() + .minPriority(Log.WARN) + .showThread(false) + .showTimestamp(false) + .onlyLogWhenTestFails(false) + .build(); + @Before public void setUp() throws Exception { // Set test computer configuration @@ -98,121 +124,120 @@ public void testFloppyController() throws Exception { FloppyDrive drive = floppyController.getFloppyDrive(FloppyDriveIdentifier.A); assertEquals(0, drive.getNextTrackNumber(false)); assertEquals(1, drive.getNextTrackNumber(true)); - drive.setCurrentTrack(FloppyController.TRACKS_PER_DISK - 1, FloppyDriveSide.DOWN); - assertEquals(FloppyController.TRACKS_PER_DISK - 2, drive.getNextTrackNumber(false)); - assertEquals(FloppyController.TRACKS_PER_DISK - 1, drive.getNextTrackNumber(true)); + drive.setCurrentTrack(drive.getLastTrackNumber(), FloppyDriveSide.DOWN); + assertEquals(drive.getLastTrackNumber() - 1, drive.getNextTrackNumber(false)); + assertEquals(drive.getLastTrackNumber(), drive.getNextTrackNumber(true)); drive.setCurrentTrack(0, FloppyDriveSide.DOWN); // Check unmounted track data reading for (int position = 0; position < FloppyController.WORDS_PER_TRACK; position++) { assertEquals(0, drive.readCurrentTrackData(position)); } // Mount disk image file - byte[] testDiskImageData = mountTestDiskImage(); + byte[] testDiskImageData = mountTestDiskImage(true); int testDiskImageDataIndex = 0; // Check mounted disk image data reading for (int trackNumber = 0; trackNumber < TEST_DISK_NUM_TRACKS; trackNumber++) { - System.out.println("Track: " + trackNumber); for (FloppyDriveSide trackSide : FloppyDriveSide.values()) { drive.setCurrentTrack(trackNumber, trackSide); - int trackPosition = 0; - int wordsToCheck = FloppyDriveTrackSequence.SEQ_GAP1_LENGTH; - // Check GAP1 - while (wordsToCheck-- > 0) { - assertEquals("GAP1 position: " + trackPosition, FloppyDriveTrackSequence.SEQ_GAP, - drive.readCurrentTrackData(trackPosition++)); + testDiskImageDataIndex = checkCurrentTrackData(drive, testDiskImageData, + testDiskImageDataIndex); + } + } + } + + private int checkCurrentTrackData(FloppyDrive drive, byte[] data, int offset) { + int trackPosition = 0; + // Skip GAP1 + while (drive.readCurrentTrackData(trackPosition) == FloppyDrive.SEQ_GAP) { + trackPosition++; + } + // Check sectors + for (int sectorNumber = 1; sectorNumber <= FloppyController.SECTORS_PER_TRACK; sectorNumber++) { + // Check header sync + int wordsToCheck = FloppyDrive.SEQ_SYNC_LENGTH; + while (wordsToCheck-- > 0) { + assertEquals("header sync position: " + trackPosition, + FloppyDrive.SEQ_SYNC, drive.readCurrentTrackData(trackPosition++)); + } + // Check IDAM + assertTrue("IDAM position: " + trackPosition, + drive.isCurrentTrackDataMarkerPosition(trackPosition)); + assertEquals("IDAM word 1 position: " + trackPosition, 0xa1a1, + drive.readCurrentTrackData(trackPosition++)); + assertEquals("IDAM word 2 position: " + trackPosition, 0xa1fe, + drive.readCurrentTrackData(trackPosition++)); + // Check head / track numbers + assertEquals("head/track numbers position: " + trackPosition, + (drive.getCurrentTrackNumber() << 8) | drive.getCurrentTrackSide().ordinal(), + drive.readCurrentTrackData(trackPosition++)); + // Check sector number / sector size + assertEquals("sector number / size position: " + trackPosition, + (sectorNumber << 8) | 2, + drive.readCurrentTrackData(trackPosition++)); + // Check sector header CRC + assertEquals("sector header CRC position: " + trackPosition, + Crc16.calculate(new byte[] { + (byte) 0xa1, (byte) 0xa1, (byte) 0xa1, (byte) 0xfe, + (byte) drive.getCurrentTrackNumber(), + (byte) drive.getCurrentTrackSide().ordinal(), + (byte) sectorNumber, 2 }) & 0177777, + drive.readCurrentTrackData(trackPosition++) & 0177777); + // Skip GAP2 + while (drive.readCurrentTrackData(trackPosition) == FloppyDrive.SEQ_GAP) { + trackPosition++; + } + // Check data sync + wordsToCheck = FloppyDrive.SEQ_SYNC_LENGTH; + while (wordsToCheck-- > 0) { + assertEquals("data sync position: " + trackPosition, + FloppyDrive.SEQ_SYNC, drive.readCurrentTrackData(trackPosition++)); + } + // Check DATA AM + assertTrue("DATA AM position: " + trackPosition, + drive.isCurrentTrackDataMarkerPosition(trackPosition)); + assertEquals("DATA AM word 1 position: " + trackPosition, 0xa1a1, + drive.readCurrentTrackData(trackPosition++)); + assertEquals("DATA AM word 2 position: " + trackPosition, 0xa1fb, + drive.readCurrentTrackData(trackPosition++)); + // Check data + short crcValue = Crc16.calculate(new byte[] { (byte) 0xa1, (byte) 0xa1, + (byte) 0xa1, (byte) 0xfb }); + wordsToCheck = FloppyController.WORDS_PER_SECTOR; + while (wordsToCheck-- > 0) { + byte dataByte1 = data[offset++]; + byte dataByte2 = data[offset++]; + assertEquals("Sector data position: " + trackPosition + + ", image index: " + (offset - 2), + ((dataByte1 << 8) & 0177400) | (dataByte2 & 0377), + drive.readCurrentTrackData(trackPosition++)); + crcValue = Crc16.calculate(crcValue, dataByte1); + crcValue = Crc16.calculate(crcValue, dataByte2); + } + // Check sector data CRC + assertEquals("sector data CRC", crcValue & 0177777, + drive.readCurrentTrackData(trackPosition++) & 0177777); + if (sectorNumber < FloppyController.SECTORS_PER_TRACK) { + // Skip GAP3 + while (drive.readCurrentTrackData(trackPosition) == FloppyDrive.SEQ_GAP) { + trackPosition++; + assertTrue("GAP3", trackPosition < FloppyController.WORDS_PER_TRACK); } - // Check sectors - for (int sectorNumber = 1; sectorNumber <= FloppyController.SECTORS_PER_TRACK; sectorNumber++) { - // Check header sync - wordsToCheck = FloppyDriveTrackSequence.SEQ_SYNC_LENGTH; - while (wordsToCheck-- > 0) { - assertEquals("header sync position: " + trackPosition, FloppyDriveTrackSequence.SEQ_SYNC, - drive.readCurrentTrackData(trackPosition++)); - } - // Check IDAM - assertTrue("IDAM position: " + trackPosition, - drive.isCurrentTrackDataMarkerPosition(trackPosition)); - assertEquals("IDAM word 1 position: " + trackPosition, 0xa1a1, - drive.readCurrentTrackData(trackPosition++)); - assertEquals("IDAM word 2 position: " + trackPosition, 0xa1fe, - drive.readCurrentTrackData(trackPosition++)); - // Check head / track numbers - assertEquals("head/track numbers position: " + trackPosition, - (drive.getCurrentTrackNumber() << 8) | drive.getCurrentTrackSide().ordinal(), - drive.readCurrentTrackData(trackPosition++)); - // Check sector number / sector size - assertEquals("sector number / size position: " + trackPosition, - (sectorNumber << 8) | 2, - drive.readCurrentTrackData(trackPosition++)); - // Check sector head CRC - assertTrue("sector header CRC position: " + trackPosition, - drive.isCurrentTrackDataCrcPosition(trackPosition)); - assertEquals("sector header CRC position: " + trackPosition, - Crc16.calculate(new byte[] { - (byte) 0xa1, (byte) 0xa1, (byte) 0xa1, (byte) 0xfe, - (byte) drive.getCurrentTrackNumber(), - (byte) drive.getCurrentTrackSide().ordinal(), - (byte) sectorNumber, 2 }) & 0177777, - drive.readCurrentTrackData(trackPosition++) & 0177777); - // Check GAP2 - wordsToCheck = FloppyDriveTrackSequence.SEQ_GAP2_LENGTH; - while (wordsToCheck-- > 0) { - assertEquals("GAP2 position: " + trackPosition, - FloppyDriveTrackSequence.SEQ_GAP, - drive.readCurrentTrackData(trackPosition++)); - } - // Check data sync - wordsToCheck = FloppyDriveTrackSequence.SEQ_SYNC_LENGTH; - while (wordsToCheck-- > 0) { - assertEquals("data sync position: " + trackPosition, - FloppyDriveTrackSequence.SEQ_SYNC, - drive.readCurrentTrackData(trackPosition++)); - } - // Check DATA AM - assertTrue("DATA AM position: " + trackPosition, - drive.isCurrentTrackDataMarkerPosition(trackPosition)); - assertEquals("DATA AM word 1 position: " + trackPosition, 0xa1a1, - drive.readCurrentTrackData(trackPosition++)); - assertEquals("DATA AM word 2 position: " + trackPosition, 0xa1fb, - drive.readCurrentTrackData(trackPosition++)); - // Check data - short crcValue = Crc16.calculate(new byte[] { (byte) 0xa1, (byte) 0xa1, - (byte) 0xa1, (byte) 0xfb }); - wordsToCheck = FloppyController.BYTES_PER_SECTOR >> 1; - while (wordsToCheck-- > 0) { - byte dataByte1 = testDiskImageData[testDiskImageDataIndex++]; - byte dataByte2 = testDiskImageData[testDiskImageDataIndex++]; - assertEquals("Sector data position: " + trackPosition + - ", image index: " + (testDiskImageDataIndex - 2), - ((dataByte1 << 8) & 0177400) | (dataByte2 & 0377), - drive.readCurrentTrackData(trackPosition++)); - crcValue = Crc16.calculate(crcValue, dataByte1); - crcValue = Crc16.calculate(crcValue, dataByte2); - } - // Check sector data CRC - assertTrue("sector data CRC position: " + trackPosition, - drive.isCurrentTrackDataCrcPosition(trackPosition)); - assertEquals("sector data CRC", crcValue & 0177777, - drive.readCurrentTrackData(trackPosition++) & 0177777); - // Check GAP3/GAP4B - wordsToCheck = (sectorNumber < FloppyController.SECTORS_PER_TRACK) - ? FloppyDriveTrackSequence.SEQ_GAP3_LENGTH - : (FloppyController.WORDS_PER_TRACK - trackPosition ); - while (wordsToCheck-- > 0) { - assertEquals("GAP3/GAP4B position: " + trackPosition, - FloppyDriveTrackSequence.SEQ_GAP, - drive.readCurrentTrackData(trackPosition++)); - } + } else { + // Check GAP4B + while (trackPosition < FloppyController.WORDS_PER_TRACK) { + assertEquals("GAP4B position: " + trackPosition, + FloppyDrive.SEQ_GAP, drive.readCurrentTrackData(trackPosition++)); } } } + return offset; } @Test - public void testFloppyControllerOperations() throws Exception { + public void testFloppyControllerReadOperations() throws Exception { // floppyController.setDebugEnabled(true); // Mount disk image file - byte[] testDiskImageData = mountTestDiskImage(); + byte[] testDiskImageData = mountTestDiskImage(true); Cpu cpu = computer.getCpu(); // Initialize FDD cpu.writeRegister(false, Cpu.R3, FDD_BLOCK_START_ADDR); @@ -226,8 +251,9 @@ public void testFloppyControllerOperations() throws Exception { cpu.writeRegister(false, Cpu.R1, 0400); // Data length cpu.writeRegister(false, Cpu.R2, 01000); // Data read address assertTrue("can't read sector " + blockNumber, execute(0160004)); - assertTrue("sector " + blockNumber + " read error " + computer.readMemory(false, 052), - !cpu.isPswFlagSet(Cpu.PSW_FLAG_C)); + assertFalse("sector " + blockNumber + " read error " + + computer.readMemory(false, FDD_ERROR_CODE_ADDR), + cpu.isPswFlagSet(Cpu.PSW_FLAG_C)); // Check read data for (int address = 01000; address < 02000; address++) { assertEquals("sector " + blockNumber + " read error at address " + @@ -236,11 +262,13 @@ public void testFloppyControllerOperations() throws Exception { } } // Multisector read - dataIndex = 0; cpu.writeRegister(false, Cpu.R0, 0); // Sector number cpu.writeRegister(false, Cpu.R1, 020000); // Data length cpu.writeRegister(false, Cpu.R2, 040000); // Data read address assertTrue("can't read block", execute(0160004)); + assertFalse("block read error " + computer.readMemory(true, FDD_ERROR_CODE_ADDR), + cpu.isPswFlagSet(Cpu.PSW_FLAG_C)); + dataIndex = 0; for (int address = 040000; address < 0100000; address++) { assertEquals("block read error at address " + Integer.toOctalString(address), testDiskImageData[dataIndex++] & 0377, @@ -248,11 +276,54 @@ public void testFloppyControllerOperations() throws Exception { } } - private byte[] mountTestDiskImage() throws Exception { + @Test + public void testFloppyControllerWriteOperations() throws Exception { +// floppyController.setDebugEnabled(true); + // Mount disk image file + byte[] testDiskImageData = mountTestDiskImage(false); + Cpu cpu = computer.getCpu(); + // Initialize FDD + cpu.writeRegister(false, Cpu.R3, FDD_BLOCK_START_ADDR); + assertTrue("can't initialize FDD", execute(0160010)); + cpu.writeMemory(true, FDD_BLOCK_DRIVE_NUM, FloppyDriveIdentifier.A.ordinal()); // Select drive + // Track formatting + FloppyDrive drive = floppyController.getFloppyDrive(FloppyDriveIdentifier.A); + ByteBuffer buf = drive.getMountedDiskImageBuffer(); + cpu.writeMemory(true, FDD_BLOCK_FLAGS_A, 0); // Standard disk format + cpu.writeMemory(true, FDD_BLOCK_FORMAT_BYTE, 0x42); // Byte to write + cpu.writeMemory(true, FDD_BLOCK_DISK_SIDE, 0); // Select side + cpu.writeMemory(true, FDD_BLOCK_DISK_TRACK, 0); // Select track + cpu.writeMemory(false, FDD_BLOCK_SECLEN, 0400); // Sector length + assertTrue("can't format track", execute(0160012)); + assertFalse("track formatting error " + computer.readMemory(true, FDD_ERROR_CODE_ADDR), + cpu.isPswFlagSet(Cpu.PSW_FLAG_C)); + byte[] trackData = new byte[FloppyController.SECTORS_PER_TRACK * FloppyController.BYTES_PER_SECTOR]; + Arrays.fill(trackData, (byte) 0x42); + checkCurrentTrackData(drive, trackData, 0); + for (int i = 0; i < FloppyController.SECTORS_PER_TRACK * FloppyController.BYTES_PER_SECTOR; i++) { + assertEquals("track formatting error at " + i, + trackData[i] & 0377, buf.get(i) & 0377); + } + // Multisector write + for (int i = 0; i < 020000; i++) { + cpu.writeMemory(true, 040000 + i, testDiskImageData[i]); + } + cpu.writeRegister(false, Cpu.R0, 0); // Sector number + cpu.writeRegister(false, Cpu.R1, -020000); // Data length (negative for write) + cpu.writeRegister(false, Cpu.R2, 040000); // Data write address + assertTrue("can't write block", execute(0160004)); + assertFalse("block write error " + computer.readMemory(true, FDD_ERROR_CODE_ADDR), + cpu.isPswFlagSet(Cpu.PSW_FLAG_C)); + for (int i = 0; i < 020000; i++) { + assertEquals("block write error at " + i, + testDiskImageData[i] & 0377, buf.get(i) & 0377); + } + } + + private byte[] mountTestDiskImage(boolean isWriteProtected) throws Exception { File testDiskImageFile = getTestResourceFile(TEST_DISK_IMAGE_FILE_NAME); byte[] testDiskImageData = FileUtils.readFileToByteArray(testDiskImageFile); - floppyController.mountDiskImage(testDiskImageFile.toURI().toString(), - FloppyDriveIdentifier.A, true); + floppyController.mountDiskImage(testDiskImageFile, FloppyDriveIdentifier.A, isWriteProtected); return testDiskImageData; } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c5793e4..3450072 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -#Thu Jun 04 17:02:34 MSK 2020 +#Thu Oct 15 23:53:44 MSK 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME