From e31e54732207bf5ef0971e5a8a461694e47225b5 Mon Sep 17 00:00:00 2001 From: dg Date: Sat, 6 May 2023 00:20:48 +0000 Subject: [PATCH 1/4] Add a routine to count the number of bits in a word. --- lib/utils.cc | 11 +++++++++++ lib/utils.h | 1 + 2 files changed, 12 insertions(+) diff --git a/lib/utils.cc b/lib/utils.cc index f8d14306..d7a27776 100644 --- a/lib/utils.cc +++ b/lib/utils.cc @@ -198,3 +198,14 @@ 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; +} + diff --git a/lib/utils.h b/lib/utils.h index ea183d8a..7335b136 100644 --- a/lib/utils.h +++ b/lib/utils.h @@ -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); /* If set, any running job will terminate as soon as possible (with an error). */ From 10605b3908f126d6c7f7374ff9480e93d0de2145 Mon Sep 17 00:00:00 2001 From: dg Date: Sat, 6 May 2023 00:21:10 +0000 Subject: [PATCH 2/4] Add a read-only MS2000 file system, and a format (with no encoder or decoder). --- lib/build.mk | 1 + lib/vfs/vfs.cc | 3 +++ lib/vfs/vfs.h | 2 ++ lib/vfs/vfs.proto | 6 +++++- src/formats/build.mk | 1 + src/formats/ms2000.textpb | 24 ++++++++++++++++++++++++ 6 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 src/formats/ms2000.textpb diff --git a/lib/build.mk b/lib/build.mk index 8fc6b76e..a94fb337 100644 --- a/lib/build.mk +++ b/lib/build.mk @@ -79,6 +79,7 @@ LIBFLUXENGINE_SRCS = \ lib/vfs/fatfs.cc \ lib/vfs/lif.cc \ lib/vfs/machfs.cc \ + lib/vfs/microdos.cc \ lib/vfs/prodos.cc \ lib/vfs/smaky6fs.cc \ lib/vfs/philefs.cc \ diff --git a/lib/vfs/vfs.cc b/lib/vfs/vfs.cc index 6eefe4ea..574a193a 100644 --- a/lib/vfs/vfs.cc +++ b/lib/vfs/vfs.cc @@ -215,6 +215,9 @@ std::unique_ptr Filesystem::createFilesystem( case FilesystemProto::LIF: return Filesystem::createLifFilesystem(config, image); + case FilesystemProto::MICRODOS: + return Filesystem::createMicrodosFilesystem(config, image); + default: Error() << "no filesystem configured"; return std::unique_ptr(); diff --git a/lib/vfs/vfs.h b/lib/vfs/vfs.h index 41e6ae2f..5a416fd2 100644 --- a/lib/vfs/vfs.h +++ b/lib/vfs/vfs.h @@ -256,6 +256,8 @@ public: const FilesystemProto& config, std::shared_ptr image); static std::unique_ptr createLifFilesystem( const FilesystemProto& config, std::shared_ptr image); + static std::unique_ptr createMicrodosFilesystem( + const FilesystemProto& config, std::shared_ptr image); static std::unique_ptr createFilesystem( const FilesystemProto& config, std::shared_ptr image); diff --git a/lib/vfs/vfs.proto b/lib/vfs/vfs.proto index 0c7630df..08189930 100644 --- a/lib/vfs/vfs.proto +++ b/lib/vfs/vfs.proto @@ -81,7 +81,9 @@ message LifProto [ default = 256, (help) = "LIF filesystem block size" ]; } -// NEXT_TAG: 15 +message MicrodosProto {} + +// NEXT_TAG: 16 message FilesystemProto { enum FilesystemType @@ -99,6 +101,7 @@ message FilesystemProto APPLEDOS = 10; PHILE = 11; LIF = 12; + MICRODOS = 13; } optional FilesystemType type = 10 @@ -116,6 +119,7 @@ message FilesystemProto optional Smaky6FsProto smaky6 = 11; optional PhileProto phile = 13; optional LifProto lif = 14; + optional MicrodosProto microdos = 15; optional SectorListProto sector_order = 9 [ (help) = "specify the filesystem order of sectors" ]; diff --git a/src/formats/build.mk b/src/formats/build.mk index 9fe82e4e..215c09bf 100644 --- a/src/formats/build.mk +++ b/src/formats/build.mk @@ -23,6 +23,7 @@ FORMATS = \ icl30 \ mac \ micropolis \ + ms2000 \ mx \ n88basic \ northstar \ diff --git a/src/formats/ms2000.textpb b/src/formats/ms2000.textpb new file mode 100644 index 00000000..dcd7b088 --- /dev/null +++ b/src/formats/ms2000.textpb @@ -0,0 +1,24 @@ +comment: 'MS2000 Microdisk Development System' + +image_writer { + filename: "ms2000.img" + type: IMG +} + +layout { + tracks: 70 + sides: 1 + layoutdata { + sector_size: 512 + physical { + start_sector: 1 + count: 9 + } + } +} + +filesystem { + type: MICRODOS +} + + From ba1f8b8ed84a143f54fa9245edb781233ff80a17 Mon Sep 17 00:00:00 2001 From: dg Date: Sat, 6 May 2023 00:28:13 +0000 Subject: [PATCH 3/4] Add missing file. Reformat. --- lib/flags.cc | 20 ++-- lib/utils.cc | 19 ++-- lib/vfs/microdos.cc | 223 ++++++++++++++++++++++++++++++++++++++++++++ lib/vfs/vfs.proto | 4 +- 4 files changed, 244 insertions(+), 22 deletions(-) create mode 100644 lib/vfs/microdos.cc diff --git a/lib/flags.cc b/lib/flags.cc index f9ece11f..968eb4ca 100644 --- a/lib/flags.cc +++ b/lib/flags.cc @@ -240,17 +240,17 @@ std::vector FlagGroup::parseFlagsWithFilenames(int argc, FlagGroup::applyOption(*defaultOption); } - /* Next, any standalone options. */ + /* Next, any standalone options. */ + + for (auto& option : config.option()) + { + if (options.find(option.name()) != options.end()) + { + FlagGroup::applyOption(option); + options.erase(option.name()); + } + } - for (auto& option : config.option()) - { - if (options.find(option.name()) != options.end()) - { - FlagGroup::applyOption(option); - options.erase(option.name()); - } - } - if (!options.empty()) Error() << fmt::format( "--{} is not a known flag or format option; try --help", diff --git a/lib/utils.cc b/lib/utils.cc index d7a27776..4d7b4f41 100644 --- a/lib/utils.cc +++ b/lib/utils.cc @@ -194,18 +194,17 @@ std::string tohex(const std::string& s) bool doesFileExist(const std::string& filename) { - std::ifstream f(filename); - return f.good(); + std::ifstream f(filename); + return f.good(); } int countSetBits(uint32_t word) { - int b = 0; - while (word) - { - b += word & 1; - word >>= 1; - } - return b; + int b = 0; + while (word) + { + b += word & 1; + word >>= 1; + } + return b; } - diff --git a/lib/vfs/microdos.cc b/lib/vfs/microdos.cc new file mode 100644 index 00000000..94b75266 --- /dev/null +++ b/lib/vfs/microdos.cc @@ -0,0 +1,223 @@ +#include "lib/globals.h" +#include "lib/vfs/vfs.h" +#include "lib/config.pb.h" +#include "lib/utils.h" +#include + +/* 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 sdws; + unsigned sectors; + unsigned lastSectorBytes; + unsigned loadSectors; + unsigned loadAddress; + unsigned startAddress; + }; + +public: + MicrodosFilesystem( + const MicrodosProto& config, std::shared_ptr 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 getMetadata() override + { + mount(); + + std::map 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 getDirent(const Path& path) override + { + mount(); + if (path.size() != 1) + throw BadPathException(); + + return findFile(path.front()); + } + + std::vector> list(const Path& path) override + { + mount(); + if (!path.empty()) + throw FileNotFoundException(); + + std::vector> 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(*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 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> _dirents; +}; + +std::unique_ptr Filesystem::createMicrodosFilesystem( + const FilesystemProto& config, std::shared_ptr sectors) +{ + return std::make_unique(config.microdos(), sectors); +} diff --git a/lib/vfs/vfs.proto b/lib/vfs/vfs.proto index 08189930..539c4ecc 100644 --- a/lib/vfs/vfs.proto +++ b/lib/vfs/vfs.proto @@ -101,7 +101,7 @@ message FilesystemProto APPLEDOS = 10; PHILE = 11; LIF = 12; - MICRODOS = 13; + MICRODOS = 13; } optional FilesystemType type = 10 @@ -119,7 +119,7 @@ message FilesystemProto optional Smaky6FsProto smaky6 = 11; optional PhileProto phile = 13; optional LifProto lif = 14; - optional MicrodosProto microdos = 15; + optional MicrodosProto microdos = 15; optional SectorListProto sector_order = 9 [ (help) = "specify the filesystem order of sectors" ]; From 7456fd0c90295e0f694c4ae6c7c16095e05f05a3 Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 19 Aug 2023 23:29:55 +0200 Subject: [PATCH 4/4] Make the MS2000 stuff work again. Write documentation. --- README.md | 1 + src/formats/ms2000.textpb | 36 +++++++++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7870220b..dd2cc632 100644 --- a/README.md +++ b/README.md @@ -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 | 🦄 | 🦄 | | diff --git a/src/formats/ms2000.textpb b/src/formats/ms2000.textpb index dcd7b088..0e85b60d 100644 --- a/src/formats/ms2000.textpb +++ b/src/formats/ms2000.textpb @@ -1,13 +1,47 @@ 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: IMG + type: IMAGETYPE_IMG } layout { tracks: 70 sides: 1 + tpi: 135 layoutdata { sector_size: 512 physical {