Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 32 additions & 16 deletions Source/Teensy/BlockDevice/TRBlockImage.h
Original file line number Diff line number Diff line change
@@ -1,49 +1,65 @@
// SPDX-License-Identifier: MIT
// A CBM disk image (D64, D71, D81) as the block device's backend: the track and sector the driver
// sends become an offset in the image file.
// sends become an offset in the image file, by the shared engine's geometry (docs/Architecture/Disk-Images.md).
#pragma once
#include "../GEOS/GeosDiskPatch.h"
#include "../DiskImage/DiskImage.h"
#include "TRBlock.h"

#define TRBLOCK_IMAGE_WRITABLE 0x01

// Set Writable, Context and the callbacks, then TRBlockImageOpen. The engine keeps a pointer to the
// struct, so it stays where it was opened.
struct TRBlockImage
{
const struct GeosFormat *Format;
bool Writable;
d64_t Disk;
bool Open, Writable;
void *Context;
bool (*Read)(void *Context, uint32_t Offset, uint8_t *Block);
bool (*Write)(void *Context, uint32_t Offset, const uint8_t *Block);
bool (*Flush)(void *Context);
};

static inline bool TRBlockImageHasSector(const struct GeosFormat *Format, uint8_t Track, uint8_t Sector)
FLASHMEM static int TRBlockImageSectorRead(void *Context, uint8_t Track, uint8_t Sector, uint8_t *Block)
{
return Track >= 1 && Track <= Format->Tracks && Sector < GeosSectorsOnTrack(Format, Track);
struct TRBlockImage *Image = (struct TRBlockImage *)Context;
return Image->Read(Image->Context, (uint32_t)d64_sector_offset(&Image->Disk, Track, Sector), Block) ? 0 : -1;
}

FLASHMEM static int TRBlockImageSectorWrite(void *Context, uint8_t Track, uint8_t Sector, const uint8_t *Block)
{
struct TRBlockImage *Image = (struct TRBlockImage *)Context;
return Image->Write(Image->Context, (uint32_t)d64_sector_offset(&Image->Disk, Track, Sector), Block) ? 0 : -1;
}

// An error table (one byte per sector after the data) is left unread: a GEOS driver has no status for a
// sector marked bad, so it gets the sector's bytes, as a drive that read it anyway would give them.
FLASHMEM static bool TRBlockImageOpen(struct TRBlockImage *Image, uint64_t Size)
{
Image->Open = Size <= SIZE_MAX && !d64_open_exact(&Image->Disk, TRBlockImageSectorRead, Image, (size_t)Size);
if (Image->Open && Image->Writable) Image->Disk.write = TRBlockImageSectorWrite;
return Image->Open;
}

FLASHMEM static uint8_t TRBlockImageTransfer(void *Context, bool Write, uint8_t Track, uint8_t Sector, uint8_t *Block)
{
struct TRBlockImage *Image = (struct TRBlockImage *)Context;
uint32_t Offset;

if (!Image->Format) return TRBlockNoImage;
if (!TRBlockImageHasSector(Image->Format, Track, Sector)) return TRBlockBadAddress;
Offset = GeosOffset(Image->Format, Track, Sector);
if (!Write) return Image->Read(Image->Context, Offset, Block) ? TRBlockDone : TRBlockReadFailed;
if (!Image->Open) return TRBlockNoImage;
if (d64_sector_offset(&Image->Disk, Track, Sector) < 0) return TRBlockBadAddress;
if (!Write) return d64_block_read(&Image->Disk, Track, Sector, Block) ? TRBlockReadFailed : TRBlockDone;
if (!Image->Writable) return TRBlockReadOnly;
return Image->Write(Image->Context, Offset, Block) ? TRBlockDone : TRBlockWriteFailed;
return d64_block_write(&Image->Disk, Track, Sector, Block) ? TRBlockWriteFailed : TRBlockDone;
}

