Skip to content

Commit

Permalink
Merge pull request #668 from davidgiven/ms2000
Browse files Browse the repository at this point in the history
Add basic support for the MS2000 Microdos file system.
  • Loading branch information
davidgiven authored Aug 19, 2023
2 parents 9bd969a + 7456fd0 commit ed315ea
Show file tree
Hide file tree
Showing 10 changed files with 311 additions and 5 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ choices because they can store multiple types of file system.
| [`icl30`](doc/disk-icl30.md) | ICL Model 30: CP/M; 263kB 35-track DSSD | 🦖 | | CPMFS |
| [`mac`](doc/disk-mac.md) | Macintosh: 400kB/800kB 3.5" GCR | 🦄 | 🦄 | MACHFS |
| [`micropolis`](doc/disk-micropolis.md) | Micropolis: 100tpi MetaFloppy disks | 🦄 | 🦄 | |
| [`ms2000`](doc/disk-ms2000.md) | : MS2000 Microdisk Development System | | | MICRODOS |
| [`mx`](doc/disk-mx.md) | DVK MX: Soviet-era PDP-11 clone | 🦖 | | |
| [`n88basic`](doc/disk-n88basic.md) | N88-BASIC: PC8800/PC98 5.25" 77-track 26-sector DSHD | 🦄 | 🦄 | |
| [`northstar`](doc/disk-northstar.md) | Northstar: 5.25" hard sectored | 🦄 | 🦄 | |
Expand Down
1 change: 1 addition & 0 deletions lib/build.mk
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ LIBFLUXENGINE_SRCS = \
lib/vfs/imagesectorinterface.cc \
lib/vfs/lif.cc \
lib/vfs/machfs.cc \
lib/vfs/microdos.cc \
lib/vfs/philefs.cc \
lib/vfs/prodos.cc \
lib/vfs/roland.cc \
Expand Down
11 changes: 11 additions & 0 deletions lib/utils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,17 @@ bool doesFileExist(const std::string& filename)
return f.good();
}

int countSetBits(uint32_t word)
{
int b = 0;
while (word)
{
b += word & 1;
word >>= 1;
}
return b;
}

