Merge pull request #668 from davidgiven/ms2000

Add basic support for the MS2000 Microdos file system.
This commit is contained in:
David Given
2023-08-19 23:54:27 +02:00
committed by GitHub
10 changed files with 311 additions and 5 deletions

View File

@@ -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 | 🦄 | 🦄 | |

View File

@@ -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 \

View File

@@ -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;

View File

@@ -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>

223
lib/vfs/microdos.cc Normal file
View File

@@ -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);
}

View File

@@ -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);

View File

@@ -264,6 +264,8 @@ public:
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(

View File

@@ -88,6 +88,9 @@ message LifProto
[ default = 256, (help) = "LIF filesystem block size" ];
}
message MicrodosProto {}
// NEXT_TAG: 16
message ZDosProto
{
message Location
@@ -110,7 +113,7 @@ message RolandFsProto
[ (help) = "number of directory entries", default = 79 ];
}
// NEXT_TAG: 17
// NEXT_TAG: 18
message FilesystemProto
{
enum FilesystemType
@@ -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
@@ -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" ];

View File

@@ -21,6 +21,7 @@ FORMATS = \
icl30 \
mac \
micropolis \
ms2000 \
mx \
n88basic \
northstar \

58
src/formats/ms2000.textpb Normal file
View File

@@ -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
}