FLASHMEM static uint8_t TRBlockImageInfo(void *Context, uint8_t *Out)
{
struct TRBlockImage *Image = (struct TRBlockImage *)Context;
uint32_t Blocks;

if (!Image->Format) return TRBlockNoImage;
Blocks = Image->Format->Size / GEOS_SECTOR_SIZE;
if (!Image->Open) return TRBlockNoImage;
Blocks = (uint32_t)d64_sectors(&Image->Disk);

Out[0] = (uint8_t)(Image->Format - GeosFormats + 1);
Out[0] = (uint8_t)(Image->Disk.fmt + 1);
Out[1] = Image->Writable ? TRBLOCK_IMAGE_WRITABLE : 0;
Out[2] = (uint8_t)Blocks;
Out[3] = (uint8_t)(Blocks >> 8);
Expand All @@ -55,7 +71,7 @@ FLASHMEM static uint8_t TRBlockImageFlush(void *Context)
{
struct TRBlockImage *Image = (struct TRBlockImage *)Context;

if (!Image->Format) return TRBlockNoImage;
if (!Image->Open) return TRBlockNoImage;
return Image->Flush(Image->Context) ? TRBlockDone : TRBlockWriteFailed;
}

Expand Down
174 changes: 55 additions & 119 deletions Source/Teensy/D64.ino
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

#include "DiskImage/MenuDisk.h"

enum enDxxFileNameBytes
{
DxxFNB_NameLength = 16,
Expand All @@ -36,25 +38,25 @@ enum enDxxFileTypes
DxxFT_REL = 4,
};

FLASHMEM uint32_t D64Offset(uint8_t Track, uint8_t Sector)
// An image file open for the shared disk image engine (DiskImage/MenuDisk.h)
struct DxxImage
{
File *Image;
d64_t Disk;
};

FLASHMEM static int DxxSectorRead(void *Context, uint8_t Track, uint8_t Sector, uint8_t *Block)
{
if (Track<18) return ( ((Track- 1)*21+Sector)*256);
if (Track<25) return (0x16500+((Track-18)*19+Sector)*256);
if (Track<31) return (0x1ea00+((Track-25)*18+Sector)*256);
return (0x25600+((Track-31)*17+Sector)*256);
struct DxxImage *Dxx = (struct DxxImage *)Context;
return Dxx->Image->seek(d64_sector_offset(&Dxx->Disk, Track, Sector)) && Dxx->Image->read(Block, D64_SECTOR) == D64_SECTOR ? 0 : -1;
}

FLASHMEM uint32_t DxxOffset(uint8_t DiskType, uint8_t Track, uint8_t Sector)
FLASHMEM static bool DxxOpen(struct DxxImage *Dxx, File *Image, uint8_t DiskType)
{
if (DiskType == rtD64) return D64Offset(Track, Sector);
enum MenuDiskKind Kind = DiskType == rtD71 ? MenuDiskD71 : DiskType == rtD81 ? MenuDiskD81 : MenuDiskD64;

if (DiskType == rtD71)
{
if (Track>35) return (0x2AB00 + D64Offset(Track-35, Sector)); //side 1
else return D64Offset(Track, Sector); //side 0
}

return (((Track- 1)*40+Sector)*256); // Defaulte to (DiskType == rtD81)
Dxx->Image = Image;
return MenuDiskOpen(&Dxx->Disk, Kind, Image->size(), DxxSectorRead, Dxx);
}

FLASHMEM uint8_t ItemTypeFromDxxFileType(uint8_t FileType)
Expand All @@ -74,6 +76,30 @@ FLASHMEM uint8_t ItemTypeFromDxxFileType(uint8_t FileType)
return RetItemType;
}

FLASHMEM static bool AddDxxEntry(void *Context, const uint8_t *Name, uint8_t FileType, uint8_t FileTrack, uint8_t FileSect)
{
if (NumDrvDirMenuItems >= MaxMenuItems) return false;

char *Entry = (char*)malloc(DxxFNB_Bytes); // 16 char max + term + ftrack + fsec + DiskType
for(uint8_t CharNum=0; CharNum<DxxFNB_NameLength; CharNum++)
{ //converting to ascii, then back to petscii for display later. All other file names are ascii...
uint8_t Char = Name[CharNum];
if (Char & 0x80) Char &= 0x7f; //Cap petscii to ascii
else if (Char & 0x40) Char |= 0x20; //lower case petscii to ascii
Entry[CharNum] = Char;
}
Entry[DxxFNB_NameTerm] = 0;
Entry[DxxFNB_Track] = FileTrack; //store start track in case we need to load later
Entry[DxxFNB_Sector] = FileSect;
Entry[DxxFNB_DiskType] = *(uint8_t *)Context;

DriveDirMenu[NumDrvDirMenuItems].Name = Entry;
DriveDirMenu[NumDrvDirMenuItems].ItemType = ItemTypeFromDxxFileType(FileType);
Printf_dbg(" Name:%s\n", Entry);
NumDrvDirMenuItems++;
return true;
}