uint32_t unbcd(uint32_t bcd)
{
uint32_t dec = 0;
Expand Down
1 change: 1 addition & 0 deletions lib/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ extern std::string quote(const std::string& s);
extern std::string unhex(const std::string& s);
extern std::string tohex(const std::string& s);
extern bool doesFileExist(const std::string& filename);
extern int countSetBits(uint32_t word);
extern uint32_t unbcd(uint32_t bcd);

template <class K, class V>
Expand Down
223 changes: 223 additions & 0 deletions lib/vfs/microdos.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
#include "lib/globals.h"
#include "lib/vfs/vfs.h"
#include "lib/config.pb.h"
#include "lib/utils.h"
#include <fmt/format.h>

/* See https://www.hp9845.net/9845/projects/hpdir/#lif_filesystem for
* a description. */

static void trimZeros(std::string s)
{
s.erase(std::remove(s.begin(), s.end(), 0), s.end());
}

class MicrodosFilesystem : public Filesystem
{
struct SDW
{
unsigned start;
unsigned length;
};

class MicrodosDirent : public Dirent
{
public:
MicrodosDirent(MicrodosFilesystem& fs, Bytes& bytes)
{
file_type = TYPE_FILE;

ByteReader br(bytes);
auto stem = trimWhitespace(br.read(6));
auto ext = trimWhitespace(br.read(3));
filename = fmt::format("{}.{}", stem, ext);

br.skip(1);
ssn = br.read_be16();
attr = br.read_8();

Bytes rib = fs.getLogicalSector(ssn);
ByteReader rbr(rib);
for (int i = 0; i < 57; i++)
{
unsigned w = rbr.read_be16();
if (w & 0x8000)
{
/* Last. */
sectors = w & 0x7fff;
break;
}
else
{
/* Each record except the last is 24 bits long. */
w = (w << 8) | rbr.read_8();
sdws.emplace_back(SDW{w & 0xffff, (w >> 16) + 1});
}
}
rbr.seek(500);
lastSectorBytes = rbr.read_be16();
loadSectors = rbr.read_be16();
loadAddress = rbr.read_be16();
startAddress = rbr.read_be16();

length = sectors * 512;

mode = "";
path = {filename};

attributes[Filesystem::FILENAME] = filename;
attributes[Filesystem::LENGTH] = std::to_string(length);
attributes[Filesystem::FILE_TYPE] = "file";
attributes[Filesystem::MODE] = mode;
attributes["microdos.ssn"] = std::to_string(ssn);
attributes["microdos.attr"] = fmt::format("0x{:x}", attr);
attributes["microdos.sdw_count"] = std::to_string(sdws.size());
attributes["microdos.total_sectors"] = std::to_string(sectors);
attributes["microdos.lastSectorBytes"] =
std::to_string(lastSectorBytes);
attributes["microdos.loadSectors"] = std::to_string(loadSectors);
attributes["microdos.loadAddress"] =
fmt::format("0x{:x}", loadAddress);
attributes["microdos.startAddress"] =
fmt::format("0x{:x}", startAddress);
}

public:
unsigned ssn;
unsigned attr;
std::vector<SDW> sdws;
unsigned sectors;
unsigned lastSectorBytes;
unsigned loadSectors;
unsigned loadAddress;
unsigned startAddress;
};

public:
MicrodosFilesystem(
const MicrodosProto& config, std::shared_ptr<SectorInterface> sectors):
Filesystem(sectors),
_config(config)
{
}

uint32_t capabilities() const
{
return OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_GETDIRENT;
}

FilesystemStatus check() override
{
return FS_OK;
}

std::map<std::string, std::string> getMetadata() override
{
mount();

std::map<std::string, std::string> attributes;

attributes[VOLUME_NAME] = _volumeLabel;
attributes[TOTAL_BLOCKS] = std::to_string(_totalBlocks);
attributes[USED_BLOCKS] = std::to_string(_usedBlocks);
attributes[BLOCK_SIZE] = "512";
return attributes;
}

std::shared_ptr<Dirent> getDirent(const Path& path) override
{
mount();
if (path.size() != 1)
throw BadPathException();

return findFile(path.front());
}

std::vector<std::shared_ptr<Dirent>> list(const Path& path) override
{
mount();
if (!path.empty())
throw FileNotFoundException();

std::vector<std::shared_ptr<Dirent>> result;
for (auto& de : _dirents)
result.push_back(de);
return result;
}

Bytes getFile(const Path& path) override
{
mount();
if (path.size() != 1)
throw BadPathException();

auto dirent = findFile(path.front());

Bytes data;
ByteWriter bw(data);
for (const auto& sdw : dirent->sdws)
bw += getLogicalSector(sdw.start, sdw.length);

return data.slice(512);
}

private:
void mount()
{
_rootBlock = getLogicalSector(0);
_catBlock = getLogicalSector(9);
Bytes directory = getLogicalSector(1, 8);

ByteReader rbr(_rootBlock);
rbr.seek(20);
_volumeLabel = trimWhitespace(rbr.read(44));

_dirents.clear();
ByteReader dbr(directory);
while (!dbr.eof())
{
Bytes direntBytes = dbr.read(16);
if ((direntBytes[0] != 0) && (direntBytes[0] != 0xff))
{
auto dirent =
std::make_unique<MicrodosDirent>(*this, direntBytes);
_dirents.push_back(std::move(dirent));
}
}

ByteReader cbr(_catBlock);
_totalBlocks = 630;
_usedBlocks = 0;
for (int i = 0; i < _totalBlocks / 8; i++)
{
uint8_t b = cbr.read_8();
_usedBlocks += countSetBits(b);
}
}

std::shared_ptr<MicrodosDirent> findFile(const std::string filename)
{
for (const auto& dirent : _dirents)
{
if (dirent->filename == filename)
return dirent;
}

throw FileNotFoundException();
}

private:
const MicrodosProto& _config;
Bytes _rootBlock;
Bytes _catBlock;
std::string _volumeLabel;
unsigned _totalBlocks;
unsigned _usedBlocks;
std::vector<std::shared_ptr<MicrodosDirent>> _dirents;
};

std::unique_ptr<Filesystem> Filesystem::createMicrodosFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> sectors)
{
return std::make_unique<MicrodosFilesystem>(config.microdos(), sectors);
}
3 changes: 3 additions & 0 deletions lib/vfs/vfs.cc
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,9 @@ std::unique_ptr<Filesystem> Filesystem::createFilesystem(
case FilesystemProto::LIF:
return Filesystem::createLifFilesystem(config, image);

case FilesystemProto::MICRODOS:
return Filesystem::createMicrodosFilesystem(config, image);

case FilesystemProto::ZDOS:
return Filesystem::createZDosFilesystem(config, image);

Expand Down
2 changes: 2 additions & 0 deletions lib/vfs/vfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,8 @@ class Filesystem
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
static std::unique_ptr<Filesystem> createLifFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
static std::unique_ptr<Filesystem> createMicrodosFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
static std::unique_ptr<Filesystem> createZDosFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
static std::unique_ptr<Filesystem> createRolandFsFilesystem(
Expand Down
15 changes: 10 additions & 5 deletions lib/vfs/vfs.proto
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ message LifProto
[ default = 256, (help) = "LIF filesystem block size" ];
}

message MicrodosProto {}

// NEXT_TAG: 16
message ZDosProto
{
message Location
Expand All @@ -110,7 +113,7 @@ message RolandFsProto
[ (help) = "number of directory entries", default = 79 ];
}

// NEXT_TAG: 17
// NEXT_TAG: 18
message FilesystemProto
{
enum FilesystemType
Expand All @@ -128,8 +131,9 @@ message FilesystemProto
APPLEDOS = 10;
PHILE = 11;
LIF = 12;
ZDOS = 13;
ROLAND = 14;
MICRODOS = 13;
ZDOS = 14;
ROLAND = 15;
}

optional FilesystemType type = 10
Expand All @@ -147,8 +151,9 @@ message FilesystemProto
optional Smaky6FsProto smaky6 = 11;
optional PhileProto phile = 13;
optional LifProto lif = 14;
optional ZDosProto zdos = 15;
optional RolandFsProto roland = 16;
optional MicrodosProto microdos = 15;
optional ZDosProto zdos = 16;
optional RolandFsProto roland = 17;

optional SectorListProto sector_order = 9
[ (help) = "specify the filesystem order of sectors" ];
Expand Down
1 change: 1 addition & 0 deletions src/formats/build.mk
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ FORMATS = \
icl30 \
mac \
micropolis \
ms2000 \
mx \
n88basic \
northstar \
Expand Down
58 changes: 58 additions & 0 deletions src/formats/ms2000.textpb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
comment: 'MS2000 Microdisk Development System'

documentation:
<<<
The RCA MicroDisk Development System MS2000 is a highly obscure (i.e. I gather
that single digit numbers of original machines exist) development system for the
RCA1802 series of CPUs, as made famous by the Cosmac ELF. It was a fairly
straightforward big bag o'RAM system with a 2kB boot ROM, 62kB of RAM, twin
floppy drives and a serial terminal --- CP/M users will find it very familiar.

Read and writing disks is currently not supported by FluxEngine, but there is
basic support for the MicroDisk operating system's file system. This should
allow files to be read from MS2000 disk images.

The disks are normal DD 3.5" disks, using a 70-track, single sided variation of
the venerable IBM floppy disk scheme, so allowing 315kB of storage per disk.

If you have access to flux files for MS2000 disks, please [get in
touch](https://github.com/davidgiven/cpm65/issues/new) --- I would like to add
better support for these.
>>>

documentation:
<<<
## References

- [The EMMA-02 emulator](https://www.emma02.hobby-site.com/ms2000.html), which
supports the MS2000 and provides information on it.
>>>

image_reader {
filename: "ms2000.img"
type: IMAGETYPE_IMG
}

image_writer {
filename: "ms2000.img"
type: IMAGETYPE_IMG
}

layout {
tracks: 70
sides: 1
tpi: 135
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 9
}
}
}

filesystem {
type: MICRODOS
}


0 comments on commit ed315ea

Please sign in to comment.