mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-31 11:17:01 -07:00
Merge branch 'northstar' of https://github.com/hharte/fluxengine into hharte-northstar
This commit is contained in:
@@ -79,6 +79,7 @@ CY_ISR(index_irq_cb)
|
||||
* the track. */
|
||||
static bool hardsec_index_irq_primed = false;
|
||||
static uint32_t hardsec_last_pulse_time = 0;
|
||||
uint32_t index_pulse_duration = clock - hardsec_last_pulse_time;
|
||||
|
||||
if (!hardsec_index_threshold)
|
||||
{
|
||||
@@ -87,12 +88,18 @@ CY_ISR(index_irq_cb)
|
||||
}
|
||||
else
|
||||
{
|
||||
index_irq = hardsec_index_irq_primed;
|
||||
/* It's only an index pulse if the previous pulse is less than
|
||||
* the threshold.
|
||||
*/
|
||||
index_irq = (index_pulse_duration <= hardsec_index_threshold) ?
|
||||
hardsec_index_irq_primed : false;
|
||||
|
||||
if (index_irq)
|
||||
hardsec_index_irq_primed = false;
|
||||
else
|
||||
hardsec_index_irq_primed =
|
||||
clock - hardsec_last_pulse_time <= hardsec_index_threshold;
|
||||
index_pulse_duration <= hardsec_index_threshold;
|
||||
|
||||
hardsec_last_pulse_time = clock;
|
||||
}
|
||||
|
||||
@@ -275,7 +282,6 @@ static void seek_to(int track)
|
||||
CyWdtClear();
|
||||
}
|
||||
CyDelay(STEP_SETTLING_TIME);
|
||||
|
||||
TK43_REG_Write(track < 43); /* high if 0..42, low if 43 or up */
|
||||
print("finished seek");
|
||||
}
|
||||
@@ -305,7 +311,7 @@ static void cmd_measure_speed(struct measurespeed_frame* f)
|
||||
while (!index_irq)
|
||||
{
|
||||
elapsed = clock - start_clock;
|
||||
if (elapsed > 1000)
|
||||
if (elapsed > 1500)
|
||||
{
|
||||
elapsed = 0;
|
||||
break;
|
||||
@@ -416,7 +422,6 @@ static void cmd_read(struct read_frame* f)
|
||||
seek_to(current_track);
|
||||
SIDE_REG_Write(f->side);
|
||||
STEP_REG_Write(f->side); /* for drives which multiplex SIDE and DIR */
|
||||
|
||||
/* Do slow setup *before* we go into the real-time bit. */
|
||||
|
||||
{
|
||||
@@ -562,7 +567,6 @@ static void cmd_write(struct write_frame* f)
|
||||
seek_to(current_track);
|
||||
SIDE_REG_Write(f->side);
|
||||
STEP_REG_Write(f->side); /* for drives which multiplex SIDE and DIR */
|
||||
|
||||
SEQUENCER_CONTROL_Write(1); /* put the sequencer into reset */
|
||||
{
|
||||
uint8_t i = CyEnterCriticalSection();
|
||||
@@ -627,7 +631,6 @@ static void cmd_write(struct write_frame* f)
|
||||
|
||||
/* Wait for the index marker. While this happens, the DMA engine
|
||||
* will prime the FIFO. */
|
||||
|
||||
hardsec_index_threshold = f->hardsec_threshold_ms;
|
||||
index_irq = false;
|
||||
while (!index_irq)
|
||||
@@ -693,7 +696,7 @@ abort:
|
||||
static void cmd_erase(struct erase_frame* f)
|
||||
{
|
||||
SIDE_REG_Write(f->side);
|
||||
seek_to(current_track);
|
||||
seek_to(current_track);
|
||||
/* Disk is now spinning. */
|
||||
|
||||
print("start erasing");
|
||||
|
||||
175
arch/northstar/decoder.cc
Normal file
175
arch/northstar/decoder.cc
Normal file
@@ -0,0 +1,175 @@
|
||||
/* Decoder for North Star 10-sector hard-sectored disks.
|
||||
*
|
||||
* Supports both single- and double-density. For the sector format and
|
||||
* checksum algorithm, see pp. 33 of the North Star Double Density Controller
|
||||
* manual:
|
||||
*
|
||||
* http://bitsavers.org/pdf/northstar/boards/Northstar_MDS-A-D_1978.pdf
|
||||
*
|
||||
* North Star disks do not contain any track/head/sector information
|
||||
* encoded in the sector record. For this reason, we have to be absolutely
|
||||
* sure that the hardSectorId is correct.
|
||||
*/
|
||||
|
||||
#include "globals.h"
|
||||
#include "fluxmap.h"
|
||||
#include "decoders/fluxmapreader.h"
|
||||
#include "decoders/decoders.h"
|
||||
#include "sector.h"
|
||||
#include "northstar.h"
|
||||
#include "bytes.h"
|
||||
#include "fmt/format.h"
|
||||
|
||||
/*
|
||||
* MFM sectors have 32 bytes of 00's followed by two sync characters,
|
||||
* specified in the North Star MDS manual as 0xFBFB.
|
||||
*
|
||||
* This is true for most disks; however, I found a few disks, including an
|
||||
* original North Star DOS/BASIC v2.2.1 DQ disk) that uses 0xFBnn, where
|
||||
* nn is an incrementing pattern.
|
||||
*
|
||||
* 00 00 00 F B
|
||||
* 0000 0000 0000 0000 0000 0000 0101 0101 0100 0101
|
||||
* A A A A A A 5 5 4 5
|
||||
*/
|
||||
static const FluxPattern MFM_PATTERN(64, 0xAAAAAAAAAAAA5545LL);
|
||||
|
||||
/* FM sectors have 16 bytes of 00's followed by 0xFB.
|
||||
* 00 FB
|
||||
* 0000 0000 1111 1111 1110 1111
|
||||
* A A F F E F
|
||||
*/
|
||||
static const FluxPattern FM_PATTERN(64, 0xAAAAAAAAAAAAFFEFLL);
|
||||
|
||||
const FluxMatchers ANY_SECTOR_PATTERN(
|
||||
{
|
||||
&MFM_PATTERN,
|
||||
&FM_PATTERN,
|
||||
}
|
||||
);
|
||||
|
||||
/* Search for FM or MFM sector record */
|
||||
AbstractDecoder::RecordType NorthstarDecoder::advanceToNextRecord()
|
||||
{
|
||||
nanoseconds_t now = _fmr->tell().ns();
|
||||
|
||||
/* For all but the first sector, seek to the next sector pulse.
|
||||
* The first sector does not contain the sector pulse in the fluxmap.
|
||||
*/
|
||||
if (now != 0) {
|
||||
_fmr->seekToIndexMark();
|
||||
now = _fmr->tell().ns();
|
||||
}
|
||||
|
||||
/* Discard a possible partial sector at the end of the track.
|
||||
* This partial sector could be mistaken for a conflicted sector, if
|
||||
* whatever data read happens to match the checksum of 0, which is
|
||||
* rare, but has been observed on some disks.
|
||||
*/
|
||||
if (now > (_fmr->getDuration() - 21e6)) {
|
||||
_fmr->seekToIndexMark();
|
||||
return(UNKNOWN_RECORD);
|
||||
}
|
||||
|
||||
int msSinceIndex = std::round(now / 1e6);
|
||||
|
||||
const FluxMatcher* matcher = nullptr;
|
||||
|
||||
/* Note that the seekToPattern ignores the sector pulses, so if
|
||||
* a sector is not found for some reason, the seek will advance
|
||||
* past one or more sector pulses. For this reason, calculate
|
||||
* _hardSectorId after the sector header is found.
|
||||
*/
|
||||
_sector->clock = _fmr->seekToPattern(ANY_SECTOR_PATTERN, matcher);
|
||||
|
||||
int sectorFoundTimeRaw = std::round((_fmr->tell().ns()) / 1e6);
|
||||
int sectorFoundTime;
|
||||
|
||||
/* Round time to the nearest 20ms */
|
||||
if ((sectorFoundTimeRaw % 20) < 10) {
|
||||
sectorFoundTime = (sectorFoundTimeRaw / 20) * 20;
|
||||
}
|
||||
else {
|
||||
sectorFoundTime = ((sectorFoundTimeRaw + 20) / 20) * 20;
|
||||
}
|
||||
|
||||
/* Calculate the sector ID based on time since the index */
|
||||
_hardSectorId = (sectorFoundTime / 20) % 10;
|
||||
|
||||
// std::cout << fmt::format(
|
||||
// "Sector ID {}: hole at {}ms, sector start at {}ms",
|
||||
// _hardSectorId, msSinceIndex, sectorFoundTimeRaw) << std::endl;
|
||||
|
||||
if (matcher == &MFM_PATTERN) {
|
||||
_sectorType = SECTOR_TYPE_MFM;
|
||||
readRawBits(48);
|
||||
return SECTOR_RECORD;
|
||||
}
|
||||
|
||||
if (matcher == &FM_PATTERN) {
|
||||
_sectorType = SECTOR_TYPE_FM;
|
||||
readRawBits(48);
|
||||
return SECTOR_RECORD;
|
||||
}
|
||||
|
||||
return UNKNOWN_RECORD;
|
||||
}
|
||||
|
||||
/* Checksum is initially 0.
|
||||
* For each data byte, XOR with the current checksum.
|
||||
* Rotate checksum left, carrying bit 7 to bit 0.
|
||||
*/
|
||||
uint8_t northstarChecksum(const Bytes& bytes) {
|
||||
ByteReader br(bytes);
|
||||
uint8_t checksum = 0;
|
||||
|
||||
while (!br.eof()) {
|
||||
checksum ^= br.read_8();
|
||||
checksum = ((checksum << 1) | ((checksum >> 7)));
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
void NorthstarDecoder::decodeSectorRecord()
|
||||
{
|
||||
unsigned recordSize, payloadSize, headerSize;
|
||||
|
||||
if (_sectorType == SECTOR_TYPE_MFM) {
|
||||
recordSize = NORTHSTAR_ENCODED_SECTOR_SIZE_DD;
|
||||
payloadSize = NORTHSTAR_PAYLOAD_SIZE_DD;
|
||||
headerSize = NORTHSTAR_HEADER_SIZE_DD;
|
||||
}
|
||||
else {
|
||||
recordSize = NORTHSTAR_ENCODED_SECTOR_SIZE_SD;
|
||||
payloadSize = NORTHSTAR_PAYLOAD_SIZE_SD;
|
||||
headerSize = NORTHSTAR_HEADER_SIZE_SD;
|
||||
}
|
||||
|
||||
auto rawbits = readRawBits(recordSize * 16);
|
||||
auto bytes = decodeFmMfm(rawbits).slice(0, recordSize);
|
||||
ByteReader br(bytes);
|
||||
uint8_t sync_char;
|
||||
|
||||
_sector->logicalSide = _sector->physicalSide;
|
||||
_sector->logicalSector = _hardSectorId;
|
||||
_sector->logicalTrack = _sector->physicalTrack;
|
||||
|
||||
sync_char = br.read_8(); /* Sync char: 0xFB */
|
||||
if (_sectorType == SECTOR_TYPE_MFM) {
|
||||
sync_char = br.read_8();/* MFM second Sync char, usually 0xFB */
|
||||
}
|
||||
|
||||
_sector->data = br.read(payloadSize);
|
||||
|
||||
uint8_t wantChecksum = br.read_8();
|
||||
uint8_t gotChecksum = northstarChecksum(bytes.slice(headerSize, payloadSize));
|
||||
|
||||
_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
|
||||
std::set<unsigned> NorthstarDecoder::requiredSectors(Track& track) const
|
||||
{
|
||||
static std::set<unsigned> sectors = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
||||
return sectors;
|
||||
}
|
||||
132
arch/northstar/encoder.cc
Normal file
132
arch/northstar/encoder.cc
Normal file
@@ -0,0 +1,132 @@
|
||||
#include "globals.h"
|
||||
#include "northstar.h"
|
||||
#include "sectorset.h"
|
||||
|
||||
FlagGroup northstarEncoderFlags;
|
||||
|
||||
#define GAP_FILL_SIZE_SD 30
|
||||
#define PRE_HEADER_GAP_FILL_SIZE_SD 9
|
||||
#define GAP_FILL_SIZE_DD 62
|
||||
#define PRE_HEADER_GAP_FILL_SIZE_DD 16
|
||||
|
||||
#define GAP1_FILL_BYTE (0x4F)
|
||||
#define GAP2_FILL_BYTE (0x4F)
|
||||
|
||||
#define TOTAL_SECTOR_BYTES ()
|
||||
|
||||
static void write_sector(std::vector<bool>& bits, unsigned& cursor, const Sector* sector)
|
||||
{
|
||||
int preambleSize = 0;
|
||||
int encodedSectorSize = 0;
|
||||
int gapFillSize = 0;
|
||||
int preHeaderGapFillSize = 0;
|
||||
|
||||
bool doubleDensity;
|
||||
|
||||
switch (sector->data.size()) {
|
||||
case NORTHSTAR_PAYLOAD_SIZE_SD:
|
||||
preambleSize = NORTHSTAR_PREAMBLE_SIZE_SD;
|
||||
encodedSectorSize = PRE_HEADER_GAP_FILL_SIZE_SD + NORTHSTAR_ENCODED_SECTOR_SIZE_SD + GAP_FILL_SIZE_SD;
|
||||
gapFillSize = GAP_FILL_SIZE_SD;
|
||||
preHeaderGapFillSize = PRE_HEADER_GAP_FILL_SIZE_SD;
|
||||
doubleDensity = false;
|
||||
break;
|
||||
case NORTHSTAR_PAYLOAD_SIZE_DD:
|
||||
preambleSize = NORTHSTAR_PREAMBLE_SIZE_DD;
|
||||
encodedSectorSize = PRE_HEADER_GAP_FILL_SIZE_DD + NORTHSTAR_ENCODED_SECTOR_SIZE_DD + GAP_FILL_SIZE_DD;
|
||||
gapFillSize = GAP_FILL_SIZE_DD;
|
||||
preHeaderGapFillSize = PRE_HEADER_GAP_FILL_SIZE_DD;
|
||||
doubleDensity = true;
|
||||
break;
|
||||
default:
|
||||
Error() << "unsupported sector size --- you must pick 256 or 512";
|
||||
break;
|
||||
}
|
||||
|
||||
int fullSectorSize = preambleSize + encodedSectorSize;
|
||||
auto fullSector = std::make_shared<std::vector<uint8_t>>();
|
||||
fullSector->reserve(fullSectorSize);
|
||||
|
||||
/* sector gap after index pulse */
|
||||
for (int i = 0; i < preHeaderGapFillSize; i++)
|
||||
fullSector->push_back(GAP1_FILL_BYTE);
|
||||
|
||||
/* sector preamble */
|
||||
for (int i = 0; i < preambleSize; i++)
|
||||
fullSector->push_back(0);
|
||||
|
||||
Bytes sectorData;
|
||||
if (sector->data.size() == encodedSectorSize)
|
||||
sectorData = sector->data;
|
||||
else {
|
||||
ByteWriter writer(sectorData);
|
||||
writer.write_8(0xFB); /* sync character */
|
||||
if (doubleDensity == true) {
|
||||
writer.write_8(0xFB); /* Double-density has two sync characters */
|
||||
}
|
||||
writer += sector->data;
|
||||
if (doubleDensity == true) {
|
||||
writer.write_8(northstarChecksum(sectorData.slice(2)));
|
||||
} else {
|
||||
writer.write_8(northstarChecksum(sectorData.slice(1)));
|
||||
}
|
||||
}
|
||||
for (uint8_t b : sectorData)
|
||||
fullSector->push_back(b);
|
||||
|
||||
if (sector->logicalSector != 9) {
|
||||
/* sector postamble */
|
||||
for (int i = 0; i < gapFillSize; i++)
|
||||
fullSector->push_back(GAP2_FILL_BYTE);
|
||||
|
||||
if (fullSector->size() != fullSectorSize)
|
||||
Error() << "sector mismatched length (" << sector->data.size() << ") expected: " << fullSector->size() << " got " << fullSectorSize;
|
||||
} else {
|
||||
/* sector postamble */
|
||||
for (int i = 0; i < gapFillSize; i++)
|
||||
fullSector->push_back(GAP2_FILL_BYTE);
|
||||
}
|
||||
|
||||
bool lastBit = false;
|
||||
|
||||
if (doubleDensity == true) {
|
||||
encodeMfm(bits, cursor, fullSector, lastBit);
|
||||
}
|
||||
else {
|
||||
encodeFm(bits, cursor, fullSector);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Fluxmap> NorthstarEncoder::encode(
|
||||
int physicalTrack, int physicalSide, const SectorSet& allSectors)
|
||||
{
|
||||
int bitsPerRevolution = 100000;
|
||||
double clockRateUs = 4.00;
|
||||
|
||||
if ((physicalTrack < 0) || (physicalTrack >= 35))
|
||||
return std::unique_ptr<Fluxmap>();
|
||||
|
||||
const auto& sector = allSectors.get(physicalTrack, physicalSide, 0);
|
||||
|
||||
if (sector->data.size() == NORTHSTAR_PAYLOAD_SIZE_SD) {
|
||||
bitsPerRevolution /= 2; // FM
|
||||
} else {
|
||||
clockRateUs /= 2.00;
|
||||
}
|
||||
|
||||
std::vector<bool> bits(bitsPerRevolution);
|
||||
unsigned cursor = 0;
|
||||
|
||||
for (int sectorId = 0; sectorId < 10; sectorId++)
|
||||
{
|
||||
const auto& sectorData = allSectors.get(physicalTrack, physicalSide, sectorId);
|
||||
write_sector(bits, cursor, sectorData);
|
||||
}
|
||||
|
||||
if (cursor > bits.size())
|
||||
Error() << "track data overrun";
|
||||
|
||||
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
|
||||
fluxmap->appendBits(bits, clockRateUs * 1e3);
|
||||
return fluxmap;
|
||||
}
|
||||
60
arch/northstar/northstar.h
Normal file
60
arch/northstar/northstar.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#ifndef NORTHSTAR_H
|
||||
#define NORTHSTAR_H
|
||||
|
||||
/* Northstar floppies are 10-hard sectored disks with a sector format as follows:
|
||||
*
|
||||
* |----------------------------------|
|
||||
* | SYNC Byte | Payload | Checksum |
|
||||
* |------------+----------+----------|
|
||||
* | 1 (0xFB) | 256 (SD) | 1 |
|
||||
* | 2 (0xFBFB) | 512 (DD) | |
|
||||
* |----------------------------------|
|
||||
*
|
||||
*/
|
||||
|
||||
#include "decoders/decoders.h"
|
||||
#include "encoders/encoders.h"
|
||||
|
||||
#define NORTHSTAR_PREAMBLE_SIZE_SD (16)
|
||||
#define NORTHSTAR_PREAMBLE_SIZE_DD (32)
|
||||
#define NORTHSTAR_HEADER_SIZE_SD (1)
|
||||
#define NORTHSTAR_HEADER_SIZE_DD (2)
|
||||
#define NORTHSTAR_PAYLOAD_SIZE_SD (256)
|
||||
#define NORTHSTAR_PAYLOAD_SIZE_DD (512)
|
||||
#define NORTHSTAR_CHECKSUM_SIZE (1)
|
||||
#define NORTHSTAR_ENCODED_SECTOR_SIZE_SD (NORTHSTAR_HEADER_SIZE_SD + NORTHSTAR_PAYLOAD_SIZE_SD + NORTHSTAR_CHECKSUM_SIZE)
|
||||
#define NORTHSTAR_ENCODED_SECTOR_SIZE_DD (NORTHSTAR_HEADER_SIZE_DD + NORTHSTAR_PAYLOAD_SIZE_DD + NORTHSTAR_CHECKSUM_SIZE)
|
||||
|
||||
#define SECTOR_TYPE_MFM (0)
|
||||
#define SECTOR_TYPE_FM (1)
|
||||
|
||||
class NorthstarDecoder : public AbstractDecoder
|
||||
{
|
||||
public:
|
||||
NorthstarDecoder()
|
||||
{
|
||||
_sectorType = SECTOR_TYPE_MFM;
|
||||
}
|
||||
|
||||
virtual ~NorthstarDecoder() {}
|
||||
|
||||
RecordType advanceToNextRecord();
|
||||
void decodeSectorRecord();
|
||||
std::set<unsigned> requiredSectors(Track& track) const;
|
||||
|
||||
private:
|
||||
uint8_t _sectorType;
|
||||
uint8_t _hardSectorId;
|
||||
};
|
||||
|
||||
class NorthstarEncoder : public AbstractEncoder
|
||||
{
|
||||
public:
|
||||
virtual ~NorthstarEncoder() {}
|
||||
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide, const SectorSet& allSectors);
|
||||
};
|
||||
|
||||
extern FlagGroup northstarEncoderFlags;
|
||||
extern uint8_t northstarChecksum(const Bytes& bytes);
|
||||
|
||||
#endif /* NORTHSTAR */
|
||||
72
doc/disk-northstar.md
Normal file
72
doc/disk-northstar.md
Normal file
@@ -0,0 +1,72 @@
|
||||
Disk: Northstar
|
||||
================
|
||||
|
||||
Northstar Floppy disks use 10-sector hard sectored disks with either FM or MFM
|
||||
encoding. They may be single- or double-sided. Each of the 10 sectors contains
|
||||
256 (FM) or 512 (MFM) bytes of data. The disk has 35 cylinders, with tracks 0-
|
||||
34 on side 0, and tracks 35-69 on side 1. Tracks on side 1 are numbered "back-
|
||||
wards" in that track 35 corresponds to cylinder 34, side 1, and track 69
|
||||
corresponds to cylinder 0, side 1.
|
||||
|
||||
The Northstar sector format does not include any head positioning information.
|
||||
As such, reads from Northstar floppies need to by synchronized with the index
|
||||
pulse, in order to properly identify the sector being read. This is handled
|
||||
automatically by FluxEngine.
|
||||
|
||||
Due to the nature of the track ordering on side 1, an .nsi image reader and
|
||||
writer are provided for double-sided disks. The .nsi image writer supports
|
||||
both single- and double-sided disks; however single-sided .nsi images are
|
||||
equivalent to .img images.
|
||||
|
||||
Reading disks
|
||||
-------------
|
||||
|
||||
You must use a 48-TPI (40-track) 300RPM 5.25" floppy drive.
|
||||
|
||||
To read a double-sided North Star floppy, run:
|
||||
|
||||
```
|
||||
fluxengine read northstar
|
||||
```
|
||||
|
||||
To read a single-sided North Star floppy, run:
|
||||
|
||||
```
|
||||
fluxengine read northstar -s:0
|
||||
```
|
||||
|
||||
You should end up with a `northstar.nsi` with a file size dependent on the floppy
|
||||
disk type:
|
||||
|
||||
| Disk Type | File Size (bytes) |
|
||||
| ----------------------------------- | ------- |
|
||||
| Single-Sided, Single-Density (SSSD) | 89,600 |
|
||||
| Single-Sided, Double-Density (SSDD) | 179,200 |
|
||||
| Double-Sided, Double-Density (DSDD) | 358,400 |
|
||||
|
||||
Writing disks
|
||||
-------------
|
||||
|
||||
You must use a 48-TPI (40-track) 300RPM 5.25" floppy drive and make
|
||||
sure that the drive's spindle speed is adjusted to exactly 300RPM.
|
||||
|
||||
To write a double-sided North Star floppy, run:
|
||||
|
||||
```
|
||||
fluxengine write northstar -i image_to_write.nsi
|
||||
```
|
||||
|
||||
To write a single-sided North Star floppy, run:
|
||||
|
||||
```
|
||||
fluxengine write northstar -i image_to_write.nsi -d:s=0
|
||||
```
|
||||
|
||||
|
||||
Useful references
|
||||
-----------------
|
||||
|
||||
- [MICRO-DISK SYSTEM MDS-A-D DOUBLE DENSITY Manual][northstar_mds].
|
||||
Page 33 documents sector format for single- and double-density.
|
||||
|
||||
[northstar_mds]: http://bitsavers.org/pdf/northstar/boards/Northstar_MDS-A-D_1978.pdf
|
||||
@@ -88,6 +88,11 @@ public:
|
||||
_pos = pos;
|
||||
}
|
||||
|
||||
int getDuration(void)
|
||||
{
|
||||
return (_fluxmap.duration());
|
||||
}
|
||||
|
||||
uint8_t getNextEvent(unsigned& ticks);
|
||||
unsigned findEvent(uint8_t bits);
|
||||
unsigned readInterval(nanoseconds_t clock); /* with debounce support */
|
||||
|
||||
104
lib/imagereader/nsiimagereader.cc
Normal file
104
lib/imagereader/nsiimagereader.cc
Normal file
@@ -0,0 +1,104 @@
|
||||
/* Image reader for Northstar floppy disk images */
|
||||
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "dataspec.h"
|
||||
#include "sector.h"
|
||||
#include "sectorset.h"
|
||||
#include "imagereader/imagereader.h"
|
||||
#include "fmt/format.h"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
class NsiImageReader : public ImageReader
|
||||
{
|
||||
public:
|
||||
NsiImageReader(const ImageSpec& spec):
|
||||
ImageReader(spec)
|
||||
{}
|
||||
|
||||
SectorSet readImage()
|
||||
{
|
||||
std::ifstream inputFile(spec.filename, std::ios::in | std::ios::binary);
|
||||
if (!inputFile.is_open())
|
||||
Error() << "cannot open input file";
|
||||
|
||||
if ((spec.cylinders == 0) && (spec.heads == 0) && (spec.sectors == 0) && (spec.bytes == 0)) {
|
||||
const auto begin = inputFile.tellg();
|
||||
inputFile.seekg(0, std::ios::end);
|
||||
const auto end = inputFile.tellg();
|
||||
const auto fsize = (end - begin);
|
||||
|
||||
std::cout << "NSI: Autodetecting geometry based on file size: " << fsize << std::endl;
|
||||
|
||||
spec.cylinders = 35;
|
||||
spec.sectors = 10;
|
||||
|
||||
switch (fsize) {
|
||||
case 358400:
|
||||
spec.heads = 2;
|
||||
spec.bytes = 512;
|
||||
break;
|
||||
case 179200:
|
||||
spec.heads = 1;
|
||||
spec.bytes = 512;
|
||||
break;
|
||||
case 89600:
|
||||
spec.heads = 1;
|
||||
spec.bytes = 256;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
size_t trackSize = spec.sectors * spec.bytes;
|
||||
|
||||
std::cout << fmt::format("reading {} tracks, {} heads, {} sectors, {} bytes per sector, {} kB total",
|
||||
spec.cylinders, spec.heads,
|
||||
spec.sectors, spec.bytes,
|
||||
spec.cylinders * spec.heads * trackSize / 1024)
|
||||
<< std::endl;
|
||||
|
||||
SectorSet sectors;
|
||||
unsigned sectorFileOffset;
|
||||
|
||||
for (int head = 0; head < spec.heads; head++)
|
||||
{
|
||||
for (int track = 0; track < spec.cylinders; track++)
|
||||
{
|
||||
for (int sectorId = 0; sectorId < spec.sectors; sectorId++)
|
||||
{
|
||||
if (head == 0) { /* Head 0 is from track 0-34 */
|
||||
sectorFileOffset = track * trackSize + sectorId * spec.bytes;
|
||||
}
|
||||
else { /* Head 1 is from track 70-35 */
|
||||
sectorFileOffset = (trackSize * spec.cylinders) + /* Skip over side 0 */
|
||||
((spec.cylinders - track - 1) * trackSize) +
|
||||
(sectorId * spec.bytes); /* Sector offset from beginning of track. */
|
||||
}
|
||||
|
||||
inputFile.seekg(sectorFileOffset, std::ios::beg);
|
||||
|
||||
Bytes data(spec.bytes);
|
||||
inputFile.read((char*) data.begin(), spec.bytes);
|
||||
|
||||
std::unique_ptr<Sector>& sector = sectors.get(track, head, sectorId);
|
||||
sector.reset(new Sector);
|
||||
sector->status = Sector::OK;
|
||||
sector->logicalTrack = sector->physicalTrack = track;
|
||||
sector->logicalSide = sector->physicalSide = head;
|
||||
sector->logicalSector = sectorId;
|
||||
sector->data = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
return sectors;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<ImageReader> ImageReader::createNsiImageReader(
|
||||
const ImageSpec& spec)
|
||||
{
|
||||
return std::unique_ptr<ImageReader>(new NsiImageReader(spec));
|
||||
}
|
||||
|
||||
78
lib/imagewriter/nsiimagewriter.cc
Normal file
78
lib/imagewriter/nsiimagewriter.cc
Normal file
@@ -0,0 +1,78 @@
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "dataspec.h"
|
||||
#include "sector.h"
|
||||
#include "sectorset.h"
|
||||
#include "imagewriter/imagewriter.h"
|
||||
#include "fmt/format.h"
|
||||
#include "decoders/decoders.h"
|
||||
#include "arch/northstar/northstar.h"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
class NSIImageWriter : public ImageWriter
|
||||
{
|
||||
public:
|
||||
NSIImageWriter(const SectorSet& sectors, const ImageSpec& spec):
|
||||
ImageWriter(sectors, spec)
|
||||
{}
|
||||
|
||||
void writeImage()
|
||||
{
|
||||
unsigned numCylinders = spec.cylinders;
|
||||
unsigned numHeads = spec.heads;
|
||||
unsigned numSectors = spec.sectors;
|
||||
unsigned numTracks = numCylinders * numHeads;
|
||||
unsigned numBytes = spec.bytes;
|
||||
int head;
|
||||
|
||||
size_t trackSize = numSectors * numBytes;
|
||||
|
||||
if ((numBytes != 256) && (numBytes != 512) && (numBytes != 257) && (numBytes != 513))
|
||||
Error() << "Sector size must be 256 or 512.";
|
||||
|
||||
if (numCylinders != 35)
|
||||
Error() << "Number of cylinders must be 35.";
|
||||
|
||||
std::cout << fmt::format("Writing {} cylinders, {} heads, {} sectors, {} ({} bytes/sector), {} kB total",
|
||||
numCylinders, numHeads,
|
||||
numSectors, numBytes == 256 ? "SD" : "DD", numBytes,
|
||||
numTracks * trackSize / 1024)
|
||||
<< std::endl;
|
||||
|
||||
std::ofstream outputFile(spec.filename, std::ios::out | std::ios::binary);
|
||||
if (!outputFile.is_open())
|
||||
Error() << "cannot open output file";
|
||||
|
||||
unsigned sectorFileOffset;
|
||||
|
||||
for (int track = 0; track < numCylinders * numHeads; track++)
|
||||
{
|
||||
head = (track < numCylinders) ? 0 : 1;
|
||||
for (int sectorId = 0; sectorId < numSectors; sectorId++)
|
||||
{
|
||||
const auto& sector = sectors.get(track % numCylinders, head, sectorId);
|
||||
if (sector)
|
||||
{
|
||||
if (head == 0) { /* Side 0 is from track 0-34 */
|
||||
sectorFileOffset = track * trackSize + sectorId * numBytes;
|
||||
}
|
||||
else { /* Side 1 is from track 70-35 */
|
||||
sectorFileOffset = (numBytes * numSectors * numCylinders) + /* Skip over side 0 */
|
||||
((numCylinders - 1) - (track % numCylinders)) * (numBytes * numSectors) +
|
||||
(sectorId * numBytes); /* Sector offset from beginning of track. */
|
||||
}
|
||||
outputFile.seekp(sectorFileOffset, std::ios::beg);
|
||||
sector->data.slice(0, numBytes).writeTo(outputFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<ImageWriter> ImageWriter::createNSIImageWriter(
|
||||
const SectorSet& sectors, const ImageSpec& spec)
|
||||
{
|
||||
return std::unique_ptr<ImageWriter>(new NSIImageWriter(sectors, spec));
|
||||
}
|
||||
27
src/fe-readnorthstar.cc
Normal file
27
src/fe-readnorthstar.cc
Normal file
@@ -0,0 +1,27 @@
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "reader.h"
|
||||
#include "fluxmap.h"
|
||||
#include "encoders/encoders.h"
|
||||
#include "decoders/decoders.h"
|
||||
#include "sector.h"
|
||||
#include "sectorset.h"
|
||||
#include "record.h"
|
||||
#include "northstar/northstar.h"
|
||||
#include "fmt/format.h"
|
||||
|
||||
static FlagGroup flags { &readerFlags };
|
||||
|
||||
int mainReadNorthstar(int argc, const char* argv[])
|
||||
{
|
||||
setReaderDefaultSource(":t=0-34");
|
||||
setReaderDefaultOutput("northstar.nsi");
|
||||
setReaderHardSectorCount(10);
|
||||
setReaderFluxSourceSynced(true);
|
||||
flags.parseFlags(argc, argv);
|
||||
|
||||
NorthstarDecoder decoder;
|
||||
readDiskCommand(decoder);
|
||||
return 0;
|
||||
}
|
||||
|
||||
20
src/fe-writenorthstar.cc
Normal file
20
src/fe-writenorthstar.cc
Normal file
@@ -0,0 +1,20 @@
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "decoders/decoders.h"
|
||||
#include "northstar/northstar.h"
|
||||
#include "writer.h"
|
||||
|
||||
static FlagGroup flags { &writerFlags, &northstarEncoderFlags };
|
||||
|
||||
int mainWriteNorthstar(int argc, const char* argv[])
|
||||
{
|
||||
setWriterDefaultDest(":t=0-34");
|
||||
setWriterHardSectorCount(10);
|
||||
flags.parseFlags(argc, argv);
|
||||
|
||||
NorthstarEncoder encoder;
|
||||
writeDiskCommand(encoder);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user