FLASHMEM void LoadDxxDirectory(FS *sourceFS, uint8_t DiskType)
{
//Interpreted from: https://ist.uwaterloo.ca/~schepers/formats/D64.TXT D71.TXT D81.TXT DISK.TXT
Expand All @@ -86,13 +112,11 @@ FLASHMEM void LoadDxxDirectory(FS *sourceFS, uint8_t DiskType)
AddDirEntry(UpDirString);

File myFile = sourceFS->open(DriveDirPath, FILE_READ); //path includes d64/71/81 file
struct DxxImage Dxx;
if (!myFile) return;

uint32_t FileSize = myFile.size();
Printf_dbg("Loading dir from %s (%lu bytes) type: %d\n", DriveDirPath, FileSize, DiskType);
if((DiskType == rtD64 && (FileSize < 171520 || FileSize > 197376)) || // Realm.d64 = 171520
(DiskType == rtD71 && (FileSize < 349696 || FileSize > 351062)) ||
(DiskType == rtD81 && (FileSize < 819200 || FileSize > 822400)) )
Printf_dbg("Loading dir from %s (%lu bytes) type: %d\n", DriveDirPath, (uint32_t)myFile.size(), DiskType);
if (!DxxOpen(&Dxx, &myFile, DiskType))
{
//Create dummy file/name to display err message
DriveDirMenu[1].ItemType = rtUnknown;
Expand All @@ -101,76 +125,7 @@ FLASHMEM void LoadDxxDirectory(FS *sourceFS, uint8_t DiskType)
return;
}

uint8_t Track = 18;
uint8_t Sector = 1;
uint8_t SecOffset = 0;
uint8_t FileName[16];
uint8_t NextTrack, NextSect, FileType, FileTrack, FileSect;

if(DiskType == rtD81)
{
Track = 40;
Sector = 3;
}

//An image's directory chain can link back to itself; a real one is at most 37 sectors (D81)
const uint8_t MaxDxxDirSectors = 64;
uint8_t DirSectorsRead = 0;
while(Track != 0 && DirSectorsRead++ < MaxDxxDirSectors && NumDrvDirMenuItems < MaxMenuItems)
{
uint32_t CurTSOffset = DxxOffset(DiskType, Track, Sector);
Printf_dbg("Track:%d Sector:%d = DxxOffset:$%x\n", Track, Sector, CurTSOffset);

do
{
myFile.seek(CurTSOffset+SecOffset);

NextTrack = myFile.read(); //0x00 Only first is valid, rest in sect should be 00
NextSect = myFile.read(); //0x01 Only first is valid, rest in sect should be 00
FileType = myFile.read(); //0x02
FileTrack = myFile.read(); //0x03
FileSect = myFile.read(); //0x04
myFile.read(FileName, DxxFNB_NameLength); //0x05-0x14, padded with $a0 (space)

Printf_dbg(" SecOffset $%02x: NTrack:%02d NSect:%02d Type:$%02x FTrack:%02d FSect:%02d\n",
SecOffset, NextTrack, NextSect, FileType, FileTrack, FileSect);

if(SecOffset == 0)
{
Track = NextTrack; //will be 0 if reading last sector
Sector = NextSect;
}

if (FileName[0] && NumDrvDirMenuItems >= MaxMenuItems) SecOffset = 0; //array full: stop
else if (FileName[0]) //check for end of dir, no entry
{ //valid dir entry
DriveDirMenu[NumDrvDirMenuItems].Name = (char*)malloc(DxxFNB_Bytes); // 16 char max + term + ftrack + fsec + DiskType

for(uint8_t CharNum=0; CharNum<DxxFNB_NameLength; CharNum++)
{ //converting to ascii, then back to petscii for display later. All other file names are ascii...
if (FileName[CharNum] & 0x80) FileName[CharNum] &= 0x7f; //Cap petscii to ascii
else if (FileName[CharNum] & 0x40) FileName[CharNum] |= 0x20; //lower case petscii to ascii
}

memcpy(DriveDirMenu[NumDrvDirMenuItems].Name, FileName, DxxFNB_NameLength);
DriveDirMenu[NumDrvDirMenuItems].Name[DxxFNB_NameTerm] = 0; //terminate it
DriveDirMenu[NumDrvDirMenuItems].Name[DxxFNB_Track] = FileTrack; //store start track in case we need to load later
DriveDirMenu[NumDrvDirMenuItems].Name[DxxFNB_Sector] = FileSect; //store start sector
DriveDirMenu[NumDrvDirMenuItems].Name[DxxFNB_DiskType] = DiskType; //store disk type
DriveDirMenu[NumDrvDirMenuItems].ItemType = ItemTypeFromDxxFileType(FileType);

Printf_dbg(" Name:%s\n", DriveDirMenu[NumDrvDirMenuItems].Name);
SecOffset +=0x20; //uint8_t rolls over to 0x00 at end of 256 byte sector
NumDrvDirMenuItems++;
}
else
{ // end of dir, Track should also be 0 at this point
SecOffset = 0;
}

} while (SecOffset != 0);

}
MenuDiskList(&Dxx.Disk, AddDxxEntry, &DiskType);
Printf_dbg("Loaded %d items in %lumS\n", NumDrvDirMenuItems, (millis()-beginWait));
myFile.close();

Expand All @@ -189,39 +144,20 @@ FLASHMEM bool LoadDxxFile(StructMenuItem* MyMenuItem, FS *sourceFS)
return false;
}

uint8_t Track = MyMenuItem->Name[DxxFNB_Track];
uint8_t Sector = MyMenuItem->Name[DxxFNB_Sector];
uint8_t DiskType = MyMenuItem->Name[DxxFNB_DiskType];
MyMenuItem->Size = 0;
struct DxxImage Dxx;
int32_t Length = DxxOpen(&Dxx, &myFile, MyMenuItem->Name[DxxFNB_DiskType])
? MenuDiskReadFile(&Dxx.Disk, MyMenuItem->Name[DxxFNB_Track], MyMenuItem->Name[DxxFNB_Sector], RAM_Image, RAM_ImageSize)
: MENUDISK_BROKEN;

// Load the whole thing into the RAM1 buffer
while(Track != 0)
if (Length < 0)
{
uint32_t CurTSOffset = DxxOffset(DiskType, Track, Sector);
Printf_dbg("Track:%d Sector:%d = D64Offset:$%06x\n", Track, Sector, CurTSOffset);

if (MyMenuItem->Size + 254 > RAM_ImageSize)
{
SendMsgPrintfln("File too large");
myFile.close();
return false;
}
myFile.seek(CurTSOffset);
Track = myFile.read(); //0x00
Sector = myFile.read(); //0x01

uint16_t BytesToCopy = 254; //normal sector: full 254 data bytes used
if (Track == 0)
{ //last sector: Sector byte instead gives the offset of the last used byte
//(counting the 2 link bytes), so valid data bytes = Sector-1
BytesToCopy = (Sector >= 1) ? (Sector - 1) : 0;
if (BytesToCopy > 254) BytesToCopy = 254; //safety clamp for corrupt images
}

myFile.read(RAM_Image+MyMenuItem->Size, BytesToCopy); //read in only the valid bytes
MyMenuItem->Size += BytesToCopy;
if (Length == MENUDISK_TOO_BIG) SendMsgPrintfln(PSTR("File too large"));
else SendMsgPrintfln(PSTR("File could not be read"));
myFile.close();
return false;
}

MyMenuItem->Size = Length;

SendMsgPrintfln("Done");
myFile.close();
Printf_dbg("Size:$%04x\n", MyMenuItem->Size);
Expand Down
19 changes: 19 additions & 0 deletions Source/Teensy/DiskImage/DiskImage.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: MIT
// The D64/D71/D81 engine that the GEOS support, the TR block device and VDrive share (docs/Architecture/Disk-Images.md).
// The firmware builds every .ino and the headers they include as one translation unit, and so does each host
// harness, so the code comes in once, here, behind the guard.
#pragma once
#ifdef ARDUINO
#include <avr/pgmspace.h>
#ifndef VD_COLD
#define VD_COLD FLASHMEM
#endif
#ifndef D64_FLASH
#define D64_FLASH PROGMEM
#endif
#else
#define FLASHMEM
#endif

#include "d64.h"
#include "d64.c"
Loading
Loading