diff --git a/cpp/disk_image/disk_image_handle.cpp b/cpp/disk_image/disk_image_handle.cpp new file mode 100644 index 00000000..078ded3c --- /dev/null +++ b/cpp/disk_image/disk_image_handle.cpp @@ -0,0 +1,59 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2022 akuker +// +// Base class for interfacing with disk images. +// +// [ DiskImageHandle ] +// +//--------------------------------------------------------------------------- + +#include "disk_image/disk_image_handle.h" + +DiskImageHandle::DiskImageHandle(const Filepath &path, int size, uint32_t blocks, off_t imgoff) +{ + + serial = 0; + sec_path = path; + sec_size = size; + sec_blocks = blocks; + imgoffset = imgoff; +} +DiskImageHandle::~DiskImageHandle() +{ +} + +off_t DiskImageHandle::GetSectorOffset(int block) +{ + + int sector_num = block & 0xff; + return (off_t)sector_num << sec_size; +} + +off_t DiskImageHandle::GetTrackOffset(int block) +{ + + // Assuming that all tracks hold 256 sectors + int track_num = block >> 8; + + // Calculate offset (previous tracks are considered to hold 256 sectors) + off_t offset = ((off_t)track_num << 8); + if (cd_raw) + { + ASSERT(sec_size == 11); + offset *= 0x930; + offset += 0x10; + } + else + { + offset <<= sec_size; + } + + // Add offset to real image + offset += imgoffset; + + return offset; +} diff --git a/cpp/disk_image/disk_image_handle.h b/cpp/disk_image/disk_image_handle.h new file mode 100644 index 00000000..84ec5c2c --- /dev/null +++ b/cpp/disk_image/disk_image_handle.h @@ -0,0 +1,43 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2022 akuker +// +// Base class for interfacing with disk images. +// +// [ DiskImageHandle ] +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "filepath.h" + +class DiskImageHandle +{ +public: + DiskImageHandle(const Filepath &path, int size, uint32_t blocks, off_t imgoff = 0); + virtual ~DiskImageHandle(); + + void SetRawMode(bool raw) { cd_raw = raw; }; // CD-ROM raw mode setting + + // Access + virtual bool Save() = 0; // Save and release all + virtual bool ReadSector(BYTE *buf, int block) = 0; // Sector Read + virtual bool WriteSector(const BYTE *buf, int block) = 0; // Sector Write + virtual bool GetCache(int index, int &track, DWORD &serial) const = 0; // Get cache information + +protected: + bool cd_raw = false; + DWORD serial; // Last serial number + Filepath sec_path; // Path + + int sec_size; // Sector Size (8=256, 9=512, 10=1024, 11=2048, 12=4096) + int sec_blocks; // Blocks per sector + off_t imgoffset; // Offset to actual data + + off_t GetTrackOffset(int block); + off_t GetSectorOffset(int block); +}; diff --git a/cpp/disk_image/disk_image_handle_factory.cpp b/cpp/disk_image/disk_image_handle_factory.cpp new file mode 100644 index 00000000..e88e5ba7 --- /dev/null +++ b/cpp/disk_image/disk_image_handle_factory.cpp @@ -0,0 +1,48 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2022 akuker +// +// Factory class for creating DiskImageHandles +// +// [ DiskImageHandleFactory ] +// +//--------------------------------------------------------------------------- + +#include "disk_image/disk_image_handle_factory.h" +#include "log.h" +#include "disk_image/disk_track_cache.h" +#include "disk_image/mmap_file_handle.h" +#include "disk_image/posix_file_handle.h" + +DiskImageHandleType DiskImageHandleFactory::current_access_type = DiskImageHandleType::ePosixFile; + +DiskImageHandle *DiskImageHandleFactory::CreateDiskImageHandle(const Filepath &path, int size, uint32_t blocks, off_t imgoff) +{ + + DiskImageHandle *result = NULL; + + if (current_access_type == DiskImageHandleType::eMmapFile) + { + LOGINFO("%s Creating MmapFileAccess %s", __PRETTY_FUNCTION__, path.GetPath()) + result = new MmapFileHandle(path, size, blocks, imgoff); + } + else if (current_access_type == DiskImageHandleType::eRamCache) + { + LOGINFO("%s Creating DiskCache %s", __PRETTY_FUNCTION__, path.GetPath()) + result = new DiskCache(path, size, blocks, imgoff); + } + else if (current_access_type == DiskImageHandleType::ePosixFile) + { + LOGINFO("%s Creating PosixFileHandle %s", __PRETTY_FUNCTION__, path.GetPath()) + result = new PosixFileHandle(path, size, blocks, imgoff); + } + + if (result == NULL) + { + LOGWARN("%s Unable to create the File Access", __PRETTY_FUNCTION__); + } + return result; +} diff --git a/cpp/disk_image/disk_image_handle_factory.h b/cpp/disk_image/disk_image_handle_factory.h new file mode 100644 index 00000000..6a79a341 --- /dev/null +++ b/cpp/disk_image/disk_image_handle_factory.h @@ -0,0 +1,35 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2022 akuker +// +// Factory class for creating DiskImageHandles +// +// [ DiskImageHandleFactory ] +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "disk_image/disk_image_handle.h" + +enum DiskImageHandleType +{ + eRamCache, + eMmapFile, + ePosixFile, +}; + +class DiskImageHandleFactory +{ +public: + static void SetFileAccessMethod(DiskImageHandleType method) { current_access_type = method; }; + static DiskImageHandleType GetFileAccessMethod() { return current_access_type; }; + + static DiskImageHandle *CreateDiskImageHandle(const Filepath &path, int size, uint32_t blocks, off_t imgoff = 0); + +private: + static DiskImageHandleType current_access_type; +}; \ No newline at end of file diff --git a/cpp/disk_image/disk_track_cache.cpp b/cpp/disk_image/disk_track_cache.cpp new file mode 100644 index 00000000..85b75b0f --- /dev/null +++ b/cpp/disk_image/disk_track_cache.cpp @@ -0,0 +1,575 @@ +//--------------------------------------------------------------------------- +// +// X68000 EMULATOR "XM6" +// +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// +// XM6i +// Copyright (C) 2010-2015 isaki@NetBSD.org +// Copyright (C) 2010 Y.Sugahara +// +// Imported sava's Anex86/T98Next image and MO format support patch. +// Comments translated to english by akuker. +// +// [ DiskTrack and DiskCache ] +// +//--------------------------------------------------------------------------- + +#include "os.h" +#include "log.h" +#include "fileio.h" +#include "disk_track_cache.h" + +//=========================================================================== +// +// Disk Track +// +//=========================================================================== + +DiskTrack::DiskTrack() +{ + // Initialization of internal information + dt.track = 0; + dt.size = 0; + dt.sectors = 0; + dt.raw = FALSE; + dt.init = FALSE; + dt.changed = FALSE; + dt.length = 0; + dt.buffer = NULL; + dt.maplen = 0; + dt.changemap = NULL; + dt.imgoffset = 0; +} + +DiskTrack::~DiskTrack() +{ + // Release memory, but do not save automatically + if (dt.buffer) { + free(dt.buffer); + dt.buffer = NULL; + } + if (dt.changemap) { + free(dt.changemap); + dt.changemap = NULL; + } +} + +void DiskTrack::Init(int track, int size, int sectors, BOOL raw, off_t imgoff) +{ + ASSERT(track >= 0); + ASSERT((sectors > 0) && (sectors <= 0x100)); + ASSERT(imgoff >= 0); + + // Set Parameters + dt.track = track; + dt.size = size; + dt.sectors = sectors; + dt.raw = raw; + + // Not initialized (needs to be loaded) + dt.init = FALSE; + + // Not Changed + dt.changed = FALSE; + + // Offset to actual data + dt.imgoffset = imgoff; +} + +bool DiskTrack::Load(const Filepath& path) +{ + // Not needed if already loaded + if (dt.init) { + ASSERT(dt.buffer); + ASSERT(dt.changemap); + return true; + } + + // Calculate offset (previous tracks are considered to hold 256 sectors) + off_t offset = ((off_t)dt.track << 8); + if (dt.raw) { + ASSERT(dt.size == 11); + offset *= 0x930; + offset += 0x10; + } else { + offset <<= dt.size; + } + + // Add offset to real image + offset += dt.imgoffset; + + // Calculate length (data size of this track) + int length = dt.sectors << dt.size; + + // Allocate buffer memory + ASSERT((dt.sectors > 0) && (dt.sectors <= 0x100)); + + if (dt.buffer == NULL) { + if (posix_memalign((void **)&dt.buffer, 512, ((length + 511) / 512) * 512)) { + LOGWARN("%s posix_memalign failed", __PRETTY_FUNCTION__); + } + dt.length = length; + } + + if (!dt.buffer) { + return false; + } + + // Reallocate if the buffer length is different + if (dt.length != (DWORD)length) { + free(dt.buffer); + if (posix_memalign((void **)&dt.buffer, 512, ((length + 511) / 512) * 512)) { + LOGWARN("%s posix_memalign failed", __PRETTY_FUNCTION__); + } + dt.length = length; + } + + // Reserve change map memory + if (dt.changemap == NULL) { + dt.changemap = (BOOL *)malloc(dt.sectors * sizeof(BOOL)); + dt.maplen = dt.sectors; + } + + if (!dt.changemap) { + return false; + } + + // Reallocate if the buffer length is different + if (dt.maplen != (DWORD)dt.sectors) { + free(dt.changemap); + dt.changemap = (BOOL *)malloc(dt.sectors * sizeof(BOOL)); + dt.maplen = dt.sectors; + } + + // Clear changemap + memset(dt.changemap, 0x00, dt.sectors * sizeof(BOOL)); + + // Read from File + Fileio fio; + if (!fio.OpenDIO(path, Fileio::ReadOnly)) { + return false; + } + if (dt.raw) { + // Split Reading + for (int i = 0; i < dt.sectors; i++) { + // Seek + if (!fio.Seek(offset)) { + fio.Close(); + return false; + } + + // Read + if (!fio.Read(&dt.buffer[i << dt.size], 1 << dt.size)) { + fio.Close(); + return false; + } + + // Next offset + offset += 0x930; + } + } else { + // Continuous reading + if (!fio.Seek(offset)) { + fio.Close(); + return false; + } + if (!fio.Read(dt.buffer, length)) { + fio.Close(); + return false; + } + } + fio.Close(); + + // Set a flag and end normally + dt.init = TRUE; + dt.changed = FALSE; + return true; +} + +bool DiskTrack::Save(const Filepath& path) +{ + // Not needed if not initialized + if (!dt.init) { + return true; + } + + // Not needed unless changed + if (!dt.changed) { + return true; + } + + // Need to write + ASSERT(dt.buffer); + ASSERT(dt.changemap); + ASSERT((dt.sectors > 0) && (dt.sectors <= 0x100)); + + // Writing in RAW mode is not allowed + ASSERT(!dt.raw); + + // Calculate offset (previous tracks are considered to hold 256 sectors) + off_t offset = ((off_t)dt.track << 8); + offset <<= dt.size; + + // Add offset to real image + offset += dt.imgoffset; + + // Calculate length per sector + int length = 1 << dt.size; + + // Open file + Fileio fio; + if (!fio.Open(path, Fileio::ReadWrite)) { + return false; + } + + // Partial write loop + int total; + for (int i = 0; i < dt.sectors;) { + // If changed + if (dt.changemap[i]) { + // Initialize write size + total = 0; + + // Seek + if (!fio.Seek(offset + ((off_t)i << dt.size))) { + fio.Close(); + return false; + } + + // Consectutive sector length + int j; + for (j = i; j < dt.sectors; j++) { + // end when interrupted + if (!dt.changemap[j]) { + break; + } + + // Add one sector + total += length; + } + + // Write + if (!fio.Write(&dt.buffer[i << dt.size], total)) { + fio.Close(); + return false; + } + + // To unmodified sector + i = j; + } else { + // Next Sector + i++; + } + } + + // Close + fio.Close(); + + // Drop the change flag and exit + memset(dt.changemap, 0x00, dt.sectors * sizeof(BOOL)); + dt.changed = FALSE; + return true; +} + +bool DiskTrack::ReadSector(BYTE *buf, int sec) const +{ + ASSERT(buf); + ASSERT((sec >= 0) & (sec < 0x100)); + + LOGTRACE("%s reading sector: %d", __PRETTY_FUNCTION__,sec); + // Error if not initialized + if (!dt.init) { + return false; + } + + // // Error if the number of sectors exceeds the valid number + if (sec >= dt.sectors) { + return false; + } + + // Copy + ASSERT(dt.buffer); + ASSERT((dt.sectors > 0) && (dt.sectors <= 0x100)); + memcpy(buf, &dt.buffer[(off_t)sec << dt.size], (off_t)1 << dt.size); + + // Success + return true; +} + +bool DiskTrack::WriteSector(const BYTE *buf, int sec) +{ + ASSERT(buf); + ASSERT((sec >= 0) & (sec < 0x100)); + ASSERT(!dt.raw); + + // Error if not initialized + if (!dt.init) { + return false; + } + + // // Error if the number of sectors exceeds the valid number + if (sec >= dt.sectors) { + return false; + } + + // Calculate offset and length + int offset = sec << dt.size; + int length = 1 << dt.size; + + // Compare + ASSERT(dt.buffer); + ASSERT((dt.sectors > 0) && (dt.sectors <= 0x100)); + if (memcmp(buf, &dt.buffer[offset], length) == 0) { + // Exit normally since it's attempting to write the same thing + return true; + } + + // Copy, change + memcpy(&dt.buffer[offset], buf, length); + dt.changemap[sec] = TRUE; + dt.changed = TRUE; + + // Success + return true; +} + +//=========================================================================== +// +// Disk Cache +// +//=========================================================================== + +DiskCache::DiskCache(const Filepath& path, int size, uint32_t blocks, off_t imgoff) : DiskImageHandle(path, size, blocks, imgoff) +{ + ASSERT(blocks > 0); + ASSERT(imgoff >= 0); + + // Cache work + for (int i = 0; i < CacheMax; i++) { + cache[i].disktrk = NULL; + cache[i].serial = 0; + } + +} + +DiskCache::~DiskCache() +{ + // Clear the track + Clear(); +} + +bool DiskCache::Save() +{ + // Save track + for (int i = 0; i < CacheMax; i++) { + // Is it a valid track? + if (cache[i].disktrk) { + // Save + if (!cache[i].disktrk->Save(sec_path)) { + return false; + } + } + } + + return true; +} + +//--------------------------------------------------------------------------- +// +// Get disk cache information +// +//--------------------------------------------------------------------------- +bool DiskCache::GetCache(int index, int& track, DWORD& aserial) const +{ + ASSERT((index >= 0) && (index < CacheMax)); + + // false if unused + if (!cache[index].disktrk) { + return false; + } + + // Set track and serial + track = cache[index].disktrk->GetTrack(); + aserial = cache[index].serial; + + return true; +} + +void DiskCache::Clear() +{ + // Free the cache + for (int i = 0; i < CacheMax; i++) { + if (cache[i].disktrk) { + delete cache[i].disktrk; + cache[i].disktrk = NULL; + } + } +} + +bool DiskCache::ReadSector(BYTE *buf, int block) +{ + ASSERT(sec_size != 0); + + // Update first + UpdateSerialNumber(); + + // Calculate track (fixed to 256 sectors/track) + int track = block >> 8; + + // Get the track data + DiskTrack *disktrk = Assign(track); + if (!disktrk) { + return false; + } + + // Read the track data to the cache + return disktrk->ReadSector(buf, block & 0xff); +} + +bool DiskCache::WriteSector(const BYTE *buf, int block) +{ + ASSERT(sec_size != 0); + + // Update first + UpdateSerialNumber(); + + // Calculate track (fixed to 256 sectors/track) + int track = block >> 8; + + // Get that track data + DiskTrack *disktrk = Assign(track); + if (!disktrk) { + return false; + } + + // Write the data to the cache + return disktrk->WriteSector(buf, block & 0xff); +} + +//--------------------------------------------------------------------------- +// +// Track Assignment +// +//--------------------------------------------------------------------------- +DiskTrack* DiskCache::Assign(int track) +{ + ASSERT(sec_size != 0); + ASSERT(track >= 0); + + // First, check if it is already assigned + for (int i = 0; i < CacheMax; i++) { + if (cache[i].disktrk) { + if (cache[i].disktrk->GetTrack() == track) { + // Track match + cache[i].serial = serial; + return cache[i].disktrk; + } + } + } + + // Next, check for empty + for (int i = 0; i < CacheMax; i++) { + if (!cache[i].disktrk) { + // Try loading + if (Load(i, track)) { + // Success loading + cache[i].serial = serial; + return cache[i].disktrk; + } + + // Load failed + return NULL; + } + } + + // Finally, find the youngest serial number and delete it + + // Set index 0 as candidate c + DWORD s = cache[0].serial; + int c = 0; + + // Compare candidate with serial and update to smaller one + for (int i = 0; i < CacheMax; i++) { + ASSERT(cache[i].disktrk); + + // Compare and update the existing serial + if (cache[i].serial < s) { + s = cache[i].serial; + c = i; + } + } + + // Save this track + if (!cache[c].disktrk->Save(sec_path)) { + return NULL; + } + + // Delete this track + DiskTrack *disktrk = cache[c].disktrk; + cache[c].disktrk = NULL; + + if (Load(c, track, disktrk)) { + // Successful loading + cache[c].serial = serial; + return cache[c].disktrk; + } + + // Load failed + return NULL; +} + +//--------------------------------------------------------------------------- +// +// Load cache +// +//--------------------------------------------------------------------------- +bool DiskCache::Load(int index, int track, DiskTrack *disktrk) +{ + ASSERT((index >= 0) && (index < CacheMax)); + ASSERT(track >= 0); + ASSERT(!cache[index].disktrk); + + // Get the number of sectors on this track + int sectors = sec_blocks - (track << 8); + ASSERT(sectors > 0); + if (sectors > 0x100) { + sectors = 0x100; + } + + // Create a disk track + if (disktrk == NULL) { + disktrk = new DiskTrack(); + } + + // Initialize disk track + disktrk->Init(track, sec_size, sectors, cd_raw, imgoffset); + + // Try loading + if (!disktrk->Load(sec_path)) { + // Failure + delete disktrk; + return false; + } + + // Allocation successful, work set + cache[index].disktrk = disktrk; + + return true; +} + +void DiskCache::UpdateSerialNumber() +{ + // Update and do nothing except 0 + serial++; + if (serial != 0) { + return; + } + + // Clear serial of all caches (loop in 32bit) + for (int i = 0; i < CacheMax; i++) { + cache[i].serial = 0; + } +} + diff --git a/cpp/disk_image/disk_track_cache.h b/cpp/disk_image/disk_track_cache.h new file mode 100644 index 00000000..dacba06f --- /dev/null +++ b/cpp/disk_image/disk_track_cache.h @@ -0,0 +1,90 @@ +//--------------------------------------------------------------------------- +// +// X68000 EMULATOR "XM6" +// +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// +// XM6i +// Copyright (C) 2010-2015 isaki@NetBSD.org +// +// Imported sava's Anex86/T98Next image and MO format support patch. +// Comments translated to english by akuker. +// +// [ DiskTrack and DiskCache ] +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "filepath.h" +#include "disk_image/disk_image_handle.h" + +// Number of tracks to cache +#define CacheMax 16 + +class DiskTrack +{ +private: + struct { + int track; // Track Number + int size; // Sector Size (8=256, 9=512, 10=1024, 11=2048, 12=4096) + int sectors; // Number of sectors(<0x100) + DWORD length; // Data buffer length + BYTE *buffer; // Data buffer + BOOL init; // Is it initilized? + BOOL changed; // Changed flag + DWORD maplen; // Changed map length + BOOL *changemap; // Changed map + BOOL raw; // RAW mode flag + off_t imgoffset; // Offset to actual data + } dt; + +public: + DiskTrack(); + ~DiskTrack(); + +private: + friend class DiskCache; + + void Init(int track, int size, int sectors, BOOL raw = FALSE, off_t imgoff = 0); + bool Load(const Filepath& path); + bool Save(const Filepath& path); + + // Read / Write + bool ReadSector(BYTE *buf, int sec) const; // Sector Read + bool WriteSector(const BYTE *buf, int sec); // Sector Write + + int GetTrack() const { return dt.track; } // Get track +}; + +class DiskCache : public DiskImageHandle +{ +public: + // Internal data definition + typedef struct { + DiskTrack *disktrk; // Disk Track + DWORD serial; // Serial + } cache_t; + +public: + DiskCache(const Filepath& path, int size, uint32_t blocks, off_t imgoff = 0); + ~DiskCache(); + + // Access + bool Save() override; // Save and release all + bool ReadSector(BYTE *buf, int block) override; // Sector Read + bool WriteSector(const BYTE *buf, int block) override; // Sector Write + bool GetCache(int index, int& track, DWORD& serial) const override; // Get cache information + +private: + // Internal Management + void Clear(); // Clear all tracks + DiskTrack* Assign(int track); // Load track + bool Load(int index, int track, DiskTrack *disktrk = NULL); // Load track + void UpdateSerialNumber(); // Update serial number + + // Internal data + cache_t cache[CacheMax]; // Cache management +}; + diff --git a/cpp/disk_image/mmap_file_handle.cpp b/cpp/disk_image/mmap_file_handle.cpp new file mode 100644 index 00000000..80c21a67 --- /dev/null +++ b/cpp/disk_image/mmap_file_handle.cpp @@ -0,0 +1,106 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2022 akuker +// +// This method of file access will use the mmap() capabilities to 'map' the +// file into memory. +// +// This doesn't actually copy the file into RAM, but instead maps the file +// as virtual memory. Behind the scenes, the file is still being accessed. +// +// The operating system will do some caching of the data to prevent direct +// drive access for each read/write. So, instead of implementing our own +// caching mechanism (like in disk_track_cache.h), we just rely on the +// operating system. +// +// [ MmapFilehandle ] +// +//--------------------------------------------------------------------------- + +#include "mmap_file_handle.h" +#include "log.h" +#include +#include +#include + +//=========================================================================== +// +// Direct file access that will map the file into virtual memory space +// +//=========================================================================== +MmapFileHandle::MmapFileHandle(const Filepath &path, int size, uint32_t blocks, off_t imgoff) : DiskImageHandle(path, size, blocks, imgoff) +{ + ASSERT(blocks > 0); + ASSERT(imgoff >= 0); + + fd = open(path.GetPath(), O_RDWR); + if (fd < 0) + { + LOGWARN("Unable to open file %s. Errno:%d", path.GetPath(), errno) + } + LOGWARN("%s opened %s", __PRETTY_FUNCTION__, path.GetPath()); + struct stat sb; + if (fstat(fd, &sb) < 0) + { + LOGWARN("Unable to run fstat. Errno:%d", errno); + } + printf("Size: %llu\n", (uint64_t)sb.st_size); + + LOGWARN("%s mmap-ed file of size: %llu", __PRETTY_FUNCTION__, (uint64_t)sb.st_size); + + // int x = EACCES; + + memory_block = (const char *)mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + int errno_val = errno; + if (memory_block == MAP_FAILED) + { + LOGWARN("Unabled to memory map file %s", path.GetPath()); + LOGWARN(" Errno:%d", errno_val); + return; + } + initialized = true; +} + +MmapFileHandle::~MmapFileHandle() +{ + munmap((void *)memory_block, sb.st_size); + close(fd); + + // Force the OS to save any cached data to the disk + sync(); +} + +bool MmapFileHandle::ReadSector(BYTE *buf, int block) +{ + ASSERT(sec_size != 0); + ASSERT(buf); + ASSERT(block < sec_blocks); + ASSERT(memory_block); + + int sector_size_bytes = (off_t)1 << sec_size; + + // Calculate offset into the image file + off_t offset = GetTrackOffset(block); + offset += GetSectorOffset(block); + + memcpy(buf, &memory_block[offset], sector_size_bytes); + + return true; +} + +bool MmapFileHandle::WriteSector(const BYTE *buf, int block) +{ + + ASSERT(buf); + ASSERT(block < sec_blocks); + ASSERT(memory_block); + + ASSERT((block * sec_size) <= (sb.st_size + sec_size)); + + memcpy((void *)&memory_block[(block * sec_size)], buf, sec_size); + + return true; +} diff --git a/cpp/disk_image/mmap_file_handle.h b/cpp/disk_image/mmap_file_handle.h new file mode 100644 index 00000000..3a3ecce9 --- /dev/null +++ b/cpp/disk_image/mmap_file_handle.h @@ -0,0 +1,49 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2022 akuker +// +// This method of file access will use the mmap() capabilities to 'map' the +// file into memory. +// +// This doesn't actually copy the file into RAM, but instead maps the file +// as virtual memory. Behind the scenes, the file is still being accessed. +// +// The operating system will do some caching of the data to prevent direct +// drive access for each read/write. So, instead of implementing our own +// caching mechanism (like in disk_track_cache.h), we just rely on the +// operating system. +// +// [ MmapFileHandle ] +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "filepath.h" +#include "disk_image/disk_image_handle.h" + +class MmapFileHandle : public DiskImageHandle +{ + +public: + MmapFileHandle(const Filepath &path, int size, uint32_t blocks, off_t imgoff = 0); + ~MmapFileHandle(); + + void SetRawMode(BOOL raw); // CD-ROM raw mode setting + + // Access + bool Save() { return true; }; // Save and release all + bool ReadSector(BYTE *buf, int block); // Sector Read + bool WriteSector(const BYTE *buf, int block); // Sector Write + bool GetCache(int index, int &track, DWORD &serial) const { return true; }; // Get cache information + +private: + const char *memory_block; + struct stat sb; + int fd; + + bool initialized = false; +}; diff --git a/cpp/disk_image/posix_file_handle.cpp b/cpp/disk_image/posix_file_handle.cpp new file mode 100644 index 00000000..cf32b653 --- /dev/null +++ b/cpp/disk_image/posix_file_handle.cpp @@ -0,0 +1,108 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2022 akuker +// +// [ PosixFileHandle ] +// +//--------------------------------------------------------------------------- + +#include "posix_file_handle.h" +#include "log.h" +#include +#include +#include + +//=========================================================================== +// +// Direct file access that will map the file into virtual memory space +// +//=========================================================================== +PosixFileHandle::PosixFileHandle(const Filepath &path, int size, uint32_t blocks, off_t imgoff) : DiskImageHandle(path, size, blocks, imgoff) +{ + ASSERT(blocks > 0); + ASSERT(imgoff >= 0); + + fd = open(path.GetPath(), O_RDWR); + if (fd < 0) + { + LOGWARN("Unable to open file %s. Errno:%d", path.GetPath(), errno) + return; + } + struct stat sb; + if (fstat(fd, &sb) < 0) + { + LOGWARN("Unable to run fstat. Errno:%d", errno); + return; + } + + LOGWARN("%s opened file of size: %llu", __PRETTY_FUNCTION__, (uint64_t)sb.st_size); + + initialized = true; +} + +PosixFileHandle::~PosixFileHandle() +{ + close(fd); + + // Force the OS to save any cached data to the disk + sync(); +} + +bool PosixFileHandle::ReadSector(BYTE *buf, int block) +{ + if (!initialized) + { + return false; + } + + ASSERT(sec_size != 0); + ASSERT(buf); + ASSERT(block < sec_blocks); + ASSERT(memory_block); + + size_t sector_size_bytes = (size_t)1 << sec_size; + + // Calculate offset into the image file + off_t offset = GetTrackOffset(block); + offset += GetSectorOffset(block); + + lseek(fd, offset, SEEK_SET); + size_t result = read(fd, buf, sector_size_bytes); + if (result != sector_size_bytes) + { + LOGWARN("%s only read %d bytes but wanted %d ", __PRETTY_FUNCTION__, result, sector_size_bytes); + } + + return true; +} + +bool PosixFileHandle::WriteSector(const BYTE *buf, int block) +{ + if (!initialized) + { + return false; + } + + ASSERT(buf); + ASSERT(block < sec_blocks); + ASSERT(memory_block); + + ASSERT((block * sec_size) <= (sb.st_size + sec_size)); + + size_t sector_size_bytes = (size_t)1 << sec_size; + + off_t offset = GetTrackOffset(block); + offset += GetSectorOffset(block); + + lseek(fd, offset, SEEK_SET); + size_t result = write(fd, buf, sector_size_bytes); + if (result != sector_size_bytes) + { + LOGWARN("%s only wrote %d bytes but wanted %d ", __PRETTY_FUNCTION__, result, sector_size_bytes); + } + + return true; +} diff --git a/cpp/disk_image/posix_file_handle.h b/cpp/disk_image/posix_file_handle.h new file mode 100644 index 00000000..57aa886a --- /dev/null +++ b/cpp/disk_image/posix_file_handle.h @@ -0,0 +1,35 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2022 akuker +// +// [ PosixFileHandle ] +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "filepath.h" +#include "disk_image/disk_image_handle.h" + +class PosixFileHandle : public DiskImageHandle +{ + +public: + PosixFileHandle(const Filepath &path, int size, uint32_t blocks, off_t imgoff = 0); + ~PosixFileHandle(); + + void SetRawMode(BOOL raw); // CD-ROM raw mode setting + + // Access + bool Save() { return true; }; // Save and release all + bool ReadSector(BYTE *buf, int block); // Sector Read + bool WriteSector(const BYTE *buf, int block); // Sector Write + bool GetCache(int index, int &track, DWORD &serial) const { return true; }; // Get cache information + +private: + int fd; + bool initialized = false; +}; diff --git a/src_old/raspberrypi/.gitignore b/src_old/raspberrypi/.gitignore new file mode 100644 index 00000000..321cbbb5 --- /dev/null +++ b/src_old/raspberrypi/.gitignore @@ -0,0 +1,22 @@ +*.bak +*.HDA +*.save +*.cbp +*.layout +*.log +*.vcd +*.json +*.html +rascsi +scsimon +rasctl +sasidump +rasdump +scisparse +obj +bin +/rascsi_interface.pb.cpp +/rascsi_interface.pb.h +.project +.cproject +.settings diff --git a/src_old/raspberrypi/Makefile b/src_old/raspberrypi/Makefile new file mode 100644 index 00000000..9cee39e3 --- /dev/null +++ b/src_old/raspberrypi/Makefile @@ -0,0 +1,286 @@ +.DEFAULT_GOAL: all + +## Optional build flags: +## CROSS_COMPILE : Specify which compiler toolchain to use. +## To cross compile set this accordingly, e.g. to: +## arm-linux-gnueabihf- +CROSS_COMPILE = + +CC = $(CROSS_COMPILE)gcc +CXX = $(CROSS_COMPILE)g++ + +## DEBUG=1 : A Debug build includes the debugger symbols +## and disables compiler optimization. Typically, +## this is only used by developers. +DEBUG ?= 0 +ifeq ($(DEBUG), 1) + # Debug CFLAGS + CFLAGS += -O0 -g -Wall -DDEBUG + CXXFLAGS += -O0 -g -Wall -DDEBUG + BUILD_TYPE = Debug +else + # Release CFLAGS + CFLAGS += -O3 -Wall -Werror -DNDEBUG + CXXFLAGS += -O3 -Wall -Werror -DNDEBUG + BUILD_TYPE = Release +endif +ifeq ("$(shell uname -s)","Linux") + # -Wno-psabi might not work on non-Linux platforms + CXXFLAGS += -Wno-psabi +endif + +CFLAGS += -iquote . -D_FILE_OFFSET_BITS=64 -MD -MP +CXXFLAGS += -std=c++17 -iquote . -D_FILE_OFFSET_BITS=64 -MD -MP + +## EXTRA_FLAGS : Can be used to pass special purpose flags +CFLAGS += $(EXTRA_FLAGS) +CXXFLAGS += $(EXTRA_FLAGS) + +# If we're using GCC version 10 or later, we need to add the FMT_HEADER_ONLY definition +GCCVERSION10 := $(shell expr `gcc -dumpversion` \>= 10) + +ifeq "$(GCCVERSION10)" "1" + CFLAGS += -DFMT_HEADER_ONLY + CXXFLAGS += -DFMT_HEADER_ONLY +endif + + + +## CONNECT_TYPE=FULLSPEC : Specify the type of RaSCSI board type +## that you are using. The typical options are +## STANDARD or FULLSPEC. The default is FULLSPEC +## * THIS IS TYPICALLY THE ONLY COMPILE OPTION YOU +## * NEED TO SPECIFY +# If its not specified, build for FULLSPEC configuration +CONNECT_TYPE ?= FULLSPEC + +ifdef CONNECT_TYPE +CFLAGS += -DCONNECT_TYPE_$(CONNECT_TYPE) +CXXFLAGS += -DCONNECT_TYPE_$(CONNECT_TYPE) +endif + +RASCSI = rascsi +RASCTL = rasctl +RASDUMP = rasdump +SASIDUMP = sasidump +SCSIMON = scsimon + +SYSTEMD_CONF = /etc/systemd/system/rascsi.service +RSYSLOG_CONF = /etc/rsyslog.d/rascsi.conf +RSYSLOG_LOG = /var/log/rascsi.log + +USR_LOCAL_BIN = /usr/local/bin +MAN_PAGE_DIR = /usr/local/man/man1 +DOC_DIR = ../../doc +OS_FILES = ./os_integration + +OBJDIR := ./obj/$(shell echo $(CONNECT_TYPE) | tr '[:upper:]' '[:lower:]') +BINDIR := ./bin/$(shell echo $(CONNECT_TYPE) | tr '[:upper:]' '[:lower:]') + +BIN_ALL = \ + $(BINDIR)/$(RASCSI) \ + $(BINDIR)/$(RASCTL) \ + $(BINDIR)/$(SCSIMON) \ + $(BINDIR)/$(RASDUMP) \ + $(BINDIR)/$(SASIDUMP) + +SRC_PROTOC = \ + rascsi_interface.proto + +SRC_PROTOBUF = \ + rascsi_interface.pb.cpp + +SRC_RASCSI = \ + rascsi.cpp \ + scsi.cpp \ + gpiobus.cpp \ + filepath.cpp \ + fileio.cpp\ + rascsi_version.cpp \ + rascsi_image.cpp \ + rascsi_response.cpp \ + rasutil.cpp \ + protobuf_util.cpp \ + localizer.cpp +SRC_RASCSI += $(shell find ./controllers -name '*.cpp') +SRC_RASCSI += $(shell find ./devices -name '*.cpp') +SRC_RASCSI += $(shell find ./disk_image -name '*.cpp') +SRC_RASCSI += $(SRC_PROTOBUF) + +SRC_SCSIMON = \ + scsimon.cpp \ + scsi.cpp \ + gpiobus.cpp \ + rascsi_version.cpp +SRC_SCSIMON += $(shell find ./monitor -name '*.cpp') + +SRC_RASCTL = \ + rasctl.cpp\ + rasctl_commands.cpp \ + rasctl_display.cpp \ + rascsi_version.cpp \ + rasutil.cpp \ + protobuf_util.cpp \ + localizer.cpp +SRC_RASCTL += $(SRC_PROTOBUF) + +SRC_RASDUMP = \ + rasdump.cpp \ + scsi.cpp \ + gpiobus.cpp \ + filepath.cpp \ + fileio.cpp\ + rascsi_version.cpp + +SRC_SASIDUMP = \ + sasidump.cpp \ + scsi.cpp \ + gpiobus.cpp \ + filepath.cpp \ + fileio.cpp\ + rascsi_version.cpp + +vpath %.h ./ ./controllers ./devices ./disk_image ./monitor +vpath %.cpp ./ ./controllers ./devices ./disk_image ./monitor +vpath %.o ./$(OBJDIR) +vpath ./$(BINDIR) + + +OBJ_RASCSI := $(addprefix $(OBJDIR)/,$(notdir $(SRC_RASCSI:%.cpp=%.o))) +OBJ_RASCTL := $(addprefix $(OBJDIR)/,$(notdir $(SRC_RASCTL:%.cpp=%.o))) +OBJ_RASDUMP := $(addprefix $(OBJDIR)/,$(notdir $(SRC_RASDUMP:%.cpp=%.o))) +OBJ_SASIDUMP := $(addprefix $(OBJDIR)/,$(notdir $(SRC_SASIDUMP:%.cpp=%.o))) +OBJ_SCSIMON := $(addprefix $(OBJDIR)/,$(notdir $(SRC_SCSIMON:%.cpp=%.o))) + +GEN_PROTOBUF := $(SRC_PROTOBUF) rascsi_interface.pb.h + + +# The following will include all of the auto-generated dependency files (*.d) +# if they exist. This will trigger a rebuild of a source file if a header changes +ALL_DEPS := $(patsubst %.o,%.d,$(OBJ_RASCSI) $(OBJ_RASCTL) $(OBJ_SCSIMON)) +-include $(ALL_DEPS) + +$(OBJDIR) $(BINDIR): + echo "-- Creating directory $@" + mkdir -p $@ + +$(OBJDIR)/%.o: %.cpp | $(OBJDIR) + $(CXX) $(CXXFLAGS) -c $< -o $@ + +$(SRC_PROTOBUF): $(SRC_PROTOC) + echo "-- Generating protobuf-based source files" + protoc --cpp_out=. $(SRC_PROTOC) + mv rascsi_interface.pb.cc $@ + +## Build Targets: +## all : Rebuild all of the executable files and re-generate +## the text versions of the manpages +## docs : Re-generate the text versions of the man pages +.DEFAULT_GOAL := all +.PHONY: all ALL docs +all: $(BIN_ALL) docs +ALL: all + +docs: $(DOC_DIR)/rascsi_man_page.txt $(DOC_DIR)/rasctl_man_page.txt $(DOC_DIR)/scsimon_man_page.txt + +$(BINDIR)/$(RASCSI): $(SRC_PROTOBUF) $(OBJ_RASCSI) | $(BINDIR) + $(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASCSI) -lpthread -lpcap -lprotobuf -lstdc++fs + +$(BINDIR)/$(RASCTL): $(SRC_PROTOBUF) $(OBJ_RASCTL) | $(BINDIR) + $(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASCTL) -lpthread -lprotobuf -lstdc++fs + +$(BINDIR)/$(RASDUMP): $(OBJ_RASDUMP) | $(BINDIR) + $(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASDUMP) + +$(BINDIR)/$(SASIDUMP): $(OBJ_SASIDUMP) | $(BINDIR) + $(CXX) $(CXXFLAGS) -o $@ $(OBJ_SASIDUMP) + +$(BINDIR)/$(SCSIMON): $(OBJ_SCSIMON) | $(BINDIR) + $(CXX) $(CXXFLAGS) -o $@ $(OBJ_SCSIMON) -lpthread + + +# Phony rules for building individual utilities +.PHONY: $(RASCSI) $(RASCTL) $(RASDUMP) $(SASIDUMP) $(SCSIMON) +$(RASCSI) : $(BINDIR)/$(RASCSI) +$(RASCTL) : $(BINDIR)/$(RASCTL) +$(RASDUMP) : $(BINDIR)/$(RASDUMP) +$(SASIDUMP): $(BINDIR)/$(SASIDUMP) +$(SCSIMON) : $(BINDIR)/$(SCSIMON) + + +## clean : Remove all of the object files, intermediate +## compiler files and executable files +.PHONY: clean +clean: + rm -rf $(OBJDIR) $(BINDIR) $(GEN_PROTOBUF) + +## install : Copies all of the man pages to the correct location +## Copies the binaries to a global install location +## Configures the Systemd and RSyslog services to auto-run RaSCSI +## * This target needs to be run with sudo (ex: sudo make install) +## * Before running this, you need to stop the rascsi service if +## * it is already running: +## * sudo systemctl stop rascsi +## * After running this, you will need to reboot or run: +## * sudo systemctl daemon-reload +## * sudo systemctl restart rsyslog +## * sudo systemctl enable rascsi +## * sudo systemctl start rascsi +.PHONY: install +install: \ + $(MAN_PAGE_DIR)/rascsi.1 \ + $(MAN_PAGE_DIR)/rasctl.1 \ + $(MAN_PAGE_DIR)/scsimon.1 \ + $(MAN_PAGE_DIR)/rasdump.1 \ + $(MAN_PAGE_DIR)/sasidump.1 \ + $(USR_LOCAL_BIN)/$(RASCTL) \ + $(USR_LOCAL_BIN)/$(RASCSI) \ + $(USR_LOCAL_BIN)/$(SCSIMON) \ + $(USR_LOCAL_BIN)/$(RASDUMP) \ + $(USR_LOCAL_BIN)/$(SASIDUMP) \ + $(SYSTEMD_CONF) \ + $(RSYSLOG_CONF) \ + $(RSYSLOG_LOG) + @echo "-- Done installing!" + +$(USR_LOCAL_BIN)% : $(BINDIR)/% + @echo "-- Copying $@" + cp $< $@ + +$(MAN_PAGE_DIR)/%.1 : $(DOC_DIR)/%.1 | $(MAN_PAGE_DIR)/ + @echo "-- Copying $@" + cp $< $@ + +$(DOC_DIR)/%_man_page.txt : $(DOC_DIR)/%.1 + @echo "!! ------ THIS FILE IS AUTO_GENERATED! DO NOT MANUALLY UPDATE!!!" > $@ + @echo "!! ------ The native file is $(notdir $<). Re-run 'make docs' after updating\n\n" >> $@ + man -l $< | col -bx >> $@ + +$(SYSTEMD_CONF) : $(OS_FILES)/$(notdir $(SYSTEMD_CONF)) + @echo "-- Copying $@" + cp $< $@ + +$(RSYSLOG_CONF) : $(OS_FILES)/$(notdir $(RSYSLOG_CONF)) + @echo "-- Copying $@" + cp $< $@ + +$(RSYSLOG_LOG) : + @echo "-- Creating $@" + touch /var/log/rascsi.log + chown root:adm /var/log/rascsi.log + +$(MAN_PAGE_DIR)/: + echo "-- Creating directory $@" + mkdir -p $@ + +## help : Lists information about how to use the makefile +# The help rule is based upon the approach from: +# https://swcarpentry.github.io/make-novice/08-self-doc/index.html +.PHONY: help +help : Makefile + @sed -n 's/^##//p' $< + +## Debug : Same as 'all'. Useful when using a debugger. +.PHONY: Debug +Debug: all + diff --git a/src_old/raspberrypi/command_context.h b/src_old/raspberrypi/command_context.h new file mode 100644 index 00000000..37ae76ba --- /dev/null +++ b/src_old/raspberrypi/command_context.h @@ -0,0 +1,17 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2021 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#pragma once + +#include + +struct CommandContext { + int fd; + std::string locale; +}; diff --git a/src_old/raspberrypi/config.h b/src_old/raspberrypi/config.h new file mode 100644 index 00000000..bde2641f --- /dev/null +++ b/src_old/raspberrypi/config.h @@ -0,0 +1,26 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Powered by XM6 TypeG Technology. +// Copyright (C) 2016-2020 GIMONS +// +// [ Common Definitions ] +// +//--------------------------------------------------------------------------- + +#pragma once + +//--------------------------------------------------------------------------- +// +// Various Operation Settings +// +//--------------------------------------------------------------------------- +#define USE_SEL_EVENT_ENABLE // Check SEL signal by event +#define REMOVE_FIXED_SASIHD_SIZE // remove the size limitation of SASIHD +// This avoids an indefinite loop with warnings if there is no RaSCSI hardware +// and thus helps with running certain tests on X86 hardware. +#if defined(__x86_64__) || defined(__X86__) +#undef USE_SEL_EVENT_ENABLE +#endif diff --git a/src_old/raspberrypi/controllers/sasidev_ctrl.cpp b/src_old/raspberrypi/controllers/sasidev_ctrl.cpp new file mode 100644 index 00000000..b02e6a6f --- /dev/null +++ b/src_old/raspberrypi/controllers/sasidev_ctrl.cpp @@ -0,0 +1,1281 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// Copyright (C) akuker +// +// Licensed under the BSD 3-Clause License. +// See LICENSE file in the project root folder. +// +// [ SASI device controller ] +// +//--------------------------------------------------------------------------- +#include "controllers/sasidev_ctrl.h" +#include "filepath.h" +#include "gpiobus.h" +#include "devices/scsi_host_bridge.h" +#include "devices/scsi_daynaport.h" +#include + +//=========================================================================== +// +// SASI Device +// +//=========================================================================== + +//--------------------------------------------------------------------------- +// +// Constructor +// +//--------------------------------------------------------------------------- +SASIDEV::SASIDEV() +{ + // Work initialization + ctrl.phase = BUS::busfree; + ctrl.m_scsi_id = UNKNOWN_SCSI_ID; + ctrl.bus = NULL; + memset(ctrl.cmd, 0x00, sizeof(ctrl.cmd)); + ctrl.status = 0x00; + ctrl.message = 0x00; + ctrl.execstart = 0; + // The initial buffer size will default to either the default buffer size OR + // the size of an Ethernet message, whichever is larger. + ctrl.bufsize = std::max(DEFAULT_BUFFER_SIZE, ETH_FRAME_LEN + 16 + ETH_FCS_LEN); + ctrl.buffer = (BYTE *)malloc(ctrl.bufsize); + memset(ctrl.buffer, 0x00, ctrl.bufsize); + ctrl.blocks = 0; + ctrl.next = 0; + ctrl.offset = 0; + ctrl.length = 0; + ctrl.lun = -1; + + // Logical unit initialization + for (int i = 0; i < UnitMax; i++) { + ctrl.unit[i] = NULL; + } +} + +//--------------------------------------------------------------------------- +// +// Destructor +// +//--------------------------------------------------------------------------- +SASIDEV::~SASIDEV() +{ + // Free the buffer + if (ctrl.buffer) { + free(ctrl.buffer); + ctrl.buffer = NULL; + } +} + +//--------------------------------------------------------------------------- +// +// Device reset +// +//--------------------------------------------------------------------------- +void SASIDEV::Reset() +{ + // Work initialization + memset(ctrl.cmd, 0x00, sizeof(ctrl.cmd)); + ctrl.phase = BUS::busfree; + ctrl.status = 0x00; + ctrl.message = 0x00; + ctrl.execstart = 0; + memset(ctrl.buffer, 0x00, ctrl.bufsize); + ctrl.blocks = 0; + ctrl.next = 0; + ctrl.offset = 0; + ctrl.length = 0; + + // Unit initialization + for (int i = 0; i < UnitMax; i++) { + if (ctrl.unit[i]) { + ctrl.unit[i]->Reset(); + } + } +} + +//--------------------------------------------------------------------------- +// +// Connect the controller +// +//--------------------------------------------------------------------------- +void SASIDEV::Connect(int id, BUS *bus) +{ + ctrl.m_scsi_id = id; + ctrl.bus = bus; +} + +//--------------------------------------------------------------------------- +// +// Set the logical unit +// +//--------------------------------------------------------------------------- +void SASIDEV::SetUnit(int no, PrimaryDevice *dev) +{ + ASSERT(no < UnitMax); + + ctrl.unit[no] = dev; +} + +//--------------------------------------------------------------------------- +// +// Check to see if this has a valid LUN +// +//--------------------------------------------------------------------------- +bool SASIDEV::HasUnit() +{ + for (int i = 0; i < UnitMax; i++) { + if (ctrl.unit[i]) { + return true; + } + } + + return false; +} + +//--------------------------------------------------------------------------- +// +// Run +// +//--------------------------------------------------------------------------- +BUS::phase_t SASIDEV::Process(int) +{ + // Do nothing if not connected + if (ctrl.m_scsi_id < 0 || ctrl.bus == NULL) { + return ctrl.phase; + } + + // Get bus information + ((GPIOBUS*)ctrl.bus)->Aquire(); + + // For the monitor tool, we shouldn't need to reset. We're just logging information + // Reset + if (ctrl.bus->GetRST()) { + LOGINFO("RESET signal received"); + + // Reset the controller + Reset(); + + // Reset the bus + ctrl.bus->Reset(); + return ctrl.phase; + } + + // Phase processing + switch (ctrl.phase) { + // Bus free + case BUS::busfree: + BusFree(); + break; + + // Selection + case BUS::selection: + Selection(); + break; + + // Data out (MCI=000) + case BUS::dataout: + DataOut(); + break; + + // Data in (MCI=001) + case BUS::datain: + DataIn(); + break; + + // Command (MCI=010) + case BUS::command: + Command(); + break; + + // Status (MCI=011) + case BUS::status: + Status(); + break; + + // Msg in (MCI=111) + case BUS::msgin: + MsgIn(); + break; + + // Other + default: + ASSERT(FALSE); + break; + } + + return ctrl.phase; +} + +//--------------------------------------------------------------------------- +// +// Bus free phase +// +//--------------------------------------------------------------------------- +void SASIDEV::BusFree() +{ + // Phase change + if (ctrl.phase != BUS::busfree) { + LOGTRACE("%s Bus free phase", __PRETTY_FUNCTION__); + + // Phase Setting + ctrl.phase = BUS::busfree; + + // Set Signal lines + ctrl.bus->SetREQ(FALSE); + ctrl.bus->SetMSG(FALSE); + ctrl.bus->SetCD(FALSE); + ctrl.bus->SetIO(FALSE); + ctrl.bus->SetBSY(false); + + // Initialize status and message + ctrl.status = 0x00; + ctrl.message = 0x00; + + ctrl.lun = -1; + + return; + } + + // Move to selection phase + if (ctrl.bus->GetSEL() && !ctrl.bus->GetBSY()) { + Selection(); + } +} + +//--------------------------------------------------------------------------- +// +// Selection phase +// +//--------------------------------------------------------------------------- +void SASIDEV::Selection() +{ + // Phase change + if (ctrl.phase != BUS::selection) { + // Invalid if IDs do not match + DWORD id = 1 << ctrl.m_scsi_id; + if ((ctrl.bus->GetDAT() & id) == 0) { + return; + } + + // Return if there is no valid LUN + if (!HasUnit()) { + return; + } + + LOGTRACE("%s Selection Phase ID=%d (with device)", __PRETTY_FUNCTION__, (int)ctrl.m_scsi_id); + + // Phase change + ctrl.phase = BUS::selection; + + // Raiase BSY and respond + ctrl.bus->SetBSY(true); + return; + } + + // Command phase shifts when selection is completed + if (!ctrl.bus->GetSEL() && ctrl.bus->GetBSY()) { + Command(); + } +} + +//--------------------------------------------------------------------------- +// +// Command phase (used by SASI and SCSI) +// +//--------------------------------------------------------------------------- +void SASIDEV::Command() +{ + // Phase change + if (ctrl.phase != BUS::command) { + LOGTRACE("%s Command Phase", __PRETTY_FUNCTION__); + + // Phase Setting + ctrl.phase = BUS::command; + + // Signal line operated by the target + ctrl.bus->SetMSG(FALSE); + ctrl.bus->SetCD(TRUE); + ctrl.bus->SetIO(FALSE); + + // Data transfer is 6 bytes x 1 block + ctrl.offset = 0; + ctrl.length = 6; + ctrl.blocks = 1; + + // If no byte can be received move to the status phase + int count = ctrl.bus->CommandHandShake(ctrl.buffer); + if (!count) { + Error(); + return; + } + + ctrl.length = GPIOBUS::GetCommandByteCount(ctrl.buffer[0]); + + // If not able to receive all, move to the status phase + if (count != (int)ctrl.length) { + Error(); + return; + } + + // Command data transfer + for (int i = 0; i < (int)ctrl.length; i++) { + ctrl.cmd[i] = (DWORD)ctrl.buffer[i]; + LOGTRACE("%s CDB[%d]=$%02X",__PRETTY_FUNCTION__, i, ctrl.cmd[i]); + } + + // Clear length and block + ctrl.length = 0; + ctrl.blocks = 0; + + // Execution Phase + Execute(); + } +} + +//--------------------------------------------------------------------------- +// +// Execution Phase +// +//--------------------------------------------------------------------------- +void SASIDEV::Execute() +{ + LOGTRACE("%s Execution Phase Command %02X", __PRETTY_FUNCTION__, (WORD)ctrl.cmd[0]); + + // Phase Setting + ctrl.phase = BUS::execute; + + // Initialization for data transfer + ctrl.offset = 0; + ctrl.blocks = 1; + ctrl.execstart = SysTimer::GetTimerLow(); + + // Discard pending sense data from the previous command if the current command is not REQUEST SENSE + if ((SASIDEV::sasi_command)ctrl.cmd[0] != SASIDEV::eCmdRequestSense) { + ctrl.status = 0; + ctrl.device->SetStatusCode(0); + } + + // Process by command + // TODO This code does not belong here. Each device type needs such a dispatcher, which the controller has to call. + switch ((SASIDEV::sasi_command)ctrl.cmd[0]) { + // TEST UNIT READY + case SASIDEV::eCmdTestUnitReady: + CmdTestUnitReady(); + return; + + // REZERO UNIT + case SASIDEV::eCmdRezero: + CmdRezero(); + return; + + // REQUEST SENSE + case SASIDEV::eCmdRequestSense: + CmdRequestSense(); + return; + + // FORMAT + case SASIDEV::eCmdFormat: + CmdFormat(); + return; + + // REASSIGN BLOCKS + case SASIDEV::eCmdReassign: + CmdReassignBlocks(); + return; + + // READ(6) + case SASIDEV::eCmdRead6: + CmdRead6(); + return; + + // WRITE(6) + case SASIDEV::eCmdWrite6: + CmdWrite6(); + return; + + // SEEK(6) + case SASIDEV::eCmdSeek6: + CmdSeek6(); + return; + + // ASSIGN (SASI only) + // This doesn't exist in the SCSI Spec, but was in the original RaSCSI code. + // leaving it here for now.... + case SASIDEV::eCmdSasiCmdAssign: + CmdAssign(); + return; + + // RESERVE UNIT(16) + case SASIDEV::eCmdReserve6: + CmdReserveUnit(); + return; + + // RELEASE UNIT(17) + case eCmdRelease6: + CmdReleaseUnit(); + return; + + // SPECIFY (SASI only) + // This doesn't exist in the SCSI Spec, but was in the original RaSCSI code. + // leaving it here for now.... + case SASIDEV::eCmdInvalid: + CmdSpecify(); + return; + + default: + break; + } + + // Unsupported command + LOGTRACE("%s ID %d received unsupported command: $%02X", __PRETTY_FUNCTION__, GetSCSIID(), (BYTE)ctrl.cmd[0]); + + // Logical Unit + DWORD lun = GetEffectiveLun(); + if (ctrl.unit[lun]) { + // Command processing on drive + ctrl.unit[lun]->SetStatusCode(STATUS_INVALIDCMD); + } + + // Failure (Error) + Error(); +} + +//--------------------------------------------------------------------------- +// +// Status phase +// +//--------------------------------------------------------------------------- +void SASIDEV::Status() +{ + // Phase change + if (ctrl.phase != BUS::status) { + // Minimum execution time + if (ctrl.execstart > 0) { + DWORD min_exec_time = IsSASI() ? min_exec_time_sasi : min_exec_time_scsi; + DWORD time = SysTimer::GetTimerLow() - ctrl.execstart; + if (time < min_exec_time) { + SysTimer::SleepUsec(min_exec_time - time); + } + ctrl.execstart = 0; + } else { + SysTimer::SleepUsec(5); + } + + LOGTRACE("%s Status phase", __PRETTY_FUNCTION__); + + // Phase Setting + ctrl.phase = BUS::status; + + // Signal line operated by the target + ctrl.bus->SetMSG(FALSE); + ctrl.bus->SetCD(TRUE); + ctrl.bus->SetIO(TRUE); + + // Data transfer is 1 byte x 1 block + ctrl.offset = 0; + ctrl.length = 1; + ctrl.blocks = 1; + ctrl.buffer[0] = (BYTE)ctrl.status; + + LOGTRACE( "%s Status Phase $%02X",__PRETTY_FUNCTION__, (unsigned int)ctrl.status); + + return; + } + + // Send + Send(); +} + +//--------------------------------------------------------------------------- +// +// Message in phase (used by SASI and SCSI) +// +//--------------------------------------------------------------------------- +void SASIDEV::MsgIn() +{ + // Phase change + if (ctrl.phase != BUS::msgin) { + LOGTRACE("%s Starting Message in phase", __PRETTY_FUNCTION__); + + // Phase Setting + ctrl.phase = BUS::msgin; + + // Signal line operated by the target + ctrl.bus->SetMSG(TRUE); + ctrl.bus->SetCD(TRUE); + ctrl.bus->SetIO(TRUE); + + // length, blocks are already set + ASSERT(ctrl.length > 0); + ASSERT(ctrl.blocks > 0); + ctrl.offset = 0; + return; + } + + //Send + LOGTRACE("%s Transitioning to Send()", __PRETTY_FUNCTION__); + Send(); +} + +//--------------------------------------------------------------------------- +// +// Data-in Phase (used by SASI and SCSI) +// +//--------------------------------------------------------------------------- +void SASIDEV::DataIn() +{ + ASSERT(ctrl.length >= 0); + + // Phase change + if (ctrl.phase != BUS::datain) { + // Minimum execution time + if (ctrl.execstart > 0) { + DWORD min_exec_time = IsSASI() ? min_exec_time_sasi : min_exec_time_scsi; + DWORD time = SysTimer::GetTimerLow() - ctrl.execstart; + if (time < min_exec_time) { + SysTimer::SleepUsec(min_exec_time - time); + } + ctrl.execstart = 0; + } + + // If the length is 0, go to the status phase + if (ctrl.length == 0) { + Status(); + return; + } + + LOGTRACE("%s Going into Data-in Phase", __PRETTY_FUNCTION__); + // Phase Setting + ctrl.phase = BUS::datain; + + // Signal line operated by the target + ctrl.bus->SetMSG(FALSE); + ctrl.bus->SetCD(FALSE); + ctrl.bus->SetIO(TRUE); + + // length, blocks are already set + ASSERT(ctrl.length > 0); + ASSERT(ctrl.blocks > 0); + ctrl.offset = 0; + + return; + } + + // Send + Send(); +} + +//--------------------------------------------------------------------------- +// +// Data out phase (used by SASI and SCSI) +// +//--------------------------------------------------------------------------- +void SASIDEV::DataOut() +{ + ASSERT(ctrl.length >= 0); + + // Phase change + if (ctrl.phase != BUS::dataout) { + // Minimum execution time + if (ctrl.execstart > 0) { + DWORD min_exec_time = IsSASI() ? min_exec_time_sasi : min_exec_time_scsi; + DWORD time = SysTimer::GetTimerLow() - ctrl.execstart; + if (time < min_exec_time) { + SysTimer::SleepUsec(min_exec_time - time); + } + ctrl.execstart = 0; + } + + // If the length is 0, go to the status phase + if (ctrl.length == 0) { + Status(); + return; + } + + LOGTRACE("%s Data out phase", __PRETTY_FUNCTION__); + + // Phase Setting + ctrl.phase = BUS::dataout; + + // Signal line operated by the target + ctrl.bus->SetMSG(FALSE); + ctrl.bus->SetCD(FALSE); + ctrl.bus->SetIO(FALSE); + + // Length has already been calculated + ASSERT(ctrl.length > 0); + ctrl.offset = 0; + return; + } + + // Receive + Receive(); +} + +//--------------------------------------------------------------------------- +// +// Error +// +//--------------------------------------------------------------------------- +void SASIDEV::Error(ERROR_CODES::sense_key sense_key, ERROR_CODES::asc asc, ERROR_CODES::status status) +{ + // Get bus information + ctrl.bus->Aquire(); + + // Reset check + if (ctrl.bus->GetRST()) { + // Reset the controller + Reset(); + + // Reset the bus + ctrl.bus->Reset(); + return; + } + + // Bus free for status phase and message in phase + if (ctrl.phase == BUS::status || ctrl.phase == BUS::msgin) { + BusFree(); + return; + } + + // Logical Unit + DWORD lun = GetEffectiveLun(); + + // Set status and message + ctrl.status = (lun << 5) | status; + + // status phase + Status(); +} + +//--------------------------------------------------------------------------- +// +// TEST UNIT READY +// +//--------------------------------------------------------------------------- +void SASIDEV::CmdTestUnitReady() +{ + LOGTRACE("%s TEST UNIT READY Command ", __PRETTY_FUNCTION__); + + // Command processing on drive + ctrl.device->TestUnitReady(this); +} + +//--------------------------------------------------------------------------- +// +// REZERO UNIT +// +//--------------------------------------------------------------------------- +void SASIDEV::CmdRezero() +{ + LOGTRACE( "%s REZERO UNIT Command ", __PRETTY_FUNCTION__); + + // Command processing on drive + ((Disk *)ctrl.device)->Rezero(this); +} + +//--------------------------------------------------------------------------- +// +// REQUEST SENSE +// +//--------------------------------------------------------------------------- +void SASIDEV::CmdRequestSense() +{ + LOGTRACE( "%s REQUEST SENSE Command ", __PRETTY_FUNCTION__); + + // Command processing on drive + ctrl.device->RequestSense(this); +} + +//--------------------------------------------------------------------------- +// +// FORMAT UNIT +// +//--------------------------------------------------------------------------- +void SASIDEV::CmdFormat() +{ + LOGTRACE( "%s FORMAT UNIT Command ", __PRETTY_FUNCTION__); + + // Command processing on drive + ((Disk *)ctrl.device)->FormatUnit(this); +} + +//--------------------------------------------------------------------------- +// +// REASSIGN BLOCKS +// +//--------------------------------------------------------------------------- +void SASIDEV::CmdReassignBlocks() +{ + LOGTRACE("%s REASSIGN BLOCKS Command ", __PRETTY_FUNCTION__); + + // Command processing on drive + ((Disk *)ctrl.device)->ReassignBlocks(this); +} + +//--------------------------------------------------------------------------- +// +// RESERVE UNIT(16) +// +// The reserve/release commands are only used in multi-initiator +// environments. RaSCSI doesn't support this use case. However, some old +// versions of Solaris will issue the reserve/release commands. We will +// just respond with an OK status. +// +//--------------------------------------------------------------------------- +void SASIDEV::CmdReserveUnit() +{ + LOGTRACE( "%s Reserve(6) Command", __PRETTY_FUNCTION__); + + // status phase + Status(); +} + +//--------------------------------------------------------------------------- +// +// RELEASE UNIT(17) +// +// The reserve/release commands are only used in multi-initiator +// environments. RaSCSI doesn't support this use case. However, some old +// versions of Solaris will issue the reserve/release commands. We will +// just respond with an OK status. +// +//--------------------------------------------------------------------------- +void SASIDEV::CmdReleaseUnit() +{ + LOGTRACE( "%s Release(6) Command", __PRETTY_FUNCTION__); + + // status phase + Status(); +} + +//--------------------------------------------------------------------------- +// +// READ(6) +// +//--------------------------------------------------------------------------- +void SASIDEV::CmdRead6() +{ + // Get record number and block number + DWORD record = ctrl.cmd[1] & 0x1f; + record <<= 8; + record |= ctrl.cmd[2]; + record <<= 8; + record |= ctrl.cmd[3]; + ctrl.blocks = ctrl.cmd[4]; + if (ctrl.blocks == 0) { + ctrl.blocks = 0x100; + } + + LOGTRACE("%s READ(6) command record=%d blocks=%d", __PRETTY_FUNCTION__, (unsigned int)record, (int)ctrl.blocks); + + // Command processing on drive + ctrl.length = ((Disk *)ctrl.device)->Read(ctrl.cmd, ctrl.buffer, record); + if (ctrl.length <= 0) { + // Failure (Error) + Error(); + return; + } + + // Set next block + ctrl.next = record + 1; + + // Read phase + DataIn(); +} + +//--------------------------------------------------------------------------- +// +// WRITE(6) +// +//--------------------------------------------------------------------------- +void SASIDEV::CmdWrite6() +{ + // Get record number and block number + DWORD record = ctrl.cmd[1] & 0x1f; + record <<= 8; + record |= ctrl.cmd[2]; + record <<= 8; + record |= ctrl.cmd[3]; + ctrl.blocks = ctrl.cmd[4]; + if (ctrl.blocks == 0) { + ctrl.blocks = 0x100; + } + + LOGTRACE("%s WRITE(6) command record=%d blocks=%d", __PRETTY_FUNCTION__, (WORD)record, (WORD)ctrl.blocks); + + // Command processing on drive + ctrl.length = ((Disk *)ctrl.device)->WriteCheck(record); + if (ctrl.length <= 0) { + // Failure (Error) + Error(); + return; + } + + // Set next block + ctrl.next = record + 1; + + // Write phase + DataOut(); +} + +//--------------------------------------------------------------------------- +// +// SEEK(6) +// +//--------------------------------------------------------------------------- +void SASIDEV::CmdSeek6() +{ + LOGTRACE("%s SEEK(6) Command ", __PRETTY_FUNCTION__); + + // Command processing on drive + ((Disk *)ctrl.device)->Seek6(this); +} + +//--------------------------------------------------------------------------- +// +// ASSIGN +// +//--------------------------------------------------------------------------- +void SASIDEV::CmdAssign() +{ + LOGTRACE("%s ASSIGN Command ", __PRETTY_FUNCTION__); + + // Command processing on device + bool status = ctrl.device->CheckReady(); + if (!status) { + // Failure (Error) + Error(); + return; + } + + // Request 4 bytes of data + ctrl.length = 4; + + // Write phase + DataOut(); +} + +//--------------------------------------------------------------------------- +// +// SPECIFY +// +//--------------------------------------------------------------------------- +void SASIDEV::CmdSpecify() +{ + LOGTRACE("%s SPECIFY Command ", __PRETTY_FUNCTION__); + + // Command processing on device + bool status =ctrl.device->CheckReady(); + if (!status) { + // Failure (Error) + Error(); + return; + } + + // Request 10 bytes of data + ctrl.length = 10; + + // Write phase + DataOut(); +} + +//=========================================================================== +// +// Data transfer +// +//=========================================================================== + +//--------------------------------------------------------------------------- +// +// Data transmission +// +//--------------------------------------------------------------------------- +void SASIDEV::Send() +{ + ASSERT(!ctrl.bus->GetREQ()); + ASSERT(ctrl.bus->GetIO()); + + // Check that the length isn't 0 + if (ctrl.length != 0) { + int len = ctrl.bus->SendHandShake( + &ctrl.buffer[ctrl.offset], ctrl.length, BUS::SEND_NO_DELAY); + + // If you can not send it all, move on to the status phase + if (len != (int)ctrl.length) { + LOGERROR("%s ctrl.length (%d) did not match the amount of data sent (%d)",__PRETTY_FUNCTION__, (int)ctrl.length, len); + Error(); + return; + } + + // Offset and Length + ctrl.offset += ctrl.length; + ctrl.length = 0; + return; + } + else{ + LOGINFO("%s ctrl.length was 0", __PRETTY_FUNCTION__); + } + + // Remove block and initialize the result + ctrl.blocks--; + bool result = true; + + // Process after data collection (read/data-in only) + if (ctrl.phase == BUS::datain) { + if (ctrl.blocks != 0) { + // Set next buffer (set offset, length) + result = XferIn(ctrl.buffer); + LOGTRACE("%s xfer in: %d",__PRETTY_FUNCTION__, result); + LOGTRACE("%s processing after data collection", __PRETTY_FUNCTION__); + } + } + + // If result FALSE, move to the status phase + if (!result) { + LOGERROR("%s Send result was false", __PRETTY_FUNCTION__); + Error(); + return; + } + + // Continue sending if block != 0 + if (ctrl.blocks != 0){ + ASSERT(ctrl.length > 0); + ASSERT(ctrl.offset == 0); + return; + } + + // Move to the next phase + switch (ctrl.phase) { + // Message in phase + case BUS::msgin: + // Bus free phase + BusFree(); + break; + + // Data-in Phase + case BUS::datain: + // status phase + Status(); + break; + + // Status phase + case BUS::status: + // Message in phase + ctrl.length = 1; + ctrl.blocks = 1; + ctrl.buffer[0] = (BYTE)ctrl.message; + MsgIn(); + break; + + // Other (impossible) + default: + ASSERT(FALSE); + break; + } +} + +//--------------------------------------------------------------------------- +// +// Receive data +// +//--------------------------------------------------------------------------- +void SASIDEV::Receive() +{ + // REQ is low + ASSERT(!ctrl.bus->GetREQ()); + ASSERT(!ctrl.bus->GetIO()); + + // Length != 0 if received + if (ctrl.length != 0) { + // Receive + int len = ctrl.bus->ReceiveHandShake( + &ctrl.buffer[ctrl.offset], ctrl.length); + LOGDEBUG("%s Received %d bytes", __PRETTY_FUNCTION__, len); + + // If not able to receive all, move to status phase + if (len != (int)ctrl.length) { + Error(); + return; + } + + // Offset and Length + ctrl.offset += ctrl.length; + ctrl.length = 0; + return; + } + else + { + LOGDEBUG("%s ctrl.length was 0", __PRETTY_FUNCTION__); + } + + // Remove the control block and initialize the result + ctrl.blocks--; + bool result = true; + + // Process the data out phase + if (ctrl.phase == BUS::dataout) { + if (ctrl.blocks == 0) { + // End with this buffer + result = XferOut(false); + } else { + // Continue to next buffer (set offset, length) + result = XferOut(true); + } + } + + // If result is false, move to the status phase + if (!result) { + Error(); + return; + } + + // Continue to receive is block != 0 + if (ctrl.blocks != 0){ + ASSERT(ctrl.length > 0); + ASSERT(ctrl.offset == 0); + return; + } + + // Move to the next phase + switch (ctrl.phase) { + // Data out phase + case BUS::dataout: + LOGTRACE("%s transitioning to FlushUnit()",__PRETTY_FUNCTION__); + // Flush + FlushUnit(); + + // status phase + Status(); + break; + + // Other (impossible) + default: + ASSERT(FALSE); + break; + } +} + +//--------------------------------------------------------------------------- +// +// Data transfer IN +// *Reset offset and length +// TODO XferIn probably needs a dispatcher, in order to avoid subclassing Disk, i.e. +// just like the actual SCSI commands XferIn should be executed by the respective device +// +//--------------------------------------------------------------------------- +bool SASIDEV::XferIn(BYTE *buf) +{ + ASSERT(ctrl.phase == BUS::datain); + LOGTRACE("%s ctrl.cmd[0]=%02X", __PRETTY_FUNCTION__, (unsigned int)ctrl.cmd[0]); + + // Logical Unit + DWORD lun = GetEffectiveLun(); + if (!ctrl.unit[lun]) { + return false; + } + + // Limited to read commands + switch (ctrl.cmd[0]) { + case eCmdRead6: + case eCmdRead10: + case eCmdRead16: + // Read from disk + ctrl.length = ((Disk *)ctrl.unit[lun])->Read(ctrl.cmd, buf, ctrl.next); + ctrl.next++; + + // If there is an error, go to the status phase + if (ctrl.length <= 0) { + // Cancel data-in + return false; + } + + // If things are normal, work setting + ctrl.offset = 0; + break; + + // Other (impossible) + default: + ASSERT(FALSE); + return false; + } + + // Succeeded in setting the buffer + return true; +} + +//--------------------------------------------------------------------------- +// +// Data transfer OUT +// *If cont=true, reset the offset and length +// TODO XferOut probably needs a dispatcher, in order to avoid subclassing Disk, i.e. +// just like the actual SCSI commands XferOut should be executed by the respective device +// +//--------------------------------------------------------------------------- +bool SASIDEV::XferOut(bool cont) +{ + ASSERT(ctrl.phase == BUS::dataout); + + // Logical Unit + DWORD lun = GetEffectiveLun(); + if (!ctrl.unit[lun]) { + return false; + } + Disk *device = (Disk *)ctrl.unit[lun]; + + switch (ctrl.cmd[0]) { + case SASIDEV::eCmdModeSelect6: + case SASIDEV::eCmdModeSelect10: + if (!device->ModeSelect(ctrl.cmd, ctrl.buffer, ctrl.offset)) { + // MODE SELECT failed + return false; + } + break; + + case SASIDEV::eCmdWrite6: + case SASIDEV::eCmdWrite10: + case SASIDEV::eCmdWrite16: + case SASIDEV::eCmdVerify10: + case SASIDEV::eCmdVerify16: + { + // If we're a host bridge, use the host bridge's SendMessage10 function + // TODO This class must not know about SCSIBR + SCSIBR *bridge = dynamic_cast(device); + if (bridge) { + if (!bridge->SendMessage10(ctrl.cmd, ctrl.buffer)) { + // write failed + return false; + } + + // If normal, work setting + ctrl.offset = 0; + break; + } + + // Special case Write function for DaynaPort + // TODO This class must not know about DaynaPort + SCSIDaynaPort *daynaport = dynamic_cast(device); + if (daynaport) { + if (!daynaport->Write(ctrl.cmd, ctrl.buffer, ctrl.length)) { + // write failed + return false; + } + + // If normal, work setting + ctrl.offset = 0; + ctrl.blocks = 0; + break; + } + + if (!device->Write(ctrl.cmd, ctrl.buffer, ctrl.next - 1)) { + // Write failed + return false; + } + + // If you do not need the next block, end here + ctrl.next++; + if (!cont) { + break; + } + + // Check the next block + ctrl.length = device->WriteCheck(ctrl.next - 1); + if (ctrl.length <= 0) { + // Cannot write + return false; + } + + // If normal, work setting + ctrl.offset = 0; + break; + } + + // SPECIFY(SASI only) + case SASIDEV::eCmdInvalid: + break; + + case SASIDEV::eCmdSetMcastAddr: + LOGTRACE("%s Done with DaynaPort Set Multicast Address", __PRETTY_FUNCTION__); + break; + + default: + LOGWARN("Received an unexpected command ($%02X) in %s", (WORD)ctrl.cmd[0] , __PRETTY_FUNCTION__) + break; + } + + // Buffer saved successfully + return true; +} + +//--------------------------------------------------------------------------- +// +// Logical unit flush +// +//--------------------------------------------------------------------------- +void SASIDEV::FlushUnit() +{ + ASSERT(ctrl.phase == BUS::dataout); + + // Logical Unit + DWORD lun = GetEffectiveLun(); + if (!ctrl.unit[lun]) { + return; + } + + Disk *disk = (Disk *)ctrl.unit[lun]; + + // WRITE system only + switch ((SASIDEV::sasi_command)ctrl.cmd[0]) { + case SASIDEV::eCmdWrite6: + case SASIDEV::eCmdWrite10: + case SASIDEV::eCmdWrite16: + case SASIDEV::eCmdWriteLong16: + case SASIDEV::eCmdVerify10: + case SASIDEV::eCmdVerify16: + break; + + case SASIDEV::eCmdModeSelect6: + case SASIDEV::eCmdModeSelect10: + // Debug code related to Issue #2 on github, where we get an unhandled Mode Select when + // the mac is rebooted + // https://github.com/akuker/RASCSI/issues/2 + LOGWARN("Received \'Mode Select\'\n"); + LOGWARN(" Operation Code: [%02X]\n", (WORD)ctrl.cmd[0]); + LOGWARN(" Logical Unit %01X, PF %01X, SP %01X [%02X]\n",\ + (WORD)ctrl.cmd[1] >> 5, 1 & ((WORD)ctrl.cmd[1] >> 4), \ + (WORD)ctrl.cmd[1] & 1, (WORD)ctrl.cmd[1]); + LOGWARN(" Reserved: %02X\n", (WORD)ctrl.cmd[2]); + LOGWARN(" Reserved: %02X\n", (WORD)ctrl.cmd[3]); + LOGWARN(" Parameter List Len %02X\n", (WORD)ctrl.cmd[4]); + LOGWARN(" Reserved: %02X\n",(WORD)ctrl.cmd[5]); + LOGWARN(" Ctrl Len: %08X\n",(WORD)ctrl.length); + + if (!disk->ModeSelect( + ctrl.cmd, ctrl.buffer, ctrl.offset)) { + // MODE SELECT failed + LOGWARN("Error occured while processing Mode Select command %02X\n", (unsigned char)ctrl.cmd[0]); + return; + } + break; + + case SASIDEV::eCmdSetMcastAddr: + // TODO: Eventually, we should store off the multicast address configuration data here... + break; + + default: + LOGWARN("Received an unexpected flush command $%02X\n",(WORD)ctrl.cmd[0]); + break; + } +} + +int SASIDEV::GetEffectiveLun() const +{ + return ctrl.lun != -1 ? ctrl.lun : (ctrl.cmd[1] >> 5) & 0x07; +} + diff --git a/src_old/raspberrypi/controllers/sasidev_ctrl.h b/src_old/raspberrypi/controllers/sasidev_ctrl.h new file mode 100644 index 00000000..c4253c1e --- /dev/null +++ b/src_old/raspberrypi/controllers/sasidev_ctrl.h @@ -0,0 +1,204 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// Copyright (C) akuker +// +// Licensed under the BSD 3-Clause License. +// See LICENSE file in the project root folder. +// +// [ SASI device controller ] +// +//--------------------------------------------------------------------------- +#pragma once + +#include "../config.h" +#include "os.h" +#include "scsi.h" +#include "fileio.h" + +class Device; +class PrimaryDevice; + +//=========================================================================== +// +// SASI Controller +// +//=========================================================================== +class SASIDEV +{ +protected: + enum scsi_message_code : BYTE { + eMsgCodeAbort = 0x06, + eMsgCodeAbortTag = 0x0D, + eMsgCodeBusDeviceReset = 0x0C, + eMsgCodeClearQueue = 0x0E, + eMsgCodeCommandComplete = 0x00, + eMsgCodeDisconnect = 0x04, + eMsgCodeIdentify = 0x80, + eMsgCodeIgnoreWideResidue = 0x23, // (Two Bytes) + eMsgCodeInitiateRecovery = 0x0F, + eMsgCodeInitiatorDetectedError = 0x05, + eMsgCodeLinkedCommandComplete = 0x0A, + eMsgCodeLinkedCommandCompleteWithFlag = 0x0B, + eMsgCodeMessageParityError = 0x09, + eMsgCodeMessageReject = 0x07, + eMsgCodeNoOperation = 0x08, + eMsgCodeHeadOfQueueTag = 0x21, + eMsgCodeOrderedQueueTag = 0x22, + eMsgCodeSimpleQueueTag = 0x20, + eMsgCodeReleaseRecovery = 0x10, + eMsgCodeRestorePointers = 0x03, + eMsgCodeSaveDataPointer = 0x02, + eMsgCodeTerminateIOProcess = 0x11 + }; + +private: + enum sasi_command : int { + eCmdTestUnitReady = 0x00, + eCmdRezero = 0x01, + eCmdRequestSense = 0x03, + eCmdFormat = 0x06, + eCmdReassign = 0x07, + eCmdRead6 = 0x08, + eCmdWrite6 = 0x0A, + eCmdSeek6 = 0x0B, + eCmdSetMcastAddr = 0x0D, // DaynaPort specific command + eCmdModeSelect6 = 0x15, + eCmdReserve6 = 0x16, + eCmdRelease6 = 0x17, + eCmdRead10 = 0x28, + eCmdWrite10 = 0x2A, + eCmdVerify10 = 0x2E, + eCmdVerify = 0x2F, + eCmdModeSelect10 = 0x55, + eCmdRead16 = 0x88, + eCmdWrite16 = 0x8A, + eCmdVerify16 = 0x8F, + eCmdWriteLong10 = 0x3F, + eCmdWriteLong16 = 0x9F, + eCmdInvalid = 0xC2, + eCmdSasiCmdAssign = 0x0E + }; + +public: + enum { + UnitMax = 32 // Maximum number of logical units + }; + + const int UNKNOWN_SCSI_ID = -1; + const int DEFAULT_BUFFER_SIZE = 0x1000; + // TODO Remove this duplicate + const int DAYNAPORT_BUFFER_SIZE = 0x1000000; + + // For timing adjustments + enum { + min_exec_time_sasi = 100, // SASI BOOT/FORMAT 30:NG 35:OK + min_exec_time_scsi = 50 + }; + + // Internal data definition + typedef struct { + // General + BUS::phase_t phase; // Transition phase + int m_scsi_id; // Controller ID (0-7) + BUS *bus; // Bus + + // commands + DWORD cmd[16]; // Command data + DWORD status; // Status data + DWORD message; // Message data + + // Run + DWORD execstart; // Execution start time + + // Transfer + BYTE *buffer; // Transfer data buffer + int bufsize; // Transfer data buffer size + uint32_t blocks; // Number of transfer block + DWORD next; // Next record + DWORD offset; // Transfer offset + DWORD length; // Transfer remaining length + + // Logical unit + PrimaryDevice *unit[UnitMax]; + + // The current device + PrimaryDevice *device; + + // The LUN from the IDENTIFY message + int lun; + } ctrl_t; + +public: + // Basic Functions + SASIDEV(); + virtual ~SASIDEV(); // Destructor + virtual void Reset(); // Device Reset + + // External API + virtual BUS::phase_t Process(int); // Run + + // Connect + void Connect(int id, BUS *sbus); // Controller connection + Device* GetUnit(int no); // Get logical unit + void SetUnit(int no, PrimaryDevice *dev); // Logical unit setting + bool HasUnit(); // Has a valid logical unit + + // Other + BUS::phase_t GetPhase() {return ctrl.phase;} // Get the phase + + int GetSCSIID() {return ctrl.m_scsi_id;} // Get the ID + ctrl_t* GetCtrl() { return &ctrl; } // Get the internal information address + virtual bool IsSASI() const { return true; } // SASI Check + virtual bool IsSCSI() const { return false; } // SCSI check + +public: + void DataIn(); // Data in phase + void Status(); // Status phase + void MsgIn(); // Message in phase + void DataOut(); // Data out phase + + // Get LUN based on IDENTIFY message, with LUN from the CDB as fallback + int GetEffectiveLun() const; + + virtual void Error(ERROR_CODES::sense_key sense_key = ERROR_CODES::sense_key::NO_SENSE, + ERROR_CODES::asc = ERROR_CODES::asc::NO_ADDITIONAL_SENSE_INFORMATION, + ERROR_CODES::status = ERROR_CODES::status::CHECK_CONDITION); // Common error handling + +protected: + // Phase processing + virtual void BusFree(); // Bus free phase + virtual void Selection(); // Selection phase + virtual void Command(); // Command phase + virtual void Execute(); // Execution phase + + // Commands + void CmdTestUnitReady(); // TEST UNIT READY command + void CmdRezero(); // REZERO UNIT command + void CmdRequestSense(); // REQUEST SENSE command + void CmdFormat(); // FORMAT command + void CmdReassignBlocks(); // REASSIGN BLOCKS command + void CmdReserveUnit(); // RESERVE UNIT command + void CmdReleaseUnit(); // RELEASE UNIT command + void CmdRead6(); // READ(6) command + void CmdWrite6(); // WRITE(6) command + void CmdSeek6(); // SEEK(6) command + void CmdAssign(); // ASSIGN command + void CmdSpecify(); // SPECIFY command + + // Data transfer + virtual void Send(); // Send data + virtual void Receive(); // Receive data + + bool XferIn(BYTE* buf); // Data transfer IN + virtual bool XferOut(bool cont); // Data transfer OUT + + // Special operations + void FlushUnit(); // Flush the logical unit + + ctrl_t ctrl; // Internal data +}; diff --git a/src_old/raspberrypi/controllers/scsidev_ctrl.cpp b/src_old/raspberrypi/controllers/scsidev_ctrl.cpp new file mode 100644 index 00000000..c7efe4c9 --- /dev/null +++ b/src_old/raspberrypi/controllers/scsidev_ctrl.cpp @@ -0,0 +1,907 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// Copyright (C) akuker +// +// Licensed under the BSD 3-Clause License. +// See LICENSE file in the project root folder. +// +// [ SCSI device controller ] +// +//--------------------------------------------------------------------------- +#include "log.h" +#include "controllers/scsidev_ctrl.h" +#include "gpiobus.h" +#include "devices/scsi_daynaport.h" +#include "devices/scsi_printer.h" + +//=========================================================================== +// +// SCSI Device +// +//=========================================================================== + +SCSIDEV::SCSIDEV() : SASIDEV() +{ + scsi.is_byte_transfer = false; + scsi.bytes_to_transfer = 0; + shutdown_mode = NONE; + + // Synchronous transfer work initialization + scsi.syncenable = FALSE; + scsi.syncperiod = 50; + scsi.syncoffset = 0; + scsi.atnmsg = false; + scsi.msc = 0; + memset(scsi.msb, 0x00, sizeof(scsi.msb)); +} + +SCSIDEV::~SCSIDEV() +{ +} + +void SCSIDEV::Reset() +{ + scsi.is_byte_transfer = false; + scsi.bytes_to_transfer = 0; + + // Work initialization + scsi.atnmsg = false; + scsi.msc = 0; + memset(scsi.msb, 0x00, sizeof(scsi.msb)); + + super::Reset(); +} + +BUS::phase_t SCSIDEV::Process(int initiator_id) +{ + // Do nothing if not connected + if (ctrl.m_scsi_id < 0 || ctrl.bus == NULL) { + return ctrl.phase; + } + + // Get bus information + ctrl.bus->Aquire(); + + // Check to see if the reset signal was asserted + if (ctrl.bus->GetRST()) { + LOGWARN("RESET signal received!"); + + // Reset the controller + Reset(); + + // Reset the bus + ctrl.bus->Reset(); + return ctrl.phase; + } + + scsi.initiator_id = initiator_id; + + // Phase processing + switch (ctrl.phase) { + // Bus free phase + case BUS::busfree: + BusFree(); + break; + + // Selection + case BUS::selection: + Selection(); + break; + + // Data out (MCI=000) + case BUS::dataout: + DataOut(); + break; + + // Data in (MCI=001) + case BUS::datain: + DataIn(); + break; + + // Command (MCI=010) + case BUS::command: + Command(); + break; + + // Status (MCI=011) + case BUS::status: + Status(); + break; + + // Message out (MCI=110) + case BUS::msgout: + MsgOut(); + break; + + // Message in (MCI=111) + case BUS::msgin: + MsgIn(); + break; + + default: + assert(false); + break; + } + + return ctrl.phase; +} + +//--------------------------------------------------------------------------- +// +// Bus free phase +// +//--------------------------------------------------------------------------- +void SCSIDEV::BusFree() +{ + // Phase change + if (ctrl.phase != BUS::busfree) { + LOGTRACE("%s Bus free phase", __PRETTY_FUNCTION__); + + // Phase setting + ctrl.phase = BUS::busfree; + + // Set Signal lines + ctrl.bus->SetREQ(FALSE); + ctrl.bus->SetMSG(FALSE); + ctrl.bus->SetCD(FALSE); + ctrl.bus->SetIO(FALSE); + ctrl.bus->SetBSY(false); + + // Initialize status and message + ctrl.status = 0x00; + ctrl.message = 0x00; + + // Initialize ATN message reception status + scsi.atnmsg = false; + + ctrl.lun = -1; + + scsi.is_byte_transfer = false; + scsi.bytes_to_transfer = 0; + + // When the bus is free RaSCSI or the Pi may be shut down + switch(shutdown_mode) { + case STOP_RASCSI: + LOGINFO("RaSCSI shutdown requested"); + exit(0); + break; + + case STOP_PI: + LOGINFO("Raspberry Pi shutdown requested"); + if (system("init 0") == -1) { + LOGERROR("Raspberry Pi shutdown failed: %s", strerror(errno)); + } + break; + + case RESTART_PI: + LOGINFO("Raspberry Pi restart requested"); + if (system("init 6") == -1) { + LOGERROR("Raspberry Pi restart failed: %s", strerror(errno)); + } + break; + + default: + break; + } + + return; + } + + // Move to selection phase + if (ctrl.bus->GetSEL() && !ctrl.bus->GetBSY()) { + Selection(); + } +} + +//--------------------------------------------------------------------------- +// +// Selection Phase +// +//--------------------------------------------------------------------------- +void SCSIDEV::Selection() +{ + // Phase change + if (ctrl.phase != BUS::selection) { + // invalid if IDs do not match + int id = 1 << ctrl.m_scsi_id; + if ((ctrl.bus->GetDAT() & id) == 0) { + return; + } + + // Return if there is no valid LUN + if (!HasUnit()) { + return; + } + + LOGTRACE("%s Selection Phase ID=%d (with device)", __PRETTY_FUNCTION__, (int)ctrl.m_scsi_id); + + if (scsi.initiator_id != UNKNOWN_SCSI_ID) { + LOGTRACE("%s Initiator ID is %d", __PRETTY_FUNCTION__, scsi.initiator_id); + } + else { + LOGTRACE("%s Initiator ID is unknown", __PRETTY_FUNCTION__); + } + + // Phase setting + ctrl.phase = BUS::selection; + + // Raise BSY and respond + ctrl.bus->SetBSY(true); + return; + } + + // Selection completed + if (!ctrl.bus->GetSEL() && ctrl.bus->GetBSY()) { + // Message out phase if ATN=1, otherwise command phase + if (ctrl.bus->GetATN()) { + MsgOut(); + } else { + Command(); + } + } +} + +//--------------------------------------------------------------------------- +// +// Execution Phase +// +//--------------------------------------------------------------------------- +void SCSIDEV::Execute() +{ + LOGTRACE("%s Execution phase command $%02X", __PRETTY_FUNCTION__, (unsigned int)ctrl.cmd[0]); + + // Phase Setting + ctrl.phase = BUS::execute; + + // Initialization for data transfer + ctrl.offset = 0; + ctrl.blocks = 1; + ctrl.execstart = SysTimer::GetTimerLow(); + + // Discard pending sense data from the previous command if the current command is not REQUEST SENSE + if ((scsi_defs::scsi_command)ctrl.cmd[0] != scsi_defs::eCmdRequestSense) { + ctrl.status = 0; + } + + LOGDEBUG("++++ CMD ++++ %s Executing command $%02X", __PRETTY_FUNCTION__, (unsigned int)ctrl.cmd[0]); + + int lun = GetEffectiveLun(); + if (!ctrl.unit[lun]) { + if ((scsi_defs::scsi_command)ctrl.cmd[0] != scsi_defs::eCmdInquiry && + (scsi_defs::scsi_command)ctrl.cmd[0] != scsi_defs::eCmdRequestSense) { + LOGDEBUG("Invalid LUN %d for ID %d", lun, GetSCSIID()); + + Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::INVALID_LUN); + return; + } + // Use LUN 0 for INQUIRY and REQUEST SENSE because LUN0 is assumed to be always available. + // INQUIRY and REQUEST SENSE have a special LUN handling of their own, required by the SCSI standard. + else { + assert(ctrl.unit[0]); + + lun = 0; + } + } + + ctrl.device = ctrl.unit[lun]; + + // Discard pending sense data from the previous command if the current command is not REQUEST SENSE + if ((scsi_defs::scsi_command)ctrl.cmd[0] != scsi_defs::eCmdRequestSense) { + ctrl.device->SetStatusCode(0); + } + + if (!ctrl.device->Dispatch(this)) { + LOGTRACE("ID %d LUN %d received unsupported command: $%02X", GetSCSIID(), lun, (BYTE)ctrl.cmd[0]); + + Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::INVALID_COMMAND_OPERATION_CODE); + } + + // SCSI-2 p.104 4.4.3 Incorrect logical unit handling + if ((scsi_defs::scsi_command)ctrl.cmd[0] == scsi_defs::eCmdInquiry && !ctrl.unit[lun]) { + lun = GetEffectiveLun(); + + LOGTRACE("Reporting LUN %d for device ID %d as not supported", lun, ctrl.device->GetId()); + + ctrl.buffer[0] = 0x7f; + } +} + +//--------------------------------------------------------------------------- +// +// Message out phase +// +//--------------------------------------------------------------------------- +void SCSIDEV::MsgOut() +{ + LOGTRACE("%s ID %d",__PRETTY_FUNCTION__, GetSCSIID()); + + // Phase change + if (ctrl.phase != BUS::msgout) { + LOGTRACE("Message Out Phase"); + + // process the IDENTIFY message + if (ctrl.phase == BUS::selection) { + scsi.atnmsg = true; + scsi.msc = 0; + memset(scsi.msb, 0x00, sizeof(scsi.msb)); + } + + // Phase Setting + ctrl.phase = BUS::msgout; + + // Signal line operated by the target + ctrl.bus->SetMSG(TRUE); + ctrl.bus->SetCD(TRUE); + ctrl.bus->SetIO(FALSE); + + // Data transfer is 1 byte x 1 block + ctrl.offset = 0; + ctrl.length = 1; + ctrl.blocks = 1; + + return; + } + + Receive(); +} + +//--------------------------------------------------------------------------- +// +// Common Error Handling +// +//--------------------------------------------------------------------------- +void SCSIDEV::Error(ERROR_CODES::sense_key sense_key, ERROR_CODES::asc asc, ERROR_CODES::status status) +{ + // Get bus information + ctrl.bus->Aquire(); + + // Reset check + if (ctrl.bus->GetRST()) { + // Reset the controller + Reset(); + + // Reset the bus + ctrl.bus->Reset(); + return; + } + + // Bus free for status phase and message in phase + if (ctrl.phase == BUS::status || ctrl.phase == BUS::msgin) { + BusFree(); + return; + } + + int lun = GetEffectiveLun(); + if (!ctrl.unit[lun] || asc == ERROR_CODES::INVALID_LUN) { + lun = 0; + } + + if (sense_key || asc) { + // Set Sense Key and ASC for a subsequent REQUEST SENSE + ctrl.unit[lun]->SetStatusCode((sense_key << 16) | (asc << 8)); + } + + ctrl.status = status; + ctrl.message = 0x00; + + LOGTRACE("%s Error (to status phase)", __PRETTY_FUNCTION__); + + Status(); +} + +//--------------------------------------------------------------------------- +// +// Send data +// +//--------------------------------------------------------------------------- +void SCSIDEV::Send() +{ + ASSERT(!ctrl.bus->GetREQ()); + ASSERT(ctrl.bus->GetIO()); + + if (ctrl.length != 0) { + LOGTRACE("%s%s", __PRETTY_FUNCTION__, (" Sending handhake with offset " + to_string(ctrl.offset) + ", length " + + to_string(ctrl.length)).c_str()); + + int len = ctrl.bus->SendHandShake(&ctrl.buffer[ctrl.offset], ctrl.length, ctrl.unit[0]->GetSendDelay()); + + // If you cannot send all, move to status phase + if (len != (int)ctrl.length) { + Error(); + return; + } + + // offset and length + ctrl.offset += ctrl.length; + ctrl.length = 0; + return; + } + + // Block subtraction, result initialization + ctrl.blocks--; + bool result = true; + + // Processing after data collection (read/data-in only) + if (ctrl.phase == BUS::datain) { + if (ctrl.blocks != 0) { + // set next buffer (set offset, length) + result = XferIn(ctrl.buffer); + LOGTRACE("%s%s", __PRETTY_FUNCTION__, (" Processing after data collection. Blocks: " + to_string(ctrl.blocks)).c_str()); + } + } + + // If result FALSE, move to status phase + if (!result) { + Error(); + return; + } + + // Continue sending if block !=0 + if (ctrl.blocks != 0){ + LOGTRACE("%s%s", __PRETTY_FUNCTION__, (" Continuing to send. Blocks: " + to_string(ctrl.blocks)).c_str()); + ASSERT(ctrl.length > 0); + ASSERT(ctrl.offset == 0); + return; + } + + // Move to next phase + LOGTRACE("%s Move to next phase %s (%d)", __PRETTY_FUNCTION__, BUS::GetPhaseStrRaw(ctrl.phase), ctrl.phase); + switch (ctrl.phase) { + // Message in phase + case BUS::msgin: + // Completed sending response to extended message of IDENTIFY message + if (scsi.atnmsg) { + // flag off + scsi.atnmsg = false; + + // command phase + Command(); + } else { + // Bus free phase + BusFree(); + } + break; + + // Data-in Phase + case BUS::datain: + // status phase + Status(); + break; + + // status phase + case BUS::status: + // Message in phase + ctrl.length = 1; + ctrl.blocks = 1; + ctrl.buffer[0] = (BYTE)ctrl.message; + MsgIn(); + break; + + default: + assert(false); + break; + } +} + +//--------------------------------------------------------------------------- +// +// Receive Data +// +//--------------------------------------------------------------------------- +void SCSIDEV::Receive() +{ + if (scsi.is_byte_transfer) { + ReceiveBytes(); + return; + } + + int len; + BYTE data; + + LOGTRACE("%s",__PRETTY_FUNCTION__); + + // REQ is low + ASSERT(!ctrl.bus->GetREQ()); + ASSERT(!ctrl.bus->GetIO()); + + // Length != 0 if received + if (ctrl.length != 0) { + LOGTRACE("%s Length is %d bytes", __PRETTY_FUNCTION__, (int)ctrl.length); + // Receive + len = ctrl.bus->ReceiveHandShake(&ctrl.buffer[ctrl.offset], ctrl.length); + + // If not able to receive all, move to status phase + if (len != (int)ctrl.length) { + LOGERROR("%s Not able to receive %d bytes of data, only received %d. Going to error",__PRETTY_FUNCTION__, (int)ctrl.length, len); + Error(); + return; + } + + // Offset and Length + ctrl.offset += ctrl.length; + ctrl.length = 0; + return; + } + + // Block subtraction, result initialization + ctrl.blocks--; + bool result = true; + + // Processing after receiving data (by phase) + LOGTRACE("%s ctrl.phase: %d (%s)",__PRETTY_FUNCTION__, (int)ctrl.phase, BUS::GetPhaseStrRaw(ctrl.phase)); + switch (ctrl.phase) { + + // Data out phase + case BUS::dataout: + if (ctrl.blocks == 0) { + // End with this buffer + result = XferOut(false); + } else { + // Continue to next buffer (set offset, length) + result = XferOut(true); + } + break; + + // Message out phase + case BUS::msgout: + ctrl.message = ctrl.buffer[0]; + if (!XferMsg(ctrl.message)) { + // Immediately free the bus if message output fails + BusFree(); + return; + } + + // Clear message data in preparation for message-in + ctrl.message = 0x00; + break; + + default: + break; + } + + // If result FALSE, move to status phase + if (!result) { + Error(); + return; + } + + // Continue to receive if block !=0 + if (ctrl.blocks != 0){ + ASSERT(ctrl.length > 0); + ASSERT(ctrl.offset == 0); + return; + } + + // Move to next phase + switch (ctrl.phase) { + // Command phase + case BUS::command: + len = GPIOBUS::GetCommandByteCount(ctrl.buffer[0]); + + for (int i = 0; i < len; i++) { + ctrl.cmd[i] = ctrl.buffer[i]; + LOGTRACE("%s Command Byte %d: $%02X",__PRETTY_FUNCTION__, i, ctrl.cmd[i]); + } + + // Execution Phase + Execute(); + break; + + // Message out phase + case BUS::msgout: + // Continue message out phase as long as ATN keeps asserting + if (ctrl.bus->GetATN()) { + // Data transfer is 1 byte x 1 block + ctrl.offset = 0; + ctrl.length = 1; + ctrl.blocks = 1; + return; + } + + // Parsing messages sent by ATN + if (scsi.atnmsg) { + int i = 0; + while (i < scsi.msc) { + // Message type + data = scsi.msb[i]; + + // ABORT + if (data == 0x06) { + LOGTRACE("Message code ABORT $%02X", data); + BusFree(); + return; + } + + // BUS DEVICE RESET + if (data == 0x0C) { + LOGTRACE("Message code BUS DEVICE RESET $%02X", data); + scsi.syncoffset = 0; + BusFree(); + return; + } + + // IDENTIFY + if (data >= 0x80) { + ctrl.lun = data & 0x1F; + LOGTRACE("Message code IDENTIFY $%02X, LUN %d selected", data, ctrl.lun); + } + + // Extended Message + if (data == 0x01) { + LOGTRACE("Message code EXTENDED MESSAGE $%02X", data); + + // Check only when synchronous transfer is possible + if (!scsi.syncenable || scsi.msb[i + 2] != 0x01) { + ctrl.length = 1; + ctrl.blocks = 1; + ctrl.buffer[0] = 0x07; + MsgIn(); + return; + } + + // Transfer period factor (limited to 50 x 4 = 200ns) + scsi.syncperiod = scsi.msb[i + 3]; + if (scsi.syncperiod > 50) { + scsi.syncperiod = 50; + } + + // REQ/ACK offset(limited to 16) + scsi.syncoffset = scsi.msb[i + 4]; + if (scsi.syncoffset > 16) { + scsi.syncoffset = 16; + } + + // STDR response message generation + ctrl.length = 5; + ctrl.blocks = 1; + ctrl.buffer[0] = 0x01; + ctrl.buffer[1] = 0x03; + ctrl.buffer[2] = 0x01; + ctrl.buffer[3] = (BYTE)scsi.syncperiod; + ctrl.buffer[4] = (BYTE)scsi.syncoffset; + MsgIn(); + return; + } + + // next + i++; + } + } + + // Initialize ATN message reception status + scsi.atnmsg = false; + + // Command phase + Command(); + break; + + // Data out phase + case BUS::dataout: + FlushUnit(); + + // status phase + Status(); + break; + + default: + assert(false); + break; + } +} + +//--------------------------------------------------------------------------- +// +// Transfer MSG +// +//--------------------------------------------------------------------------- +bool SCSIDEV::XferMsg(int msg) +{ + ASSERT(ctrl.phase == BUS::msgout); + + // Save message out data + if (scsi.atnmsg) { + scsi.msb[scsi.msc] = (BYTE)msg; + scsi.msc++; + scsi.msc %= 256; + } + + return true; +} + +void SCSIDEV::ReceiveBytes() +{ + uint32_t len; + BYTE data; + + LOGTRACE("%s",__PRETTY_FUNCTION__); + + // REQ is low + ASSERT(!ctrl.bus->GetREQ()); + ASSERT(!ctrl.bus->GetIO()); + + if (ctrl.length) { + LOGTRACE("%s Length is %d bytes", __PRETTY_FUNCTION__, ctrl.length); + + len = ctrl.bus->ReceiveHandShake(&ctrl.buffer[ctrl.offset], ctrl.length); + + // If not able to receive all, move to status phase + if (len != ctrl.length) { + LOGERROR("%s Not able to receive %d bytes of data, only received %d. Going to error", + __PRETTY_FUNCTION__, ctrl.length, len); + Error(); + return; + } + + ctrl.offset += ctrl.length; + scsi.bytes_to_transfer = ctrl.length; + ctrl.length = 0; + return; + } + + // Result initialization + bool result = true; + + // Processing after receiving data (by phase) + LOGTRACE("%s ctrl.phase: %d (%s)",__PRETTY_FUNCTION__, (int)ctrl.phase, BUS::GetPhaseStrRaw(ctrl.phase)); + switch (ctrl.phase) { + + case BUS::dataout: + result = XferOut(false); + break; + + case BUS::msgout: + ctrl.message = ctrl.buffer[0]; + if (!XferMsg(ctrl.message)) { + // Immediately free the bus if message output fails + BusFree(); + return; + } + + // Clear message data in preparation for message-in + ctrl.message = 0x00; + break; + + default: + break; + } + + // If result FALSE, move to status phase + if (!result) { + Error(); + return; + } + + // Move to next phase + switch (ctrl.phase) { + case BUS::command: + len = GPIOBUS::GetCommandByteCount(ctrl.buffer[0]); + + for (uint32_t i = 0; i < len; i++) { + ctrl.cmd[i] = ctrl.buffer[i]; + LOGTRACE("%s Command Byte %d: $%02X",__PRETTY_FUNCTION__, i, ctrl.cmd[i]); + } + + Execute(); + break; + + case BUS::msgout: + // Continue message out phase as long as ATN keeps asserting + if (ctrl.bus->GetATN()) { + // Data transfer is 1 byte x 1 block + ctrl.offset = 0; + ctrl.length = 1; + ctrl.blocks = 1; + return; + } + + // Parsing messages sent by ATN + if (scsi.atnmsg) { + int i = 0; + while (i < scsi.msc) { + // Message type + data = scsi.msb[i]; + + // ABORT + if (data == 0x06) { + LOGTRACE("Message code ABORT $%02X", data); + BusFree(); + return; + } + + // BUS DEVICE RESET + if (data == 0x0C) { + LOGTRACE("Message code BUS DEVICE RESET $%02X", data); + scsi.syncoffset = 0; + BusFree(); + return; + } + + // IDENTIFY + if (data >= 0x80) { + ctrl.lun = data & 0x1F; + LOGTRACE("Message code IDENTIFY $%02X, LUN %d selected", data, ctrl.lun); + } + + // Extended Message + if (data == 0x01) { + LOGTRACE("Message code EXTENDED MESSAGE $%02X", data); + + // Check only when synchronous transfer is possible + if (!scsi.syncenable || scsi.msb[i + 2] != 0x01) { + ctrl.length = 1; + ctrl.blocks = 1; + ctrl.buffer[0] = 0x07; + MsgIn(); + return; + } + + // Transfer period factor (limited to 50 x 4 = 200ns) + scsi.syncperiod = scsi.msb[i + 3]; + if (scsi.syncperiod > 50) { + scsi.syncoffset = 50; + } + + // REQ/ACK offset(limited to 16) + scsi.syncoffset = scsi.msb[i + 4]; + if (scsi.syncoffset > 16) { + scsi.syncoffset = 16; + } + + // STDR response message generation + ctrl.length = 5; + ctrl.blocks = 1; + ctrl.buffer[0] = 0x01; + ctrl.buffer[1] = 0x03; + ctrl.buffer[2] = 0x01; + ctrl.buffer[3] = (BYTE)scsi.syncperiod; + ctrl.buffer[4] = (BYTE)scsi.syncoffset; + MsgIn(); + return; + } + + // next + i++; + } + } + + // Initialize ATN message reception status + scsi.atnmsg = false; + + Command(); + break; + + case BUS::dataout: + Status(); + break; + + default: + assert(false); + break; + } +} + +bool SCSIDEV::XferOut(bool cont) +{ + if (!scsi.is_byte_transfer) { + return super::XferOut(cont); + } + + ASSERT(ctrl.phase == BUS::dataout); + + scsi.is_byte_transfer = false; + + PrimaryDevice *device = dynamic_cast(ctrl.unit[GetEffectiveLun()]); + if (device && ctrl.cmd[0] == scsi_defs::eCmdWrite6) { + return device->WriteBytes(ctrl.buffer, scsi.bytes_to_transfer); + } + + LOGWARN("Received an unexpected command ($%02X) in %s", (WORD)ctrl.cmd[0] , __PRETTY_FUNCTION__) + + return false; +} + diff --git a/src_old/raspberrypi/controllers/scsidev_ctrl.h b/src_old/raspberrypi/controllers/scsidev_ctrl.h new file mode 100644 index 00000000..68fc3772 --- /dev/null +++ b/src_old/raspberrypi/controllers/scsidev_ctrl.h @@ -0,0 +1,100 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// Copyright (C) akuker +// +// Licensed under the BSD 3-Clause License. +// See LICENSE file in the project root folder. +// +// [ SCSI device controller ] +// +//--------------------------------------------------------------------------- +#pragma once + +#include "controllers/sasidev_ctrl.h" +#include + +//=========================================================================== +// +// SCSI Device (Interits SASI device) +// +//=========================================================================== +class SCSIDEV : public SASIDEV +{ +public: + + enum rascsi_shutdown_mode { + NONE, + STOP_RASCSI, + STOP_PI, + RESTART_PI + }; + + // Internal data definition + typedef struct { + // Synchronous transfer + BOOL syncenable; // Synchronous transfer possible + int syncperiod; // Synchronous transfer period + int syncoffset; // Synchronous transfer offset + int syncack; // Number of synchronous transfer ACKs + + // ATN message + bool atnmsg; + int msc; + BYTE msb[256]; + + // -1 means that the initiator ID is unknown, e.g. with Atari ACSI and old host adapters + int initiator_id; + + bool is_byte_transfer; + uint32_t bytes_to_transfer; + } scsi_t; + + SCSIDEV(); + ~SCSIDEV(); + + void Reset() override; + + BUS::phase_t Process(int) override; + + void Receive() override; + + bool IsSASI() const override { return false; } + bool IsSCSI() const override { return true; } + + // Common error handling + void Error(ERROR_CODES::sense_key sense_key = ERROR_CODES::sense_key::NO_SENSE, + ERROR_CODES::asc asc = ERROR_CODES::asc::NO_ADDITIONAL_SENSE_INFORMATION, + ERROR_CODES::status status = ERROR_CODES::status::CHECK_CONDITION) override; + + void ScheduleShutDown(rascsi_shutdown_mode shutdown_mode) { this->shutdown_mode = shutdown_mode; } + + int GetInitiatorId() const { return scsi.initiator_id; } + bool IsByteTransfer() const { return scsi.is_byte_transfer; } + void SetByteTransfer(bool is_byte_transfer) { scsi.is_byte_transfer = is_byte_transfer; } + +private: + typedef SASIDEV super; + + // Phases + void BusFree() override; + void Selection() override; + void Execute() override; + void MsgOut(); + + // Data transfer + void Send() override; + bool XferMsg(int); + bool XferOut(bool); + void ReceiveBytes(); + + // Internal data + scsi_t scsi; + + rascsi_shutdown_mode shutdown_mode; +}; + diff --git a/src_old/raspberrypi/devices/cfilesystem.cpp b/src_old/raspberrypi/devices/cfilesystem.cpp new file mode 100644 index 00000000..ee4eddd3 --- /dev/null +++ b/src_old/raspberrypi/devices/cfilesystem.cpp @@ -0,0 +1,4099 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Powered by XM6 TypeG Technology. +// Copyright (C) 2016-2020 GIMONS +// +// Imported sava's bugfix patch(in RASDRV DOS edition). +// +// [ Host File System for the X68000 ] +// +// Note: This functionality is specific to the X68000 +// operating system. +// It is highly unlikely that this will work for other +// platforms. +// +//--------------------------------------------------------------------------- + +#include "os.h" +#include "log.h" +#include "filepath.h" +#include "cfilesystem.h" + +//--------------------------------------------------------------------------- +// +// Kanji code conversion +// +//--------------------------------------------------------------------------- +#define IC_BUF_SIZE 1024 +static char convert_buf[IC_BUF_SIZE]; +#define CONVERT(src, dest, inbuf, outbuf, outsize) \ + convert(src, dest, (char *)inbuf, outbuf, outsize) +static void convert(char const *src, char const *dest, + char *inbuf, char *outbuf, size_t outsize) +{ + *outbuf = '\0'; + size_t in = strlen(inbuf); + size_t out = outsize - 1; + + iconv_t cd = iconv_open(dest, src); + if (cd == (iconv_t)-1) { + return; + } + + size_t ret = iconv(cd, &inbuf, &in, &outbuf, &out); + if (ret == (size_t)-1) { + return; + } + + iconv_close(cd); + *outbuf = '\0'; +} + +//--------------------------------------------------------------------------- +// +// SJIS->UTF8 conversion +// +//--------------------------------------------------------------------------- +static char* SJIS2UTF8(const char *sjis, char *utf8, size_t bufsize) +{ + CONVERT("SJIS", "UTF-8", sjis, utf8, bufsize); + return convert_buf; +} + +//--------------------------------------------------------------------------- +// +// UTF8->SJIS conversion +// +//--------------------------------------------------------------------------- +static char* UTF82SJIS(const char *utf8, char *sjis, size_t bufsize) +{ + CONVERT("UTF-8", "SJIS", utf8, sjis, bufsize); + return convert_buf; +} + +//--------------------------------------------------------------------------- +// +// SJIS->UTF8 conversion (simplified versoin) +// +//--------------------------------------------------------------------------- +static char* S2U(const char *sjis) +{ + SJIS2UTF8(sjis, convert_buf, IC_BUF_SIZE); + return convert_buf; +} + +//--------------------------------------------------------------------------- +// +// UTF8->SJIS conversion (simplified version) +// +//--------------------------------------------------------------------------- +static char* U2S(const char *utf8) +{ + UTF82SJIS(utf8, convert_buf, IC_BUF_SIZE); + return convert_buf; +} + +//--------------------------------------------------------------------------- +// +/// Get path name +/// +/// From the structure used in Human68k namests, get the Human68k path name. +/// A 66 byte buffer is required for writing. +// +//--------------------------------------------------------------------------- +void Human68k::namests_t::GetCopyPath(BYTE* szPath) const +{ + ASSERT(szPath); + + BYTE* p = szPath; + for (size_t i = 0; i < 65; i++) { + BYTE c = path[i]; + if (c == '\0') + break; + if (c == 0x09) { + c = '/'; + } + *p++ = c; + } + + *p = '\0'; +} + +//--------------------------------------------------------------------------- +// +/// Get file name +/// +/// From the structure used in Human68k namests, get the Human68k file name. +/// A 23 byte buffer is required for writing. +// +//--------------------------------------------------------------------------- +void Human68k::namests_t::GetCopyFilename(BYTE* szFilename) const +{ + ASSERT(szFilename); + + size_t i; + BYTE* p = szFilename; + + // Transfer the base file name + for (i = 0; i < 8; i++) { + BYTE c = name[i]; + if (c == ' ') { + // Check that the file name continues after a space is detected + /// TODO: Should change this function to be compatible with 8+3 chars and TwentyOne + // Continue if add[0] is a valid character + if (add[0] != '\0') + goto next_name; + // Continue if a non-space character exists after name[i] + for (size_t j = i + 1; j < 8; j++) { + if (name[j] != ' ') + goto next_name; + } + // Exit if the file name ends + break; + } + next_name: + *p++ = c; + } + // At this point, the number of read characters becomes i >= 8 + + // If the body of the file name exceeds 8 characters, add the extraneous part + if (i >= 8) { + // Transfer the extraneous part + for (i = 0; i < 10; i++) { + BYTE c = add[i]; + if (c == '\0') + break; + *p++ = c; + } + // At this point, the number of read characters becomes i >= 10 + } + + // Transfer the file extension if it exists + if (ext[0] != ' ' || ext[1] != ' ' || ext[2] != ' ') { + *p++ = '.'; + for (i = 0; i < 3; i++) { + BYTE c = ext[i]; + if (c == ' ') { + // Check that the file extension continues after a space is detected + /// TODO: Should change this function to be compatible with 8+3 chars and TwentyOne + // Continue if a non-space character exists after ext[i] + for (size_t j = i + 1; j < 3; j++) { + if (ext[j] != ' ') + goto next_ext; + } + // If the extension ends, the transfer ends + break; + } + next_ext: + *p++ = c; + } + // When all the characters are read, here i >= 3 + } + + // Add sentinel + *p = '\0'; +} + +//=========================================================================== +// +// Host side drive +// +//=========================================================================== + +CHostDrv::CHostDrv() +{ + m_bWriteProtect = FALSE; + m_bEnable = FALSE; + m_capCache.sectors = 0; + m_bVolumeCache = FALSE; + m_szVolumeCache[0] = _T('\0'); + m_szBase[0] = _T('\0'); + m_nRing = 0; +} + +CHostDrv::~CHostDrv() +{ + CHostPath* p; + while ((p = (CHostPath*)m_cRing.Next()) != &m_cRing) { + delete p; + ASSERT(m_nRing); + m_nRing--; + } + + // Confirm that the entity does not exist (just in case) + ASSERT(m_cRing.Next() == &m_cRing); + ASSERT(m_cRing.Prev() == &m_cRing); + ASSERT(m_nRing == 0); +} + +//--------------------------------------------------------------------------- +// +// Initialization (device boot and load) +// +//--------------------------------------------------------------------------- +void CHostDrv::Init(const TCHAR* szBase, DWORD nFlag) +{ + ASSERT(szBase); + ASSERT(strlen(szBase) < FILEPATH_MAX); + ASSERT(m_bWriteProtect == FALSE); + ASSERT(m_bEnable == FALSE); + ASSERT(m_capCache.sectors == 0); + ASSERT(m_bVolumeCache == FALSE); + ASSERT(m_szVolumeCache[0] == _T('\0')); + + // Confirm that the entity does not exist (just in case) + ASSERT(m_cRing.Next() == &m_cRing); + ASSERT(m_cRing.Prev() == &m_cRing); + ASSERT(m_nRing == 0); + + // Receive parameters + if (nFlag & FSFLAG_WRITE_PROTECT) + m_bWriteProtect = TRUE; + strcpy(m_szBase, szBase); + + // Remove the last path delimiter in the base path + // @warning needs to be modified when using Unicode + TCHAR* pClear = NULL; + TCHAR* p = m_szBase; + for (;;) { + TCHAR c = *p; + if (c == _T('\0')) + break; + if (c == _T('/') || c == _T('\\')) { + pClear = p; + } else { + pClear = NULL; + } + if (((TCHAR)0x80 <= c && c <= (TCHAR)0x9F) || (TCHAR)0xE0 <= c) { // To be precise: 0x81~0x9F 0xE0~0xEF + p++; + if (*p == _T('\0')) + break; + } + p++; + } + if (pClear) + *pClear = _T('\0'); + + // Status update + m_bEnable = TRUE; +} + +//--------------------------------------------------------------------------- +// +// Media check +// +//--------------------------------------------------------------------------- +BOOL CHostDrv::isMediaOffline() +{ + // Offline status check + return m_bEnable == FALSE; +} + +//--------------------------------------------------------------------------- +// +// Get media bytes +// +//--------------------------------------------------------------------------- +BYTE CHostDrv::GetMediaByte() const +{ + return Human68k::MEDIA_REMOTE; +} + +//--------------------------------------------------------------------------- +// +// Get drive status +// +//--------------------------------------------------------------------------- +DWORD CHostDrv::GetStatus() const +{ + return 0x40 | (m_bEnable ? (m_bWriteProtect ? 0x08 : 0) | 0x02 : 0); +} + +//--------------------------------------------------------------------------- +// +// Media status settings +// +//--------------------------------------------------------------------------- +void CHostDrv::SetEnable(BOOL bEnable) +{ + m_bEnable = bEnable; + + if (bEnable == FALSE) { + // Clear cache + m_capCache.sectors = 0; + m_bVolumeCache = FALSE; + m_szVolumeCache[0] = _T('\0'); + } +} + +//--------------------------------------------------------------------------- +// +// Media change check +// +//--------------------------------------------------------------------------- +BOOL CHostDrv::CheckMedia() +{ + // Status update + Update(); + if (m_bEnable == FALSE) + CleanCache(); + + return m_bEnable; +} + +//--------------------------------------------------------------------------- +// +// Media status update +// +//--------------------------------------------------------------------------- +void CHostDrv::Update() +{ + // Considered as media insertion + BOOL bEnable = TRUE; + + // Media status reflected + SetEnable(bEnable); +} + +//--------------------------------------------------------------------------- +// +// Eject +// +//--------------------------------------------------------------------------- +void CHostDrv::Eject() +{ + // Media discharge + CleanCache(); + SetEnable(FALSE); + + // Status update + Update(); +} + +//--------------------------------------------------------------------------- +// +// Get volume label +// +//--------------------------------------------------------------------------- +void CHostDrv::GetVolume(TCHAR* szLabel) +{ + ASSERT(szLabel); + ASSERT(m_bEnable); + + // Get volume label + strcpy(m_szVolumeCache, "RASDRV "); + if (m_szBase[0]) { + strcat(m_szVolumeCache, m_szBase); + } else { + strcat(m_szVolumeCache, "/"); + } + + // Cache update + m_bVolumeCache = TRUE; + + // Transfer content + strcpy(szLabel, m_szVolumeCache); +} + +//--------------------------------------------------------------------------- +// +/// Get volume label from cache +/// +/// Transfer the cached volume label information. +/// If the cache contents are valid return TRUE, if invalid return FALSE. +// +//--------------------------------------------------------------------------- +BOOL CHostDrv::GetVolumeCache(TCHAR* szLabel) const +{ + ASSERT(szLabel); + + // Transfer contents + strcpy(szLabel, m_szVolumeCache); + + return m_bVolumeCache; +} + +//--------------------------------------------------------------------------- +// +/// Get Capacity +// +//--------------------------------------------------------------------------- +DWORD CHostDrv::GetCapacity(Human68k::capacity_t* pCapacity) +{ + ASSERT(pCapacity); + ASSERT(m_bEnable); + + DWORD nFree = 0x7FFF8000; + DWORD freearea; + DWORD clusters; + DWORD sectors; + + freearea = 0xFFFF; + clusters = 0xFFFF; + sectors = 64; + + // Estimated parameter range + ASSERT(freearea <= 0xFFFF); + ASSERT(clusters <= 0xFFFF); + ASSERT(sectors <= 64); + + // Update cache + m_capCache.freearea = (WORD)freearea; + m_capCache.clusters = (WORD)clusters; + m_capCache.sectors = (WORD)sectors; + m_capCache.bytes = 512; + + // Transfer contents + memcpy(pCapacity, &m_capCache, sizeof(m_capCache)); + + return nFree; +} + +//--------------------------------------------------------------------------- +// +/// Get capacity from the cache +/// +/// Transfer the capacity data stored in cache. +/// If the contents of the cache is valid return TRUE, is invalid return FALSE. +// +//--------------------------------------------------------------------------- +BOOL CHostDrv::GetCapacityCache(Human68k::capacity_t* pCapacity) const +{ + ASSERT(pCapacity); + + // Transfer contents + memcpy(pCapacity, &m_capCache, sizeof(m_capCache)); + + return m_capCache.sectors != 0; +} + +//--------------------------------------------------------------------------- +// +/// Update all cache +// +//--------------------------------------------------------------------------- +void CHostDrv::CleanCache() +{ + Lock(); + for (CHostPath* p = (CHostPath*)m_cRing.Next(); p != &m_cRing;) { + p->Release(); + p = (CHostPath*)p->Next(); + } + Unlock(); +} + +//--------------------------------------------------------------------------- +// +/// Update the cache for the specified path +// +//--------------------------------------------------------------------------- +void CHostDrv::CleanCache(const BYTE* szHumanPath) +{ + ASSERT(szHumanPath); + + Lock(); + CHostPath* p = FindCache(szHumanPath); + if (p) { + p->Restore(); + p->Release(); + } + Unlock(); +} + +//--------------------------------------------------------------------------- +// +/// Update the cache below and including the specified path +// +//--------------------------------------------------------------------------- +void CHostDrv::CleanCacheChild(const BYTE* szHumanPath) +{ + ASSERT(szHumanPath); + + Lock(); + CHostPath* p = (CHostPath*)m_cRing.Next(); + while (p != &m_cRing) { + if (p->isSameChild(szHumanPath)) + p->Release(); + p = (CHostPath*)p->Next(); + } + Unlock(); +} + +//--------------------------------------------------------------------------- +// +/// Delete the cache for the specified path +// +//--------------------------------------------------------------------------- +void CHostDrv::DeleteCache(const BYTE* szHumanPath) +{ + ASSERT(szHumanPath); + + Lock(); + CHostPath* p = FindCache(szHumanPath); + if (p) { + delete p; + ASSERT(m_nRing); + m_nRing--; + } + Unlock(); +} + +//--------------------------------------------------------------------------- +// +/// Check if the specified path is cached +/// +/// Check if whether it is a perfect match with the cache buffer, and return the name if found. +/// File names are excempted. +/// Make sure to lock from the top. +// +//--------------------------------------------------------------------------- +CHostPath* CHostDrv::FindCache(const BYTE* szHuman) +{ + ASSERT(szHuman); + + // Find something that matches perfectly with either of the stored file names + for (CHostPath* p = (CHostPath*)m_cRing.Next(); p != &m_cRing;) { + if (p->isSameHuman(szHuman)) + return p; + p = (CHostPath*)p->Next(); + } + + return NULL; +} + +//--------------------------------------------------------------------------- +// +/// Get the host side name from cached data. +/// +/// Confirm if the path is cached. If not, throw an error. +/// Carry out an update check on found cache. If an update is needed, throw an error. +/// Make sure to lock from the top. +// +//--------------------------------------------------------------------------- +CHostPath* CHostDrv::CopyCache(CHostFiles* pFiles) +{ + ASSERT(pFiles); + ASSERT(strlen((const char*)pFiles->GetHumanPath()) < HUMAN68K_PATH_MAX); + + // Find in cache + CHostPath* pPath = FindCache(pFiles->GetHumanPath()); + if (pPath == NULL) { + return NULL; // Error: No cache + } + + // Move to the beginning of the ring + pPath->Insert(&m_cRing); + + // Cache update check + if (pPath->isRefresh()) { + return NULL; // Error: Cache update is required + } + + // Store the host side path + pFiles->SetResult(pPath->GetHost()); + + return pPath; +} + +//--------------------------------------------------------------------------- +// +/// Get all the data required for a host side name structure +/// +/// File names can be abbreviated. (Normally not selected) +/// Make sure to lock from the top. +/// Be careful not to append the path separator char to the end of the base path. +/// Initiate VM threads when there is a chance of multiple file accesses. +/// +/// How to use: +/// When CopyCache() throws an error execute MakeCache(). It ensures you get a correct host side path. +/// +/// Split all file names and path names. +/// Confirm that it's cached from the top level directory down. +/// If it's cached, do a destruction check. If it was destroyed, treat it as uncached. +/// If it isn't cached, build cache. +/// Exit after processing all directory and file names in order. +/// Make it NULL is an error is thrown. +// +//--------------------------------------------------------------------------- +CHostPath* CHostDrv::MakeCache(CHostFiles* pFiles) +{ + ASSERT(pFiles); + ASSERT(strlen((const char*)pFiles->GetHumanPath()) < HUMAN68K_PATH_MAX); + + ASSERT(m_szBase); + ASSERT(strlen(m_szBase) < FILEPATH_MAX); + + BYTE szHumanPath[HUMAN68K_PATH_MAX]; // Path names are entered in order from the route + szHumanPath[0] = '\0'; + size_t nHumanPath = 0; + + TCHAR szHostPath[FILEPATH_MAX]; + strcpy(szHostPath, m_szBase); + size_t nHostPath = strlen(szHostPath); + + CHostPath* pPath; + const BYTE* p = pFiles->GetHumanPath(); + for (;;) { + // Add path separators + if (nHumanPath + 1 >= HUMAN68K_PATH_MAX) + return NULL; // Error: The Human68k path is too long + szHumanPath[nHumanPath++] = '/'; + szHumanPath[nHumanPath] = '\0'; + if (nHostPath + 1 >= FILEPATH_MAX) + return NULL; // Error: The host side path is too long + szHostPath[nHostPath++] = _T('/'); + szHostPath[nHostPath] = _T('\0'); + + // Insert one file + BYTE szHumanFilename[24]; // File name part + p = SeparateCopyFilename(p, szHumanFilename); + if (p == NULL) + return NULL; // Error: Failed to read file name + size_t n = strlen((const char*)szHumanFilename); + if (nHumanPath + n >= HUMAN68K_PATH_MAX) + return NULL; // Error: The Human68k path is too long + + // Is the relevant path cached? + pPath = FindCache(szHumanPath); + if (pPath == NULL) { + // Check for max number of cache + if (m_nRing >= XM6_HOST_DIRENTRY_CACHE_MAX) { + // Destroy the oldest cache and reuse it + pPath = (CHostPath*)m_cRing.Prev(); + pPath->Clean(); // Release all files. Release update check handlers. + } else { + // Register new + pPath = new CHostPath; + ASSERT(pPath); + m_nRing++; + } + pPath->SetHuman(szHumanPath); + pPath->SetHost(szHostPath); + + // Update status + pPath->Refresh(); + } + + // Cache update check + if (pPath->isRefresh()) { + Update(); + + // Update status + pPath->Refresh(); + } + + // Into the beginning of the ring + pPath->Insert(&m_cRing); + + // Exit if there is not file name + if (n == 0) + break; + + // Find the next path + // Confirm if directory from the middle of the path + const CHostFilename* pFilename; + if (*p != '\0') + pFilename = pPath->FindFilename(szHumanFilename, Human68k::AT_DIRECTORY); + else + pFilename = pPath->FindFilename(szHumanFilename); + if (pFilename == NULL) + return NULL; // Error: Could not find path or file names in the middle + + // Link path name + strcpy((char*)szHumanPath + nHumanPath, (const char*)szHumanFilename); + nHumanPath += n; + + n = strlen(pFilename->GetHost()); + if (nHostPath + n >= FILEPATH_MAX) + return NULL; // Error: Host side path is too long + strcpy(szHostPath + nHostPath, pFilename->GetHost()); + nHostPath += n; + + // PLEASE CONTINUE + if (*p == '\0') + break; + } + + // Store the host side path name + pFiles->SetResult(szHostPath); + + return pPath; +} + +//--------------------------------------------------------------------------- +// +/// Find host side name (path name + file name (can be abbeviated) + attribute) +/// +/// Set all Human68k parameters once more. +// +//--------------------------------------------------------------------------- +BOOL CHostDrv::Find(CHostFiles* pFiles) +{ + ASSERT(pFiles); + + Lock(); + + // Get path name and build cache + CHostPath* pPath = CopyCache(pFiles); + if (pPath == NULL) { + pPath = MakeCache(pFiles); + if (pPath == NULL) { + Unlock(); + CleanCache(); + return FALSE; // Error: Failed to build cache + } + } + + // Store host side path + pFiles->SetResult(pPath->GetHost()); + + // Exit if only path name + if (pFiles->isPathOnly()) { + Unlock(); + return TRUE; // Normal exit: only path name + } + + // Find file name + const CHostFilename* pFilename = pFiles->Find(pPath); + if (pFilename == NULL) { + Unlock(); + return FALSE; // Error: Could not get file name + } + + // Store the Human68k side search results + pFiles->SetEntry(pFilename); + + // Store the host side full path name + pFiles->AddResult(pFilename->GetHost()); + + Unlock(); + + return TRUE; +} + +//=========================================================================== +// +// Directory entry: File name +// +//=========================================================================== + +CHostFilename::CHostFilename() +{ + m_bCorrect = FALSE; + m_pszHumanExt = FALSE; + m_pszHumanLast = FALSE; +} + +//--------------------------------------------------------------------------- +// +/// Set host side name +// +//--------------------------------------------------------------------------- +void CHostFilename::SetHost(const TCHAR* szHost) +{ + ASSERT(szHost); + ASSERT(strlen(szHost) < FILEPATH_MAX); + + strcpy(m_szHost, szHost); +} + +//--------------------------------------------------------------------------- +// +/// Copy the Human68k file name elements +// +//--------------------------------------------------------------------------- +BYTE* CHostFilename::CopyName(BYTE* pWrite, const BYTE* pFirst, const BYTE* pLast) // static +{ + ASSERT(pWrite); + ASSERT(pFirst); + ASSERT(pLast); + + for (const BYTE* p = pFirst; p < pLast; p++) { + *pWrite++ = *p; + } + + return pWrite; +} + +//--------------------------------------------------------------------------- +// +/// Convert the Human68k side name +/// +/// Once more, execute SetHost(). +/// Carry out name conversion to the 18+3 standard. +/// Automatically delete spaces in the beginning and end of the names, since Human68k can't handle them. +/// The directory entry name segment is created at the time of conversion using knowledge of the location of the file name extension. +/// Afterwards, a file name validity check is performed. (Ex. file names consisting of 8 spaces only.) +/// No file name duplication check is performed so be careful. Such validation is carried out in classes higher up. +/// Adhers to the naming standards of: TwentyOne version 1.36c modified +14 patchlevel9 or later +// +//--------------------------------------------------------------------------- +void CHostFilename::ConvertHuman(int nCount) +{ + char szHost[FILEPATH_MAX]; + + + // Don't do conversion for special directory names + if (m_szHost[0] == _T('.') && + (m_szHost[1] == _T('\0') || (m_szHost[1] == _T('.') && m_szHost[2] == _T('\0')))) { + strcpy((char*)m_szHuman, m_szHost); + + m_bCorrect = TRUE; + m_pszHumanLast = m_szHuman + strlen((const char*)m_szHuman); + m_pszHumanExt = m_pszHumanLast; + return; + } + + size_t nMax = 18; // Number of bytes for the base segment (base name and extension) + DWORD nOption = CFileSys::GetFileOption(); + if (nOption & WINDRV_OPT_CONVERT_LENGTH) + nMax = 8; + + // Preparations to adjust the base name segment + BYTE szNumber[8]; + BYTE* pNumber = NULL; + if (nCount >= 0) { + pNumber = &szNumber[8]; + for (DWORD i = 0; i < 5; i++) { // Max 5+1 digits (always leave the first 2 bytes of the base name) + int n = nCount % 36; + nMax--; + pNumber--; + *pNumber = (BYTE)(n + (n < 10 ? '0' : 'A' - 10)); + nCount /= 36; + if (nCount == 0) + break; + } + nMax--; + pNumber--; + BYTE c = (BYTE)((nOption >> 24) & 0x7F); + if (c == 0) + c = XM6_HOST_FILENAME_MARK; + *pNumber = c; + } + + // Char conversion + BYTE szHuman[FILEPATH_MAX]; + const BYTE* pFirst = szHuman; + const BYTE* pLast; + const BYTE* pExt = NULL; + + { + strcpy(szHost, m_szHost); + const BYTE* pRead = (const BYTE*)szHost; + BYTE* pWrite = szHuman; + const BYTE* pPeriod = SeparateExt(pRead); + + for (bool bFirst = true;; bFirst = false) { + BYTE c = *pRead++; + switch (c) { + case ' ': + if (nOption & WINDRV_OPT_REDUCED_SPACE) + continue; + if (nOption & WINDRV_OPT_CONVERT_SPACE) + c = '_'; + else if (pWrite == szHuman) + continue; // Ignore spaces in the beginning + break; + case '=': + case '+': + if (nOption & WINDRV_OPT_REDUCED_BADCHAR) + continue; + if (nOption & WINDRV_OPT_CONVERT_BADCHAR) + c = '_'; + break; + case '-': + if (bFirst) { + if (nOption & WINDRV_OPT_REDUCED_HYPHEN) + continue; + if (nOption & WINDRV_OPT_CONVERT_HYPHEN) + c = '_'; + break; + } + if (nOption & WINDRV_OPT_REDUCED_HYPHENS) + continue; + if (nOption & WINDRV_OPT_CONVERT_HYPHENS) + c = '_'; + break; + case '.': + if (pRead - 1 == pPeriod) { // Make exception for Human68k extensions + pExt = pWrite; + break; + } + if (bFirst) { + if (nOption & WINDRV_OPT_REDUCED_PERIOD) + continue; + if (nOption & WINDRV_OPT_CONVERT_PERIOD) + c = '_'; + break; + } + if (nOption & WINDRV_OPT_REDUCED_PERIODS) + continue; + if (nOption & WINDRV_OPT_CONVERT_PERIODS) + c = '_'; + break; + } + *pWrite++ = c; + if (c == '\0') + break; + } + + pLast = pWrite - 1; + } + + // Adjust extensions + if (pExt) { + // Delete spaces at the end + while (pExt < pLast - 1 && *(pLast - 1) == ' ') { + pLast--; + BYTE* p = (BYTE*)pLast; + *p = '\0'; + } + + // Delete if the file name disappeared after conversion + if (pExt + 1 >= pLast) { + pLast = pExt; + BYTE* p = (BYTE*)pLast; + *p = '\0'; // Just in case + } + } else { + pExt = pLast; + } + + // Introducing the cast of characters + // + // pFirst: I'm the glorious leader. The start of the file name. + // pCut: A.k.a. Phase. Location of the initial period. Afterwards becomes the end of the base name. + // pSecond: Hello there! I'm the incredible Murdock. The start of the file name extension. What's it to you? + // pExt: B.A. Baracus. The Human68k extension genius. But don't you dare giving me more than 3 chars, fool. + // The location of the final period. If not applicable, gets the same value as pLast. + // + // ↓pFirst ↓pStop ↓pSecond ← ↓pExt + // T h i s _ i s _ a . V e r y . L o n g . F i l e n a m e . t x t \0 + // ↑pCut ← ↑pCut initial location ↑pLast + // + // The above example becomes "This.Long.Filename.txt" after conversion + + // Evaluate first char + const BYTE* pCut = pFirst; + const BYTE* pStop = pExt - nMax; // Allow for up to 17 bytes for extension (leave base name) + if (pFirst < pExt) { + pCut++; // 1 byte always uses the base name + BYTE c = *pFirst; + if ((0x80 <= c && c <= 0x9F) || 0xE0 <= c) { // Specifically 0x81~0x9F 0xE0~0xEF + pCut++; // Base name. At least 2 bytes. + pStop++; // File extension. Max 16 bytes. + } + } + if (pStop < pFirst) + pStop = pFirst; + + // Evaluate base name + pCut = (BYTE*)strchr((const char*)pCut, '.'); // The 2nd byte of Shift-JIS is always 0x40 or higher, so this is ok + if (pCut == NULL) + pCut = pLast; + if ((size_t)(pCut - pFirst) > nMax) + pCut = pFirst + nMax; // Execute Shift-JIS 2 byte evaluation/adjustment later. Not allowed to do it here. + + // Evaluate extension + const BYTE* pSecond = pExt; + const BYTE* p; + for (p = pExt - 1; pStop < p; p--) { + if (*p == '.') + pSecond = p; // The 2nd byte of Shift-JIS is always 0x40 or higher, so this is ok + } + + // Shorten base name + size_t nExt = pExt - pSecond; // Length of extension segment + if ((size_t)(pCut - pFirst) + nExt > nMax) + pCut = pFirst + nMax - nExt; + // If in the middle of a 2 byte char, shorten even further + for (p = pFirst; p < pCut; p++) { + BYTE c = *p; + if ((0x80 <= c && c <= 0x9F) || 0xE0 <= c) { // Specifically 0x81~0x9F 0xE0~0xEF + p++; + if (p >= pCut) { + pCut--; + break; + } + } + } + + // Joining the name + BYTE* pWrite = m_szHuman; + pWrite = CopyName(pWrite, pFirst, pCut); // Transfer the base name + if (pNumber) + pWrite = CopyName(pWrite, pNumber, &szNumber[8]); // Transfer the adjustment char + pWrite = CopyName(pWrite, pSecond, pExt); // Transfer the extension name + m_pszHumanExt = pWrite; // Store the extention position + pWrite = CopyName(pWrite, pExt, pLast); // Transfer the Human68k extension + m_pszHumanLast = pWrite; // Store the end position + *pWrite = '\0'; + + // Confirm the conversion results + m_bCorrect = TRUE; + + // Fail if the base file name does not exist + if (m_pszHumanExt <= m_szHuman) + m_bCorrect = FALSE; + + // Fail if the base file name is more than 1 char and ends with a space + // While it is theoretically valid to have a base file name exceed 8 chars, + // Human68k is unable to handle it, so failing this case too. + else if (m_pszHumanExt[-1] == ' ') + m_bCorrect = FALSE; + + // Fail if the conversion result is the same as a special directory name + if (m_szHuman[0] == '.' && + (m_szHuman[1] == '\0' || (m_szHuman[1] == '.' && m_szHuman[2] == '\0'))) + m_bCorrect = FALSE; +} + +//--------------------------------------------------------------------------- +// +/// Human68k side name duplication +/// +/// Duplicates the file name segment data, then executes the correspoding initialization with ConvertHuman(). +// +//--------------------------------------------------------------------------- +void CHostFilename::CopyHuman(const BYTE* szHuman) +{ + ASSERT(szHuman); + ASSERT(strlen((const char*)szHuman) < 23); + + strcpy((char*)m_szHuman, (const char*)szHuman); + m_bCorrect = TRUE; + m_pszHumanLast = m_szHuman + strlen((const char*)m_szHuman); + m_pszHumanExt = (BYTE*)SeparateExt(m_szHuman); +} + +//--------------------------------------------------------------------------- +// +/// Set Human68k directory entry +/// +/// Apply the set file name to the directory entry with ConvertHuman(). +// +//--------------------------------------------------------------------------- +void CHostFilename::SetEntryName() +{ + + // Set file name + BYTE* p = m_szHuman; + size_t i; + for (i = 0; i < 8; i++) { + if (p < m_pszHumanExt) + m_dirHuman.name[i] = *p++; + else + m_dirHuman.name[i] = ' '; + } + + for (i = 0; i < 10; i++) { + if (p < m_pszHumanExt) + m_dirHuman.add[i] = *p++; + else + m_dirHuman.add[i] = '\0'; + } + + if (*p == '.') + p++; + for (i = 0; i < 3; i++) { + BYTE c = *p; + if (c) + p++; + m_dirHuman.ext[i] = c; + } +} + +//--------------------------------------------------------------------------- +// +/// Investigate if the Human68k side name has been processed +// +//--------------------------------------------------------------------------- +BOOL CHostFilename::isReduce() const +{ + + return strcmp((char *)m_szHost, (const char*)m_szHuman) != 0; +} + +//--------------------------------------------------------------------------- +// +/// Evaluate Human68k directory entry attribute +// +//--------------------------------------------------------------------------- +BOOL CHostFilename::CheckAttribute(DWORD nHumanAttribute) const +{ + + BYTE nAttribute = m_dirHuman.attr; + if ((nAttribute & (Human68k::AT_ARCHIVE | Human68k::AT_DIRECTORY | Human68k::AT_VOLUME)) == 0) + nAttribute |= Human68k::AT_ARCHIVE; + + return nAttribute & nHumanAttribute; +} + +//--------------------------------------------------------------------------- +// +/// Split the extension from Human68k file name +// +//--------------------------------------------------------------------------- +const BYTE* CHostFilename::SeparateExt(const BYTE* szHuman) // static +{ + // Obtain the file name length + size_t nLength = strlen((const char*)szHuman); + const BYTE* pFirst = szHuman; + const BYTE* pLast = pFirst + nLength; + + // Confirm the position of the Human68k extension + const BYTE* pExt = (BYTE*)strrchr((const char*)pFirst, '.'); // The 2nd byte of Shift-JIS is always 0x40 or higher, so this is ok + if (pExt == NULL) + pExt = pLast; + // Special handling of the pattern where the file name is 20~22 chars, and the 19th char is '.' or ends with '.' + if (20 <= nLength && nLength <= 22 && pFirst[18] == '.' && pFirst[nLength - 1] == '.') + pExt = pFirst + 18; + // Calculate the number of chars in the extension (-1:None 0:Only period 1~3:Human68k extension 4 or above:extension name) + size_t nExt = pLast - pExt - 1; + // Consider it an extension only when '.' is anywhere except the beginning of the string, and between 1~3 chars long + if (pExt == pFirst || nExt < 1 || nExt > 3) + pExt = pLast; + + return pExt; +} + +//=========================================================================== +// +// Directory entry: path name +// +//=========================================================================== + +DWORD CHostPath::g_nId; ///< Identifier creation counter + +CHostPath::CHostPath() +{ + m_bRefresh = TRUE; + m_nId = 0; + m_tBackup = FALSE; + +#ifdef _DEBUG + // Initialization is not required because this value always gets updated (used for debugging or initialization operation) + m_nId = 0; +#endif // _DEBUG +} + +CHostPath::~CHostPath() +{ + Clean(); +} + +//--------------------------------------------------------------------------- +// +/// File name memory allocation +/// +/// In most cases, the length of the host side file name is way shorter +/// than the size of the buffer. In addition, file names may be created in huge volumes. +/// Therefore, allocate variable lengths that correspond to the number of chars. +// +//--------------------------------------------------------------------------- +CHostPath::ring_t* CHostPath::Alloc(size_t nLength) // static +{ + ASSERT(nLength < FILEPATH_MAX); + + size_t n = offsetof(ring_t, f) + CHostFilename::Offset() + (nLength + 1) * sizeof(TCHAR); + ring_t* p = (ring_t*)malloc(n); + ASSERT(p); + + p->r.Init(); // This is nothing to worry about! + + return p; +} + +//--------------------------------------------------------------------------- +// +// Release file name allocations +// +//--------------------------------------------------------------------------- +void CHostPath::Free(ring_t* pRing) // static +{ + ASSERT(pRing); + + pRing->~ring_t(); + free(pRing); +} + +//--------------------------------------------------------------------------- +// +/// Initialize for reuse +// +//--------------------------------------------------------------------------- +void CHostPath::Clean() +{ + + Release(); + + // Release all file names + ring_t* p; + while ((p = (ring_t*)m_cRing.Next()) != (ring_t*)&m_cRing) { + Free(p); + } +} + +//--------------------------------------------------------------------------- +// +/// Specify Human68k side names directly +// +//--------------------------------------------------------------------------- +void CHostPath::SetHuman(const BYTE* szHuman) +{ + ASSERT(szHuman); + ASSERT(strlen((const char*)szHuman) < HUMAN68K_PATH_MAX); + + strcpy((char*)m_szHuman, (const char*)szHuman); +} + +//--------------------------------------------------------------------------- +// +/// Specify host side names directly +// +//--------------------------------------------------------------------------- +void CHostPath::SetHost(const TCHAR* szHost) +{ + ASSERT(szHost); + ASSERT(strlen(szHost) < FILEPATH_MAX); + + strcpy(m_szHost, szHost); +} + +//--------------------------------------------------------------------------- +// +/// Compare arrays (supports wildcards) +// +//--------------------------------------------------------------------------- +int CHostPath::Compare(const BYTE* pFirst, const BYTE* pLast, const BYTE* pBufFirst, const BYTE* pBufLast) +{ + ASSERT(pFirst); + ASSERT(pLast); + ASSERT(pBufFirst); + ASSERT(pBufLast); + + // Compare chars + BOOL bSkip0 = FALSE; + BOOL bSkip1 = FALSE; + for (const BYTE* p = pFirst; p < pLast; p++) { + // Read 1 char + BYTE c = *p; + BYTE d = '\0'; + if (pBufFirst < pBufLast) + d = *pBufFirst++; + + // Ajust char for comparison + if (bSkip0 == FALSE) { + if (bSkip1 == FALSE) { // First byte for both c and d + if ((0x80 <= c && c <= 0x9F) || 0xE0 <= c) { // Specifically 0x81~0x9F 0xE0~0xEF + bSkip0 = TRUE; + } + if ((0x80 <= d && d <= 0x9F) || 0xE0 <= d) { // Specifically 0x81~0x9F 0xE0~0xEF + bSkip1 = TRUE; + } + if (c == d) + continue; // Finishes the evaluation here with high probability + if ((CFileSys::GetFileOption() & WINDRV_OPT_ALPHABET) == 0) { + if ('A' <= c && c <= 'Z') + c += 'a' - 'A'; // To lower case + if ('A' <= d && d <= 'Z') + d += 'a' - 'A'; // To lower case + } + // Unify slashes and backslashes for comparison + if (c == '\\') { + c = '/'; + } + if (d == '\\') { + d = '/'; + } + } else { // Only c is first byte + if ((0x80 <= c && c <= 0x9F) || 0xE0 <= c) { // Specifically 0x81~0x9F 0xE0~0xEF + bSkip0 = TRUE; + } + bSkip1 = FALSE; + } + } else { + if (bSkip1 == FALSE) { // Only d is first byte + bSkip0 = FALSE; + if ((0x80 <= d && d <= 0x9F) || 0xE0 <= d) { // Specifically 0x81~0x9F 0xE0~0xEF + bSkip1 = TRUE; + } + } else { // Second byte for both c and d + bSkip0 = FALSE; + bSkip1 = FALSE; + } + } + + // Compare + if (c == d) + continue; + if (c == '?') + continue; + return 1; + } + if (pBufFirst < pBufLast) + return 2; + + return 0; +} + +//--------------------------------------------------------------------------- +// +/// Compare Human68k side name +// +//--------------------------------------------------------------------------- +BOOL CHostPath::isSameHuman(const BYTE* szHuman) const +{ + ASSERT(szHuman); + + // Calulate number of chars + size_t nLength = strlen((const char*)m_szHuman); + size_t n = strlen((const char*)szHuman); + + // Check number of chars + if (nLength != n) + return FALSE; + + // Compare Human68k path name + return Compare(m_szHuman, m_szHuman + nLength, szHuman, szHuman + n) == 0; +} + +BOOL CHostPath::isSameChild(const BYTE* szHuman) const +{ + ASSERT(szHuman); + + // Calulate number of chars + size_t nLength = strlen((const char*)m_szHuman); + size_t n = strlen((const char*)szHuman); + + // Check number of chars + if (nLength < n) + return FALSE; + + // Compare Human68k path name + return Compare(m_szHuman, m_szHuman + n, szHuman, szHuman + n) == 0; +} + +//--------------------------------------------------------------------------- +// +/// Find file name +/// +/// Check if whether it is a perfect match with the cache buffer, and return the name if found. +/// Path names are excempted. +/// Make sure to lock from the top. +// +//--------------------------------------------------------------------------- +const CHostFilename* CHostPath::FindFilename(const BYTE* szHuman, DWORD nHumanAttribute) const +{ + ASSERT(szHuman); + + // Calulate number of chars + const BYTE* pFirst = szHuman; + size_t nLength = strlen((const char*)pFirst); + const BYTE* pLast = pFirst + nLength; + + // Find something that matches perfectly with either of the stored file names + const ring_t* p = (ring_t*)m_cRing.Next(); + for (; p != (ring_t*)&m_cRing; p = (ring_t*)p->r.Next()) { + if (p->f.CheckAttribute(nHumanAttribute) == 0) + continue; + // Calulate number of chars + const BYTE* pBufFirst = p->f.GetHuman(); + const BYTE* pBufLast = p->f.GetHumanLast(); + size_t nBufLength = pBufLast - pBufFirst; + // Check number of chars + if (nLength != nBufLength) + continue; + // File name check + if (Compare(pFirst, pLast, pBufFirst, pBufLast) == 0) + return &p->f; + } + + return NULL; +} + +//--------------------------------------------------------------------------- +// +/// Find file name (with wildcard support) +/// +/// Check if whether it is a perfect match with the cache buffer, and return the name if found. +/// Path names are excempted. +/// Make sure to lock from the top. +// +//--------------------------------------------------------------------------- +const CHostFilename* CHostPath::FindFilenameWildcard(const BYTE* szHuman, DWORD nHumanAttribute, find_t* pFind) const +{ + ASSERT(szHuman); + ASSERT(pFind); + + // Split the base file name and Human68k file extension + const BYTE* pFirst = szHuman; + const BYTE* pLast = pFirst + strlen((const char*)pFirst); + const BYTE* pExt = CHostFilename::SeparateExt(pFirst); + + // Move to the start position + const ring_t* p = (ring_t*)m_cRing.Next(); + if (pFind->count > 0) { + if (pFind->id == m_nId) { + // If the same directory entry, continue right away from the previous position + p = pFind->pos; + } else { + // Find the start position in the directory entry contents + DWORD n = 0; + for (;; p = (ring_t*)p->r.Next()) { + if (p == (ring_t*)&m_cRing) { + // Extrapolate from the count when the same entry isn't found (just in case) + p = (ring_t*)m_cRing.Next(); + n = 0; + for (; p != (ring_t*)&m_cRing; p = (ring_t*)p->r.Next()) { + if (n >= pFind->count) + break; + n++; + } + break; + } + if (p->f.isSameEntry(&pFind->entry)) { + // Same entry is found + pFind->count = n; + break; + } + n++; + } + } + } + + // Find files + for (; p != (ring_t*)&m_cRing; p = (ring_t*)p->r.Next()) { + pFind->count++; + + if (p->f.CheckAttribute(nHumanAttribute) == 0) + continue; + + // Split the base file name and Human68k file extension + const BYTE* pBufFirst = p->f.GetHuman(); + const BYTE* pBufLast = p->f.GetHumanLast(); + const BYTE* pBufExt = p->f.GetHumanExt(); + + // Compare base file name + if (Compare(pFirst, pExt, pBufFirst, pBufExt)) + continue; + + // Compare Human68k extension + // In the case of a '.???' extension, match the Human68k extension without period. + if (strcmp((const char*)pExt, ".???") == 0 || + Compare(pExt, pLast, pBufExt, pBufLast) == 0) { + // Store the contents of the next candidate's directory entry + const ring_t* pNext = (ring_t*)p->r.Next(); + pFind->id = m_nId; + pFind->pos = pNext; + if (pNext != (ring_t*)&m_cRing) + memcpy(&pFind->entry, pNext->f.GetEntry(), sizeof(pFind->entry)); + else + memset(&pFind->entry, 0, sizeof(pFind->entry)); + return &p->f; + } + } + + pFind->id = m_nId; + pFind->pos = p; + memset(&pFind->entry, 0, sizeof(pFind->entry)); + return NULL; +} + +//--------------------------------------------------------------------------- +// +/// Confirm that the file update has been carried out +// +//--------------------------------------------------------------------------- +BOOL CHostPath::isRefresh() +{ + + return m_bRefresh; +} + +//--------------------------------------------------------------------------- +// +/// ASCII sort function +// +//--------------------------------------------------------------------------- +int AsciiSort(const dirent **a, const dirent **b) +{ + return strcmp((*a)->d_name, (*b)->d_name); +} + +//--------------------------------------------------------------------------- +// +/// Reconstruct the file +/// +/// Here we carry out the first host side file system observation. +/// Always lock from the top. +// +//--------------------------------------------------------------------------- +void CHostPath::Refresh() +{ + ASSERT(strlen(m_szHost) + 22 < FILEPATH_MAX); + + // Store time stamp + Backup(); + + TCHAR szPath[FILEPATH_MAX]; + strcpy(szPath, m_szHost); + + // Update refresh flag + m_bRefresh = FALSE; + + // Store previous cache contents + CRing cRingBackup; + m_cRing.InsertRing(&cRingBackup); + + // Register file name + /// TODO: Process file duplication by ourselves rather than using the host API. + BOOL bUpdate = FALSE; + struct dirent **pd = NULL; + int nument = 0; + int maxent = XM6_HOST_DIRENTRY_FILE_MAX; + for (int i = 0; i < maxent; i++) { + TCHAR szFilename[FILEPATH_MAX]; + if (pd == NULL) { + nument = scandir(S2U(szPath), &pd, NULL, AsciiSort); + if (nument == -1) { + pd = NULL; + break; + } + maxent = nument; + } + + // When at the top level directory, exclude current and parent + struct dirent* pe = pd[i]; + if (m_szHuman[0] == '/' && m_szHuman[1] == 0) { + if (strcmp(pe->d_name, ".") == 0 || strcmp(pe->d_name, "..") == 0) { + continue; + } + } + + // Get file name + strcpy(szFilename, U2S(pe->d_name)); + + // Allocate file name memory + ring_t* pRing = Alloc(strlen(szFilename)); + CHostFilename* pFilename = &pRing->f; + pFilename->SetHost(szFilename); + + // If there is a relevant file name in the previous cache, prioritize that for the Human68k name + ring_t* pCache = (ring_t*)cRingBackup.Next(); + for (;;) { + if (pCache == (ring_t*)&cRingBackup) { + pCache = NULL; // No relevant entry + bUpdate = TRUE; // Confirm new entry + pFilename->ConvertHuman(); + break; + } + if (strcmp(pFilename->GetHost(), pCache->f.GetHost()) == 0) { + pFilename->CopyHuman(pCache->f.GetHuman()); // Copy Human68k name + break; + } + pCache = (ring_t*)pCache->r.Next(); + } + + // If there is a new entry, carry out file name duplication check. + // If the host side file name changed, or if Human68k cannot express the file name, + // generate a new file name that passes all the below checks: + // - File name correctness + // - No duplicated names in previous entries + // - No entity with the same name exists + if (pFilename->isReduce() || !pFilename->isCorrect()) { // Confirm that file name update is required + for (DWORD n = 0; n < XM6_HOST_FILENAME_PATTERN_MAX; n++) { + // Confirm file name validity + if (pFilename->isCorrect()) { + // Confirm match with previous entry + const CHostFilename* pCheck = FindFilename(pFilename->GetHuman()); + if (pCheck == NULL) { + // If no match, confirm existence of real file + strcpy(szPath, m_szHost); + strcat(szPath, (const char*)pFilename->GetHuman()); + struct stat sb; + if (stat(S2U(szPath), &sb)) + break; // Discover available patterns + } + } + // Generate new name + pFilename->ConvertHuman(n); + } + } + + // Directory entry name + pFilename->SetEntryName(); + + // Get data + strcpy(szPath, m_szHost); + strcat(szPath, U2S(pe->d_name)); + + struct stat sb; + if (stat(S2U(szPath), &sb)) + continue; + + BYTE nHumanAttribute = Human68k::AT_ARCHIVE; + if (S_ISDIR(sb.st_mode)) + nHumanAttribute = Human68k::AT_DIRECTORY; + if ((sb.st_mode & 0200) == 0) + nHumanAttribute |= Human68k::AT_READONLY; + pFilename->SetEntryAttribute(nHumanAttribute); + + DWORD nHumanSize = (DWORD)sb.st_size; + pFilename->SetEntrySize(nHumanSize); + + WORD nHumanDate = 0; + WORD nHumanTime = 0; + struct tm* pt = localtime(&sb.st_mtime); + if (pt) { + nHumanDate = (WORD)(((pt->tm_year - 80) << 9) | ((pt->tm_mon + 1) << 5) | pt->tm_mday); + nHumanTime = (WORD)((pt->tm_hour << 11) | (pt->tm_min << 5) | (pt->tm_sec >> 1)); + } + pFilename->SetEntryDate(nHumanDate); + pFilename->SetEntryTime(nHumanTime); + + pFilename->SetEntryCluster(0); + + // Compare with previous cached contents + if (pCache) { + if (pCache->f.isSameEntry(pFilename->GetEntry())) { + Free(pRing); // Destroy entry that was created here + pRing = pCache; // Use previous cache + } else { + Free(pCache); // Remove from the next search target + bUpdate = TRUE; // Flag for update if no match + } + } + + // Add to end of ring + pRing->r.InsertTail(&m_cRing); + } + + // Release directory entry + if (pd) { + for (int i = 0; i < nument; i++) { + free(pd[i]); + } + free(pd); + } + + // Delete remaining cache + ring_t* p; + while ((p = (ring_t*)cRingBackup.Next()) != (ring_t*)&cRingBackup) { + bUpdate = TRUE; // Confirms the decrease in entries due to deletion + Free(p); + } + + // Update the identifier if the update has been carried out + if (bUpdate) + m_nId = ++g_nId; + // ASSERT(m_nId); +} + +//--------------------------------------------------------------------------- +// +/// Store the host side time stamp +// +//--------------------------------------------------------------------------- +void CHostPath::Backup() +{ + ASSERT(m_szHost); + ASSERT(strlen(m_szHost) < FILEPATH_MAX); + + TCHAR szPath[FILEPATH_MAX]; + strcpy(szPath, m_szHost); + size_t len = strlen(szPath); + + m_tBackup = 0; + if (len > 1) { // Don't do anything if it is the root directory + len--; + ASSERT(szPath[len] == _T('/')); + szPath[len] = _T('\0'); + struct stat sb; + if (stat(S2U(szPath), &sb) == 0) + m_tBackup = sb.st_mtime; + } +} + +//--------------------------------------------------------------------------- +// +/// Restore the host side time stamp +// +//--------------------------------------------------------------------------- +void CHostPath::Restore() const +{ + ASSERT(m_szHost); + ASSERT(strlen(m_szHost) < FILEPATH_MAX); + + TCHAR szPath[FILEPATH_MAX]; + strcpy(szPath, m_szHost); + size_t len = strlen(szPath); + + if (m_tBackup) { + ASSERT(len); + len--; + ASSERT(szPath[len] == _T('/')); + szPath[len] = _T('\0'); + + struct utimbuf ut; + ut.actime = m_tBackup; + ut.modtime = m_tBackup; + utime(szPath, &ut); + } +} + +//--------------------------------------------------------------------------- +// +/// Update +// +//--------------------------------------------------------------------------- +void CHostPath::Release() +{ + + m_bRefresh = TRUE; +} + +//=========================================================================== +// +// Manage directory entries +// +//=========================================================================== + +CHostEntry::CHostEntry() +{ + for (size_t n = 0; n < DriveMax; n++) { + m_pDrv[n] = NULL; + } + + m_nTimeout = 0; +} + +CHostEntry::~CHostEntry() +{ + Clean(); + +#ifdef _DEBUG + // Confirm object + for (size_t n = 0; n < DriveMax; n++) { + ASSERT(m_pDrv[n] == NULL); + } +#endif // _DEBUG +} + +//--------------------------------------------------------------------------- +// +/// Initialize (when the driver is installed) +// +//--------------------------------------------------------------------------- +void CHostEntry::Init() +{ + +#ifdef _DEBUG + // Confirm object + for (size_t n = 0; n < DriveMax; n++) { + ASSERT(m_pDrv[n] == NULL); + } +#endif // _DEBUG +} + +//--------------------------------------------------------------------------- +// +/// Release (at startup and reset) +// +//--------------------------------------------------------------------------- +void CHostEntry::Clean() +{ + + // Delete object + for (size_t n = 0; n < DriveMax; n++) { + delete m_pDrv[n]; + m_pDrv[n] = NULL; + } +} + +//--------------------------------------------------------------------------- +// +/// Update all cache +// +//--------------------------------------------------------------------------- +void CHostEntry::CleanCache() +{ + + for (size_t i = 0; i < DriveMax; i++) { + if (m_pDrv[i]) + m_pDrv[i]->CleanCache(); + } + + CHostPath::InitId(); +} + +//--------------------------------------------------------------------------- +// +/// Update the cache for the specified unit +// +//--------------------------------------------------------------------------- +void CHostEntry::CleanCache(DWORD nUnit) +{ + ASSERT(nUnit < DriveMax); + ASSERT(m_pDrv[nUnit]); + + m_pDrv[nUnit]->CleanCache(); +} + +//--------------------------------------------------------------------------- +// +/// Update the cache for the specified path +// +//--------------------------------------------------------------------------- +void CHostEntry::CleanCache(DWORD nUnit, const BYTE* szHumanPath) +{ + ASSERT(szHumanPath); + ASSERT(nUnit < DriveMax); + ASSERT(m_pDrv[nUnit]); + + m_pDrv[nUnit]->CleanCache(szHumanPath); +} + +//--------------------------------------------------------------------------- +// +/// Update all cache for the specified path and below +// +//--------------------------------------------------------------------------- +void CHostEntry::CleanCacheChild(DWORD nUnit, const BYTE* szHumanPath) +{ + ASSERT(szHumanPath); + ASSERT(nUnit < DriveMax); + ASSERT(m_pDrv[nUnit]); + + m_pDrv[nUnit]->CleanCacheChild(szHumanPath); +} + +//--------------------------------------------------------------------------- +// +/// Delete cache for the specified path +// +//--------------------------------------------------------------------------- +void CHostEntry::DeleteCache(DWORD nUnit, const BYTE* szHumanPath) +{ + ASSERT(szHumanPath); + ASSERT(nUnit < DriveMax); + ASSERT(m_pDrv[nUnit]); + + m_pDrv[nUnit]->DeleteCache(szHumanPath); +} + +//--------------------------------------------------------------------------- +// +/// Find host side names (path name + file name (can be abbreviated) + attribute) +// +//--------------------------------------------------------------------------- +BOOL CHostEntry::Find(DWORD nUnit, CHostFiles* pFiles) +{ + ASSERT(pFiles); + ASSERT(nUnit < DriveMax); + ASSERT(m_pDrv[nUnit]); + + return m_pDrv[nUnit]->Find(pFiles); +} + +void CHostEntry::ShellNotify(DWORD, const TCHAR*) {} + +//--------------------------------------------------------------------------- +// +/// Drive settings +// +//--------------------------------------------------------------------------- +void CHostEntry::SetDrv(DWORD nUnit, CHostDrv* pDrv) +{ + ASSERT(nUnit < DriveMax); + ASSERT(m_pDrv[nUnit] == NULL); + + m_pDrv[nUnit] = pDrv; +} + +//--------------------------------------------------------------------------- +// +/// Is it write-protected? +// +//--------------------------------------------------------------------------- +BOOL CHostEntry::isWriteProtect(DWORD nUnit) const +{ + ASSERT(nUnit < DriveMax); + ASSERT(m_pDrv[nUnit]); + + return m_pDrv[nUnit]->isWriteProtect(); +} + +//--------------------------------------------------------------------------- +// +/// Is it accessible? +// +//--------------------------------------------------------------------------- +BOOL CHostEntry::isEnable(DWORD nUnit) const +{ + ASSERT(nUnit < DriveMax); + ASSERT(m_pDrv[nUnit]); + + return m_pDrv[nUnit]->isEnable(); +} + +//--------------------------------------------------------------------------- +// +/// Media check +// +//--------------------------------------------------------------------------- +BOOL CHostEntry::isMediaOffline(DWORD nUnit) +{ + ASSERT(nUnit < DriveMax); + ASSERT(m_pDrv[nUnit]); + + return m_pDrv[nUnit]->isMediaOffline(); +} + +//--------------------------------------------------------------------------- +// +/// Get media byte +// +//--------------------------------------------------------------------------- +BYTE CHostEntry::GetMediaByte(DWORD nUnit) const +{ + ASSERT(nUnit < DriveMax); + ASSERT(m_pDrv[nUnit]); + + return m_pDrv[nUnit]->GetMediaByte(); +} + +//--------------------------------------------------------------------------- +// +/// Get drive status +// +//--------------------------------------------------------------------------- +DWORD CHostEntry::GetStatus(DWORD nUnit) const +{ + ASSERT(nUnit < DriveMax); + ASSERT(m_pDrv[nUnit]); + + return m_pDrv[nUnit]->GetStatus(); +} + +//--------------------------------------------------------------------------- +// +/// Media change check +// +//--------------------------------------------------------------------------- +BOOL CHostEntry::CheckMedia(DWORD nUnit) +{ + ASSERT(nUnit < DriveMax); + ASSERT(m_pDrv[nUnit]); + + return m_pDrv[nUnit]->CheckMedia(); +} + +//--------------------------------------------------------------------------- +// +/// Eject +// +//--------------------------------------------------------------------------- +void CHostEntry::Eject(DWORD nUnit) +{ + ASSERT(nUnit < DriveMax); + ASSERT(m_pDrv[nUnit]); + + m_pDrv[nUnit]->Eject(); +} + +//--------------------------------------------------------------------------- +// +/// Get volume label +// +//--------------------------------------------------------------------------- +void CHostEntry::GetVolume(DWORD nUnit, TCHAR* szLabel) +{ + ASSERT(nUnit < DriveMax); + ASSERT(m_pDrv[nUnit]); + + m_pDrv[nUnit]->GetVolume(szLabel); +} + +//--------------------------------------------------------------------------- +// +/// Get volume label from cache +// +//--------------------------------------------------------------------------- +BOOL CHostEntry::GetVolumeCache(DWORD nUnit, TCHAR* szLabel) const +{ + ASSERT(nUnit < DriveMax); + ASSERT(m_pDrv[nUnit]); + + return m_pDrv[nUnit]->GetVolumeCache(szLabel); +} + +//--------------------------------------------------------------------------- +// +/// Get capacity +// +//--------------------------------------------------------------------------- +DWORD CHostEntry::GetCapacity(DWORD nUnit, Human68k::capacity_t* pCapacity) +{ + ASSERT(nUnit < DriveMax); + ASSERT(m_pDrv[nUnit]); + + return m_pDrv[nUnit]->GetCapacity(pCapacity); +} + +//--------------------------------------------------------------------------- +// +/// Get cluster size from cache +// +//--------------------------------------------------------------------------- +BOOL CHostEntry::GetCapacityCache(DWORD nUnit, Human68k::capacity_t* pCapacity) const +{ + ASSERT(nUnit < DriveMax); + ASSERT(m_pDrv[nUnit]); + + return m_pDrv[nUnit]->GetCapacityCache(pCapacity); +} + +//--------------------------------------------------------------------------- +// +/// Split and copy the first element from the Human68k full path name +/// +/// Get the first element from the Human68k full path name and delete the path separator char. +/// 23 bytes is required in the buffer to write to. +/// A Human68k path always starts with a '/'. +/// Throw an error if 2 '/' appears in sequence. +/// If the array ends with a '/' treat it as an empty array and don't trow an error. +// +//--------------------------------------------------------------------------- +const BYTE* CHostDrv::SeparateCopyFilename(const BYTE* szHuman, BYTE* szBuffer) // static +{ + ASSERT(szHuman); + ASSERT(szBuffer); + + const size_t nMax = 22; + const BYTE* p = szHuman; + + BYTE c = *p++; // Read + if (c != '/' && c != '\\') + return NULL; // Error: Invalid path name + + // Insert one file + size_t i = 0; + for (;;) { + c = *p; // Read + if (c == '\0') + break; // Exit if at the end of an array (return the end position) + if (c == '/' || c == '\\') { + if (i == 0) + return NULL; // Error: Two separator chars appear in sequence + break; // Exit after reading the separator (return the char position) + } + p++; + + if (i >= nMax) + return NULL; // Error: The first byte hits the end of the buffer + szBuffer[i++] = c; // Read + + if ((0x80 <= c && c <= 0x9F) || 0xE0 <= c) { // Specifically 0x81~0x9F and 0xE0~0xEF + c = *p++; // Read + if (c < 0x40) + return NULL; // Error: Invalid Shift-JIS 2nd byte + + if (i >= nMax) + return NULL; // Error: The second byte hits the end of the buffer + szBuffer[i++] = c; // Read + } + } + szBuffer[i] = '\0'; // Read + + return p; +} + +//=========================================================================== +// +// File search processing +// +//=========================================================================== + +void CHostFiles::Init() +{ +} + +//--------------------------------------------------------------------------- +// +/// Generate path and file name internally +// +//--------------------------------------------------------------------------- +void CHostFiles::SetPath(const Human68k::namests_t* pNamests) +{ + ASSERT(pNamests); + + pNamests->GetCopyPath(m_szHumanPath); + pNamests->GetCopyFilename(m_szHumanFilename); + m_nHumanWildcard = 0; + m_nHumanAttribute = Human68k::AT_ARCHIVE; + m_findNext.Clear(); +} + +//--------------------------------------------------------------------------- +// +/// Find file on the Human68k side and create data on the host side +// +//--------------------------------------------------------------------------- +BOOL CHostFiles::Find(DWORD nUnit, CHostEntry* pEntry) +{ + ASSERT(pEntry); + + return pEntry->Find(nUnit, this); +} + +//--------------------------------------------------------------------------- +// +/// Find file name +// +//--------------------------------------------------------------------------- +const CHostFilename* CHostFiles::Find(CHostPath* pPath) +{ + ASSERT(pPath); + + if (m_nHumanWildcard) + return pPath->FindFilenameWildcard(m_szHumanFilename, m_nHumanAttribute, &m_findNext); + + return pPath->FindFilename(m_szHumanFilename, m_nHumanAttribute); +} + +//--------------------------------------------------------------------------- +// +/// Store the Human68k side search results +// +//--------------------------------------------------------------------------- +void CHostFiles::SetEntry(const CHostFilename* pFilename) +{ + ASSERT(pFilename); + + // Store Human68k directory entry + memcpy(&m_dirHuman, pFilename->GetEntry(), sizeof(m_dirHuman)); + + // Stire Human68k file name + strcpy((char*)m_szHumanResult, (const char*)pFilename->GetHuman()); +} + +//--------------------------------------------------------------------------- +// +/// Set host side name +// +//--------------------------------------------------------------------------- +void CHostFiles::SetResult(const TCHAR* szPath) +{ + ASSERT(szPath); + ASSERT(strlen(szPath) < FILEPATH_MAX); + + strcpy(m_szHostResult, szPath); +} + +//--------------------------------------------------------------------------- +// +/// Add file name to the host side name +// +//--------------------------------------------------------------------------- +void CHostFiles::AddResult(const TCHAR* szPath) +{ + ASSERT(szPath); + ASSERT(strlen(m_szHostResult) + strlen(szPath) < FILEPATH_MAX); + + strcat(m_szHostResult, szPath); +} + +//--------------------------------------------------------------------------- +// +/// Add a new Human68k file name to the host side name +// +//--------------------------------------------------------------------------- +void CHostFiles::AddFilename() +{ + ASSERT(strlen(m_szHostResult) + strlen((const char*)m_szHumanFilename) < FILEPATH_MAX); + strncat(m_szHostResult, (const char*)m_szHumanFilename, ARRAY_SIZE(m_szHumanFilename)); +} + +//=========================================================================== +// +// File search memory manager +// +//=========================================================================== + +#ifdef _DEBUG +CHostFilesManager::~CHostFilesManager() +{ + // Confirm that the entity does not exist (just in case) + ASSERT(m_cRing.Next() == &m_cRing); + ASSERT(m_cRing.Prev() == &m_cRing); +} +#endif // _DEBUG + +//--------------------------------------------------------------------------- +// +/// Initialization (when the driver is installed) +// +//--------------------------------------------------------------------------- +void CHostFilesManager::Init() +{ + + // Confirm that the entity does not exist (just in case) + ASSERT(m_cRing.Next() == &m_cRing); + ASSERT(m_cRing.Prev() == &m_cRing); + + // Allocate memory + for (DWORD i = 0; i < XM6_HOST_FILES_MAX; i++) { + ring_t* p = new ring_t; + ASSERT(p); + p->r.Insert(&m_cRing); + } +} + +//--------------------------------------------------------------------------- +// +/// Release (at startup and reset) +// +//--------------------------------------------------------------------------- +void CHostFilesManager::Clean() +{ + + // Release memory + CRing* p; + while ((p = m_cRing.Next()) != &m_cRing) { + delete (ring_t*)p; + } +} + +CHostFiles* CHostFilesManager::Alloc(DWORD nKey) +{ + ASSERT(nKey); + + // Select from the end + ring_t* p = (ring_t*)m_cRing.Prev(); + + // Move to the start of the ring + p->r.Insert(&m_cRing); + + p->f.SetKey(nKey); + + return &p->f; +} + +CHostFiles* CHostFilesManager::Search(DWORD nKey) +{ + // ASSERT(nKey); // The search key may become 0 due to DPB damage + + // Find the relevant object + ring_t* p = (ring_t*)m_cRing.Next(); + for (; p != (ring_t*)&m_cRing; p = (ring_t*)p->r.Next()) { + if (p->f.isSameKey(nKey)) { + // Move to the start of the ring + p->r.Insert(&m_cRing); + return &p->f; + } + } + + return NULL; +} + +void CHostFilesManager::Free(CHostFiles* pFiles) +{ + ASSERT(pFiles); + + // Release + pFiles->SetKey(0); + pFiles->Init(); + + // Move to the end of the ring + ring_t* p = (ring_t*)((size_t)pFiles - offsetof(ring_t, f)); + p->r.InsertTail(&m_cRing); +} + +//=========================================================================== +// +// FCB processing +// +//=========================================================================== + +void CHostFcb::Init() +{ + m_bUpdate = FALSE; + m_pFile = NULL; +} + +//--------------------------------------------------------------------------- +// +/// Set file open mode +// +//--------------------------------------------------------------------------- +BOOL CHostFcb::SetMode(DWORD nHumanMode) +{ + switch (nHumanMode & Human68k::OP_MASK) { + case Human68k::OP_READ: + m_pszMode = "rb"; + break; + case Human68k::OP_WRITE: + m_pszMode = "wb"; + break; + case Human68k::OP_FULL: + m_pszMode = "r+b"; + break; + default: + return FALSE; + } + + m_bFlag = (nHumanMode & Human68k::OP_SPECIAL) != 0; + + return TRUE; +} + +void CHostFcb::SetFilename(const TCHAR* szFilename) +{ + ASSERT(szFilename); + ASSERT(strlen(szFilename) < FILEPATH_MAX); + + strcpy(m_szFilename, szFilename); +} + +void CHostFcb::SetHumanPath(const BYTE* szHumanPath) +{ + ASSERT(szHumanPath); + ASSERT(strlen((const char*)szHumanPath) < HUMAN68K_PATH_MAX); + + strcpy((char*)m_szHumanPath, (const char*)szHumanPath); +} + +//--------------------------------------------------------------------------- +// +/// Create file +/// +/// Return FALSE if error is thrown. +// +//--------------------------------------------------------------------------- +BOOL CHostFcb::Create(Human68k::fcb_t* pFcb, DWORD nHumanAttribute, BOOL bForce) +{ + ASSERT((nHumanAttribute & (Human68k::AT_DIRECTORY | Human68k::AT_VOLUME)) == 0); + ASSERT(strlen(m_szFilename) > 0); + ASSERT(m_pFile == NULL); + + // Duplication check + if (bForce == FALSE) { + struct stat sb; + if (stat(S2U(m_szFilename), &sb) == 0) + return FALSE; + } + + // Create file + m_pFile = fopen(S2U(m_szFilename), "w+b"); /// @warning The ideal operation is to overwrite each attribute + + return m_pFile != NULL; +} + +//--------------------------------------------------------------------------- +// +/// File open +/// +/// Return FALSE if error is thrown. +// +//--------------------------------------------------------------------------- +BOOL CHostFcb::Open() +{ + struct stat st; + + ASSERT(strlen(m_szFilename) > 0); + + // Fail if directory + if (stat(S2U(m_szFilename), &st) == 0) { + if ((st.st_mode & S_IFMT) == S_IFDIR) { + return FALSE || m_bFlag; + } + } + + // File open + if (m_pFile == NULL) + m_pFile = fopen(S2U(m_szFilename), m_pszMode); + + return m_pFile != NULL || m_bFlag; +} + +//--------------------------------------------------------------------------- +// +/// File seek +/// +/// Return FALSE if error is thrown. +// +//--------------------------------------------------------------------------- +BOOL CHostFcb::Rewind(DWORD nOffset) +{ + ASSERT(m_pFile); + + if (fseek(m_pFile, nOffset, SEEK_SET)) + return FALSE; + + return ftell(m_pFile) != -1L; +} + +//--------------------------------------------------------------------------- +// +/// Read file +/// +/// Handle a 0 byte read as normal operation too. +/// Return -1 if error is thrown. +// +//--------------------------------------------------------------------------- +DWORD CHostFcb::Read(BYTE* pBuffer, DWORD nSize) +{ + ASSERT(pBuffer); + ASSERT(m_pFile); + + size_t nResult = fread(pBuffer, sizeof(BYTE), nSize, m_pFile); + if (ferror(m_pFile)) + nResult = (size_t)-1; + + return (DWORD)nResult; +} + +//--------------------------------------------------------------------------- +// +/// Write file +/// +/// Handle a 0 byte read as normal operation too. +/// Return -1 if error is thrown. +// +//--------------------------------------------------------------------------- +DWORD CHostFcb::Write(const BYTE* pBuffer, DWORD nSize) +{ + ASSERT(pBuffer); + ASSERT(m_pFile); + + size_t nResult = fwrite(pBuffer, sizeof(BYTE), nSize, m_pFile); + if (ferror(m_pFile)) + nResult = (size_t)-1; + + return (DWORD)nResult; +} + +//--------------------------------------------------------------------------- +// +/// Truncate file +/// +/// Return FALSE if error is thrown. +// +//--------------------------------------------------------------------------- +BOOL CHostFcb::Truncate() +{ + ASSERT(m_pFile); + + return ftruncate(fileno(m_pFile), ftell(m_pFile)) == 0; +} + +//--------------------------------------------------------------------------- +// +/// File seek +/// +/// Return -1 if error is thrown. +// +//--------------------------------------------------------------------------- +DWORD CHostFcb::Seek(DWORD nOffset, DWORD nHumanSeek) +{ + ASSERT(nHumanSeek == Human68k::SK_BEGIN || + nHumanSeek == Human68k::SK_CURRENT || nHumanSeek == Human68k::SK_END); + ASSERT(m_pFile); + + int nSeek; + switch (nHumanSeek) { + case Human68k::SK_BEGIN: + nSeek = SEEK_SET; + break; + case Human68k::SK_CURRENT: + nSeek = SEEK_CUR; + break; + // case SK_END: + default: + nSeek = SEEK_END; + break; + } + if (fseek(m_pFile, nOffset, nSeek)) + return (DWORD)-1; + + return (DWORD)ftell(m_pFile); +} + +//--------------------------------------------------------------------------- +// +/// Set file time stamp +/// +/// Return FALSE if error is thrown. +// +//--------------------------------------------------------------------------- +BOOL CHostFcb::TimeStamp(DWORD nHumanTime) +{ + ASSERT(m_pFile || m_bFlag); + + struct tm t = { 0 }; + t.tm_year = (nHumanTime >> 25) + 80; + t.tm_mon = ((nHumanTime >> 21) - 1) & 15; + t.tm_mday = (nHumanTime >> 16) & 31; + t.tm_hour = (nHumanTime >> 11) & 31; + t.tm_min = (nHumanTime >> 5) & 63; + t.tm_sec = (nHumanTime & 31) << 1; + time_t ti = mktime(&t); + if (ti == (time_t)-1) + return FALSE; + struct utimbuf ut; + ut.actime = ti; + ut.modtime = ti; + + // This is for preventing the last updated time stamp to be overwritten upon closing. + // Flush and synchronize before updating the time stamp. + fflush(m_pFile); + + return utime(S2U(m_szFilename), &ut) == 0 || m_bFlag; +} + +//--------------------------------------------------------------------------- +// +/// File close +/// +/// Return FALSE if error is thrown. +// +//--------------------------------------------------------------------------- +BOOL CHostFcb::Close() +{ + BOOL bResult = TRUE; + + // File close + // Always initialize because of the Close→Free (internally one more Close) flow. + if (m_pFile) { + fclose(m_pFile); + m_pFile = NULL; + } + + return bResult; +} + +//=========================================================================== +// +// FCB processing manager +// +//=========================================================================== + +#ifdef _DEBUG +CHostFcbManager::~CHostFcbManager() +{ + // Confirm that the entity does not exist (just in case) + ASSERT(m_cRing.Next() == &m_cRing); + ASSERT(m_cRing.Prev() == &m_cRing); +} +#endif // _DEBUG + +//--------------------------------------------------------------------------- +// +// Initialization (when the driver is installed) +// +//--------------------------------------------------------------------------- +void CHostFcbManager::Init() +{ + + // Confirm that the entity does not exist (just in case) + ASSERT(m_cRing.Next() == &m_cRing); + ASSERT(m_cRing.Prev() == &m_cRing); + + // Memory allocation + for (DWORD i = 0; i < XM6_HOST_FCB_MAX; i++) { + ring_t* p = new ring_t; + ASSERT(p); + p->r.Insert(&m_cRing); + } +} + +//--------------------------------------------------------------------------- +// +// Clean (at startup/reset) +// +//--------------------------------------------------------------------------- +void CHostFcbManager::Clean() +{ + + // Fast task killer + CRing* p; + while ((p = m_cRing.Next()) != &m_cRing) { + delete (ring_t*)p; + } +} + +CHostFcb* CHostFcbManager::Alloc(DWORD nKey) +{ + ASSERT(nKey); + + // Select from the end + ring_t* p = (ring_t*)m_cRing.Prev(); + + // Error if in use (just in case) + if (p->f.isSameKey(0) == FALSE) { + ASSERT(0); + return NULL; + } + + // Move to the top of the ring + p->r.Insert(&m_cRing); + + // Set key + p->f.SetKey(nKey); + + return &p->f; +} + +CHostFcb* CHostFcbManager::Search(DWORD nKey) +{ + ASSERT(nKey); + + // Search for applicable objects + ring_t* p = (ring_t*)m_cRing.Next(); + while (p != (ring_t*)&m_cRing) { + if (p->f.isSameKey(nKey)) { + // Move to the top of the ring + p->r.Insert(&m_cRing); + return &p->f; + } + p = (ring_t*)p->r.Next(); + } + + return NULL; +} + +void CHostFcbManager::Free(CHostFcb* pFcb) +{ + ASSERT(pFcb); + + // Free + pFcb->SetKey(0); + pFcb->Close(); + + // Move to the end of the ring + ring_t* p = (ring_t*)((size_t)pFcb - offsetof(ring_t, f)); + p->r.InsertTail(&m_cRing); +} + +//=========================================================================== +// +// Host side file system +// +//=========================================================================== + +DWORD CFileSys::g_nOption; // File name conversion flag + +CFileSys::CFileSys() +{ + m_nHostSectorCount = 0; + + // Config data initialization + m_nDrives = 0; + + for (size_t n = 0; n < DriveMax; n++) { + m_nFlag[n] = 0; + m_szBase[n][0] = _T('\0'); + } + + // Initialize TwentyOne option monitoring + m_nKernel = 0; + m_nKernelSearch = 0; + + // Initialize operational flags + m_nOptionDefault = 0; + m_nOption = 0; + ASSERT(g_nOption == 0); + + // Number of registered drives are 0 + m_nUnits = 0; +} + +//--------------------------------------------------------------------------- +// +/// Reset (close all) +// +//--------------------------------------------------------------------------- +void CFileSys::Reset() +{ + + // Initialize virtual sectors + m_nHostSectorCount = 0; + memset(m_nHostSectorBuffer, 0, sizeof(m_nHostSectorBuffer)); + + // File search memory - release (on startup and reset) + m_cFiles.Clean(); + + // FCB operation memory (on startup and reset) + m_cFcb.Clean(); + + // Directory entry - release (on startup and reset) + m_cEntry.Clean(); + + // Initialize TwentyOne option monitoring + m_nKernel = 0; + m_nKernelSearch = 0; + + // Initialize operational flags + SetOption(m_nOptionDefault); +} + +//--------------------------------------------------------------------------- +// +/// Initialize (device startup and load) +// +//--------------------------------------------------------------------------- +void CFileSys::Init() +{ + + // Initialize file search memory (device startup and load) + m_cFiles.Init(); + + // Initialize FCB operation memory (device startup and load) + m_cFcb.Init(); + + // Initialize directory entries (device startup and load) + m_cEntry.Init(); + + // Evaluate per-path setting validity + DWORD nDrives = m_nDrives; + if (nDrives == 0) { + // Use root directory instead of per-path settings + strcpy(m_szBase[0], _T("/")); + m_nFlag[0] = 0; + nDrives++; + } + + // Register file system + DWORD nUnit = 0; + for (DWORD n = 0; n < nDrives; n++) { + // Don't register is base path do not exist + if (m_szBase[n][0] == _T('\0')) + continue; + + // Create 1 unit file system + CHostDrv* p = new CHostDrv; // std::nothrow + if (p) { + m_cEntry.SetDrv(nUnit, p); + p->Init(m_szBase[n], m_nFlag[n]); + + // To the next unit + nUnit++; + } + } + + // Store the registered number of drives + m_nUnits = nUnit; +} + +//--------------------------------------------------------------------------- +// +/// $40 - Device startup +// +//--------------------------------------------------------------------------- +DWORD CFileSys::InitDevice(const Human68k::argument_t* pArgument) +{ + + InitOption(pArgument); + + // File system initialization + Init(); + + return m_nUnits; +} + +//--------------------------------------------------------------------------- +// +/// $41 - Directory check +// +//--------------------------------------------------------------------------- +int CFileSys::CheckDir(DWORD nUnit, const Human68k::namests_t* pNamests) +{ + ASSERT(pNamests); + + // Unit check + if (nUnit >= DriveMax) + return FS_FATAL_INVALIDUNIT; + if (nUnit >= m_nUnits) + return FS_INVALIDFUNC; // Avoid triggering a fatal error in mint when resuming with an invalid drive + + // Generate path name + CHostFiles f; + f.SetPath(pNamests); + if (f.isRootPath()) + return 0; + f.SetPathOnly(); + if (f.Find(nUnit, &m_cEntry) == FALSE) + return FS_DIRNOTFND; + + return 0; +} + +//--------------------------------------------------------------------------- +// +/// $42 - Create directory +// +//--------------------------------------------------------------------------- +int CFileSys::MakeDir(DWORD nUnit, const Human68k::namests_t* pNamests) +{ + ASSERT(pNamests); + + // Unit check + if (nUnit >= DriveMax) + return FS_FATAL_INVALIDUNIT; + ASSERT(nUnit < m_nUnits); + if (nUnit >= m_nUnits) + return FS_FATAL_MEDIAOFFLINE; // Just in case + + // Media check + if (m_cEntry.isMediaOffline(nUnit)) + return FS_FATAL_MEDIAOFFLINE; + + // Write-protect check + if (m_cEntry.isWriteProtect(nUnit)) + return FS_FATAL_WRITEPROTECT; + + // Generate path name + CHostFiles f; + f.SetPath(pNamests); + f.SetPathOnly(); + if (f.Find(nUnit, &m_cEntry) == FALSE) + return FS_INVALIDPATH; + f.AddFilename(); + + // Create directory + if (mkdir(S2U(f.GetPath()), 0777)) + return FS_INVALIDPATH; + + // Update cache + m_cEntry.CleanCache(nUnit, f.GetHumanPath()); + + return 0; +} + +//--------------------------------------------------------------------------- +// +/// $43 - Delete directory +// +//--------------------------------------------------------------------------- +int CFileSys::RemoveDir(DWORD nUnit, const Human68k::namests_t* pNamests) +{ + ASSERT(pNamests); + + // Unit check + if (nUnit >= DriveMax) + return FS_FATAL_INVALIDUNIT; + ASSERT(nUnit < m_nUnits); + if (nUnit >= m_nUnits) + return FS_FATAL_MEDIAOFFLINE; // Just in case + + // Media check + if (m_cEntry.isMediaOffline(nUnit)) + return FS_FATAL_MEDIAOFFLINE; + + // Write-protect check + if (m_cEntry.isWriteProtect(nUnit)) + return FS_FATAL_WRITEPROTECT; + + // Generate path name + CHostFiles f; + f.SetPath(pNamests); + f.SetAttribute(Human68k::AT_DIRECTORY); + if (f.Find(nUnit, &m_cEntry) == FALSE) + return FS_DIRNOTFND; + + // Delete cache + BYTE szHuman[HUMAN68K_PATH_MAX + 24]; + ASSERT(strlen((const char*)f.GetHumanPath()) + + strlen((const char*)f.GetHumanFilename()) < HUMAN68K_PATH_MAX + 24); + strcpy((char*)szHuman, (const char*)f.GetHumanPath()); + strcat((char*)szHuman, (const char*)f.GetHumanFilename()); + strcat((char*)szHuman, "/"); + m_cEntry.DeleteCache(nUnit, szHuman); + + // Delete directory + if (rmdir(S2U(f.GetPath()))) + return FS_CANTDELETE; + + // Update cache + m_cEntry.CleanCache(nUnit, f.GetHumanPath()); + + return 0; +} + +//--------------------------------------------------------------------------- +// +/// $44 - Change file name +// +//--------------------------------------------------------------------------- +int CFileSys::Rename(DWORD nUnit, const Human68k::namests_t* pNamests, const Human68k::namests_t* pNamestsNew) +{ + ASSERT(pNamests); + + // Unit check + if (nUnit >= DriveMax) + return FS_FATAL_INVALIDUNIT; + ASSERT(nUnit < m_nUnits); + if (nUnit >= m_nUnits) + return FS_FATAL_MEDIAOFFLINE; // Just in case + + // Media check + if (m_cEntry.isMediaOffline(nUnit)) + return FS_FATAL_MEDIAOFFLINE; + + // Write-protect check + if (m_cEntry.isWriteProtect(nUnit)) + return FS_FATAL_WRITEPROTECT; + + // Generate path name + CHostFiles f; + f.SetPath(pNamests); + f.SetAttribute(Human68k::AT_ALL); + if (f.Find(nUnit, &m_cEntry) == FALSE) + return FS_FILENOTFND; + + CHostFiles fNew; + fNew.SetPath(pNamestsNew); + fNew.SetPathOnly(); + if (fNew.Find(nUnit, &m_cEntry) == FALSE) + return FS_INVALIDPATH; + fNew.AddFilename(); + + // Update cache + if (f.GetAttribute() & Human68k::AT_DIRECTORY) + m_cEntry.CleanCacheChild(nUnit, f.GetHumanPath()); + + // Change file name + char szFrom[FILENAME_MAX]; + char szTo[FILENAME_MAX]; + SJIS2UTF8(f.GetPath(), szFrom, FILENAME_MAX); + SJIS2UTF8(fNew.GetPath(), szTo, FILENAME_MAX); + if (rename(szFrom, szTo)) { + return FS_FILENOTFND; + } + + // Update cache + m_cEntry.CleanCache(nUnit, f.GetHumanPath()); + m_cEntry.CleanCache(nUnit, fNew.GetHumanPath()); + + return 0; +} + +//--------------------------------------------------------------------------- +// +/// $45 - Delete file +// +//--------------------------------------------------------------------------- +int CFileSys::Delete(DWORD nUnit, const Human68k::namests_t* pNamests) +{ + ASSERT(pNamests); + + // Unit check + if (nUnit >= DriveMax) + return FS_FATAL_INVALIDUNIT; + ASSERT(nUnit < m_nUnits); + if (nUnit >= m_nUnits) + return FS_FATAL_MEDIAOFFLINE; // Just in case + + // Media check + if (m_cEntry.isMediaOffline(nUnit)) + return FS_FATAL_MEDIAOFFLINE; + + // Write-protect check + if (m_cEntry.isWriteProtect(nUnit)) + return FS_FATAL_WRITEPROTECT; + + // Generate path name + CHostFiles f; + f.SetPath(pNamests); + if (f.Find(nUnit, &m_cEntry) == FALSE) + return FS_FILENOTFND; + + // Delete file + if (unlink(S2U(f.GetPath()))) + return FS_CANTDELETE; + + // Update cache + m_cEntry.CleanCache(nUnit, f.GetHumanPath()); + + return 0; +} + +//--------------------------------------------------------------------------- +// +/// $46 - Get/set file attribute +// +//--------------------------------------------------------------------------- +int CFileSys::Attribute(DWORD nUnit, const Human68k::namests_t* pNamests, DWORD nHumanAttribute) +{ + ASSERT(pNamests); + + // Unit check + if (nUnit >= DriveMax) + return FS_FATAL_INVALIDUNIT; + if (nUnit >= m_nUnits) + return FS_FATAL_MEDIAOFFLINE; // This occurs when resuming with an invalid drive + + // Media check + if (m_cEntry.isMediaOffline(nUnit)) + return FS_FATAL_MEDIAOFFLINE; + + // Generate path name + CHostFiles f; + f.SetPath(pNamests); + f.SetAttribute(Human68k::AT_ALL); + if (f.Find(nUnit, &m_cEntry) == FALSE) + return FS_FILENOTFND; + + // Exit if attribute is acquired + if (nHumanAttribute == 0xFF) + return f.GetAttribute(); + + // Attribute check + if (nHumanAttribute & Human68k::AT_VOLUME) + return FS_CANTACCESS; + + // Write-protect check + if (m_cEntry.isWriteProtect(nUnit)) + return FS_FATAL_WRITEPROTECT; + + // Generate attribute + DWORD nAttribute = (nHumanAttribute & Human68k::AT_READONLY) | + (f.GetAttribute() & ~Human68k::AT_READONLY); + if (f.GetAttribute() != nAttribute) { + struct stat sb; + if (stat(S2U(f.GetPath()), &sb)) + return FS_FILENOTFND; + mode_t m = sb.st_mode & 0777; + if (nAttribute & Human68k::AT_READONLY) + m &= 0555; // ugo-w + else + m |= 0200; // u+w + + // Set attribute + if (chmod(S2U(f.GetPath()), m)) + return FS_FILENOTFND; + } + + // Update cache + m_cEntry.CleanCache(nUnit, f.GetHumanPath()); + + // Get attribute after changing + if (f.Find(nUnit, &m_cEntry) == FALSE) + return FS_FILENOTFND; + + return f.GetAttribute(); +} + +//--------------------------------------------------------------------------- +// +/// $47 - File search +// +//--------------------------------------------------------------------------- +int CFileSys::Files(DWORD nUnit, DWORD nKey, const Human68k::namests_t* pNamests, Human68k::files_t* pFiles) +{ + ASSERT(pNamests); + ASSERT(nKey); + ASSERT(pFiles); + + // Release if memory with the same key already exists + CHostFiles* pHostFiles = m_cFiles.Search(nKey); + if (pHostFiles != NULL) { + m_cFiles.Free(pHostFiles); + } + + // Unit check + if (nUnit >= DriveMax) + return FS_FATAL_INVALIDUNIT; + if (nUnit >= m_nUnits) + return FS_FATAL_MEDIAOFFLINE; // This occurs when resuming with an invalid drive + + // Get volume label + /** @note + This skips the media check when getting the volume label to avoid triggering a fatal error + with host side removable media (CD-ROM, etc.) Some poorly coded applications will attempt to + get the volume label even though a proper error was thrown doing a media change check just before. + */ + if ((pFiles->fatr & (Human68k::AT_ARCHIVE | Human68k::AT_DIRECTORY | Human68k::AT_VOLUME)) + == Human68k::AT_VOLUME) { + // Path check + CHostFiles f; + f.SetPath(pNamests); + if (f.isRootPath() == FALSE) + return FS_FILENOTFND; + + // Immediately return the results without allocating buffer + if (FilesVolume(nUnit, pFiles) == FALSE) + return FS_FILENOTFND; + return 0; + } + + // Media check + if (m_cEntry.isMediaOffline(nUnit)) + return FS_FATAL_MEDIAOFFLINE; + + // Allocate buffer + pHostFiles = m_cFiles.Alloc(nKey); + if (pHostFiles == NULL) + return FS_OUTOFMEM; + + // Directory check + pHostFiles->SetPath(pNamests); + if (pHostFiles->isRootPath() == FALSE) { + pHostFiles->SetPathOnly(); + if (pHostFiles->Find(nUnit, &m_cEntry) == FALSE) { + m_cFiles.Free(pHostFiles); + return FS_DIRNOTFND; + } + } + + // Enable wildcards + pHostFiles->SetPathWildcard(); + pHostFiles->SetAttribute(pFiles->fatr); + + // Find file + if (pHostFiles->Find(nUnit, &m_cEntry) == FALSE) { + m_cFiles.Free(pHostFiles); + return FS_FILENOTFND; + } + + // Store search results + pFiles->attr = (BYTE)pHostFiles->GetAttribute(); + pFiles->date = pHostFiles->GetDate(); + pFiles->time = pHostFiles->GetTime(); + pFiles->size = pHostFiles->GetSize(); + strcpy((char*)pFiles->full, (const char*)pHostFiles->GetHumanResult()); + + // Specify pseudo-directory entry + pFiles->sector = nKey; + pFiles->offset = 0; + + // When the file name does not include wildcards, the buffer may be released + if (pNamests->wildcard == 0) { + // However, there is a chance the virtual selector may be used for emulation, so don't release immediately + // m_cFiles.Free(pHostFiles); + } + + return 0; +} + +//--------------------------------------------------------------------------- +// +/// $48 - Search next file +// +//--------------------------------------------------------------------------- +int CFileSys::NFiles(DWORD nUnit, DWORD nKey, Human68k::files_t* pFiles) +{ + ASSERT(nKey); + ASSERT(pFiles); + + // Unit check + if (nUnit >= DriveMax) + return FS_FATAL_INVALIDUNIT; + if (nUnit >= m_nUnits) + return FS_FATAL_MEDIAOFFLINE; // This occurs when resuming with an invalid drive + + // Media check + if (m_cEntry.isMediaOffline(nUnit)) + return FS_FATAL_MEDIAOFFLINE; + + // Find buffer + CHostFiles* pHostFiles = m_cFiles.Search(nKey); + if (pHostFiles == NULL) + return FS_INVALIDPTR; + + // Find file + if (pHostFiles->Find(nUnit, &m_cEntry) == FALSE) { + m_cFiles.Free(pHostFiles); + return FS_FILENOTFND; + } + + ASSERT(pFiles->sector == nKey); + ASSERT(pFiles->offset == 0); + + // Store search results + pFiles->attr = (BYTE)pHostFiles->GetAttribute(); + pFiles->date = pHostFiles->GetDate(); + pFiles->time = pHostFiles->GetTime(); + pFiles->size = pHostFiles->GetSize(); + strcpy((char*)pFiles->full, (const char*)pHostFiles->GetHumanResult()); + + return 0; +} + +//--------------------------------------------------------------------------- +// +/// $49 - Create new file +// +//--------------------------------------------------------------------------- +int CFileSys::Create(DWORD nUnit, DWORD nKey, const Human68k::namests_t* pNamests, Human68k::fcb_t* pFcb, DWORD nHumanAttribute, BOOL bForce) +{ + ASSERT(pNamests); + ASSERT(nKey); + ASSERT(pFcb); + + // Unit check + if (nUnit >= DriveMax) + return FS_FATAL_INVALIDUNIT; + ASSERT(nUnit < m_nUnits); + if (nUnit >= m_nUnits) + return FS_FATAL_MEDIAOFFLINE; // Just in case + + // Media check + if (m_cEntry.isMediaOffline(nUnit)) + return FS_FATAL_MEDIAOFFLINE; + + // Write-protect check + if (m_cEntry.isWriteProtect(nUnit)) + return FS_FATAL_WRITEPROTECT; + + // Release if memory with the same key already exists + if (m_cFcb.Search(nKey) != NULL) + return FS_INVALIDPTR; + + // Generate path name + CHostFiles f; + f.SetPath(pNamests); + f.SetPathOnly(); + if (f.Find(nUnit, &m_cEntry) == FALSE) + return FS_INVALIDPATH; + f.AddFilename(); + + // Attribute check + if (nHumanAttribute & (Human68k::AT_DIRECTORY | Human68k::AT_VOLUME)) + return FS_CANTACCESS; + + // Store path name + CHostFcb* pHostFcb = m_cFcb.Alloc(nKey); + if (pHostFcb == NULL) + return FS_OUTOFMEM; + pHostFcb->SetFilename(f.GetPath()); + pHostFcb->SetHumanPath(f.GetHumanPath()); + + // Set open mode + pFcb->mode = (WORD)((pFcb->mode & ~Human68k::OP_MASK) | Human68k::OP_FULL); + if (pHostFcb->SetMode(pFcb->mode) == FALSE) { + m_cFcb.Free(pHostFcb); + return FS_ILLEGALMOD; + } + + // Create file + if (pHostFcb->Create(pFcb, nHumanAttribute, bForce) == FALSE) { + m_cFcb.Free(pHostFcb); + return FS_FILEEXIST; + } + + // Update cache + m_cEntry.CleanCache(nUnit, f.GetHumanPath()); + + return 0; +} + +//--------------------------------------------------------------------------- +// +/// $4A - File open +// +//--------------------------------------------------------------------------- +int CFileSys::Open(DWORD nUnit, DWORD nKey, const Human68k::namests_t* pNamests, Human68k::fcb_t* pFcb) +{ + ASSERT(pNamests); + ASSERT(nKey); + ASSERT(pFcb); + + // Unit check + if (nUnit >= DriveMax) + return FS_FATAL_INVALIDUNIT; + if (nUnit >= m_nUnits) + return FS_FATAL_MEDIAOFFLINE; // This occurs when resuming with an invalid drive + + // Media check + if (m_cEntry.isMediaOffline(nUnit)) + return FS_FATAL_MEDIAOFFLINE; + + // Write-protect check + switch (pFcb->mode & Human68k::OP_MASK) { + case Human68k::OP_WRITE: + case Human68k::OP_FULL: + if (m_cEntry.isWriteProtect(nUnit)) + return FS_FATAL_WRITEPROTECT; + } + + // Release if memory with the same key already exists + if (m_cFcb.Search(nKey) != NULL) + return FS_INVALIDPRM; + + // Generate path name + CHostFiles f; + f.SetPath(pNamests); + f.SetAttribute(Human68k::AT_ALL); + if (f.Find(nUnit, &m_cEntry) == FALSE) + return FS_FILENOTFND; + + // Time stamp + pFcb->date = f.GetDate(); + pFcb->time = f.GetTime(); + + // File size + pFcb->size = f.GetSize(); + + // Store path name + CHostFcb* pHostFcb = m_cFcb.Alloc(nKey); + if (pHostFcb == NULL) + return FS_OUTOFMEM; + pHostFcb->SetFilename(f.GetPath()); + pHostFcb->SetHumanPath(f.GetHumanPath()); + + // Set open mode + if (pHostFcb->SetMode(pFcb->mode) == FALSE) { + m_cFcb.Free(pHostFcb); + return FS_ILLEGALMOD; + } + + // File open + if (pHostFcb->Open() == FALSE) { + m_cFcb.Free(pHostFcb); + return FS_INVALIDPATH; + } + + return 0; +} + +//--------------------------------------------------------------------------- +// +/// $4B - File close +// +//--------------------------------------------------------------------------- +int CFileSys::Close(DWORD nUnit, DWORD nKey, Human68k::fcb_t* /* pFcb */) +{ + ASSERT(nKey); + + // Unit check + if (nUnit >= DriveMax) + return FS_FATAL_INVALIDUNIT; + if (nUnit >= m_nUnits) + return FS_FATAL_MEDIAOFFLINE; // This occurs when resuming with an invalid drive + + // Media check + if (m_cEntry.isMediaOffline(nUnit)) + return FS_FATAL_MEDIAOFFLINE; + + // Throw error if memory with the same key does not exist + CHostFcb* pHostFcb = m_cFcb.Search(nKey); + if (pHostFcb == NULL) + return FS_INVALIDPRM; + + // File close and release memory + m_cFcb.Free(pHostFcb); + + // Update cache + if (pHostFcb->isUpdate()) + m_cEntry.CleanCache(nUnit, pHostFcb->GetHumanPath()); + + /// TODO: Match the FCB status on close with other devices + + return 0; +} + +//--------------------------------------------------------------------------- +// +/// $4C - Read file +/// +/// Clean exit when 0 bytes are read. +// +//--------------------------------------------------------------------------- +int CFileSys::Read(DWORD nKey, Human68k::fcb_t* pFcb, BYTE* pBuffer, DWORD nSize) +{ + ASSERT(nKey); + ASSERT(pFcb); + // ASSERT(pBuffer); // Evaluate only when needed + ASSERT(nSize <= 0xFFFFFF); // Clipped + + // Throw error if memory with the same key does not exist + CHostFcb* pHostFcb = m_cFcb.Search(nKey); + if (pHostFcb == NULL) + return FS_NOTOPENED; + + // Confirm the existence of the buffer + if (pBuffer == NULL) { + m_cFcb.Free(pHostFcb); + return FS_INVALIDFUNC; + } + + // Read + DWORD nResult; + nResult = pHostFcb->Read(pBuffer, nSize); + if (nResult == (DWORD)-1) { + m_cFcb.Free(pHostFcb); + return FS_INVALIDFUNC; // TODO: Should return error code 10 (read error) as well here + } + ASSERT(nResult <= nSize); + + // Update file pointer + pFcb->fileptr += nResult; /// TODO: Maybe an overflow check is needed here? + + return nResult; +} + +//--------------------------------------------------------------------------- +// +/// $4D - Write file +/// +/// Truncate file if 0 bytes are written. +// +//--------------------------------------------------------------------------- +int CFileSys::Write(DWORD nKey, Human68k::fcb_t* pFcb, const BYTE* pBuffer, DWORD nSize) +{ + ASSERT(nKey); + ASSERT(pFcb); + // ASSERT(pBuffer); // Evaluate only when needed + ASSERT(nSize <= 0xFFFFFF); // Clipped + + // Throw error if memory with the same key does not exist + CHostFcb* pHostFcb = m_cFcb.Search(nKey); + if (pHostFcb == NULL) + return FS_NOTOPENED; + + DWORD nResult; + if (nSize == 0) { + // Truncate + if (pHostFcb->Truncate() == FALSE) { + m_cFcb.Free(pHostFcb); + return FS_CANTSEEK; + } + + // Update file size + pFcb->size = pFcb->fileptr; + + nResult = 0; + } else { + // Confirm the existence of the buffer + if (pBuffer == NULL) { + m_cFcb.Free(pHostFcb); + return FS_INVALIDFUNC; + } + + // Write + nResult = pHostFcb->Write(pBuffer, nSize); + if (nResult == (DWORD)-1) { + m_cFcb.Free(pHostFcb); + return FS_CANTWRITE; /// TODO: Should return error code 11 (write error) as well here + } + ASSERT(nResult <= nSize); + + // Update file pointer + pFcb->fileptr += nResult; /// TODO: Do we need an overflow check here? + + // Update file size + if (pFcb->size < pFcb->fileptr) + pFcb->size = pFcb->fileptr; + } + + // Update flag + pHostFcb->SetUpdate(); + + return nResult; +} + +//--------------------------------------------------------------------------- +// +/// $4E - File seek +// +//--------------------------------------------------------------------------- +int CFileSys::Seek(DWORD nKey, Human68k::fcb_t* pFcb, DWORD nSeek, int nOffset) +{ + ASSERT(pFcb); + + // Throw error if memory with the same key does not exist + CHostFcb* pHostFcb = m_cFcb.Search(nKey); + if (pHostFcb == NULL) + return FS_NOTOPENED; + + // Parameter check + if (nSeek > Human68k::SK_END) { + m_cFcb.Free(pHostFcb); + return FS_INVALIDPRM; + } + + // File seek + DWORD nResult = pHostFcb->Seek(nOffset, nSeek); + if (nResult == (DWORD)-1) { + m_cFcb.Free(pHostFcb); + return FS_CANTSEEK; + } + + // Update file pointer + pFcb->fileptr = nResult; + + return nResult; +} + +//--------------------------------------------------------------------------- +// +/// $4F - Get/set file time stamp +/// +/// Throw error when the top 16 bits are $FFFF. +// +//--------------------------------------------------------------------------- +DWORD CFileSys::TimeStamp(DWORD nUnit, DWORD nKey, Human68k::fcb_t* pFcb, DWORD nHumanTime) +{ + ASSERT(nKey); + ASSERT(pFcb); + + // Get only + if (nHumanTime == 0) + return ((DWORD)pFcb->date << 16) | pFcb->time; + + // Unit check + if (nUnit >= DriveMax) + return FS_FATAL_INVALIDUNIT; + ASSERT(nUnit < m_nUnits); + if (nUnit >= m_nUnits) + return FS_FATAL_MEDIAOFFLINE; // Just in case + + // Media check + if (m_cEntry.isMediaOffline(nUnit)) + return FS_FATAL_MEDIAOFFLINE; + + // Write-protect check + if (m_cEntry.isWriteProtect(nUnit)) + return FS_FATAL_WRITEPROTECT; + + // Throw error if memory with the same key does not exist + CHostFcb* pHostFcb = m_cFcb.Search(nKey); + if (pHostFcb == NULL) + return FS_NOTOPENED; + + // Set time stamp + if (pHostFcb->TimeStamp(nHumanTime) == FALSE) { + m_cFcb.Free(pHostFcb); + return FS_INVALIDPRM; + } + pFcb->date = (WORD)(nHumanTime >> 16); + pFcb->time = (WORD)nHumanTime; + + // Update cache + m_cEntry.CleanCache(nUnit, pHostFcb->GetHumanPath()); + + return 0; +} + +//--------------------------------------------------------------------------- +// +/// $50 - Get capacity +// +//--------------------------------------------------------------------------- +int CFileSys::GetCapacity(DWORD nUnit, Human68k::capacity_t* pCapacity) +{ + ASSERT(pCapacity); + + // Unit check + if (nUnit >= DriveMax) + return FS_FATAL_INVALIDUNIT; + ASSERT(nUnit < m_nUnits); + if (nUnit >= m_nUnits) + return FS_FATAL_MEDIAOFFLINE; // Just in case + + // Media check + if (m_cEntry.isMediaOffline(nUnit)) + return FS_FATAL_MEDIAOFFLINE; + + // Get capacity + return m_cEntry.GetCapacity(nUnit, pCapacity); +} + +//--------------------------------------------------------------------------- +// +/// $51 - Inspect/control drive status +// +//--------------------------------------------------------------------------- +int CFileSys::CtrlDrive(DWORD nUnit, Human68k::ctrldrive_t* pCtrlDrive) +{ + ASSERT(pCtrlDrive); + + // Unit check + if (nUnit >= DriveMax) + return FS_FATAL_INVALIDUNIT; + if (nUnit >= m_nUnits) + return FS_INVALIDFUNC; // Avoid triggering a fatal error in mint when resuming with an invalid drive + + switch (pCtrlDrive->status) { + case 0: // Inspect status + case 9: // Inspect status 2 + pCtrlDrive->status = (BYTE)m_cEntry.GetStatus(nUnit); + return pCtrlDrive->status; + + case 1: // Eject + case 2: // Eject forbidden 1 (not implemented) + case 3: // Eject allowed 1 (not implemented) + case 4: // Flash LED when media is not inserted (not implemented) + case 5: // Turn off LED when media is not inserted (not implemented) + case 6: // Eject forbidden 2 (not implemented) + case 7: // Eject allowed 2 (not implemented) + return 0; + + case 8: // Eject inspection + return 1; + } + + return FS_INVALIDFUNC; +} + +//--------------------------------------------------------------------------- +// +/// $52 - Get DPB +/// +/// If DPB cannot be acquired after resuming, the drive will be torn down internally in Human68k. +/// Therefore, treat even a unit out of bounds as normal operation. +// +//--------------------------------------------------------------------------- +int CFileSys::GetDPB(DWORD nUnit, Human68k::dpb_t* pDpb) +{ + ASSERT(pDpb); + + // Unit check + if (nUnit >= DriveMax) + return FS_FATAL_INVALIDUNIT; + + Human68k::capacity_t cap; + BYTE media = Human68k::MEDIA_REMOTE; + if (nUnit < m_nUnits) { + media = m_cEntry.GetMediaByte(nUnit); + + // Acquire sector data + if (m_cEntry.GetCapacityCache(nUnit, &cap) == FALSE) { + // Carry out an extra media check here because it may be skipped when doing a manual eject + if (m_cEntry.isEnable(nUnit) == FALSE) + goto none; + + // Media check + if (m_cEntry.isMediaOffline(nUnit)) + goto none; + + // Get drive status + m_cEntry.GetCapacity(nUnit, &cap); + } + } else { + none: + cap.clusters = 4; // This is totally fine, right? + cap.sectors = 64; + cap.bytes = 512; + } + + // Calculate number of shifts + DWORD nSize = 1; + DWORD nShift = 0; + for (;;) { + if (nSize >= cap.sectors) + break; + nSize <<= 1; + nShift++; + } + + // Sector number calculation + // + // In the following order: + // Cluster 0: Unused + // Cluster 1: FAT + // Cluster 2: Root directory + // Cluster 3: Data memory (pseudo-sector) + DWORD nFat = cap.sectors; + DWORD nRoot = cap.sectors * 2; + DWORD nData = cap.sectors * 3; + + // Set DPB + pDpb->sector_size = (WORD)cap.bytes; // Bytes per sector + pDpb->cluster_size = + (BYTE)(cap.sectors - 1); // Sectors per cluster - 1 + pDpb->shift = (BYTE)nShift; // Number of cluster → sector shifts + pDpb->fat_sector = (WORD)nFat; // First FAT sector number + pDpb->fat_max = 1; // Number of FAT memory spaces + pDpb->fat_size = (BYTE)cap.sectors; // Number of sectors controlled by FAT (excluding copies) + pDpb->file_max = + (WORD)(cap.sectors * cap.bytes / 0x20); // Number of files in the root directory + pDpb->data_sector = (WORD)nData; // First sector number of data memory + pDpb->cluster_max = (WORD)cap.clusters; // Total number of clusters + 1 + pDpb->root_sector = (WORD)nRoot; // First sector number of the root directory + pDpb->media = media; // Media byte + + return 0; +} + +//--------------------------------------------------------------------------- +// +/// $53 - Read sector +/// +/// We use pseudo-sectors. +/// Buffer size is hard coded to $200 byte. +// +//--------------------------------------------------------------------------- +int CFileSys::DiskRead(DWORD nUnit, BYTE* pBuffer, DWORD nSector, DWORD nSize) +{ + ASSERT(pBuffer); + + // Unit check + if (nUnit >= DriveMax) + return FS_FATAL_INVALIDUNIT; + if (nUnit >= m_nUnits) + return FS_FATAL_MEDIAOFFLINE; // This occurs when resuming with an invalid drive + + // Media check + if (m_cEntry.isMediaOffline(nUnit)) + return FS_FATAL_MEDIAOFFLINE; + + // Throw error if number of sectors exceed 1 + if (nSize != 1) + return FS_INVALIDPRM; + + // Acquire sector data + Human68k::capacity_t cap; + if (m_cEntry.GetCapacityCache(nUnit, &cap) == FALSE) { + // Get drive status + m_cEntry.GetCapacity(nUnit, &cap); + } + + // Access pseudo-directory entry + CHostFiles* pHostFiles = m_cFiles.Search(nSector); + if (pHostFiles) { + // Generate pseudo-directory entry + Human68k::dirent_t* dir = (Human68k::dirent_t*)pBuffer; + memcpy(pBuffer, pHostFiles->GetEntry(), sizeof(*dir)); + memset(pBuffer + sizeof(*dir), 0xE5, 0x200 - sizeof(*dir)); + + // Register the pseudo-sector number that points to the file entity inside the pseudo-directory. + // Note that in lzdsys the sector number to read is calculated by the following formula: + // (dirent.cluster - 2) * (dpb.cluster_size + 1) + dpb.data_sector + /// @warning little endian only + dir->cluster = (WORD)(m_nHostSectorCount + 2); // Pseudo-sector number + m_nHostSectorBuffer[m_nHostSectorCount] = nSector; // Entity that points to the pseudo-sector + m_nHostSectorCount++; + m_nHostSectorCount %= XM6_HOST_PSEUDO_CLUSTER_MAX; + + return 0; + } + + // Calculate the sector number from the cluster number + DWORD n = nSector - (3 * cap.sectors); + DWORD nMod = 1; + if (cap.sectors) { + // Beware that cap.sectors becomes 0 when media does not exist + nMod = n % cap.sectors; + n /= cap.sectors; + } + + // Access the file entity + if (nMod == 0 && n < XM6_HOST_PSEUDO_CLUSTER_MAX) { + pHostFiles = m_cFiles.Search(m_nHostSectorBuffer[n]); // Find entity + if (pHostFiles) { + // Generate pseudo-sector + CHostFcb f; + f.SetFilename(pHostFiles->GetPath()); + f.SetMode(Human68k::OP_READ); + if (f.Open() == FALSE) + return FS_INVALIDPRM; + memset(pBuffer, 0, 0x200); + DWORD nResult = f.Read(pBuffer, 0x200); + f.Close(); + if (nResult == (DWORD)-1) + return FS_INVALIDPRM; + + return 0; + } + } + + return FS_INVALIDPRM; +} + +//--------------------------------------------------------------------------- +// +/// $54 - Write sector +// +//--------------------------------------------------------------------------- +int CFileSys::DiskWrite(DWORD nUnit) +{ + + // Unit check + if (nUnit >= DriveMax) + return FS_FATAL_INVALIDUNIT; + ASSERT(nUnit < m_nUnits); + if (nUnit >= m_nUnits) + return FS_FATAL_MEDIAOFFLINE; // Just in case + + // Media check + if (m_cEntry.isMediaOffline(nUnit)) + return FS_FATAL_MEDIAOFFLINE; + + // Write-protect check + if (m_cEntry.isWriteProtect(nUnit)) + return FS_FATAL_WRITEPROTECT; + + // Thrust at reality + return FS_INVALIDPRM; +} + +//--------------------------------------------------------------------------- +// +/// $55 - IOCTRL +// +//--------------------------------------------------------------------------- +int CFileSys::Ioctrl(DWORD nUnit, DWORD nFunction, Human68k::ioctrl_t* pIoctrl) +{ + ASSERT(pIoctrl); + + // Unit check + if (nUnit >= DriveMax) + return FS_FATAL_INVALIDUNIT; + if (nUnit >= m_nUnits) + return FS_INVALIDFUNC; // Avoid triggering a fatal error in mint when resuming with an invalid drive + + switch (nFunction) { + case 0: + // Acquire media byte + pIoctrl->media = m_cEntry.GetMediaByte(nUnit); + return 0; + + case 1: + // Dummy for Human68k compatibility + pIoctrl->param = -1; + return 0; + + case 2: + switch (pIoctrl->param) { + case (DWORD)-1: + // Re-identify media + m_cEntry.isMediaOffline(nUnit); + return 0; + + case 0: + case 1: + // Dummy for Human68k compatibility + return 0; + } + break; + + case (DWORD)-1: + // Resident evaluation + memcpy(pIoctrl->buffer, "WindrvXM", 8); + return 0; + + case (DWORD)-2: + // Set options + SetOption(pIoctrl->param); + return 0; + + case (DWORD)-3: + // Get options + pIoctrl->param = GetOption(); + return 0; + } + + return FS_NOTIOCTRL; +} + +//--------------------------------------------------------------------------- +// +/// $56 - Flush +// +//--------------------------------------------------------------------------- +int CFileSys::Flush(DWORD nUnit) +{ + + // Unit check + if (nUnit >= DriveMax) + return FS_FATAL_INVALIDUNIT; + if (nUnit >= m_nUnits) + return FS_INVALIDFUNC; // Avoid triggering a fatal error returning from a mint command when resuming with an invalid drive + + // Always succeed + return 0; +} + +//--------------------------------------------------------------------------- +// +/// $57 - Media change check +// +//--------------------------------------------------------------------------- +int CFileSys::CheckMedia(DWORD nUnit) +{ + + // Unit check + if (nUnit >= DriveMax) + return FS_FATAL_INVALIDUNIT; + if (nUnit >= m_nUnits) + return FS_INVALIDFUNC; // Avoid triggering a fatal error in mint when resuming with an invalid drive + + // Media change check + BOOL bResult = m_cEntry.CheckMedia(nUnit); + + // Throw error when media is not inserted + if (bResult == FALSE) { + return FS_INVALIDFUNC; + } + + return 0; +} + +//--------------------------------------------------------------------------- +// +/// $58 - Lock +// +//--------------------------------------------------------------------------- +int CFileSys::Lock(DWORD nUnit) +{ + + // Unit check + if (nUnit >= DriveMax) + return FS_FATAL_INVALIDUNIT; + ASSERT(nUnit < m_nUnits); + if (nUnit >= m_nUnits) + return FS_FATAL_MEDIAOFFLINE; // Just in case + + // Media check + if (m_cEntry.isMediaOffline(nUnit)) + return FS_FATAL_MEDIAOFFLINE; + + // Always succeed + return 0; +} + +//--------------------------------------------------------------------------- +// +/// Set options +// +//--------------------------------------------------------------------------- +void CFileSys::SetOption(DWORD nOption) +{ + + // Clear cache when option settings change + if (m_nOption ^ nOption) + m_cEntry.CleanCache(); + + m_nOption = nOption; + g_nOption = nOption; +} + +//--------------------------------------------------------------------------- +// +/// Initialize options +// +//--------------------------------------------------------------------------- +void CFileSys::InitOption(const Human68k::argument_t* pArgument) +{ + ASSERT(pArgument); + + // Initialize number of drives + m_nDrives = 0; + + const BYTE* pp = pArgument->buf; + pp += strlen((const char*)pp) + 1; + + DWORD nOption = m_nOptionDefault; + for (;;) { + ASSERT(pp < pArgument->buf + sizeof(*pArgument)); + const BYTE* p = pp; + BYTE c = *p++; + if (c == '\0') + break; + + DWORD nMode; + if (c == '+') { + nMode = 1; + } else if (c == '-') { + nMode = 0; + } else if (c == '/') { + // Specify default base path + if (m_nDrives < DriveMax) { + p--; + strcpy(m_szBase[m_nDrives], (const char *)p); + m_nDrives++; + } + pp += strlen((const char*)pp) + 1; + continue; + } else { + // Continue since no option is specified + pp += strlen((const char*)pp) + 1; + continue; + } + + for (;;) { + c = *p++; + if (c == '\0') + break; + + DWORD nBit = 0; + switch (c) { + case 'A': case 'a': nBit = WINDRV_OPT_CONVERT_LENGTH; break; + case 'T': case 't': nBit = WINDRV_OPT_COMPARE_LENGTH; nMode ^= 1; break; + case 'C': case 'c': nBit = WINDRV_OPT_ALPHABET; break; + + case 'E': nBit = WINDRV_OPT_CONVERT_PERIOD; break; + case 'P': nBit = WINDRV_OPT_CONVERT_PERIODS; break; + case 'N': nBit = WINDRV_OPT_CONVERT_HYPHEN; break; + case 'H': nBit = WINDRV_OPT_CONVERT_HYPHENS; break; + case 'X': nBit = WINDRV_OPT_CONVERT_BADCHAR; break; + case 'S': nBit = WINDRV_OPT_CONVERT_SPACE; break; + + case 'e': nBit = WINDRV_OPT_REDUCED_PERIOD; break; + case 'p': nBit = WINDRV_OPT_REDUCED_PERIODS; break; + case 'n': nBit = WINDRV_OPT_REDUCED_HYPHEN; break; + case 'h': nBit = WINDRV_OPT_REDUCED_HYPHENS; break; + case 'x': nBit = WINDRV_OPT_REDUCED_BADCHAR; break; + case 's': nBit = WINDRV_OPT_REDUCED_SPACE; break; + } + + if (nMode) + nOption |= nBit; + else + nOption &= ~nBit; + } + + pp = p; + } + + // Set options + if (nOption != m_nOption) { + SetOption(nOption); + } +} + +//--------------------------------------------------------------------------- +// +/// Get volume label +// +//--------------------------------------------------------------------------- +BOOL CFileSys::FilesVolume(DWORD nUnit, Human68k::files_t* pFiles) +{ + ASSERT(pFiles); + + // Get volume label + TCHAR szVolume[32]; + BOOL bResult = m_cEntry.GetVolumeCache(nUnit, szVolume); + if (bResult == FALSE) { + // Carry out an extra media check here because it may be skipped when doing a manual eject + if (m_cEntry.isEnable(nUnit) == FALSE) + return FALSE; + + // Media check + if (m_cEntry.isMediaOffline(nUnit)) + return FALSE; + + // Get volume label + m_cEntry.GetVolume(nUnit, szVolume); + } + if (szVolume[0] == _T('\0')) + return FALSE; + + pFiles->attr = Human68k::AT_VOLUME; + pFiles->time = 0; + pFiles->date = 0; + pFiles->size = 0; + + CHostFilename fname; + fname.SetHost(szVolume); + fname.ConvertHuman(); + strcpy((char*)pFiles->full, (const char*)fname.GetHuman()); + + return TRUE; +} diff --git a/src_old/raspberrypi/devices/cfilesystem.h b/src_old/raspberrypi/devices/cfilesystem.h new file mode 100644 index 00000000..21f9593a --- /dev/null +++ b/src_old/raspberrypi/devices/cfilesystem.h @@ -0,0 +1,944 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Powered by XM6 TypeG Technology. +// Copyright (C) 2016-2020 GIMONS +// [ Host File System for the X68000 ] +// +// Note: This functionality is specific to the X68000 operating system. +// It is highly unlikely that this will work for other platforms. +//--------------------------------------------------------------------------- + +#pragma once + +//--------------------------------------------------------------------------- +// +// Status code definitions +// +//--------------------------------------------------------------------------- +#define FS_INVALIDFUNC 0xFFFFFFFF ///< Executed an invalid function +#define FS_FILENOTFND 0xFFFFFFFE ///< The selected file can not be found +#define FS_DIRNOTFND 0xFFFFFFFD ///< The selected directory can not be found +#define FS_OVEROPENED 0xFFFFFFFC ///< There are too many files open +#define FS_CANTACCESS 0xFFFFFFFB ///< Can not access the direcory or volume +#define FS_NOTOPENED 0xFFFFFFFA ///< The selected handle is not opened +#define FS_INVALIDMEM 0xFFFFFFF9 ///< Memory management has been destroyed +#define FS_OUTOFMEM 0xFFFFFFF8 ///< Insufficient memory for execution +#define FS_INVALIDPTR 0xFFFFFFF7 ///< Selected an invalid memory management pointer +#define FS_INVALIDENV 0xFFFFFFF6 ///< Selected an invalid environment +#define FS_ILLEGALFMT 0xFFFFFFF5 ///< The exeucted file is in an invalid format +#define FS_ILLEGALMOD 0xFFFFFFF4 ///< Invalid open access mode +#define FS_INVALIDPATH 0xFFFFFFF3 ///< Mistake in selected file name +#define FS_INVALIDPRM 0xFFFFFFF2 ///< Called with an invalid parameter +#define FS_INVALIDDRV 0xFFFFFFF1 ///< Mistake in selected drive +#define FS_DELCURDIR 0xFFFFFFF0 ///< Unable to delete the current directory +#define FS_NOTIOCTRL 0xFFFFFFEF ///< Unable to use IOCTRL with the device +#define FS_LASTFILE 0xFFFFFFEE ///< Can not find any more files +#define FS_CANTWRITE 0xFFFFFFED ///< Selected file can not be written +#define FS_DIRALREADY 0xFFFFFFEC ///< Selected directory is already registered +#define FS_CANTDELETE 0xFFFFFFEB ///< Can not delete because of a file +#define FS_CANTRENAME 0xFFFFFFEA ///< Can not rename because of a file +#define FS_DISKFULL 0xFFFFFFE9 ///< Can not create a file because the disk is full +#define FS_DIRFULL 0xFFFFFFE8 ///< Can not create a file because the directory is full +#define FS_CANTSEEK 0xFFFFFFE7 ///< Can not seek in the selected location +#define FS_SUPERVISOR 0xFFFFFFE6 ///< Selected supervisor in supervisor mode +#define FS_THREADNAME 0xFFFFFFE5 ///< A thread with this name already exists +#define FS_BUFWRITE 0xFFFFFFE4 ///< Writing to inter-process communication buffers is disallowed +#define FS_BACKGROUND 0xFFFFFFE3 ///< Unable to start a background process +#define FS_OUTOFLOCK 0xFFFFFFE0 ///< Insufficient lock space +#define FS_LOCKED 0xFFFFFFDF ///< Can not access because it is locked +#define FS_DRIVEOPENED 0xFFFFFFDE ///< Selected drive has an open handler +#define FS_LINKOVER 0xFFFFFFDD ///< The symbolic link is nested over 16 times +#define FS_FILEEXIST 0xFFFFFFB0 ///< The file exists + +#define FS_FATAL_MEDIAOFFLINE 0xFFFFFFA3 ///< No media inserted +#define FS_FATAL_WRITEPROTECT 0xFFFFFFA2 ///< Write protected +#define FS_FATAL_INVALIDCOMMAND 0xFFFFFFA1 ///< Invalid command number +#define FS_FATAL_INVALIDUNIT 0xFFFFFFA0 ///< Invalid unit number + +#define HUMAN68K_PATH_MAX 96 ///< Longest path allowed in Human68k + +//=========================================================================== +// +/// Human68k name space +// +//=========================================================================== +namespace Human68k { + /// File attribute bit + enum attribute_t { + AT_READONLY = 0x01, ///< Read only attribute + AT_HIDDEN = 0x02, ///< Hidden attribute + AT_SYSTEM = 0x04, ///< System attribute + AT_VOLUME = 0x08, ///< Volume label attribute + AT_DIRECTORY = 0x10, ///< Directory attribute + AT_ARCHIVE = 0x20, ///< Archive attribute + AT_ALL = 0xFF, ///< All attribute bits are 1 + }; + + /// File open modes + enum open_t { + OP_READ = 0, ///< Read + OP_WRITE = 1, ///< Write + OP_FULL = 2, ///< Read/Write + OP_MASK = 0x0F, ///< Decision mask + OP_SHARE_NONE = 0x10, ///< Sharing forbidden + OP_SHARE_READ = 0x20, ///< Read sharing + OP_SHARE_WRITE = 0x30, ///< Write sharing + OP_SHARE_FULL = 0x40, ///< Read/Write sharing + OP_SHARE_MASK = 0x70, ///< Sharing decision mask + OP_SPECIAL = 0x100, ///< Dictionary access + }; + + /// Seek types + enum seek_t { + SK_BEGIN = 0, ///< From the beginning of a file + SK_CURRENT = 1, ///< From the current location + SK_END = 2, ///< From the end of the file + }; + + /// Media byte + enum media_t { + MEDIA_2DD_10 = 0xE0, ///< 2DD/10 sector + MEDIA_1D_9 = 0xE5, ///< 1D/9 sector + MEDIA_2D_9 = 0xE6, ///< 2D/9 sector + MEDIA_1D_8 = 0xE7, ///< 1D/8 sector + MEDIA_2D_8 = 0xE8, ///< 2D/8 sector + MEDIA_2HT = 0xEA, ///< 2HT + MEDIA_2HS = 0xEB, ///< 2HS + MEDIA_2HDE = 0xEC, ///< 2DDE + MEDIA_1DD_9 = 0xEE, ///< 1DD/9 sector + MEDIA_1DD_8 = 0xEF, ///< 1DD/8 sector + MEDIA_MANUAL = 0xF1, ///< Remote drive (manual eject) + MEDIA_REMOVABLE = 0xF2, ///< Remote drive (removable) + MEDIA_REMOTE = 0xF3, ///< Remote drive + MEDIA_DAT = 0xF4, ///< SCSI-DAT + MEDIA_CDROM = 0xF5, ///< SCSI-CDROM + MEDIA_MO = 0xF6, ///< SCSI-MO + MEDIA_SCSI_HD = 0xF7, ///< SCSI-HD + MEDIA_SASI_HD = 0xF8, ///< SASI-HD + MEDIA_RAMDISK = 0xF9, ///< RAM disk + MEDIA_2HQ = 0xFA, ///< 2HQ + MEDIA_2DD_8 = 0xFB, ///< 2DD/8 sector + MEDIA_2DD_9 = 0xFC, ///< 2DD/9 sector + MEDIA_2HC = 0xFD, ///< 2HC + MEDIA_2HD = 0xFE, ///< 2HD + }; + + struct namests_t { + BYTE wildcard; ///< Wildcard character length + BYTE drive; ///< Drive number + BYTE path[65]; ///< Path (subdirectory +/) + BYTE name[8]; ///< File name (PADDING 0x20) + BYTE ext[3]; ///< Extension (PADDING 0x20) + BYTE add[10]; ///< File name addition (PADDING 0x00) + + void GetCopyPath(BYTE* szPath) const; + void GetCopyFilename(BYTE* szFilename) const; + }; + + struct files_t { + BYTE fatr; ///< + 0 search attribute; read-only + // BYTE drive; ///< + 1 drive number; read-only + DWORD sector; ///< + 2 directory sector; DOS _FILES first address substitute + // WORD cluster; ///< + 6 directory cluster; details unknown (unused) + WORD offset; ///< + 8 directory entry; write-only + // BYTE name[8]; ///< +10 working file name; write-only (unused) + // BYTE ext[3]; ///< +18 working extension; write-only (unused) + BYTE attr; ///< +21 file attribute; write-only + WORD time; ///< +22 last change time of day; write-only + WORD date; ///< +24 last change date; write-only + DWORD size; ///< +26 file size; write-only + BYTE full[23]; ///< +30 full name; write-only + }; + + struct fcb_t { + // BYTE pad00[6]; ///< + 0~+ 5 (unused) + DWORD fileptr; ///< + 6~+ 9 file pointer + // BYTE pad01[4]; ///< +10~+13 (unused) + WORD mode; ///< +14~+15 open mode + // BYTE pad02[16]; ///< +16~+31 (unused) + // DWORD zero; ///< +32~+35 zeros are written when opened (unused) + // BYTE name[8]; ///< +36~+43 file name (PADDING 0x20) (unused) + // BYTE ext[3]; ///< +44~+46 extension (PADDING 0x20) (unused) + BYTE attr; ///< +47 file attribute + // BYTE add[10]; ///< +48~+57 file name addition (PADDING 0x00) (unused) + WORD time; ///< +58~+59 last change time of day + WORD date; ///< +60~+61 last change date + // WORD cluster; ///< +62~+63 cluster number (unused) + DWORD size; ///< +64~+67 file size + // BYTE pad03[28]; ///< +68~+95 FAT cache (unused) + }; + + struct capacity_t { + WORD freearea; ///< + 0 Number of available clusters + WORD clusters; ///< + 2 Total number of clusters + WORD sectors; ///< + 4 Number of sectors per cluster + WORD bytes; ///< + 6 Number of bytes per sector + }; + + struct ctrldrive_t { + BYTE status; ///< +13 status + BYTE pad[3]; ///< Padding + }; + + struct dpb_t { + WORD sector_size; ///< + 0 Number of bytes in one sector + BYTE cluster_size; ///< + 2 Number sectors in one cluster -1 + BYTE shift; ///< + 3 Number of cluster→sector shifts + WORD fat_sector; ///< + 4 FAT first sector number + BYTE fat_max; ///< + 6 FAT storage quantity + BYTE fat_size; ///< + 7 FAT controlled sector number (excluding duplicates) + WORD file_max; ///< + 8 Number of files in the root directory + WORD data_sector; ///< +10 First sector number of data storage + WORD cluster_max; ///< +12 Total number of clusters +1 + WORD root_sector; ///< +14 First sector number of root directory + // DWORD driverentry; ///< +16 Device driver pointer (unused) + BYTE media; ///< +20 Media identifier + // BYTE flag; ///< +21 Flag used by DPB (unused) + }; + + /// Directory entry struct + struct dirent_t { + BYTE name[8]; ///< + 0 File name (PADDING 0x20) + BYTE ext[3]; ///< + 8 Extension (PADDING 0x20) + BYTE attr; ///< +11 File attribute + BYTE add[10]; ///< +12 File name addition (PADDING 0x00) + WORD time; ///< +22 Last change time of day + WORD date; ///< +24 Last change date + WORD cluster; ///< +26 Cluster number + DWORD size; ///< +28 File size + }; + + /// IOCTRL parameter union + union ioctrl_t { + BYTE buffer[8]; ///< Access in byte units + DWORD param; ///< Parameter (First 4 bytes) + WORD media; ///< Media byte (First 2 bytes) + }; + + /// Command line parameter struct + /** + The driver itself is included in the beginning of the argument, + so setting to a length longer than HUMAN68K_PATH_MAX + */ + struct argument_t { + BYTE buf[256]; ///< Command line argument + }; +} + +/// Number of FILES buffers +/** +Under normal circumstances it's enough with just a few buffers, +but Human68k multitasking may lead to multiple threads working +deeply in the system, which is why this value is set this high. + +Default is 20 buffers. +*/ +#define XM6_HOST_FILES_MAX 20 + +/// Number of FCB buffers +/** +This decides how many files can be opened at the same time. + +Default is 100 files. +*/ +#define XM6_HOST_FCB_MAX 100 + +/// Max number of virtual clusters and sectors +/** +Number of virtual sectors used for accessing the first sector of a file entity. +Allocating a generous amount to exceed the number of threads lzdsys uses for access. + +Default is 10 sectors. +*/ +#define XM6_HOST_PSEUDO_CLUSTER_MAX 10 + +/// Number of caches for directory entries +/** +Human68k carries out a large number of checks of directory entries when doing an operation +inside a subdirectory. This specifies the number of caches used to speed up this operation. +Cache is allocated per drive. The more you add the faster it gets, but use too many +and the host OS gets under a heavy load, so be careful. + +Default is 16. +*/ +#define XM6_HOST_DIRENTRY_CACHE_MAX 16 + +/// Max number of entries that can be stored per directory +/** +When a large number of files are stored in a directory, a larger amount of data than +contemporanous applications can handle will be returned. This may lead to errors such as +partial data being recognized, performance dropping significantly, or OOM crashes. +To guard against this, an upper limit is defined here. In the case of a particular +file manager, the upper limit is 2560 files. This is one good example to use as reference. + +Default is around 60000 entries. (Upper limit of the FAT root directory) +*/ +#define XM6_HOST_DIRENTRY_FILE_MAX 65535 + +/// Max number of patterns for file name deduplication +/** +The file names on the Human68k side are automatically created based on the file system on +the host side. However, Human68k have stricter file name length restrictions than the host has. +Because of this, there is a risk that file name duplication will occur. When this happens, +WindrvXM will use a certain renaming heuristic to generate alternate file names to resolve +the duplication. Theoretically, there are over 60 million (36^5) unique file names that +can be generated by this method. However, in reality any more than a few hundred +deduplications will take excessive processing time. So here an upper limit to deduplication +is set in order to maintain system performance. If a system is operated with common sense, +you should only need a few dozen deduplication patterns, so this value can be kept low +to further improve performance. In the case deduplication is not carried out, multiple files +with the same name will be created. When trying to access such files, +only the first entry will ever be accessed. + +Default is 36 patterns. +*/ +#define XM6_HOST_FILENAME_PATTERN_MAX 36 + +/// Duplicate file identification mark +/** +A symbol used to distinguish between host and Human68k files. +Do not use a command shell escape character, or similar protected symbol. + +Default is '@'. +*/ +#define XM6_HOST_FILENAME_MARK '@' + +/// WINDRV operational flags +/** +Normally set to 0. When put in the OS trash can for deletion, it is set to 1. +Other values are reserved for future use. +Can be used for future extentions such as internal operational flags or mock media byte. +*/ +enum { + WINDRV_OPT_REMOVE = 0x00000001, ///< Bit 0: File delete process 0:Directly 1:Trash can + WINDRV_OPT_ALPHABET = 0x00000020, ///< Bit 5: File name comparison; Alphabet distinction 0:No 1:Yes 0:-C 1:+C + WINDRV_OPT_COMPARE_LENGTH = 0x00000040, ///< Bit 6: File name comparison; String length (unimplemented) 0:18+3 1:8+3 0:+T 1:-T + WINDRV_OPT_CONVERT_LENGTH = 0x00000080, ///< Bit 7: File name conversion; String length 0:18+3 1:8+3 0:-A 1:+A + WINDRV_OPT_CONVERT_SPACE = 0x00000100, ///< Bit 8: File name conversion; Space 0:No 1:'_' + WINDRV_OPT_CONVERT_BADCHAR = 0x00000200, ///< Bit 9: File name conversion; Invalid char 0:No 1:'_' + WINDRV_OPT_CONVERT_HYPHENS = 0x00000400, ///< Bit10: File name conversion; Middle hyphen 0:No 1:'_' + WINDRV_OPT_CONVERT_HYPHEN = 0x00000800, ///< Bit11: File name conversion; Initial hyphen 0:No 1:'_' + WINDRV_OPT_CONVERT_PERIODS = 0x00001000, ///< Bit12: File name conversion; Middle period 0:No 1:'_' + WINDRV_OPT_CONVERT_PERIOD = 0x00002000, ///< Bit13: File name conversion; Initial period 0:No 1:'_' + WINDRV_OPT_REDUCED_SPACE = 0x00010000, ///< Bit16: File name reduction; Space 0:No 1:Reduced + WINDRV_OPT_REDUCED_BADCHAR = 0x00020000, ///< Bit17: File name reduction; Invalid char 0:No 1:Reduced + WINDRV_OPT_REDUCED_HYPHENS = 0x00040000, ///< Bit18: File name reduction Middle hyphen 0:No 1:Reduced + WINDRV_OPT_REDUCED_HYPHEN = 0x00080000, ///< Bit19: File name reduction Initial hyphen 0:No 1:Reduced + WINDRV_OPT_REDUCED_PERIODS = 0x00100000, ///< Bit20: File name reduction Middle period 0:No 1:Reduced + WINDRV_OPT_REDUCED_PERIOD = 0x00200000, ///< Bit21: File name reduction Initial period 0:No 1:Reduced + // Bit24~30 Duplicate file identification mark 0:Automatic 1~127:Chars +}; + +/// File system operational flag +/** +Normal is 0. Becomes 1 if attempting to mount in read-only mode. +Reserving the other values for future use. +Insurance against hard-to-detect devices such as homemade USB storage. +*/ +enum { + FSFLAG_WRITE_PROTECT = 0x00000001, ///< Bit0: Force write protect + FSFLAG_REMOVABLE = 0x00000002, ///< Bit1: Force removable media + FSFLAG_MANUAL = 0x00000004, ///< Bit2: Force manual eject +}; + +//=========================================================================== +// +/// Full ring list +/// +/// First (root.next) is the most recent object. +/// Last (root.prev) is the oldest / unused object. +/// For code optimization purposes, always upcast the pointer when deleting. +// +//=========================================================================== +class CRing { +public: + CRing() { Init(); } + ~CRing() { Remove(); } + void Init() { next = prev = this; } + + CRing* Next() const { return next; } ///< Get the next element + CRing* Prev() const { return prev; } ///< Get the previous element + + void Insert(CRing* pRoot) + { + // Separate the relevant objects + ASSERT(next); + ASSERT(prev); + next->prev = prev; + prev->next = next; + // Insert into the beginning of the ring + ASSERT(pRoot); + ASSERT(pRoot->next); + next = pRoot->next; + prev = pRoot; + pRoot->next->prev = this; + pRoot->next = this; + } + ///< Separate objects & insert into the beginning of the ring + + void InsertTail(CRing* pRoot) + { + // Separate the relevant objects + ASSERT(next); + ASSERT(prev); + next->prev = prev; + prev->next = next; + // Insert into the end of the ring + ASSERT(pRoot); + ASSERT(pRoot->prev); + next = pRoot; + prev = pRoot->prev; + pRoot->prev->next = this; + pRoot->prev = this; + } + ///< Separate objects & insert into the end of the ring + + void InsertRing(CRing* pRoot) + { + if (next == prev) return; + + // Insert into the beginning of the ring + ASSERT(pRoot); + ASSERT(pRoot->next); + pRoot->next->prev = prev; + prev->next = pRoot->next; + pRoot->next = next; + next->prev = pRoot; + + // Empty self + next = prev = this; + } + ///< Separate objects except self & insert into the beginning of the ring + + void Remove() + { + // Separate the relevant objects + ASSERT(next); + ASSERT(prev); + next->prev = prev; + prev->next = next; + // To be safe, assign self (nothing stops you from separating any number of times) + next = prev = this; + } + ///< Separate objects + +private: + CRing* next; ///< Next element + CRing* prev; ///< Previous element +}; + +//=========================================================================== +// +/// Directory Entry: File Name +// +//=========================================================================== +class CHostFilename { +public: + CHostFilename(); + static size_t Offset() { return offsetof(CHostFilename, m_szHost); } ///< Get offset location + + void SetHost(const TCHAR* szHost); ///< Set the name of the host + const TCHAR* GetHost() const { return m_szHost; } ///< Get the name of the host + void ConvertHuman(int nCount = -1); ///< Convert the Human68k name + void CopyHuman(const BYTE* szHuman); ///< Copy the Human68k name + BOOL isReduce() const; ///< Inspect if the Human68k name is generated + BOOL isCorrect() const { return m_bCorrect; } ///< Inspect if the Human68k file name adhers to naming rules + const BYTE* GetHuman() const { return m_szHuman; } ///< Get Human68k file name + const BYTE* GetHumanLast() const + { return m_pszHumanLast; } ///< Get Human68k file name + const BYTE* GetHumanExt() const { return m_pszHumanExt; }///< Get Human68k file name + void SetEntryName(); ///< Set Human68k directory entry + void SetEntryAttribute(BYTE nHumanAttribute) + { m_dirHuman.attr = nHumanAttribute; } ///< Set Human68k directory entry + void SetEntrySize(DWORD nHumanSize) + { m_dirHuman.size = nHumanSize; } ///< Set Human68k directory entry + void SetEntryDate(WORD nHumanDate) + { m_dirHuman.date = nHumanDate; } ///< Set Human68k directory entry + void SetEntryTime(WORD nHumanTime) + { m_dirHuman.time = nHumanTime; } ///< Set Human68k directory entry + void SetEntryCluster(WORD nHumanCluster) + { m_dirHuman.cluster = nHumanCluster; } ///< Set Human68k directory entry + const Human68k::dirent_t* GetEntry() const + { return &m_dirHuman; } ///< Get Human68k directory entry + BOOL CheckAttribute(DWORD nHumanAttribute) const; ///< Determine Human68k directory entry attributes + BOOL isSameEntry(const Human68k::dirent_t* pdirHuman) const + { ASSERT(pdirHuman); return memcmp(&m_dirHuman, pdirHuman, sizeof(m_dirHuman)) == 0; } + ///< Determine Human68k directory entry match + + // Path name operations + static const BYTE* SeparateExt(const BYTE* szHuman); ///< Extract extension from Human68k file name + +private: + static BYTE* CopyName(BYTE* pWrite, const BYTE* pFirst, const BYTE* pLast); + ///< Copy Human68k file name elements + + const BYTE* m_pszHumanLast; ///< Last position of the Human68k internal name of the relevant entry + const BYTE* m_pszHumanExt; ///< Position of the extension of the Human68k internal name of the relevant entry + BOOL m_bCorrect; ///< TRUE if the relevant entry of the Human68k internal name is correct + BYTE m_szHuman[24]; ///< Human68k internal name of the relevant entry + Human68k::dirent_t m_dirHuman; ///< All information for the Human68k relevant entry + TCHAR m_szHost[FILEPATH_MAX]; ///< The host name of the relevant entry (variable length) +}; + +//=========================================================================== +// +/// Directory entry: path name +/// +/// A file path in Human68k always begings with / and ends with / +/// They don't hold unit numbers. +/// Include the base path part of the name on the host side for a performance boost. +// +//=========================================================================== +/** @note +Most Human68k applications are written in a way that expects time stamps not to +get updated for new directories created as a result of file operations, which +triggers updates to directory entires. +However, on the host file system, new directories do typically get an updated time stamp. + +The unfortunate outcome is that when copying a directory for instance, the time stamp +will get overwritten even if the application did not intend for the time stamp to get updated. + +Here follows an implementation of a directory cache FAT time stamp emulation feature. +At the time of a file system update on the host side, time stamp information will be restored +in order to achieve expected behavior on the Human68k side. +*/ +class CHostPath: public CRing { + /// For memory management + struct ring_t { + CRing r; + CHostFilename f; + }; + +public: + /// Search buffer + struct find_t { + DWORD count; ///< Search execution count + 1 (When 0 the below value is invalid) + DWORD id; ///< Entry unique ID for the path of the next search + const ring_t* pos; ///< Position of the next search (When identical to unique ID) + Human68k::dirent_t entry; ///< Contents of the next seach entry + + void Clear() { count = 0; } ///< Initialize + }; + + CHostPath(); + ~CHostPath(); + void Clean(); ///< Initialialize for reuse + + void SetHuman(const BYTE* szHuman); ///< Directly specify the name on the Human68k side + void SetHost(const TCHAR* szHost); ///< Directly specify the name on the host side + BOOL isSameHuman(const BYTE* szHuman) const; ///< Compare the name on the Human68k side + BOOL isSameChild(const BYTE* szHuman) const; ///< Compare the name on the Human68k side + const TCHAR* GetHost() const { return m_szHost; } ///< Obtain the name on the host side + const CHostFilename* FindFilename(const BYTE* szHuman, DWORD nHumanAttribute = Human68k::AT_ALL) const; + ///< Find file name + const CHostFilename* FindFilenameWildcard(const BYTE* szHuman, DWORD nHumanAttribute, find_t* pFind) const; + ///< Find file name (with support for wildcards) + BOOL isRefresh(); ///< Check that the file change has been done + void Refresh(); ///< Refresh file + void Backup(); /// Backup the time stamp on the host side + void Restore() const; /// Restore the time stamp on the host side + void Release(); ///< Update + + // CHostEntry is an external API that we use + static void InitId() { g_nId = 0; } ///< Initialize the counter for the unique ID generation + +private: + static ring_t* Alloc(size_t nLength); ///< Allocate memory for the file name + static void Free(ring_t* pRing); ///< Release memory for the file name + static int Compare(const BYTE* pFirst, const BYTE* pLast, const BYTE* pBufFirst, const BYTE* pBufLast); + ///< Compare string (with support for wildcards) + + CRing m_cRing; ///< For CHostFilename linking + time_t m_tBackup; ///< For time stamp restoration + BOOL m_bRefresh; ///< Refresh flag + DWORD m_nId; ///< Unique ID (When the value has changed, it means an update has been made) + BYTE m_szHuman[HUMAN68K_PATH_MAX]; ///< The internal Human68k name for the relevant entry + TCHAR m_szHost[FILEPATH_MAX]; ///< The host side name for the relevant entry + + static DWORD g_nId; ///< Counter for the unique ID generation +}; + +//=========================================================================== +// +/// File search processing +/// +/// It's pretty much impossible to process Human68k file names as Unicode internally. +/// So, we carry out binary conversion for processing. We leave it up to the +/// directory entry cache to handle the conversion, which allows WINDRV to read +/// everything as Shift-JIS. Additionally, it allows Human68k names to be +/// fully independent of base path assignments. +/// +/// We create directory entry cache just before file handling. +/// Since creating directory entires is very costly, we try to reuse created entries +/// as much as humanly possible. +/// +/// There are three kinds of file search. They are all processed in CHostFiles::Find() +/// 1. Search by path name only; the only attribute is 'directory'; _CHKDIR _CREATE +/// 2. Path + file name + attribute search; _OPEN +/// 3. Path + wildcard + attribute search; _FILES _NFILES +/// The search results are kept as directory entry data. +// +//=========================================================================== +class CHostFiles { +public: + CHostFiles() { SetKey(0); Init(); } + void Init(); + + void SetKey(DWORD nKey) { m_nKey = nKey; } ///< Set search key + BOOL isSameKey(DWORD nKey) const { return m_nKey == nKey; } ///< Compare search key + void SetPath(const Human68k::namests_t* pNamests); ///< Create path and file name internally + BOOL isRootPath() const { return m_szHumanPath[1] == '\0'; } ///< Check if root directory + void SetPathWildcard() { m_nHumanWildcard = 1; } ///< Enable file search using wildcards + void SetPathOnly() { m_nHumanWildcard = 0xFF; } ///< Enable only path names + BOOL isPathOnly() const { return m_nHumanWildcard == 0xFF; } ///< Check if set to only path names + void SetAttribute(DWORD nHumanAttribute) { m_nHumanAttribute = nHumanAttribute; } + ///< Set search attribute + BOOL Find(DWORD nUnit, class CHostEntry* pEntry); ///< Find files on the Human68k side, generating data on the host side + const CHostFilename* Find(CHostPath* pPath); ///< Find file name + void SetEntry(const CHostFilename* pFilename); ///< Store search results on the Human68k side + void SetResult(const TCHAR* szPath); ///< Set names on the host side + void AddResult(const TCHAR* szPath); ///< Add file name to the name on the host side + void AddFilename(); ///< Add the new Human68k file name to the name on the host side + + const TCHAR* GetPath() const { return m_szHostResult; } ///< Get the name on the host side + + const Human68k::dirent_t* GetEntry() const { return &m_dirHuman; }///< Get Human68k directory entry + + DWORD GetAttribute() const { return m_dirHuman.attr; } ///< Get Human68k attribute + WORD GetDate() const { return m_dirHuman.date; } ///< Get Human68k date + WORD GetTime() const { return m_dirHuman.time; } ///< Get Human68k time + DWORD GetSize() const { return m_dirHuman.size; } ///< Get Human68k file size + const BYTE* GetHumanFilename() const { return m_szHumanFilename; }///< Get Human68k file name + const BYTE* GetHumanResult() const { return m_szHumanResult; } ///< Get Human68k file name search results + const BYTE* GetHumanPath() const { return m_szHumanPath; } ///< Get Human68k path name + +private: + DWORD m_nKey; ///< FILES buffer address for Human68k; 0 is unused + DWORD m_nHumanWildcard; ///< Human68k wildcard data + DWORD m_nHumanAttribute; ///< Human68k search attribute + CHostPath::find_t m_findNext; ///< Next search location data + Human68k::dirent_t m_dirHuman; ///< Search results: Human68k file data + BYTE m_szHumanFilename[24]; ///< Human68k file name + BYTE m_szHumanResult[24]; ///< Search results: Human68k file name + BYTE m_szHumanPath[HUMAN68K_PATH_MAX]; + ///< Human68k path name + TCHAR m_szHostResult[FILEPATH_MAX]; ///< Search results: host's full path name +}; + +//=========================================================================== +// +/// File search memory manager +// +//=========================================================================== +class CHostFilesManager { +public: +#ifdef _DEBUG + ~CHostFilesManager(); +#endif // _DEBUG + void Init(); ///< Initialization (when the driver is installed) + void Clean(); ///< Release (when starting up or resetting) + + CHostFiles* Alloc(DWORD nKey); + CHostFiles* Search(DWORD nKey); + void Free(CHostFiles* pFiles); +private: + /// For memory management + struct ring_t { + CRing r; + CHostFiles f; + }; + + CRing m_cRing; ///< For attaching to CHostFiles +}; + +//=========================================================================== +// +/// FCB processing +// +//=========================================================================== +class CHostFcb { +public: + CHostFcb() { SetKey(0); Init(); } + ~CHostFcb() { Close(); } + void Init(); + + void SetKey(DWORD nKey) { m_nKey = nKey; } ///< Set search key + BOOL isSameKey(DWORD nKey) const { return m_nKey == nKey; } ///< Compare search key + void SetUpdate() { m_bUpdate = TRUE; } ///< Update + BOOL isUpdate() const { return m_bUpdate; } ///< Get update state + BOOL SetMode(DWORD nHumanMode); ///< Set file open mode + void SetFilename(const TCHAR* szFilename); ///< Set file name + void SetHumanPath(const BYTE* szHumanPath); ///< Set Human68k path name + const BYTE* GetHumanPath() const { return m_szHumanPath; } ///< Get Human68k path name + + BOOL Create(Human68k::fcb_t* pFcb, DWORD nHumanAttribute, BOOL bForce); ///< Create file + BOOL Open(); ///< Open file + BOOL Rewind(DWORD nOffset); ///< Seek file + DWORD Read(BYTE* pBuffer, DWORD nSize); ///< Read file + DWORD Write(const BYTE* pBuffer, DWORD nSize); ///< Write file + BOOL Truncate(); ///< Truncate file + DWORD Seek(DWORD nOffset, DWORD nHumanSeek); ///< Seek file + BOOL TimeStamp(DWORD nHumanTime); ///< Set file time stamp + BOOL Close(); ///< Close file + +private: + DWORD m_nKey; ///< Human68k FCB buffer address (0 if unused) + BOOL m_bUpdate; ///< Update flag + FILE* m_pFile; ///< Host side file object + const char* m_pszMode; ///< Host side file open mode + bool m_bFlag; ///< Host side file open flag + BYTE m_szHumanPath[HUMAN68K_PATH_MAX]; + ///< Human68k path name + TCHAR m_szFilename[FILEPATH_MAX]; ///< Host side file name +}; + +//=========================================================================== +// +/// FCB processing manager +// +//=========================================================================== +class CHostFcbManager { +public: + #ifdef _DEBUG + ~CHostFcbManager(); + #endif // _DEBUG + void Init(); ///< Initialization (when the driver is installed) + void Clean(); ///< Release (when starting up or resetting) + + CHostFcb* Alloc(DWORD nKey); + CHostFcb* Search(DWORD nKey); + void Free(CHostFcb* p); + +private: + /// For memory management + struct ring_t { + CRing r; + CHostFcb f; + }; + + CRing m_cRing; ///< For attaching to CHostFcb +}; + +//=========================================================================== +// +/// Host side drive +/// +/// Keeps the required data for each drive, managed in CHostEntry. +// +//=========================================================================== +class CHostDrv +{ +public: + CHostDrv(); + ~CHostDrv(); + void Init(const TCHAR* szBase, DWORD nFlag); ///< Initialization (device startup and load) + + BOOL isWriteProtect() const { return m_bWriteProtect; } + BOOL isEnable() const { return m_bEnable; } ///< Is it accessible? + BOOL isMediaOffline(); + BYTE GetMediaByte() const; + DWORD GetStatus() const; + void SetEnable(BOOL bEnable); ///< Set media status + BOOL CheckMedia(); ///< Check if media was changed + void Update(); ///< Update media status + void Eject(); + void GetVolume(TCHAR* szLabel); ///< Get volume label + BOOL GetVolumeCache(TCHAR* szLabel) const; ///< Get volume label from cache + DWORD GetCapacity(Human68k::capacity_t* pCapacity); + BOOL GetCapacityCache(Human68k::capacity_t* pCapacity) const; ///< Get capacity from cache + + // Cache operations + void CleanCache(); ///< Update all cache + void CleanCache(const BYTE* szHumanPath); ///< Update cache for the specified path + void CleanCacheChild(const BYTE* szHumanPath); ///< Update all cache below the specified path + void DeleteCache(const BYTE* szHumanPath); ///< Delete the cache for the specified path + CHostPath* FindCache(const BYTE* szHuman); ///< Inspect if the specified path is cached + CHostPath* CopyCache(CHostFiles* pFiles); ///< Acquire the host side name on the basis of cache information + CHostPath* MakeCache(CHostFiles* pFiles); ///< Get all required data to construct a host side name + BOOL Find(CHostFiles* pFiles); ///< Find host side name (path + file name (can be abbreviated) + attribute) + +private: + // Path name operations + static const BYTE* SeparateCopyFilename(const BYTE* szHuman, BYTE* szBuffer); + ///< Split and copy the first element of the Human68k full path name + + void Lock() {} + void Unlock() {} + + /// For memory management + struct ring_t { + CRing r; + CHostPath f; + }; + + BOOL m_bWriteProtect; ///< TRUE if write-protected + BOOL m_bEnable; ///< TRUE if media is usable + DWORD m_nRing; ///< Number of stored path names + CRing m_cRing; ///< For attaching to CHostPath + Human68k::capacity_t m_capCache; ///< Sector data cache: if "sectors == 0" then not cached + BOOL m_bVolumeCache; ///< TRUE if the volume label has been read + TCHAR m_szVolumeCache[24]; ///< Volume label cache + TCHAR m_szBase[FILEPATH_MAX]; ///< Base path +}; + +//=========================================================================== +// +/// Directory entry management +// +//=========================================================================== +class CHostEntry { +public: + CHostEntry(); + ~CHostEntry(); + void Init(); ///< Initialization (when the driver is installed) + void Clean(); ///< Release (when starting up or resetting) + + // Cache operations + void CleanCache(); ///< Update all cache + void CleanCache(DWORD nUnit); ///< Update cache for the specified unit + void CleanCache(DWORD nUnit, const BYTE* szHumanPath); ///< Update cache for the specified path + void CleanCacheChild(DWORD nUnit, const BYTE* szHumanPath); ///< Update cache below the specified path + void DeleteCache(DWORD nUnit, const BYTE* szHumanPath); ///< Delete cache for the specified path + BOOL Find(DWORD nUnit, CHostFiles* pFiles); ///< Find host side name (path + file name (can be abbreviated) + attribute) + void ShellNotify(DWORD nEvent, const TCHAR* szPath); ///< Notify status change in the host side file system + + // Drive object operations + void SetDrv(DWORD nUnit, CHostDrv* pDrv); + BOOL isWriteProtect(DWORD nUnit) const; + BOOL isEnable(DWORD nUnit) const; ///< Is it accessible? + BOOL isMediaOffline(DWORD nUnit); + BYTE GetMediaByte(DWORD nUnit) const; + DWORD GetStatus(DWORD nUnit) const; ///< Get drive status + BOOL CheckMedia(DWORD nUnit); ///< Media change check + void Eject(DWORD nUnit); + void GetVolume(DWORD nUnit, TCHAR* szLabel); ///< Get volume label + BOOL GetVolumeCache(DWORD nUnit, TCHAR* szLabel) const; ///< Get volume label from cache + DWORD GetCapacity(DWORD nUnit, Human68k::capacity_t* pCapacity); + BOOL GetCapacityCache(DWORD nUnit, Human68k::capacity_t* pCapacity) const; + ///< Get cluster size from cache + + enum { + DriveMax = 10 ///< Max number of drive candidates + }; + +private: + CHostDrv* m_pDrv[DriveMax]; ///< Host side drive object + DWORD m_nTimeout; ///< Last time a timeout check was carried out +}; + +//=========================================================================== +// +/// Host side file system +// +//=========================================================================== +/** @note +Current state of affairs: + +While it violates the design philosophy of XM6, we should find a way for +'class Windrv' and 'class CWindrv' to have a direct pointer to 'class CFileSys'. +This way, we get the following benefits. + +Benefit no. 1 +Makes it possible to manage a large number of command handler methods in one place. +There is a high chance the command handlers will change drastically because of +host system architectural changes, so we will save a huge amount of maintenance work +in the long run. + +Benefit no. 2 +We would get rid of virtual funcion code for processing table creation and lookup. +It is not feasible to implement code in XM6 for simultaneous use of file system objects. +Therefore file system object polymorphism is a waste of CPU cycles. + +I made the change as an experiment. Performance did improve. +The improvement was obvious from looking at the source the compiler spit out +after changing the FILESYS_FAST_STRUCTURE value in windrv.h. +You may understand now why I decided to rant here. + +The easy solution is to put the content of 'class CFileSys' into 'class CWindrv'. +(To be honest, I really want to deprecate 'class CHost'... I wonder if there's a good way...) +*/ +class CFileSys +{ +public: + CFileSys(); + virtual ~CFileSys() {}; + + void Reset(); ///< Reset (close all) + void Init(); ///< Initialization (device startup and load) + + // Command handlers + DWORD InitDevice(const Human68k::argument_t* pArgument); ///< $40 - Device startup + int CheckDir(DWORD nUnit, const Human68k::namests_t* pNamests); ///< $41 - Directory check + int MakeDir(DWORD nUnit, const Human68k::namests_t* pNamests); ///< $42 - Create directory + int RemoveDir(DWORD nUnit, const Human68k::namests_t* pNamests); ///< $43 - Delete directory + int Rename(DWORD nUnit, const Human68k::namests_t* pNamests, const Human68k::namests_t* pNamestsNew); + ///< $44 - Change file name + int Delete(DWORD nUnit, const Human68k::namests_t* pNamests); ///< $45 - Delete file + int Attribute(DWORD nUnit, const Human68k::namests_t* pNamests, DWORD nHumanAttribute); + ///< $46 - Get / set file attribute + int Files(DWORD nUnit, DWORD nKey, const Human68k::namests_t* pNamests, Human68k::files_t* pFiles); + ///< $47 - Find file + int NFiles(DWORD nUnit, DWORD nKey, Human68k::files_t* pFiles); ///< $48 - Find next file + int Create(DWORD nUnit, DWORD nKey, const Human68k::namests_t* pNamests, Human68k::fcb_t* pFcb, DWORD nHumanAttribute, BOOL bForce); + ///< $49 - Create file + int Open(DWORD nUnit, DWORD nKey, const Human68k::namests_t* pNamests, Human68k::fcb_t* pFcb); + ///< $4A - Open file + int Close(DWORD nUnit, DWORD nKey, Human68k::fcb_t* pFcb); ///< $4B - Close file + int Read(DWORD nKey, Human68k::fcb_t* pFcb, BYTE* pAddress, DWORD nSize); + ///< $4C - Read file + int Write(DWORD nKey, Human68k::fcb_t* pFcb, const BYTE* pAddress, DWORD nSize); + ///< $4D - Write file + int Seek(DWORD nKey, Human68k::fcb_t* pFcb, DWORD nSeek, int nOffset); ///< $4E - Seek file + DWORD TimeStamp(DWORD nUnit, DWORD nKey, Human68k::fcb_t* pFcb, DWORD nHumanTime); + ///< $4F - Get / set file timestamp + int GetCapacity(DWORD nUnit, Human68k::capacity_t* pCapacity); ///< $50 - Get capacity + int CtrlDrive(DWORD nUnit, Human68k::ctrldrive_t* pCtrlDrive); ///< $51 - Inspect / control drive status + int GetDPB(DWORD nUnit, Human68k::dpb_t* pDpb); ///< $52 - Get DPB + int DiskRead(DWORD nUnit, BYTE* pBuffer, DWORD nSector, DWORD nSize); ///< $53 - Read sectors + int DiskWrite(DWORD nUnit); ///< $54 - Write sectors + int Ioctrl(DWORD nUnit, DWORD nFunction, Human68k::ioctrl_t* pIoctrl); ///< $55 - IOCTRL + int Flush(DWORD nUnit); ///< $56 - Flush + int CheckMedia(DWORD nUnit); ///< $57 - Media change check + int Lock(DWORD nUnit); ///< $58 - Lock + + void SetOption(DWORD nOption); ///< Set option + DWORD GetOption() const { return m_nOption; } ///< Get option + DWORD GetDefault() const { return m_nOptionDefault; } ///< Get default options + static DWORD GetFileOption() { return g_nOption; } ///< Get file name change option + void ShellNotify(DWORD nEvent, const TCHAR* szPath) + { m_cEntry.ShellNotify(nEvent, szPath); } ///< Notify host side file system status change + + enum { + DriveMax = CHostEntry::DriveMax ///< Max number of drive candidates + }; + +private: + void InitOption(const Human68k::argument_t* pArgument); + BOOL FilesVolume(DWORD nUnit, Human68k::files_t* pFiles); ///< Get volume label + + DWORD m_nUnits; ///< Number of current drive objects (Changes for every resume) + + DWORD m_nOption; ///< Current runtime flag + DWORD m_nOptionDefault; ///< Runtime flag at reset + + DWORD m_nDrives; ///< Number of candidates for base path status restoration (scan every time if 0) + + DWORD m_nKernel; ///< Counter for kernel check + DWORD m_nKernelSearch; ///< Initial address for NUL device + + DWORD m_nHostSectorCount; ///< Virtual sector identifier + + CHostFilesManager m_cFiles; ///< File search memory + CHostFcbManager m_cFcb; ///< FCB operation memory + CHostEntry m_cEntry; ///< Drive object and directory entry + + DWORD m_nHostSectorBuffer[XM6_HOST_PSEUDO_CLUSTER_MAX]; + ///< Entity that the virtual sector points to + + DWORD m_nFlag[DriveMax]; ///< Candidate runtime flag for base path restoration + TCHAR m_szBase[DriveMax][FILEPATH_MAX]; ///< Candidate for base path restoration + static DWORD g_nOption; ///< File name change flag +}; diff --git a/src_old/raspberrypi/devices/ctapdriver.cpp b/src_old/raspberrypi/devices/ctapdriver.cpp new file mode 100644 index 00000000..3a328339 --- /dev/null +++ b/src_old/raspberrypi/devices/ctapdriver.cpp @@ -0,0 +1,535 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Powered by XM6 TypeG Technology. +// Copyright (C) 2016-2020 GIMONS +// Copyright (C) akuker +// +// [ TAP Driver ] +// +//--------------------------------------------------------------------------- + +#include +#include +#ifdef __linux__ +#include +#include +#include +#endif +#include "os.h" +#include "ctapdriver.h" +#include "log.h" +#include "rasutil.h" +#include "exceptions.h" +#include + +#define BRIDGE_NAME "rascsi_bridge" + +using namespace std; +using namespace ras_util; + +CTapDriver::CTapDriver() +{ + m_hTAP = -1; + memset(&m_MacAddr, 0, sizeof(m_MacAddr)); + m_pcap = NULL; + m_pcap_dumper = NULL; +} + +//--------------------------------------------------------------------------- +// +// Initialization +// +//--------------------------------------------------------------------------- +static bool br_setif(int br_socket_fd, const char* bridgename, const char* ifname, bool add) { + struct ifreq ifr; + ifr.ifr_ifindex = if_nametoindex(ifname); + if (ifr.ifr_ifindex == 0) { + LOGERROR("Can't if_nametoindex: %s", strerror(errno)); + return false; + } + strncpy(ifr.ifr_name, bridgename, IFNAMSIZ); + if (ioctl(br_socket_fd, add ? SIOCBRADDIF : SIOCBRDELIF, &ifr) < 0) { + LOGERROR("Can't ioctl %s: %s", add ? "SIOCBRADDIF" : "SIOCBRDELIF", strerror(errno)); + return false; + } + return true; +} + +static bool ip_link(int fd, const char* ifname, bool up) { + struct ifreq ifr; + strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1); // Need to save room for null terminator + int err = ioctl(fd, SIOCGIFFLAGS, &ifr); + if (err) { + LOGERROR("Can't ioctl SIOCGIFFLAGS: %s", strerror(errno)); + return false; + } + ifr.ifr_flags &= ~IFF_UP; + if (up) { + ifr.ifr_flags |= IFF_UP; + } + err = ioctl(fd, SIOCSIFFLAGS, &ifr); + if (err) { + LOGERROR("Can't ioctl SIOCSIFFLAGS: %s", strerror(errno)); + return false; + } + return true; +} + +static bool is_interface_up(const string& interface) { + string file = "/sys/class/net/"; + file += interface; + file += "/carrier"; + + bool status = true; + FILE *fp = fopen(file.c_str(), "r"); + if (!fp || fgetc(fp) != '1') { + status = false; + } + + if (fp) { + fclose(fp); + } + + return status; +} + +bool CTapDriver::Init(const map& const_params) +{ + map params = const_params; + if (params.count("interfaces")) { + LOGWARN("You are using the deprecated 'interfaces' parameter. " + "Provide the interface list and the IP address/netmask with the 'interface' and 'inet' parameters"); + + // TODO Remove the deprecated syntax in a future version + const string& interfaces = params["interfaces"]; + size_t separatorPos = interfaces.find(':'); + if (separatorPos != string::npos) { + params["interface"] = interfaces.substr(0, separatorPos); + params["inet"] = interfaces.substr(separatorPos + 1); + } + } + + stringstream s(params["interface"]); + string interface; + while (getline(s, interface, ',')) { + this->interfaces.push_back(interface); + } + this->inet = params["inet"]; + + LOGTRACE("Opening Tap device"); + // TAP device initilization + if ((m_hTAP = open("/dev/net/tun", O_RDWR)) < 0) { + LOGERROR("Can't open tun: %s", strerror(errno)); + return false; + } + + LOGTRACE("Opened tap device %d",m_hTAP); + + // IFF_NO_PI for no extra packet information + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_flags = IFF_TAP | IFF_NO_PI; + char dev[IFNAMSIZ] = "ras0"; + strncpy(ifr.ifr_name, dev, IFNAMSIZ); + + LOGTRACE("Going to open %s", ifr.ifr_name); + + int ret = ioctl(m_hTAP, TUNSETIFF, (void *)&ifr); + if (ret < 0) { + LOGERROR("Can't ioctl TUNSETIFF: %s", strerror(errno)); + + close(m_hTAP); + return false; + } + + LOGTRACE("Return code from ioctl was %d", ret); + + int ip_fd = socket(PF_INET, SOCK_DGRAM, 0); + if (ip_fd < 0) { + LOGERROR("Can't open ip socket: %s", strerror(errno)); + + close(m_hTAP); + return false; + } + + int br_socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0); + if (br_socket_fd < 0) { + LOGERROR("Can't open bridge socket: %s", strerror(errno)); + + close(m_hTAP); + close(ip_fd); + return false; + } + + // Check if the bridge has already been created + string sys_file = "/sys/class/net/"; + sys_file += BRIDGE_NAME; + if (access(sys_file.c_str(), F_OK)) { + LOGINFO("%s is not yet available", BRIDGE_NAME); + + LOGTRACE("Checking which interface is available for creating the bridge"); + + string bridge_interface; + for (const string& interface : interfaces) { + if (is_interface_up(interface)) { + LOGTRACE("%s", string("Interface " + interface + " is up").c_str()); + + bridge_interface = interface; + break; + } + else { + LOGTRACE("%s", string("Interface " + interface + " is not available or is not up").c_str()); + } + } + + if (bridge_interface.empty()) { + LOGERROR("No interface is up, not creating bridge"); + return false; + } + + LOGINFO("Creating %s for interface %s", BRIDGE_NAME, bridge_interface.c_str()); + + if (bridge_interface == "eth0") { + LOGTRACE("brctl addbr %s", BRIDGE_NAME); + + if ((ret = ioctl(br_socket_fd, SIOCBRADDBR, BRIDGE_NAME)) < 0) { + LOGERROR("Can't ioctl SIOCBRADDBR: %s", strerror(errno)); + + close(m_hTAP); + close(ip_fd); + close(br_socket_fd); + return false; + } + + LOGTRACE("brctl addif %s %s", BRIDGE_NAME, bridge_interface.c_str()); + + if (!br_setif(br_socket_fd, BRIDGE_NAME, bridge_interface.c_str(), true)) { + close(m_hTAP); + close(ip_fd); + close(br_socket_fd); + return false; + } + } + else { + string address = inet; + string netmask = "255.255.255.0"; + size_t separatorPos = inet.find('/'); + if (separatorPos != string::npos) { + address = inet.substr(0, separatorPos); + + int m; + if (!GetAsInt(inet.substr(separatorPos + 1), m) || m < 8 || m > 32) { + LOGERROR("Invalid CIDR netmask notation '%s'", inet.substr(separatorPos + 1).c_str()); + + close(m_hTAP); + close(ip_fd); + close(br_socket_fd); + return false; + } + + // long long is required for compatibility with 32 bit platforms + long long mask = pow(2, 32) - (1 << (32 - m)); + char buf[16]; + sprintf(buf, "%lld.%lld.%lld.%lld", (mask >> 24) & 0xff, (mask >> 16) & 0xff, (mask >> 8) & 0xff, + mask & 0xff); + netmask = buf; + } + + LOGTRACE("brctl addbr %s", BRIDGE_NAME); + + if ((ret = ioctl(br_socket_fd, SIOCBRADDBR, BRIDGE_NAME)) < 0) { + LOGERROR("Can't ioctl SIOCBRADDBR: %s", strerror(errno)); + + close(m_hTAP); + close(ip_fd); + close(br_socket_fd); + return false; + } + + struct ifreq ifr_a; + ifr_a.ifr_addr.sa_family = AF_INET; + strncpy(ifr_a.ifr_name, BRIDGE_NAME, IFNAMSIZ); + struct sockaddr_in* addr = (struct sockaddr_in*)&ifr_a.ifr_addr; + if (inet_pton(AF_INET, address.c_str(), &addr->sin_addr) != 1) { + LOGERROR("Can't convert '%s' into a network address: %s", address.c_str(), strerror(errno)); + + close(m_hTAP); + close(ip_fd); + close(br_socket_fd); + return false; + } + + struct ifreq ifr_n; + ifr_n.ifr_addr.sa_family = AF_INET; + strncpy(ifr_n.ifr_name, BRIDGE_NAME, IFNAMSIZ); + struct sockaddr_in* mask = (struct sockaddr_in*)&ifr_n.ifr_addr; + if (inet_pton(AF_INET, netmask.c_str(), &mask->sin_addr) != 1) { + LOGERROR("Can't convert '%s' into a netmask: %s", netmask.c_str(), strerror(errno)); + + close(m_hTAP); + close(ip_fd); + close(br_socket_fd); + return false; + } + + LOGTRACE("ip address add %s dev %s", inet.c_str(), BRIDGE_NAME); + + if (ioctl(ip_fd, SIOCSIFADDR, &ifr_a) < 0 || ioctl(ip_fd, SIOCSIFNETMASK, &ifr_n) < 0) { + LOGERROR("Can't ioctl SIOCSIFADDR or SIOCSIFNETMASK: %s", strerror(errno)); + + close(m_hTAP); + close(ip_fd); + close(br_socket_fd); + return false; + } + } + + LOGTRACE("ip link set dev %s up", BRIDGE_NAME); + + if (!ip_link(ip_fd, BRIDGE_NAME, true)) { + close(m_hTAP); + close(ip_fd); + close(br_socket_fd); + return false; + } + } + else + { + LOGINFO("%s is already available", BRIDGE_NAME); + } + + LOGTRACE("ip link set ras0 up"); + + if (!ip_link(ip_fd, "ras0", true)) { + close(m_hTAP); + close(ip_fd); + close(br_socket_fd); + return false; + } + + LOGTRACE("brctl addif %s ras0", BRIDGE_NAME); + + if (!br_setif(br_socket_fd, BRIDGE_NAME, "ras0", true)) { + close(m_hTAP); + close(ip_fd); + close(br_socket_fd); + return false; + } + + // Get MAC address + LOGTRACE("Getting the MAC address"); + + ifr.ifr_addr.sa_family = AF_INET; + if ((ret = ioctl(m_hTAP, SIOCGIFHWADDR, &ifr)) < 0) { + LOGERROR("Can't ioctl SIOCGIFHWADDR: %s", strerror(errno)); + + close(m_hTAP); + close(ip_fd); + close(br_socket_fd); + return false; + } + LOGTRACE("Got the MAC"); + + // Save MAC address + memcpy(m_MacAddr, ifr.ifr_hwaddr.sa_data, sizeof(m_MacAddr)); + + close(ip_fd); + close(br_socket_fd); + + LOGINFO("Tap device %s created", ifr.ifr_name); + + return true; +} + +void CTapDriver::OpenDump(const Filepath& path) { + if (m_pcap == NULL) { + m_pcap = pcap_open_dead(DLT_EN10MB, 65535); + } + if (m_pcap_dumper != NULL) { + pcap_dump_close(m_pcap_dumper); + } + m_pcap_dumper = pcap_dump_open(m_pcap, path.GetPath()); + if (m_pcap_dumper == NULL) { + LOGERROR("Can't open pcap file: %s", pcap_geterr(m_pcap)); + throw io_exception("Can't open pcap file"); + } + + LOGTRACE("%s Opened %s for dumping", __PRETTY_FUNCTION__, path.GetPath()); +} + +void CTapDriver::Cleanup() +{ + int br_socket_fd = -1; + if ((br_socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) { + LOGERROR("Can't open bridge socket: %s", strerror(errno)); + } else { + LOGDEBUG("brctl delif %s ras0", BRIDGE_NAME); + if (!br_setif(br_socket_fd, BRIDGE_NAME, "ras0", false)) { + LOGWARN("Warning: Removing ras0 from the bridge failed."); + LOGWARN("You may need to manually remove the ras0 tap device from the bridge"); + } + close(br_socket_fd); + } + + // Release TAP defice + if (m_hTAP != -1) { + close(m_hTAP); + m_hTAP = -1; + } + + if (m_pcap_dumper != NULL) { + pcap_dump_close(m_pcap_dumper); + m_pcap_dumper = NULL; + } + + if (m_pcap != NULL) { + pcap_close(m_pcap); + m_pcap = NULL; + } +} + +bool CTapDriver::Enable(){ + int fd = socket(PF_INET, SOCK_DGRAM, 0); + LOGDEBUG("%s: ip link set ras0 up", __PRETTY_FUNCTION__); + bool result = ip_link(fd, "ras0", true); + close(fd); + return result; +} + +bool CTapDriver::Disable(){ + int fd = socket(PF_INET, SOCK_DGRAM, 0); + LOGDEBUG("%s: ip link set ras0 down", __PRETTY_FUNCTION__); + bool result = ip_link(fd, "ras0", false); + close(fd); + return result; +} + +void CTapDriver::Flush(){ + LOGTRACE("%s", __PRETTY_FUNCTION__); + while(PendingPackets()){ + (void)Rx(m_garbage_buffer); + } +} + +void CTapDriver::GetMacAddr(BYTE *mac) +{ + ASSERT(mac); + + memcpy(mac, m_MacAddr, sizeof(m_MacAddr)); +} + +//--------------------------------------------------------------------------- +// +// Receive +// +//--------------------------------------------------------------------------- +bool CTapDriver::PendingPackets() +{ + struct pollfd fds; + + ASSERT(m_hTAP != -1); + + // Check if there is data that can be received + fds.fd = m_hTAP; + fds.events = POLLIN | POLLERR; + fds.revents = 0; + poll(&fds, 1, 0); + LOGTRACE("%s %u revents", __PRETTY_FUNCTION__, fds.revents); + if (!(fds.revents & POLLIN)) { + return false; + }else { + return true; + } +} + +// See https://stackoverflow.com/questions/21001659/crc32-algorithm-implementation-in-c-without-a-look-up-table-and-with-a-public-li +uint32_t crc32(BYTE *buf, int length) { + uint32_t crc = 0xffffffff; + for (int i = 0; i < length; i++) { + crc ^= buf[i]; + for (int j = 0; j < 8; j++) { + uint32_t mask = -(crc & 1); + crc = (crc >> 1) ^ (0xEDB88320 & mask); + } + } + return ~crc; +} + +//--------------------------------------------------------------------------- +// +// Receive +// +//--------------------------------------------------------------------------- +int CTapDriver::Rx(BYTE *buf) +{ + ASSERT(m_hTAP != -1); + + // Check if there is data that can be received + if (!PendingPackets()) { + return 0; + } + + // Receive + DWORD dwReceived = read(m_hTAP, buf, ETH_FRAME_LEN); + if (dwReceived == (DWORD)-1) { + LOGWARN("%s Error occured while receiving an packet", __PRETTY_FUNCTION__); + return 0; + } + + // If reception is enabled + if (dwReceived > 0) { + // We need to add the Frame Check Status (FCS) CRC back onto the end of the packet. + // The Linux network subsystem removes it, since most software apps shouldn't ever + // need it. + int crc = crc32(buf, dwReceived); + + buf[dwReceived + 0] = (BYTE)((crc >> 0) & 0xFF); + buf[dwReceived + 1] = (BYTE)((crc >> 8) & 0xFF); + buf[dwReceived + 2] = (BYTE)((crc >> 16) & 0xFF); + buf[dwReceived + 3] = (BYTE)((crc >> 24) & 0xFF); + + LOGDEBUG("%s CRC is %08X - %02X %02X %02X %02X\n", __PRETTY_FUNCTION__, crc, buf[dwReceived+0], buf[dwReceived+1], buf[dwReceived+2], buf[dwReceived+3]); + + // Add FCS size to the received message size + dwReceived += 4; + } + + if (m_pcap_dumper != NULL) { + struct pcap_pkthdr h = { + .caplen = dwReceived, + .len = dwReceived, + }; + gettimeofday(&h.ts, NULL); + pcap_dump((u_char*)m_pcap_dumper, &h, buf); + LOGTRACE("%s Dumped %d byte packet (first byte: %02x last byte: %02x)", __PRETTY_FUNCTION__, (unsigned int)dwReceived, buf[0], buf[dwReceived-1]); + } + + // Return the number of bytes + return dwReceived; +} + +//--------------------------------------------------------------------------- +// +// Send +// +//--------------------------------------------------------------------------- +int CTapDriver::Tx(const BYTE *buf, int len) +{ + ASSERT(m_hTAP != -1); + + if (m_pcap_dumper != NULL) { + struct pcap_pkthdr h = { + .caplen = (bpf_u_int32)len, + .len = (bpf_u_int32)len, + }; + gettimeofday(&h.ts, NULL); + pcap_dump((u_char*)m_pcap_dumper, &h, buf); + LOGTRACE("%s Dumped %d byte packet (first byte: %02x last byte: %02x)", __PRETTY_FUNCTION__, (unsigned int)h.len, buf[0], buf[h.len-1]); + } + + // Start sending + return write(m_hTAP, buf, len); +} diff --git a/src_old/raspberrypi/devices/ctapdriver.h b/src_old/raspberrypi/devices/ctapdriver.h new file mode 100644 index 00000000..70ff51e3 --- /dev/null +++ b/src_old/raspberrypi/devices/ctapdriver.h @@ -0,0 +1,72 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Powered by XM6 TypeG Technology. +// Copyright (C) 2016-2020 GIMONS +// Copyright (C) akuker +// +// [ TAP Driver ] +// +//--------------------------------------------------------------------------- + +#pragma once + +#include +#include "filepath.h" +#include +#include +#include + +#ifndef ETH_FRAME_LEN +#define ETH_FRAME_LEN 1514 +#endif + +using namespace std; + +//=========================================================================== +// +// Linux Tap Driver +// +//=========================================================================== +class CTapDriver +{ +private: + + friend class SCSIDaynaPort; + friend class SCSIBR; + + CTapDriver(); + ~CTapDriver() {} + + bool Init(const map&); + +public: + + void OpenDump(const Filepath& path); + // Capture packets + void Cleanup(); // Cleanup + void GetMacAddr(BYTE *mac); // Get Mac Address + int Rx(BYTE *buf); // Receive + int Tx(const BYTE *buf, int len); // Send + bool PendingPackets(); // Check if there are IP packets available + bool Enable(); // Enable the ras0 interface + bool Disable(); // Disable the ras0 interface + void Flush(); // Purge all of the packets that are waiting to be processed + +private: + BYTE m_MacAddr[6]; // MAC Address + int m_hTAP; // File handle + + BYTE m_garbage_buffer[ETH_FRAME_LEN]; + + pcap_t *m_pcap; + pcap_dumper_t *m_pcap_dumper; + + // Prioritized comma-separated list of interfaces to create the bridge for + vector interfaces; + + string inet; +}; + diff --git a/src_old/raspberrypi/devices/device.cpp b/src_old/raspberrypi/devices/device.cpp new file mode 100644 index 00000000..83658f4a --- /dev/null +++ b/src_old/raspberrypi/devices/device.cpp @@ -0,0 +1,179 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2021 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include +#include "rascsi_version.h" +#include "os.h" +#include "log.h" +#include "exceptions.h" +#include "device.h" + +set Device::devices; + +Device::Device(const string& type) +{ + assert(type.length() == 4); + + devices.insert(this); + + this->type = type; + + vendor = DEFAULT_VENDOR; + char rev[5]; + sprintf(rev, "%02d%02d", rascsi_major_version, rascsi_minor_version); + revision = rev; + + ready = false; + reset = false; + attn = false; + supported_luns = 32; + protectable = false; + write_protected = false; + read_only = false; + stoppable = false; + stopped = false; + removable = false; + removed = false; + lockable = false; + locked = false; + block_size_configurable = false; + supports_params = false; + + id = 0; + lun = 0; + + status_code = STATUS_NOERROR; +} + +Device::~Device() +{ + devices.erase(this); +} + +void Device::Reset() +{ + locked = false; + attn = false; + reset = false; +} + +void Device::SetProtected(bool write_protected) +{ + if (!read_only) { + this->write_protected = write_protected; + } +} + +void Device::SetVendor(const string& vendor) +{ + if (vendor.empty() || vendor.length() > 8) { + throw illegal_argument_exception("Vendor '" + vendor + "' must be between 1 and 8 characters"); + } + + this->vendor = vendor; +} + +void Device::SetProduct(const string& product, bool force) +{ + // Changing the device name is not SCSI compliant + if (!this->product.empty() && !force) { + return; + } + + if (product.empty() || product.length() > 16) { + throw illegal_argument_exception("Product '" + product + "' must be between 1 and 16 characters"); + } + + this->product = product; +} + +void Device::SetRevision(const string& revision) +{ + if (revision.empty() || revision.length() > 4) { + throw illegal_argument_exception("Revision '" + revision + "' must be between 1 and 4 characters"); + } + + this->revision = revision; +} + +const string Device::GetPaddedName() const +{ + string name = vendor; + name.append(8 - vendor.length(), ' '); + name += product; + name.append(16 - product.length(), ' '); + name += revision; + name.append(4 - revision.length(), ' '); + + assert(name.length() == 28); + + return name; +} + +const string Device::GetParam(const string& key) +{ + return params.find(key) != params.end() ? params[key] : ""; +} + +void Device::SetParams(const map& params) +{ + this->params = GetDefaultParams(); + + for (const auto& param : params) { + this->params[param.first] = param.second; + } +} + +void Device::SetStatusCode(int status_code) +{ + if (status_code) { + LOGDEBUG("Error status: Sense Key $%02X, ASC $%02X, ASCQ $%02X", status_code >> 16, (status_code >> 8 &0xff), status_code & 0xff); + } + + this->status_code = status_code; +} + +bool Device::Start() +{ + if (!ready) { + return false; + } + + stopped = false; + + return true; +} + +void Device::Stop() +{ + ready = false; + attn = false; + stopped = true; +} + +bool Device::Eject(bool force) +{ + if (!ready || !removable) { + return false; + } + + // Must be unlocked if there is no force flag + if (!force && locked) { + return false; + } + + ready = false; + attn = false; + removed = true; + write_protected = false; + locked = false; + stopped = true; + + return true; +} diff --git a/src_old/raspberrypi/devices/device.h b/src_old/raspberrypi/devices/device.h new file mode 100644 index 00000000..b3b321f8 --- /dev/null +++ b/src_old/raspberrypi/devices/device.h @@ -0,0 +1,187 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2021 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#pragma once + +#include +#include +#include + +using namespace std; + +#define DEFAULT_VENDOR "RaSCSI" + +//--------------------------------------------------------------------------- +// +// Error definition (sense code returned by REQUEST SENSE) +// +// MSB Reserved (0x00) +// Sense Key +// Additional Sense Code (ASC) +// LSB Additional Sense Code Qualifier(ASCQ) +// +//--------------------------------------------------------------------------- +#define STATUS_NOERROR 0x00000000 // NO ADDITIONAL SENSE INFO. +#define STATUS_DEVRESET 0x00062900 // POWER ON OR RESET OCCURED +#define STATUS_NOTREADY 0x00023a00 // MEDIUM NOT PRESENT +#define STATUS_ATTENTION 0x00062800 // MEDIUM MAY HAVE CHANGED +#define STATUS_PREVENT 0x00045302 // MEDIUM REMOVAL PREVENTED +#define STATUS_READFAULT 0x00031100 // UNRECOVERED READ ERROR +#define STATUS_WRITEFAULT 0x00030300 // PERIPHERAL DEVICE WRITE FAULT +#define STATUS_WRITEPROTECT 0x00042700 // WRITE PROTECTED +#define STATUS_MISCOMPARE 0x000e1d00 // MISCOMPARE DURING VERIFY +#define STATUS_INVALIDCMD 0x00052000 // INVALID COMMAND OPERATION CODE +#define STATUS_INVALIDLBA 0x00052100 // LOGICAL BLOCK ADDR. OUT OF RANGE +#define STATUS_INVALIDCDB 0x00052400 // INVALID FIELD IN CDB +#define STATUS_INVALIDLUN 0x00052500 // LOGICAL UNIT NOT SUPPORTED +#define STATUS_INVALIDPRM 0x00052600 // INVALID FIELD IN PARAMETER LIST +#define STATUS_INVALIDMSG 0x00054900 // INVALID MESSAGE ERROR +#define STATUS_PARAMLEN 0x00051a00 // PARAMETERS LIST LENGTH ERROR +#define STATUS_PARAMNOT 0x00052601 // PARAMETERS NOT SUPPORTED +#define STATUS_PARAMVALUE 0x00052602 // PARAMETERS VALUE INVALID +#define STATUS_PARAMSAVE 0x00053900 // SAVING PARAMETERS NOT SUPPORTED +#define STATUS_NODEFECT 0x00010000 // DEFECT LIST NOT FOUND + +class SCSIDEV; + +class Device +{ +private: + + string type; + + bool ready; + bool reset; + bool attn; + + // Number of supported luns + int supported_luns; + + // Device is protectable/write-protected + bool protectable; + bool write_protected; + // Device is permanently read-only + bool read_only; + + // Device can be stopped (parked)/is stopped (parked) + bool stoppable; + bool stopped; + + // Device is removable/removed + bool removable; + bool removed; + + // Device is lockable/locked + bool lockable; + bool locked; + + // The block size is configurable + bool block_size_configurable; + + // Device can be created with parameters + bool supports_params; + + // Device ID and LUN + int32_t id; + int32_t lun; + + // Device identifier (for INQUIRY) + string vendor; + string product; + string revision; + + // The parameters the device was created with + map params; + + // The default parameters + map default_params; + + // Sense Key, ASC and ASCQ + int status_code; + +protected: + + static set devices; + +public: + + Device(const string&); + virtual ~Device(); + + // Override for device specific initializations, to be called after all device properties have been set + virtual bool Init(const map&) { return true; }; + + virtual bool Dispatch(SCSIDEV *) = 0; + + const string& GetType() const { return type; } + + bool IsReady() const { return ready; } + void SetReady(bool ready) { this->ready = ready; } + bool IsReset() const { return reset; } + void SetReset(bool reset) { this->reset = reset; } + void Reset(); + bool IsAttn() const { return attn; } + void SetAttn(bool attn) { this->attn = attn; } + + int GetSupportedLuns() const { return supported_luns; } + void SetSupportedLuns(int supported_luns) { this->supported_luns = supported_luns; } + + bool IsProtectable() const { return protectable; } + void SetProtectable(bool protectable) { this->protectable = protectable; } + bool IsProtected() const { return write_protected; } + void SetProtected(bool); + bool IsReadOnly() const { return read_only; } + void SetReadOnly(bool read_only) { this->read_only = read_only; } + + bool IsStoppable() const { return stoppable; } + void SetStoppable(bool stoppable) { this->stoppable = stoppable; } + bool IsStopped() const { return stopped; } + void SetStopped(bool stopped) { this->stopped = stopped; } + bool IsRemovable() const { return removable; } + void SetRemovable(bool removable) { this->removable = removable; } + bool IsRemoved() const { return removed; } + void SetRemoved(bool removed) { this->removed = removed; } + + bool IsLockable() const { return lockable; } + void SetLockable(bool lockable) { this->lockable = lockable; } + bool IsLocked() const { return locked; } + void SetLocked(bool locked) { this->locked = locked; } + + int32_t GetId() const { return id; } + void SetId(int32_t id) { this->id = id; } + int32_t GetLun() const { return lun; } + void SetLun(int32_t lun) { this->lun = lun; } + + const string GetVendor() const { return vendor; } + void SetVendor(const string&); + const string GetProduct() const { return product; } + void SetProduct(const string&, bool = true); + const string GetRevision() const { return revision; } + void SetRevision(const string&); + const string GetPaddedName() const; + + bool SupportsParams() const { return supports_params; } + bool SupportsFile() const { return !supports_params; } + void SupportsParams(bool supports_paams) { this->supports_params = supports_paams; } + const map GetParams() const { return params; } + const string GetParam(const string&); + void SetParams(const map&); + const map GetDefaultParams() const { return default_params; } + void SetDefaultParams(const map& default_params) { this->default_params = default_params; } + + int GetStatusCode() const { return status_code; } + void SetStatusCode(int); + + bool Start(); + void Stop(); + virtual bool Eject(bool); + + bool IsSASIHD() const { return type == "SAHD"; } + bool IsSCSIHD() const { return type == "SCHD" || type == "SCRM"; } +}; diff --git a/src_old/raspberrypi/devices/device_factory.cpp b/src_old/raspberrypi/devices/device_factory.cpp new file mode 100644 index 00000000..989cf7f1 --- /dev/null +++ b/src_old/raspberrypi/devices/device_factory.cpp @@ -0,0 +1,273 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2021 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include "sasihd.h" +#include "scsihd.h" +#include "scsihd_nec.h" +#include "scsimo.h" +#include "scsicd.h" +#include "scsi_printer.h" +#include "scsi_host_bridge.h" +#include "scsi_daynaport.h" +#include "exceptions.h" +#include "device_factory.h" +#include +#include +#include +#include "host_services.h" + +using namespace std; +using namespace rascsi_interface; + +DeviceFactory::DeviceFactory() +{ + sector_sizes[SAHD] = { 256, 1024 }; + sector_sizes[SCHD] = { 512, 1024, 2048, 4096 }; + sector_sizes[SCRM] = { 512, 1024, 2048, 4096 }; + sector_sizes[SCMO] = { 512, 1024, 2048, 4096 }; + sector_sizes[SCCD] = { 512, 2048}; + + // 128 MB, 512 bytes per sector, 248826 sectors + geometries[SCMO][0x797f400] = make_pair(512, 248826); + // 230 MB, 512 bytes per block, 446325 sectors + geometries[SCMO][0xd9eea00] = make_pair(512, 446325); + // 540 MB, 512 bytes per sector, 1041500 sectors + geometries[SCMO][0x1fc8b800] = make_pair(512, 1041500); + // 640 MB, 20248 bytes per sector, 310352 sectors + geometries[SCMO][0x25e28000] = make_pair(2048, 310352); + + string network_interfaces; + for (const auto& network_interface : GetNetworkInterfaces()) { + if (!network_interfaces.empty()) { + network_interfaces += ","; + } + network_interfaces += network_interface; + } + + default_params[SCBR]["interface"] = network_interfaces; + default_params[SCBR]["inet"] = "10.10.20.1/24"; + default_params[SCDP]["interface"] = network_interfaces; + default_params[SCDP]["inet"] = "10.10.20.1/24"; + default_params[SCLP]["cmd"] = "lp -oraw %f"; + default_params[SCLP]["timeout"] = "30"; + + extension_mapping["hdf"] = SAHD; + extension_mapping["hds"] = SCHD; + extension_mapping["hda"] = SCHD; + extension_mapping["hdn"] = SCHD; + extension_mapping["hdi"] = SCHD; + extension_mapping["nhd"] = SCHD; + extension_mapping["hdr"] = SCRM; + extension_mapping["mos"] = SCMO; + extension_mapping["iso"] = SCCD; +} + +DeviceFactory& DeviceFactory::instance() +{ + static DeviceFactory instance; + return instance; +} + +string DeviceFactory::GetExtension(const string& filename) const +{ + string ext; + size_t separator = filename.rfind('.'); + if (separator != string::npos) { + ext = filename.substr(separator + 1); + } + std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c){ return std::tolower(c); }); + + return ext; +} + +PbDeviceType DeviceFactory::GetTypeForFile(const string& filename) +{ + string ext = GetExtension(filename); + + if (extension_mapping.find(ext) != extension_mapping.end()) { + return extension_mapping[ext]; + } + else if (filename == "bridge") { + return SCBR; + } + else if (filename == "daynaport") { + return SCDP; + } + else if (filename == "printer") { + return SCLP; + } + else if (filename == "services") { + return SCHS; + } + + return UNDEFINED; +} + +Device *DeviceFactory::CreateDevice(PbDeviceType type, const string& filename) +{ + // If no type was specified try to derive the device type from the filename + if (type == UNDEFINED) { + type = GetTypeForFile(filename); + if (type == UNDEFINED) { + return NULL; + } + } + + Device *device = NULL; + try { + switch (type) { + case SAHD: + device = new SASIHD(sector_sizes[SAHD]); + device->SetSupportedLuns(2); + device->SetProduct("SASI HD"); + break; + + case SCHD: { + string ext = GetExtension(filename); + if (ext == "hdn" || ext == "hdi" || ext == "nhd") { + device = new SCSIHD_NEC({ 512 }); + } else { + device = new SCSIHD(sector_sizes[SCHD], false); + + // Some Apple tools require a particular drive identification + if (ext == "hda") { + device->SetVendor("QUANTUM"); + device->SetProduct("FIREBALL"); + } + } + device->SetProtectable(true); + device->SetStoppable(true); + break; + } + + case SCRM: + device = new SCSIHD(sector_sizes[SCRM], true); + device->SetProtectable(true); + device->SetStoppable(true); + device->SetRemovable(true); + device->SetLockable(true); + device->SetProduct("SCSI HD (REM.)"); + break; + + case SCMO: + device = new SCSIMO(sector_sizes[SCMO], geometries[SCMO]); + device->SetProtectable(true); + device->SetStoppable(true); + device->SetRemovable(true); + device->SetLockable(true); + device->SetProduct("SCSI MO"); + break; + + case SCCD: + device = new SCSICD(sector_sizes[SCCD]); + device->SetReadOnly(true); + device->SetStoppable(true); + device->SetRemovable(true); + device->SetLockable(true); + device->SetProduct("SCSI CD-ROM"); + break; + + case SCBR: + device = new SCSIBR(); + device->SetProduct("SCSI HOST BRIDGE"); + device->SupportsParams(true); + device->SetDefaultParams(default_params[SCBR]); + break; + + case SCDP: + device = new SCSIDaynaPort(); + // Since this is an emulation for a specific device the full INQUIRY data have to be set accordingly + device->SetVendor("Dayna"); + device->SetProduct("SCSI/Link"); + device->SetRevision("1.4a"); + device->SupportsParams(true); + device->SetDefaultParams(default_params[SCDP]); + break; + + case SCHS: + device = new HostServices(); + // Since this is an emulation for a specific device the full INQUIRY data have to be set accordingly + device->SetVendor("RaSCSI"); + device->SetProduct("Host Services"); + break; + + case SCLP: + device = new SCSIPrinter(); + device->SetProduct("SCSI PRINTER"); + device->SupportsParams(true); + device->SetDefaultParams(default_params[SCLP]); + break; + + default: + break; + } + } + catch(const illegal_argument_exception& e) { + // There was an internal problem with setting up the device data for INQUIRY + return NULL; + } + + return device; +} + +const set& DeviceFactory::GetSectorSizes(const string& type) +{ + PbDeviceType t = UNDEFINED; + PbDeviceType_Parse(type, &t); + return sector_sizes[t]; +} + +const set DeviceFactory::GetCapacities(PbDeviceType type) +{ + set keys; + + for (const auto& geometry : geometries[type]) { + keys.insert(geometry.first); + } + + return keys; +} + +const list DeviceFactory::GetNetworkInterfaces() const +{ + list network_interfaces; + + struct ifaddrs *addrs; + getifaddrs(&addrs); + struct ifaddrs *tmp = addrs; + + while (tmp) { + if (tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_PACKET && + strcmp(tmp->ifa_name, "lo") && strcmp(tmp->ifa_name, "rascsi_bridge")) { + int fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); + + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + + strcpy(ifr.ifr_name, tmp->ifa_name); + if (!ioctl(fd, SIOCGIFFLAGS, &ifr)) { + close(fd); + + // Only list interfaces that are up + if (ifr.ifr_flags & IFF_UP) { + network_interfaces.push_back(tmp->ifa_name); + } + } + else { + close(fd); + } + } + + tmp = tmp->ifa_next; + } + + freeifaddrs(addrs); + + return network_interfaces; +} diff --git a/src_old/raspberrypi/devices/device_factory.h b/src_old/raspberrypi/devices/device_factory.h new file mode 100644 index 00000000..d6f73f63 --- /dev/null +++ b/src_old/raspberrypi/devices/device_factory.h @@ -0,0 +1,58 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2021 Uwe Seimet +// +// The DeviceFactory singleton creates devices based on their type and the image file extension +// +//--------------------------------------------------------------------------- + +#pragma once + +#include +#include +#include +#include +#include "rascsi_interface.pb.h" + +using namespace std; +using namespace rascsi_interface; + +typedef pair Geometry; + +class Device; + +class DeviceFactory +{ +private: + DeviceFactory(); + ~DeviceFactory() {}; + +public: + + static DeviceFactory& instance(); + + Device *CreateDevice(PbDeviceType, const string&); + PbDeviceType GetTypeForFile(const string&); + const set& GetSectorSizes(PbDeviceType type) { return sector_sizes[type]; } + const set& GetSectorSizes(const string&); + const set GetCapacities(PbDeviceType); + const map& GetDefaultParams(PbDeviceType type) { return default_params[type]; } + const list GetNetworkInterfaces() const; + const map GetExtensionMapping() const { return extension_mapping; } + +private: + + map> sector_sizes; + + // Optional mapping of drive capacities to drive geometries + map> geometries; + + map> default_params; + + map extension_mapping; + + string GetExtension(const string&) const; +}; diff --git a/src_old/raspberrypi/devices/disk.cpp b/src_old/raspberrypi/devices/disk.cpp new file mode 100644 index 00000000..9fdaa4df --- /dev/null +++ b/src_old/raspberrypi/devices/disk.cpp @@ -0,0 +1,1206 @@ +//--------------------------------------------------------------------------- +// +// X68000 EMULATOR "XM6" +// +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// +// XM6i +// Copyright (C) 2010-2015 isaki@NetBSD.org +// Copyright (C) 2010 Y.Sugahara +// +// Imported sava's Anex86/T98Next image and MO format support patch. +// Comments translated to english by akuker. +// +//--------------------------------------------------------------------------- + +#include "os.h" +#include "device_factory.h" +#include "exceptions.h" +#include "disk.h" +#include "mode_page_device.h" +#include "disk_image/disk_image_handle_factory.h" + +using namespace scsi_defs; + +Disk::Disk(const string& id) : ModePageDevice(id), ScsiBlockCommands() +{ + // Work initialization + configured_sector_size = 0; + disk.size = 0; + disk.blocks = 0; + disk.dcache = NULL; + disk.image_offset = 0; + disk.is_medium_changed = false; + + dispatcher.AddCommand(eCmdRezero, "Rezero", &Disk::Rezero); + dispatcher.AddCommand(eCmdFormat, "FormatUnit", &Disk::FormatUnit); + dispatcher.AddCommand(eCmdReassign, "ReassignBlocks", &Disk::ReassignBlocks); + dispatcher.AddCommand(eCmdRead6, "Read6", &Disk::Read6); + dispatcher.AddCommand(eCmdWrite6, "Write6", &Disk::Write6); + dispatcher.AddCommand(eCmdSeek6, "Seek6", &Disk::Seek6); + dispatcher.AddCommand(eCmdReserve6, "Reserve6", &Disk::Reserve); + dispatcher.AddCommand(eCmdRelease6, "Release6", &Disk::Release); + dispatcher.AddCommand(eCmdStartStop, "StartStopUnit", &Disk::StartStopUnit); + dispatcher.AddCommand(eCmdSendDiag, "SendDiagnostic", &Disk::SendDiagnostic); + dispatcher.AddCommand(eCmdRemoval, "PreventAllowMediumRemoval", &Disk::PreventAllowMediumRemoval); + dispatcher.AddCommand(eCmdReadCapacity10, "ReadCapacity10", &Disk::ReadCapacity10); + dispatcher.AddCommand(eCmdRead10, "Read10", &Disk::Read10); + dispatcher.AddCommand(eCmdWrite10, "Write10", &Disk::Write10); + dispatcher.AddCommand(eCmdReadLong10, "ReadLong10", &Disk::ReadLong10); + dispatcher.AddCommand(eCmdWriteLong10, "WriteLong10", &Disk::WriteLong10); + dispatcher.AddCommand(eCmdWriteLong16, "WriteLong16", &Disk::WriteLong16); + dispatcher.AddCommand(eCmdSeek10, "Seek10", &Disk::Seek10); + dispatcher.AddCommand(eCmdVerify10, "Verify10", &Disk::Verify10); + dispatcher.AddCommand(eCmdSynchronizeCache10, "SynchronizeCache10", &Disk::SynchronizeCache10); + dispatcher.AddCommand(eCmdSynchronizeCache16, "SynchronizeCache16", &Disk::SynchronizeCache16); + dispatcher.AddCommand(eCmdReadDefectData10, "ReadDefectData10", &Disk::ReadDefectData10); + dispatcher.AddCommand(eCmdReserve10, "Reserve10", &Disk::Reserve); + dispatcher.AddCommand(eCmdRelease10, "Release10", &Disk::Release); + dispatcher.AddCommand(eCmdRead16, "Read16", &Disk::Read16); + dispatcher.AddCommand(eCmdWrite16, "Write16", &Disk::Write16); + dispatcher.AddCommand(eCmdVerify16, "Verify16", &Disk::Verify16); + dispatcher.AddCommand(eCmdReadCapacity16_ReadLong16, "ReadCapacity16/ReadLong16", &Disk::ReadCapacity16_ReadLong16); +} + +Disk::~Disk() +{ + // Save disk cache + if (IsReady()) { + // Only if ready... + FlushCache(); + } + + // Clear disk cache + if (disk.dcache) { + delete disk.dcache; + disk.dcache = NULL; + } +} + +bool Disk::Dispatch(SCSIDEV *controller) +{ + // Media changes must be reported on the next access, i.e. not only for TEST UNIT READY + if (disk.is_medium_changed) { + assert(IsRemovable()); + + disk.is_medium_changed = false; + + controller->Error(ERROR_CODES::sense_key::UNIT_ATTENTION, ERROR_CODES::asc::NOT_READY_TO_READY_CHANGE); + return true; + } + + // The superclass handles the less specific commands + return dispatcher.Dispatch(this, controller) ? true : super::Dispatch(controller); +} + +//--------------------------------------------------------------------------- +// +// Open +// * Call as a post-process after successful opening in a derived class +// +//--------------------------------------------------------------------------- +void Disk::Open(const Filepath& path) +{ + assert(disk.blocks > 0); + + SetReady(true); + + // Cache initialization + assert (!disk.dcache); + disk.dcache = DiskImageHandleFactory::CreateDiskImageHandle(path, disk.size, disk.blocks, disk.image_offset); + + // Can read/write open + Fileio fio; + if (fio.Open(path, Fileio::ReadWrite)) { + // Write permission + fio.Close(); + } else { + // Permanently write-protected + SetReadOnly(true); + SetProtectable(false); + SetProtected(false); + } + + SetStopped(false); + SetRemoved(false); + SetLocked(false); +} + +void Disk::FlushCache() +{ + if (disk.dcache) { + disk.dcache->Save(); + } +} + +void Disk::Rezero(SASIDEV *controller) +{ + if (!CheckReady()) { + controller->Error(); + return; + } + + controller->Status(); +} + +void Disk::FormatUnit(SASIDEV *controller) +{ + if (!Format(ctrl->cmd)) { + controller->Error(); + return; + } + + controller->Status(); +} + +void Disk::ReassignBlocks(SASIDEV *controller) +{ + if (!CheckReady()) { + controller->Error(); + return; + } + + controller->Status(); +} + +void Disk::Read(SASIDEV *controller, uint64_t record) +{ + ctrl->length = Read(ctrl->cmd, ctrl->buffer, record); + LOGTRACE("%s ctrl.length is %d", __PRETTY_FUNCTION__, (int)ctrl->length); + + if (ctrl->length <= 0) { + controller->Error(); + return; + } + + // Set next block + ctrl->next = record + 1; + + controller->DataIn(); +} + +void Disk::Read6(SASIDEV *controller) +{ + uint64_t start; + if (GetStartAndCount(controller, start, ctrl->blocks, RW6)) { + Read(controller, start); + } +} + +void Disk::Read10(SASIDEV *controller) +{ + uint64_t start; + if (GetStartAndCount(controller, start, ctrl->blocks, RW10)) { + Read(controller, start); + } +} + +void Disk::Read16(SASIDEV *controller) +{ + uint64_t start; + if (GetStartAndCount(controller, start, ctrl->blocks, RW16)) { + Read(controller, start); + } +} + +void Disk::ReadWriteLong10(SASIDEV *controller) +{ + // Transfer lengths other than 0 are not supported, which is compliant with the SCSI standard + if (ctrl->cmd[7] || ctrl->cmd[8]) { + controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::INVALID_FIELD_IN_CDB); + return; + } + + if (CheckBlockAddress(controller, RW10)) { + controller->Status(); + } +} + +void Disk::ReadLong10(SASIDEV *controller) +{ + ReadWriteLong10(controller); +} + +void Disk::ReadWriteLong16(SASIDEV *controller) +{ + // Transfer lengths other than 0 are not supported, which is compliant with the SCSI standard + if (ctrl->cmd[12] || ctrl->cmd[13]) { + controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::INVALID_FIELD_IN_CDB); + return; + } + + if (CheckBlockAddress(controller, RW16)) { + controller->Status(); + } +} + +void Disk::ReadLong16(SASIDEV *controller) +{ + ReadWriteLong16(controller); +} + +void Disk::Write(SASIDEV *controller, uint64_t record) +{ + ctrl->length = WriteCheck(record); + if (ctrl->length == 0) { + controller->Error(ERROR_CODES::sense_key::NOT_READY, ERROR_CODES::asc::NO_ADDITIONAL_SENSE_INFORMATION); + return; + } + else if (ctrl->length < 0) { + controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::WRITE_PROTECTED); + return; + } + + // Set next block + ctrl->next = record + 1; + + controller->DataOut(); +} + +void Disk::Write6(SASIDEV *controller) +{ + uint64_t start; + if (GetStartAndCount(controller, start, ctrl->blocks, RW6)) { + Write(controller, start); + } +} + +void Disk::Write10(SASIDEV *controller) +{ + uint64_t start; + if (GetStartAndCount(controller, start, ctrl->blocks, RW10)) { + Write(controller, start); + } +} + +void Disk::Write16(SASIDEV *controller) +{ + uint64_t start; + if (GetStartAndCount(controller, start, ctrl->blocks, RW16)) { + Write(controller, start); + } +} + +void Disk::WriteLong10(SASIDEV *controller) +{ + ReadWriteLong10(controller); +} + +void Disk::WriteLong16(SASIDEV *controller) +{ + ReadWriteLong16(controller); +} + +void Disk::Verify(SASIDEV *controller, uint64_t record) +{ + // if BytChk=0 + if ((ctrl->cmd[1] & 0x02) == 0) { + Seek(controller); + return; + } + + // Test loading + ctrl->length = Read(ctrl->cmd, ctrl->buffer, record); + if (ctrl->length <= 0) { + controller->Error(); + return; + } + + // Set next block + ctrl->next = record + 1; + + controller->DataOut(); +} + +void Disk::Verify10(SASIDEV *controller) +{ + // Get record number and block number + uint64_t record; + if (GetStartAndCount(controller, record, ctrl->blocks, RW10)) { + Verify(controller, record); + } +} + +void Disk::Verify16(SASIDEV *controller) +{ + // Get record number and block number + uint64_t record; + if (GetStartAndCount(controller, record, ctrl->blocks, RW16)) { + Verify(controller, record); + } +} + +void Disk::StartStopUnit(SASIDEV *controller) +{ + if (!StartStop(ctrl->cmd)) { + controller->Error(); + return; + } + + controller->Status(); +} + +void Disk::SendDiagnostic(SASIDEV *controller) +{ + if (!SendDiag(ctrl->cmd)) { + controller->Error(); + return; + } + + controller->Status(); +} + +void Disk::PreventAllowMediumRemoval(SASIDEV *controller) +{ + if (!CheckReady()) { + controller->Error(); + return; + } + + bool lock = ctrl->cmd[4] & 0x01; + + LOGTRACE("%s", lock ? "Locking medium" : "Unlocking medium"); + + SetLocked(lock); + + controller->Status(); +} + +void Disk::SynchronizeCache10(SASIDEV *controller) +{ + FlushCache(); + + controller->Status(); +} + +void Disk::SynchronizeCache16(SASIDEV *controller) +{ + return SynchronizeCache10(controller); +} + +void Disk::ReadDefectData10(SASIDEV *controller) +{ + ctrl->length = ReadDefectData10(ctrl->cmd, ctrl->buffer, ctrl->bufsize); + if (ctrl->length <= 4) { + controller->Error(); + return; + } + + controller->DataIn(); +} + +void Disk::MediumChanged() +{ + assert(IsRemovable()); + + if (IsRemovable()) { + disk.is_medium_changed = true; + } +} + +bool Disk::Eject(bool force) +{ + bool status = Device::Eject(force); + if (status) { + FlushCache(); + delete disk.dcache; + disk.dcache = NULL; + + // The image file for this drive is not in use anymore + FileSupport *file_support = dynamic_cast(this); + if (file_support) { + file_support->UnreserveFile(); + } + } + + return status; +} + +int Disk::ModeSense6(const DWORD *cdb, BYTE *buf) +{ + // Get length, clear buffer + int length = (int)cdb[4]; + memset(buf, 0, length); + + // Basic information + int size = 4; + + // Add block descriptor if DBD is 0 + if ((cdb[1] & 0x08) == 0) { + // Mode parameter header, block descriptor length + buf[3] = 0x08; + + // Only if ready + if (IsReady()) { + // Short LBA mode parameter block descriptor (number of blocks and block length) + + uint64_t disk_blocks = GetBlockCount(); + buf[4] = disk_blocks >> 24; + buf[5] = disk_blocks >> 16; + buf[6] = disk_blocks >> 8; + buf[7] = disk_blocks; + + // Block descriptor (block length) + uint32_t disk_size = GetSectorSizeInBytes(); + buf[9] = disk_size >> 16; + buf[10] = disk_size >> 8; + buf[11] = disk_size; + } + + size = 12; + } + + int pages_size = super::AddModePages(cdb, &buf[size], length - size); + if (!pages_size) { + return 0; + } + size += pages_size; + + // Do not return more than ALLOCATION LENGTH bytes + if (size > length) { + size = length; + } + + // Final setting of mode data length + buf[0] = size; + + return size; +} + +int Disk::ModeSense10(const DWORD *cdb, BYTE *buf, int max_length) +{ + // Get length, clear buffer + int length = (cdb[7] << 8) | cdb[8]; + if (length > max_length) { + length = max_length; + } + memset(buf, 0, length); + + // Basic Information + int size = 8; + + // Add block descriptor if DBD is 0 + if ((cdb[1] & 0x08) == 0) { + // Only if ready + if (IsReady()) { + uint64_t disk_blocks = GetBlockCount(); + uint32_t disk_size = GetSectorSizeInBytes(); + + // Check LLBAA for short or long block descriptor + if ((cdb[1] & 0x10) == 0 || disk_blocks <= 0xFFFFFFFF) { + // Mode parameter header, block descriptor length + buf[7] = 0x08; + + // Short LBA mode parameter block descriptor (number of blocks and block length) + + buf[8] = disk_blocks >> 24; + buf[9] = disk_blocks >> 16; + buf[10] = disk_blocks >> 8; + buf[11] = disk_blocks; + + buf[13] = disk_size >> 16; + buf[14] = disk_size >> 8; + buf[15] = disk_size; + + size = 16; + } + else { + // Mode parameter header, LONGLBA + buf[4] = 0x01; + + // Mode parameter header, block descriptor length + buf[7] = 0x10; + + // Long LBA mode parameter block descriptor (number of blocks and block length) + + buf[8] = disk_blocks >> 56; + buf[9] = disk_blocks >> 48; + buf[10] = disk_blocks >> 40; + buf[11] = disk_blocks >> 32; + buf[12] = disk_blocks >> 24; + buf[13] = disk_blocks >> 16; + buf[14] = disk_blocks >> 8; + buf[15] = disk_blocks; + + buf[20] = disk_size >> 24; + buf[21] = disk_size >> 16; + buf[22] = disk_size >> 8; + buf[23] = disk_size; + + size = 24; + } + } + } + + int pages_size = super::AddModePages(cdb, &buf[size], length - size); + if (!pages_size) { + return 0; + } + size += pages_size; + + // Do not return more than ALLOCATION LENGTH bytes + if (size > length) { + size = length; + } + + // Final setting of mode data length + buf[0] = size >> 8; + buf[1] = size; + + return size; +} + +void Disk::SetDeviceParameters(BYTE *buf) +{ + // DEVICE SPECIFIC PARAMETER + if (IsProtected()) { + buf[3] = 0x80; + } +} + +void Disk::AddModePages(map>& pages, int page, bool changeable) const +{ + // Page code 1 (read-write error recovery) + if (page == 0x01 || page == 0x3f) { + AddErrorPage(pages, changeable); + } + + // Page code 3 (format device) + if (page == 0x03 || page == 0x3f) { + AddFormatPage(pages, changeable); + } + + // Page code 4 (drive parameter) + if (page == 0x04 || page == 0x3f) { + AddDrivePage(pages, changeable); + } + + // Page code 8 (caching) + if (page == 0x08 || page == 0x3f) { + AddCachePage(pages, changeable); + } + + // Page (vendor special) + AddVendorPage(pages, page, changeable); +} + +void Disk::AddErrorPage(map>& pages, bool) const +{ + vector buf(12); + + // Retry count is 0, limit time uses internal default value + + pages[1] = buf; +} + +void Disk::AddFormatPage(map>& pages, bool changeable) const +{ + vector buf(24); + + // Page can be saved + buf[0] = 0x80; + + // Show the number of bytes in the physical sector as changeable + // (though it cannot be changed in practice) + if (changeable) { + buf[0xc] = 0xff; + buf[0xd] = 0xff; + + pages[3] = buf; + + return; + } + + if (IsReady()) { + // Set the number of tracks in one zone to 8 (TODO) + buf[0x3] = 0x08; + + // Set sector/track to 25 (TODO) + buf[0xa] = 0x00; + buf[0xb] = 0x19; + + // Set the number of bytes in the physical sector + int size = 1 << disk.size; + buf[0xc] = (BYTE)(size >> 8); + buf[0xd] = (BYTE)size; + } + + // Set removable attribute + if (IsRemovable()) { + buf[20] = 0x20; + } + + pages[3] = buf; +} + +void Disk::AddDrivePage(map>& pages, bool changeable) const +{ + vector buf(24); + + // No changeable area + if (changeable) { + pages[4] = buf; + + return; + } + + if (IsReady()) { + // Set the number of cylinders (total number of blocks + // divided by 25 sectors/track and 8 heads) + uint32_t cylinder = disk.blocks; + cylinder >>= 3; + cylinder /= 25; + buf[0x2] = (BYTE)(cylinder >> 16); + buf[0x3] = (BYTE)(cylinder >> 8); + buf[0x4] = (BYTE)cylinder; + + // Fix the head at 8 + buf[0x5] = 0x8; + } + + pages[4] = buf; +} + +void Disk::AddCachePage(map>& pages, bool) const +{ + vector buf(12); + + // Only read cache is valid, no prefetch + + pages[8] = buf; +} + +void Disk::AddVendorPage(map>&, int, bool) const +{ + // Nothing to add by default +} + +int Disk::ReadDefectData10(const DWORD *cdb, BYTE *buf, int max_length) +{ + // Get length, clear buffer + int length = (cdb[7] << 8) | cdb[8]; + if (length > max_length) { + length = max_length; + } + memset(buf, 0, length); + + // P/G/FORMAT + buf[1] = (cdb[1] & 0x18) | 5; + buf[3] = 8; + + buf[4] = 0xff; + buf[5] = 0xff; + buf[6] = 0xff; + buf[7] = 0xff; + + buf[8] = 0xff; + buf[9] = 0xff; + buf[10] = 0xff; + buf[11] = 0xff; + + // no list + SetStatusCode(STATUS_NODEFECT); + return 4; +} + +//--------------------------------------------------------------------------- +// +// FORMAT UNIT +// *Opcode $06 for SASI, Opcode $04 for SCSI +// +//--------------------------------------------------------------------------- +bool Disk::Format(const DWORD *cdb) +{ + if (!CheckReady()) { + return false; + } + + // FMTDATA=1 is not supported (but OK if there is no DEFECT LIST) + if ((cdb[1] & 0x10) != 0 && cdb[4] != 0) { + SetStatusCode(STATUS_INVALIDCDB); + return false; + } + + // FORMAT Success + return true; +} + +// TODO Read more than one block in a single call. Currently blocked by the SASI code (missing early range check) +// and the track-oriented cache. +int Disk::Read(const DWORD *cdb, BYTE *buf, uint64_t block) +{ + LOGTRACE("%s", __PRETTY_FUNCTION__); + + if (!CheckReady()) { + return 0; + } + + // Error if the total number of blocks is exceeded + if (block >= disk.blocks) { + SetStatusCode(STATUS_INVALIDLBA); + return 0; + } + + // leave it to the cache + if (!disk.dcache->ReadSector(buf, block)) { + SetStatusCode(STATUS_READFAULT); + return 0; + } + + // Success + return 1 << disk.size; +} + +int Disk::WriteCheck(DWORD block) +{ + // Status check + if (!CheckReady()) { + LOGDEBUG("WriteCheck failed (not ready)"); + return 0; + } + + // Error if the total number of blocks is exceeded + if (block >= disk.blocks) { + LOGDEBUG("WriteCheck failed (capacity exceeded)"); + return 0; + } + + // Error if write protected + if (IsProtected()) { + LOGDEBUG("WriteCheck failed (protected)"); + return -1; + } + + // Success + return 1 << disk.size; +} + +// TODO Write more than one block in a single call. Currently blocked by the SASI code (missing early range check) +// and the track-oriented cache. +bool Disk::Write(const DWORD *cdb, const BYTE *buf, DWORD block) +{ + LOGTRACE("%s", __PRETTY_FUNCTION__); + + // Error if not ready + if (!IsReady()) { + SetStatusCode(STATUS_NOTREADY); + return false; + } + + // Error if the total number of blocks is exceeded + if (block >= disk.blocks) { + SetStatusCode(STATUS_INVALIDLBA); + return false; + } + + // Error if write protected + if (IsProtected()) { + SetStatusCode(STATUS_WRITEPROTECT); + return false; + } + + // Leave it to the cache + if (!disk.dcache->WriteSector(buf, block)) { + SetStatusCode(STATUS_WRITEFAULT); + return false; + } + + return true; +} + +// TODO For SCSI the specification mandates that the block address is verified +void Disk::Seek(SASIDEV *controller) +{ + if (!CheckReady()) { + controller->Error(); + return; + } + + controller->Status(); +} + +//--------------------------------------------------------------------------- +// +// SEEK(6) +// Does not check LBA (SASI IOCS) +// +//--------------------------------------------------------------------------- +void Disk::Seek6(SASIDEV *controller) +{ + Seek(controller); +} + +void Disk::Seek10(SASIDEV *controller) +{ + Seek(controller); +} + +bool Disk::StartStop(const DWORD *cdb) +{ + bool start = cdb[4] & 0x01; + bool load = cdb[4] & 0x02; + + if (load) { + LOGTRACE("%s", start ? "Loading medium" : "Ejecting medium"); + } + else { + LOGTRACE("%s", start ? "Starting unit" : "Stopping unit"); + + SetStopped(!start); + } + + if (!start) { + FlushCache(); + + // Look at the eject bit and eject if necessary + if (load) { + if (IsLocked()) { + // Cannot be ejected because it is locked + SetStatusCode(STATUS_PREVENT); + return false; + } + + // Eject + return Eject(false); + } + } + + return true; +} + +bool Disk::SendDiag(const DWORD *cdb) +{ + // Do not support PF bit + if (cdb[1] & 0x10) { + SetStatusCode(STATUS_INVALIDCDB); + return false; + } + + // Do not support parameter list + if ((cdb[3] != 0) || (cdb[4] != 0)) { + SetStatusCode(STATUS_INVALIDCDB); + return false; + } + + return true; +} + +void Disk::ReadCapacity10(SASIDEV *controller) +{ + if (!CheckReady() || disk.blocks <= 0) { + controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::MEDIUM_NOT_PRESENT); + return; + } + + BYTE *buf = ctrl->buffer; + + // Create end of logical block address (disk.blocks-1) + uint32_t blocks = disk.blocks - 1; + buf[0] = (BYTE)(blocks >> 24); + buf[1] = (BYTE)(blocks >> 16); + buf[2] = (BYTE)(blocks >> 8); + buf[3] = (BYTE)blocks; + + // Create block length (1 << disk.size) + uint32_t length = 1 << disk.size; + buf[4] = (BYTE)(length >> 24); + buf[5] = (BYTE)(length >> 16); + buf[6] = (BYTE)(length >> 8); + buf[7] = (BYTE)length; + + // the size + ctrl->length = 8; + + controller->DataIn(); +} + +void Disk::ReadCapacity16(SASIDEV *controller) +{ + if (!CheckReady() || disk.blocks <= 0) { + controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::MEDIUM_NOT_PRESENT); + return; + } + + BYTE *buf = ctrl->buffer; + + // Create end of logical block address (disk.blocks-1) + uint64_t blocks = disk.blocks - 1; + buf[0] = (BYTE)(blocks >> 56); + buf[1] = (BYTE)(blocks >> 48); + buf[2] = (BYTE)(blocks >> 40); + buf[3] = (BYTE)(blocks >> 32); + buf[4] = (BYTE)(blocks >> 24); + buf[5] = (BYTE)(blocks >> 16); + buf[6] = (BYTE)(blocks >> 8); + buf[7] = (BYTE)blocks; + + // Create block length (1 << disk.size) + uint32_t length = 1 << disk.size; + buf[8] = (BYTE)(length >> 24); + buf[9] = (BYTE)(length >> 16); + buf[10] = (BYTE)(length >> 8); + buf[11] = (BYTE)length; + + buf[12] = 0; + + // Logical blocks per physical block: not reported (1 or more) + buf[13] = 0; + + // the size + ctrl->length = 14; + + controller->DataIn(); +} + +void Disk::ReadCapacity16_ReadLong16(SASIDEV *controller) +{ + // The service action determines the actual command + switch (ctrl->cmd[1] & 0x1f) { + case 0x10: + ReadCapacity16(controller); + break; + + case 0x11: + ReadLong16(controller); + break; + + default: + controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::INVALID_FIELD_IN_CDB); + break; + } +} + +//--------------------------------------------------------------------------- +// +// RESERVE/RELEASE(6/10) +// +// The reserve/release commands are only used in multi-initiator +// environments. RaSCSI doesn't support this use case. However, some old +// versions of Solaris will issue the reserve/release commands. We will +// just respond with an OK status. +// +//--------------------------------------------------------------------------- +void Disk::Reserve(SASIDEV *controller) +{ + controller->Status(); +} + +void Disk::Release(SASIDEV *controller) +{ + controller->Status(); +} + +//--------------------------------------------------------------------------- +// +// Check/Get start sector and sector count for a READ/WRITE or READ/WRITE LONG operation +// +//--------------------------------------------------------------------------- + +bool Disk::CheckBlockAddress(SASIDEV *controller, access_mode mode) +{ + uint64_t block = ctrl->cmd[2]; + block <<= 8; + block |= ctrl->cmd[3]; + block <<= 8; + block |= ctrl->cmd[4]; + block <<= 8; + block |= ctrl->cmd[5]; + + if (mode == RW16) { + block <<= 8; + block |= ctrl->cmd[6]; + block <<= 8; + block |= ctrl->cmd[7]; + block <<= 8; + block |= ctrl->cmd[8]; + block <<= 8; + block |= ctrl->cmd[9]; + } + + uint64_t capacity = GetBlockCount(); + if (block > capacity) { + LOGTRACE("%s", ("Capacity of " + to_string(capacity) + " blocks exceeded: Trying to access block " + + to_string(block)).c_str()); + controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::LBA_OUT_OF_RANGE); + return false; + } + + return true; +} + +bool Disk::GetStartAndCount(SASIDEV *controller, uint64_t& start, uint32_t& count, access_mode mode) +{ + if (mode == RW6) { + start = ctrl->cmd[1] & 0x1f; + start <<= 8; + start |= ctrl->cmd[2]; + start <<= 8; + start |= ctrl->cmd[3]; + + count = ctrl->cmd[4]; + if (!count) { + count= 0x100; + } + } + else { + start = ctrl->cmd[2]; + start <<= 8; + start |= ctrl->cmd[3]; + start <<= 8; + start |= ctrl->cmd[4]; + start <<= 8; + start |= ctrl->cmd[5]; + + if (mode == RW16) { + start <<= 8; + start |= ctrl->cmd[6]; + start <<= 8; + start |= ctrl->cmd[7]; + start <<= 8; + start |= ctrl->cmd[8]; + start <<= 8; + start |= ctrl->cmd[9]; + } + + if (mode == RW16) { + count = ctrl->cmd[10]; + count <<= 8; + count |= ctrl->cmd[11]; + count <<= 8; + count |= ctrl->cmd[12]; + count <<= 8; + count |= ctrl->cmd[13]; + } + else { + count = ctrl->cmd[7]; + count <<= 8; + count |= ctrl->cmd[8]; + } + } + + LOGDEBUG("%s READ/WRITE/VERIFY command record=$%08X blocks=%d", __PRETTY_FUNCTION__, (uint32_t)start, count); + + // Check capacity + uint64_t capacity = GetBlockCount(); + if (start > capacity || start + count > capacity) { + LOGTRACE("%s", ("Capacity of " + to_string(capacity) + " blocks exceeded: Trying to access block " + + to_string(start) + ", block count " + to_string(ctrl->blocks)).c_str()); + controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::LBA_OUT_OF_RANGE); + return false; + } + + // Do not process 0 blocks + if (!count) { + LOGTRACE("NOT processing 0 blocks"); + controller->Status(); + return false; + } + + return true; +} + +uint32_t Disk::GetSectorSizeInBytes() const +{ + return disk.size ? 1 << disk.size : 0; +} + +void Disk::SetSectorSizeInBytes(uint32_t size, bool sasi) +{ + set sector_sizes = DeviceFactory::instance().GetSectorSizes(GetType()); + if (!sector_sizes.empty() && sector_sizes.find(size) == sector_sizes.end()) { + throw io_exception("Invalid block size of " + to_string(size) + " bytes"); + } + + switch (size) { + case 256: + disk.size = 8; + break; + + case 512: + disk.size = 9; + break; + + case 1024: + disk.size = 10; + break; + + case 2048: + disk.size = 11; + break; + + case 4096: + disk.size = 12; + break; + + default: + assert(false); + break; + } +} + +uint32_t Disk::GetSectorSizeShiftCount() const +{ + return disk.size; +} + +void Disk::SetSectorSizeShiftCount(uint32_t size) +{ + disk.size = size; +} + +bool Disk::IsSectorSizeConfigurable() const +{ + return !sector_sizes.empty(); +} + +void Disk::SetSectorSizes(const set& sector_sizes) +{ + this->sector_sizes = sector_sizes; +} + +uint32_t Disk::GetConfiguredSectorSize() const +{ + return configured_sector_size; +} + +bool Disk::SetConfiguredSectorSize(uint32_t configured_sector_size) +{ + DeviceFactory& device_factory = DeviceFactory::instance(); + + set sector_sizes = device_factory.GetSectorSizes(GetType()); + if (sector_sizes.find(configured_sector_size) == sector_sizes.end()) { + return false; + } + + this->configured_sector_size = configured_sector_size; + + return true; +} + +void Disk::SetGeometries(const map& geometries) +{ + this->geometries = geometries; +} + +bool Disk::SetGeometryForCapacity(uint64_t capacity) { + const auto& geometry = geometries.find(capacity); + if (geometry != geometries.end()) { + SetSectorSizeInBytes(geometry->second.first, false); + SetBlockCount(geometry->second.second); + + return true; + } + + return false; +} + +uint64_t Disk::GetBlockCount() const +{ + return disk.blocks; +} + +void Disk::SetBlockCount(uint32_t blocks) +{ + disk.blocks = blocks; +} diff --git a/src_old/raspberrypi/devices/disk.h b/src_old/raspberrypi/devices/disk.h new file mode 100644 index 00000000..a5af3ad2 --- /dev/null +++ b/src_old/raspberrypi/devices/disk.h @@ -0,0 +1,159 @@ +//--------------------------------------------------------------------------- +// +// X68000 EMULATOR "XM6" +// +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// +// XM6i +// Copyright (C) 2010-2015 isaki@NetBSD.org +// +// Imported sava's Anex86/T98Next image and MO format support patch. +// Comments translated to english by akuker. +// +// [ Disk ] +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "log.h" +#include "scsi.h" +#include "controllers/scsidev_ctrl.h" +#include "device.h" +#include "device_factory.h" +#include "disk_image/disk_image_handle_factory.h" +#include "file_support.h" +#include "filepath.h" +#include "interfaces/scsi_block_commands.h" +#include "mode_page_device.h" +#include +#include +#include + +using namespace std; + +class Disk : public ModePageDevice, ScsiBlockCommands +{ +private: + enum access_mode { RW6, RW10, RW16 }; + + // The supported configurable block sizes, empty if not configurable + set sector_sizes; + uint32_t configured_sector_size; + + // The mapping of supported capacities to block sizes and block counts, empty if there is no capacity restriction + map geometries; + + typedef struct { + uint32_t size; // Sector Size (8=256, 9=512, 10=1024, 11=2048, 12=4096) + // TODO blocks should be a 64 bit value in order to support higher capacities + uint32_t blocks; // Total number of sectors + DiskImageHandle *dcache; // Disk cache + off_t image_offset; // Offset to actual data + bool is_medium_changed; + } disk_t; + + Dispatcher dispatcher; + +public: + Disk(const string&); + virtual ~Disk(); + + virtual bool Dispatch(SCSIDEV *) override; + + void MediumChanged(); + void ReserveFile(const string&); + + // Media Operations + virtual void Open(const Filepath& path); + void GetPath(Filepath& path) const; + bool Eject(bool) override; + +private: + typedef ModePageDevice super; + + // Commands covered by the SCSI specification (see https://www.t10.org/drafts.htm) + void StartStopUnit(SASIDEV *); + void SendDiagnostic(SASIDEV *); + void PreventAllowMediumRemoval(SASIDEV *); + void SynchronizeCache10(SASIDEV *); + void SynchronizeCache16(SASIDEV *); + void ReadDefectData10(SASIDEV *); + virtual void Read6(SASIDEV *); + void Read10(SASIDEV *) override; + void Read16(SASIDEV *) override; + virtual void Write6(SASIDEV *); + void Write10(SASIDEV *) override; + void Write16(SASIDEV *) override; + void ReadLong10(SASIDEV *); + void ReadLong16(SASIDEV *); + void WriteLong10(SASIDEV *); + void WriteLong16(SASIDEV *); + void Verify10(SASIDEV *); + void Verify16(SASIDEV *); + void Seek(SASIDEV *); + void Seek10(SASIDEV *); + void ReadCapacity10(SASIDEV *) override; + void ReadCapacity16(SASIDEV *) override; + void Reserve(SASIDEV *); + void Release(SASIDEV *); + +public: + + // Commands covered by the SCSI specification (see https://www.t10.org/drafts.htm) + void Rezero(SASIDEV *); + void FormatUnit(SASIDEV *) override; + void ReassignBlocks(SASIDEV *); + void Seek6(SASIDEV *); + + // Command helpers + virtual int Inquiry(const DWORD *cdb, BYTE *buf) = 0; // INQUIRY command + virtual int WriteCheck(DWORD block); // WRITE check + virtual bool Write(const DWORD *cdb, const BYTE *buf, DWORD block); // WRITE command + bool StartStop(const DWORD *cdb); // START STOP UNIT command + bool SendDiag(const DWORD *cdb); // SEND DIAGNOSTIC command + + virtual int Read(const DWORD *cdb, BYTE *buf, uint64_t block); + int ReadDefectData10(const DWORD *, BYTE *, int); + + uint32_t GetSectorSizeInBytes() const; + void SetSectorSizeInBytes(uint32_t, bool); + uint32_t GetSectorSizeShiftCount() const; + void SetSectorSizeShiftCount(uint32_t); + bool IsSectorSizeConfigurable() const; + set GetSectorSizes() const; + void SetSectorSizes(const set&); + uint32_t GetConfiguredSectorSize() const; + bool SetConfiguredSectorSize(uint32_t); + void SetGeometries(const map&); + bool SetGeometryForCapacity(uint64_t); + uint64_t GetBlockCount() const; + void SetBlockCount(uint32_t); + bool CheckBlockAddress(SASIDEV *, access_mode); + bool GetStartAndCount(SASIDEV *, uint64_t&, uint32_t&, access_mode); + void FlushCache(); + +protected: + int ModeSense6(const DWORD *cdb, BYTE *buf); + int ModeSense10(const DWORD *cdb, BYTE *buf, int); + virtual void SetDeviceParameters(BYTE *); + void AddModePages(map>&, int, bool) const override; + virtual void AddErrorPage(map>&, bool) const; + virtual void AddFormatPage(map>&, bool) const; + virtual void AddDrivePage(map>&, bool) const; + void AddCachePage(map>&, bool) const; + virtual void AddVendorPage(map>&, int, bool) const; + + // Internal disk data + disk_t disk; + +private: + void Read(SASIDEV *, uint64_t); + void Write(SASIDEV *, uint64_t); + void Verify(SASIDEV *, uint64_t); + void ReadWriteLong10(SASIDEV *); + void ReadWriteLong16(SASIDEV *); + void ReadCapacity16_ReadLong16(SASIDEV *); + bool Format(const DWORD *cdb); +}; diff --git a/src_old/raspberrypi/devices/dispatcher.h b/src_old/raspberrypi/devices/dispatcher.h new file mode 100644 index 00000000..6a2a46c4 --- /dev/null +++ b/src_old/raspberrypi/devices/dispatcher.h @@ -0,0 +1,67 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2022 Uwe Seimet +// +// A template for dispatching SCSI commands +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "log.h" +#include "scsi.h" +#include + +class SASIDEV; +class SCSIDEV; + +using namespace std; +using namespace scsi_defs; + +template +class Dispatcher +{ +public: + + Dispatcher() {} + ~Dispatcher() + { + for (auto const& command : commands) { + delete command.second; + } + } + + typedef struct _command_t { + const char* name; + void (T::*execute)(U *); + + _command_t(const char* _name, void (T::*_execute)(U *)) : name(_name), execute(_execute) { }; + } command_t; + map commands; + + void AddCommand(scsi_command opcode, const char* name, void (T::*execute)(U *)) + { + commands[opcode] = new command_t(name, execute); + } + + bool Dispatch(T *instance, U *controller) + { + SASIDEV::ctrl_t *ctrl = controller->GetCtrl(); + instance->SetCtrl(ctrl); + + const auto& it = commands.find(static_cast(ctrl->cmd[0])); + if (it != commands.end()) { + LOGDEBUG("%s Executing %s ($%02X)", __PRETTY_FUNCTION__, it->second->name, (unsigned int)ctrl->cmd[0]); + + (instance->*it->second->execute)(controller); + + return true; + } + + // Unknown command + return false; + } +}; diff --git a/src_old/raspberrypi/devices/file_support.cpp b/src_old/raspberrypi/devices/file_support.cpp new file mode 100644 index 00000000..09b9061a --- /dev/null +++ b/src_old/raspberrypi/devices/file_support.cpp @@ -0,0 +1,43 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2021 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include +#include +#include "file_support.h" + +using namespace std; + +map FileSupport::reserved_files; + +void FileSupport::ReserveFile(const Filepath& path, int id, int lun) +{ + reserved_files[path.GetPath()] = make_pair(id, lun); +} + +void FileSupport::UnreserveFile() +{ + reserved_files.erase(diskpath.GetPath()); +} + +bool FileSupport::GetIdsForReservedFile(const Filepath& path, int& id, int& unit) +{ + if (reserved_files.find(path.GetPath()) != reserved_files.end()) { + const id_set ids = reserved_files[path.GetPath()]; + id = ids.first; + unit = ids.second; + return true; + } + + return false; +} + +void FileSupport::UnreserveAll() +{ + reserved_files.clear(); +} diff --git a/src_old/raspberrypi/devices/file_support.h b/src_old/raspberrypi/devices/file_support.h new file mode 100644 index 00000000..0d7df448 --- /dev/null +++ b/src_old/raspberrypi/devices/file_support.h @@ -0,0 +1,46 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2021 Uwe Seimet +// +// Devices inheriting from FileSupport support image files +// +//--------------------------------------------------------------------------- + +#pragma once + +#include +#include +#include "filepath.h" + +using namespace std; + +typedef pair id_set; + +class FileSupport +{ +private: + Filepath diskpath; + + // The list of image files in use and the IDs and LUNs using these files + static map reserved_files; + +public: + + FileSupport() {}; + virtual ~FileSupport() {}; + + void GetPath(Filepath& path) const { path = diskpath; } + void SetPath(const Filepath& path) { diskpath = path; } + static const map GetReservedFiles(){ return reserved_files; } + static void SetReservedFiles(const map& files_in_use) { FileSupport::reserved_files = files_in_use; } + void ReserveFile(const Filepath&, int, int); + void UnreserveFile(); + + static bool GetIdsForReservedFile(const Filepath&, int&, int&); + static void UnreserveAll(); + + virtual void Open(const Filepath&) = 0; +}; diff --git a/src_old/raspberrypi/devices/host_services.cpp b/src_old/raspberrypi/devices/host_services.cpp new file mode 100644 index 00000000..0c25bdf0 --- /dev/null +++ b/src_old/raspberrypi/devices/host_services.cpp @@ -0,0 +1,192 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2022 Uwe Seimet +// +// Host Services with realtime clock and shutdown support +// +//--------------------------------------------------------------------------- + +// +// Features of the host services device: +// +// 1. Vendor-specific mode page 0x20 returns the current date and time (realtime clock) +// +// typedef struct { +// // Major and minor version of this data structure +// uint8_t major_version; +// uint8_t minor_version; +// // Current date and time, with daylight savings time adjustment applied +// uint8_t year; // year - 1900 +// uint8_t month; // 0-11 +// uint8_t day; // 1-31 +// uint8_t hour; // 0-23 +// uint8_t minute; // 0-59 +// uint8_t second; // 0-59 +// } mode_page_datetime; +// +// 2. START/STOP UNIT shuts down RaSCSI or shuts down/reboots the Raspberry Pi +// a) !start && !load (STOP): Shut down RaSCSI +// b) !start && load (EJECT): Shut down the Raspberry Pi +// c) start && load (LOAD): Reboot the Raspberry Pi +// + +#include "controllers/scsidev_ctrl.h" +#include "disk.h" +#include "host_services.h" + +using namespace scsi_defs; + +HostServices::HostServices() : ModePageDevice("SCHS") +{ + dispatcher.AddCommand(eCmdTestUnitReady, "TestUnitReady", &HostServices::TestUnitReady); + dispatcher.AddCommand(eCmdStartStop, "StartStopUnit", &HostServices::StartStopUnit); +} + +bool HostServices::Dispatch(SCSIDEV *controller) +{ + // The superclass class handles the less specific commands + return dispatcher.Dispatch(this, controller) ? true : super::Dispatch(controller); +} + +void HostServices::TestUnitReady(SCSIDEV *controller) +{ + // Always successful + controller->Status(); +} + +int HostServices::Inquiry(const DWORD *cdb, BYTE *buf) +{ + // Processor device, SPC-5, not removable + return PrimaryDevice::Inquiry(3, 7, false, cdb, buf); +} + +void HostServices::StartStopUnit(SCSIDEV *controller) +{ + bool start = ctrl->cmd[4] & 0x01; + bool load = ctrl->cmd[4] & 0x02; + + if (!start) { + // Flush any caches + for (Device *device : devices) { + Disk *disk = dynamic_cast(device); + if (disk) { + disk->FlushCache(); + } + } + + if (load) { + controller->ScheduleShutDown(SCSIDEV::rascsi_shutdown_mode::STOP_PI); + } + else { + controller->ScheduleShutDown(SCSIDEV::rascsi_shutdown_mode::STOP_RASCSI); + } + + controller->Status(); + return; + } + else { + if (load) { + controller->ScheduleShutDown(SCSIDEV::rascsi_shutdown_mode::RESTART_PI); + + controller->Status(); + return; + } + } + + controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::INVALID_FIELD_IN_CDB); +} + +int HostServices::ModeSense6(const DWORD *cdb, BYTE *buf) +{ + // Block descriptors cannot be returned + if (!(cdb[1] & 0x08)) { + return 0; + } + + int length = (int)cdb[4]; + memset(buf, 0, length); + + // Basic information + int size = 4; + + int pages_size = super::AddModePages(cdb, &buf[size], length - size); + if (!pages_size) { + return 0; + } + size += pages_size; + + // Do not return more than ALLOCATION LENGTH bytes + if (size > length) { + size = length; + } + + buf[0] = size; + + return size; +} + +int HostServices::ModeSense10(const DWORD *cdb, BYTE *buf, int max_length) +{ + // Block descriptors cannot be returned + if (!(cdb[1] & 0x08)) { + return 0; + } + + int length = (cdb[7] << 8) | cdb[8]; + if (length > max_length) { + length = max_length; + } + memset(buf, 0, length); + + // Basic information + int size = 8; + + int pages_size = super::AddModePages(cdb, &buf[size], length - size); + if (!pages_size) { + return 0; + } + size += pages_size; + + // Do not return more than ALLOCATION LENGTH bytes + if (size > length) { + size = length; + } + + buf[0] = size >> 8; + buf[1] = size; + + return size; +} + +void HostServices::AddModePages(map>& pages, int page, bool changeable) const +{ + if (page == 0x20 || page == 0x3f) { + AddRealtimeClockPage(pages, changeable); + } +} + +void HostServices::AddRealtimeClockPage(map>& pages, bool changeable) const +{ + vector buf(10); + + if (!changeable) { + // Data structure version 1.0 + buf[2] = 0x01; + buf[3] = 0x00; + + std::time_t t = std::time(NULL); + std::tm tm = *std::localtime(&t); + buf[4] = tm.tm_year; + buf[5] = tm.tm_mon; + buf[6] = tm.tm_mday; + buf[7] = tm.tm_hour; + buf[8] = tm.tm_min; + // Ignore leap second for simplicity + buf[9] = tm.tm_sec < 60 ? tm.tm_sec : 59; + } + + pages[32] = buf; +} diff --git a/src_old/raspberrypi/devices/host_services.h b/src_old/raspberrypi/devices/host_services.h new file mode 100644 index 00000000..d71ee9b2 --- /dev/null +++ b/src_old/raspberrypi/devices/host_services.h @@ -0,0 +1,43 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2022 Uwe Seimet +// +// Host Services with realtime clock and shutdown support +// +//--------------------------------------------------------------------------- +#pragma once + +#include "mode_page_device.h" +#include + +using namespace std; + +class HostServices: public ModePageDevice +{ + +public: + + HostServices(); + ~HostServices() {} + + virtual bool Dispatch(SCSIDEV *) override; + + int Inquiry(const DWORD *, BYTE *) override; + void TestUnitReady(SCSIDEV *); + void StartStopUnit(SCSIDEV *); + + int ModeSense6(const DWORD *, BYTE *); + int ModeSense10(const DWORD *, BYTE *, int); + +private: + + typedef ModePageDevice super; + + Dispatcher dispatcher; + + void AddModePages(map>&, int, bool) const override; + void AddRealtimeClockPage(map>&, bool) const; +}; diff --git a/src_old/raspberrypi/devices/interfaces/scsi_block_commands.h b/src_old/raspberrypi/devices/interfaces/scsi_block_commands.h new file mode 100644 index 00000000..584f6af8 --- /dev/null +++ b/src_old/raspberrypi/devices/interfaces/scsi_block_commands.h @@ -0,0 +1,33 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2021 Uwe Seimet +// +// Interface for SCSI block commands (see https://www.t10.org/drafts.htm, SBC-5) +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "scsi_primary_commands.h" + +class SASIDEV; + +class ScsiBlockCommands : virtual public ScsiPrimaryCommands +{ +public: + + ScsiBlockCommands() {} + virtual ~ScsiBlockCommands() {} + + // Mandatory commands + virtual void FormatUnit(SASIDEV *) = 0; + virtual void ReadCapacity10(SASIDEV *) = 0; + virtual void ReadCapacity16(SASIDEV *) = 0; + virtual void Read10(SASIDEV *) = 0; + virtual void Read16(SASIDEV *) = 0; + virtual void Write10(SASIDEV *) = 0; + virtual void Write16(SASIDEV *) = 0; +}; diff --git a/src_old/raspberrypi/devices/interfaces/scsi_mmc_commands.h b/src_old/raspberrypi/devices/interfaces/scsi_mmc_commands.h new file mode 100644 index 00000000..d275490e --- /dev/null +++ b/src_old/raspberrypi/devices/interfaces/scsi_mmc_commands.h @@ -0,0 +1,27 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2021 Uwe Seimet +// +// Interface for SCSI Multi-Media commands (see https://www.t10.org/drafts.htm, MMC-6) +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "scsi_primary_commands.h" + +class SASIDEV; + +class ScsiMmcCommands : virtual public ScsiPrimaryCommands +{ +public: + + ScsiMmcCommands() {} + virtual ~ScsiMmcCommands() {} + + virtual void ReadToc(SASIDEV *) = 0; + virtual void GetEventStatusNotification(SASIDEV *) = 0; +}; diff --git a/src_old/raspberrypi/devices/interfaces/scsi_primary_commands.h b/src_old/raspberrypi/devices/interfaces/scsi_primary_commands.h new file mode 100644 index 00000000..585c90e5 --- /dev/null +++ b/src_old/raspberrypi/devices/interfaces/scsi_primary_commands.h @@ -0,0 +1,30 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2021 Uwe Seimet +// +// Interface for SCSI primary commands (see https://www.t10.org/drafts.htm, SPC-6) +// +//--------------------------------------------------------------------------- + +#pragma once + +class SASIDEV; + +class ScsiPrimaryCommands +{ +public: + + ScsiPrimaryCommands() {} + virtual ~ScsiPrimaryCommands() {} + + // Mandatory commands + virtual void TestUnitReady(SASIDEV *) = 0; + virtual void Inquiry(SASIDEV *) = 0; + virtual void ReportLuns(SASIDEV *) = 0; + + // Implemented for all RaSCSI device types + virtual void RequestSense(SASIDEV *) = 0; +}; diff --git a/src_old/raspberrypi/devices/interfaces/scsi_printer_commands.h b/src_old/raspberrypi/devices/interfaces/scsi_printer_commands.h new file mode 100644 index 00000000..c4ad78b4 --- /dev/null +++ b/src_old/raspberrypi/devices/interfaces/scsi_printer_commands.h @@ -0,0 +1,30 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2022 Uwe Seimet +// +// Interface for SCSI printer commands (see SCSI-2 specification) +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "scsi_primary_commands.h" + +class SCSIDEV; + +class ScsiPrinterCommands : virtual public ScsiPrimaryCommands +{ +public: + + ScsiPrinterCommands() {} + virtual ~ScsiPrinterCommands() {} + + // Mandatory commands + virtual void Print(SCSIDEV *) = 0; + virtual void ReleaseUnit(SCSIDEV *) = 0; + virtual void ReserveUnit(SCSIDEV *) = 0; + virtual void SendDiagnostic(SCSIDEV *) = 0; +}; diff --git a/src_old/raspberrypi/devices/mode_page_device.cpp b/src_old/raspberrypi/devices/mode_page_device.cpp new file mode 100644 index 00000000..d625d580 --- /dev/null +++ b/src_old/raspberrypi/devices/mode_page_device.cpp @@ -0,0 +1,178 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2022 Uwe Seimet +// +// A basic device with mode page support, to be used for subclassing +// +//--------------------------------------------------------------------------- + +#include "log.h" +#include "controllers/scsidev_ctrl.h" +#include "mode_page_device.h" + +using namespace std; +using namespace scsi_defs; + +ModePageDevice::ModePageDevice(const string& id) : PrimaryDevice(id) +{ + dispatcher.AddCommand(eCmdModeSense6, "ModeSense6", &ModePageDevice::ModeSense6); + dispatcher.AddCommand(eCmdModeSense10, "ModeSense10", &ModePageDevice::ModeSense10); + dispatcher.AddCommand(eCmdModeSelect6, "ModeSelect6", &ModePageDevice::ModeSelect6); + dispatcher.AddCommand(eCmdModeSelect10, "ModeSelect10", &ModePageDevice::ModeSelect10); +} + +bool ModePageDevice::Dispatch(SCSIDEV *controller) +{ + // The superclass class handles the less specific commands + return dispatcher.Dispatch(this, controller) ? true : super::Dispatch(controller); +} + +int ModePageDevice::AddModePages(const DWORD *cdb, BYTE *buf, int max_length) +{ + bool changeable = (cdb[2] & 0xc0) == 0x40; + + // Get page code (0x3f means all pages) + int page = cdb[2] & 0x3f; + + LOGTRACE("%s Requesting mode page $%02X", __PRETTY_FUNCTION__, page); + + // Mode page data mapped to the respective page numbers, C++ maps are ordered by key + map> pages; + AddModePages(pages, page, changeable); + + // If no mode data were added at all something must be wrong + if (pages.empty()) { + LOGTRACE("%s Unsupported mode page $%02X", __PRETTY_FUNCTION__, page); + SetStatusCode(STATUS_INVALIDCDB); + return 0; + } + + int size = 0; + + vector page0; + for (auto const& page : pages) { + if (size + (int)page.second.size() > max_length) { + LOGWARN("Mode page data size exceeds reserved buffer size"); + + page0.clear(); + + break; + } + else { + // The specification mandates that page 0 must be returned after all others + if (page.first) { + // Page data + memcpy(&buf[size], page.second.data(), page.second.size()); + // Page code, PS bit may already have been set + buf[size] |= page.first; + // Page payload size + buf[size + 1] = page.second.size() - 2; + + size += page.second.size(); + } + else { + page0 = page.second; + } + } + } + + // Page 0 must be last + if (!page0.empty()) { + memcpy(&buf[size], page0.data(), page0.size()); + // Page payload size + buf[size + 1] = page0.size() - 2; + size += page0.size(); + } + + return size; +} + +void ModePageDevice::ModeSense6(SASIDEV *controller) +{ + ctrl->length = ModeSense6(ctrl->cmd, ctrl->buffer); + if (ctrl->length <= 0) { + controller->Error(); + return; + } + + controller->DataIn(); +} + +void ModePageDevice::ModeSense10(SASIDEV *controller) +{ + ctrl->length = ModeSense10(ctrl->cmd, ctrl->buffer, ctrl->bufsize); + if (ctrl->length <= 0) { + controller->Error(); + return; + } + + controller->DataIn(); +} + +bool ModePageDevice::ModeSelect(const DWORD*, const BYTE *, int) +{ + // Cannot be set + SetStatusCode(STATUS_INVALIDPRM); + + return false; +} + +void ModePageDevice::ModeSelect6(SASIDEV *controller) +{ + LOGTRACE("%s Unsupported mode page $%02X", __PRETTY_FUNCTION__, ctrl->buffer[0]); + + ctrl->length = ModeSelectCheck6(); + if (ctrl->length <= 0) { + controller->Error(); + return; + } + + controller->DataOut(); +} + +void ModePageDevice::ModeSelect10(SASIDEV *controller) +{ + LOGTRACE("%s Unsupported mode page $%02X", __PRETTY_FUNCTION__, ctrl->buffer[0]); + + ctrl->length = ModeSelectCheck10(); + if (ctrl->length <= 0) { + controller->Error(); + return; + } + + controller->DataOut(); +} + +int ModePageDevice::ModeSelectCheck(int length) +{ + // Error if save parameters are set for other types than of SCHD or SCRM + // TODO The assumption above is not correct, and this code should be located elsewhere + if (!IsSCSIHD() && (ctrl->cmd[1] & 0x01)) { + SetStatusCode(STATUS_INVALIDCDB); + return 0; + } + + return length; +} + +int ModePageDevice::ModeSelectCheck6() +{ + // Receive the data specified by the parameter length + return ModeSelectCheck(ctrl->cmd[4]); +} + +int ModePageDevice::ModeSelectCheck10() +{ + // Receive the data specified by the parameter length + int length = ctrl->cmd[7]; + length <<= 8; + length |= ctrl->cmd[8]; + if (length > ctrl->bufsize) { + length = ctrl->bufsize; + } + + return ModeSelectCheck(length); +} diff --git a/src_old/raspberrypi/devices/mode_page_device.h b/src_old/raspberrypi/devices/mode_page_device.h new file mode 100644 index 00000000..19ec9db0 --- /dev/null +++ b/src_old/raspberrypi/devices/mode_page_device.h @@ -0,0 +1,53 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2022 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "primary_device.h" +#include +#include +#include + +using namespace std; + +class ModePageDevice: public PrimaryDevice +{ +public: + + ModePageDevice(const string&); + virtual ~ModePageDevice() {} + + virtual bool Dispatch(SCSIDEV *) override; + + virtual int ModeSense6(const DWORD *, BYTE *) = 0; + virtual int ModeSense10(const DWORD *, BYTE *, int) = 0; + + // TODO This method should not be called by SASIDEV + virtual bool ModeSelect(const DWORD *, const BYTE *, int); + +protected: + + int AddModePages(const DWORD *, BYTE *, int); + virtual void AddModePages(map>&, int, bool) const = 0; + +private: + + typedef PrimaryDevice super; + + Dispatcher dispatcher; + + void ModeSense6(SASIDEV *); + void ModeSense10(SASIDEV *); + void ModeSelect6(SASIDEV *); + void ModeSelect10(SASIDEV *); + + int ModeSelectCheck(int); + int ModeSelectCheck6(); + int ModeSelectCheck10(); +}; diff --git a/src_old/raspberrypi/devices/primary_device.cpp b/src_old/raspberrypi/devices/primary_device.cpp new file mode 100644 index 00000000..cad1212f --- /dev/null +++ b/src_old/raspberrypi/devices/primary_device.cpp @@ -0,0 +1,219 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2022 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include "log.h" +#include "controllers/scsidev_ctrl.h" +#include "dispatcher.h" +#include "primary_device.h" + +using namespace std; +using namespace scsi_defs; + +PrimaryDevice::PrimaryDevice(const string& id) : ScsiPrimaryCommands(), Device(id) +{ + ctrl = NULL; + + // Mandatory SCSI primary commands + dispatcher.AddCommand(eCmdTestUnitReady, "TestUnitReady", &PrimaryDevice::TestUnitReady); + dispatcher.AddCommand(eCmdInquiry, "Inquiry", &PrimaryDevice::Inquiry); + dispatcher.AddCommand(eCmdReportLuns, "ReportLuns", &PrimaryDevice::ReportLuns); + + // Optional commands used by all RaSCSI devices + dispatcher.AddCommand(eCmdRequestSense, "RequestSense", &PrimaryDevice::RequestSense); +} + +bool PrimaryDevice::Dispatch(SCSIDEV *controller) +{ + return dispatcher.Dispatch(this, controller); +} + +void PrimaryDevice::TestUnitReady(SASIDEV *controller) +{ + if (!CheckReady()) { + controller->Error(); + return; + } + + controller->Status(); +} + +void PrimaryDevice::Inquiry(SASIDEV *controller) +{ + ctrl->length = Inquiry(ctrl->cmd, ctrl->buffer); + if (ctrl->length <= 0) { + controller->Error(); + return; + } + + int lun = controller->GetEffectiveLun(); + + // Report if the device does not support the requested LUN + if (!ctrl->unit[lun]) { + LOGTRACE("Reporting LUN %d for device ID %d as not supported", lun, ctrl->device->GetId()); + + // Signal that the requested LUN does not exist + ctrl->buffer[0] |= 0x7f; + } + + controller->DataIn(); +} + +void PrimaryDevice::ReportLuns(SASIDEV *controller) +{ + BYTE *buf = ctrl->buffer; + + int allocation_length = (ctrl->cmd[6] << 24) + (ctrl->cmd[7] << 16) + (ctrl->cmd[8] << 8) + ctrl->cmd[9]; + memset(buf, 0, allocation_length); + + // Count number of available LUNs for the current device + int luns; + for (luns = 0; luns < controller->GetCtrl()->device->GetSupportedLuns(); luns++) { + if (!controller->GetCtrl()->unit[luns]) { + break; + } + } + + // LUN list length, 8 bytes per LUN + // SCSI standard: The contents of the LUN LIST LENGTH field are not altered based on the allocation length + buf[0] = (luns * 8) >> 24; + buf[1] = (luns * 8) >> 16; + buf[2] = (luns * 8) >> 8; + buf[3] = luns * 8; + + ctrl->length = allocation_length < 8 + luns * 8 ? allocation_length : 8 + luns * 8; + + controller->DataIn(); +} + +void PrimaryDevice::RequestSense(SASIDEV *controller) +{ + int lun = controller->GetEffectiveLun(); + + // Note: According to the SCSI specs the LUN handling for REQUEST SENSE non-existing LUNs do *not* result + // in CHECK CONDITION. Only the Sense Key and ASC are set in order to signal the non-existing LUN. + if (!ctrl->unit[lun]) { + // LUN 0 can be assumed to be present (required to call RequestSense() below) + lun = 0; + + controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::INVALID_LUN); + ctrl->status = 0x00; + } + + ctrl->length = ((PrimaryDevice *)ctrl->unit[lun])->RequestSense(ctrl->cmd, ctrl->buffer); + assert(ctrl->length > 0); + + LOGTRACE("%s Status $%02X, Sense Key $%02X, ASC $%02X",__PRETTY_FUNCTION__, ctrl->status, ctrl->buffer[2], ctrl->buffer[12]); + + controller->DataIn(); +} + +bool PrimaryDevice::CheckReady() +{ + // Not ready if reset + if (IsReset()) { + SetStatusCode(STATUS_DEVRESET); + SetReset(false); + LOGTRACE("%s Device in reset", __PRETTY_FUNCTION__); + return false; + } + + // Not ready if it needs attention + if (IsAttn()) { + SetStatusCode(STATUS_ATTENTION); + SetAttn(false); + LOGTRACE("%s Device in needs attention", __PRETTY_FUNCTION__); + return false; + } + + // Return status if not ready + if (!IsReady()) { + SetStatusCode(STATUS_NOTREADY); + LOGTRACE("%s Device not ready", __PRETTY_FUNCTION__); + return false; + } + + // Initialization with no error + LOGTRACE("%s Device is ready", __PRETTY_FUNCTION__); + + return true; +} + +int PrimaryDevice::Inquiry(int type, int scsi_level, bool is_removable, const DWORD *cdb, BYTE *buf) +{ + // EVPD and page code check + if ((cdb[1] & 0x01) || cdb[2]) { + SetStatusCode(STATUS_INVALIDCDB); + return 0; + } + + int allocation_length = cdb[4] + (cdb[3] << 8); + if (allocation_length > 4) { + if (allocation_length > 44) { + allocation_length = 44; + } + + // Basic data + // buf[0] ... SCSI Device type + // buf[1] ... Bit 7: Removable/not removable + // buf[2] ... SCSI-2 compliant command system + // buf[3] ... SCSI-2 compliant Inquiry response + // buf[4] ... Inquiry additional data + memset(buf, 0, allocation_length); + buf[0] = type; + buf[1] = is_removable ? 0x80 : 0x00; + buf[2] = scsi_level; + // Response data format is SCSI-2 for devices supporting SCSI-2 or newer, otherwise it is SCSI-1-CCS + buf[3] = scsi_level >= 2 ? 2 : 0; + buf[4] = 0x1F; + + // Padded vendor, product, revision + memcpy(&buf[8], GetPaddedName().c_str(), 28); + } + + return allocation_length; +} + +int PrimaryDevice::RequestSense(const DWORD *cdb, BYTE *buf) +{ + // Return not ready only if there are no errors + if (GetStatusCode() == STATUS_NOERROR && !IsReady()) { + SetStatusCode(STATUS_NOTREADY); + } + + // Size determination (according to allocation length) + int size = (int)cdb[4]; + assert(size >= 0 && size < 0x100); + + // For SCSI-1, transfer 4 bytes when the size is 0 + if (size == 0) { + size = 4; + } + + // Clear the buffer + memset(buf, 0, size); + + // Set 18 bytes including extended sense data + + // Current error + buf[0] = 0x70; + + buf[2] = (BYTE)(GetStatusCode() >> 16); + buf[7] = 10; + buf[12] = (BYTE)(GetStatusCode() >> 8); + buf[13] = (BYTE)GetStatusCode(); + + return size; +} + +bool PrimaryDevice::WriteBytes(BYTE *buf, uint32_t length) +{ + LOGERROR("%s Writing bytes is not supported by this device", __PRETTY_FUNCTION__); + + return false; +} diff --git a/src_old/raspberrypi/devices/primary_device.h b/src_old/raspberrypi/devices/primary_device.h new file mode 100644 index 00000000..c8923fee --- /dev/null +++ b/src_old/raspberrypi/devices/primary_device.h @@ -0,0 +1,54 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2022 Uwe Seimet +// +// A device implementing mandatory SCSI primary commands, to be used for subclassing +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "controllers/scsidev_ctrl.h" +#include "interfaces/scsi_primary_commands.h" +#include "device.h" +#include "dispatcher.h" +#include + +using namespace std; + +class PrimaryDevice: public Device, virtual public ScsiPrimaryCommands +{ +public: + + PrimaryDevice(const string&); + virtual ~PrimaryDevice() {} + + virtual bool Dispatch(SCSIDEV *); + + void TestUnitReady(SASIDEV *); + void RequestSense(SASIDEV *); + + void SetCtrl(SASIDEV::ctrl_t *ctrl) { this->ctrl = ctrl; } + + bool CheckReady(); + virtual int Inquiry(const DWORD *, BYTE *) = 0; + virtual int RequestSense(const DWORD *, BYTE *); + virtual bool WriteBytes(BYTE *, uint32_t); + virtual int GetSendDelay() { return BUS::SEND_NO_DELAY; } + +protected: + + int Inquiry(int, int, bool, const DWORD *, BYTE *); + + SASIDEV::ctrl_t *ctrl; + +private: + + Dispatcher dispatcher; + + void Inquiry(SASIDEV *); + void ReportLuns(SASIDEV *); +}; diff --git a/src_old/raspberrypi/devices/sasihd.cpp b/src_old/raspberrypi/devices/sasihd.cpp new file mode 100644 index 00000000..463eda6b --- /dev/null +++ b/src_old/raspberrypi/devices/sasihd.cpp @@ -0,0 +1,107 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// Copyright (C) akuker +// +// Licensed under the BSD 3-Clause License. +// See LICENSE file in the project root folder. +// +// [ SASI hard disk ] +// +//--------------------------------------------------------------------------- + +#include "sasihd.h" +#include "fileio.h" +#include "exceptions.h" +#include "../config.h" + +SASIHD::SASIHD(const set& sector_sizes) : Disk("SAHD") +{ + SetSectorSizes(sector_sizes); +} + +void SASIHD::Reset() +{ + // Unlock, clear attention + SetLocked(false); + SetAttn(false); + + // Reset, clear the code + SetReset(false); + SetStatusCode(STATUS_NOERROR); +} + +void SASIHD::Open(const Filepath& path) +{ + assert(!IsReady()); + + // Open as read-only + Fileio fio; + if (!fio.Open(path, Fileio::ReadOnly)) { + throw file_not_found_exception("Can't open SASI hard disk file"); + } + + // Get file size + off_t size = fio.GetFileSize(); + fio.Close(); + + // Sector size (default 256 bytes) and number of blocks + SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 256, true); + SetBlockCount((DWORD)(size >> GetSectorSizeShiftCount())); + + #if defined(REMOVE_FIXED_SASIHD_SIZE) + // Effective size must be a multiple of the sector size + size = (size / GetSectorSizeInBytes()) * GetSectorSizeInBytes(); + #else + // 10MB, 20MB, 40MBのみ + switch (size) { + // 10MB (10441728 BS=256 C=40788) + case 0x9f5400: + break; + + // 20MB (20748288 BS=256 C=81048) + case 0x13c9800: + break; + + // 40MB (41496576 BS=256 C=162096) + case 0x2793000: + break; + + // Other (Not supported ) + default: + throw io_exception("Unsupported file size"); + } + #endif // REMOVE_FIXED_SASIHD_SIZE + + Disk::Open(path); + FileSupport::SetPath(path); +} + +int SASIHD::Inquiry(const DWORD* /*cdb*/, BYTE* /*buf*/) +{ + SetStatusCode(STATUS_INVALIDCMD); + return 0; +} + +int SASIHD::RequestSense(const DWORD *cdb, BYTE *buf) +{ + // Size decision + int size = (int)cdb[4]; + assert(size >= 0 && size < 0x100); + + // Transfer 4 bytes when size 0 (Shugart Associates System Interface specification) + if (size == 0) { + size = 4; + } + + // SASI fixed to non-extended format + memset(buf, 0, size); + buf[0] = (BYTE)(GetStatusCode() >> 16); + buf[1] = (BYTE)(GetLun() << 5); + + return size; +} diff --git a/src_old/raspberrypi/devices/sasihd.h b/src_old/raspberrypi/devices/sasihd.h new file mode 100644 index 00000000..c802364b --- /dev/null +++ b/src_old/raspberrypi/devices/sasihd.h @@ -0,0 +1,39 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// Copyright (C) akuker +// +// Licensed under the BSD 3-Clause License. +// See LICENSE file in the project root folder. +// +// [ SASI hard disk ] +// +//--------------------------------------------------------------------------- +#pragma once + +#include "os.h" +#include "disk.h" +#include "filepath.h" + +//=========================================================================== +// +// SASI Hard Disk +// +//=========================================================================== +class SASIHD : public Disk, public FileSupport +{ +public: + SASIHD(const set&); + ~SASIHD() {} + + void Reset(); + void Open(const Filepath& path) override; + + // Commands + int RequestSense(const DWORD *cdb, BYTE *buf) override; + int Inquiry(const DWORD *cdb, BYTE *buf) override; +}; diff --git a/src_old/raspberrypi/devices/scsi_daynaport.cpp b/src_old/raspberrypi/devices/scsi_daynaport.cpp new file mode 100644 index 00000000..4094fbcc --- /dev/null +++ b/src_old/raspberrypi/devices/scsi_daynaport.cpp @@ -0,0 +1,593 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2020 akuker +// Copyright (C) 2014-2020 GIMONS +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// +// Licensed under the BSD 3-Clause License. +// See LICENSE file in the project root folder. +// +// [ Emulation of the DaynaPort SCSI Link Ethernet interface ] +// +// This design is derived from the SLINKCMD.TXT file, as well as David Kuder's +// Tiny SCSI Emulator +// - SLINKCMD: http://www.bitsavers.org/pdf/apple/scsi/dayna/daynaPORT/SLINKCMD.TXT +// - Tiny SCSI : https://hackaday.io/project/18974-tiny-scsi-emulator +// +// Additional documentation and clarification is available at the +// following link: +// - https://github.com/akuker/RASCSI/wiki/Dayna-Port-SCSI-Link +// +// This does NOT include the file system functionality that is present +// in the Sharp X68000 host bridge. +// +// Note: This requires a DaynaPort SCSI Link driver. +//--------------------------------------------------------------------------- + +#include "scsi_daynaport.h" + +using namespace scsi_defs; + +const BYTE SCSIDaynaPort::m_bcast_addr[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; +const BYTE SCSIDaynaPort::m_apple_talk_addr[6] = { 0x09, 0x00, 0x07, 0xff, 0xff, 0xff }; + +// TODO Disk should not be the superclass +SCSIDaynaPort::SCSIDaynaPort() : Disk("SCDP") +{ + m_tap = NULL; + m_bTapEnable = false; + + dispatcher.AddCommand(eCmdTestUnitReady, "TestUnitReady", &SCSIDaynaPort::TestUnitReady); + dispatcher.AddCommand(eCmdRead6, "Read6", &SCSIDaynaPort::Read6); + dispatcher.AddCommand(eCmdWrite6, "Write6", &SCSIDaynaPort::Write6); + dispatcher.AddCommand(eCmdRetrieveStats, "RetrieveStats", &SCSIDaynaPort::RetrieveStatistics); + dispatcher.AddCommand(eCmdSetIfaceMode, "SetIfaceMode", &SCSIDaynaPort::SetInterfaceMode); + dispatcher.AddCommand(eCmdSetMcastAddr, "SetMcastAddr", &SCSIDaynaPort::SetMcastAddr); + dispatcher.AddCommand(eCmdEnableInterface, "EnableInterface", &SCSIDaynaPort::EnableInterface); +} + +SCSIDaynaPort::~SCSIDaynaPort() +{ + // TAP driver release + if (m_tap) { + m_tap->Cleanup(); + delete m_tap; + } +} + +bool SCSIDaynaPort::Dispatch(SCSIDEV *controller) +{ + // The superclass class handles the less specific commands + return dispatcher.Dispatch(this, controller) ? true : super::Dispatch(controller); +} + +bool SCSIDaynaPort::Init(const map& params) +{ + SetParams(params); + +#ifdef __linux__ + // TAP Driver Generation + m_tap = new CTapDriver(); + m_bTapEnable = m_tap->Init(GetParams()); + if(!m_bTapEnable){ + LOGERROR("Unable to open the TAP interface"); + +// Not terminating on regular Linux PCs is helpful for testing +#if !defined(__x86_64__) && !defined(__X86__) + return false; +#endif + } else { + LOGDEBUG("Tap interface created"); + } + + this->Reset(); + SetReady(true); + SetReset(false); + + // Generate MAC Address + memset(m_mac_addr, 0x00, 6); + + // if (m_bTapEnable) { + // tap->GetMacAddr(m_mac_addr); + // m_mac_addr[5]++; + // } + // !!!!!!!!!!!!!!!!! For now, hard code the MAC address. Its annoying when it keeps changing during development! + // TODO: Remove this hard-coded address + m_mac_addr[0]=0x00; + m_mac_addr[1]=0x80; + m_mac_addr[2]=0x19; + m_mac_addr[3]=0x10; + m_mac_addr[4]=0x98; + m_mac_addr[5]=0xE3; +#endif // linux + + return true; +} + +void SCSIDaynaPort::Open(const Filepath& path) +{ + m_tap->OpenDump(path); +} + +int SCSIDaynaPort::Inquiry(const DWORD *cdb, BYTE *buf) +{ + // Processor device, SCSI-2, not removable + return PrimaryDevice::Inquiry(3, 2, false, cdb, buf); +} + +//--------------------------------------------------------------------------- +// +// READ +// +// Command: 08 00 00 LL LL XX (LLLL is data length, XX = c0 or 80) +// Function: Read a packet at a time from the device (standard SCSI Read) +// Type: Input; the following data is returned: +// LL LL NN NN NN NN XX XX XX ... CC CC CC CC +// where: +// LLLL is normally the length of the packet (a 2-byte +// big-endian hex value), including 4 trailing bytes +// of CRC, but excluding itself and the flag field. +// See below for special values +// NNNNNNNN is a 4-byte flag field with the following meanings: +// FFFFFFFF a packet has been dropped (?); in this case +// the length field appears to be always 4000 +// 00000010 there are more packets currently available +// in SCSI/Link memory +// 00000000 this is the last packet +// XX XX ... is the actual packet +// CCCCCCCC is the CRC +// +// Notes: +// - When all packets have been retrieved successfully, a length field +// of 0000 is returned; however, if a packet has been dropped, the +// SCSI/Link will instead return a non-zero length field with a flag +// of FFFFFFFF when there are no more packets available. This behaviour +// seems to continue until a disable/enable sequence has been issued. +// - The SCSI/Link apparently has about 6KB buffer space for packets. +// +//--------------------------------------------------------------------------- +int SCSIDaynaPort::Read(const DWORD *cdb, BYTE *buf, uint64_t block) +{ + int rx_packet_size = 0; + scsi_resp_read_t *response = (scsi_resp_read_t*)buf; + + int requested_length = cdb[4]; + LOGTRACE("%s Read maximum length %d, (%04X)", __PRETTY_FUNCTION__, requested_length, requested_length); + + + // At host startup, it will send a READ(6) command with a length of 1. We should + // respond by going into the status mode with a code of 0x02 + if (requested_length == 1) { + return 0; + } + + // Some of the packets we receive will not be for us. So, we'll keep pulling messages + // until the buffer is empty, or we've read X times. (X is just a made up number) + bool send_message_to_host; + int read_count = 0; + while (read_count < MAX_READ_RETRIES) { + read_count++; + + // The first 2 bytes are reserved for the length of the packet + // The next 4 bytes are reserved for a flag field + //rx_packet_size = m_tap->Rx(response->data); + rx_packet_size = m_tap->Rx(&buf[DAYNAPORT_READ_HEADER_SZ]); + + // If we didn't receive anything, return size of 0 + if (rx_packet_size <= 0) { + LOGTRACE("%s No packet received", __PRETTY_FUNCTION__); + response->length = 0; + response->flags = e_no_more_data; + return DAYNAPORT_READ_HEADER_SZ; + } + + LOGTRACE("%s Packet Sz %d (%08X) read: %d", __PRETTY_FUNCTION__, (unsigned int) rx_packet_size, (unsigned int) rx_packet_size, read_count); + + // This is a very basic filter to prevent unnecessary packets from + // being sent to the SCSI initiator. + send_message_to_host = false; + + // The following doesn't seem to work with unicast messages. Temporarily removing the filtering + // functionality. + /////// // Check if received packet destination MAC address matches the + /////// // DaynaPort MAC. For IP packets, the mac_address will be the first 6 bytes + /////// // of the data. + /////// if (memcmp(response->data, m_mac_addr, 6) == 0) { + /////// send_message_to_host = true; + /////// } + + /////// // Check to see if this is a broadcast message + /////// if (memcmp(response->data, m_bcast_addr, 6) == 0) { + /////// send_message_to_host = true; + /////// } + + /////// // Check to see if this is an AppleTalk Message + /////// if (memcmp(response->data, m_apple_talk_addr, 6) == 0) { + /////// send_message_to_host = true; + /////// } + send_message_to_host = true; + + // TODO: We should check to see if this message is in the multicast + // configuration from SCSI command 0x0D + + if (!send_message_to_host) { + LOGDEBUG("%s Received a packet that's not for me: %02X %02X %02X %02X %02X %02X", \ + __PRETTY_FUNCTION__, + (int)response->data[0], + (int)response->data[1], + (int)response->data[2], + (int)response->data[3], + (int)response->data[4], + (int)response->data[5]); + + // If there are pending packets to be processed, we'll tell the host that the read + // length was 0. + if (!m_tap->PendingPackets()) + { + response->length = 0; + response->flags = e_no_more_data; + return DAYNAPORT_READ_HEADER_SZ; + } + } + else { + // TODO: Need to do some sort of size checking. The buffer can easily overflow, probably. + + // response->length = rx_packet_size; + // if(m_tap->PendingPackets()){ + // response->flags = e_more_data_available; + // } else { + // response->flags = e_no_more_data; + // } + int size = rx_packet_size; + if (size < 64) { + // A frame must have at least 64 bytes (see https://github.com/akuker/RASCSI/issues/619) + // Note that this work-around breaks the checksum. As currently there are no known drivers + // that care for the checksum, and the Daynaport driver for the Atari expects frames of + // 64 bytes it was decided to accept the broken checksum. If a driver should pop up that + // breaks because of this, the work-around has to be re-evaluated. + size = 64; + } + buf[0] = size >> 8; + buf[1] = size; + + buf[2] = 0; + buf[3] = 0; + buf[4] = 0; + if(m_tap->PendingPackets()){ + buf[5] = 0x10; + } else { + buf[5] = 0; + } + + // Return the packet size + 2 for the length + 4 for the flag field + // The CRC was already appended by the ctapdriver + return size + DAYNAPORT_READ_HEADER_SZ; + } + // If we got to this point, there are still messages in the queue, so + // we should loop back and get the next one. + } // end while + + response->length = 0; + response->flags = e_no_more_data; + return DAYNAPORT_READ_HEADER_SZ; +} + +//--------------------------------------------------------------------------- +// +// WRITE check +// +//--------------------------------------------------------------------------- +int SCSIDaynaPort::WriteCheck(DWORD block) +{ + // Status check + if (!CheckReady()) { + return 0; + } + + if (!m_bTapEnable){ + SetStatusCode(STATUS_NOTREADY); + return 0; + } + + // Success + return 1; +} + +//--------------------------------------------------------------------------- +// +// Write +// +// Command: 0a 00 00 LL LL XX (LLLL is data length, XX = 80 or 00) +// Function: Write a packet at a time to the device (standard SCSI Write) +// Type: Output; the format of the data to be sent depends on the value +// of XX, as follows: +// - if XX = 00, LLLL is the packet length, and the data to be sent +// must be an image of the data packet +// - if XX = 80, LLLL is the packet length + 8, and the data to be +// sent is: +// PP PP 00 00 XX XX XX ... 00 00 00 00 +// where: +// PPPP is the actual (2-byte big-endian) packet length +// XX XX ... is the actual packet +// +//--------------------------------------------------------------------------- +bool SCSIDaynaPort::Write(const DWORD *cdb, const BYTE *buf, DWORD block) +{ + BYTE data_format = cdb[5]; + WORD data_length = (WORD)cdb[4] + ((WORD)cdb[3] << 8); + + if (data_format == 0x00){ + m_tap->Tx(buf, data_length); + LOGTRACE("%s Transmitted %u bytes (00 format)", __PRETTY_FUNCTION__, data_length); + return true; + } + else if (data_format == 0x80){ + // The data length is specified in the first 2 bytes of the payload + data_length=(WORD)buf[1] + ((WORD)buf[0] << 8); + m_tap->Tx(&buf[4], data_length); + LOGTRACE("%s Transmitted %u bytes (80 format)", __PRETTY_FUNCTION__, data_length); + return true; + } + else + { + // LOGWARN("%s Unknown data format %02X", __PRETTY_FUNCTION__, (unsigned int)command->format); + LOGWARN("%s Unknown data format %02X", __PRETTY_FUNCTION__, (unsigned int)data_format); + return true; + } +} + +//--------------------------------------------------------------------------- +// +// RetrieveStats +// +// Command: 09 00 00 00 12 00 +// Function: Retrieve MAC address and device statistics +// Type: Input; returns 18 (decimal) bytes of data as follows: +// - bytes 0-5: the current hardware ethernet (MAC) address +// - bytes 6-17: three long word (4-byte) counters (little-endian). +// Notes: The contents of the three longs are typically zero, and their +// usage is unclear; they are suspected to be: +// - long #1: frame alignment errors +// - long #2: CRC errors +// - long #3: frames lost +// +//--------------------------------------------------------------------------- +int SCSIDaynaPort::RetrieveStats(const DWORD *cdb, BYTE *buffer) +{ + int allocation_length = cdb[4] + (((DWORD)cdb[3]) << 8); + + // memset(buffer,0,18); + // memcpy(&buffer[0],m_mac_addr,sizeof(m_mac_addr)); + // uint32_t frame_alignment_errors; + // uint32_t crc_errors; + // uint32_t frames_lost; + // // frame alignment errors + // frame_alignment_errors = htonl(0); + // memcpy(&(buffer[6]),&frame_alignment_errors,sizeof(frame_alignment_errors)); + // // CRC errors + // crc_errors = htonl(0); + // memcpy(&(buffer[10]),&crc_errors,sizeof(crc_errors)); + // // frames lost + // frames_lost = htonl(0); + // memcpy(&(buffer[14]),&frames_lost,sizeof(frames_lost)); + + int response_size = sizeof(m_scsi_link_stats); + memcpy(buffer, &m_scsi_link_stats, sizeof(m_scsi_link_stats)); + + if (response_size > allocation_length) { + response_size = allocation_length; + } + + // Success + return response_size; +} + +//--------------------------------------------------------------------------- +// +// Enable or Disable the interface +// +// Command: 0e 00 00 00 00 XX (XX = 80 or 00) +// Function: Enable (80) / disable (00) Ethernet interface +// Type: No data transferred +// Notes: After issuing an Enable, the initiator should avoid sending +// any subsequent commands to the device for approximately 0.5 +// seconds +// +//--------------------------------------------------------------------------- +bool SCSIDaynaPort::EnableInterface(const DWORD *cdb) +{ + bool result; + if (cdb[5] & 0x80) { + result = m_tap->Enable(); + if (result) { + LOGINFO("The DaynaPort interface has been ENABLED."); + } + else{ + LOGWARN("Unable to enable the DaynaPort Interface"); + } + m_tap->Flush(); + } + else { + result = m_tap->Disable(); + if (result) { + LOGINFO("The DaynaPort interface has been DISABLED."); + } + else{ + LOGWARN("Unable to disable the DaynaPort Interface"); + } + } + + return result; +} + +void SCSIDaynaPort::TestUnitReady(SASIDEV *controller) +{ + // TEST UNIT READY Success + + controller->Status(); +} + +void SCSIDaynaPort::Read6(SASIDEV *controller) +{ + // Get record number and block number + uint32_t record = ctrl->cmd[1] & 0x1f; + record <<= 8; + record |= ctrl->cmd[2]; + record <<= 8; + record |= ctrl->cmd[3]; + ctrl->blocks=1; + + // If any commands have a bogus control value, they were probably not + // generated by the DaynaPort driver so ignore them + if (ctrl->cmd[5] != 0xc0 && ctrl->cmd[5] != 0x80) { + LOGTRACE("%s Control value %d, (%04X), returning invalid CDB", __PRETTY_FUNCTION__, ctrl->cmd[5], ctrl->cmd[5]); + controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::INVALID_FIELD_IN_CDB); + return; + } + + LOGTRACE("%s READ(6) command record=%d blocks=%d", __PRETTY_FUNCTION__, (unsigned int)record, (int)ctrl->blocks); + + ctrl->length = Read(ctrl->cmd, ctrl->buffer, record); + LOGTRACE("%s ctrl.length is %d", __PRETTY_FUNCTION__, (int)ctrl->length); + + // Set next block + ctrl->next = record + 1; + + controller->DataIn(); +} + +void SCSIDaynaPort::Write6(SASIDEV *controller) +{ + // Reallocate buffer (because it is not transfer for each block) + if (ctrl->bufsize < DAYNAPORT_BUFFER_SIZE) { + free(ctrl->buffer); + ctrl->bufsize = DAYNAPORT_BUFFER_SIZE; + ctrl->buffer = (BYTE *)malloc(ctrl->bufsize); + } + + DWORD data_format = ctrl->cmd[5]; + + if(data_format == 0x00) { + ctrl->length = (WORD)ctrl->cmd[4] + ((WORD)ctrl->cmd[3] << 8); + } + else if (data_format == 0x80) { + ctrl->length = (WORD)ctrl->cmd[4] + ((WORD)ctrl->cmd[3] << 8) + 8; + } + else { + LOGWARN("%s Unknown data format %02X", __PRETTY_FUNCTION__, (unsigned int)data_format); + } + LOGTRACE("%s length: %04X (%d) format: %02X", __PRETTY_FUNCTION__, (unsigned int)ctrl->length, (int)ctrl->length, (unsigned int)data_format); + + if (ctrl->length <= 0) { + // Failure (Error) + controller->Error(); + return; + } + + // Set next block + ctrl->blocks = 1; + ctrl->next = 1; + + controller->DataOut(); +} + +void SCSIDaynaPort::RetrieveStatistics(SASIDEV *controller) +{ + ctrl->length = RetrieveStats(ctrl->cmd, ctrl->buffer); + if (ctrl->length <= 0) { + // Failure (Error) + controller->Error(); + return; + } + + // Set next block + ctrl->blocks = 1; + ctrl->next = 1; + + controller->DataIn(); +} + +//--------------------------------------------------------------------------- +// +// Set interface mode/Set MAC address +// +// Set Interface Mode (0c) +// ----------------------- +// Command: 0c 00 00 00 FF 80 (FF = 08 or 04) +// Function: Allow interface to receive broadcast messages (FF = 04); the +// function of (FF = 08) is currently unknown. +// Type: No data transferred +// Notes: This command is accepted by firmware 1.4a & 2.0f, but has no +// effect on 2.0f, which is always capable of receiving broadcast +// messages. In 1.4a, once broadcast mode is set, it remains set +// until the interface is disabled. +// +// Set MAC Address (0c) +// -------------------- +// Command: 0c 00 00 00 FF 40 (FF = 08 or 04) +// Function: Set MAC address +// Type: Output; overrides built-in MAC address with user-specified +// 6-byte value +// Notes: This command is intended primarily for debugging/test purposes. +// Disabling the interface resets the MAC address to the built-in +// value. +// +//--------------------------------------------------------------------------- +void SCSIDaynaPort::SetInterfaceMode(SASIDEV *controller) +{ + // Check whether this command is telling us to "Set Interface Mode" or "Set MAC Address" + + ctrl->length = RetrieveStats(ctrl->cmd, ctrl->buffer); + switch(ctrl->cmd[5]){ + case SCSIDaynaPort::CMD_SCSILINK_SETMODE: + // TODO Not implemented, do nothing + controller->Status(); + break; + + case SCSIDaynaPort::CMD_SCSILINK_SETMAC: + ctrl->length = 6; + controller->DataOut(); + break; + + default: + LOGWARN("%s Unknown SetInterface command received: %02X", __PRETTY_FUNCTION__, (unsigned int)ctrl->cmd[5]); + break; + } +} + +void SCSIDaynaPort::SetMcastAddr(SASIDEV *controller) +{ + ctrl->length = (DWORD)ctrl->cmd[4]; + if (ctrl->length == 0) { + LOGWARN("%s Not supported SetMcastAddr Command %02X", __PRETTY_FUNCTION__, (WORD)ctrl->cmd[2]); + + // Failure (Error) + controller->Error(); + return; + } + + controller->DataOut(); +} + +void SCSIDaynaPort::EnableInterface(SASIDEV *controller) +{ + bool status = EnableInterface(ctrl->cmd); + if (!status) { + // Failure (Error) + controller->Error(); + return; + } + + controller->Status(); +} + +int SCSIDaynaPort::GetSendDelay() +{ + // The Daynaport needs to have a delay after the size/flags field + // of the read response. In the MacOS driver, it looks like the + // driver is doing two "READ" system calls. + return DAYNAPORT_READ_HEADER_SZ; +} diff --git a/src_old/raspberrypi/devices/scsi_daynaport.h b/src_old/raspberrypi/devices/scsi_daynaport.h new file mode 100644 index 00000000..cda2cb0c --- /dev/null +++ b/src_old/raspberrypi/devices/scsi_daynaport.h @@ -0,0 +1,143 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2020 akuker +// Copyright (C) 2014-2020 GIMONS +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// +// Licensed under the BSD 3-Clause License. +// See LICENSE file in the project root folder. +// +// [ Emulation of the DaynaPort SCSI Link Ethernet interface ] +// +// This design is derived from the SLINKCMD.TXT file, as well as David Kuder's +// Tiny SCSI Emulator +// - SLINKCMD: http://www.bitsavers.org/pdf/apple/scsi/dayna/daynaPORT/SLINKCMD.TXT +// - Tiny SCSI : https://hackaday.io/project/18974-tiny-scsi-emulator +// +// Special thanks to @PotatoFi for loaning me his Farallon EtherMac for +// this development. (Farallon's EtherMac is a re-branded DaynaPort +// SCSI/Link-T). +// +// This does NOT include the file system functionality that is present +// in the Sharp X68000 host bridge. +// +// Note: This requires the DaynaPort SCSI Link driver. +//--------------------------------------------------------------------------- +#pragma once + +#include "os.h" +#include "disk.h" +#include "ctapdriver.h" +#include +#include + +//=========================================================================== +// +// DaynaPort SCSI Link +// +//=========================================================================== +class SCSIDaynaPort: public Disk +{ + +public: + SCSIDaynaPort(); + ~SCSIDaynaPort(); + + bool Init(const map&) override; + void Open(const Filepath& path) override; + + // Commands + int Inquiry(const DWORD *cdb, BYTE *buffer) override; + int Read(const DWORD *cdb, BYTE *buf, uint64_t block) override; + bool Write(const DWORD *cdb, const BYTE *buf, DWORD block) override; + int WriteCheck(DWORD block) override; // WRITE check + + int RetrieveStats(const DWORD *cdb, BYTE *buffer); + bool EnableInterface(const DWORD *cdb); + + void SetMacAddr(const DWORD *cdb, BYTE *buffer); // Set MAC address + + void TestUnitReady(SASIDEV *) override; + void Read6(SASIDEV *) override; + void Write6(SASIDEV *) override; + void RetrieveStatistics(SASIDEV *); + void SetInterfaceMode(SASIDEV *); + void SetMcastAddr(SASIDEV *); + void EnableInterface(SASIDEV *); + int GetSendDelay() override; + + bool Dispatch(SCSIDEV *) override; + + const int DAYNAPORT_BUFFER_SIZE = 0x1000000; + + static const BYTE CMD_SCSILINK_STATS = 0x09; + static const BYTE CMD_SCSILINK_ENABLE = 0x0E; + static const BYTE CMD_SCSILINK_SET = 0x0C; + static const BYTE CMD_SCSILINK_SETMODE = 0x80; + static const BYTE CMD_SCSILINK_SETMAC = 0x40; + + // When we're reading the Linux tap device, most of the messages will not be for us, so we + // need to filter through those. However, we don't want to keep re-reading the packets + // indefinitely. So, we'll pick a large-ish number that will cause the emulated DaynaPort + // to respond with "no data" after MAX_READ_RETRIES tries. + static const int MAX_READ_RETRIES = 50; + + // The READ response has a header which consists of: + // 2 bytes - payload size + // 4 bytes - status flags + static const DWORD DAYNAPORT_READ_HEADER_SZ = 2 + 4; + +private: + typedef Disk super; + + Dispatcher dispatcher; + + typedef struct __attribute__((packed)) { + BYTE operation_code; + BYTE misc_cdb_information; + BYTE logical_block_address; + uint16_t length; + BYTE format; + } scsi_cmd_daynaport_write_t; + + enum read_data_flags_t : uint32_t { + e_no_more_data = 0x00000000, + e_more_data_available = 0x00000001, + e_dropped_packets = 0xFFFFFFFF, + }; + + typedef struct __attribute__((packed)) { + uint32_t length; + read_data_flags_t flags; + BYTE pad; + BYTE data[ETH_FRAME_LEN + sizeof(uint32_t)]; // Frame length + 4 byte CRC + } scsi_resp_read_t; + + typedef struct __attribute__((packed)) { + BYTE mac_address[6]; + uint32_t frame_alignment_errors; + uint32_t crc_errors; + uint32_t frames_lost; + } scsi_resp_link_stats_t; + + scsi_resp_link_stats_t m_scsi_link_stats = { + .mac_address = { 0x00, 0x80, 0x19, 0x10, 0x98, 0xE3 },//MAC address of @PotatoFi's DayanPort + .frame_alignment_errors = 0, + .crc_errors = 0, + .frames_lost = 0, + }; + + const BYTE m_daynacom_mac_prefix[3] = { 0x00, 0x80, 0x19 }; + + CTapDriver *m_tap; + // TAP driver + bool m_bTapEnable; + // TAP valid flag + BYTE m_mac_addr[6]; + // MAC Address + static const BYTE m_bcast_addr[6]; + static const BYTE m_apple_talk_addr[6]; +}; diff --git a/src_old/raspberrypi/devices/scsi_host_bridge.cpp b/src_old/raspberrypi/devices/scsi_host_bridge.cpp new file mode 100644 index 00000000..d0dbb8c7 --- /dev/null +++ b/src_old/raspberrypi/devices/scsi_host_bridge.cpp @@ -0,0 +1,1184 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// Copyright (C) akuker +// +// Licensed under the BSD 3-Clause License. +// See LICENSE file in the project root folder. +// +// [ SCSI Host Bridge for the Sharp X68000 ] +// +// Note: This requires a special driver on the host system and will only +// work with the Sharp X68000 operating system. +//--------------------------------------------------------------------------- + +#include "controllers/scsidev_ctrl.h" +#include "scsi_host_bridge.h" +#include "ctapdriver.h" +#include "cfilesystem.h" + +using namespace std; +using namespace scsi_defs; + +SCSIBR::SCSIBR() : Disk("SCBR") +{ + tap = NULL; + m_bTapEnable = false; + packet_enable = false; + + fsoptlen = 0; + fsoutlen = 0; + fsresult = 0; + packet_len = 0; + + // Create host file system + fs = new CFileSys(); + fs->Reset(); + + dispatcher.AddCommand(eCmdTestUnitReady, "TestUnitReady", &SCSIBR::TestUnitReady); + dispatcher.AddCommand(eCmdRead6, "GetMessage10", &SCSIBR::GetMessage10); + dispatcher.AddCommand(eCmdWrite6, "SendMessage10", &SCSIBR::SendMessage10); +} + +SCSIBR::~SCSIBR() +{ + // TAP driver release + if (tap) { + tap->Cleanup(); + delete tap; + } + + // Release host file system + if (fs) { + fs->Reset(); + delete fs; + } +} + +bool SCSIBR::Init(const map& params) +{ + SetParams(params); + +#ifdef __linux__ + // TAP Driver Generation + tap = new CTapDriver(); + m_bTapEnable = tap->Init(GetParams()); + if (!m_bTapEnable){ + LOGERROR("Unable to open the TAP interface"); + return false; + } + + // Generate MAC Address + memset(mac_addr, 0x00, 6); + if (m_bTapEnable) { + tap->GetMacAddr(mac_addr); + mac_addr[5]++; + } + + // Packet reception flag OFF + packet_enable = false; +#endif + + SetReady(m_bTapEnable); + + // Not terminating on regular Linux PCs is helpful for testing +#if defined(__x86_64__) || defined(__X86__) + return true; +#else + return m_bTapEnable; +#endif +} + +bool SCSIBR::Dispatch(SCSIDEV *controller) +{ + // The superclass class handles the less specific commands + return dispatcher.Dispatch(this, controller) ? true : super::Dispatch(controller); +} + +//--------------------------------------------------------------------------- +// +// INQUIRY +// +//--------------------------------------------------------------------------- +int SCSIBR::Inquiry(const DWORD *cdb, BYTE *buf) +{ + // EVPD check + if (cdb[1] & 0x01) { + SetStatusCode(STATUS_INVALIDCDB); + return 0; + } + + // Basic data + // buf[0] ... Communication Device + // buf[2] ... SCSI-2 compliant command system + // buf[3] ... SCSI-2 compliant Inquiry response + // buf[4] ... Inquiry additional data + memset(buf, 0, 8); + buf[0] = 0x09; + buf[2] = 0x02; + buf[3] = 0x02; + buf[4] = 0x1F + 8; // required + 8 byte extension + + // Padded vendor, product, revision + memcpy(&buf[8], GetPaddedName().c_str(), 28); + + // Optional function valid flag + buf[36] = '0'; + + // TAP Enable + if (m_bTapEnable) { + buf[37] = '1'; + } + + // CFileSys Enable + buf[38] = '1'; + + // Size of data that can be returned + int size = (buf[4] + 5); + + // Limit if the other buffer is small + if (size > (int)cdb[4]) { + size = (int)cdb[4]; + } + + // Success + return size; +} + +void SCSIBR::TestUnitReady(SASIDEV *controller) +{ + // Always successful + controller->Status(); +} + +int SCSIBR::GetMessage10(const DWORD *cdb, BYTE *buf) +{ + // Type + int type = cdb[2]; + + // Function number + int func = cdb[3]; + + // Phase + int phase = cdb[9]; + + switch (type) { + case 1: // Ethernet + // Do not process if TAP is invalid + if (!m_bTapEnable) { + return 0; + } + + switch (func) { + case 0: // Get MAC address + return GetMacAddr(buf); + + case 1: // Received packet acquisition (size/buffer) + if (phase == 0) { + // Get packet size + ReceivePacket(); + buf[0] = (BYTE)(packet_len >> 8); + buf[1] = (BYTE)packet_len; + return 2; + } else { + // Get package data + GetPacketBuf(buf); + return packet_len; + } + + case 2: // Received packet acquisition (size + buffer simultaneously) + ReceivePacket(); + buf[0] = (BYTE)(packet_len >> 8); + buf[1] = (BYTE)packet_len; + GetPacketBuf(&buf[2]); + return packet_len + 2; + + case 3: // Simultaneous acquisition of multiple packets (size + buffer simultaneously) + // Currently the maximum number of packets is 10 + // Isn't it too fast if I increase more? + int total_len = 0; + for (int i = 0; i < 10; i++) { + ReceivePacket(); + *buf++ = (BYTE)(packet_len >> 8); + *buf++ = (BYTE)packet_len; + total_len += 2; + if (packet_len == 0) + break; + GetPacketBuf(buf); + buf += packet_len; + total_len += packet_len; + } + return total_len; + } + break; + + case 2: // Host Drive + switch (phase) { + case 0: // Get result code + return ReadFsResult(buf); + + case 1: // Return data acquisition + return ReadFsOut(buf); + + case 2: // Return additional data acquisition + return ReadFsOpt(buf); + } + break; + } + + // Error + ASSERT(false); + return 0; +} + +bool SCSIBR::SendMessage10(const DWORD *cdb, BYTE *buf) +{ + // Type + int type = cdb[2]; + + // Function number + int func = cdb[3]; + + // Phase + int phase = cdb[9]; + + // Get the number of lights + int len = cdb[6]; + len <<= 8; + len |= cdb[7]; + len <<= 8; + len |= cdb[8]; + + switch (type) { + case 1: // Ethernet + // Do not process if TAP is invalid + if (!m_bTapEnable) { + return false; + } + + switch (func) { + case 0: // MAC address setting + SetMacAddr(buf); + return true; + + case 1: // Send packet + SendPacket(buf, len); + return true; + } + break; + + case 2: // Host drive + switch (phase) { + case 0: // issue command + WriteFs(func, buf); + return true; + + case 1: // additional data writing + WriteFsOpt(buf, len); + return true; + } + break; + } + + // Error + ASSERT(false); + return false; +} + +void SCSIBR::GetMessage10(SASIDEV *controller) +{ + // Reallocate buffer (because it is not transfer for each block) + if (ctrl->bufsize < 0x1000000) { + free(ctrl->buffer); + ctrl->bufsize = 0x1000000; + ctrl->buffer = (BYTE *)malloc(ctrl->bufsize); + } + + ctrl->length = GetMessage10(ctrl->cmd, ctrl->buffer); + if (ctrl->length <= 0) { + // Failure (Error) + controller->Error(); + return; + } + + // Set next block + ctrl->blocks = 1; + ctrl->next = 1; + + controller->DataIn(); +} + +//--------------------------------------------------------------------------- +// +// SEND MESSAGE(10) +// +// This Send Message command is used by the X68000 host driver +// +//--------------------------------------------------------------------------- +void SCSIBR::SendMessage10(SASIDEV *controller) +{ + // Reallocate buffer (because it is not transfer for each block) + if (ctrl->bufsize < 0x1000000) { + free(ctrl->buffer); + ctrl->bufsize = 0x1000000; + ctrl->buffer = (BYTE *)malloc(ctrl->bufsize); + } + + // Set transfer amount + ctrl->length = ctrl->cmd[6]; + ctrl->length <<= 8; + ctrl->length |= ctrl->cmd[7]; + ctrl->length <<= 8; + ctrl->length |= ctrl->cmd[8]; + + if (ctrl->length <= 0) { + // Failure (Error) + controller->Error(); + return; + } + + // Set next block + ctrl->blocks = 1; + ctrl->next = 1; + + controller->DataOut(); +} + +int SCSIBR::GetMacAddr(BYTE *mac) +{ + memcpy(mac, mac_addr, 6); + return 6; +} + +void SCSIBR::SetMacAddr(BYTE *mac) +{ + memcpy(mac_addr, mac, 6); +} + +void SCSIBR::ReceivePacket() +{ + static const BYTE bcast_addr[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + + assert(tap); + + // previous packet has not been received + if (packet_enable) { + return; + } + + // Receive packet + packet_len = tap->Rx(packet_buf); + + // Check if received packet + if (memcmp(packet_buf, mac_addr, 6) != 0) { + if (memcmp(packet_buf, bcast_addr, 6) != 0) { + packet_len = 0; + return; + } + } + + // Discard if it exceeds the buffer size + if (packet_len > 2048) { + packet_len = 0; + return; + } + + // Store in receive buffer + if (packet_len > 0) { + packet_enable = true; + } +} + +void SCSIBR::GetPacketBuf(BYTE *buf) +{ + assert(tap); + + // Size limit + int len = packet_len; + if (len > 2048) { + len = 2048; + } + + // Copy + memcpy(buf, packet_buf, len); + + // Received + packet_enable = false; +} + +void SCSIBR::SendPacket(BYTE *buf, int len) +{ + assert(tap); + + tap->Tx(buf, len); +} + +//--------------------------------------------------------------------------- +// +// $40 - Device Boot +// +//--------------------------------------------------------------------------- +void SCSIBR::FS_InitDevice(BYTE *buf) +{ + fs->Reset(); + fsresult = fs->InitDevice((Human68k::argument_t*)buf); +} + +//--------------------------------------------------------------------------- +// +// $41 - Directory Check +// +//--------------------------------------------------------------------------- +void SCSIBR::FS_CheckDir(BYTE *buf) +{ + DWORD *dp = (DWORD*)buf; + DWORD nUnit = ntohl(*dp); + int i = sizeof(DWORD); + + Human68k::namests_t *pNamests = (Human68k::namests_t*)&buf[i]; + i += sizeof(Human68k::namests_t); + + fsresult = fs->CheckDir(nUnit, pNamests); +} + +//--------------------------------------------------------------------------- +// +// $42 - Create Directory +// +//--------------------------------------------------------------------------- +void SCSIBR::FS_MakeDir(BYTE *buf) +{ + DWORD *dp = (DWORD*)buf; + DWORD nUnit = ntohl(*dp); + int i = sizeof(DWORD); + + Human68k::namests_t *pNamests = (Human68k::namests_t*)&buf[i]; + i += sizeof(Human68k::namests_t); + + fsresult = fs->MakeDir(nUnit, pNamests); +} + +//--------------------------------------------------------------------------- +// +// $43 - Remove Directory +// +//--------------------------------------------------------------------------- +void SCSIBR::FS_RemoveDir(BYTE *buf) +{ + DWORD *dp = (DWORD*)buf; + DWORD nUnit = ntohl(*dp); + int i = sizeof(DWORD); + + Human68k::namests_t *pNamests = (Human68k::namests_t*)&buf[i]; + i += sizeof(Human68k::namests_t); + + fsresult = fs->RemoveDir(nUnit, pNamests); +} + +//--------------------------------------------------------------------------- +// +// $44 - Rename +// +//--------------------------------------------------------------------------- +void SCSIBR::FS_Rename(BYTE *buf) +{ + DWORD *dp = (DWORD*)buf; + DWORD nUnit = ntohl(*dp); + int i = sizeof(DWORD); + + Human68k::namests_t *pNamests = (Human68k::namests_t*)&buf[i]; + i += sizeof(Human68k::namests_t); + + Human68k::namests_t *pNamestsNew = (Human68k::namests_t*)&buf[i]; + i += sizeof(Human68k::namests_t); + + fsresult = fs->Rename(nUnit, pNamests, pNamestsNew); +} + +//--------------------------------------------------------------------------- +// +// $45 - Delete File +// +//--------------------------------------------------------------------------- +void SCSIBR::FS_Delete(BYTE *buf) +{ + DWORD *dp = (DWORD*)buf; + DWORD nUnit = ntohl(*dp); + int i = sizeof(DWORD); + + Human68k::namests_t *pNamests = (Human68k::namests_t*)&buf[i]; + i += sizeof(Human68k::namests_t); + + fsresult = fs->Delete(nUnit, pNamests); +} + +//--------------------------------------------------------------------------- +// +// $46 - Get / Set file attributes +// +//--------------------------------------------------------------------------- +void SCSIBR::FS_Attribute(BYTE *buf) +{ + DWORD *dp = (DWORD*)buf; + DWORD nUnit = ntohl(*dp); + int i = sizeof(DWORD); + + Human68k::namests_t *pNamests = (Human68k::namests_t*)&buf[i]; + i += sizeof(Human68k::namests_t); + + dp = (DWORD*)&buf[i]; + DWORD nHumanAttribute = ntohl(*dp); + i += sizeof(DWORD); + + fsresult = fs->Attribute(nUnit, pNamests, nHumanAttribute); +} + +//--------------------------------------------------------------------------- +// +// $47 - File Search +// +//--------------------------------------------------------------------------- +void SCSIBR::FS_Files(BYTE *buf) +{ + DWORD *dp = (DWORD*)buf; + DWORD nUnit = ntohl(*dp); + int i = sizeof(DWORD); + + dp = (DWORD*)&buf[i]; + DWORD nKey = ntohl(*dp); + i += sizeof(DWORD); + + Human68k::namests_t *pNamests = (Human68k::namests_t*)&buf[i]; + i += sizeof(Human68k::namests_t); + + Human68k::files_t *files = (Human68k::files_t*)&buf[i]; + i += sizeof(Human68k::files_t); + + files->sector = ntohl(files->sector); + files->offset = ntohs(files->offset); + files->time = ntohs(files->time); + files->date = ntohs(files->date); + files->size = ntohl(files->size); + + fsresult = fs->Files(nUnit, nKey, pNamests, files); + + files->sector = htonl(files->sector); + files->offset = htons(files->offset); + files->time = htons(files->time); + files->date = htons(files->date); + files->size = htonl(files->size); + + i = 0; + memcpy(&fsout[i], files, sizeof(Human68k::files_t)); + i += sizeof(Human68k::files_t); + + fsoutlen = i; +} + +//--------------------------------------------------------------------------- +// +// $48 - File next search +// +//--------------------------------------------------------------------------- +void SCSIBR::FS_NFiles(BYTE *buf) +{ + DWORD *dp = (DWORD*)buf; + DWORD nUnit = ntohl(*dp); + int i = sizeof(DWORD); + + dp = (DWORD*)&buf[i]; + DWORD nKey = ntohl(*dp); + i += sizeof(DWORD); + + Human68k::files_t *files = (Human68k::files_t*)&buf[i]; + i += sizeof(Human68k::files_t); + + files->sector = ntohl(files->sector); + files->offset = ntohs(files->offset); + files->time = ntohs(files->time); + files->date = ntohs(files->date); + files->size = ntohl(files->size); + + fsresult = fs->NFiles(nUnit, nKey, files); + + files->sector = htonl(files->sector); + files->offset = htons(files->offset); + files->time = htons(files->time); + files->date = htons(files->date); + files->size = htonl(files->size); + + i = 0; + memcpy(&fsout[i], files, sizeof(Human68k::files_t)); + i += sizeof(Human68k::files_t); + + fsoutlen = i; +} + +//--------------------------------------------------------------------------- +// +// $49 - File Creation +// +//--------------------------------------------------------------------------- +void SCSIBR::FS_Create(BYTE *buf) +{ + DWORD *dp = (DWORD*)buf; + DWORD nUnit = ntohl(*dp); + int i = sizeof(DWORD); + + dp = (DWORD*)&buf[i]; + DWORD nKey = ntohl(*dp); + i += sizeof(DWORD); + + Human68k::namests_t *pNamests = (Human68k::namests_t*)&buf[i]; + i += sizeof(Human68k::namests_t); + + Human68k::fcb_t *pFcb = (Human68k::fcb_t*)&buf[i]; + i += sizeof(Human68k::fcb_t); + + dp = (DWORD*)&buf[i]; + DWORD nAttribute = ntohl(*dp); + i += sizeof(DWORD); + + BOOL *bp = (BOOL*)&buf[i]; + DWORD bForce = ntohl(*bp); + i += sizeof(BOOL); + + pFcb->fileptr = ntohl(pFcb->fileptr); + pFcb->mode = ntohs(pFcb->mode); + pFcb->time = ntohs(pFcb->time); + pFcb->date = ntohs(pFcb->date); + pFcb->size = ntohl(pFcb->size); + + fsresult = fs->Create(nUnit, nKey, pNamests, pFcb, nAttribute, bForce); + + pFcb->fileptr = htonl(pFcb->fileptr); + pFcb->mode = htons(pFcb->mode); + pFcb->time = htons(pFcb->time); + pFcb->date = htons(pFcb->date); + pFcb->size = htonl(pFcb->size); + + i = 0; + memcpy(&fsout[i], pFcb, sizeof(Human68k::fcb_t)); + i += sizeof(Human68k::fcb_t); + + fsoutlen = i; +} + +//--------------------------------------------------------------------------- +// +// $4A - Open File +// +//--------------------------------------------------------------------------- +void SCSIBR::FS_Open(BYTE *buf) +{ + DWORD *dp = (DWORD*)buf; + DWORD nUnit = ntohl(*dp); + int i = sizeof(DWORD); + + dp = (DWORD*)&buf[i]; + DWORD nKey = ntohl(*dp); + i += sizeof(DWORD); + + Human68k::namests_t *pNamests = (Human68k::namests_t*)&buf[i]; + i += sizeof(Human68k::namests_t); + + Human68k::fcb_t *pFcb = (Human68k::fcb_t*)&buf[i]; + i += sizeof(Human68k::fcb_t); + + pFcb->fileptr = ntohl(pFcb->fileptr); + pFcb->mode = ntohs(pFcb->mode); + pFcb->time = ntohs(pFcb->time); + pFcb->date = ntohs(pFcb->date); + pFcb->size = ntohl(pFcb->size); + + fsresult = fs->Open(nUnit, nKey, pNamests, pFcb); + + pFcb->fileptr = htonl(pFcb->fileptr); + pFcb->mode = htons(pFcb->mode); + pFcb->time = htons(pFcb->time); + pFcb->date = htons(pFcb->date); + pFcb->size = htonl(pFcb->size); + + i = 0; + memcpy(&fsout[i], pFcb, sizeof(Human68k::fcb_t)); + i += sizeof(Human68k::fcb_t); + + fsoutlen = i; +} + +//--------------------------------------------------------------------------- +// +// $4B - Close File +// +//--------------------------------------------------------------------------- +void SCSIBR::FS_Close(BYTE *buf) +{ + DWORD *dp = (DWORD*)buf; + DWORD nUnit = ntohl(*dp); + int i = sizeof(DWORD); + + dp = (DWORD*)&buf[i]; + DWORD nKey = ntohl(*dp); + i += sizeof(DWORD); + + Human68k::fcb_t *pFcb = (Human68k::fcb_t*)&buf[i]; + i += sizeof(Human68k::fcb_t); + + pFcb->fileptr = ntohl(pFcb->fileptr); + pFcb->mode = ntohs(pFcb->mode); + pFcb->time = ntohs(pFcb->time); + pFcb->date = ntohs(pFcb->date); + pFcb->size = ntohl(pFcb->size); + + fsresult = fs->Close(nUnit, nKey, pFcb); + + pFcb->fileptr = htonl(pFcb->fileptr); + pFcb->mode = htons(pFcb->mode); + pFcb->time = htons(pFcb->time); + pFcb->date = htons(pFcb->date); + pFcb->size = htonl(pFcb->size); + + i = 0; + memcpy(&fsout[i], pFcb, sizeof(Human68k::fcb_t)); + i += sizeof(Human68k::fcb_t); + + fsoutlen = i; +} + +//--------------------------------------------------------------------------- +// +// $4C - Read File +// +//--------------------------------------------------------------------------- +void SCSIBR::FS_Read(BYTE *buf) +{ + DWORD *dp = (DWORD*)buf; + DWORD nKey = ntohl(*dp); + int i = sizeof(DWORD); + + Human68k::fcb_t *pFcb = (Human68k::fcb_t*)&buf[i]; + i += sizeof(Human68k::fcb_t); + + dp = (DWORD*)&buf[i]; + DWORD nSize = ntohl(*dp); + i += sizeof(DWORD); + + pFcb->fileptr = ntohl(pFcb->fileptr); + pFcb->mode = ntohs(pFcb->mode); + pFcb->time = ntohs(pFcb->time); + pFcb->date = ntohs(pFcb->date); + pFcb->size = ntohl(pFcb->size); + + fsresult = fs->Read(nKey, pFcb, fsopt, nSize); + + pFcb->fileptr = htonl(pFcb->fileptr); + pFcb->mode = htons(pFcb->mode); + pFcb->time = htons(pFcb->time); + pFcb->date = htons(pFcb->date); + pFcb->size = htonl(pFcb->size); + + i = 0; + memcpy(&fsout[i], pFcb, sizeof(Human68k::fcb_t)); + i += sizeof(Human68k::fcb_t); + + fsoutlen = i; + + fsoptlen = fsresult; +} + +//--------------------------------------------------------------------------- +// +// $4D - Write file +// +//--------------------------------------------------------------------------- +void SCSIBR::FS_Write(BYTE *buf) +{ + DWORD *dp = (DWORD*)buf; + DWORD nKey = ntohl(*dp); + int i = sizeof(DWORD); + + Human68k::fcb_t *pFcb = (Human68k::fcb_t*)&buf[i]; + i += sizeof(Human68k::fcb_t); + + dp = (DWORD*)&buf[i]; + DWORD nSize = ntohl(*dp); + i += sizeof(DWORD); + + pFcb->fileptr = ntohl(pFcb->fileptr); + pFcb->mode = ntohs(pFcb->mode); + pFcb->time = ntohs(pFcb->time); + pFcb->date = ntohs(pFcb->date); + pFcb->size = ntohl(pFcb->size); + + fsresult = fs->Write(nKey, pFcb, fsopt, nSize); + + pFcb->fileptr = htonl(pFcb->fileptr); + pFcb->mode = htons(pFcb->mode); + pFcb->time = htons(pFcb->time); + pFcb->date = htons(pFcb->date); + pFcb->size = htonl(pFcb->size); + + i = 0; + memcpy(&fsout[i], pFcb, sizeof(Human68k::fcb_t)); + i += sizeof(Human68k::fcb_t); + + fsoutlen = i; +} + +//--------------------------------------------------------------------------- +// +// $4E - Seek file +// +//--------------------------------------------------------------------------- +void SCSIBR::FS_Seek(BYTE *buf) +{ + DWORD *dp = (DWORD*)buf; + DWORD nKey = ntohl(*dp); + int i = sizeof(DWORD); + + Human68k::fcb_t *pFcb = (Human68k::fcb_t*)&buf[i]; + i += sizeof(Human68k::fcb_t); + + dp = (DWORD*)&buf[i]; + DWORD nMode = ntohl(*dp); + i += sizeof(DWORD); + + int *ip = (int*)&buf[i]; + int nOffset = ntohl(*ip); + i += sizeof(int); + + pFcb->fileptr = ntohl(pFcb->fileptr); + pFcb->mode = ntohs(pFcb->mode); + pFcb->time = ntohs(pFcb->time); + pFcb->date = ntohs(pFcb->date); + pFcb->size = ntohl(pFcb->size); + + fsresult = fs->Seek(nKey, pFcb, nMode, nOffset); + + pFcb->fileptr = htonl(pFcb->fileptr); + pFcb->mode = htons(pFcb->mode); + pFcb->time = htons(pFcb->time); + pFcb->date = htons(pFcb->date); + pFcb->size = htonl(pFcb->size); + + i = 0; + memcpy(&fsout[i], pFcb, sizeof(Human68k::fcb_t)); + i += sizeof(Human68k::fcb_t); + + fsoutlen = i; +} + +//--------------------------------------------------------------------------- +// +// $4F - File Timestamp Get / Set +// +//--------------------------------------------------------------------------- +void SCSIBR::FS_TimeStamp(BYTE *buf) +{ + DWORD *dp = (DWORD*)buf; + DWORD nUnit = ntohl(*dp); + int i = sizeof(DWORD); + + dp = (DWORD*)&buf[i]; + DWORD nKey = ntohl(*dp); + i += sizeof(DWORD); + + Human68k::fcb_t *pFcb = (Human68k::fcb_t*)&buf[i]; + i += sizeof(Human68k::fcb_t); + + dp = (DWORD*)&buf[i]; + DWORD nHumanTime = ntohl(*dp); + i += sizeof(DWORD); + + pFcb->fileptr = ntohl(pFcb->fileptr); + pFcb->mode = ntohs(pFcb->mode); + pFcb->time = ntohs(pFcb->time); + pFcb->date = ntohs(pFcb->date); + pFcb->size = ntohl(pFcb->size); + + fsresult = fs->TimeStamp(nUnit, nKey, pFcb, nHumanTime); + + pFcb->fileptr = htonl(pFcb->fileptr); + pFcb->mode = htons(pFcb->mode); + pFcb->time = htons(pFcb->time); + pFcb->date = htons(pFcb->date); + pFcb->size = htonl(pFcb->size); + + i = 0; + memcpy(&fsout[i], pFcb, sizeof(Human68k::fcb_t)); + i += sizeof(Human68k::fcb_t); + + fsoutlen = i; +} + +//--------------------------------------------------------------------------- +// +// $50 - Get Capacity +// +//--------------------------------------------------------------------------- +void SCSIBR::FS_GetCapacity(BYTE *buf) +{ + DWORD *dp = (DWORD*)buf; + DWORD nUnit = ntohl(*dp); + + Human68k::capacity_t cap; + fsresult = fs->GetCapacity(nUnit, &cap); + + cap.freearea = htons(cap.freearea); + cap.clusters = htons(cap.clusters); + cap.sectors = htons(cap.sectors); + cap.bytes = htons(cap.bytes); + + memcpy(fsout, &cap, sizeof(Human68k::capacity_t)); + fsoutlen = sizeof(Human68k::capacity_t); +} + +//--------------------------------------------------------------------------- +// +// $51 - Drive status inspection/control +// +//--------------------------------------------------------------------------- +void SCSIBR::FS_CtrlDrive(BYTE *buf) +{ + DWORD *dp = (DWORD*)buf; + DWORD nUnit = ntohl(*dp); + int i = sizeof(DWORD); + + Human68k::ctrldrive_t *pCtrlDrive = (Human68k::ctrldrive_t*)&buf[i]; + + fsresult = fs->CtrlDrive(nUnit, pCtrlDrive); + + memcpy(fsout, pCtrlDrive, sizeof(Human68k::ctrldrive_t)); + fsoutlen = sizeof(Human68k::ctrldrive_t); +} + +//--------------------------------------------------------------------------- +// +// $52 - Get DPB +// +//--------------------------------------------------------------------------- +void SCSIBR::FS_GetDPB(BYTE *buf) +{ + DWORD *dp = (DWORD*)buf; + DWORD nUnit = ntohl(*dp); + + Human68k::dpb_t dpb; + fsresult = fs->GetDPB(nUnit, &dpb); + + dpb.sector_size = htons(dpb.sector_size); + dpb.fat_sector = htons(dpb.fat_sector); + dpb.file_max = htons(dpb.file_max); + dpb.data_sector = htons(dpb.data_sector); + dpb.cluster_max = htons(dpb.cluster_max); + dpb.root_sector = htons(dpb.root_sector); + + memcpy(fsout, &dpb, sizeof(Human68k::dpb_t)); + fsoutlen = sizeof(Human68k::dpb_t); +} + +//--------------------------------------------------------------------------- +// +// $53 - Read Sector +// +//--------------------------------------------------------------------------- +void SCSIBR::FS_DiskRead(BYTE *buf) +{ + DWORD *dp = (DWORD*)buf; + DWORD nUnit = ntohl(*dp); + int i = sizeof(DWORD); + + dp = (DWORD*)&buf[i]; + DWORD nSector = ntohl(*dp); + i += sizeof(DWORD); + + dp = (DWORD*)&buf[i]; + DWORD nSize = ntohl(*dp); + i += sizeof(DWORD); + + fsresult = fs->DiskRead(nUnit, fsout, nSector, nSize); + fsoutlen = 0x200; +} + +//--------------------------------------------------------------------------- +// +// $54 - Write Sector +// +//--------------------------------------------------------------------------- +void SCSIBR::FS_DiskWrite(BYTE *buf) +{ + DWORD *dp = (DWORD*)buf; + DWORD nUnit = ntohl(*dp); + + fsresult = fs->DiskWrite(nUnit); +} + +//--------------------------------------------------------------------------- +// +// $55 - IOCTRL +// +//--------------------------------------------------------------------------- +void SCSIBR::FS_Ioctrl(BYTE *buf) +{ + DWORD *dp = (DWORD*)buf; + DWORD nUnit = ntohl(*dp); + int i = sizeof(DWORD); + + dp = (DWORD*)&buf[i]; + DWORD nFunction = ntohl(*dp); + i += sizeof(DWORD); + + Human68k::ioctrl_t *pIoctrl = (Human68k::ioctrl_t*)&buf[i]; + i += sizeof(Human68k::ioctrl_t); + + switch (nFunction) { + case 2: + case (DWORD)-2: + pIoctrl->param = htonl(pIoctrl->param); + break; + } + + fsresult = fs->Ioctrl(nUnit, nFunction, pIoctrl); + + switch (nFunction) { + case 0: + pIoctrl->media = htons(pIoctrl->media); + break; + case 1: + case (DWORD)-3: + pIoctrl->param = htonl(pIoctrl->param); + break; + } + + i = 0; + memcpy(&fsout[i], pIoctrl, sizeof(Human68k::ioctrl_t)); + i += sizeof(Human68k::ioctrl_t); + fsoutlen = i; +} + +//--------------------------------------------------------------------------- +// +// $56 - Flush +// +//--------------------------------------------------------------------------- +void SCSIBR::FS_Flush(BYTE *buf) +{ + DWORD *dp = (DWORD*)buf; + DWORD nUnit = ntohl(*dp); + + fsresult = fs->Flush(nUnit); +} + +//--------------------------------------------------------------------------- +// +// $57 - Check Media +// +//--------------------------------------------------------------------------- +void SCSIBR::FS_CheckMedia(BYTE *buf) +{ + DWORD *dp = (DWORD*)buf; + DWORD nUnit = ntohl(*dp); + + fsresult = fs->CheckMedia(nUnit); +} + +//--------------------------------------------------------------------------- +// +// $58 - Lock +// +//--------------------------------------------------------------------------- +void SCSIBR::FS_Lock(BYTE *buf) +{ + DWORD *dp = (DWORD*)buf; + DWORD nUnit = ntohl(*dp); + + fsresult = fs->Lock(nUnit); +} + +//--------------------------------------------------------------------------- +// +// Read Filesystem (result code) +// +//--------------------------------------------------------------------------- +int SCSIBR::ReadFsResult(BYTE *buf) +{ + DWORD *dp = (DWORD*)buf; + *dp = htonl(fsresult); + return sizeof(DWORD); +} + +//--------------------------------------------------------------------------- +// +// Read Filesystem (return data) +// +//--------------------------------------------------------------------------- +int SCSIBR::ReadFsOut(BYTE *buf) +{ + memcpy(buf, fsout, fsoutlen); + return fsoutlen; +} + +//--------------------------------------------------------------------------- +// +// Read file system (return option data) +// +//--------------------------------------------------------------------------- +int SCSIBR::ReadFsOpt(BYTE *buf) +{ + memcpy(buf, fsopt, fsoptlen); + return fsoptlen; +} + +//--------------------------------------------------------------------------- +// +// Write Filesystem +// +//--------------------------------------------------------------------------- +void SCSIBR::WriteFs(int func, BYTE *buf) +{ + fsresult = FS_FATAL_INVALIDCOMMAND; + fsoutlen = 0; + fsoptlen = 0; + + func &= 0x1f; + switch (func) { + case 0x00: return FS_InitDevice(buf); // $40 - start device + case 0x01: return FS_CheckDir(buf); // $41 - directory check + case 0x02: return FS_MakeDir(buf); // $42 - create directory + case 0x03: return FS_RemoveDir(buf); // $43 - remove directory + case 0x04: return FS_Rename(buf); // $44 - change file name + case 0x05: return FS_Delete(buf); // $45 - delete file + case 0x06: return FS_Attribute(buf); // $46 - Get/set file attribute + case 0x07: return FS_Files(buf); // $47 - file search + case 0x08: return FS_NFiles(buf); // $48 - next file search + case 0x09: return FS_Create(buf); // $49 - create file + case 0x0A: return FS_Open(buf); // $4A - File open + case 0x0B: return FS_Close(buf); // $4B - File close + case 0x0C: return FS_Read(buf); // $4C - read file + case 0x0D: return FS_Write(buf); // $4D - write file + case 0x0E: return FS_Seek(buf); // $4E - File seek + case 0x0F: return FS_TimeStamp(buf); // $4F - Get/set file modification time + case 0x10: return FS_GetCapacity(buf); // $50 - get capacity + case 0x11: return FS_CtrlDrive(buf); // $51 - Drive control/state check + case 0x12: return FS_GetDPB(buf); // $52 - Get DPB + case 0x13: return FS_DiskRead(buf); // $53 - read sector + case 0x14: return FS_DiskWrite(buf); // $54 - write sector + case 0x15: return FS_Ioctrl(buf); // $55 - IOCTRL + case 0x16: return FS_Flush(buf); // $56 - flush + case 0x17: return FS_CheckMedia(buf); // $57 - check media exchange + case 0x18: return FS_Lock(buf); // $58 - exclusive control + } +} + +//--------------------------------------------------------------------------- +// +// File system write (input option data) +// +//--------------------------------------------------------------------------- +void SCSIBR::WriteFsOpt(BYTE *buf, int num) +{ + memcpy(fsopt, buf, num); +} diff --git a/src_old/raspberrypi/devices/scsi_host_bridge.h b/src_old/raspberrypi/devices/scsi_host_bridge.h new file mode 100644 index 00000000..e7e304e7 --- /dev/null +++ b/src_old/raspberrypi/devices/scsi_host_bridge.h @@ -0,0 +1,107 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// Copyright (C) akuker +// +// Licensed under the BSD 3-Clause License. +// See LICENSE file in the project root folder. +// +// [ SCSI Host Bridge for the Sharp X68000 ] +// +// Note: This requires a special driver on the host system and will only +// work with the Sharp X68000 operating system. +//--------------------------------------------------------------------------- +#pragma once + +#include "os.h" +#include "disk.h" +#include + +//=========================================================================== +// +// SCSI Host Bridge +// +//=========================================================================== +class CTapDriver; +class CFileSys; + +class SCSIBR : public Disk +{ + +public: + SCSIBR(); + ~SCSIBR(); + + bool Init(const map&) override; + bool Dispatch(SCSIDEV *) override; + + // Commands + int Inquiry(const DWORD *cdb, BYTE *buf) override; // INQUIRY command + int GetMessage10(const DWORD *cdb, BYTE *buf); // GET MESSAGE10 command + bool SendMessage10(const DWORD *cdb, BYTE *buf); // SEND MESSAGE10 command + void TestUnitReady(SASIDEV *) override; + void GetMessage10(SASIDEV *); + void SendMessage10(SASIDEV *); + +private: + typedef Disk super; + + Dispatcher dispatcher; + + int GetMacAddr(BYTE *buf); // Get MAC address + void SetMacAddr(BYTE *buf); // Set MAC address + void ReceivePacket(); // Receive a packet + void GetPacketBuf(BYTE *buf); // Get a packet + void SendPacket(BYTE *buf, int len); // Send a packet + + CTapDriver *tap; // TAP driver + bool m_bTapEnable; // TAP valid flag + BYTE mac_addr[6]; // MAC Addres + int packet_len; // Receive packet size + BYTE packet_buf[0x1000]; // Receive packet buffer + bool packet_enable; // Received packet valid + + int ReadFsResult(BYTE *buf); // Read filesystem (result code) + int ReadFsOut(BYTE *buf); // Read filesystem (return data) + int ReadFsOpt(BYTE *buf); // Read file system (optional data) + void WriteFs(int func, BYTE *buf); // File system write (execute) + void WriteFsOpt(BYTE *buf, int len); // File system write (optional data) + + // Command handlers + void FS_InitDevice(BYTE *buf); // $40 - boot + void FS_CheckDir(BYTE *buf); // $41 - directory check + void FS_MakeDir(BYTE *buf); // $42 - create directory + void FS_RemoveDir(BYTE *buf); // $43 - delete directory + void FS_Rename(BYTE *buf); // $44 - change filename + void FS_Delete(BYTE *buf); // $45 - delete file + void FS_Attribute(BYTE *buf); // $46 - get/set file attributes + void FS_Files(BYTE *buf); // $47 - file search + void FS_NFiles(BYTE *buf); // $48 - find next file + void FS_Create(BYTE *buf); // $49 - create file + void FS_Open(BYTE *buf); // $4A - open file + void FS_Close(BYTE *buf); // $4B - close file + void FS_Read(BYTE *buf); // $4C - read file + void FS_Write(BYTE *buf); // $4D - write file + void FS_Seek(BYTE *buf); // $4E - seek file + void FS_TimeStamp(BYTE *buf); // $4F - get/set file time + void FS_GetCapacity(BYTE *buf); // $50 - get capacity + void FS_CtrlDrive(BYTE *buf); // $51 - drive status check/control + void FS_GetDPB(BYTE *buf); // $52 - get DPB + void FS_DiskRead(BYTE *buf); // $53 - read sector + void FS_DiskWrite(BYTE *buf); // $54 - write sector + void FS_Ioctrl(BYTE *buf); // $55 - IOCTRL + void FS_Flush(BYTE *buf); // $56 - flush cache + void FS_CheckMedia(BYTE *buf); // $57 - check media + void FS_Lock(BYTE *buf); // $58 - get exclusive control + + CFileSys *fs; // File system accessor + DWORD fsresult; // File system access result code + BYTE fsout[0x800]; // File system access result buffer + DWORD fsoutlen; // File system access result buffer size + BYTE fsopt[0x1000000]; // File system access buffer + DWORD fsoptlen; // File system access buffer size +}; diff --git a/src_old/raspberrypi/devices/scsi_printer.cpp b/src_old/raspberrypi/devices/scsi_printer.cpp new file mode 100644 index 00000000..e340ac6d --- /dev/null +++ b/src_old/raspberrypi/devices/scsi_printer.cpp @@ -0,0 +1,303 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2022 Uwe Seimet +// +// Implementation of a SCSI printer (see SCSI-2 specification for a command description) +// +//--------------------------------------------------------------------------- + +// +// How to print: +// +// 1. The client reserves the printer device with RESERVE UNIT (optional step, mandatory for +// a multi-initiator environment). +// 2. The client sends the data to be printed with one or several PRINT commands. Due to +// https://github.com/akuker/RASCSI/issues/669 the maximum transfer size per PRINT command is +// limited to 4096 bytes. +// 3. The client triggers printing with SYNCHRONIZE BUFFER. Each SYNCHRONIZE BUFFER results in +// the print command for this printer (see below) to be called for the data not yet printed. +// 4. The client releases the printer with RELEASE UNIT (optional step, mandatory for a +// multi-initiator environment). +// +// A client usually does not know whether it is running in a multi-initiator environment. This is why +// always using a reservation is recommended. +// +// The command to be used for printing can be set with the "cmd" property when attaching the device. +// By default the data to be printed are sent to the printer unmodified, using "lp -oraw %f". This +// requires that the client uses a printer driver compatible with the respective printer, or that the +// printing service on the Pi is configured to do any necessary conversions, or that the print command +// applies any conversions on the file to be printed (%f) before passing it to the printing service. +// 'enscript' is an example for a conversion tool. +// By attaching different devices/LUNs multiple printers (i.e. different print commands) are possible. +// Note that the print command is not executed by root but with the permissions of the lp user. +// +// With STOP PRINT printing can be cancelled before SYNCHRONIZE BUFFER was sent. +// +// SEND DIAGNOSTIC currently returns no data. +// + +#include +#include "controllers/scsidev_ctrl.h" +#include "../rasutil.h" +#include "scsi_printer.h" +#include + +#define NOT_RESERVED -2 + +using namespace std; +using namespace scsi_defs; +using namespace ras_util; + +SCSIPrinter::SCSIPrinter() : PrimaryDevice("SCLP"), ScsiPrinterCommands() +{ + fd = -1; + reserving_initiator = NOT_RESERVED; + reservation_time = 0; + timeout = 0; + + dispatcher.AddCommand(eCmdTestUnitReady, "TestUnitReady", &SCSIPrinter::TestUnitReady); + dispatcher.AddCommand(eCmdReserve6, "ReserveUnit", &SCSIPrinter::ReserveUnit); + dispatcher.AddCommand(eCmdRelease6, "ReleaseUnit", &SCSIPrinter::ReleaseUnit); + dispatcher.AddCommand(eCmdWrite6, "Print", &SCSIPrinter::Print); + dispatcher.AddCommand(eCmdSynchronizeBuffer, "SynchronizeBuffer", &SCSIPrinter::SynchronizeBuffer); + dispatcher.AddCommand(eCmdSendDiag, "SendDiagnostic", &SCSIPrinter::SendDiagnostic); + dispatcher.AddCommand(eCmdStartStop, "StopPrint", &SCSIPrinter::StopPrint); +} + +SCSIPrinter::~SCSIPrinter() +{ + Cleanup(); +} + +bool SCSIPrinter::Init(const map& params) +{ + SetParams(params); + + if (GetParam("cmd").find("%f") == string::npos) { + LOGERROR("Missing filename specifier %s", "%f"); + return false; + } + + if (!GetAsInt(GetParam("timeout"), timeout) || timeout <= 0) { + LOGERROR("Reservation timeout value must be > 0"); + return false; + } + + return true; +} + +bool SCSIPrinter::Dispatch(SCSIDEV *controller) +{ + // The superclass class handles the less specific commands + return dispatcher.Dispatch(this, controller) ? true : super::Dispatch(controller); +} + +void SCSIPrinter::TestUnitReady(SCSIDEV *controller) +{ + if (!CheckReservation(controller)) { + return; + } + + controller->Status(); +} + +int SCSIPrinter::Inquiry(const DWORD *cdb, BYTE *buf) +{ + // Printer device, SCSI-2, not removable + return PrimaryDevice::Inquiry(2, 2, false, cdb, buf); +} + +void SCSIPrinter::ReserveUnit(SCSIDEV *controller) +{ + // The printer is released after a configurable time in order to prevent deadlocks caused by broken clients + if (reservation_time + timeout < time(0)) { + DiscardReservation(); + } + + if (!CheckReservation(controller)) { + return; + } + + reserving_initiator = controller->GetInitiatorId(); + + if (reserving_initiator != -1) { + LOGTRACE("Reserved device ID %d, LUN %d for initiator ID %d", GetId(), GetLun(), reserving_initiator); + } + else { + LOGTRACE("Reserved device ID %d, LUN %d for unknown initiator", GetId(), GetLun()); + } + + Cleanup(); + + controller->Status(); +} + +void SCSIPrinter::ReleaseUnit(SCSIDEV *controller) +{ + if (!CheckReservation(controller)) { + return; + } + + if (reserving_initiator != -1) { + LOGTRACE("Released device ID %d, LUN %d reserved by initiator ID %d", GetId(), GetLun(), reserving_initiator); + } + else { + LOGTRACE("Released device ID %d, LUN %d reserved by unknown initiator", GetId(), GetLun()); + } + + DiscardReservation(); + + controller->Status(); +} + +void SCSIPrinter::Print(SCSIDEV *controller) +{ + if (!CheckReservation(controller)) { + return; + } + + uint32_t length = ctrl->cmd[2]; + length <<= 8; + length |= ctrl->cmd[3]; + length <<= 8; + length |= ctrl->cmd[4]; + + LOGTRACE("Receiving %d bytes to be printed", length); + + // TODO This device suffers from the statically allocated buffer size, + // see https://github.com/akuker/RASCSI/issues/669 + if (length > (uint32_t)ctrl->bufsize) { + LOGERROR("Transfer buffer overflow"); + + controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::INVALID_FIELD_IN_CDB); + return; + } + + ctrl->length = length; + controller->SetByteTransfer(true); + + controller->DataOut(); +} + +void SCSIPrinter::SynchronizeBuffer(SCSIDEV *controller) +{ + if (!CheckReservation(controller)) { + return; + } + + if (fd == -1) { + controller->Error(); + return; + } + + // Make the file readable for the lp user + fchmod(fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + struct stat st; + fstat(fd, &st); + + close(fd); + fd = -1; + + string cmd = GetParam("cmd"); + size_t file_position = cmd.find("%f"); + assert(file_position != string::npos); + cmd.replace(file_position, 2, filename); + cmd = "sudo -u lp " + cmd; + + LOGTRACE("%s", string("Printing file with size of " + to_string(st.st_size) +" byte(s)").c_str()); + + LOGDEBUG("Executing '%s'", cmd.c_str()); + + if (system(cmd.c_str())) { + LOGERROR("Printing failed, the printing system might not be configured"); + + controller->Error(); + } + else { + controller->Status(); + } + + unlink(filename); +} + +void SCSIPrinter::SendDiagnostic(SCSIDEV *controller) +{ + if (!CheckReservation(controller)) { + return; + } + + controller->Status(); +} + +void SCSIPrinter::StopPrint(SCSIDEV *controller) +{ + if (!CheckReservation(controller)) { + return; + } + + // Nothing to do, printing has not yet been started + + controller->Status(); +} + +bool SCSIPrinter::WriteBytes(BYTE *buf, uint32_t length) +{ + if (fd == -1) { + strcpy(filename, TMP_FILE_PATTERN); + fd = mkstemp(filename); + if (fd == -1) { + LOGERROR("Can't create printer output file: %s", strerror(errno)); + return false; + } + + LOGTRACE("Created printer output file '%s'", filename); + } + + LOGTRACE("Appending %d byte(s) to printer output file", length); + + write(fd, buf, length); + + return true; +} + +bool SCSIPrinter::CheckReservation(SCSIDEV *controller) +{ + if (reserving_initiator == NOT_RESERVED || reserving_initiator == controller->GetInitiatorId()) { + reservation_time = time(0); + + return true; + } + + if (controller->GetInitiatorId() != -1) { + LOGTRACE("Initiator ID %d tries to access reserved device ID %d, LUN %d", controller->GetInitiatorId(), GetId(), GetLun()); + } + else { + LOGTRACE("Unknown initiator tries to access reserved device ID %d, LUN %d", GetId(), GetLun()); + } + + controller->Error(ERROR_CODES::sense_key::ABORTED_COMMAND, ERROR_CODES::asc::NO_ADDITIONAL_SENSE_INFORMATION, + ERROR_CODES::status::RESERVATION_CONFLICT); + + return false; +} + +void SCSIPrinter::DiscardReservation() +{ + Cleanup(); + + reserving_initiator = NOT_RESERVED; +} + +void SCSIPrinter::Cleanup() +{ + if (fd != -1) { + close(fd); + fd = -1; + + unlink(filename); + } +} diff --git a/src_old/raspberrypi/devices/scsi_printer.h b/src_old/raspberrypi/devices/scsi_printer.h new file mode 100644 index 00000000..1abc441f --- /dev/null +++ b/src_old/raspberrypi/devices/scsi_printer.h @@ -0,0 +1,61 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2022 Uwe Seimet +// +// Implementation of a SCSI printer (see SCSI-2 specification for a command description) +// +//--------------------------------------------------------------------------- +#pragma once + +#include "interfaces/scsi_printer_commands.h" +#include "primary_device.h" +#include +#include + +using namespace std; + +#define TMP_FILE_PATTERN "/tmp/rascsi_sclp-XXXXXX" + +class SCSIPrinter: public PrimaryDevice, ScsiPrinterCommands +{ + +public: + + SCSIPrinter(); + ~SCSIPrinter(); + + virtual bool Dispatch(SCSIDEV *) override; + + bool Init(const map&); + + int Inquiry(const DWORD *, BYTE *) override; + void TestUnitReady(SCSIDEV *); + void ReserveUnit(SCSIDEV *); + void ReleaseUnit(SCSIDEV *); + void Print(SCSIDEV *); + void SynchronizeBuffer(SCSIDEV *); + void SendDiagnostic(SCSIDEV *); + void StopPrint(SCSIDEV *); + + bool WriteBytes(BYTE *, uint32_t) override; + bool CheckReservation(SCSIDEV *); + void DiscardReservation(); + void Cleanup(); + +private: + + typedef PrimaryDevice super; + + Dispatcher dispatcher; + + char filename[sizeof(TMP_FILE_PATTERN) + 1]; + int fd; + + int reserving_initiator; + + time_t reservation_time; + int timeout; +}; diff --git a/src_old/raspberrypi/devices/scsicd.cpp b/src_old/raspberrypi/devices/scsicd.cpp new file mode 100644 index 00000000..58dbf996 --- /dev/null +++ b/src_old/raspberrypi/devices/scsicd.cpp @@ -0,0 +1,738 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// Copyright (C) akuker +// +// Licensed under the BSD 3-Clause License. +// See LICENSE file in the project root folder. +// +// [ SCSI CD-ROM ] +// +//--------------------------------------------------------------------------- + +#include "scsicd.h" +#include "fileio.h" +#include "exceptions.h" +#include "disk_image/disk_image_handle_factory.h" + +using namespace scsi_defs; + +//=========================================================================== +// +// CD Track +// +//=========================================================================== + +CDTrack::CDTrack(SCSICD *scsicd) +{ + ASSERT(scsicd); + + // Set parent CD-ROM device + cdrom = scsicd; + + // Track defaults to disabled + valid = false; + + // Initialize other data + track_no = -1; + first_lba = 0; + last_lba = 0; + audio = false; + raw = false; +} + +void CDTrack::Init(int track, DWORD first, DWORD last) +{ + ASSERT(!valid); + ASSERT(track >= 1); + ASSERT(first < last); + + // Set and enable track number + track_no = track; + valid = TRUE; + + // Remember LBA + first_lba = first; + last_lba = last; +} + +void CDTrack::SetPath(bool cdda, const Filepath& path) +{ + ASSERT(valid); + + // CD-DA or data + audio = cdda; + + // Remember the path + imgpath = path; +} + +void CDTrack::GetPath(Filepath& path) const +{ + ASSERT(valid); + + // Return the path (by reference) + path = imgpath; +} + +void CDTrack::AddIndex(int index, DWORD lba) +{ + ASSERT(valid); + ASSERT(index > 0); + ASSERT(first_lba <= lba); + ASSERT(lba <= last_lba); + + // Currently does not support indexes + ASSERT(FALSE); +} + +//--------------------------------------------------------------------------- +// +// Gets the start of LBA +// +//--------------------------------------------------------------------------- +DWORD CDTrack::GetFirst() const +{ + ASSERT(valid); + ASSERT(first_lba < last_lba); + + return first_lba; +} + +//--------------------------------------------------------------------------- +// +// Get the end of LBA +// +//--------------------------------------------------------------------------- +DWORD CDTrack::GetLast() const +{ + ASSERT(valid); + ASSERT(first_lba < last_lba); + + return last_lba; +} + +DWORD CDTrack::GetBlocks() const +{ + ASSERT(valid); + ASSERT(first_lba < last_lba); + + // Calculate from start LBA and end LBA + return (DWORD)(last_lba - first_lba + 1); +} + +int CDTrack::GetTrackNo() const +{ + ASSERT(valid); + ASSERT(track_no >= 1); + + return track_no; +} + +//--------------------------------------------------------------------------- +// +// Is valid block +// +//--------------------------------------------------------------------------- +bool CDTrack::IsValid(DWORD lba) const +{ + // FALSE if the track itself is invalid + if (!valid) { + return false; + } + + // If the block is BEFORE the first block + if (lba < first_lba) { + return false; + } + + // If the block is AFTER the last block + if (last_lba < lba) { + return false; + } + + // This track is valid + return true; +} + +//--------------------------------------------------------------------------- +// +// Is audio track +// +//--------------------------------------------------------------------------- +bool CDTrack::IsAudio() const +{ + ASSERT(valid); + + return audio; +} + +//=========================================================================== +// +// SCSI CD-ROM +// +//=========================================================================== + +SCSICD::SCSICD(const set& sector_sizes) : Disk("SCCD"), ScsiMmcCommands(), FileSupport() +{ + SetSectorSizes(sector_sizes); + + // NOT in raw format + rawfile = false; + + // Frame initialization + frame = 0; + + // Track initialization + for (int i = 0; i < TrackMax; i++) { + track[i] = NULL; + } + tracks = 0; + dataindex = -1; + audioindex = -1; + + dispatcher.AddCommand(eCmdReadToc, "ReadToc", &SCSICD::ReadToc); + dispatcher.AddCommand(eCmdGetEventStatusNotification, "GetEventStatusNotification", &SCSICD::GetEventStatusNotification); +} + +SCSICD::~SCSICD() +{ + // Clear track + ClearTrack(); +} + +bool SCSICD::Dispatch(SCSIDEV *controller) +{ + // The superclass class handles the less specific commands + return dispatcher.Dispatch(this, controller) ? true : super::Dispatch(controller); +} + +void SCSICD::Open(const Filepath& path) +{ + off_t size; + + ASSERT(!IsReady()); + + // Initialization, track clear + SetBlockCount(0); + rawfile = false; + ClearTrack(); + + // Open as read-only + Fileio fio; + if (!fio.Open(path, Fileio::ReadOnly)) { + throw file_not_found_exception("Can't open CD-ROM file"); + } + + // Default sector size is 2048 bytes + SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 2048, false); + + // Close and transfer for physical CD access + if (path.GetPath()[0] == _T('\\')) { + // Close + fio.Close(); + + // Open physical CD + OpenPhysical(path); + } else { + // Get file size + size = fio.GetFileSize(); + if (size <= 4) { + fio.Close(); + throw io_exception("CD-ROM file size must be at least 4 bytes"); + } + + // Judge whether it is a CUE sheet or an ISO file + TCHAR file[5]; + fio.Read(file, 4); + file[4] = '\0'; + fio.Close(); + + // If it starts with FILE, consider it as a CUE sheet + if (!strncasecmp(file, _T("FILE"), 4)) { + // Open as CUE + OpenCue(path); + } else { + // Open as ISO + OpenIso(path); + } + } + + // Successful opening + ASSERT(GetBlockCount() > 0); + + super::Open(path); + FileSupport::SetPath(path); + + // Set RAW flag + ASSERT(disk.dcache); + disk.dcache->SetRawMode(rawfile); + + // Attention if ready + if (IsReady()) { + SetAttn(true); + } +} + +void SCSICD::OpenCue(const Filepath& /*path*/) +{ + throw io_exception("Opening CUE CD-ROM files is not supported"); +} + +void SCSICD::OpenIso(const Filepath& path) +{ + // Open as read-only + Fileio fio; + if (!fio.Open(path, Fileio::ReadOnly)) { + throw io_exception("Can't open ISO CD-ROM file"); + } + + // Get file size + off_t size = fio.GetFileSize(); + if (size < 0x800) { + fio.Close(); + throw io_exception("ISO CD-ROM file size must be at least 2048 bytes"); + } + + // Read the first 12 bytes and close + BYTE header[12]; + if (!fio.Read(header, sizeof(header))) { + fio.Close(); + throw io_exception("Can't read header of ISO CD-ROM file"); + } + + // Check if it is RAW format + BYTE sync[12]; + memset(sync, 0xff, sizeof(sync)); + sync[0] = 0x00; + sync[11] = 0x00; + rawfile = false; + if (memcmp(header, sync, sizeof(sync)) == 0) { + // 00,FFx10,00, so it is presumed to be RAW format + if (!fio.Read(header, 4)) { + fio.Close(); + throw io_exception("Can't read header of raw ISO CD-ROM file"); + } + + // Supports MODE1/2048 or MODE1/2352 only + if (header[3] != 0x01) { + // Different mode + fio.Close(); + throw io_exception("Illegal raw ISO CD-ROM file header"); + } + + // Set to RAW file + rawfile = true; + } + fio.Close(); + + if (rawfile) { + // Size must be a multiple of 2536 + if (size % 2536) { + throw io_exception("Raw ISO CD-ROM file size must be a multiple of 2536 bytes but is " + + to_string(size) + " bytes"); + } + + // Set the number of blocks + SetBlockCount((DWORD)(size / 0x930)); + } else { + // Set the number of blocks + SetBlockCount((DWORD)(size >> GetSectorSizeShiftCount())); + } + + // Create only one data track + ASSERT(!track[0]); + track[0] = new CDTrack(this); + track[0]->Init(1, 0, GetBlockCount() - 1); + track[0]->SetPath(false, path); + tracks = 1; + dataindex = 0; +} + +void SCSICD::OpenPhysical(const Filepath& path) +{ + // Open as read-only + Fileio fio; + if (!fio.Open(path, Fileio::ReadOnly)) { + throw io_exception("Can't open CD-ROM file"); + } + + // Get size + off_t size = fio.GetFileSize(); + if (size < 0x800) { + fio.Close(); + throw io_exception("CD-ROM file size must be at least 2048 bytes"); + } + + // Close + fio.Close(); + + // Effective size must be a multiple of 512 + size = (size / 512) * 512; + + // Set the number of blocks + SetBlockCount((DWORD)(size >> GetSectorSizeShiftCount())); + + // Create only one data track + ASSERT(!track[0]); + track[0] = new CDTrack(this); + track[0]->Init(1, 0, GetBlockCount() - 1); + track[0]->SetPath(false, path); + tracks = 1; + dataindex = 0; +} + +void SCSICD::ReadToc(SASIDEV *controller) +{ + ctrl->length = ReadToc(ctrl->cmd, ctrl->buffer); + if (ctrl->length <= 0) { + // Failure (Error) + controller->Error(); + return; + } + + controller->DataIn(); +} + +int SCSICD::Inquiry(const DWORD *cdb, BYTE *buf) +{ + // EVPD check + if (cdb[1] & 0x01) { + SetStatusCode(STATUS_INVALIDCDB); + return 0; + } + + // Basic data + // buf[0] ... CD-ROM Device + // buf[1] ... Removable + // buf[2] ... SCSI-2 compliant command system + // buf[3] ... SCSI-2 compliant Inquiry response + // buf[4] ... Inquiry additional data + memset(buf, 0, 8); + buf[0] = 0x05; + buf[1] = 0x80; + buf[2] = 0x02; + buf[3] = 0x02; + buf[4] = 0x1F; + + // Fill with blanks + memset(&buf[8], 0x20, buf[4] - 3); + + // Padded vendor, product, revision + memcpy(&buf[8], GetPaddedName().c_str(), 28); + +// +// The following code worked with the modified Apple CD-ROM drivers. Need to +// test with the original code to see if it works as well.... +// buf[4] = 42; // Required +// +// // Fill with blanks +// memset(&buf[8], 0x20, buf[4] - 3); +// +// // Vendor name +// memcpy(&buf[8], BENDER_SIGNATURE, strlen(BENDER_SIGNATURE)); +// +// // Product name +// memcpy(&buf[16], "CD-ROM CDU-8003A", 16); +// +// // Revision (XM6 version number) +//// sprintf(rev, "1.9a", +// //// (int)major, (int)(minor >> 4), (int)(minor & 0x0f)); +// memcpy(&buf[32], "1.9a", 4); +// +// //strcpy(&buf[35],"A1.9a"); +// buf[36]=0x20; +// memcpy(&buf[37],"1999/01/01",10); + + // Size of data that can be returned + int size = (buf[4] + 5); + + // Limit if the other buffer is small + if (size > (int)cdb[4]) { + size = (int)cdb[4]; + } + + return size; +} + +void SCSICD::AddModePages(map>& pages, int page, bool changeable) const +{ + super::AddModePages(pages, page, changeable); + + // Page code 13 + if (page == 0x0d || page == 0x3f) { + AddCDROMPage(pages, changeable); + } + + // Page code 14 + if (page == 0x0e || page == 0x3f) { + AddCDDAPage(pages, changeable); + } +} + +void SCSICD::AddCDROMPage(map>& pages, bool changeable) const +{ + vector buf(8); + + // No changeable area + if (!changeable) { + // 2 seconds for inactive timer + buf[3] = 0x05; + + // MSF multiples are 60 and 75 respectively + buf[5] = 60; + buf[7] = 75; + } + + pages[13] = buf; +} + +void SCSICD::AddCDDAPage(map>& pages, bool) const +{ + vector buf(16); + + // Audio waits for operation completion and allows + // PLAY across multiple tracks + + pages[14] = buf; +} + +int SCSICD::Read(const DWORD *cdb, BYTE *buf, uint64_t block) +{ + ASSERT(buf); + + // Status check + if (!CheckReady()) { + return 0; + } + + // Search for the track + int index = SearchTrack(block); + + // if invalid, out of range + if (index < 0) { + SetStatusCode(STATUS_INVALIDLBA); + return 0; + } + ASSERT(track[index]); + + // If different from the current data track + if (dataindex != index) { + // Delete current disk cache (no need to save) + delete disk.dcache; + disk.dcache = NULL; + + // Reset the number of blocks + SetBlockCount(track[index]->GetBlocks()); + ASSERT(GetBlockCount() > 0); + + // Recreate the disk cache + Filepath path; + track[index]->GetPath(path); + disk.dcache = DiskImageHandleFactory::CreateDiskImageHandle(path, GetSectorSizeShiftCount(), GetBlockCount()); + disk.dcache->SetRawMode(rawfile); + + // Reset data index + dataindex = index; + } + + // Base class + ASSERT(dataindex >= 0); + return super::Read(cdb, buf, block); +} + +int SCSICD::ReadToc(const DWORD *cdb, BYTE *buf) +{ + ASSERT(cdb); + ASSERT(buf); + + // Check if ready + if (!CheckReady()) { + return 0; + } + + // If ready, there is at least one track + ASSERT(tracks > 0); + ASSERT(track[0]); + + // Get allocation length, clear buffer + int length = cdb[7] << 8; + length |= cdb[8]; + memset(buf, 0, length); + + // Get MSF Flag + bool msf = cdb[1] & 0x02; + + // Get and check the last track number + int last = track[tracks - 1]->GetTrackNo(); + if ((int)cdb[6] > last) { + // Except for AA + if (cdb[6] != 0xaa) { + SetStatusCode(STATUS_INVALIDCDB); + return 0; + } + } + + // Check start index + int index = 0; + if (cdb[6] != 0x00) { + // Advance the track until the track numbers match + while (track[index]) { + if ((int)cdb[6] == track[index]->GetTrackNo()) { + break; + } + index++; + } + + // AA if not found or internal error + if (!track[index]) { + if (cdb[6] == 0xaa) { + // Returns the final LBA+1 because it is AA + buf[0] = 0x00; + buf[1] = 0x0a; + buf[2] = (BYTE)track[0]->GetTrackNo(); + buf[3] = (BYTE)last; + buf[6] = 0xaa; + DWORD lba = track[tracks - 1]->GetLast() + 1; + if (msf) { + LBAtoMSF(lba, &buf[8]); + } else { + buf[10] = (BYTE)(lba >> 8); + buf[11] = (BYTE)lba; + } + return length; + } + + // Otherwise, error + SetStatusCode(STATUS_INVALIDCDB); + return 0; + } + } + + // Number of track descriptors returned this time (number of loops) + int loop = last - track[index]->GetTrackNo() + 1; + ASSERT(loop >= 1); + + // Create header + buf[0] = (BYTE)(((loop << 3) + 2) >> 8); + buf[1] = (BYTE)((loop << 3) + 2); + buf[2] = (BYTE)track[0]->GetTrackNo(); + buf[3] = (BYTE)last; + buf += 4; + + // Loop.... + for (int i = 0; i < loop; i++) { + // ADR and Control + if (track[index]->IsAudio()) { + // audio track + buf[1] = 0x10; + } else { + // data track + buf[1] = 0x14; + } + + // track number + buf[2] = (BYTE)track[index]->GetTrackNo(); + + // track address + if (msf) { + LBAtoMSF(track[index]->GetFirst(), &buf[4]); + } else { + buf[6] = (BYTE)(track[index]->GetFirst() >> 8); + buf[7] = (BYTE)(track[index]->GetFirst()); + } + + // Advance buffer pointer and index + buf += 8; + index++; + } + + // Always return only the allocation length + return length; +} + +void SCSICD::GetEventStatusNotification(SASIDEV *controller) +{ + if (!(ctrl->cmd[1] & 0x01)) { + // Asynchronous notification is optional and not supported by rascsi + controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::INVALID_FIELD_IN_CDB); + return; + } + + LOGTRACE("Received request for event polling, which is currently not supported"); + controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::INVALID_FIELD_IN_CDB); +} + +//--------------------------------------------------------------------------- +// +// LBA→MSF Conversion +// +//--------------------------------------------------------------------------- +void SCSICD::LBAtoMSF(DWORD lba, BYTE *msf) const +{ + // 75 and 75*60 get the remainder + DWORD m = lba / (75 * 60); + DWORD s = lba % (75 * 60); + DWORD f = s % 75; + s /= 75; + + // The base point is M=0, S=2, F=0 + s += 2; + if (s >= 60) { + s -= 60; + m++; + } + + // Store + ASSERT(m < 0x100); + ASSERT(s < 60); + ASSERT(f < 75); + msf[0] = 0x00; + msf[1] = (BYTE)m; + msf[2] = (BYTE)s; + msf[3] = (BYTE)f; +} + +void SCSICD::ClearTrack() +{ + // delete the track object + for (int i = 0; i < TrackMax; i++) { + if (track[i]) { + delete track[i]; + track[i] = NULL; + } + } + + // Number of tracks is 0 + tracks = 0; + + // No settings for data and audio + dataindex = -1; + audioindex = -1; +} + +//--------------------------------------------------------------------------- +// +// Track Search +// * Returns -1 if not found +// +//--------------------------------------------------------------------------- +int SCSICD::SearchTrack(DWORD lba) const +{ + // Track loop + for (int i = 0; i < tracks; i++) { + // Listen to the track + ASSERT(track[i]); + if (track[i]->IsValid(lba)) { + return i; + } + } + + // Track wasn't found + return -1; +} + diff --git a/src_old/raspberrypi/devices/scsicd.h b/src_old/raspberrypi/devices/scsicd.h new file mode 100644 index 00000000..b22f398d --- /dev/null +++ b/src_old/raspberrypi/devices/scsicd.h @@ -0,0 +1,124 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// Copyright (C) akuker +// +// Licensed under the BSD 3-Clause License. +// See LICENSE file in the project root folder. +// +// [ SCSI CD-ROM ] +// +//--------------------------------------------------------------------------- +#pragma once + +#include "os.h" +#include "disk.h" +#include "filepath.h" +#include "interfaces/scsi_mmc_commands.h" +#include "interfaces/scsi_primary_commands.h" + +class SCSICD; + +//=========================================================================== +// +// CD-ROM Track +// +//=========================================================================== +class CDTrack +{ +private: + + friend class SCSICD; + + CDTrack(SCSICD *scsicd); + virtual ~CDTrack() {} + +public: + + void Init(int track, DWORD first, DWORD last); + + // Properties + void SetPath(bool cdda, const Filepath& path); // Set the path + void GetPath(Filepath& path) const; // Get the path + void AddIndex(int index, DWORD lba); // Add index + DWORD GetFirst() const; // Get the start LBA + DWORD GetLast() const; // Get the last LBA + DWORD GetBlocks() const; // Get the number of blocks + int GetTrackNo() const; // Get the track number + bool IsValid(DWORD lba) const; // Is this a valid LBA? + bool IsAudio() const; // Is this an audio track? + +private: + SCSICD *cdrom; // Parent device + bool valid; // Valid track + int track_no; // Track number + DWORD first_lba; // First LBA + DWORD last_lba; // Last LBA + bool audio; // Audio track flag + bool raw; // RAW data flag + Filepath imgpath; // Image file path +}; + +//=========================================================================== +// +// SCSI CD-ROM +// +//=========================================================================== +class SCSICD : public Disk, public ScsiMmcCommands, public FileSupport +{ + +public: + enum { + TrackMax = 96 // Maximum number of tracks + }; + + SCSICD(const set&); + ~SCSICD(); + + bool Dispatch(SCSIDEV *) override; + + void Open(const Filepath& path) override; + + // Commands + int Inquiry(const DWORD *cdb, BYTE *buf) override; // INQUIRY command + int Read(const DWORD *cdb, BYTE *buf, uint64_t block) override; // READ command + int ReadToc(const DWORD *cdb, BYTE *buf); // READ TOC command + +protected: + + void AddModePages(map>&, int, bool) const override; + +private: + typedef Disk super; + + Dispatcher dispatcher; + + void AddCDROMPage(map>&, bool) const; + void AddCDDAPage(map>&, bool) const; + + // Open + void OpenCue(const Filepath& path); // Open(CUE) + void OpenIso(const Filepath& path); // Open(ISO) + void OpenPhysical(const Filepath& path); // Open(Physical) + + void ReadToc(SASIDEV *) override; + void GetEventStatusNotification(SASIDEV *) override; + + void LBAtoMSF(DWORD lba, BYTE *msf) const; // LBA→MSF conversion + + bool rawfile; // RAW flag + + // Track management + void ClearTrack(); // Clear the track + int SearchTrack(DWORD lba) const; // Track search + CDTrack* track[TrackMax]; // Track opbject references + int tracks; // Effective number of track objects + int dataindex; // Current data track + int audioindex; // Current audio track + + int frame; // Frame number +}; diff --git a/src_old/raspberrypi/devices/scsihd.cpp b/src_old/raspberrypi/devices/scsihd.cpp new file mode 100644 index 00000000..6a0cc194 --- /dev/null +++ b/src_old/raspberrypi/devices/scsihd.cpp @@ -0,0 +1,228 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// Copyright (C) akuker +// +// Licensed under the BSD 3-Clause License. +// See LICENSE file in the project root folder. +// +// [ SCSI hard disk ] +// +//--------------------------------------------------------------------------- +#include "scsihd.h" +#include "fileio.h" +#include "exceptions.h" +#include + +#define DEFAULT_PRODUCT "SCSI HD" + +//=========================================================================== +// +// SCSI Hard Disk +// +//=========================================================================== + +SCSIHD::SCSIHD(const set& sector_sizes, bool removable) : Disk(removable ? "SCRM" : "SCHD") +{ + SetSectorSizes(sector_sizes); +} + +void SCSIHD::FinalizeSetup(const Filepath &path, off_t size) +{ + // 2TB is the current maximum + if (size > 2LL * 1024 * 1024 * 1024 * 1024) { + throw io_exception("File size must not exceed 2 TiB"); + } + + // For non-removable media drives set the default product name based on the drive capacity + if (!IsRemovable()) { + uint64_t capacity = GetBlockCount() * GetSectorSizeInBytes(); + string unit; + if (capacity >= 1000000) { + capacity /= 1000000; + unit = "MB"; + } + else { + capacity /= 1000; + unit = "KB"; + } + stringstream product; + product << DEFAULT_PRODUCT << " " << capacity << " " << unit; + SetProduct(product.str(), false); + } + + SetReadOnly(false); + SetProtectable(true); + SetProtected(false); + + Disk::Open(path); + FileSupport::SetPath(path); +} + +void SCSIHD::Reset() +{ + // Unlock and release attention + SetLocked(false); + SetAttn(false); + + // No reset, clear code + SetReset(false); + SetStatusCode(STATUS_NOERROR); +} + +void SCSIHD::Open(const Filepath& path) +{ + assert(!IsReady()); + + // Open as read-only + Fileio fio; + if (!fio.Open(path, Fileio::ReadOnly)) { + throw file_not_found_exception("Can't open SCSI hard disk file"); + } + + // Get file size + off_t size = fio.GetFileSize(); + fio.Close(); + + // Sector size (default 512 bytes) and number of blocks + SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 512, false); + SetBlockCount((DWORD)(size >> GetSectorSizeShiftCount())); + + // Effective size must be a multiple of the sector size + size = (size / GetSectorSizeInBytes()) * GetSectorSizeInBytes(); + + FinalizeSetup(path, size); +} + +int SCSIHD::Inquiry(const DWORD *cdb, BYTE *buf) +{ + // EVPD check + if (cdb[1] & 0x01) { + SetStatusCode(STATUS_INVALIDCDB); + return 0; + } + + // Basic data + // buf[0] ... Direct Access Device + // buf[1] ... Bit 7 set means removable + // buf[2] ... SCSI-2 compliant command system + // buf[3] ... SCSI-2 compliant Inquiry response + // buf[4] ... Inquiry additional data + memset(buf, 0, 8); + buf[1] = IsRemovable() ? 0x80 : 0x00; + buf[2] = 0x02; + buf[3] = 0x02; + buf[4] = 0x1F; + + // Padded vendor, product, revision + memcpy(&buf[8], GetPaddedName().c_str(), 28); + + // Size of data that can be returned + int size = (buf[4] + 5); + + // Limit if the other buffer is small + if (size > (int)cdb[4]) { + size = (int)cdb[4]; + } + + return size; +} + +bool SCSIHD::ModeSelect(const DWORD *cdb, const BYTE *buf, int length) +{ + assert(length >= 0); + + int size; + + // PF + if (cdb[1] & 0x10) { + // Mode Parameter header + if (length >= 12) { + // Check the block length bytes + size = 1 << GetSectorSizeShiftCount(); + if (buf[9] != (BYTE)(size >> 16) || + buf[10] != (BYTE)(size >> 8) || + buf[11] != (BYTE)size) { + // currently does not allow changing sector length + SetStatusCode(STATUS_INVALIDPRM); + return false; + } + buf += 12; + length -= 12; + } + + // Parsing the page + while (length > 0) { + // Get page + BYTE page = buf[0]; + + switch (page) { + // format device + case 0x03: + // check the number of bytes in the physical sector + size = 1 << GetSectorSizeShiftCount(); + if (buf[0xc] != (BYTE)(size >> 8) || + buf[0xd] != (BYTE)size) { + // currently does not allow changing sector length + SetStatusCode(STATUS_INVALIDPRM); + return false; + } + break; + + // CD-ROM Parameters + // TODO Move to scsicd.cpp + // According to the SONY CDU-541 manual, Page code 8 is supposed + // to set the Logical Block Adress Format, as well as the + // inactivity timer multiplier + case 0x08: + // Debug code for Issue #2: + // https://github.com/akuker/RASCSI/issues/2 + LOGWARN("[Unhandled page code] Received mode page code 8 with total length %d\n ", length); + for (int i = 0; i>& pages, int page, bool changeable) const +{ + // Page code 48 + if (page != 0x30 && page != 0x3f) { + return; + } + + vector buf(30); + + // No changeable area + if (!changeable) { + memcpy(&buf.data()[0xa], "APPLE COMPUTER, INC.", 20); + } + + pages[48] = buf; +} diff --git a/src_old/raspberrypi/devices/scsihd.h b/src_old/raspberrypi/devices/scsihd.h new file mode 100644 index 00000000..128578f5 --- /dev/null +++ b/src_old/raspberrypi/devices/scsihd.h @@ -0,0 +1,38 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// Copyright (C) akuker +// +// Licensed under the BSD 3-Clause License. +// See LICENSE file in the project root folder. +// +// [ SCSI hard disk ] +// +//--------------------------------------------------------------------------- +#pragma once + +#include "os.h" +#include "disk.h" +#include "filepath.h" + +class SCSIHD : public Disk, public FileSupport +{ +public: + SCSIHD(const set&, bool); + virtual ~SCSIHD() {} + + void FinalizeSetup(const Filepath&, off_t); + + void Reset(); + virtual void Open(const Filepath&) override; + + // Commands + virtual int Inquiry(const DWORD *cdb, BYTE *buf) override; + bool ModeSelect(const DWORD *cdb, const BYTE *buf, int length) override; + + void AddVendorPage(map>&, int, bool) const override; +}; diff --git a/src_old/raspberrypi/devices/scsihd_nec.cpp b/src_old/raspberrypi/devices/scsihd_nec.cpp new file mode 100644 index 00000000..02b93eae --- /dev/null +++ b/src_old/raspberrypi/devices/scsihd_nec.cpp @@ -0,0 +1,214 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// Copyright (C) akuker +// +// Licensed under the BSD 3-Clause License. +// See LICENSE file in the project root folder. +// +// [ SCSI NEC "Genuine" Hard Disk] +// +//--------------------------------------------------------------------------- + +#include "scsihd_nec.h" +#include "fileio.h" +#include "exceptions.h" + +SCSIHD_NEC::SCSIHD_NEC(const set& sector_sizes) : SCSIHD(sector_sizes, false) +{ + // Work initialization + cylinders = 0; + heads = 0; + sectors = 0; +} + +//--------------------------------------------------------------------------- +// +// Extract words that are supposed to be little endian +// +//--------------------------------------------------------------------------- +static inline WORD getWordLE(const BYTE *b) +{ + return ((WORD)(b[1]) << 8) | b[0]; +} + +//--------------------------------------------------------------------------- +// +// Extract longwords assumed to be little endian +// +//--------------------------------------------------------------------------- +static inline DWORD getDwordLE(const BYTE *b) +{ + return ((DWORD)(b[3]) << 24) | ((DWORD)(b[2]) << 16) | ((DWORD)(b[1]) << 8) | b[0]; +} + +void SCSIHD_NEC::Open(const Filepath& path) +{ + ASSERT(!IsReady()); + + // Open as read-only + Fileio fio; + if (!fio.Open(path, Fileio::ReadOnly)) { + throw file_not_found_exception("Can't open hard disk file"); + } + + // Get file size + off_t size = fio.GetFileSize(); + + // NEC root sector + BYTE root_sector[512]; + if (size >= (off_t)sizeof(root_sector)) { + if (!fio.Read(root_sector, sizeof(root_sector))) { + fio.Close(); + throw io_exception("Can't read NEC hard disk file root sector"); + } + } + fio.Close(); + + // Effective size must be a multiple of 512 + size = (size / 512) * 512; + + int image_size = 0; + int sector_size = 0; + + // Determine parameters by extension + const char *ext = path.GetFileExt(); + + // PC-9801-55 NEC genuine? + if (!strcasecmp(ext, ".hdn")) { + // Assuming sector size 512, number of sectors 25, number of heads 8 as default settings + disk.image_offset = 0; + image_size = size; + sector_size = 512; + sectors = 25; + heads = 8; + cylinders = (int)(size >> 9); + cylinders >>= 3; + cylinders /= 25; + } + // Anex86 HD image? + else if (!strcasecmp(ext, ".hdi")) { + disk.image_offset = getDwordLE(&root_sector[8]); + image_size = getDwordLE(&root_sector[12]); + sector_size = getDwordLE(&root_sector[16]); + sectors = getDwordLE(&root_sector[20]); + heads = getDwordLE(&root_sector[24]); + cylinders = getDwordLE(&root_sector[28]); + } + // T98Next HD image? + else if (!strcasecmp(ext, ".nhd")) { + if (!memcmp(root_sector, "T98HDDIMAGE.R0\0", 15)) { + disk.image_offset = getDwordLE(&root_sector[0x110]); + cylinders = getDwordLE(&root_sector[0x114]); + heads = getWordLE(&root_sector[0x118]); + sectors = getWordLE(&root_sector[0x11a]); + sector_size = getWordLE(&root_sector[0x11c]); + image_size = (off_t)cylinders * heads * sectors * sector_size; + } + else { + throw io_exception("Invalid NEC image file format"); + } + } + + // Image size consistency check + if (disk.image_offset + image_size > size || (image_size % sector_size != 0)) { + throw io_exception("Image size consistency check failed"); + } + + // Calculate sector size + for (size = 16; size > 0; --size) { + if ((1 << size) == sector_size) + break; + } + if (size <= 0 || size > 16) { + throw io_exception("Invalid NEC disk size"); + } + SetSectorSizeShiftCount(size); + + // Number of blocks + SetBlockCount(image_size >> disk.size); + + FinalizeSetup(path, size); +} + +int SCSIHD_NEC::Inquiry(const DWORD *cdb, BYTE *buf) +{ + int size = SCSIHD::Inquiry(cdb, buf); + + // This drive is a SCSI-1 SCCS drive + buf[2] = 0x01; + buf[3] = 0x01; + + return size; +} + +void SCSIHD_NEC::AddErrorPage(map>& pages, bool) const +{ + vector buf(8); + + // The retry count is 0, and the limit time uses the default value inside the device. + + pages[1] = buf; +} + +void SCSIHD_NEC::AddFormatPage(map>& pages, bool changeable) const +{ + vector buf(24); + + // Page can be saved + buf[0] = 0x80; + + // Make the number of bytes in the physical sector appear mutable (although it cannot actually be) + if (changeable) { + buf[0xc] = 0xff; + buf[0xd] = 0xff; + + pages[3] = buf; + + return; + } + + if (IsReady()) { + // Set the number of tracks in one zone (PC-9801-55 seems to see this value) + buf[0x2] = (BYTE)(heads >> 8); + buf[0x3] = (BYTE)heads; + + // Set the number of sectors per track + buf[0xa] = (BYTE)(sectors >> 8); + buf[0xb] = (BYTE)sectors; + + // Set the number of bytes in the physical sector + int size = 1 << disk.size; + buf[0xc] = (BYTE)(size >> 8); + buf[0xd] = (BYTE)size; + } + + // Set removable attributes (remains of the old days) + if (IsRemovable()) { + buf[20] = 0x20; + } + + pages[3] = buf; +} + +void SCSIHD_NEC::AddDrivePage(map>& pages, bool changeable) const +{ + vector buf(20); + + // No changeable area + if (!changeable && IsReady()) { + // Set the number of cylinders + buf[0x2] = (BYTE)(cylinders >> 16); + buf[0x3] = (BYTE)(cylinders >> 8); + buf[0x4] = (BYTE)cylinders; + + // Set the number of heads + buf[0x5] = (BYTE)heads; + } + + pages[4] = buf; +} diff --git a/src_old/raspberrypi/devices/scsihd_nec.h b/src_old/raspberrypi/devices/scsihd_nec.h new file mode 100644 index 00000000..75923123 --- /dev/null +++ b/src_old/raspberrypi/devices/scsihd_nec.h @@ -0,0 +1,45 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// Copyright (C) akuker +// +// Licensed under the BSD 3-Clause License. +// See LICENSE file in the project root folder. +// +// [ SCSI NEC "Genuine" Hard Disk] +// +//--------------------------------------------------------------------------- +#pragma once + +#include "scsihd.h" + +//=========================================================================== +// +// SCSI hard disk (PC-9801-55 NEC genuine / Anex86 / T98Next) +// +//=========================================================================== +class SCSIHD_NEC : public SCSIHD +{ +public: + SCSIHD_NEC(const set&); + ~SCSIHD_NEC() {} + + void Open(const Filepath& path) override; + + // Commands + int Inquiry(const DWORD *cdb, BYTE *buf) override; + + void AddErrorPage(map>&, bool) const override; + void AddFormatPage(map>&, bool) const override; + void AddDrivePage(map>&, bool) const override; + +private: + // Geometry data + int cylinders; + int heads; + int sectors; +}; diff --git a/src_old/raspberrypi/devices/scsimo.cpp b/src_old/raspberrypi/devices/scsimo.cpp new file mode 100644 index 00000000..0bed7926 --- /dev/null +++ b/src_old/raspberrypi/devices/scsimo.cpp @@ -0,0 +1,289 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// Copyright (C) akuker +// +// Licensed under the BSD 3-Clause License. +// See LICENSE file in the project root folder. +// +// [ SCSI Magneto-Optical Disk] +// +//--------------------------------------------------------------------------- + +#include "scsimo.h" + +#include "fileio.h" +#include "exceptions.h" + +SCSIMO::SCSIMO(const set& sector_sizes, const map& geometries) : Disk("SCMO") +{ + SetSectorSizes(sector_sizes); + SetGeometries(geometries); +} + +void SCSIMO::Open(const Filepath& path) +{ + assert(!IsReady()); + + // Open as read-only + Fileio fio; + + if (!fio.Open(path, Fileio::ReadOnly)) { + throw file_not_found_exception("Can't open MO file"); + } + + // Get file size + off_t size = fio.GetFileSize(); + fio.Close(); + + // For some priorities there are hard-coded, well-defined sector sizes and block counts + if (!SetGeometryForCapacity(size)) { + // Sector size (default 512 bytes) and number of blocks + SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 512, true); + SetBlockCount(size >> GetSectorSizeShiftCount()); + } + + // Effective size must be a multiple of the sector size + size = (size / GetSectorSizeInBytes()) * GetSectorSizeInBytes(); + + SetReadOnly(false); + SetProtectable(true); + SetProtected(false); + + Disk::Open(path); + FileSupport::SetPath(path); + + // Attention if ready + if (IsReady()) { + SetAttn(true); + } +} + +int SCSIMO::Inquiry(const DWORD *cdb, BYTE *buf) +{ + // EVPD check + if (cdb[1] & 0x01) { + SetStatusCode(STATUS_INVALIDCDB); + return FALSE; + } + + // Basic data + // buf[0] ... Optical Memory Device + // buf[1] ... Removable + // buf[2] ... SCSI-2 compliant command system + // buf[3] ... SCSI-2 compliant Inquiry response + // buf[4] ... Inquiry additional data + memset(buf, 0, 8); + buf[0] = 0x07; + buf[1] = 0x80; + buf[2] = 0x02; + buf[3] = 0x02; + buf[4] = 0x1F; + + // Padded vendor, product, revision + memcpy(&buf[8], GetPaddedName().c_str(), 28); + + // Size return data + int size = (buf[4] + 5); + + // Limit the size if the buffer is too small + if (size > (int)cdb[4]) { + size = (int)cdb[4]; + } + + return size; +} + +void SCSIMO::SetDeviceParameters(BYTE *buf) +{ + Disk::SetDeviceParameters(buf); + + // MEDIUM TYPE: Optical reversible or erasable + buf[2] = 0x03; +} + +void SCSIMO::AddModePages(map>& pages, int page, bool changeable) const +{ + Disk::AddModePages(pages, page, changeable); + + // Page code 6 + if (page == 0x06 || page == 0x3f) { + AddOptionPage(pages, changeable); + } +} + +void SCSIMO::AddOptionPage(map>& pages, bool) const +{ + vector buf(4); + pages[6] = buf; + + // Do not report update blocks +} + +bool SCSIMO::ModeSelect(const DWORD *cdb, const BYTE *buf, int length) +{ + int size; + + ASSERT(length >= 0); + + // PF + if (cdb[1] & 0x10) { + // Mode Parameter header + if (length >= 12) { + // Check the block length (in bytes) + size = 1 << GetSectorSizeShiftCount(); + if (buf[9] != (BYTE)(size >> 16) || + buf[10] != (BYTE)(size >> 8) || buf[11] != (BYTE)size) { + // Currently does not allow changing sector length + SetStatusCode(STATUS_INVALIDPRM); + return false; + } + buf += 12; + length -= 12; + } + + // Parsing the page + while (length > 0) { + // Get the page + int page = buf[0]; + + switch (page) { + // format device + case 0x03: + // Check the number of bytes in the physical sector + size = 1 << GetSectorSizeShiftCount(); + if (buf[0xc] != (BYTE)(size >> 8) || + buf[0xd] != (BYTE)size) { + // Currently does not allow changing sector length + SetStatusCode(STATUS_INVALIDPRM); + return false; + } + break; + // vendor unique format + case 0x20: + // just ignore, for now + break; + + // Other page + default: + break; + } + + // Advance to the next page + size = buf[1] + 2; + length -= size; + buf += size; + } + } + + // Do not generate an error for the time being (MINIX) + return true; +} + +//--------------------------------------------------------------------------- +// +// Vendor Unique Format Page 20h (MO) +// +//--------------------------------------------------------------------------- +void SCSIMO::AddVendorPage(map>& pages, int page, bool changeable) const +{ + // Page code 20h + if (page != 0x20 && page != 0x3f) { + return; + } + + vector buf(12); + + // No changeable area + if (changeable) { + pages[32] = buf; + + return; + } + + /* + mode page code 20h - Vendor Unique Format Page + format mode XXh type 0 + information: http://h20628.www2.hp.com/km-ext/kmcsdirect/emr_na-lpg28560-1.pdf + + offset description + 02h format mode + 03h type of format (0) + 04~07h size of user band (total sectors?) + 08~09h size of spare band (spare sectors?) + 0A~0Bh number of bands + + actual value of each 3.5inches optical medium (grabbed by Fujitsu M2513EL) + + 128M 230M 540M 640M + --------------------------------------------------- + size of user band 3CBFAh 6CF75h FE45Ch 4BC50h + size of spare band 0400h 0401h 08CAh 08C4h + number of bands 0001h 000Ah 0012h 000Bh + + further information: http://r2089.blog36.fc2.com/blog-entry-177.html + */ + + if (IsReady()) { + unsigned spare = 0; + unsigned bands = 0; + uint64_t blocks = GetBlockCount(); + + if (GetSectorSizeInBytes() == 512) { + switch (blocks) { + // 128MB + case 248826: + spare = 1024; + bands = 1; + break; + + // 230MB + case 446325: + spare = 1025; + bands = 10; + break; + + // 540MB + case 1041500: + spare = 2250; + bands = 18; + break; + } + } + + if (GetSectorSizeInBytes() == 2048) { + switch (blocks) { + // 640MB + case 310352: + spare = 2244; + bands = 11; + break; + + // 1.3GB (lpproj: not tested with real device) + case 605846: + spare = 4437; + bands = 18; + break; + } + } + + buf[2] = 0; // format mode + buf[3] = 0; // type of format + buf[4] = (BYTE)(blocks >> 24); + buf[5] = (BYTE)(blocks >> 16); + buf[6] = (BYTE)(blocks >> 8); + buf[7] = (BYTE)blocks; + buf[8] = (BYTE)(spare >> 8); + buf[9] = (BYTE)spare; + buf[10] = (BYTE)(bands >> 8); + buf[11] = (BYTE)bands; + } + + pages[32] = buf; + + return; +} diff --git a/src_old/raspberrypi/devices/scsimo.h b/src_old/raspberrypi/devices/scsimo.h new file mode 100644 index 00000000..b55d42ea --- /dev/null +++ b/src_old/raspberrypi/devices/scsimo.h @@ -0,0 +1,44 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// Copyright (C) akuker +// +// Licensed under the BSD 3-Clause License. +// See LICENSE file in the project root folder. +// +// [ SCSI Magneto-Optical Disk] +// +//--------------------------------------------------------------------------- +#pragma once + +#include "os.h" +#include "disk.h" +#include "filepath.h" + +class SCSIMO : public Disk, public FileSupport +{ +public: + SCSIMO(const set&, const map&); + ~SCSIMO() {} + + void Open(const Filepath& path) override; + + // Commands + int Inquiry(const DWORD *cdb, BYTE *buf) override; + bool ModeSelect(const DWORD *cdb, const BYTE *buf, int length) override; + +protected: + + // Internal processing + void SetDeviceParameters(BYTE *) override; + void AddModePages(map>&, int, bool) const override; + void AddVendorPage(map>&, int, bool) const override; + +private: + + void AddOptionPage(map>&, bool) const; +}; diff --git a/src_old/raspberrypi/disk_image/disk_image_handle.cpp b/src_old/raspberrypi/disk_image/disk_image_handle.cpp new file mode 100644 index 00000000..078ded3c --- /dev/null +++ b/src_old/raspberrypi/disk_image/disk_image_handle.cpp @@ -0,0 +1,59 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2022 akuker +// +// Base class for interfacing with disk images. +// +// [ DiskImageHandle ] +// +//--------------------------------------------------------------------------- + +#include "disk_image/disk_image_handle.h" + +DiskImageHandle::DiskImageHandle(const Filepath &path, int size, uint32_t blocks, off_t imgoff) +{ + + serial = 0; + sec_path = path; + sec_size = size; + sec_blocks = blocks; + imgoffset = imgoff; +} +DiskImageHandle::~DiskImageHandle() +{ +} + +off_t DiskImageHandle::GetSectorOffset(int block) +{ + + int sector_num = block & 0xff; + return (off_t)sector_num << sec_size; +} + +off_t DiskImageHandle::GetTrackOffset(int block) +{ + + // Assuming that all tracks hold 256 sectors + int track_num = block >> 8; + + // Calculate offset (previous tracks are considered to hold 256 sectors) + off_t offset = ((off_t)track_num << 8); + if (cd_raw) + { + ASSERT(sec_size == 11); + offset *= 0x930; + offset += 0x10; + } + else + { + offset <<= sec_size; + } + + // Add offset to real image + offset += imgoffset; + + return offset; +} diff --git a/src_old/raspberrypi/disk_image/disk_image_handle.h b/src_old/raspberrypi/disk_image/disk_image_handle.h new file mode 100644 index 00000000..84ec5c2c --- /dev/null +++ b/src_old/raspberrypi/disk_image/disk_image_handle.h @@ -0,0 +1,43 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2022 akuker +// +// Base class for interfacing with disk images. +// +// [ DiskImageHandle ] +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "filepath.h" + +class DiskImageHandle +{ +public: + DiskImageHandle(const Filepath &path, int size, uint32_t blocks, off_t imgoff = 0); + virtual ~DiskImageHandle(); + + void SetRawMode(bool raw) { cd_raw = raw; }; // CD-ROM raw mode setting + + // Access + virtual bool Save() = 0; // Save and release all + virtual bool ReadSector(BYTE *buf, int block) = 0; // Sector Read + virtual bool WriteSector(const BYTE *buf, int block) = 0; // Sector Write + virtual bool GetCache(int index, int &track, DWORD &serial) const = 0; // Get cache information + +protected: + bool cd_raw = false; + DWORD serial; // Last serial number + Filepath sec_path; // Path + + int sec_size; // Sector Size (8=256, 9=512, 10=1024, 11=2048, 12=4096) + int sec_blocks; // Blocks per sector + off_t imgoffset; // Offset to actual data + + off_t GetTrackOffset(int block); + off_t GetSectorOffset(int block); +}; diff --git a/src_old/raspberrypi/disk_image/disk_image_handle_factory.cpp b/src_old/raspberrypi/disk_image/disk_image_handle_factory.cpp new file mode 100644 index 00000000..e88e5ba7 --- /dev/null +++ b/src_old/raspberrypi/disk_image/disk_image_handle_factory.cpp @@ -0,0 +1,48 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2022 akuker +// +// Factory class for creating DiskImageHandles +// +// [ DiskImageHandleFactory ] +// +//--------------------------------------------------------------------------- + +#include "disk_image/disk_image_handle_factory.h" +#include "log.h" +#include "disk_image/disk_track_cache.h" +#include "disk_image/mmap_file_handle.h" +#include "disk_image/posix_file_handle.h" + +DiskImageHandleType DiskImageHandleFactory::current_access_type = DiskImageHandleType::ePosixFile; + +DiskImageHandle *DiskImageHandleFactory::CreateDiskImageHandle(const Filepath &path, int size, uint32_t blocks, off_t imgoff) +{ + + DiskImageHandle *result = NULL; + + if (current_access_type == DiskImageHandleType::eMmapFile) + { + LOGINFO("%s Creating MmapFileAccess %s", __PRETTY_FUNCTION__, path.GetPath()) + result = new MmapFileHandle(path, size, blocks, imgoff); + } + else if (current_access_type == DiskImageHandleType::eRamCache) + { + LOGINFO("%s Creating DiskCache %s", __PRETTY_FUNCTION__, path.GetPath()) + result = new DiskCache(path, size, blocks, imgoff); + } + else if (current_access_type == DiskImageHandleType::ePosixFile) + { + LOGINFO("%s Creating PosixFileHandle %s", __PRETTY_FUNCTION__, path.GetPath()) + result = new PosixFileHandle(path, size, blocks, imgoff); + } + + if (result == NULL) + { + LOGWARN("%s Unable to create the File Access", __PRETTY_FUNCTION__); + } + return result; +} diff --git a/src_old/raspberrypi/disk_image/disk_image_handle_factory.h b/src_old/raspberrypi/disk_image/disk_image_handle_factory.h new file mode 100644 index 00000000..6a79a341 --- /dev/null +++ b/src_old/raspberrypi/disk_image/disk_image_handle_factory.h @@ -0,0 +1,35 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2022 akuker +// +// Factory class for creating DiskImageHandles +// +// [ DiskImageHandleFactory ] +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "disk_image/disk_image_handle.h" + +enum DiskImageHandleType +{ + eRamCache, + eMmapFile, + ePosixFile, +}; + +class DiskImageHandleFactory +{ +public: + static void SetFileAccessMethod(DiskImageHandleType method) { current_access_type = method; }; + static DiskImageHandleType GetFileAccessMethod() { return current_access_type; }; + + static DiskImageHandle *CreateDiskImageHandle(const Filepath &path, int size, uint32_t blocks, off_t imgoff = 0); + +private: + static DiskImageHandleType current_access_type; +}; \ No newline at end of file diff --git a/src_old/raspberrypi/disk_image/disk_track_cache.cpp b/src_old/raspberrypi/disk_image/disk_track_cache.cpp new file mode 100644 index 00000000..85b75b0f --- /dev/null +++ b/src_old/raspberrypi/disk_image/disk_track_cache.cpp @@ -0,0 +1,575 @@ +//--------------------------------------------------------------------------- +// +// X68000 EMULATOR "XM6" +// +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// +// XM6i +// Copyright (C) 2010-2015 isaki@NetBSD.org +// Copyright (C) 2010 Y.Sugahara +// +// Imported sava's Anex86/T98Next image and MO format support patch. +// Comments translated to english by akuker. +// +// [ DiskTrack and DiskCache ] +// +//--------------------------------------------------------------------------- + +#include "os.h" +#include "log.h" +#include "fileio.h" +#include "disk_track_cache.h" + +//=========================================================================== +// +// Disk Track +// +//=========================================================================== + +DiskTrack::DiskTrack() +{ + // Initialization of internal information + dt.track = 0; + dt.size = 0; + dt.sectors = 0; + dt.raw = FALSE; + dt.init = FALSE; + dt.changed = FALSE; + dt.length = 0; + dt.buffer = NULL; + dt.maplen = 0; + dt.changemap = NULL; + dt.imgoffset = 0; +} + +DiskTrack::~DiskTrack() +{ + // Release memory, but do not save automatically + if (dt.buffer) { + free(dt.buffer); + dt.buffer = NULL; + } + if (dt.changemap) { + free(dt.changemap); + dt.changemap = NULL; + } +} + +void DiskTrack::Init(int track, int size, int sectors, BOOL raw, off_t imgoff) +{ + ASSERT(track >= 0); + ASSERT((sectors > 0) && (sectors <= 0x100)); + ASSERT(imgoff >= 0); + + // Set Parameters + dt.track = track; + dt.size = size; + dt.sectors = sectors; + dt.raw = raw; + + // Not initialized (needs to be loaded) + dt.init = FALSE; + + // Not Changed + dt.changed = FALSE; + + // Offset to actual data + dt.imgoffset = imgoff; +} + +bool DiskTrack::Load(const Filepath& path) +{ + // Not needed if already loaded + if (dt.init) { + ASSERT(dt.buffer); + ASSERT(dt.changemap); + return true; + } + + // Calculate offset (previous tracks are considered to hold 256 sectors) + off_t offset = ((off_t)dt.track << 8); + if (dt.raw) { + ASSERT(dt.size == 11); + offset *= 0x930; + offset += 0x10; + } else { + offset <<= dt.size; + } + + // Add offset to real image + offset += dt.imgoffset; + + // Calculate length (data size of this track) + int length = dt.sectors << dt.size; + + // Allocate buffer memory + ASSERT((dt.sectors > 0) && (dt.sectors <= 0x100)); + + if (dt.buffer == NULL) { + if (posix_memalign((void **)&dt.buffer, 512, ((length + 511) / 512) * 512)) { + LOGWARN("%s posix_memalign failed", __PRETTY_FUNCTION__); + } + dt.length = length; + } + + if (!dt.buffer) { + return false; + } + + // Reallocate if the buffer length is different + if (dt.length != (DWORD)length) { + free(dt.buffer); + if (posix_memalign((void **)&dt.buffer, 512, ((length + 511) / 512) * 512)) { + LOGWARN("%s posix_memalign failed", __PRETTY_FUNCTION__); + } + dt.length = length; + } + + // Reserve change map memory + if (dt.changemap == NULL) { + dt.changemap = (BOOL *)malloc(dt.sectors * sizeof(BOOL)); + dt.maplen = dt.sectors; + } + + if (!dt.changemap) { + return false; + } + + // Reallocate if the buffer length is different + if (dt.maplen != (DWORD)dt.sectors) { + free(dt.changemap); + dt.changemap = (BOOL *)malloc(dt.sectors * sizeof(BOOL)); + dt.maplen = dt.sectors; + } + + // Clear changemap + memset(dt.changemap, 0x00, dt.sectors * sizeof(BOOL)); + + // Read from File + Fileio fio; + if (!fio.OpenDIO(path, Fileio::ReadOnly)) { + return false; + } + if (dt.raw) { + // Split Reading + for (int i = 0; i < dt.sectors; i++) { + // Seek + if (!fio.Seek(offset)) { + fio.Close(); + return false; + } + + // Read + if (!fio.Read(&dt.buffer[i << dt.size], 1 << dt.size)) { + fio.Close(); + return false; + } + + // Next offset + offset += 0x930; + } + } else { + // Continuous reading + if (!fio.Seek(offset)) { + fio.Close(); + return false; + } + if (!fio.Read(dt.buffer, length)) { + fio.Close(); + return false; + } + } + fio.Close(); + + // Set a flag and end normally + dt.init = TRUE; + dt.changed = FALSE; + return true; +} + +bool DiskTrack::Save(const Filepath& path) +{ + // Not needed if not initialized + if (!dt.init) { + return true; + } + + // Not needed unless changed + if (!dt.changed) { + return true; + } + + // Need to write + ASSERT(dt.buffer); + ASSERT(dt.changemap); + ASSERT((dt.sectors > 0) && (dt.sectors <= 0x100)); + + // Writing in RAW mode is not allowed + ASSERT(!dt.raw); + + // Calculate offset (previous tracks are considered to hold 256 sectors) + off_t offset = ((off_t)dt.track << 8); + offset <<= dt.size; + + // Add offset to real image + offset += dt.imgoffset; + + // Calculate length per sector + int length = 1 << dt.size; + + // Open file + Fileio fio; + if (!fio.Open(path, Fileio::ReadWrite)) { + return false; + } + + // Partial write loop + int total; + for (int i = 0; i < dt.sectors;) { + // If changed + if (dt.changemap[i]) { + // Initialize write size + total = 0; + + // Seek + if (!fio.Seek(offset + ((off_t)i << dt.size))) { + fio.Close(); + return false; + } + + // Consectutive sector length + int j; + for (j = i; j < dt.sectors; j++) { + // end when interrupted + if (!dt.changemap[j]) { + break; + } + + // Add one sector + total += length; + } + + // Write + if (!fio.Write(&dt.buffer[i << dt.size], total)) { + fio.Close(); + return false; + } + + // To unmodified sector + i = j; + } else { + // Next Sector + i++; + } + } + + // Close + fio.Close(); + + // Drop the change flag and exit + memset(dt.changemap, 0x00, dt.sectors * sizeof(BOOL)); + dt.changed = FALSE; + return true; +} + +bool DiskTrack::ReadSector(BYTE *buf, int sec) const +{ + ASSERT(buf); + ASSERT((sec >= 0) & (sec < 0x100)); + + LOGTRACE("%s reading sector: %d", __PRETTY_FUNCTION__,sec); + // Error if not initialized + if (!dt.init) { + return false; + } + + // // Error if the number of sectors exceeds the valid number + if (sec >= dt.sectors) { + return false; + } + + // Copy + ASSERT(dt.buffer); + ASSERT((dt.sectors > 0) && (dt.sectors <= 0x100)); + memcpy(buf, &dt.buffer[(off_t)sec << dt.size], (off_t)1 << dt.size); + + // Success + return true; +} + +bool DiskTrack::WriteSector(const BYTE *buf, int sec) +{ + ASSERT(buf); + ASSERT((sec >= 0) & (sec < 0x100)); + ASSERT(!dt.raw); + + // Error if not initialized + if (!dt.init) { + return false; + } + + // // Error if the number of sectors exceeds the valid number + if (sec >= dt.sectors) { + return false; + } + + // Calculate offset and length + int offset = sec << dt.size; + int length = 1 << dt.size; + + // Compare + ASSERT(dt.buffer); + ASSERT((dt.sectors > 0) && (dt.sectors <= 0x100)); + if (memcmp(buf, &dt.buffer[offset], length) == 0) { + // Exit normally since it's attempting to write the same thing + return true; + } + + // Copy, change + memcpy(&dt.buffer[offset], buf, length); + dt.changemap[sec] = TRUE; + dt.changed = TRUE; + + // Success + return true; +} + +//=========================================================================== +// +// Disk Cache +// +//=========================================================================== + +DiskCache::DiskCache(const Filepath& path, int size, uint32_t blocks, off_t imgoff) : DiskImageHandle(path, size, blocks, imgoff) +{ + ASSERT(blocks > 0); + ASSERT(imgoff >= 0); + + // Cache work + for (int i = 0; i < CacheMax; i++) { + cache[i].disktrk = NULL; + cache[i].serial = 0; + } + +} + +DiskCache::~DiskCache() +{ + // Clear the track + Clear(); +} + +bool DiskCache::Save() +{ + // Save track + for (int i = 0; i < CacheMax; i++) { + // Is it a valid track? + if (cache[i].disktrk) { + // Save + if (!cache[i].disktrk->Save(sec_path)) { + return false; + } + } + } + + return true; +} + +//--------------------------------------------------------------------------- +// +// Get disk cache information +// +//--------------------------------------------------------------------------- +bool DiskCache::GetCache(int index, int& track, DWORD& aserial) const +{ + ASSERT((index >= 0) && (index < CacheMax)); + + // false if unused + if (!cache[index].disktrk) { + return false; + } + + // Set track and serial + track = cache[index].disktrk->GetTrack(); + aserial = cache[index].serial; + + return true; +} + +void DiskCache::Clear() +{ + // Free the cache + for (int i = 0; i < CacheMax; i++) { + if (cache[i].disktrk) { + delete cache[i].disktrk; + cache[i].disktrk = NULL; + } + } +} + +bool DiskCache::ReadSector(BYTE *buf, int block) +{ + ASSERT(sec_size != 0); + + // Update first + UpdateSerialNumber(); + + // Calculate track (fixed to 256 sectors/track) + int track = block >> 8; + + // Get the track data + DiskTrack *disktrk = Assign(track); + if (!disktrk) { + return false; + } + + // Read the track data to the cache + return disktrk->ReadSector(buf, block & 0xff); +} + +bool DiskCache::WriteSector(const BYTE *buf, int block) +{ + ASSERT(sec_size != 0); + + // Update first + UpdateSerialNumber(); + + // Calculate track (fixed to 256 sectors/track) + int track = block >> 8; + + // Get that track data + DiskTrack *disktrk = Assign(track); + if (!disktrk) { + return false; + } + + // Write the data to the cache + return disktrk->WriteSector(buf, block & 0xff); +} + +//--------------------------------------------------------------------------- +// +// Track Assignment +// +//--------------------------------------------------------------------------- +DiskTrack* DiskCache::Assign(int track) +{ + ASSERT(sec_size != 0); + ASSERT(track >= 0); + + // First, check if it is already assigned + for (int i = 0; i < CacheMax; i++) { + if (cache[i].disktrk) { + if (cache[i].disktrk->GetTrack() == track) { + // Track match + cache[i].serial = serial; + return cache[i].disktrk; + } + } + } + + // Next, check for empty + for (int i = 0; i < CacheMax; i++) { + if (!cache[i].disktrk) { + // Try loading + if (Load(i, track)) { + // Success loading + cache[i].serial = serial; + return cache[i].disktrk; + } + + // Load failed + return NULL; + } + } + + // Finally, find the youngest serial number and delete it + + // Set index 0 as candidate c + DWORD s = cache[0].serial; + int c = 0; + + // Compare candidate with serial and update to smaller one + for (int i = 0; i < CacheMax; i++) { + ASSERT(cache[i].disktrk); + + // Compare and update the existing serial + if (cache[i].serial < s) { + s = cache[i].serial; + c = i; + } + } + + // Save this track + if (!cache[c].disktrk->Save(sec_path)) { + return NULL; + } + + // Delete this track + DiskTrack *disktrk = cache[c].disktrk; + cache[c].disktrk = NULL; + + if (Load(c, track, disktrk)) { + // Successful loading + cache[c].serial = serial; + return cache[c].disktrk; + } + + // Load failed + return NULL; +} + +//--------------------------------------------------------------------------- +// +// Load cache +// +//--------------------------------------------------------------------------- +bool DiskCache::Load(int index, int track, DiskTrack *disktrk) +{ + ASSERT((index >= 0) && (index < CacheMax)); + ASSERT(track >= 0); + ASSERT(!cache[index].disktrk); + + // Get the number of sectors on this track + int sectors = sec_blocks - (track << 8); + ASSERT(sectors > 0); + if (sectors > 0x100) { + sectors = 0x100; + } + + // Create a disk track + if (disktrk == NULL) { + disktrk = new DiskTrack(); + } + + // Initialize disk track + disktrk->Init(track, sec_size, sectors, cd_raw, imgoffset); + + // Try loading + if (!disktrk->Load(sec_path)) { + // Failure + delete disktrk; + return false; + } + + // Allocation successful, work set + cache[index].disktrk = disktrk; + + return true; +} + +void DiskCache::UpdateSerialNumber() +{ + // Update and do nothing except 0 + serial++; + if (serial != 0) { + return; + } + + // Clear serial of all caches (loop in 32bit) + for (int i = 0; i < CacheMax; i++) { + cache[i].serial = 0; + } +} + diff --git a/src_old/raspberrypi/disk_image/disk_track_cache.h b/src_old/raspberrypi/disk_image/disk_track_cache.h new file mode 100644 index 00000000..dacba06f --- /dev/null +++ b/src_old/raspberrypi/disk_image/disk_track_cache.h @@ -0,0 +1,90 @@ +//--------------------------------------------------------------------------- +// +// X68000 EMULATOR "XM6" +// +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// +// XM6i +// Copyright (C) 2010-2015 isaki@NetBSD.org +// +// Imported sava's Anex86/T98Next image and MO format support patch. +// Comments translated to english by akuker. +// +// [ DiskTrack and DiskCache ] +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "filepath.h" +#include "disk_image/disk_image_handle.h" + +// Number of tracks to cache +#define CacheMax 16 + +class DiskTrack +{ +private: + struct { + int track; // Track Number + int size; // Sector Size (8=256, 9=512, 10=1024, 11=2048, 12=4096) + int sectors; // Number of sectors(<0x100) + DWORD length; // Data buffer length + BYTE *buffer; // Data buffer + BOOL init; // Is it initilized? + BOOL changed; // Changed flag + DWORD maplen; // Changed map length + BOOL *changemap; // Changed map + BOOL raw; // RAW mode flag + off_t imgoffset; // Offset to actual data + } dt; + +public: + DiskTrack(); + ~DiskTrack(); + +private: + friend class DiskCache; + + void Init(int track, int size, int sectors, BOOL raw = FALSE, off_t imgoff = 0); + bool Load(const Filepath& path); + bool Save(const Filepath& path); + + // Read / Write + bool ReadSector(BYTE *buf, int sec) const; // Sector Read + bool WriteSector(const BYTE *buf, int sec); // Sector Write + + int GetTrack() const { return dt.track; } // Get track +}; + +class DiskCache : public DiskImageHandle +{ +public: + // Internal data definition + typedef struct { + DiskTrack *disktrk; // Disk Track + DWORD serial; // Serial + } cache_t; + +public: + DiskCache(const Filepath& path, int size, uint32_t blocks, off_t imgoff = 0); + ~DiskCache(); + + // Access + bool Save() override; // Save and release all + bool ReadSector(BYTE *buf, int block) override; // Sector Read + bool WriteSector(const BYTE *buf, int block) override; // Sector Write + bool GetCache(int index, int& track, DWORD& serial) const override; // Get cache information + +private: + // Internal Management + void Clear(); // Clear all tracks + DiskTrack* Assign(int track); // Load track + bool Load(int index, int track, DiskTrack *disktrk = NULL); // Load track + void UpdateSerialNumber(); // Update serial number + + // Internal data + cache_t cache[CacheMax]; // Cache management +}; + diff --git a/src_old/raspberrypi/disk_image/mmap_file_handle.cpp b/src_old/raspberrypi/disk_image/mmap_file_handle.cpp new file mode 100644 index 00000000..80c21a67 --- /dev/null +++ b/src_old/raspberrypi/disk_image/mmap_file_handle.cpp @@ -0,0 +1,106 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2022 akuker +// +// This method of file access will use the mmap() capabilities to 'map' the +// file into memory. +// +// This doesn't actually copy the file into RAM, but instead maps the file +// as virtual memory. Behind the scenes, the file is still being accessed. +// +// The operating system will do some caching of the data to prevent direct +// drive access for each read/write. So, instead of implementing our own +// caching mechanism (like in disk_track_cache.h), we just rely on the +// operating system. +// +// [ MmapFilehandle ] +// +//--------------------------------------------------------------------------- + +#include "mmap_file_handle.h" +#include "log.h" +#include +#include +#include + +//=========================================================================== +// +// Direct file access that will map the file into virtual memory space +// +//=========================================================================== +MmapFileHandle::MmapFileHandle(const Filepath &path, int size, uint32_t blocks, off_t imgoff) : DiskImageHandle(path, size, blocks, imgoff) +{ + ASSERT(blocks > 0); + ASSERT(imgoff >= 0); + + fd = open(path.GetPath(), O_RDWR); + if (fd < 0) + { + LOGWARN("Unable to open file %s. Errno:%d", path.GetPath(), errno) + } + LOGWARN("%s opened %s", __PRETTY_FUNCTION__, path.GetPath()); + struct stat sb; + if (fstat(fd, &sb) < 0) + { + LOGWARN("Unable to run fstat. Errno:%d", errno); + } + printf("Size: %llu\n", (uint64_t)sb.st_size); + + LOGWARN("%s mmap-ed file of size: %llu", __PRETTY_FUNCTION__, (uint64_t)sb.st_size); + + // int x = EACCES; + + memory_block = (const char *)mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + int errno_val = errno; + if (memory_block == MAP_FAILED) + { + LOGWARN("Unabled to memory map file %s", path.GetPath()); + LOGWARN(" Errno:%d", errno_val); + return; + } + initialized = true; +} + +MmapFileHandle::~MmapFileHandle() +{ + munmap((void *)memory_block, sb.st_size); + close(fd); + + // Force the OS to save any cached data to the disk + sync(); +} + +bool MmapFileHandle::ReadSector(BYTE *buf, int block) +{ + ASSERT(sec_size != 0); + ASSERT(buf); + ASSERT(block < sec_blocks); + ASSERT(memory_block); + + int sector_size_bytes = (off_t)1 << sec_size; + + // Calculate offset into the image file + off_t offset = GetTrackOffset(block); + offset += GetSectorOffset(block); + + memcpy(buf, &memory_block[offset], sector_size_bytes); + + return true; +} + +bool MmapFileHandle::WriteSector(const BYTE *buf, int block) +{ + + ASSERT(buf); + ASSERT(block < sec_blocks); + ASSERT(memory_block); + + ASSERT((block * sec_size) <= (sb.st_size + sec_size)); + + memcpy((void *)&memory_block[(block * sec_size)], buf, sec_size); + + return true; +} diff --git a/src_old/raspberrypi/disk_image/mmap_file_handle.h b/src_old/raspberrypi/disk_image/mmap_file_handle.h new file mode 100644 index 00000000..3a3ecce9 --- /dev/null +++ b/src_old/raspberrypi/disk_image/mmap_file_handle.h @@ -0,0 +1,49 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2022 akuker +// +// This method of file access will use the mmap() capabilities to 'map' the +// file into memory. +// +// This doesn't actually copy the file into RAM, but instead maps the file +// as virtual memory. Behind the scenes, the file is still being accessed. +// +// The operating system will do some caching of the data to prevent direct +// drive access for each read/write. So, instead of implementing our own +// caching mechanism (like in disk_track_cache.h), we just rely on the +// operating system. +// +// [ MmapFileHandle ] +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "filepath.h" +#include "disk_image/disk_image_handle.h" + +class MmapFileHandle : public DiskImageHandle +{ + +public: + MmapFileHandle(const Filepath &path, int size, uint32_t blocks, off_t imgoff = 0); + ~MmapFileHandle(); + + void SetRawMode(BOOL raw); // CD-ROM raw mode setting + + // Access + bool Save() { return true; }; // Save and release all + bool ReadSector(BYTE *buf, int block); // Sector Read + bool WriteSector(const BYTE *buf, int block); // Sector Write + bool GetCache(int index, int &track, DWORD &serial) const { return true; }; // Get cache information + +private: + const char *memory_block; + struct stat sb; + int fd; + + bool initialized = false; +}; diff --git a/src_old/raspberrypi/disk_image/posix_file_handle.cpp b/src_old/raspberrypi/disk_image/posix_file_handle.cpp new file mode 100644 index 00000000..cf32b653 --- /dev/null +++ b/src_old/raspberrypi/disk_image/posix_file_handle.cpp @@ -0,0 +1,108 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2022 akuker +// +// [ PosixFileHandle ] +// +//--------------------------------------------------------------------------- + +#include "posix_file_handle.h" +#include "log.h" +#include +#include +#include + +//=========================================================================== +// +// Direct file access that will map the file into virtual memory space +// +//=========================================================================== +PosixFileHandle::PosixFileHandle(const Filepath &path, int size, uint32_t blocks, off_t imgoff) : DiskImageHandle(path, size, blocks, imgoff) +{ + ASSERT(blocks > 0); + ASSERT(imgoff >= 0); + + fd = open(path.GetPath(), O_RDWR); + if (fd < 0) + { + LOGWARN("Unable to open file %s. Errno:%d", path.GetPath(), errno) + return; + } + struct stat sb; + if (fstat(fd, &sb) < 0) + { + LOGWARN("Unable to run fstat. Errno:%d", errno); + return; + } + + LOGWARN("%s opened file of size: %llu", __PRETTY_FUNCTION__, (uint64_t)sb.st_size); + + initialized = true; +} + +PosixFileHandle::~PosixFileHandle() +{ + close(fd); + + // Force the OS to save any cached data to the disk + sync(); +} + +bool PosixFileHandle::ReadSector(BYTE *buf, int block) +{ + if (!initialized) + { + return false; + } + + ASSERT(sec_size != 0); + ASSERT(buf); + ASSERT(block < sec_blocks); + ASSERT(memory_block); + + size_t sector_size_bytes = (size_t)1 << sec_size; + + // Calculate offset into the image file + off_t offset = GetTrackOffset(block); + offset += GetSectorOffset(block); + + lseek(fd, offset, SEEK_SET); + size_t result = read(fd, buf, sector_size_bytes); + if (result != sector_size_bytes) + { + LOGWARN("%s only read %d bytes but wanted %d ", __PRETTY_FUNCTION__, result, sector_size_bytes); + } + + return true; +} + +bool PosixFileHandle::WriteSector(const BYTE *buf, int block) +{ + if (!initialized) + { + return false; + } + + ASSERT(buf); + ASSERT(block < sec_blocks); + ASSERT(memory_block); + + ASSERT((block * sec_size) <= (sb.st_size + sec_size)); + + size_t sector_size_bytes = (size_t)1 << sec_size; + + off_t offset = GetTrackOffset(block); + offset += GetSectorOffset(block); + + lseek(fd, offset, SEEK_SET); + size_t result = write(fd, buf, sector_size_bytes); + if (result != sector_size_bytes) + { + LOGWARN("%s only wrote %d bytes but wanted %d ", __PRETTY_FUNCTION__, result, sector_size_bytes); + } + + return true; +} diff --git a/src_old/raspberrypi/disk_image/posix_file_handle.h b/src_old/raspberrypi/disk_image/posix_file_handle.h new file mode 100644 index 00000000..57aa886a --- /dev/null +++ b/src_old/raspberrypi/disk_image/posix_file_handle.h @@ -0,0 +1,35 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2022 akuker +// +// [ PosixFileHandle ] +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "filepath.h" +#include "disk_image/disk_image_handle.h" + +class PosixFileHandle : public DiskImageHandle +{ + +public: + PosixFileHandle(const Filepath &path, int size, uint32_t blocks, off_t imgoff = 0); + ~PosixFileHandle(); + + void SetRawMode(BOOL raw); // CD-ROM raw mode setting + + // Access + bool Save() { return true; }; // Save and release all + bool ReadSector(BYTE *buf, int block); // Sector Read + bool WriteSector(const BYTE *buf, int block); // Sector Write + bool GetCache(int index, int &track, DWORD &serial) const { return true; }; // Get cache information + +private: + int fd; + bool initialized = false; +}; diff --git a/src_old/raspberrypi/exceptions.h b/src_old/raspberrypi/exceptions.h new file mode 100644 index 00000000..e9c7e431 --- /dev/null +++ b/src_old/raspberrypi/exceptions.h @@ -0,0 +1,49 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2021 Uwe Seimet +// +// Various exceptions +// +//--------------------------------------------------------------------------- + +#pragma once + +#include +#include + +using namespace std; + +class illegal_argument_exception final : public exception { +private: + string msg; + +public: + illegal_argument_exception(const string& _msg) : msg(_msg) {} + illegal_argument_exception() {}; + + const string& getmsg() const { + return msg; + } +}; + +class io_exception : public exception { +private: + string msg; + +public: + io_exception(const string& _msg) : msg(_msg) {} + virtual ~io_exception() {} + + const string& getmsg() const { + return msg; + } +}; + +class file_not_found_exception : public io_exception { +public: + file_not_found_exception(const string& msg) : io_exception(msg) {} + ~file_not_found_exception() {} +}; diff --git a/src_old/raspberrypi/fileio.cpp b/src_old/raspberrypi/fileio.cpp new file mode 100644 index 00000000..c6dc70b2 --- /dev/null +++ b/src_old/raspberrypi/fileio.cpp @@ -0,0 +1,241 @@ +//--------------------------------------------------------------------------- +// +// X68000 EMULATOR "XM6" +// +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2010-2020 GIMONS +// [ File I/O (Subset for RaSCSI) ] +// +//--------------------------------------------------------------------------- + +#include "os.h" +#include "filepath.h" +#include "fileio.h" + +#include "config.h" + +//=========================================================================== +// +// File I/O +// +//=========================================================================== + +Fileio::Fileio() +{ + // Initialize work + handle = -1; +} + +Fileio::~Fileio() +{ + ASSERT(handle == -1); + + // Safety measure for Release + Close(); +} + +BOOL Fileio::Load(const Filepath& path, void *buffer, int size) +{ + ASSERT(buffer); + ASSERT(size > 0); + ASSERT(handle < 0); + + if (!Open(path, ReadOnly)) { + return FALSE; + } + + if (!Read(buffer, size)) { + Close(); + return FALSE; + } + + Close(); + + return TRUE; +} + +BOOL Fileio::Save(const Filepath& path, void *buffer, int size) +{ + ASSERT(buffer); + ASSERT(size > 0); + ASSERT(handle < 0); + + if (!Open(path, WriteOnly)) { + return FALSE; + } + + if (!Write(buffer, size)) { + Close(); + return FALSE; + } + + Close(); + + return TRUE; +} + +BOOL Fileio::Open(const char *fname, OpenMode mode, BOOL directIO) +{ + mode_t omode; + + ASSERT(fname); + ASSERT(handle < 0); + + // Always fail a read from a null array + if (fname[0] == _T('\0')) { + handle = -1; + return FALSE; + } + + // Default mode + omode = directIO ? O_DIRECT : 0; + + switch (mode) { + case ReadOnly: + handle = open(fname, O_RDONLY | omode); + break; + + case WriteOnly: + handle = open(fname, O_CREAT | O_WRONLY | O_TRUNC | omode, 0666); + break; + + case ReadWrite: + // Make sure RW does not succeed when reading from CD-ROM + if (access(fname, 0x06) != 0) { + return FALSE; + } + handle = open(fname, O_RDWR | omode); + break; + + default: + ASSERT(FALSE); + break; + } + + // Evaluate results + if (handle == -1) { + return FALSE; + } + + ASSERT(handle >= 0); + return TRUE; +} + +BOOL Fileio::Open(const char *fname, OpenMode mode) +{ + + return Open(fname, mode, FALSE); +} + +BOOL Fileio::Open(const Filepath& path, OpenMode mode) +{ + + return Open(path.GetPath(), mode); +} + +BOOL Fileio::OpenDIO(const char *fname, OpenMode mode) +{ + + // Open with included O_DIRECT + if (!Open(fname, mode, TRUE)) { + // Normal mode retry (tmpfs etc.) + return Open(fname, mode, FALSE); + } + + return TRUE; +} + +BOOL Fileio::OpenDIO(const Filepath& path, OpenMode mode) +{ + + return OpenDIO(path.GetPath(), mode); +} + +BOOL Fileio::Read(void *buffer, int size) +{ + int count; + + ASSERT(buffer); + ASSERT(size > 0); + ASSERT(handle >= 0); + + count = read(handle, buffer, size); + if (count != size) { + return FALSE; + } + + return TRUE; +} + +BOOL Fileio::Write(const void *buffer, int size) +{ + int count; + + ASSERT(buffer); + ASSERT(size > 0); + ASSERT(handle >= 0); + + count = write(handle, buffer, size); + if (count != size) { + return FALSE; + } + + return TRUE; +} + +BOOL Fileio::Seek(off_t offset, BOOL relative) +{ + ASSERT(handle >= 0); + ASSERT(offset >= 0); + + // Add current position in case of relative seek + if (relative) { + offset += GetFilePos(); + } + + if (lseek(handle, offset, SEEK_SET) != offset) { + return FALSE; + } + + return TRUE; +} + +off_t Fileio::GetFileSize() +{ + off_t cur; + off_t end; + + ASSERT(handle >= 0); + + // Get file position in 64bit + cur = GetFilePos(); + + // Get file size in64bitで + end = lseek(handle, 0, SEEK_END); + + // Return to start position + Seek(cur); + + return end; +} + +off_t Fileio::GetFilePos() const +{ + off_t pos; + + ASSERT(handle >= 0); + + // Get file position in 64bit + pos = lseek(handle, 0, SEEK_CUR); + return pos; + +} + +void Fileio::Close() +{ + + if (handle != -1) { + close(handle); + handle = -1; + } +} diff --git a/src_old/raspberrypi/fileio.h b/src_old/raspberrypi/fileio.h new file mode 100644 index 00000000..f0815d08 --- /dev/null +++ b/src_old/raspberrypi/fileio.h @@ -0,0 +1,69 @@ +//--------------------------------------------------------------------------- +// +// X68000 EMULATOR "XM6" +// +// Copyright (C) 2001-2005 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2013-2020 GIMONS +// [ File I/O (Subset for RaSCSI) ] +// +//--------------------------------------------------------------------------- + +#if !defined(fileio_h) +#define fileio_h + +#include "filepath.h" + +//=========================================================================== +// +// Macros (for Load, Save) +// +//=========================================================================== +#define PROP_IMPORT(f, p) \ + if (!f->Read(&(p), sizeof(p))) {\ + return FALSE;\ + }\ + +#define PROP_EXPORT(f, p) \ + if (!f->Write(&(p), sizeof(p))) {\ + return FALSE;\ + }\ + +//=========================================================================== +// +// File I/O +// +//=========================================================================== +class Fileio +{ +public: + enum OpenMode { + ReadOnly, + WriteOnly, + ReadWrite + }; + +public: + Fileio(); + virtual ~Fileio(); + BOOL Load(const Filepath& path, void *buffer, int size); // Load ROM, RAM + BOOL Save(const Filepath& path, void *buffer, int size); // Save RAM + + BOOL Open(const char *fname, OpenMode mode); + BOOL Open(const Filepath& path, OpenMode mode); + BOOL OpenDIO(const char *fname, OpenMode mode); + BOOL OpenDIO(const Filepath& path, OpenMode mode); + BOOL Seek(off_t offset, BOOL relative = FALSE); + BOOL Read(void *buffer, int size); + BOOL Write(const void *buffer, int size); + off_t GetFileSize(); + off_t GetFilePos() const; + void Close(); + BOOL IsValid() const { return (BOOL)(handle != -1); } + +private: + BOOL Open(const char *fname, OpenMode mode, BOOL directIO); + + int handle; // File handle +}; + +#endif // fileio_h diff --git a/src_old/raspberrypi/filepath.cpp b/src_old/raspberrypi/filepath.cpp new file mode 100644 index 00000000..1088bcd7 --- /dev/null +++ b/src_old/raspberrypi/filepath.cpp @@ -0,0 +1,140 @@ +//--------------------------------------------------------------------------- +// +// X68000 EMULATOR "XM6" +// +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2012-2020 GIMONS +// [ File path (subset) ] +// +//--------------------------------------------------------------------------- + +#include "os.h" +#include "filepath.h" +#include "config.h" +#include "fileio.h" + +//=========================================================================== +// +// File path +// +//=========================================================================== + +Filepath::Filepath() +{ + // Clear + Clear(); +} + +Filepath::~Filepath() +{ +} + +Filepath& Filepath::operator=(const Filepath& path) +{ + // Set path (split internally) + SetPath(path.GetPath()); + + return *this; +} + +void Filepath::Clear() +{ + + // Clear the path and each part + m_szPath[0] = _T('\0'); + m_szDir[0] = _T('\0'); + m_szFile[0] = _T('\0'); + m_szExt[0] = _T('\0'); +} + +//--------------------------------------------------------------------------- +// +// File settings (user) for MBCS +// +//--------------------------------------------------------------------------- +void Filepath::SetPath(const char *path) +{ + ASSERT(path); + ASSERT(strlen(path) < _MAX_PATH); + + // Copy pathname + strcpy(m_szPath, (char *)path); + + // Split + Split(); +} + +//--------------------------------------------------------------------------- +// +// Split paths +// +//--------------------------------------------------------------------------- +void Filepath::Split() +{ + // Initialize the parts + m_szDir[0] = _T('\0'); + m_szFile[0] = _T('\0'); + m_szExt[0] = _T('\0'); + + // Split + char *pDir = strdup(m_szPath); + char *pDirName = dirname(pDir); + char *pBase = strdup(m_szPath); + char *pBaseName = basename(pBase); + char *pExtName = strrchr(pBaseName, '.'); + + // Transmit + if (pDirName) { + strcpy(m_szDir, pDirName); + strcat(m_szDir, "/"); + } + + if (pExtName) { + strcpy(m_szExt, pExtName); + } + + if (pBaseName) { + strcpy(m_szFile, pBaseName); + } + + // Release + free(pDir); + free(pBase); +} + +//--------------------------------------------------------------------------- +// +// File name + extension acquisition +// The returned pointer is temporary. Copy immediately. +// +//--------------------------------------------------------------------------- +const char *Filepath::GetFileExt() const +{ + + // Merge into static buffer + strcpy(FileExt, m_szExt); + + // Return as LPCTSTR + return (const char *)FileExt; +} + +BOOL Filepath::Save(Fileio *fio, int /*ver*/) +{ + ASSERT(fio); + + return TRUE; +} + +BOOL Filepath::Load(Fileio *fio, int /*ver*/) +{ + ASSERT(fio); + + return TRUE; +} + +//--------------------------------------------------------------------------- +// +// Filename and extension +// +//--------------------------------------------------------------------------- +TCHAR Filepath::FileExt[_MAX_FNAME + _MAX_DIR]; diff --git a/src_old/raspberrypi/filepath.h b/src_old/raspberrypi/filepath.h new file mode 100644 index 00000000..c8c947e8 --- /dev/null +++ b/src_old/raspberrypi/filepath.h @@ -0,0 +1,52 @@ +//--------------------------------------------------------------------------- +// +// X68000 EMULATOR "XM6" +// +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2012-2020 GIMONS +// [ File path (subset) ] +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "os.h" + +class Fileio; + +//--------------------------------------------------------------------------- +// +// Constant definition +// +//--------------------------------------------------------------------------- +#define FILEPATH_MAX _MAX_PATH + +//=========================================================================== +// +// File path +// Assignment operators are prepared here +// +//=========================================================================== +class Filepath +{ +public: + Filepath(); + virtual ~Filepath(); + Filepath& operator=(const Filepath& path); + + void Clear(); + void SetPath(const char *path); // File settings (user) for MBCS + const char *GetPath() const { return m_szPath; } // Get path name + const char *GetFileExt() const; // Get short name (LPCTSTR) + BOOL Save(Fileio *fio, int ver); + BOOL Load(Fileio *fio, int ver); + +private: + void Split(); // Split the path + TCHAR m_szPath[_MAX_PATH]; // File path + TCHAR m_szDir[_MAX_DIR]; // Directory + TCHAR m_szFile[_MAX_FNAME]; // File + TCHAR m_szExt[_MAX_EXT]; // Extension + + static TCHAR FileExt[_MAX_FNAME + _MAX_DIR]; // Short name (TCHAR) +}; diff --git a/src_old/raspberrypi/gpiobus.cpp b/src_old/raspberrypi/gpiobus.cpp new file mode 100644 index 00000000..6d0b155d --- /dev/null +++ b/src_old/raspberrypi/gpiobus.cpp @@ -0,0 +1,1719 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Powered by XM6 TypeG Technology. +// Copyright (C) 2016-2020 GIMONS +// +// [ GPIO-SCSI bus ] +// +//--------------------------------------------------------------------------- + +#include + +#include "os.h" +#include "gpiobus.h" + +#include "config.h" +#include "log.h" + +#ifdef __linux__ +//--------------------------------------------------------------------------- +// +// imported from bcm_host.c +// +//--------------------------------------------------------------------------- +static DWORD get_dt_ranges(const char *filename, DWORD offset) +{ + DWORD address = ~0; + FILE *fp = fopen(filename, "rb"); + if (fp) { + fseek(fp, offset, SEEK_SET); + BYTE buf[4]; + if (fread(buf, 1, sizeof buf, fp) == sizeof buf) { + address = + buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3] << 0; + } + fclose(fp); + } + return address; +} + +DWORD bcm_host_get_peripheral_address(void) +{ + DWORD address = get_dt_ranges("/proc/device-tree/soc/ranges", 4); + if (address == 0) { + address = get_dt_ranges("/proc/device-tree/soc/ranges", 8); + } + address = (address == (DWORD)~0) ? 0x20000000 : address; +#if 0 + printf("Peripheral address : 0x%lx\n", address); +#endif + return address; +} +#endif // __linux__ + +#ifdef __NetBSD__ +// Assume the Raspberry Pi series and estimate the address from CPU +DWORD bcm_host_get_peripheral_address(void) +{ + char buf[1024]; + size_t len = sizeof(buf); + DWORD address; + + if (sysctlbyname("hw.model", buf, &len, NULL, 0) || + strstr(buf, "ARM1176JZ-S") != buf) { + // Failed to get CPU model || Not BCM2835 + // use the address of BCM283[67] + address = 0x3f000000; + } else { + // Use BCM2835 address + address = 0x20000000; + } + printf("Peripheral address : 0x%lx\n", address); + return address; +} +#endif // __NetBSD__ + +GPIOBUS::GPIOBUS() +{ + actmode = TARGET; + baseaddr = 0; + gicc = 0; + gicd = 0; + gpio = 0; + level = 0; + pads = 0; + irpctl = 0; + qa7regs = 0; + signals = 0; + rpitype = 0; +} + +GPIOBUS::~GPIOBUS() +{ +} + +BOOL GPIOBUS::Init(mode_e mode) +{ +#if defined(__x86_64__) || defined(__X86__) + // When we're running on x86, there is no hardware to talk to, so just return. + return true; +#else + void *map; + int i; + int j; + int pullmode; + int fd; +#ifdef USE_SEL_EVENT_ENABLE + struct epoll_event ev; +#endif // USE_SEL_EVENT_ENABLE + + // Save operation mode + actmode = mode; + + // Get the base address + baseaddr = (DWORD)bcm_host_get_peripheral_address(); + + // Open /dev/mem + fd = open("/dev/mem", O_RDWR | O_SYNC); + if (fd == -1) { + LOGERROR("Error: Unable to open /dev/mem. Are you running as root?"); + return FALSE; + } + + // Map peripheral region memory + map = mmap(NULL, 0x1000100, PROT_READ | PROT_WRITE, MAP_SHARED, fd, baseaddr); + if (map == MAP_FAILED) { + LOGERROR("Error: Unable to map memory"); + close(fd); + return FALSE; + } + + // Determine the type of raspberry pi from the base address + if (baseaddr == 0xfe000000) { + rpitype = 4; + } else if (baseaddr == 0x3f000000) { + rpitype = 2; + } else { + rpitype = 1; + } + + // GPIO + gpio = (DWORD *)map; + gpio += GPIO_OFFSET / sizeof(DWORD); + level = &gpio[GPIO_LEV_0]; + + // PADS + pads = (DWORD *)map; + pads += PADS_OFFSET / sizeof(DWORD); + + // System timer + SysTimer::Init( + (DWORD *)map + SYST_OFFSET / sizeof(DWORD), + (DWORD *)map + ARMT_OFFSET / sizeof(DWORD)); + + // Interrupt controller + irpctl = (DWORD *)map; + irpctl += IRPT_OFFSET / sizeof(DWORD); + + // Quad-A7 control + qa7regs = (DWORD *)map; + qa7regs += QA7_OFFSET / sizeof(DWORD); + + // Map GIC memory + if (rpitype == 4) { + map = mmap(NULL, 8192, + PROT_READ | PROT_WRITE, MAP_SHARED, fd, ARM_GICD_BASE); + if (map == MAP_FAILED) { + close(fd); + return FALSE; + } + gicd = (DWORD *)map; + gicc = (DWORD *)map; + gicc += (ARM_GICC_BASE - ARM_GICD_BASE) / sizeof(DWORD); + } else { + gicd = NULL; + gicc = NULL; + } + close(fd); + + // Set Drive Strength to 16mA + DrvConfig(7); + + // Set pull up/pull down +#if SIGNAL_CONTROL_MODE == 0 + pullmode = GPIO_PULLNONE; +#elif SIGNAL_CONTROL_MODE == 1 + pullmode = GPIO_PULLUP; +#else + pullmode = GPIO_PULLDOWN; +#endif + + // Initialize all signals + for (i = 0; SignalTable[i] >= 0; i++) { + j = SignalTable[i]; + PinSetSignal(j, FALSE); + PinConfig(j, GPIO_INPUT); + PullConfig(j, pullmode); + } + + // Set control signals + PinSetSignal(PIN_ACT, FALSE); + PinSetSignal(PIN_TAD, FALSE); + PinSetSignal(PIN_IND, FALSE); + PinSetSignal(PIN_DTD, FALSE); + PinConfig(PIN_ACT, GPIO_OUTPUT); + PinConfig(PIN_TAD, GPIO_OUTPUT); + PinConfig(PIN_IND, GPIO_OUTPUT); + PinConfig(PIN_DTD, GPIO_OUTPUT); + + // Set the ENABLE signal + // This is used to show that the application is running + PinSetSignal(PIN_ENB, ENB_OFF); + PinConfig(PIN_ENB, GPIO_OUTPUT); + + // GPFSEL backup + gpfsel[0] = gpio[GPIO_FSEL_0]; + gpfsel[1] = gpio[GPIO_FSEL_1]; + gpfsel[2] = gpio[GPIO_FSEL_2]; + gpfsel[3] = gpio[GPIO_FSEL_3]; + + // Initialize SEL signal interrupt +#ifdef USE_SEL_EVENT_ENABLE + // GPIO chip open + fd = open("/dev/gpiochip0", 0); + if (fd == -1) { + LOGERROR("Unable to open /dev/gpiochip0. Is RaSCSI already running?") + return FALSE; + } + + // Event request setting + strcpy(selevreq.consumer_label, "RaSCSI"); + selevreq.lineoffset = PIN_SEL; + selevreq.handleflags = GPIOHANDLE_REQUEST_INPUT; +#if SIGNAL_CONTROL_MODE < 2 + selevreq.eventflags = GPIOEVENT_REQUEST_FALLING_EDGE; +#else + selevreq.eventflags = GPIOEVENT_REQUEST_RISING_EDGE; +#endif // SIGNAL_CONTROL_MODE + + //Get event request + if (ioctl(fd, GPIO_GET_LINEEVENT_IOCTL, &selevreq) == -1) { + LOGERROR("Unable to register event request. Is RaSCSI already running?") + close(fd); + return FALSE; + } + + // Close GPIO chip file handle + close(fd); + + // epoll initialization + epfd = epoll_create(1); + memset(&ev, 0, sizeof(ev)); + ev.events = EPOLLIN | EPOLLPRI; + ev.data.fd = selevreq.fd; + epoll_ctl(epfd, EPOLL_CTL_ADD, selevreq.fd, &ev); +#else + // Edge detection setting +#if SIGNAL_CONTROL_MODE == 2 + gpio[GPIO_AREN_0] = 1 << PIN_SEL; +#else + gpio[GPIO_AFEN_0] = 1 << PIN_SEL; +#endif // SIGNAL_CONTROL_MODE + + // Clear event + gpio[GPIO_EDS_0] = 1 << PIN_SEL; + + // Register interrupt handler + setIrqFuncAddress(IrqHandler); + + // GPIO interrupt setting + if (rpitype == 4) { + // GIC Invalid + gicd[GICD_CTLR] = 0; + + // Route all interupts to core 0 + for (i = 0; i < 8; i++) { + gicd[GICD_ICENABLER0 + i] = 0xffffffff; + gicd[GICD_ICPENDR0 + i] = 0xffffffff; + gicd[GICD_ICACTIVER0 + i] = 0xffffffff; + } + for (i = 0; i < 64; i++) { + gicd[GICD_IPRIORITYR0 + i] = 0xa0a0a0a0; + gicd[GICD_ITARGETSR0 + i] = 0x01010101; + } + + // Set all interrupts as level triggers + for (i = 0; i < 16; i++) { + gicd[GICD_ICFGR0 + i] = 0; + } + + // GIC Invalid + gicd[GICD_CTLR] = 1; + + // Enable CPU interface for core 0 + gicc[GICC_PMR] = 0xf0; + gicc[GICC_CTLR] = 1; + + // Enable interrupts + gicd[GICD_ISENABLER0 + (GIC_GPIO_IRQ / 32)] = + 1 << (GIC_GPIO_IRQ % 32); + } else { + // Enable interrupts + irpctl[IRPT_ENB_IRQ_2] = (1 << (GPIO_IRQ % 32)); + } +#endif // USE_SEL_EVENT_ENABLE + + // Create work table + MakeTable(); + + // Finally, enable ENABLE + // Show the user that this app is running + SetControl(PIN_ENB, ENB_ON); + + return TRUE; +#endif // ifdef __x86_64__ || __X86__ + +} + +void GPIOBUS::Cleanup() +{ +#if defined(__x86_64__) || defined(__X86__) + return; +#else + int i; + int pin; + + // Release SEL signal interrupt +#ifdef USE_SEL_EVENT_ENABLE + close(selevreq.fd); +#endif // USE_SEL_EVENT_ENABLE + + // Set control signals + PinSetSignal(PIN_ENB, FALSE); + PinSetSignal(PIN_ACT, FALSE); + PinSetSignal(PIN_TAD, FALSE); + PinSetSignal(PIN_IND, FALSE); + PinSetSignal(PIN_DTD, FALSE); + PinConfig(PIN_ACT, GPIO_INPUT); + PinConfig(PIN_TAD, GPIO_INPUT); + PinConfig(PIN_IND, GPIO_INPUT); + PinConfig(PIN_DTD, GPIO_INPUT); + + // Initialize all signals + for (i = 0; SignalTable[i] >= 0; i++) { + pin = SignalTable[i]; + PinSetSignal(pin, FALSE); + PinConfig(pin, GPIO_INPUT); + PullConfig(pin, GPIO_PULLNONE); + } + + // Set drive strength back to 8mA + DrvConfig(3); +#endif // ifdef __x86_64__ || __X86__ +} + +void GPIOBUS::Reset() +{ +#if defined(__x86_64__) || defined(__X86__) + return; +#else + int i; + int j; + + // Turn off active signal + SetControl(PIN_ACT, ACT_OFF); + + // Set all signals to off + for (i = 0;; i++) { + j = SignalTable[i]; + if (j < 0) { + break; + } + + SetSignal(j, OFF); + } + + if (actmode == TARGET) { + // Target mode + + // Set target signal to input + SetControl(PIN_TAD, TAD_IN); + SetMode(PIN_BSY, IN); + SetMode(PIN_MSG, IN); + SetMode(PIN_CD, IN); + SetMode(PIN_REQ, IN); + SetMode(PIN_IO, IN); + + // Set the initiator signal to input + SetControl(PIN_IND, IND_IN); + SetMode(PIN_SEL, IN); + SetMode(PIN_ATN, IN); + SetMode(PIN_ACK, IN); + SetMode(PIN_RST, IN); + + // Set data bus signals to input + SetControl(PIN_DTD, DTD_IN); + SetMode(PIN_DT0, IN); + SetMode(PIN_DT1, IN); + SetMode(PIN_DT2, IN); + SetMode(PIN_DT3, IN); + SetMode(PIN_DT4, IN); + SetMode(PIN_DT5, IN); + SetMode(PIN_DT6, IN); + SetMode(PIN_DT7, IN); + SetMode(PIN_DP, IN); + } else { + // Initiator mode + + // Set target signal to input + SetControl(PIN_TAD, TAD_IN); + SetMode(PIN_BSY, IN); + SetMode(PIN_MSG, IN); + SetMode(PIN_CD, IN); + SetMode(PIN_REQ, IN); + SetMode(PIN_IO, IN); + + // Set the initiator signal to output + SetControl(PIN_IND, IND_OUT); + SetMode(PIN_SEL, OUT); + SetMode(PIN_ATN, OUT); + SetMode(PIN_ACK, OUT); + SetMode(PIN_RST, OUT); + + // Set the data bus signals to output + SetControl(PIN_DTD, DTD_OUT); + SetMode(PIN_DT0, OUT); + SetMode(PIN_DT1, OUT); + SetMode(PIN_DT2, OUT); + SetMode(PIN_DT3, OUT); + SetMode(PIN_DT4, OUT); + SetMode(PIN_DT5, OUT); + SetMode(PIN_DT6, OUT); + SetMode(PIN_DT7, OUT); + SetMode(PIN_DP, OUT); + } + + // Initialize all signals + signals = 0; +#endif // ifdef __x86_64__ || __X86__ +} + +//--------------------------------------------------------------------------- +// +// ENB signal setting +// +//--------------------------------------------------------------------------- +void GPIOBUS::SetENB(BOOL ast) +{ + PinSetSignal(PIN_ENB, ast ? ENB_ON : ENB_OFF); +} + +//--------------------------------------------------------------------------- +// +// Get BSY signal +// +//--------------------------------------------------------------------------- +bool GPIOBUS::GetBSY() +{ + return GetSignal(PIN_BSY); +} + +//--------------------------------------------------------------------------- +// +// Set BSY signal +// +//--------------------------------------------------------------------------- +void GPIOBUS::SetBSY(bool ast) +{ + // Set BSY signal + SetSignal(PIN_BSY, ast); + + if (actmode == TARGET) { + if (ast) { + // Turn on ACTIVE signal + SetControl(PIN_ACT, ACT_ON); + + // Set Target signal to output + SetControl(PIN_TAD, TAD_OUT); + + SetMode(PIN_BSY, OUT); + SetMode(PIN_MSG, OUT); + SetMode(PIN_CD, OUT); + SetMode(PIN_REQ, OUT); + SetMode(PIN_IO, OUT); + } else { + // Turn off the ACTIVE signal + SetControl(PIN_ACT, ACT_OFF); + + // Set the target signal to input + SetControl(PIN_TAD, TAD_IN); + + SetMode(PIN_BSY, IN); + SetMode(PIN_MSG, IN); + SetMode(PIN_CD, IN); + SetMode(PIN_REQ, IN); + SetMode(PIN_IO, IN); + } + } +} + +//--------------------------------------------------------------------------- +// +// Get SEL signal +// +//--------------------------------------------------------------------------- +BOOL GPIOBUS::GetSEL() +{ + return GetSignal(PIN_SEL); +} + +//--------------------------------------------------------------------------- +// +// Set SEL signal +// +//--------------------------------------------------------------------------- +void GPIOBUS::SetSEL(BOOL ast) +{ + if (actmode == INITIATOR && ast) { + // Turn on ACTIVE signal + SetControl(PIN_ACT, ACT_ON); + } + + // Set SEL signal + SetSignal(PIN_SEL, ast); +} + +//--------------------------------------------------------------------------- +// +// Get ATN signal +// +//--------------------------------------------------------------------------- +BOOL GPIOBUS::GetATN() +{ + return GetSignal(PIN_ATN); +} + +//--------------------------------------------------------------------------- +// +// Get ATN signal +// +//--------------------------------------------------------------------------- +void GPIOBUS::SetATN(BOOL ast) +{ + SetSignal(PIN_ATN, ast); +} + +//--------------------------------------------------------------------------- +// +// Get ACK signal +// +//--------------------------------------------------------------------------- +BOOL GPIOBUS::GetACK() +{ + return GetSignal(PIN_ACK); +} + +//--------------------------------------------------------------------------- +// +// Set ACK signal +// +//--------------------------------------------------------------------------- +void GPIOBUS::SetACK(BOOL ast) +{ + SetSignal(PIN_ACK, ast); +} + +//--------------------------------------------------------------------------- +// +// Get ACK signal +// +//--------------------------------------------------------------------------- +BOOL GPIOBUS::GetACT() +{ + return GetSignal(PIN_ACT); +} + +//--------------------------------------------------------------------------- +// +// Set ACK signal +// +//--------------------------------------------------------------------------- +void GPIOBUS::SetACT(BOOL ast) +{ + SetSignal(PIN_ACT, ast); +} + +//--------------------------------------------------------------------------- +// +// Get RST signal +// +//--------------------------------------------------------------------------- +BOOL GPIOBUS::GetRST() +{ + return GetSignal(PIN_RST); +} + +//--------------------------------------------------------------------------- +// +// Set RST signal +// +//--------------------------------------------------------------------------- +void GPIOBUS::SetRST(BOOL ast) +{ + SetSignal(PIN_RST, ast); +} + +//--------------------------------------------------------------------------- +// +// Get MSG signal +// +//--------------------------------------------------------------------------- +BOOL GPIOBUS::GetMSG() +{ + return GetSignal(PIN_MSG); +} + +//--------------------------------------------------------------------------- +// +// Set MSG signal +// +//--------------------------------------------------------------------------- +void GPIOBUS::SetMSG(BOOL ast) +{ + SetSignal(PIN_MSG, ast); +} + +//--------------------------------------------------------------------------- +// +// Get CD signal +// +//--------------------------------------------------------------------------- +BOOL GPIOBUS::GetCD() +{ + return GetSignal(PIN_CD); +} + +//--------------------------------------------------------------------------- +// +// Set CD Signal +// +//--------------------------------------------------------------------------- +void GPIOBUS::SetCD(BOOL ast) +{ + SetSignal(PIN_CD, ast); +} + +//--------------------------------------------------------------------------- +// +// Get IO Signal +// +//--------------------------------------------------------------------------- +BOOL GPIOBUS::GetIO() +{ + BOOL ast = GetSignal(PIN_IO); + + if (actmode == INITIATOR) { + // Change the data input/output direction by IO signal + if (ast) { + SetControl(PIN_DTD, DTD_IN); + SetMode(PIN_DT0, IN); + SetMode(PIN_DT1, IN); + SetMode(PIN_DT2, IN); + SetMode(PIN_DT3, IN); + SetMode(PIN_DT4, IN); + SetMode(PIN_DT5, IN); + SetMode(PIN_DT6, IN); + SetMode(PIN_DT7, IN); + SetMode(PIN_DP, IN); + } else { + SetControl(PIN_DTD, DTD_OUT); + SetMode(PIN_DT0, OUT); + SetMode(PIN_DT1, OUT); + SetMode(PIN_DT2, OUT); + SetMode(PIN_DT3, OUT); + SetMode(PIN_DT4, OUT); + SetMode(PIN_DT5, OUT); + SetMode(PIN_DT6, OUT); + SetMode(PIN_DT7, OUT); + SetMode(PIN_DP, OUT); + } + } + + return ast; +} + +//--------------------------------------------------------------------------- +// +// Set IO signal +// +//--------------------------------------------------------------------------- +void GPIOBUS::SetIO(BOOL ast) +{ + SetSignal(PIN_IO, ast); + + if (actmode == TARGET) { + // Change the data input/output direction by IO signal + if (ast) { + SetControl(PIN_DTD, DTD_OUT); + SetDAT(0); + SetMode(PIN_DT0, OUT); + SetMode(PIN_DT1, OUT); + SetMode(PIN_DT2, OUT); + SetMode(PIN_DT3, OUT); + SetMode(PIN_DT4, OUT); + SetMode(PIN_DT5, OUT); + SetMode(PIN_DT6, OUT); + SetMode(PIN_DT7, OUT); + SetMode(PIN_DP, OUT); + } else { + SetControl(PIN_DTD, DTD_IN); + SetMode(PIN_DT0, IN); + SetMode(PIN_DT1, IN); + SetMode(PIN_DT2, IN); + SetMode(PIN_DT3, IN); + SetMode(PIN_DT4, IN); + SetMode(PIN_DT5, IN); + SetMode(PIN_DT6, IN); + SetMode(PIN_DT7, IN); + SetMode(PIN_DP, IN); + } + } +} + +//--------------------------------------------------------------------------- +// +// Get REQ signal +// +//--------------------------------------------------------------------------- +BOOL GPIOBUS::GetREQ() +{ + return GetSignal(PIN_REQ); +} + +//--------------------------------------------------------------------------- +// +// Set REQ signal +// +//--------------------------------------------------------------------------- +void GPIOBUS::SetREQ(BOOL ast) +{ + SetSignal(PIN_REQ, ast); +} + +//--------------------------------------------------------------------------- +// +// Get data signals +// +//--------------------------------------------------------------------------- +BYTE GPIOBUS::GetDAT() +{ + DWORD data = Aquire(); + data = + ((data >> (PIN_DT0 - 0)) & (1 << 0)) | + ((data >> (PIN_DT1 - 1)) & (1 << 1)) | + ((data >> (PIN_DT2 - 2)) & (1 << 2)) | + ((data >> (PIN_DT3 - 3)) & (1 << 3)) | + ((data >> (PIN_DT4 - 4)) & (1 << 4)) | + ((data >> (PIN_DT5 - 5)) & (1 << 5)) | + ((data >> (PIN_DT6 - 6)) & (1 << 6)) | + ((data >> (PIN_DT7 - 7)) & (1 << 7)); + + return (BYTE)data; +} + +//--------------------------------------------------------------------------- +// +// Set data signals +// +//--------------------------------------------------------------------------- +void GPIOBUS::SetDAT(BYTE dat) +{ + // Write to port +#if SIGNAL_CONTROL_MODE == 0 + DWORD fsel = gpfsel[0]; + fsel &= tblDatMsk[0][dat]; + fsel |= tblDatSet[0][dat]; + if (fsel != gpfsel[0]) { + gpfsel[0] = fsel; + gpio[GPIO_FSEL_0] = fsel; + } + + fsel = gpfsel[1]; + fsel &= tblDatMsk[1][dat]; + fsel |= tblDatSet[1][dat]; + if (fsel != gpfsel[1]) { + gpfsel[1] = fsel; + gpio[GPIO_FSEL_1] = fsel; + } + + fsel = gpfsel[2]; + fsel &= tblDatMsk[2][dat]; + fsel |= tblDatSet[2][dat]; + if (fsel != gpfsel[2]) { + gpfsel[2] = fsel; + gpio[GPIO_FSEL_2] = fsel; + } +#else + gpio[GPIO_CLR_0] = tblDatMsk[dat]; + gpio[GPIO_SET_0] = tblDatSet[dat]; +#endif // SIGNAL_CONTROL_MODE +} + +//--------------------------------------------------------------------------- +// +// Get data parity signal +// +//--------------------------------------------------------------------------- +BOOL GPIOBUS::GetDP() +{ + return GetSignal(PIN_DP); +} + +//--------------------------------------------------------------------------- +// +// Receive command handshake +// +//--------------------------------------------------------------------------- +int GPIOBUS::CommandHandShake(BYTE *buf) +{ + int count; + + // Only works in TARGET mode + if (actmode != TARGET) { + return 0; + } + + // IRQs disabled + DisableIRQ(); + + // Get the first command byte + int i = 0; + + // Assert REQ signal + SetSignal(PIN_REQ, ON); + + // Wait for ACK signal + BOOL ret = WaitSignal(PIN_ACK, TRUE); + + // Wait until the signal line stabilizes + SysTimer::SleepNsec(SCSI_DELAY_BUS_SETTLE_DELAY_NS); + + // Get data + *buf = GetDAT(); + + // Disable REQ signal + SetSignal(PIN_REQ, OFF); + + // Timeout waiting for ACK assertion + if (!ret) { + goto irq_enable_exit; + } + + // Wait for ACK to clear + ret = WaitSignal(PIN_ACK, FALSE); + + // Timeout waiting for ACK to clear + if (!ret) { + goto irq_enable_exit; + } + + // The ICD AdSCSI ST, AdSCSI Plus ST and AdSCSI Micro ST host adapters allow SCSI devices to be connected + // to the ACSI bus of Atari ST/TT computers and some clones. ICD-aware drivers prepend a $1F byte in front + // of the CDB (effectively resulting in a custom SCSI command) in order to get access to the full SCSI + // command set. Native ACSI is limited to the low SCSI command classes with command bytes < $20. + // Most other host adapters (e.g. LINK96/97 and the one by Inventronik) and also several devices (e.g. + // UltraSatan or GigaFile) that can directly be connected to the Atari's ACSI port also support ICD + // semantics. I fact, these semantics have become a standard in the Atari world. + + // RaSCSI becomes ICD compatible by ignoring the prepended $1F byte before processing the CDB. + if (*buf == 0x1F) { + SetSignal(PIN_REQ, ON); + + ret = WaitSignal(PIN_ACK, TRUE); + + SysTimer::SleepNsec(SCSI_DELAY_BUS_SETTLE_DELAY_NS); + + // Get the actual SCSI command + *buf = GetDAT(); + + SetSignal(PIN_REQ, OFF); + + if (!ret) { + goto irq_enable_exit; + } + + WaitSignal(PIN_ACK, FALSE); + + if (!ret) { + goto irq_enable_exit; + } + } + + count = GetCommandByteCount(*buf); + + // Increment buffer pointer + buf++; + + for (i = 1; i < count; i++) { + // Assert REQ signal + SetSignal(PIN_REQ, ON); + + // Wait for ACK signal + ret = WaitSignal(PIN_ACK, TRUE); + + // Wait until the signal line stabilizes + SysTimer::SleepNsec(SCSI_DELAY_BUS_SETTLE_DELAY_NS); + + // Get data + *buf = GetDAT(); + + // Clear the REQ signal + SetSignal(PIN_REQ, OFF); + + // Check for timeout waiting for ACK assertion + if (!ret) { + break; + } + + // Wait for ACK to clear + ret = WaitSignal(PIN_ACK, FALSE); + + // Check for timeout waiting for ACK to clear + if (!ret) { + break; + } + + // Advance the buffer pointer to receive the next byte + buf++; + } + +irq_enable_exit: + // IRQs enabled + EnableIRQ(); + + // returned the number of bytes received + return i; +} + +//--------------------------------------------------------------------------- +// +// Data reception handshake +// +//--------------------------------------------------------------------------- +int GPIOBUS::ReceiveHandShake(BYTE *buf, int count) +{ + int i; + BOOL ret; + DWORD phase; + + // Disable IRQs + DisableIRQ(); + + if (actmode == TARGET) { + for (i = 0; i < count; i++) { + // Assert the REQ signal + SetSignal(PIN_REQ, ON); + + // Wait for ACK + ret = WaitSignal(PIN_ACK, TRUE); + + // Wait until the signal line stabilizes + SysTimer::SleepNsec(SCSI_DELAY_BUS_SETTLE_DELAY_NS); + + // Get data + *buf = GetDAT(); + + // Clear the REQ signal + SetSignal(PIN_REQ, OFF); + + // Check for timeout waiting for ACK signal + if (!ret) { + break; + } + + // Wait for ACK to clear + ret = WaitSignal(PIN_ACK, FALSE); + + // Check for timeout waiting for ACK to clear + if (!ret) { + break; + } + + // Advance the buffer pointer to receive the next byte + buf++; + } + } else { + // Get phase + phase = Aquire() & GPIO_MCI; + + for (i = 0; i < count; i++) { + // Wait for the REQ signal to be asserted + ret = WaitSignal(PIN_REQ, TRUE); + + // Check for timeout waiting for REQ signal + if (!ret) { + break; + } + + // Phase error + if ((signals & GPIO_MCI) != phase) { + break; + } + + // Wait until the signal line stabilizes + SysTimer::SleepNsec(SCSI_DELAY_BUS_SETTLE_DELAY_NS); + + // Get data + *buf = GetDAT(); + + // Assert the ACK signal + SetSignal(PIN_ACK, ON); + + // Wait for REQ to clear + ret = WaitSignal(PIN_REQ, FALSE); + + // Clear the ACK signal + SetSignal(PIN_ACK, OFF); + + // Check for timeout waiting for REQ to clear + if (!ret) { + break; + } + + // Phase error + if ((signals & GPIO_MCI) != phase) { + break; + } + + // Advance the buffer pointer to receive the next byte + buf++; + } + } + + // Re-enable IRQ + EnableIRQ(); + + // Return the number of bytes received + return i; +} + +//--------------------------------------------------------------------------- +// +// Data transmission handshake +// +//--------------------------------------------------------------------------- +int GPIOBUS::SendHandShake(BYTE *buf, int count, int delay_after_bytes) +{ + int i; + + // Disable IRQs + DisableIRQ(); + + if (actmode == TARGET) { + for (i = 0; i < count; i++) { + if(i==delay_after_bytes){ + LOGTRACE("%s DELAYING for %dus after %d bytes", __PRETTY_FUNCTION__, SCSI_DELAY_SEND_DATA_DAYNAPORT_US, (int)delay_after_bytes); + SysTimer::SleepUsec(SCSI_DELAY_SEND_DATA_DAYNAPORT_US); + } + + // Set the DATA signals + SetDAT(*buf); + + // Wait for ACK to clear + BOOL ret = WaitSignal(PIN_ACK, FALSE); + + // Check for timeout waiting for ACK to clear + if (!ret) { + break; + } + + // Already waiting for ACK to clear + + // Assert the REQ signal + SetSignal(PIN_REQ, ON); + + // Wait for ACK + ret = WaitSignal(PIN_ACK, TRUE); + + // Clear REQ signal + SetSignal(PIN_REQ, OFF); + + // Check for timeout waiting for ACK to clear + if (!ret) { + break; + } + + // Advance the data buffer pointer to receive the next byte + buf++; + } + + // Wait for ACK to clear + WaitSignal(PIN_ACK, FALSE); + } else { + // Get Phase + DWORD phase = Aquire() & GPIO_MCI; + + for (i = 0; i < count; i++) { + if(i==delay_after_bytes){ + LOGTRACE("%s DELAYING for %dus after %d bytes", __PRETTY_FUNCTION__, SCSI_DELAY_SEND_DATA_DAYNAPORT_US, (int)delay_after_bytes); + SysTimer::SleepUsec(SCSI_DELAY_SEND_DATA_DAYNAPORT_US); + } + + // Set the DATA signals + SetDAT(*buf); + + // Wait for REQ to be asserted + BOOL ret = WaitSignal(PIN_REQ, TRUE); + + // Check for timeout waiting for REQ to be asserted + if (!ret) { + break; + } + + // Phase error + if ((signals & GPIO_MCI) != phase) { + break; + } + + // Already waiting for REQ assertion + + // Assert the ACK signal + SetSignal(PIN_ACK, ON); + + // Wait for REQ to clear + ret = WaitSignal(PIN_REQ, FALSE); + + // Clear the ACK signal + SetSignal(PIN_ACK, OFF); + + // Check for timeout waiting for REQ to clear + if (!ret) { + break; + } + + // Phase error + if ((signals & GPIO_MCI) != phase) { + break; + } + + // Advance the data buffer pointer to receive the next byte + buf++; + } + } + + // Re-enable IRQ + EnableIRQ(); + + // Return number of transmissions + return i; +} + +#ifdef USE_SEL_EVENT_ENABLE +//--------------------------------------------------------------------------- +// +// SEL signal event polling +// +//--------------------------------------------------------------------------- +int GPIOBUS::PollSelectEvent() +{ + // clear errno + errno = 0; + struct epoll_event epev; + struct gpioevent_data gpev; + + if (epoll_wait(epfd, &epev, 1, -1) <= 0) { + LOGWARN("%s epoll_wait failed", __PRETTY_FUNCTION__); + return -1; + } + + if (read(selevreq.fd, &gpev, sizeof(gpev)) < 0) { + LOGWARN("%s read failed", __PRETTY_FUNCTION__); + return -1; + } + + return 0; +} + +//--------------------------------------------------------------------------- +// +// Cancel SEL signal event +// +//--------------------------------------------------------------------------- +void GPIOBUS::ClearSelectEvent() +{ +} +#endif // USE_SEL_EVENT_ENABLE + +//--------------------------------------------------------------------------- +// +// Signal table +// +//--------------------------------------------------------------------------- +const int GPIOBUS::SignalTable[19] = { + PIN_DT0, PIN_DT1, PIN_DT2, PIN_DT3, + PIN_DT4, PIN_DT5, PIN_DT6, PIN_DT7, PIN_DP, + PIN_SEL,PIN_ATN, PIN_RST, PIN_ACK, + PIN_BSY, PIN_MSG, PIN_CD, PIN_IO, PIN_REQ, + -1 +}; + +//--------------------------------------------------------------------------- +// +// Create work table +// +//--------------------------------------------------------------------------- +void GPIOBUS::MakeTable(void) +{ + const int pintbl[] = { + PIN_DT0, PIN_DT1, PIN_DT2, PIN_DT3, PIN_DT4, + PIN_DT5, PIN_DT6, PIN_DT7, PIN_DP + }; + + int i; + int j; + BOOL tblParity[256]; +#if SIGNAL_CONTROL_MODE == 0 + int index; + int shift; +#else + DWORD gpclr; + DWORD gpset; +#endif + + // Create parity table + for (i = 0; i < 0x100; i++) { + DWORD bits = (DWORD)i; + DWORD parity = 0; + for (j = 0; j < 8; j++) { + parity ^= bits & 1; + bits >>= 1; + } + parity = ~parity; + tblParity[i] = parity & 1; + } + +#if SIGNAL_CONTROL_MODE == 0 + // Mask and setting data generation + memset(tblDatMsk, 0xff, sizeof(tblDatMsk)); + memset(tblDatSet, 0x00, sizeof(tblDatSet)); + for (i = 0; i < 0x100; i++) { + // Bit string for inspection + DWORD bits = (DWORD)i; + + // Get parity + if (tblParity[i]) { + bits |= (1 << 8); + } + + // Bit check + for (j = 0; j < 9; j++) { + // Index and shift amount calculation + index = pintbl[j] / 10; + shift = (pintbl[j] % 10) * 3; + + // Mask data + tblDatMsk[index][i] &= ~(0x7 << shift); + + // Setting data + if (bits & 1) { + tblDatSet[index][i] |= (1 << shift); + } + + bits >>= 1; + } + } +#else + // Mask and setting data generation + memset(tblDatMsk, 0x00, sizeof(tblDatMsk)); + memset(tblDatSet, 0x00, sizeof(tblDatSet)); + for (i = 0; i < 0x100; i++) { + // bit string for inspection + DWORD bits = (DWORD)i; + + // get parity + if (tblParity[i]) { + bits |= (1 << 8); + } + +#if SIGNAL_CONTROL_MODE == 1 + // Negative logic is inverted + bits = ~bits; +#endif + + // Create GPIO register information + gpclr = 0; + gpset = 0; + for (j = 0; j < 9; j++) { + if (bits & 1) { + gpset |= (1 << pintbl[j]); + } else { + gpclr |= (1 << pintbl[j]); + } + bits >>= 1; + } + + tblDatMsk[i] = gpclr; + tblDatSet[i] = gpset; + } +#endif +} + +//--------------------------------------------------------------------------- +// +// Control signal setting +// +//--------------------------------------------------------------------------- +void GPIOBUS::SetControl(int pin, BOOL ast) +{ + PinSetSignal(pin, ast); +} + +//--------------------------------------------------------------------------- +// +// Input/output mode setting +// +//--------------------------------------------------------------------------- +void GPIOBUS::SetMode(int pin, int mode) +{ +#if SIGNAL_CONTROL_MODE == 0 + if (mode == OUT) { + return; + } +#endif // SIGNAL_CONTROL_MODE + + int index = pin / 10; + int shift = (pin % 10) * 3; + DWORD data = gpfsel[index]; + data &= ~(0x7 << shift); + if (mode == OUT) { + data |= (1 << shift); + } + gpio[index] = data; + gpfsel[index] = data; +} + +//--------------------------------------------------------------------------- +// +// Get input signal value +// +//--------------------------------------------------------------------------- +BOOL GPIOBUS::GetSignal(int pin) +{ + return (signals >> pin) & 1; +} + +//--------------------------------------------------------------------------- +// +// Set output signal value +// +//--------------------------------------------------------------------------- +void GPIOBUS::SetSignal(int pin, BOOL ast) +{ +#if SIGNAL_CONTROL_MODE == 0 + int index = pin / 10; + int shift = (pin % 10) * 3; + DWORD data = gpfsel[index]; + if (ast) { + data |= (1 << shift); + } else { + data &= ~(0x7 << shift); + } + gpio[index] = data; + gpfsel[index] = data; +#elif SIGNAL_CONTROL_MODE == 1 + if (ast) { + gpio[GPIO_CLR_0] = 0x1 << pin; + } else { + gpio[GPIO_SET_0] = 0x1 << pin; + } +#elif SIGNAL_CONTROL_MODE == 2 + if (ast) { + gpio[GPIO_SET_0] = 0x1 << pin; + } else { + gpio[GPIO_CLR_0] = 0x1 << pin; + } +#endif // SIGNAL_CONTROL_MODE +} + +//--------------------------------------------------------------------------- +// +// Wait for signal change +// +//--------------------------------------------------------------------------- +BOOL GPIOBUS::WaitSignal(int pin, BOOL ast) +{ + // Get current time + DWORD now = SysTimer::GetTimerLow(); + + // Calculate timeout (3000ms) + DWORD timeout = 3000 * 1000; + + // end immediately if the signal has changed + do { + // Immediately upon receiving a reset + Aquire(); + if (GetRST()) { + return FALSE; + } + + // Check for the signal edge + if (((signals >> pin) ^ ~ast) & 1) { + return TRUE; + } + } while ((SysTimer::GetTimerLow() - now) < timeout); + + // We timed out waiting for the signal + return FALSE; +} + +//--------------------------------------------------------------------------- +// +// Disable IRQ +// +//--------------------------------------------------------------------------- +void GPIOBUS::DisableIRQ() +{ +#ifdef __linux__ + if (rpitype == 4) { + // RPI4 is disabled by GICC + giccpmr = gicc[GICC_PMR]; + gicc[GICC_PMR] = 0; + } else if (rpitype == 2) { + // RPI2,3 disable core timer IRQ + tintcore = sched_getcpu() + QA7_CORE0_TINTC; + tintctl = qa7regs[tintcore]; + qa7regs[tintcore] = 0; + } else { + // Stop system timer interrupt with interrupt controller + irptenb = irpctl[IRPT_ENB_IRQ_1]; + irpctl[IRPT_DIS_IRQ_1] = irptenb & 0xf; + } +#else + (void)0; +#endif +} + +//--------------------------------------------------------------------------- +// +// Enable IRQ +// +//--------------------------------------------------------------------------- +void GPIOBUS::EnableIRQ() +{ + if (rpitype == 4) { + // RPI4 enables interrupts via the GICC + gicc[GICC_PMR] = giccpmr; + } else if (rpitype == 2) { + // RPI2,3 re-enable core timer IRQ + qa7regs[tintcore] = tintctl; + } else { + // Restart the system timer interrupt with the interrupt controller + irpctl[IRPT_ENB_IRQ_1] = irptenb & 0xf; + } +} + +//--------------------------------------------------------------------------- +// +// Pin direction setting (input/output) +// +//--------------------------------------------------------------------------- +void GPIOBUS::PinConfig(int pin, int mode) +{ + // Check for invalid pin + if (pin < 0) { + return; + } + + int index = pin / 10; + DWORD mask = ~(0x7 << ((pin % 10) * 3)); + gpio[index] = (gpio[index] & mask) | ((mode & 0x7) << ((pin % 10) * 3)); +} + +//--------------------------------------------------------------------------- +// +// Pin pull-up/pull-down setting +// +//--------------------------------------------------------------------------- +void GPIOBUS::PullConfig(int pin, int mode) +{ + DWORD pull; + + // Check for invalid pin + if (pin < 0) { + return; + } + + if (rpitype == 4) { + switch (mode) { + case GPIO_PULLNONE: + pull = 0; + break; + case GPIO_PULLUP: + pull = 1; + break; + case GPIO_PULLDOWN: + pull = 2; + break; + default: + return; + } + + pin &= 0x1f; + int shift = (pin & 0xf) << 1; + DWORD bits = gpio[GPIO_PUPPDN0 + (pin >> 4)]; + bits &= ~(3 << shift); + bits |= (pull << shift); + gpio[GPIO_PUPPDN0 + (pin >> 4)] = bits; + } else { + pin &= 0x1f; + gpio[GPIO_PUD] = mode & 0x3; + SysTimer::SleepUsec(2); + gpio[GPIO_CLK_0] = 0x1 << pin; + SysTimer::SleepUsec(2); + gpio[GPIO_PUD] = 0; + gpio[GPIO_CLK_0] = 0; + } +} + +//--------------------------------------------------------------------------- +// +// Set output pin +// +//--------------------------------------------------------------------------- +void GPIOBUS::PinSetSignal(int pin, BOOL ast) +{ + // Check for invalid pin + if (pin < 0) { + return; + } + + if (ast) { + gpio[GPIO_SET_0] = 0x1 << pin; + } else { + gpio[GPIO_CLR_0] = 0x1 << pin; + } +} + +//--------------------------------------------------------------------------- +// +// Set the signal drive strength +// +//--------------------------------------------------------------------------- +void GPIOBUS::DrvConfig(DWORD drive) +{ + DWORD data = pads[PAD_0_27]; + pads[PAD_0_27] = (0xFFFFFFF8 & data) | drive | 0x5a000000; +} + + +//--------------------------------------------------------------------------- +// +// Generic Phase Acquisition (Doesn't read GPIO) +// +//--------------------------------------------------------------------------- +BUS::phase_t GPIOBUS::GetPhaseRaw(DWORD raw_data) +{ + // Selection Phase + if (GetPinRaw(raw_data, PIN_SEL)) + { + if(GetPinRaw(raw_data, PIN_IO)){ + return BUS::reselection; + }else{ + return BUS::selection; + } + } + + // Bus busy phase + if (!GetPinRaw(raw_data, PIN_BSY)) { + return BUS::busfree; + } + + // Get target phase from bus signal line + DWORD mci = GetPinRaw(raw_data, PIN_MSG) ? 0x04 : 0x00; + mci |= GetPinRaw(raw_data, PIN_CD) ? 0x02 : 0x00; + mci |= GetPinRaw(raw_data, PIN_IO) ? 0x01 : 0x00; + return GetPhase(mci); +} + +//--------------------------------------------------------------------------- +// +// Get the number of bytes for a command +// +// TODO The command length should be determined based on the bytes transferred in the COMMAND phase +// +//--------------------------------------------------------------------------- +int GPIOBUS::GetCommandByteCount(BYTE opcode) { + if (opcode == 0x88 || opcode == 0x8A || opcode == 0x8F || opcode == 0x91 || opcode == 0x9E || opcode == 0x9F) { + return 16; + } + else if (opcode == 0xA0) { + return 12; + } + else if (opcode >= 0x20 && opcode <= 0x7D) { + return 10; + } else { + return 6; + } +} + +//--------------------------------------------------------------------------- +// +// System timer address +// +//--------------------------------------------------------------------------- +volatile DWORD* SysTimer::systaddr; + +//--------------------------------------------------------------------------- +// +// ARM timer address +// +//--------------------------------------------------------------------------- +volatile DWORD* SysTimer::armtaddr; + +//--------------------------------------------------------------------------- +// +// Core frequency +// +//--------------------------------------------------------------------------- +volatile DWORD SysTimer::corefreq; + +//--------------------------------------------------------------------------- +// +// Initialize the system timer +// +//--------------------------------------------------------------------------- +void SysTimer::Init(DWORD *syst, DWORD *armt) +{ + // RPI Mailbox property interface + // Get max clock rate + // Tag: 0x00030004 + // + // Request: Length: 4 + // Value: u32: clock id + // Response: Length: 8 + // Value: u32: clock id, u32: rate (in Hz) + // + // Clock id + // 0x000000004: CORE + DWORD maxclock[32] = { 32, 0, 0x00030004, 8, 0, 4, 0, 0 }; + + // Save the base address + systaddr = syst; + armtaddr = armt; + + // Change the ARM timer to free run mode + armtaddr[ARMT_CTRL] = 0x00000282; + + // Get the core frequency + corefreq = 0; + int fd = open("/dev/vcio", O_RDONLY); + if (fd >= 0) { + ioctl(fd, _IOWR(100, 0, char *), maxclock); + corefreq = maxclock[6] / 1000000; + } + close(fd); +} + +//--------------------------------------------------------------------------- +// +// Get system timer low byte +// +//--------------------------------------------------------------------------- +DWORD SysTimer::GetTimerLow() { + return systaddr[SYST_CLO]; +} + +//--------------------------------------------------------------------------- +// +// Get system timer high byte +// +//--------------------------------------------------------------------------- +DWORD SysTimer::GetTimerHigh() { + return systaddr[SYST_CHI]; +} + +//--------------------------------------------------------------------------- +// +// Sleep in nanoseconds +// +//--------------------------------------------------------------------------- +void SysTimer::SleepNsec(DWORD nsec) +{ + // If time is 0, don't do anything + if (nsec == 0) { + return; + } + + // Calculate the timer difference + DWORD diff = corefreq * nsec / 1000; + + // Return if the difference in time is too small + if (diff == 0) { + return; + } + + // Start + DWORD start = armtaddr[ARMT_FREERUN]; + + // Loop until timer has elapsed + while ((armtaddr[ARMT_FREERUN] - start) < diff); +} + +//--------------------------------------------------------------------------- +// +// Sleep in microseconds +// +//--------------------------------------------------------------------------- +void SysTimer::SleepUsec(DWORD usec) +{ + // If time is 0, don't do anything + if (usec == 0) { + return; + } + + DWORD now = GetTimerLow(); + while ((GetTimerLow() - now) < usec); +} diff --git a/src_old/raspberrypi/gpiobus.h b/src_old/raspberrypi/gpiobus.h new file mode 100644 index 00000000..622cc493 --- /dev/null +++ b/src_old/raspberrypi/gpiobus.h @@ -0,0 +1,698 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Powered by XM6 TypeG Technology. +// Copyright (C) 2016-2020 GIMONS +// [ GPIO-SCSI bus ] +// +//--------------------------------------------------------------------------- + +#if !defined(gpiobus_h) +#define gpiobus_h + +#include "config.h" +#include "scsi.h" + +//--------------------------------------------------------------------------- +// +// Connection method definitions +// +//--------------------------------------------------------------------------- +//#define CONNECT_TYPE_STANDARD // Standard (SCSI logic, standard pin assignment) +//#define CONNECT_TYPE_FULLSPEC // Full spec (SCSI logic, standard pin assignment) +//#define CONNECT_TYPE_AIBOM // AIBOM version (positive logic, unique pin assignment) +//#define CONNECT_TYPE_GAMERNIUM // GAMERnium.com version (standard logic, unique pin assignment) + +//--------------------------------------------------------------------------- +// +// Signal control logic and pin assignment customization +// +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +// +// SIGNAL_CONTROL_MODE: Signal control mode selection +// You can customize the signal control logic from Version 1.22 +// +// 0:SCSI logical specification +// Conversion board using 74LS641-1 etc. directly connected or published on HP +// True : 0V +// False : Open collector output (disconnect from bus) +// +// 1:Negative logic specification (when using conversion board for negative logic -> SCSI logic) +// There is no conversion board with this specification at this time +// True : 0V -> (CONVERT) -> 0V +// False : 3.3V -> (CONVERT) -> Open collector output +// +// 2:Positive logic specification (when using the conversion board for positive logic -> SCSI logic) +// RaSCSI Adapter Rev.C @132sync etc. +// +// True : 3.3V -> (CONVERT) -> 0V +// False : 0V -> (CONVERT) -> Open collector output +// +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +// +// Control signal pin assignment setting +// GPIO pin mapping table for control signals. +// +// Control signal: +// PIN_ACT +// Signal that indicates the status of processing SCSI command. +// PIN_ENB +// Signal that indicates the valid signal from start to finish. +// PIN_TAD +// Signal that indicates the input/output direction of the target signal (BSY,IO,CD,MSG,REG). +// PIN_IND +// Signal that indicates the input/output direction of the initiator signal (SEL, ATN, RST, ACK). +// PIN_DTD +// Signal that indicates the input/output direction of the data lines (DT0...DT7,DP). +// +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +// +// Control signal output logic +// 0V:FALSE 3.3V:TRUE +// +// ACT_ON +// PIN_ACT signal +// ENB_ON +// PIN_ENB signal +// TAD_IN +// PIN_TAD This is the logic when inputting. +// IND_IN +// PIN_ENB This is the logic when inputting. +// DTD_IN +// PIN_ENB This is the logic when inputting. +// +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +// +// SCSI signal pin assignment setting +// GPIO pin mapping table for SCSI signals. +// PIN_DT0~PIN_SEL +// +//--------------------------------------------------------------------------- + +#ifdef CONNECT_TYPE_STANDARD +// +// RaSCSI standard (SCSI logic, standard pin assignment) +// +#define CONNECT_DESC "STANDARD" // Startup message + +// Select signal control mode +#define SIGNAL_CONTROL_MODE 0 // SCSI logical specification + +// Control signal pin assignment (-1 means no control) +#define PIN_ACT 4 // ACTIVE +#define PIN_ENB 5 // ENABLE +#define PIN_IND -1 // INITIATOR CTRL DIRECTION +#define PIN_TAD -1 // TARGET CTRL DIRECTION +#define PIN_DTD -1 // DATA DIRECTION + +// Control signal output logic +#define ACT_ON TRUE // ACTIVE SIGNAL ON +#define ENB_ON TRUE // ENABLE SIGNAL ON +#define IND_IN FALSE // INITIATOR SIGNAL INPUT +#define TAD_IN FALSE // TARGET SIGNAL INPUT +#define DTD_IN TRUE // DATA SIGNAL INPUT + +// SCSI signal pin assignment +#define PIN_DT0 10 // Data 0 +#define PIN_DT1 11 // Data 1 +#define PIN_DT2 12 // Data 2 +#define PIN_DT3 13 // Data 3 +#define PIN_DT4 14 // Data 4 +#define PIN_DT5 15 // Data 5 +#define PIN_DT6 16 // Data 6 +#define PIN_DT7 17 // Data 7 +#define PIN_DP 18 // Data parity +#define PIN_ATN 19 // ATN +#define PIN_RST 20 // RST +#define PIN_ACK 21 // ACK +#define PIN_REQ 22 // REQ +#define PIN_MSG 23 // MSG +#define PIN_CD 24 // CD +#define PIN_IO 25 // IO +#define PIN_BSY 26 // BSY +#define PIN_SEL 27 // SEL +#endif + +#ifdef CONNECT_TYPE_FULLSPEC +// +// RaSCSI standard (SCSI logic, standard pin assignment) +// +#define CONNECT_DESC "FULLSPEC" // Startup message + +// Select signal control mode +#define SIGNAL_CONTROL_MODE 0 // SCSI logical specification + +// Control signal pin assignment (-1 means no control) +#define PIN_ACT 4 // ACTIVE +#define PIN_ENB 5 // ENABLE +#define PIN_IND 6 // INITIATOR CTRL DIRECTION +#define PIN_TAD 7 // TARGET CTRL DIRECTION +#define PIN_DTD 8 // DATA DIRECTION + +// Control signal output logic +#define ACT_ON TRUE // ACTIVE SIGNAL ON +#define ENB_ON TRUE // ENABLE SIGNAL ON +#define IND_IN FALSE // INITIATOR SIGNAL INPUT +#define TAD_IN FALSE // TARGET SIGNAL INPUT +#define DTD_IN TRUE // DATA SIGNAL INPUT + +// SCSI signal pin assignment +#define PIN_DT0 10 // Data 0 +#define PIN_DT1 11 // Data 1 +#define PIN_DT2 12 // Data 2 +#define PIN_DT3 13 // Data 3 +#define PIN_DT4 14 // Data 4 +#define PIN_DT5 15 // Data 5 +#define PIN_DT6 16 // Data 6 +#define PIN_DT7 17 // Data 7 +#define PIN_DP 18 // Data parity +#define PIN_ATN 19 // ATN +#define PIN_RST 20 // RST +#define PIN_ACK 21 // ACK +#define PIN_REQ 22 // REQ +#define PIN_MSG 23 // MSG +#define PIN_CD 24 // CD +#define PIN_IO 25 // IO +#define PIN_BSY 26 // BSY +#define PIN_SEL 27 // SEL +#endif + +#ifdef CONNECT_TYPE_AIBOM +// +// RaSCSI Adapter Aibom version +// + +#define CONNECT_DESC "AIBOM PRODUCTS version" // Startup message + +// Select signal control mode +#define SIGNAL_CONTROL_MODE 2 // SCSI positive logic specification + +// Control signal output logic +#define ACT_ON TRUE // ACTIVE SIGNAL ON +#define ENB_ON TRUE // ENABLE SIGNAL ON +#define IND_IN FALSE // INITIATOR SIGNAL INPUT +#define TAD_IN FALSE // TARGET SIGNAL INPUT +#define DTD_IN FALSE // DATA SIGNAL INPUT + +// Control signal pin assignment (-1 means no control) +#define PIN_ACT 4 // ACTIVE +#define PIN_ENB 17 // ENABLE +#define PIN_IND 27 // INITIATOR CTRL DIRECTION +#define PIN_TAD -1 // TARGET CTRL DIRECTION +#define PIN_DTD 18 // DATA DIRECTION + +// SCSI signal pin assignment +#define PIN_DT0 6 // Data 0 +#define PIN_DT1 12 // Data 1 +#define PIN_DT2 13 // Data 2 +#define PIN_DT3 16 // Data 3 +#define PIN_DT4 19 // Data 4 +#define PIN_DT5 20 // Data 5 +#define PIN_DT6 26 // Data 6 +#define PIN_DT7 21 // Data 7 +#define PIN_DP 5 // Data parity +#define PIN_ATN 22 // ATN +#define PIN_RST 25 // RST +#define PIN_ACK 10 // ACK +#define PIN_REQ 7 // REQ +#define PIN_MSG 9 // MSG +#define PIN_CD 11 // CD +#define PIN_IO 23 // IO +#define PIN_BSY 24 // BSY +#define PIN_SEL 8 // SEL +#endif + +#ifdef CONNECT_TYPE_GAMERNIUM +// +// RaSCSI Adapter GAMERnium.com version +// + +#define CONNECT_DESC "GAMERnium.com version"// Startup message + +// Select signal control mode +#define SIGNAL_CONTROL_MODE 0 // SCSI logical specification + +// Control signal output logic +#define ACT_ON TRUE // ACTIVE SIGNAL ON +#define ENB_ON TRUE // ENABLE SIGNAL ON +#define IND_IN FALSE // INITIATOR SIGNAL INPUT +#define TAD_IN FALSE // TARGET SIGNAL INPUT +#define DTD_IN TRUE // DATA SIGNAL INPUT + +// Control signal pin assignment (-1 means no control) +#define PIN_ACT 14 // ACTIVE +#define PIN_ENB 6 // ENABLE +#define PIN_IND 7 // INITIATOR CTRL DIRECTION +#define PIN_TAD 8 // TARGET CTRL DIRECTION +#define PIN_DTD 5 // DATA DIRECTION + +// SCSI signal pin assignment +#define PIN_DT0 21 // Data 0 +#define PIN_DT1 26 // Data 1 +#define PIN_DT2 20 // Data 2 +#define PIN_DT3 19 // Data 3 +#define PIN_DT4 16 // Data 4 +#define PIN_DT5 13 // Data 5 +#define PIN_DT6 12 // Data 6 +#define PIN_DT7 11 // Data 7 +#define PIN_DP 25 // Data parity +#define PIN_ATN 10 // ATN +#define PIN_RST 22 // RST +#define PIN_ACK 24 // ACK +#define PIN_REQ 15 // REQ +#define PIN_MSG 17 // MSG +#define PIN_CD 18 // CD +#define PIN_IO 4 // IO +#define PIN_BSY 27 // BSY +#define PIN_SEL 23 // SEL +#endif + +#define ALL_SCSI_PINS \ + ((1< +#include +#include +#include +#include + +using namespace std; + +Localizer::Localizer() +{ + // Supported locales, always lower case + supported_languages = { "en", "de", "sv", "fr", "es" }; + + // Positional string arguments are %1, %2, %3 + Add(ERROR_AUTHENTICATION, "en", "Authentication failed"); + Add(ERROR_AUTHENTICATION, "de", "Authentifizierung fehlgeschlagen"); + Add(ERROR_AUTHENTICATION, "sv", "Autentisering misslyckades"); + Add(ERROR_AUTHENTICATION, "fr", "Authentification éronnée"); + Add(ERROR_AUTHENTICATION, "es", "Fallo de autentificación"); + Add(ERROR_OPERATION, "en", "Unknown operation"); + Add(ERROR_OPERATION, "de", "Unbekannte Operation"); + Add(ERROR_OPERATION, "sv", "Okänd operation"); + Add(ERROR_OPERATION, "fr", "Opération inconnue"); + Add(ERROR_OPERATION, "es", "Operación desconocida"); + Add(ERROR_LOG_LEVEL, "en", "Invalid log level %1"); + Add(ERROR_LOG_LEVEL, "de", "Ungültiger Log-Level %1"); + Add(ERROR_LOG_LEVEL, "sv", "Ogiltig loggnivå %1"); + Add(ERROR_LOG_LEVEL, "fr", "Niveau de journalisation invalide %1"); + Add(ERROR_LOG_LEVEL, "es", "Nivel de registro %1 no válido"); + Add(ERROR_MISSING_DEVICE_ID, "en", "Missing device ID"); + Add(ERROR_MISSING_DEVICE_ID, "de", "Fehlende Geräte-ID"); + Add(ERROR_MISSING_DEVICE_ID, "sv", "Enhetens ID saknas"); + Add(ERROR_MISSING_DEVICE_ID, "fr", "ID de périphérique manquante"); + Add(ERROR_MISSING_DEVICE_ID, "es", "Falta el ID del dispositivo"); + Add(ERROR_MISSING_FILENAME, "en", "Missing filename"); + Add(ERROR_MISSING_FILENAME, "de", "Fehlender Dateiname"); + Add(ERROR_MISSING_FILENAME, "sv", "Filnamn saknas"); + Add(ERROR_MISSING_FILENAME, "fr", "Nom de fichier manquant"); + Add(ERROR_MISSING_FILENAME, "es", "Falta el nombre del archivo"); + Add(ERROR_IMAGE_IN_USE, "en", "Image file '%1' is already being used by ID %2, unit %3"); + Add(ERROR_IMAGE_IN_USE, "de", "Image-Datei '%1' wird bereits von ID %2, Einheit %3 benutzt"); + Add(ERROR_IMAGE_IN_USE, "sv", "Skivbildfilen '%1' används redan av ID %2, LUN %3"); + Add(ERROR_IMAGE_IN_USE, "fr", "Le fichier d'image '%1' est déjà utilisé par l'ID %2, unité %3"); + Add(ERROR_IMAGE_IN_USE, "es", "El archivo de imagen '%1' ya está siendo utilizado por el ID %2, unidad %3"); + Add(ERROR_RESERVED_ID, "en", "Device ID %1 is reserved"); + Add(ERROR_RESERVED_ID, "de", "Geräte-ID %1 ist reserviert"); + Add(ERROR_RESERVED_ID, "sv", "Enhets-ID %1 är reserverat"); + Add(ERROR_RESERVED_ID, "fr", "ID de périphérique %1 réservée"); + Add(ERROR_RESERVED_ID, "es", "El ID de dispositivo %1 está reservado"); + Add(ERROR_NON_EXISTING_DEVICE, "en", "Command for non-existing ID %1"); + Add(ERROR_NON_EXISTING_DEVICE, "de", "Kommando für nicht existente ID %1"); + Add(ERROR_NON_EXISTING_DEVICE, "sv", "Kommando för avsaknat ID %1"); + Add(ERROR_NON_EXISTING_DEVICE, "fr", "Commande pour ID %1 non-existant"); + Add(ERROR_NON_EXISTING_DEVICE, "es", "Comando para ID %1 no existente"); + Add(ERROR_NON_EXISTING_UNIT, "en", "Command for non-existing ID %1, unit %2"); + Add(ERROR_NON_EXISTING_UNIT, "de", "Kommando für nicht existente ID %1, Einheit %2"); + Add(ERROR_NON_EXISTING_UNIT, "sv", "Kommando för avsaknat ID %1, LUN %2"); + Add(ERROR_NON_EXISTING_UNIT, "fr", "Command pour ID %1, unité %2 non-existant"); + Add(ERROR_NON_EXISTING_UNIT, "es", "Comando para ID %1 inexistente, unidad %2"); + Add(ERROR_UNKNOWN_DEVICE_TYPE, "en", "Unknown device type %1"); + Add(ERROR_UNKNOWN_DEVICE_TYPE, "de", "Unbekannter Gerätetyp %1"); + Add(ERROR_UNKNOWN_DEVICE_TYPE, "sv", "Obekant enhetstyp: %1"); + Add(ERROR_UNKNOWN_DEVICE_TYPE, "fr", "Type de périphérique inconnu %1"); + Add(ERROR_UNKNOWN_DEVICE_TYPE, "es", "Tipo de dispositivo desconocido %1"); + Add(ERROR_MISSING_DEVICE_TYPE, "en", "Device type required for unknown extension of file '%1'"); + Add(ERROR_MISSING_DEVICE_TYPE, "de", "Gerätetyp erforderlich für unbekannte Extension der Datei '%1'"); + Add(ERROR_MISSING_DEVICE_TYPE, "sv", "Man måste ange enhetstyp för obekant filändelse '%1'"); + Add(ERROR_MISSING_DEVICE_TYPE, "fr", "Type de périphérique requis pour extension inconnue du fichier '%1'"); + Add(ERROR_MISSING_DEVICE_TYPE, "es", "Tipo de dispositivo requerido para la extensión desconocida del archivo '%1'"); + Add(ERROR_DUPLICATE_ID, "en", "Duplicate ID %1, unit %2"); + Add(ERROR_DUPLICATE_ID, "de", "Doppelte ID %1, Einheit %2"); + Add(ERROR_DUPLICATE_ID, "sv", "Duplikat ID %1, LUN %2"); + Add(ERROR_DUPLICATE_ID, "fr", "ID %1, unité %2 dupliquée"); + Add(ERROR_DUPLICATE_ID, "es", "ID duplicado %1, unidad %2"); + Add(ERROR_SASI_SCSI, "en", "SASI and SCSI can't be used at the same time"); + Add(ERROR_SASI_SCSI, "de", "SASI und SCSI können nicht gleichzeitig verwendet werden"); + Add(ERROR_SASI_SCSI, "sv", "SASI och SCSI kan ej användas samtidigt"); + Add(ERROR_SASI_SCSI, "fr", "SASI et SCSI ne peuvent être utilisés en même temps"); + Add(ERROR_SASI_SCSI, "es", "SASI y SCSI no pueden utilizarse al mismo tiempo"); + Add(ERROR_EJECT_REQUIRED, "en", "Existing medium must first be ejected"); + Add(ERROR_EJECT_REQUIRED, "de", "Das vorhandene Medium muss erst ausgeworfen werden"); + Add(ERROR_EJECT_REQUIRED, "sv", "Nuvarande skiva måste utmatas först"); + Add(ERROR_EJECT_REQUIRED, "fr", "Media déjà existant doit d'abord être éjecté"); + Add(ERROR_EJECT_REQUIRED, "es", "El medio existente debe ser expulsado primero"); + Add(ERROR_DEVICE_NAME_UPDATE, "en", "Once set the device name cannot be changed anymore"); + Add(ERROR_DEVICE_NAME_UPDATE, "de", "Ein bereits gesetzter Gerätename kann nicht mehr geändert werden"); + Add(ERROR_DEVICE_NAME_UPDATE, "sv", "Enhetsnamn kan ej ändras efter att ha fastställts en gång"); + Add(ERROR_DEVICE_NAME_UPDATE, "fr", "Une fois défini, le nom de périphérique ne peut plus être changé"); + Add(ERROR_DEVICE_NAME_UPDATE, "es", "Una vez establecido el nombre del dispositivo ya no se puede cambiar"); + Add(ERROR_SHUTDOWN_MODE_MISSING, "en", "Missing shutdown mode"); + Add(ERROR_SHUTDOWN_MODE_MISSING, "de", "Fehlender Shutdown-Modus"); + Add(ERROR_SHUTDOWN_MODE_MISSING, "sv", "Avstängningsläge saknas"); + Add(ERROR_SHUTDOWN_MODE_MISSING, "fr", "Mode d'extinction manquant"); + Add(ERROR_SHUTDOWN_MODE_MISSING, "es", "Falta el modo de apagado"); + Add(ERROR_SHUTDOWN_MODE_INVALID, "en", "Invalid shutdown mode '%1'"); + Add(ERROR_SHUTDOWN_MODE_INVALID, "de", "Ungültiger Shutdown-Modus '%1'"); + Add(ERROR_SHUTDOWN_MODE_INVALID, "sv", "Ogiltigt avstängsningsläge: '%1'"); + Add(ERROR_SHUTDOWN_MODE_INVALID, "fr", "Mode d'extinction invalide '%1'"); + Add(ERROR_SHUTDOWN_MODE_INVALID, "es", "Modo de apagado inválido '%1'"); + Add(ERROR_SHUTDOWN_PERMISSION, "en", "Missing root permission for shutdown or reboot"); + Add(ERROR_SHUTDOWN_PERMISSION, "de", "Fehlende Root-Berechtigung für Shutdown oder Neustart"); + Add(ERROR_SHUTDOWN_PERMISSION, "sv", "Root-rättigheter saknas för att kunna stänga av eller starta om systemet"); + Add(ERROR_SHUTDOWN_PERMISSION, "fr", "Permissions root manquantes pour extinction ou redémarrage"); + Add(ERROR_SHUTDOWN_PERMISSION, "es", "Falta el permiso de root para el apagado o el reinicio"); + Add(ERROR_FILE_OPEN, "en", "Invalid or non-existing file '%1': %2"); + Add(ERROR_FILE_OPEN, "de", "Ungültige oder fehlende Datei '%1': %2"); + Add(ERROR_FILE_OPEN, "sv", "Ogiltig eller saknad fil '%1': %2"); + Add(ERROR_FILE_OPEN, "fr", "Fichier invalide ou non-existant '%1': %2"); + Add(ERROR_FILE_OPEN, "es", "Archivo inválido o inexistente '%1': %2"); + Add(ERROR_BLOCK_SIZE, "en", "Invalid block size %1 bytes"); + Add(ERROR_BLOCK_SIZE, "de", "Ungültige Blockgröße %1 Bytes"); + Add(ERROR_BLOCK_SIZE, "sv", "Ogiltig blockstorlek: %1 byte"); + Add(ERROR_BLOCK_SIZE, "fr", "Taille de bloc invalide %1 octets"); + Add(ERROR_BLOCK_SIZE, "es", "Tamaño de bloque inválido %1 bytes"); + Add(ERROR_BLOCK_SIZE_NOT_CONFIGURABLE, "en", "Block size for device type %1 is not configurable"); + Add(ERROR_BLOCK_SIZE_NOT_CONFIGURABLE, "de", "Blockgröße für Gerätetyp %1 ist nicht konfigurierbar"); + Add(ERROR_BLOCK_SIZE_NOT_CONFIGURABLE, "sv", "Enhetstypen %1 kan inte använda andra blockstorlekar"); + Add(ERROR_BLOCK_SIZE_NOT_CONFIGURABLE, "fr", "Taille de block pour le type de périphérique %1 non configurable"); + Add(ERROR_BLOCK_SIZE_NOT_CONFIGURABLE, "es", "El tamaño del bloque para el tipo de dispositivo %1 no es configurable"); +} + +void Localizer::Add(LocalizationKey key, const string& locale, const string& value) +{ + // Safeguards against empty messages, duplicate entries and unsupported locales + assert(locale.size()); + assert(value.size()); + assert(supported_languages.find(locale) != supported_languages.end()); + assert(localized_messages[locale][key].empty()); + + localized_messages[locale][key] = value; +} + +string Localizer::Localize(LocalizationKey key, const string& locale, const string& arg1, const string& arg2, + const string &arg3) +{ + string locale_lower = locale; + transform(locale_lower.begin(), locale_lower.end(), locale_lower.begin(), ::tolower); + + map messages = localized_messages[locale_lower]; + if (messages.empty()) { + // Try to fall back to country-indepedent locale (e.g. "en" instead of "en_US") + if (locale_lower.length() > 2) { + messages = localized_messages[locale_lower.substr(0, 2)]; + } + if (messages.empty()) { + messages = localized_messages["en"]; + } + } + + assert(!messages.empty()); + + string message = messages[key]; + if (messages.empty()) { + return "Missing localization for enum value " + to_string(key); + } + + message = regex_replace(message, regex("%1"), arg1); + message = regex_replace(message, regex("%2"), arg2); + message = regex_replace(message, regex("%3"), arg3); + + return message; +} diff --git a/src_old/raspberrypi/localizer.h b/src_old/raspberrypi/localizer.h new file mode 100644 index 00000000..e59faf84 --- /dev/null +++ b/src_old/raspberrypi/localizer.h @@ -0,0 +1,59 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2021 Uwe Seimet +// +// Message localization support. Currently only for messages with up to 3 string parameters. +// +//--------------------------------------------------------------------------- + +#pragma once + +#include +#include +#include + +using namespace std; + +enum LocalizationKey { + ERROR_AUTHENTICATION, + ERROR_OPERATION, + ERROR_LOG_LEVEL, + ERROR_MISSING_DEVICE_ID, + ERROR_MISSING_FILENAME, + ERROR_IMAGE_IN_USE, + ERROR_RESERVED_ID, + ERROR_NON_EXISTING_DEVICE, + ERROR_NON_EXISTING_UNIT, + ERROR_UNKNOWN_DEVICE_TYPE, + ERROR_MISSING_DEVICE_TYPE, + ERROR_DUPLICATE_ID, + ERROR_SASI_SCSI, + ERROR_EJECT_REQUIRED, + ERROR_DEVICE_NAME_UPDATE, + ERROR_SHUTDOWN_MODE_MISSING, + ERROR_SHUTDOWN_MODE_INVALID, + ERROR_SHUTDOWN_PERMISSION, + ERROR_FILE_OPEN, + ERROR_BLOCK_SIZE, + ERROR_BLOCK_SIZE_NOT_CONFIGURABLE +}; + +class Localizer +{ +public: + + Localizer(); + ~Localizer() {}; + + string Localize(LocalizationKey, const string&, const string& = "", const string& = "", const string& = ""); + +private: + + void Add(LocalizationKey, const string&, const string&); + map> localized_messages; + + set supported_languages; +}; diff --git a/src_old/raspberrypi/log.h b/src_old/raspberrypi/log.h new file mode 100644 index 00000000..7347376d --- /dev/null +++ b/src_old/raspberrypi/log.h @@ -0,0 +1,33 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Powered by XM6 TypeG Technology. +// Copyright (C) 2016-2020 GIMONS +// Copyright (C) 2020 akuker +// [ Logging utilities ] +// +//--------------------------------------------------------------------------- + +#if !defined(log_h) +#define log_h + +#include "spdlog/spdlog.h" +#include "spdlog/sinks/sink.h" + +#define SPDLOGWRAPPER(loglevel, ...) \ +{ \ + char logbuf[_MAX_FNAME*2]; \ + snprintf(logbuf, sizeof(logbuf), __VA_ARGS__); \ + spdlog::log(loglevel, logbuf); \ +}; + +#define LOGTRACE(...) SPDLOGWRAPPER(spdlog::level::trace, __VA_ARGS__) +#define LOGDEBUG(...) SPDLOGWRAPPER(spdlog::level::debug, __VA_ARGS__) +#define LOGINFO(...) SPDLOGWRAPPER(spdlog::level::info, __VA_ARGS__) +#define LOGWARN(...) SPDLOGWRAPPER(spdlog::level::warn, __VA_ARGS__) +#define LOGERROR(...) SPDLOGWRAPPER(spdlog::level::err, __VA_ARGS__) +#define LOGCRITICAL(...) SPDLOGWRAPPER(spdlog::level::critical, __VA_ARGS__) + +#endif diff --git a/src_old/raspberrypi/monitor/data_sample.cpp b/src_old/raspberrypi/monitor/data_sample.cpp new file mode 100644 index 00000000..ea8ec048 --- /dev/null +++ b/src_old/raspberrypi/monitor/data_sample.cpp @@ -0,0 +1,38 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) for Raspberry Pi +// +// Copyright (C) 2020-2021 akuker +// +// [ SCSI Bus Monitor ] +// +//--------------------------------------------------------------------------- +#include "os.h" +#include "scsi.h" +#include "data_sample.h" + +const char *GetPhaseStr(const data_capture *sample) +{ + return BUS::GetPhaseStrRaw(GetPhase(sample)); +} + +BUS::phase_t GetPhase(const data_capture *sample) +{ + // Selection Phase + if (GetSel(sample)) + { + return BUS::selection; + } + + // Bus busy phase + if (!GetBsy(sample)) + { + return BUS::busfree; + } + + // Get target phase from bus signal line + DWORD mci = GetMsg(sample) ? 0x04 : 0x00; + mci |= GetCd(sample) ? 0x02 : 0x00; + mci |= GetIo(sample) ? 0x01 : 0x00; + return BUS::GetPhase(mci); +} diff --git a/src_old/raspberrypi/monitor/data_sample.h b/src_old/raspberrypi/monitor/data_sample.h new file mode 100644 index 00000000..e153dc2c --- /dev/null +++ b/src_old/raspberrypi/monitor/data_sample.h @@ -0,0 +1,49 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2020-2021 akuker +// +// [ SCSI Bus Monitor ] +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "scsi.h" +#include "gpiobus.h" + +typedef struct data_capture +{ + DWORD data; + uint64_t timestamp; +} data_capture_t; + +#define GET_PIN(SAMPLE, PIN) ((bool)((SAMPLE->data >> PIN) & 1)) + +inline bool GetBsy(const data_capture *sample) { return BUS::GetPinRaw(sample->data, PIN_BSY); } +inline bool GetSel(const data_capture *sample) { return BUS::GetPinRaw(sample->data, PIN_SEL); } +inline bool GetAtn(const data_capture *sample) { return BUS::GetPinRaw(sample->data, PIN_ATN); } +inline bool GetAck(const data_capture *sample) { return BUS::GetPinRaw(sample->data, PIN_ACK); } +inline bool GetRst(const data_capture *sample) { return BUS::GetPinRaw(sample->data, PIN_RST); } +inline bool GetMsg(const data_capture *sample) { return BUS::GetPinRaw(sample->data, PIN_MSG); } +inline bool GetCd(const data_capture *sample) { return BUS::GetPinRaw(sample->data, PIN_CD); } +inline bool GetIo(const data_capture *sample) { return BUS::GetPinRaw(sample->data, PIN_IO); } +inline bool GetReq(const data_capture *sample) { return BUS::GetPinRaw(sample->data, PIN_REQ); } +inline bool GetDp(const data_capture *sample) { return BUS::GetPinRaw(sample->data, PIN_DP); } +inline BYTE GetData(const data_capture *sample) +{ + DWORD data = sample->data; + return (BYTE)((data >> (PIN_DT0 - 0)) & (1 << 0)) | + ((data >> (PIN_DT1 - 1)) & (1 << 1)) | + ((data >> (PIN_DT2 - 2)) & (1 << 2)) | + ((data >> (PIN_DT3 - 3)) & (1 << 3)) | + ((data >> (PIN_DT4 - 4)) & (1 << 4)) | + ((data >> (PIN_DT5 - 5)) & (1 << 5)) | + ((data >> (PIN_DT6 - 6)) & (1 << 6)) | + ((data >> (PIN_DT7 - 7)) & (1 << 7)); +} + +const char *GetPhaseStr(const data_capture *sample); +BUS::phase_t GetPhase(const data_capture *sample); diff --git a/src_old/raspberrypi/monitor/sm_html_report.cpp b/src_old/raspberrypi/monitor/sm_html_report.cpp new file mode 100644 index 00000000..30f45b96 --- /dev/null +++ b/src_old/raspberrypi/monitor/sm_html_report.cpp @@ -0,0 +1,205 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Powered by XM6 TypeG Technology. +// Copyright (C) 2016-2020 GIMONS +// +//--------------------------------------------------------------------------- + +#include +#include +#include +#include "os.h" +#include "log.h" +#include "sm_reports.h" +#include "rascsi_version.h" + +using namespace std; + +const static string html_header = R"( + + + + +)"; + +static void print_copyright_info(ofstream& html_fp) +{ + html_fp << "" << endl \ + << "

RaSCSI scsimon Capture Tool

" << endl \ + << "
Version " << rascsi_get_version_string() \
+            << __DATE__ << " " << __TIME__ << endl \
+            << "Copyright (C) 2016-2020 GIMONS" << endl \
+            << "Copyright (C) 2020-2021 Contributors to the RaSCSI project" << endl \
+            << "
" << endl \ + << "
" << endl; +} + +static const string html_footer = R"( + + + +)"; + + +static void print_html_data(ofstream& html_fp, const data_capture *data_capture_array, DWORD capture_count) +{ + const data_capture *data; + bool prev_data_valid = false; + bool curr_data_valid; + DWORD selected_id = 0; + BUS::phase_t prev_phase = BUS::busfree; + bool close_row = false; + int data_space_count = 0; + bool collapsible_div_active = false; + bool button_active = false; + + html_fp << "" << endl; + + for (DWORD idx = 0; idx < capture_count; idx++) + { + data = &data_capture_array[idx]; + curr_data_valid = GetAck(data) && GetReq(data); + BUS::phase_t phase = GetPhase(data); + if (phase == BUS::selection && !GetBsy(data)) + { + selected_id = GetData(data); + } + if (prev_phase != phase) + { + if (close_row) + { + if (collapsible_div_active) + { + html_fp << ""; + } + else if (button_active) + { + html_fp << ""; + } + html_fp << ""; + if (data_space_count < 1) + { + html_fp << ""; + } + else + { + html_fp << ""; + } + html_fp << "" << endl; + data_space_count = 0; + } + html_fp << ""; + close_row = true; // Close the row the next time around + html_fp << ""; + html_fp << ""; + html_fp << ""; + html_fp << "
--wc: " << std::dec << "(0x" << std::hex << data_space_count << ")
" << (double)data->timestamp / 100000 << "" << GetPhaseStr(data) << "" << std::hex << selected_id << ""; + } + if (curr_data_valid && !prev_data_valid) + { + if (data_space_count == 0) + { + button_active = true; + html_fp << "
" << endl; + collapsible_div_active = true; + button_active = false; + } + if (((data_space_count % 16) == 0) && (data_space_count > 17)) + { + html_fp << "
" << endl; + } + } + prev_data_valid = curr_data_valid; + prev_phase = phase; + } +} + +void scsimon_generate_html(const char *filename, const data_capture *data_capture_array, DWORD capture_count) +{ + LOGINFO("Creating HTML report file (%s)", filename); + + ofstream html_ofstream; + + html_ofstream.open(filename, ios::out); + + html_ofstream << html_header; + print_copyright_info(html_ofstream); + print_html_data(html_ofstream, data_capture_array, capture_count); + + html_ofstream << html_footer; + + html_ofstream.close(); +} diff --git a/src_old/raspberrypi/monitor/sm_json_report.cpp b/src_old/raspberrypi/monitor/sm_json_report.cpp new file mode 100644 index 00000000..2da7942b --- /dev/null +++ b/src_old/raspberrypi/monitor/sm_json_report.cpp @@ -0,0 +1,98 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Powered by XM6 TypeG Technology. +// Copyright (C) 2016-2020 GIMONS +// +//--------------------------------------------------------------------------- + +#include "sm_reports.h" +#include "log.h" +#include "spdlog/spdlog.h" +#include "string.h" +#include +#include +using namespace std; + +const char timestamp_label[] = "\"timestamp\":\"0x"; +const char data_label[] = "\"data\":\"0x"; + +DWORD scsimon_read_json(const char *json_filename, data_capture *data_capture_array, DWORD max_sz) +{ + char str_buf[1024]; + FILE *fp = fopen(json_filename, "r"); + DWORD sample_count = 0; + + while (fgets(str_buf, sizeof(str_buf), fp)) + { + + char timestamp[1024]; + char data[1024]; + uint64_t timestamp_uint; + uint32_t data_uint; + char *ptr; + + char *timestamp_str; + char *data_str; + timestamp_str = strstr(str_buf, timestamp_label); + if (!timestamp_str) + continue; + strncpy(timestamp, ×tamp_str[strlen(timestamp_label)], 16); + timestamp[16] = '\0'; + timestamp_uint = strtoull(timestamp, &ptr, 16); + + data_str = strstr(str_buf, data_label); + if (!data_str) + continue; + strncpy(data, &data_str[strlen(data_label)], 8); + data[8] = '\0'; + data_uint = strtoul(data, &ptr, 16); + + // printf("Time: %016llX Data: %08X\n", timestamp_uint, data_uint); + + data_capture_array[sample_count].timestamp = timestamp_uint; + data_capture_array[sample_count].data = data_uint; + sample_count++; + if (sample_count >= max_sz) + { + LOGWARN("File exceeds maximum buffer size. Some data may be missing."); + LOGWARN("Try re-running the tool with a larger buffer size"); + break; + } + } + fclose(fp); + + return sample_count; +} + +//--------------------------------------------------------------------------- +// +// Generate JSON Output File +// +//--------------------------------------------------------------------------- +void scsimon_generate_json(const char *filename, const data_capture *data_capture_array, DWORD capture_count) +{ + LOGTRACE("Creating JSON file (%s)", filename); + ofstream json_ofstream; + json_ofstream.open(filename, ios::out); + + json_ofstream << "[" << endl; + + DWORD i = 0; + while (i < capture_count) + { + json_ofstream << fmt::format("{{\"id\": \"{0:d}\", \"timestamp\":\"{1:#016x}\", \"data\":\"{2:#08x}\"}}", i, data_capture_array[i].timestamp, data_capture_array[i].data); + + if (i != (capture_count - 1)) + { + json_ofstream << ","; + } + json_ofstream << endl; + i++; + } + json_ofstream << "]" << endl; + json_ofstream.close(); + +} diff --git a/src_old/raspberrypi/monitor/sm_reports.h b/src_old/raspberrypi/monitor/sm_reports.h new file mode 100644 index 00000000..3976e133 --- /dev/null +++ b/src_old/raspberrypi/monitor/sm_reports.h @@ -0,0 +1,17 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) for Raspberry Pi +// +// Copyright (C) 2020-2021 akuker +// +// [ SCSI Bus Monitor ] +// +//--------------------------------------------------------------------------- + +#include "data_sample.h" + +DWORD scsimon_read_json(const char *json_filename, data_capture *data_capture_array, DWORD max_sz); + +void scsimon_generate_html(const char *filename, const data_capture *data_capture_array, DWORD capture_count); +void scsimon_generate_json(const char *filename, const data_capture *data_capture_array, DWORD capture_count); +void scsimon_generate_value_change_dump(const char *filename, const data_capture *data_capture_array, DWORD capture_count); diff --git a/src_old/raspberrypi/monitor/sm_vcd_report.cpp b/src_old/raspberrypi/monitor/sm_vcd_report.cpp new file mode 100644 index 00000000..762a949b --- /dev/null +++ b/src_old/raspberrypi/monitor/sm_vcd_report.cpp @@ -0,0 +1,187 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) for Raspberry Pi +// +// Copyright (C) 2020-2021 akuker +// +// [ SCSI Bus Monitor ] +// +//--------------------------------------------------------------------------- + +#include "os.h" +#include "log.h" +#include "spdlog/spdlog.h" +#include +#include +#include +#include "data_sample.h" +#include "sm_reports.h" +#include "gpiobus.h" + +using namespace std; + +//--------------------------------------------------------------------------- +// +// Constant declarations +// +//--------------------------------------------------------------------------- + +// Symbol definition for the VCD file +// These are just arbitrary symbols. They can be anything allowed by the VCD file format, +// as long as they're consistently used. +const char SYMBOL_PIN_DAT = '#'; +const char SYMBOL_PIN_ATN = '+'; +const char SYMBOL_PIN_RST = '$'; +const char SYMBOL_PIN_ACK = '%'; +const char SYMBOL_PIN_REQ = '^'; +const char SYMBOL_PIN_MSG = '&'; +const char SYMBOL_PIN_CD = '*'; +const char SYMBOL_PIN_IO = '('; +const char SYMBOL_PIN_BSY = ')'; +const char SYMBOL_PIN_SEL = '-'; +const char SYMBOL_PIN_PHASE = '='; + +// We'll use position 0 in the prev_value array to store the previous phase +#define PIN_PHASE 0 + +//--------------------------------------------------------------------------- +// +// Variable declarations +// +//--------------------------------------------------------------------------- +static BYTE prev_value[32] = {0xFF}; + +extern double ns_per_loop; + +static BOOL get_pin_value(DWORD data, int pin) +{ + return (data >> pin) & 1; +} + +static BYTE get_data_field(DWORD data) +{ + DWORD data_out = + ((data >> (PIN_DT0 - 0)) & (1 << 7)) | + ((data >> (PIN_DT1 - 1)) & (1 << 6)) | + ((data >> (PIN_DT2 - 2)) & (1 << 5)) | + ((data >> (PIN_DT3 - 3)) & (1 << 4)) | + ((data >> (PIN_DT4 - 4)) & (1 << 3)) | + ((data >> (PIN_DT5 - 5)) & (1 << 2)) | + ((data >> (PIN_DT6 - 6)) & (1 << 1)) | + ((data >> (PIN_DT7 - 7)) & (1 << 0)); + + return (BYTE)data_out; +} + +static void vcd_output_if_changed_phase(ofstream& fp, DWORD data, int pin, char symbol) +{ + BUS::phase_t new_value = GPIOBUS::GetPhaseRaw(data); + if (prev_value[pin] != new_value) + { + prev_value[pin] = new_value; + fp << "s" << GPIOBUS::GetPhaseStrRaw(new_value) << " " << symbol << endl; + } +} + +static void vcd_output_if_changed_bool(ofstream& fp, DWORD data, int pin, char symbol) +{ + BOOL new_value = get_pin_value(data, pin); + if (prev_value[pin] != new_value) + { + prev_value[pin] = new_value; + fp << new_value << symbol << endl; + } +} + +static void vcd_output_if_changed_byte(ofstream& fp, DWORD data, int pin, char symbol) +{ + BYTE new_value = get_data_field(data); + if (prev_value[pin] != new_value) + { + prev_value[pin] = new_value; + fp << "b" + << fmt::format("{0:b}", get_pin_value(data, PIN_DT7)) + << fmt::format("{0:b}", get_pin_value(data, PIN_DT6)) + << fmt::format("{0:b}", get_pin_value(data, PIN_DT5)) + << fmt::format("{0:b}", get_pin_value(data, PIN_DT4)) + << fmt::format("{0:b}", get_pin_value(data, PIN_DT3)) + << fmt::format("{0:b}", get_pin_value(data, PIN_DT2)) + << fmt::format("{0:b}", get_pin_value(data, PIN_DT1)) + << fmt::format("{0:b}", get_pin_value(data, PIN_DT0)) + << " " << symbol << endl; + } +} + +void scsimon_generate_value_change_dump(const char *filename, const data_capture *data_capture_array, DWORD capture_count) +{ + LOGTRACE("Creating Value Change Dump file (%s)", filename); + ofstream vcd_ofstream; + vcd_ofstream.open(filename, ios::out); + + // Get the current time + time_t rawtime; + time(&rawtime); + struct tm *timeinfo = localtime(&rawtime); + char timestamp[256]; + strftime(timestamp, sizeof(timestamp), "%d-%m-%Y %H-%M-%S", timeinfo); + + vcd_ofstream + << "$date" << endl + << timestamp << endl + << "$end" << endl + << "$version" << endl + << " VCD generator tool version info text." << endl + << "$end" << endl + << "$comment" << endl + << " Tool build date:" << __TIMESTAMP__ << endl + << "$end" << endl + << "$timescale 1 ns $end" << endl + << "$scope module logic $end" << endl + << "$var wire 1 " << SYMBOL_PIN_BSY << " BSY $end" << endl + << "$var wire 1 " << SYMBOL_PIN_SEL << " SEL $end" << endl + << "$var wire 1 " << SYMBOL_PIN_CD << " CD $end" << endl + << "$var wire 1 " << SYMBOL_PIN_IO << " IO $end"<< endl + << "$var wire 1 " << SYMBOL_PIN_MSG << " MSG $end"<< endl + << "$var wire 1 " << SYMBOL_PIN_REQ << " REQ $end" << endl + << "$var wire 1 " << SYMBOL_PIN_ACK << " ACK $end" << endl + << "$var wire 1 " << SYMBOL_PIN_ATN << " ATN $end" << endl + << "$var wire 1 " << SYMBOL_PIN_RST << " RST $end" << endl + << "$var wire 8 " << SYMBOL_PIN_DAT << " data $end" << endl + << "$var string 1 " << SYMBOL_PIN_PHASE << " phase $end" << endl + << "$upscope $end" << endl + << "$enddefinitions $end" << endl; + + // Initial values - default to zeros + vcd_ofstream + << "$dumpvars" << endl + << "0" << SYMBOL_PIN_BSY << endl + << "0" << SYMBOL_PIN_SEL << endl + << "0" << SYMBOL_PIN_CD << endl + << "0" << SYMBOL_PIN_IO << endl + << "0" << SYMBOL_PIN_MSG << endl + << "0" << SYMBOL_PIN_REQ << endl + << "0" << SYMBOL_PIN_ACK << endl + << "0" << SYMBOL_PIN_ATN << endl + << "0" << SYMBOL_PIN_RST << endl + << "b00000000 " << SYMBOL_PIN_DAT << endl + << "$end" << endl; + + DWORD i = 0; + while (i < capture_count) + { + vcd_ofstream << "#" << (uint64_t)(data_capture_array[i].timestamp * ns_per_loop) << endl; + vcd_output_if_changed_bool(vcd_ofstream, data_capture_array[i].data, PIN_BSY, SYMBOL_PIN_BSY); + vcd_output_if_changed_bool(vcd_ofstream, data_capture_array[i].data, PIN_SEL, SYMBOL_PIN_SEL); + vcd_output_if_changed_bool(vcd_ofstream, data_capture_array[i].data, PIN_CD, SYMBOL_PIN_CD); + vcd_output_if_changed_bool(vcd_ofstream, data_capture_array[i].data, PIN_IO, SYMBOL_PIN_IO); + vcd_output_if_changed_bool(vcd_ofstream, data_capture_array[i].data, PIN_MSG, SYMBOL_PIN_MSG); + vcd_output_if_changed_bool(vcd_ofstream, data_capture_array[i].data, PIN_REQ, SYMBOL_PIN_REQ); + vcd_output_if_changed_bool(vcd_ofstream, data_capture_array[i].data, PIN_ACK, SYMBOL_PIN_ACK); + vcd_output_if_changed_bool(vcd_ofstream, data_capture_array[i].data, PIN_ATN, SYMBOL_PIN_ATN); + vcd_output_if_changed_bool(vcd_ofstream, data_capture_array[i].data, PIN_RST, SYMBOL_PIN_RST); + vcd_output_if_changed_byte(vcd_ofstream, data_capture_array[i].data, PIN_DT0, SYMBOL_PIN_DAT); + vcd_output_if_changed_phase(vcd_ofstream, data_capture_array[i].data, PIN_PHASE, SYMBOL_PIN_PHASE); + i++; + } + vcd_ofstream.close(); +} diff --git a/src_old/raspberrypi/os.h b/src_old/raspberrypi/os.h new file mode 100644 index 00000000..3be011fb --- /dev/null +++ b/src_old/raspberrypi/os.h @@ -0,0 +1,116 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Powered by XM6 TypeG Technology. +// Copyright (C) 2016-2020 GIMONS +// Copyright (C) 2020 akuker +// +// [ OS related definitions ] +// +//--------------------------------------------------------------------------- + +#if !defined(os_h) +#define os_h + +//--------------------------------------------------------------------------- +// +// #define +// +//--------------------------------------------------------------------------- +#ifndef _LARGEFILE64_SOURCE +#define _LARGEFILE64_SOURCE +#endif + +#ifndef _FILE_OFFSET_BITS +#define _FILE_OFFSET_BITS 64 +#endif + +//--------------------------------------------------------------------------- +// +// #include +// +//--------------------------------------------------------------------------- + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------------- +// +// Basic Macros +// +//--------------------------------------------------------------------------- +#if !defined(ASSERT) +#if !defined(NDEBUG) +#define ASSERT(cond) assert(cond) +#else +#define ASSERT(cond) ((void)0) +#endif // NDEBUG +#endif // ASSERT + +#if !defined(ASSERT_DIAG) +#if !defined(NDEBUG) +#define ASSERT_DIAG() AssertDiag() +#else +#define ASSERT_DIAG() ((void)0) +#endif // NDEBUG +#endif // ASSERT_DIAG + +#define ARRAY_SIZE(x) (sizeof(x)/(sizeof(x[0]))) + +//--------------------------------------------------------------------------- +// +// Basic Type Definitions +// +//--------------------------------------------------------------------------- +typedef unsigned char BYTE; +typedef uint16_t WORD; +typedef uint32_t DWORD; +typedef int BOOL; +typedef char TCHAR; + +#if !defined(FALSE) +#define FALSE 0 +#endif + +#if !defined(TRUE) +#define TRUE 1 +#endif + +#if !defined(_T) +#define _T(x) x +#endif + +#define _MAX_PATH 260 +#define _MAX_DIR 256 +#define _MAX_FNAME 256 +#define _MAX_EXT 256 + +#endif // os_h diff --git a/src_old/raspberrypi/os_integration/dhcpcd.conf.patch b/src_old/raspberrypi/os_integration/dhcpcd.conf.patch new file mode 100644 index 00000000..459f0ee0 --- /dev/null +++ b/src_old/raspberrypi/os_integration/dhcpcd.conf.patch @@ -0,0 +1,7 @@ +--- dhcpcd_orig.conf 2021-02-26 17:32:14.065284400 -0600 ++++ dhcpcd.conf 2021-02-26 17:32:30.925039567 -0600 +@@ -58,3 +58,4 @@ + #interface eth0 + #fallback static_eth0 + # ++denyinterfaces eth0 diff --git a/src_old/raspberrypi/os_integration/rascsi.conf b/src_old/raspberrypi/os_integration/rascsi.conf new file mode 100644 index 00000000..5b3b4b3f --- /dev/null +++ b/src_old/raspberrypi/os_integration/rascsi.conf @@ -0,0 +1,2 @@ +if $programname == 'RASCSI' then /var/log/rascsi.log +& stop diff --git a/src_old/raspberrypi/os_integration/rascsi.service b/src_old/raspberrypi/os_integration/rascsi.service new file mode 100644 index 00000000..30a2a5bf --- /dev/null +++ b/src_old/raspberrypi/os_integration/rascsi.service @@ -0,0 +1,24 @@ +[Unit] +Description=RaSCSI service +After=network.target + +[Service] +Type=simple +Restart=always +ExecStart=/usr/local/bin/rascsi -r 7 +# Example 1: If you want to automatically attach a hard disk at startup, +# say an image called harddisk.hds on SCSI ID 1, change the ExecStart line to: +# +# ExecStart=/usr/local/bin/rascsi -ID1 /home/pi/images/harddisk.hds +# +# Example 2: If you want to reserve SCSI IDs to prevent usage, add '-r' followed by +# comma-separated SCSI ID numbers; for instance IDs 0 and 7: +# +# ExecStart=/usr/local/bin/rascsi -r 0,7 +# +ExecStop=/usr/local/bin/rasctl -X +SyslogIdentifier=RASCSI + +[Install] +WantedBy=multi-user.target + diff --git a/src_old/raspberrypi/os_integration/rascsi_bridge b/src_old/raspberrypi/os_integration/rascsi_bridge new file mode 100644 index 00000000..923fb35e --- /dev/null +++ b/src_old/raspberrypi/os_integration/rascsi_bridge @@ -0,0 +1,13 @@ +# +# Defines the 'rascsi_bridge' bridge that connects the RaSCSI network +# interface (ex DaynaPort SCSI/Link) to the outside world. +# +# Depending upon your system configuration, you may need to update this +# file to change 'eth0' to your Ethernet interface +# +# This file should be place in /etc/network/interfaces.d + +auto rascsi_bridge +iface rascsi_bridge inet dhcp + bridge_ports eth0 + diff --git a/src_old/raspberrypi/protobuf_util.cpp b/src_old/raspberrypi/protobuf_util.cpp new file mode 100644 index 00000000..90c71d07 --- /dev/null +++ b/src_old/raspberrypi/protobuf_util.cpp @@ -0,0 +1,199 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2021 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include +#include "os.h" +#include "log.h" +#include "rascsi_interface.pb.h" +#include "localizer.h" +#include "exceptions.h" +#include "protobuf_util.h" + +using namespace std; +using namespace rascsi_interface; + +#define FPRT(fp, ...) fprintf(fp, __VA_ARGS__ ) + +#define COMPONENT_SEPARATOR ':' +#define KEY_VALUE_SEPARATOR '=' + +Localizer localizer; + +void protobuf_util::ParseParameters(PbDeviceDefinition& device, const string& params) +{ + if (!params.empty()) { + if (params.find(KEY_VALUE_SEPARATOR) != string::npos) { + stringstream ss(params); + string p; + while (getline(ss, p, COMPONENT_SEPARATOR)) { + if (!p.empty()) { + size_t separator_pos = p.find(KEY_VALUE_SEPARATOR); + if (separator_pos != string::npos) { + AddParam(device, p.substr(0, separator_pos), p.substr(separator_pos + 1)); + } + } + } + } + // Old style parameters, for backwards compatibility only. + // Only one of these parameters will be used by rascsi, depending on the device type. + else { + AddParam(device, "file", params); + if (params != "bridge" && params != "daynaport" && params != "printer" && params != "services") { + AddParam(device, "interfaces", params); + } + } + } +} + +const string protobuf_util::GetParam(const PbCommand& command, const string& key) +{ + auto map = command.params(); + return map[key]; +} + +const string protobuf_util::GetParam(const PbDeviceDefinition& device, const string& key) +{ + auto map = device.params(); + return map[key]; +} + +void protobuf_util::AddParam(PbCommand& command, const string& key, const string& value) +{ + if (!key.empty() && !value.empty()) { + auto& map = *command.mutable_params(); + map[key] = value; + } +} + +void protobuf_util::AddParam(PbDevice& device, const string& key, const string& value) +{ + if (!key.empty() && !value.empty()) { + auto& map = *device.mutable_params(); + map[key] = value; + } +} + +void protobuf_util::AddParam(PbDeviceDefinition& device, const string& key, const string& value) +{ + if (!key.empty() && !value.empty()) { + auto& map = *device.mutable_params(); + map[key] = value; + } +} + +//--------------------------------------------------------------------------- +// +// Serialize/Deserialize protobuf message: Length followed by the actual data. +// Little endian is assumed. +// +//--------------------------------------------------------------------------- + +void protobuf_util::SerializeMessage(int fd, const google::protobuf::Message& message) +{ + string data; + message.SerializeToString(&data); + + // Write the size of the protobuf data as a header + int32_t size = data.length(); + if (write(fd, &size, sizeof(size)) != sizeof(size)) { + throw io_exception("Can't write protobuf message header"); + } + + // Write the actual protobuf data + if (write(fd, data.data(), size) != size) { + throw io_exception("Can't write protobuf message data"); + } +} + +void protobuf_util::DeserializeMessage(int fd, google::protobuf::Message& message) +{ + // Read the header with the size of the protobuf data + uint8_t header_buf[4]; + int bytes_read = ReadNBytes(fd, header_buf, sizeof(header_buf)); + if (bytes_read < (int)sizeof(header_buf)) { + return; + } + int32_t size = (header_buf[3] << 24) + (header_buf[2] << 16) + (header_buf[1] << 8) + header_buf[0]; + if (size <= 0) { + throw io_exception("Broken protobuf message header"); + } + + // Read the binary protobuf data + uint8_t data_buf[size]; + bytes_read = ReadNBytes(fd, data_buf, size); + if (bytes_read < size) { + throw io_exception("Missing protobuf message data"); + } + + // Create protobuf message + string data((const char *)data_buf, size); + message.ParseFromString(data); +} + +int protobuf_util::ReadNBytes(int fd, uint8_t *buf, int n) +{ + int offset = 0; + while (offset < n) { + ssize_t len = read(fd, buf + offset, n - offset); + if (!len) { + break; + } + + offset += len; + } + + return offset; +} + +bool protobuf_util::ReturnLocalizedError(const CommandContext& context, const LocalizationKey key, + const string& arg1, const string& arg2, const string& arg3) +{ + return ReturnLocalizedError(context, key, NO_ERROR_CODE, arg1, arg2, arg3); +} + +bool protobuf_util::ReturnLocalizedError(const CommandContext& context, const LocalizationKey key, + const PbErrorCode error_code, const string& arg1, const string& arg2, const string& arg3) +{ + // For the logfile always use English + LOGERROR("%s", localizer.Localize(key, "en", arg1, arg2, arg3).c_str()); + + return ReturnStatus(context, false, localizer.Localize(key, context.locale, arg1, arg2, arg3), error_code, false); +} + +bool protobuf_util::ReturnStatus(const CommandContext& context, bool status, const string& msg, + const PbErrorCode error_code, bool log) +{ + // Do not log twice if logging has already been done in the localized error handling above + if (log && !status && !msg.empty()) { + LOGERROR("%s", msg.c_str()); + } + + if (context.fd == -1) { + if (!msg.empty()) { + if (status) { + FPRT(stderr, "Error: "); + FPRT(stderr, "%s", msg.c_str()); + FPRT(stderr, "\n"); + } + else { + FPRT(stdout, "%s", msg.c_str()); + FPRT(stderr, "\n"); + } + } + } + else { + PbResult result; + result.set_status(status); + result.set_error_code(error_code); + result.set_msg(msg); + SerializeMessage(context.fd, result); + } + + return status; +} diff --git a/src_old/raspberrypi/protobuf_util.h b/src_old/raspberrypi/protobuf_util.h new file mode 100644 index 00000000..e07efd66 --- /dev/null +++ b/src_old/raspberrypi/protobuf_util.h @@ -0,0 +1,41 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2021 Uwe Seimet +// +// Helper methods for serializing/deserializing protobuf messages +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "google/protobuf/message.h" +#include "rascsi_interface.pb.h" +#include "command_context.h" +#include "localizer.h" +#include +#include + +using namespace std; +using namespace rascsi_interface; + +namespace protobuf_util +{ + void ParseParameters(PbDeviceDefinition&, const string&); + const string GetParam(const PbCommand&, const string&); + const string GetParam(const PbDeviceDefinition&, const string&); + void AddParam(PbCommand&, const string&, const string&); + void AddParam(PbDevice&, const string&, const string&); + void AddParam(PbDeviceDefinition&, const string&, const string&); + void SerializeMessage(int, const google::protobuf::Message&); + void DeserializeMessage(int, google::protobuf::Message&); + int ReadNBytes(int, uint8_t *, int); + bool ReturnLocalizedError(const CommandContext&, const LocalizationKey, const string& = "", const string& = "", + const string& = ""); + bool ReturnLocalizedError(const CommandContext&, const LocalizationKey, const PbErrorCode, const string& = "", + const string& = "", const string& = ""); + bool ReturnStatus(const CommandContext&, bool = true, const string& = "", + const PbErrorCode = PbErrorCode::NO_ERROR_CODE, bool = true); +} diff --git a/src_old/raspberrypi/rascsi.cpp b/src_old/raspberrypi/rascsi.cpp new file mode 100644 index 00000000..1131f8a7 --- /dev/null +++ b/src_old/raspberrypi/rascsi.cpp @@ -0,0 +1,1813 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Powered by XM6 TypeG Technology. +// Copyright (C) 2016-2020 GIMONS +// Copyright (C) 2020-2021 Contributors to the RaSCSI project +// [ RaSCSI main ] +// +//--------------------------------------------------------------------------- + +#include "os.h" +#include "controllers/sasidev_ctrl.h" +#include "devices/device_factory.h" +#include "devices/device.h" +#include "devices/disk.h" +#include "devices/file_support.h" +#include "gpiobus.h" +#include "exceptions.h" +#include "protobuf_util.h" +#include "rascsi_version.h" +#include "rascsi_response.h" +#include "rasutil.h" +#include "rascsi_image.h" +#include "rascsi_interface.pb.h" +#include "disk_image/disk_image_handle.h" +#include "spdlog/spdlog.h" +#include "spdlog/sinks/stdout_color_sinks.h" +#include +#include +#include +#include +#include +#include +#include +#include "config.h" + +using namespace std; +using namespace spdlog; +using namespace rascsi_interface; +using namespace ras_util; +using namespace protobuf_util; + +//--------------------------------------------------------------------------- +// +// Constant declarations +// +//--------------------------------------------------------------------------- +#define CtrlMax 8 // Maximum number of SCSI controllers +#define UnitNum SASIDEV::UnitMax // Number of units around controller +#define FPRT(fp, ...) fprintf(fp, __VA_ARGS__ ) + +#define COMPONENT_SEPARATOR ':' + +//--------------------------------------------------------------------------- +// +// Variable declarations +// +//--------------------------------------------------------------------------- +static volatile bool running; // Running flag +static volatile bool active; // Processing flag +vector controllers(CtrlMax); // Controllers +vector devices(CtrlMax * UnitNum); // Disks +GPIOBUS *bus; // GPIO Bus +int monsocket; // Monitor Socket +pthread_t monthread; // Monitor Thread +pthread_mutex_t ctrl_mutex; // Semaphore for the ctrl array +static void *MonThread(void *param); +string current_log_level; // Some versions of spdlog do not support get_log_level() +string access_token; +set reserved_ids; +DeviceFactory& device_factory = DeviceFactory::instance(); +RascsiImage rascsi_image; +RascsiResponse rascsi_response(&device_factory, &rascsi_image); + +//--------------------------------------------------------------------------- +// +// Signal Processing +// +//--------------------------------------------------------------------------- +void KillHandler(int sig) +{ + // Stop instruction + running = false; +} + +//--------------------------------------------------------------------------- +// +// Banner Output +// +//--------------------------------------------------------------------------- +void Banner(int argc, char* argv[]) +{ + FPRT(stdout,"SCSI Target Emulator RaSCSI(*^..^*) "); + FPRT(stdout,"version %s (%s, %s)\n", + rascsi_get_version_string(), + __DATE__, + __TIME__); + FPRT(stdout,"Powered by XM6 TypeG Technology / "); + FPRT(stdout,"Copyright (C) 2016-2020 GIMONS\n"); + FPRT(stdout,"Copyright (C) 2020-2022 Contributors to the RaSCSI project\n"); + FPRT(stdout,"Connect type : %s\n", CONNECT_DESC); + + if ((argc > 1 && strcmp(argv[1], "-h") == 0) || + (argc > 1 && strcmp(argv[1], "--help") == 0)){ + FPRT(stdout,"\n"); + FPRT(stdout,"Usage: %s [-IDn FILE] ...\n\n", argv[0]); + FPRT(stdout," n is SCSI identification number(0-7).\n"); + FPRT(stdout," FILE is disk image file.\n\n"); + FPRT(stdout,"Usage: %s [-HDn FILE] ...\n\n", argv[0]); + FPRT(stdout," n is X68000 SASI HD number(0-15).\n"); + FPRT(stdout," FILE is disk image file, \"daynaport\", \"bridge\", \"printer\" or \"services\".\n\n"); + FPRT(stdout," Image type is detected based on file extension.\n"); + FPRT(stdout," hdf : SASI HD image (XM6 SASI HD image)\n"); + FPRT(stdout," hds : SCSI HD image (Non-removable generic SCSI HD image)\n"); + FPRT(stdout," hdr : SCSI HD image (Removable generic SCSI HD image)\n"); + FPRT(stdout," hdn : SCSI HD image (NEC GENUINE)\n"); + FPRT(stdout," hdi : SCSI HD image (Anex86 HD image)\n"); + FPRT(stdout," nhd : SCSI HD image (T98Next HD image)\n"); + FPRT(stdout," mos : SCSI MO image (MO image)\n"); + FPRT(stdout," iso : SCSI CD image (ISO 9660 image)\n"); + + exit(EXIT_SUCCESS); + } +} + +//--------------------------------------------------------------------------- +// +// Initialization +// +//--------------------------------------------------------------------------- + +bool InitService(int port) +{ + int result = pthread_mutex_init(&ctrl_mutex,NULL); + if (result != EXIT_SUCCESS){ + LOGERROR("Unable to create a mutex. Error code: %d", result); + return false; + } + + // Create socket for monitor + struct sockaddr_in server; + monsocket = socket(PF_INET, SOCK_STREAM, 0); + memset(&server, 0, sizeof(server)); + server.sin_family = PF_INET; + server.sin_port = htons(port); + server.sin_addr.s_addr = htonl(INADDR_ANY); + + // allow address reuse + int yes = 1; + if (setsockopt(monsocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) { + return false; + } + + signal(SIGPIPE, SIG_IGN); + + // Bind + if (bind(monsocket, (struct sockaddr *)&server, + sizeof(struct sockaddr_in)) < 0) { + FPRT(stderr, "Error: Already running?\n"); + return false; + } + + // Create Monitor Thread + pthread_create(&monthread, NULL, MonThread, NULL); + + // Interrupt handler settings + if (signal(SIGINT, KillHandler) == SIG_ERR) { + return false; + } + if (signal(SIGHUP, KillHandler) == SIG_ERR) { + return false; + } + if (signal(SIGTERM, KillHandler) == SIG_ERR) { + return false; + } + + running = false; + active = false; + + return true; +} + +bool InitBus() +{ + // GPIOBUS creation + bus = new GPIOBUS(); + + // GPIO Initialization + if (!bus->Init()) { + return false; + } + + // Bus Reset + bus->Reset(); + + return true; +} + +//--------------------------------------------------------------------------- +// +// Cleanup +// +//--------------------------------------------------------------------------- +void Cleanup() +{ + // Delete the disks + for (auto it = devices.begin(); it != devices.end(); ++it) { + if (*it) { + delete *it; + *it = NULL; + } + } + + // Delete the Controllers + for (auto it = controllers.begin(); it != controllers.end(); ++it) { + if (*it) { + delete *it; + *it = NULL; + } + } + + // Cleanup the Bus + if (bus) { + bus->Cleanup(); + + // Discard the GPIOBUS object + delete bus; + } + + // Close the monitor socket + if (monsocket >= 0) { + close(monsocket); + } + + pthread_mutex_destroy(&ctrl_mutex); +} + +//--------------------------------------------------------------------------- +// +// Reset +// +//--------------------------------------------------------------------------- +void Reset() +{ + // Reset all of the controllers + for (const auto& controller : controllers) { + if (controller) { + controller->Reset(); + } + } + + // Reset the bus + bus->Reset(); +} + +//--------------------------------------------------------------------------- +// +// Controller Mapping +// +//--------------------------------------------------------------------------- +bool MapController(Device **map) +{ + assert(bus); + + bool status = true; + + // Take ownership of the ctrl data structure + pthread_mutex_lock(&ctrl_mutex); + + // Replace the changed unit + for (size_t i = 0; i < controllers.size(); i++) { + for (int j = 0; j < UnitNum; j++) { + int unitno = i * UnitNum + j; + if (devices[unitno] != map[unitno]) { + // Check if the original unit exists + if (devices[unitno]) { + // Disconnect it from the controller + if (controllers[i]) { + controllers[i]->SetUnit(j, NULL); + } + + // Free the Unit + delete devices[unitno]; + } + + // Setup a new unit + devices[unitno] = map[unitno]; + } + } + } + + // Reconfigure all of the controllers + int i = 0; + for (auto it = controllers.begin(); it != controllers.end(); ++i, ++it) { + // Examine the unit configuration + int sasi_num = 0; + int scsi_num = 0; + for (int j = 0; j < UnitNum; j++) { + int unitno = i * UnitNum + j; + // branch by unit type + if (devices[unitno]) { + if (devices[unitno]->IsSASIHD()) { + // Drive is SASI, so increment SASI count + sasi_num++; + } else { + // Drive is SCSI, so increment SCSI count + scsi_num++; + } + } + + // Remove the unit + if (*it) { + (*it)->SetUnit(j, NULL); + } + } + + // If there are no units connected + if (!sasi_num && !scsi_num) { + if (*it) { + delete *it; + *it = NULL; + continue; + } + } + + // Mixture of SCSI and SASI + if (sasi_num > 0 && scsi_num > 0) { + status = false; + continue; + } + + if (sasi_num > 0) { + // Only SASI Unit(s) + + // Release the controller if it is not SASI + if (*it && !(*it)->IsSASI()) { + delete *it; + *it = NULL; + } + + // Create a new SASI controller + if (!*it) { + *it = new SASIDEV(); + (*it)->Connect(i, bus); + } + } else { + // Only SCSI Unit(s) + + // Release the controller if it is not SCSI + if (*it && !(*it)->IsSCSI()) { + delete *it; + *it = NULL; + } + + // Create a new SCSI controller + if (!*it) { + *it = new SCSIDEV(); + (*it)->Connect(i, bus); + } + } + + // connect all units + for (int j = 0; j < UnitNum; j++) { + int unitno = i * UnitNum + j; + if (devices[unitno]) { + // Add the unit connection + (*it)->SetUnit(j, (static_cast(devices[unitno]))); + } + } + } + pthread_mutex_unlock(&ctrl_mutex); + + return status; +} + +bool ReadAccessToken(const char *filename) +{ + struct stat st; + if (stat(filename, &st) || !S_ISREG(st.st_mode)) { + cerr << "Can't access token file '" << optarg << "'" << endl; + return false; + } + + if (st.st_uid || st.st_gid || (st.st_mode & (S_IROTH | S_IWOTH | S_IRGRP | S_IWGRP))) { + cerr << "Access token file '" << optarg << "' must be owned by root and readable by root only" << endl; + return false; + } + + ifstream token_file(filename, ifstream::in); + if (token_file.fail()) { + cerr << "Can't open access token file '" << optarg << "'" << endl; + return false; + } + + getline(token_file, access_token); + if (token_file.fail()) { + token_file.close(); + cerr << "Can't read access token file '" << optarg << "'" << endl; + return false; + } + + if (access_token.empty()) { + token_file.close(); + cerr << "Access token file '" << optarg << "' must not be empty" << endl; + return false; + } + + token_file.close(); + + return true; +} + +string ValidateLunSetup(const PbCommand& command, const vector& existing_devices) +{ + // Mapping of available LUNs (bit vector) to devices + map luns; + + // Collect LUN vectors of new devices + for (const auto& device : command.devices()) { + luns[device.id()] |= 1 << device.unit(); + } + + // Collect LUN vectors of existing devices + for (auto const& device : existing_devices) { + if (device) { + luns[device->GetId()] |= 1 << device->GetLun(); + } + } + + // LUNs must be consecutive + for (auto const& [id, lun]: luns) { + bool is_consecutive = false; + + uint32_t lun_vector = 0; + for (int i = 0; i < 32; i++) { + lun_vector |= 1 << i; + + if (lun == lun_vector) { + is_consecutive = true; + break; + } + } + + if (!is_consecutive) { + return "LUNs for device ID " + to_string(id) + " are not consecutive"; + } + } + + return ""; +} + +bool SetLogLevel(const string& log_level) +{ + if (log_level == "trace") { + set_level(level::trace); + } + else if (log_level == "debug") { + set_level(level::debug); + } + else if (log_level == "info") { + set_level(level::info); + } + else if (log_level == "warn") { + set_level(level::warn); + } + else if (log_level == "err") { + set_level(level::err); + } + else if (log_level == "critical") { + set_level(level::critical); + } + else if (log_level == "off") { + set_level(level::off); + } + else { + return false; + } + + current_log_level = log_level; + + LOGINFO("Set log level to '%s'", current_log_level.c_str()); + + return true; +} + +void LogDevices(const string& devices) +{ + stringstream ss(devices); + string line; + + while (getline(ss, line, '\n')) { + LOGINFO("%s", line.c_str()); + } +} + +string SetReservedIds(const string& ids) +{ + list ids_to_reserve; + stringstream ss(ids); + string id; + while (getline(ss, id, ',')) { + if (!id.empty()) { + ids_to_reserve.push_back(id); + } + } + + set reserved; + for (string id_to_reserve : ids_to_reserve) { + int id; + if (!GetAsInt(id_to_reserve, id) || id > 7) { + return "Invalid ID " + id_to_reserve; + } + + if (devices[id * UnitNum]) { + return "ID " + id_to_reserve + " is currently in use"; + } + + reserved.insert(id); + } + + reserved_ids = reserved; + + if (!reserved_ids.empty()) { + string s; + bool isFirst = true; + for (auto const& reserved_id : reserved_ids) { + if (!isFirst) { + s += ", "; + } + isFirst = false; + s += to_string(reserved_id); + } + + LOGINFO("Reserved ID(s) set to %s", s.c_str()); + } + else { + LOGINFO("Cleared reserved IDs"); + } + + return ""; +} + +void DetachAll() +{ + Device *map[devices.size()]; + for (size_t i = 0; i < devices.size(); i++) { + map[i] = NULL; + } + + if (MapController(map)) { + LOGINFO("Detached all devices"); + } + + FileSupport::UnreserveAll(); +} + +bool Attach(const CommandContext& context, const PbDeviceDefinition& pb_device, Device *map[], bool dryRun) +{ + const int id = pb_device.id(); + const int unit = pb_device.unit(); + const PbDeviceType type = pb_device.type(); + + if (map[id * UnitNum + unit]) { + return ReturnLocalizedError(context, ERROR_DUPLICATE_ID, to_string(id), to_string(unit)); + } + + string filename = GetParam(pb_device, "file"); + + // Create a new device, based on the provided type or filename + Device *device = device_factory.CreateDevice(type, filename); + if (!device) { + if (type == UNDEFINED) { + return ReturnLocalizedError(context, ERROR_MISSING_DEVICE_TYPE, filename); + } + else { + return ReturnLocalizedError(context, ERROR_UNKNOWN_DEVICE_TYPE, PbDeviceType_Name(type)); + } + } + + int supported_luns = device->GetSupportedLuns(); + if (unit >= supported_luns) { + delete device; + + string error = "Invalid unit " + to_string(unit) + " for device type " + PbDeviceType_Name(type); + if (supported_luns == 1) { + error += " (0)"; + } + else { + error += " (0-" + to_string(supported_luns -1) + ")"; + } + return ReturnStatus(context, false, error); + } + + // If no filename was provided the medium is considered removed + FileSupport *file_support = dynamic_cast(device); + if (file_support) { + device->SetRemoved(filename.empty()); + } + else { + device->SetRemoved(false); + } + + device->SetId(id); + device->SetLun(unit); + + try { + if (!pb_device.vendor().empty()) { + device->SetVendor(pb_device.vendor()); + } + if (!pb_device.product().empty()) { + device->SetProduct(pb_device.product()); + } + if (!pb_device.revision().empty()) { + device->SetRevision(pb_device.revision()); + } + } + catch(const illegal_argument_exception& e) { + return ReturnStatus(context, false, e.getmsg()); + } + + if (pb_device.block_size()) { + Disk *disk = dynamic_cast(device); + if (disk && disk->IsSectorSizeConfigurable()) { + if (!disk->SetConfiguredSectorSize(pb_device.block_size())) { + delete device; + + return ReturnLocalizedError(context, ERROR_BLOCK_SIZE, to_string(pb_device.block_size())); + } + } + else { + delete device; + + return ReturnLocalizedError(context, ERROR_BLOCK_SIZE_NOT_CONFIGURABLE, PbDeviceType_Name(type)); + } + } + + // File check (type is HD, for removable media drives, CD and MO the medium (=file) may be inserted later + if (file_support && !device->IsRemovable() && filename.empty()) { + delete device; + + return ReturnStatus(context, false, "Device type " + PbDeviceType_Name(type) + " requires a filename"); + } + + Filepath filepath; + if (file_support && !filename.empty()) { + filepath.SetPath(filename.c_str()); + string initial_filename = filepath.GetPath(); + + int id; + int unit; + if (FileSupport::GetIdsForReservedFile(filepath, id, unit)) { + delete device; + + return ReturnLocalizedError(context, ERROR_IMAGE_IN_USE, filename, to_string(id), to_string(unit)); + } + + try { + try { + file_support->Open(filepath); + } + catch(const file_not_found_exception&) { + // If the file does not exist search for it in the default image folder + filepath.SetPath(string(rascsi_image.GetDefaultImageFolder() + "/" + filename).c_str()); + + if (FileSupport::GetIdsForReservedFile(filepath, id, unit)) { + delete device; + + return ReturnLocalizedError(context, ERROR_IMAGE_IN_USE, filename, to_string(id), to_string(unit)); + } + + file_support->Open(filepath); + } + } + catch(const io_exception& e) { + delete device; + + return ReturnLocalizedError(context, ERROR_FILE_OPEN, initial_filename, e.getmsg()); + } + + file_support->ReserveFile(filepath, device->GetId(), device->GetLun()); + } + + // Only non read-only devices support protect/unprotect + // This operation must not be executed before Open() because Open() overrides some settings. + if (device->IsProtectable() && !device->IsReadOnly()) { + device->SetProtected(pb_device.protected_()); + } + + // Stop the dry run here, before permanently modifying something + if (dryRun) { + delete device; + + return true; + } + + std::map params = { pb_device.params().begin(), pb_device.params().end() }; + if (!device->SupportsFile()) { + params.erase("file"); + } + if (!device->Init(params)) { + delete device; + + return ReturnStatus(context, false, "Initialization of " + PbDeviceType_Name(type) + " device, ID " +to_string(id) + + ", unit " +to_string(unit) + " failed"); + } + + // Replace with the newly created unit + map[id * UnitNum + unit] = device; + + // Re-map the controller + if (MapController(map)) { + string msg = "Attached "; + if (device->IsReadOnly()) { + msg += "read-only "; + } + else if (device->IsProtectable() && device->IsProtected()) { + msg += "protected "; + } + msg += device->GetType() + " device, ID " + to_string(id) + ", unit " + to_string(unit); + LOGINFO("%s", msg.c_str()); + + return true; + } + + return ReturnLocalizedError(context, ERROR_SASI_SCSI); +} + +bool Detach(const CommandContext& context, Device *device, Device *map[], bool dryRun) +{ + if (!dryRun) { + for (auto const& d : devices) { + // Detach all LUNs equal to or higher than the LUN specified + if (d && d->GetId() == device->GetId() && d->GetLun() >= device->GetLun()) { + map[d->GetId() * UnitNum + d->GetLun()] = NULL; + + FileSupport *file_support = dynamic_cast(d); + if (file_support) { + file_support->UnreserveFile(); + } + + LOGINFO("Detached %s device with ID %d, unit %d", d->GetType().c_str(), d->GetId(), d->GetLun()); + } + } + + // Re-map the controller + MapController(map); + } + + return true; +} + +bool Insert(const CommandContext& context, const PbDeviceDefinition& pb_device, Device *device, bool dryRun) +{ + if (!device->IsRemoved()) { + return ReturnLocalizedError(context, ERROR_EJECT_REQUIRED); + } + + if (!pb_device.vendor().empty() || !pb_device.product().empty() || !pb_device.revision().empty()) { + return ReturnLocalizedError(context, ERROR_DEVICE_NAME_UPDATE); + } + + string filename = GetParam(pb_device, "file"); + if (filename.empty()) { + return ReturnLocalizedError(context, ERROR_MISSING_FILENAME); + } + + if (dryRun) { + return true; + } + + LOGINFO("Insert %sfile '%s' requested into %s ID %d, unit %d", pb_device.protected_() ? "protected " : "", + filename.c_str(), device->GetType().c_str(), pb_device.id(), pb_device.unit()); + + Disk *disk = dynamic_cast(device); + + if (pb_device.block_size()) { + if (disk && disk->IsSectorSizeConfigurable()) { + if (!disk->SetConfiguredSectorSize(pb_device.block_size())) { + return ReturnLocalizedError(context, ERROR_BLOCK_SIZE, to_string(pb_device.block_size())); + } + } + else { + return ReturnLocalizedError(context, ERROR_BLOCK_SIZE_NOT_CONFIGURABLE, device->GetType()); + } + } + + int id; + int unit; + Filepath filepath; + filepath.SetPath(filename.c_str()); + string initial_filename = filepath.GetPath(); + + if (FileSupport::GetIdsForReservedFile(filepath, id, unit)) { + return ReturnLocalizedError(context, ERROR_IMAGE_IN_USE, filename, to_string(id), to_string(unit)); + } + + FileSupport *file_support = dynamic_cast(device); + try { + try { + file_support->Open(filepath); + } + catch(const file_not_found_exception&) { + // If the file does not exist search for it in the default image folder + filepath.SetPath((rascsi_image.GetDefaultImageFolder() + "/" + filename).c_str()); + + if (FileSupport::GetIdsForReservedFile(filepath, id, unit)) { + return ReturnLocalizedError(context, ERROR_IMAGE_IN_USE, filename, to_string(id), to_string(unit)); + } + + file_support->Open(filepath); + } + } + catch(const io_exception& e) { + return ReturnLocalizedError(context, ERROR_FILE_OPEN, initial_filename, e.getmsg()); + } + + file_support->ReserveFile(filepath, device->GetId(), device->GetLun()); + + // Only non read-only devices support protect/unprotect. + // This operation must not be executed before Open() because Open() overrides some settings. + if (device->IsProtectable() && !device->IsReadOnly()) { + device->SetProtected(pb_device.protected_()); + } + + if (disk) { + disk->MediumChanged(); + } + + return true; +} + +void TerminationHandler(int signum) +{ + DetachAll(); + + Cleanup(); + + exit(signum); +} + +//--------------------------------------------------------------------------- +// +// Command Processing +// +//--------------------------------------------------------------------------- + +bool ProcessCmd(const CommandContext& context, const PbDeviceDefinition& pb_device, const PbCommand& command, bool dryRun) +{ + const int id = pb_device.id(); + const int unit = pb_device.unit(); + const PbDeviceType type = pb_device.type(); + const PbOperation operation = command.operation(); + const map params = { command.params().begin(), command.params().end() }; + + ostringstream s; + s << (dryRun ? "Validating: " : "Executing: "); + s << "operation=" << PbOperation_Name(operation); + + if (!params.empty()) { + s << ", command params="; + bool isFirst = true; + for (const auto& param: params) { + if (!isFirst) { + s << ", "; + } + isFirst = false; + string value = param.first != "token" ? param.second : "???"; + s << "'" << param.first << "=" << value << "'"; + } + } + + s << ", device id=" << id << ", unit=" << unit << ", type=" << PbDeviceType_Name(type); + + if (pb_device.params_size()) { + s << ", device params="; + bool isFirst = true; + for (const auto& param: pb_device.params()) { + if (!isFirst) { + s << ":"; + } + isFirst = false; + s << "'" << param.first << "=" << param.second << "'"; + } + } + + s << ", vendor='" << pb_device.vendor() << "', product='" << pb_device.product() + << "', revision='" << pb_device.revision() + << "', block size=" << pb_device.block_size(); + LOGINFO("%s", s.str().c_str()); + + // Check the Controller Number + if (id < 0) { + return ReturnLocalizedError(context, ERROR_MISSING_DEVICE_ID); + } + if (id >= CtrlMax) { + return ReturnStatus(context, false, "Invalid device ID " + to_string(id) + " (0-" + to_string(CtrlMax - 1) + ")"); + } + + if (operation == ATTACH && reserved_ids.find(id) != reserved_ids.end()) { + return ReturnLocalizedError(context, ERROR_RESERVED_ID, to_string(id)); + } + + // Check the Unit Number + if (unit < 0 || unit >= UnitNum) { + return ReturnStatus(context, false, "Invalid unit " + to_string(unit) + " (0-" + to_string(UnitNum - 1) + ")"); + } + + // Copy the devices + Device *map[devices.size()]; + for (size_t i = 0; i < devices.size(); i++) { + map[i] = devices[i]; + } + + if (operation == ATTACH) { + return Attach(context, pb_device, map, dryRun); + } + + // Does the controller exist? + if (!dryRun && !controllers[id]) { + return ReturnLocalizedError(context, ERROR_NON_EXISTING_DEVICE, to_string(id)); + } + + // Does the unit exist? + Device *device = devices[id * UnitNum + unit]; + if (!device) { + return ReturnLocalizedError(context, ERROR_NON_EXISTING_UNIT, to_string(id), to_string(unit)); + } + + if (operation == DETACH) { + return Detach(context, device, map, dryRun); + } + + if ((operation == START || operation == STOP) && !device->IsStoppable()) { + return ReturnStatus(context, false, PbOperation_Name(operation) + " operation denied (" + device->GetType() + " isn't stoppable)"); + } + + if ((operation == INSERT || operation == EJECT) && !device->IsRemovable()) { + return ReturnStatus(context, false, PbOperation_Name(operation) + " operation denied (" + device->GetType() + " isn't removable)"); + } + + if ((operation == PROTECT || operation == UNPROTECT) && !device->IsProtectable()) { + return ReturnStatus(context, false, PbOperation_Name(operation) + " operation denied (" + device->GetType() + " isn't protectable)"); + } + if ((operation == PROTECT || operation == UNPROTECT) && !device->IsReady()) { + return ReturnStatus(context, false, PbOperation_Name(operation) + " operation denied (" + device->GetType() + " isn't ready)"); + } + + switch (operation) { + case START: + if (!dryRun) { + LOGINFO("Start requested for %s ID %d, unit %d", device->GetType().c_str(), id, unit); + + if (!device->Start()) { + LOGWARN("Starting %s ID %d, unit %d failed", device->GetType().c_str(), id, unit); + } + } + break; + + case STOP: + if (!dryRun) { + LOGINFO("Stop requested for %s ID %d, unit %d", device->GetType().c_str(), id, unit); + + // STOP is idempotent + device->Stop(); + } + break; + + case INSERT: + return Insert(context, pb_device, device, dryRun); + + case EJECT: + if (!dryRun) { + LOGINFO("Eject requested for %s ID %d, unit %d", device->GetType().c_str(), id, unit); + + if (!device->Eject(true)) { + LOGWARN("Ejecting %s ID %d, unit %d failed", device->GetType().c_str(), id, unit); + } + } + break; + + case PROTECT: + if (!dryRun) { + LOGINFO("Write protection requested for %s ID %d, unit %d", device->GetType().c_str(), id, unit); + + // PROTECT is idempotent + device->SetProtected(true); + } + break; + + case UNPROTECT: + if (!dryRun) { + LOGINFO("Write unprotection requested for %s ID %d, unit %d", device->GetType().c_str(), id, unit); + + // UNPROTECT is idempotent + device->SetProtected(false); + } + break; + + case ATTACH: + case DETACH: + // The non dry-run case has been handled before the switch + assert(dryRun); + break; + + case CHECK_AUTHENTICATION: + case NO_OPERATION: + // Do nothing, just log + LOGTRACE("Received %s command", PbOperation_Name(operation).c_str()); + break; + + default: + return ReturnLocalizedError(context, ERROR_OPERATION); + } + + return true; +} + +bool ProcessCmd(const CommandContext& context, const PbCommand& command) +{ + switch (command.operation()) { + case DETACH_ALL: + DetachAll(); + return ReturnStatus(context); + + case RESERVE_IDS: { + const string ids = GetParam(command, "ids"); + string error = SetReservedIds(ids); + if (!error.empty()) { + return ReturnStatus(context, false, error); + } + + return ReturnStatus(context); + } + + case CREATE_IMAGE: + return rascsi_image.CreateImage(context, command); + + case DELETE_IMAGE: + return rascsi_image.DeleteImage(context, command); + + case RENAME_IMAGE: + return rascsi_image.RenameImage(context, command); + + case COPY_IMAGE: + return rascsi_image.CopyImage(context, command); + + case PROTECT_IMAGE: + case UNPROTECT_IMAGE: + return rascsi_image.SetImagePermissions(context, command); + + default: + // This is a device-specific command handled below + break; + } + + // Remember the list of reserved files, than run the dry run + const auto reserved_files = FileSupport::GetReservedFiles(); + for (const auto& device : command.devices()) { + if (!ProcessCmd(context, device, command, true)) { + // Dry run failed, restore the file list + FileSupport::SetReservedFiles(reserved_files); + return false; + } + } + + // Restore the list of reserved files before proceeding + FileSupport::SetReservedFiles(reserved_files); + + string result = ValidateLunSetup(command, devices); + if (!result.empty()) { + return ReturnStatus(context, false, result); + } + + for (const auto& device : command.devices()) { + if (!ProcessCmd(context, device, command, false)) { + return false; + } + } + + // ATTACH and DETACH return the device list + if (context.fd != -1 && (command.operation() == ATTACH || command.operation() == DETACH)) { + // A new command with an empty device list is required here in order to return data for all devices + PbCommand command; + PbResult result; + rascsi_response.GetDevicesInfo(result, command, devices, UnitNum); + SerializeMessage(context.fd, result); + return true; + } + + return ReturnStatus(context); +} + +bool ProcessId(const string id_spec, PbDeviceType type, int& id, int& unit) +{ + size_t separator_pos = id_spec.find(COMPONENT_SEPARATOR); + if (separator_pos == string::npos) { + int max_id = type == SAHD ? 16 : 8; + + if (!GetAsInt(id_spec, id) || id < 0 || id >= max_id) { + cerr << optarg << ": Invalid device ID (0-" << (max_id - 1) << ")" << endl; + return false; + } + + // Required for SASI ID/LUN handling backwards compatibility + unit = 0; + if (type == SAHD) { + unit = id % 2; + id /= 2; + } + } + else { + int max_unit = type == SAHD ? 2 : UnitNum; + + if (!GetAsInt(id_spec.substr(0, separator_pos), id) || id < 0 || id > 7 || + !GetAsInt(id_spec.substr(separator_pos + 1), unit) || unit < 0 || unit >= max_unit) { + cerr << optarg << ": Invalid unit (0-" << (max_unit - 1) << ")" << endl; + return false; + } + } + + return true; +} + +void ShutDown(const CommandContext& context, const string& mode) { + if (mode.empty()) { + ReturnLocalizedError(context, ERROR_SHUTDOWN_MODE_MISSING); + return; + } + + PbResult result; + result.set_status(true); + + if (mode == "rascsi") { + LOGINFO("RaSCSI shutdown requested"); + + SerializeMessage(context.fd, result); + + TerminationHandler(0); + } + + // The root user has UID 0 + if (getuid()) { + ReturnLocalizedError(context, ERROR_SHUTDOWN_PERMISSION); + return; + } + + if (mode == "system") { + LOGINFO("System shutdown requested"); + + SerializeMessage(context.fd, result); + + DetachAll(); + + if (system("init 0") == -1) { + LOGERROR("System shutdown failed: %s", strerror(errno)); + } + } + else if (mode == "reboot") { + LOGINFO("System reboot requested"); + + SerializeMessage(context.fd, result); + + DetachAll(); + + if (system("init 6") == -1) { + LOGERROR("System reboot failed: %s", strerror(errno)); + } + } + else { + ReturnLocalizedError(context, ERROR_SHUTDOWN_MODE_INVALID); + } +} + +//--------------------------------------------------------------------------- +// +// Argument Parsing +// +//--------------------------------------------------------------------------- +bool ParseArgument(int argc, char* argv[], int& port) +{ + PbCommand command; + int id = -1; + int unit = -1; + PbDeviceType type = UNDEFINED; + int block_size = 0; + string name; + string log_level; + + string locale = setlocale(LC_MESSAGES, ""); + if (locale == "C") { + locale = "en"; + } + + opterr = 1; + int opt; + while ((opt = getopt(argc, argv, "-IiHhb:d:n:p:r:t:z:D:F:L:P:R:c:C")) != -1) { + switch (opt) { + // The three options below are kind of a compound option with two letters + case 'i': + case 'I': + id = -1; + unit = -1; + continue; + + case 'h': + case 'H': + id = -1; + unit = -1; + type = SAHD; + continue; + + case 'd': + case 'D': { + if (!ProcessId(optarg, type, id, unit)) { + return false; + } + continue; + } + + case 'b': { + if (!GetAsInt(optarg, block_size)) { + cerr << "Invalid block size " << optarg << endl; + return false; + } + continue; + } + + case 'z': + locale = optarg; + continue; + + case 'F': { + string result = rascsi_image.SetDefaultImageFolder(optarg); + if (!result.empty()) { + cerr << result << endl; + return false; + } + continue; + } + + case 'L': + log_level = optarg; + continue; + + case 'R': + int depth; + if (!GetAsInt(optarg, depth) || depth < 0) { + cerr << "Invalid image file scan depth " << optarg << endl; + return false; + } + rascsi_image.SetDepth(depth); + continue; + + case 'n': + name = optarg; + continue; + + case 'p': + if (!GetAsInt(optarg, port) || port <= 0 || port > 65535) { + cerr << "Invalid port " << optarg << ", port must be between 1 and 65535" << endl; + return false; + } + continue; + + case 'P': + if (!ReadAccessToken(optarg)) { + return false; + } + continue; + + case 'r': { + string error = SetReservedIds(optarg); + if (!error.empty()) { + cerr << error << endl; + return false; + } + } + continue; + + case 't': { + string t = optarg; + transform(t.begin(), t.end(), t.begin(), ::toupper); + if (!PbDeviceType_Parse(t, &type)) { + cerr << "Illegal device type '" << optarg << "'" << endl; + return false; + } + } + continue; + + case 'c' : + case 'C' : + { + string cache_mode = optarg; + transform(cache_mode.begin(), cache_mode.end(), cache_mode.begin(), ::toupper); + LOGWARN("Cache mode %s", cache_mode.c_str()); + switch(cache_mode.at(0)){ + case 'R': + DiskImageHandleFactory::SetFileAccessMethod(DiskImageHandleType::eRamCache); + break; + case 'P': + DiskImageHandleFactory::SetFileAccessMethod(DiskImageHandleType::ePosixFile); + break; + case 'M': + DiskImageHandleFactory::SetFileAccessMethod(DiskImageHandleType::eMmapFile); + break; + default: + LOGWARN("Invalid cache mode: %s Expected \"RAM, Posix or Mmmap\"", cache_mode.c_str()); + } + } + continue; + default: + return false; + + case 1: + // Encountered filename + break; + } + + if (optopt) { + return false; + } + + // Set up the device data + PbDeviceDefinition *device = command.add_devices(); + device->set_id(id); + device->set_unit(unit); + device->set_type(type); + device->set_block_size(block_size); + + ParseParameters(*device, optarg); + + size_t separator_pos = name.find(COMPONENT_SEPARATOR); + if (separator_pos != string::npos) { + device->set_vendor(name.substr(0, separator_pos)); + name = name.substr(separator_pos + 1); + separator_pos = name.find(COMPONENT_SEPARATOR); + if (separator_pos != string::npos) { + device->set_product(name.substr(0, separator_pos)); + device->set_revision(name.substr(separator_pos + 1)); + } + else { + device->set_product(name); + } + } + else { + device->set_vendor(name); + } + + id = -1; + type = UNDEFINED; + block_size = 0; + name = ""; + } + + if (!log_level.empty() && !SetLogLevel(log_level)) { + LOGWARN("Invalid log level '%s'", log_level.c_str()); + } + + // Attach all specified devices + command.set_operation(ATTACH); + + CommandContext context; + context.fd = -1; + context.locale = locale; + if (!ProcessCmd(context, command)) { + return false; + } + + // Display and log the device list + PbServerInfo server_info; + rascsi_response.GetDevices(server_info, devices); + const list& devices = { server_info.devices_info().devices().begin(), server_info.devices_info().devices().end() }; + const string device_list = ListDevices(devices); + LogDevices(device_list); + cout << device_list << endl; + + return true; +} + +//--------------------------------------------------------------------------- +// +// Pin the thread to a specific CPU +// +//--------------------------------------------------------------------------- +void FixCpu(int cpu) +{ + // Get the number of CPUs + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + sched_getaffinity(0, sizeof(cpu_set_t), &cpuset); + int cpus = CPU_COUNT(&cpuset); + + // Set the thread affinity + if (cpu < cpus) { + CPU_ZERO(&cpuset); + CPU_SET(cpu, &cpuset); + sched_setaffinity(0, sizeof(cpu_set_t), &cpuset); + } +} + +//--------------------------------------------------------------------------- +// +// Monitor Thread +// +//--------------------------------------------------------------------------- +static void *MonThread(void *param) +{ + // Scheduler Settings + struct sched_param schedparam; + schedparam.sched_priority = 0; + sched_setscheduler(0, SCHED_IDLE, &schedparam); + + // Set the affinity to a specific processor core + FixCpu(2); + + // Wait for the execution to start + while (!running) { + usleep(1); + } + + // Set up the monitor socket to receive commands + listen(monsocket, 1); + + while (true) { + CommandContext context; + context.fd = -1; + + try { + // Wait for connection + struct sockaddr_in client; + socklen_t socklen = sizeof(client); + memset(&client, 0, socklen); + context.fd = accept(monsocket, (struct sockaddr*)&client, &socklen); + if (context.fd < 0) { + throw io_exception("accept() failed"); + } + + // Read magic string + char magic[6]; + int bytes_read = ReadNBytes(context.fd, (uint8_t *)magic, sizeof(magic)); + if (!bytes_read) { + continue; + } + if (bytes_read != sizeof(magic) || strncmp(magic, "RASCSI", sizeof(magic))) { + throw io_exception("Invalid magic"); + } + + // Fetch the command + PbCommand command; + DeserializeMessage(context.fd, command); + + context.locale = GetParam(command, "locale"); + if (context.locale.empty()) { + context.locale = "en"; + } + + if (!access_token.empty() && access_token != GetParam(command, "token")) { + ReturnLocalizedError(context, ERROR_AUTHENTICATION, UNAUTHORIZED); + continue; + } + + if (!PbOperation_IsValid(command.operation())) { + LOGERROR("Received unknown command with operation opcode %d", command.operation()); + + ReturnLocalizedError(context, ERROR_OPERATION, UNKNOWN_OPERATION); + continue; + } + + LOGTRACE("Received %s command", PbOperation_Name(command.operation()).c_str()); + + PbResult result; + + switch(command.operation()) { + case LOG_LEVEL: { + string log_level = GetParam(command, "level"); + bool status = SetLogLevel(log_level); + if (!status) { + ReturnLocalizedError(context, ERROR_LOG_LEVEL, log_level); + } + else { + ReturnStatus(context); + } + break; + } + + case DEFAULT_FOLDER: { + string result = rascsi_image.SetDefaultImageFolder(GetParam(command, "folder")); + if (!result.empty()) { + ReturnStatus(context, false, result); + } + else { + ReturnStatus(context); + } + break; + } + + case DEVICES_INFO: { + rascsi_response.GetDevicesInfo(result, command, devices, UnitNum); + SerializeMessage(context.fd, result); + break; + } + + case DEVICE_TYPES_INFO: { + result.set_allocated_device_types_info(rascsi_response.GetDeviceTypesInfo(result, command)); + SerializeMessage(context.fd, result); + break; + } + + case SERVER_INFO: { + result.set_allocated_server_info(rascsi_response.GetServerInfo( + result, devices, reserved_ids, current_log_level, GetParam(command, "folder_pattern"), + GetParam(command, "file_pattern"), rascsi_image.GetDepth())); + SerializeMessage(context.fd, result); + break; + } + + case VERSION_INFO: { + result.set_allocated_version_info(rascsi_response.GetVersionInfo(result)); + SerializeMessage(context.fd, result); + break; + } + + case LOG_LEVEL_INFO: { + result.set_allocated_log_level_info(rascsi_response.GetLogLevelInfo(result, current_log_level)); + SerializeMessage(context.fd, result); + break; + } + + case DEFAULT_IMAGE_FILES_INFO: { + result.set_allocated_image_files_info(rascsi_response.GetAvailableImages(result, + GetParam(command, "folder_pattern"), GetParam(command, "file_pattern"), + rascsi_image.GetDepth())); + SerializeMessage(context.fd, result); + break; + } + + case IMAGE_FILE_INFO: { + string filename = GetParam(command, "file"); + if (filename.empty()) { + ReturnLocalizedError(context, ERROR_MISSING_FILENAME); + } + else { + PbImageFile* image_file = new PbImageFile(); + bool status = rascsi_response.GetImageFile(image_file, filename); + if (status) { + result.set_status(true); + result.set_allocated_image_file_info(image_file); + SerializeMessage(context.fd, result); + } + else { + ReturnStatus(context, false, "Can't get image file info for '" + filename + "'"); + } + } + break; + } + + case NETWORK_INTERFACES_INFO: { + result.set_allocated_network_interfaces_info(rascsi_response.GetNetworkInterfacesInfo(result)); + SerializeMessage(context.fd, result); + break; + } + + case MAPPING_INFO: { + result.set_allocated_mapping_info(rascsi_response.GetMappingInfo(result)); + SerializeMessage(context.fd, result); + break; + } + + case OPERATION_INFO: { + result.set_allocated_operation_info(rascsi_response.GetOperationInfo(result, + rascsi_image.GetDepth())); + SerializeMessage(context.fd, result); + break; + } + + case RESERVED_IDS_INFO: { + result.set_allocated_reserved_ids_info(rascsi_response.GetReservedIds(result, reserved_ids)); + SerializeMessage(context.fd, result); + break; + } + + case SHUT_DOWN: { + ShutDown(context, GetParam(command, "mode")); + break; + } + + default: { + // Wait until we become idle + while (active) { + usleep(500 * 1000); + } + + ProcessCmd(context, command); + break; + } + } + } + catch(const io_exception& e) { + LOGWARN("%s", e.getmsg().c_str()); + + // Fall through + } + + if (context.fd >= 0) { + close(context.fd); + } + } + + return NULL; +} + +//--------------------------------------------------------------------------- +// +// Main processing +// +//--------------------------------------------------------------------------- +int main(int argc, char* argv[]) +{ + GOOGLE_PROTOBUF_VERIFY_VERSION; + +#ifndef NDEBUG + // Get temporary operation info, in order to trigger an assertion on startup if the operation list is incomplete + PbResult pb_operation_info_result; + const PbOperationInfo *operation_info = rascsi_response.GetOperationInfo(pb_operation_info_result, 0); + assert(operation_info->operations_size() == PbOperation_ARRAYSIZE - 1); + delete operation_info; +#endif + + int actid; + BUS::phase_t phase; + // added setvbuf to override stdout buffering, so logs are written immediately and not when the process exits. + setvbuf(stdout, NULL, _IONBF, 0); + struct sched_param schparam; + + // Output the Banner + Banner(argc, argv); + + // ParseArgument() requires the bus to have been initialized first, which requires the root user. + // The -v option should be available for any user, which requires special handling. + for (int i = 1 ; i < argc; i++) { + if (!strcasecmp(argv[i], "-v")) { + cout << rascsi_get_version_string() << endl; + return 0; + } + } + + SetLogLevel("info"); + + // Create a thread-safe stdout logger to process the log messages + auto logger = stdout_color_mt("rascsi stdout logger"); + + int port = 6868; + + if (!InitBus()) { + return EPERM; + } + + if (!ParseArgument(argc, argv, port)) { + Cleanup(); + return -1; + } + + if (!InitService(port)) { + return EPERM; + } + + // Signal handler to detach all devices on a KILL or TERM signal + struct sigaction termination_handler; + termination_handler.sa_handler = TerminationHandler; + sigemptyset(&termination_handler.sa_mask); + termination_handler.sa_flags = 0; + sigaction(SIGINT, &termination_handler, NULL); + sigaction(SIGTERM, &termination_handler, NULL); + + // Reset + Reset(); + + // Set the affinity to a specific processor core + FixCpu(3); + +#ifdef USE_SEL_EVENT_ENABLE + // Scheduling policy setting (highest priority) + schparam.sched_priority = sched_get_priority_max(SCHED_FIFO); + sched_setscheduler(0, SCHED_FIFO, &schparam); +#endif // USE_SEL_EVENT_ENABLE + + // Start execution + running = true; + + // Main Loop + while (running) { + // Work initialization + actid = -1; + phase = BUS::busfree; + +#ifdef USE_SEL_EVENT_ENABLE + // SEL signal polling + if (bus->PollSelectEvent() < 0) { + // Stop on interrupt + if (errno == EINTR) { + break; + } + continue; + } + + // Get the bus + bus->Aquire(); +#else + bus->Aquire(); + if (!bus->GetSEL()) { + usleep(0); + continue; + } +#endif // USE_SEL_EVENT_ENABLE + + // Wait until BSY is released as there is a possibility for the + // initiator to assert it while setting the ID (for up to 3 seconds) + if (bus->GetBSY()) { + int now = SysTimer::GetTimerLow(); + while ((SysTimer::GetTimerLow() - now) < 3 * 1000 * 1000) { + bus->Aquire(); + if (!bus->GetBSY()) { + break; + } + } + } + + // Stop because the bus is busy or another device responded + if (bus->GetBSY() || !bus->GetSEL()) { + continue; + } + + pthread_mutex_lock(&ctrl_mutex); + + BYTE data = bus->GetDAT(); + + int initiator_id = -1; + + // Notify all controllers + int i = 0; + for (auto it = controllers.begin(); it != controllers.end(); ++i, ++it) { + if (!*it || (data & (1 << i)) == 0) { + continue; + } + + // Extract the SCSI initiator ID + int tmp = data - (1 << i); + if (tmp) { + initiator_id = 0; + for (int j = 0; j < 8; j++) { + tmp >>= 1; + if (tmp) { + initiator_id++; + } + else { + break; + } + } + } + + // Find the target that has moved to the selection phase + if ((*it)->Process(initiator_id) == BUS::selection) { + // Get the target ID + actid = i; + + // Bus Selection phase + phase = BUS::selection; + break; + } + } + + // Return to bus monitoring if the selection phase has not started + if (phase != BUS::selection) { + pthread_mutex_unlock(&ctrl_mutex); + continue; + } + + // Start target device + active = true; + +#ifndef USE_SEL_EVENT_ENABLE + // Scheduling policy setting (highest priority) + schparam.sched_priority = sched_get_priority_max(SCHED_FIFO); + sched_setscheduler(0, SCHED_FIFO, &schparam); +#endif // USE_SEL_EVENT_ENABLE + + // Loop until the bus is free + while (running) { + // Target drive + phase = controllers[actid]->Process(initiator_id); + + // End when the bus is free + if (phase == BUS::busfree) { + break; + } + } + pthread_mutex_unlock(&ctrl_mutex); + + +#ifndef USE_SEL_EVENT_ENABLE + // Set the scheduling priority back to normal + schparam.sched_priority = 0; + sched_setscheduler(0, SCHED_OTHER, &schparam); +#endif // USE_SEL_EVENT_ENABLE + + // End the target travel + active = false; + } + + return 0; +} diff --git a/src_old/raspberrypi/rascsi_image.cpp b/src_old/raspberrypi/rascsi_image.cpp new file mode 100644 index 00000000..b171a759 --- /dev/null +++ b/src_old/raspberrypi/rascsi_image.cpp @@ -0,0 +1,406 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2021 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include +#include +#include "os.h" +#include "log.h" +#include "filepath.h" +#include "spdlog/spdlog.h" +#include "devices/file_support.h" +#include "protobuf_util.h" +#include "rascsi_image.h" +#include +#include + +using namespace std; +using namespace spdlog; +using namespace rascsi_interface; +using namespace protobuf_util; + +#define FPRT(fp, ...) fprintf(fp, __VA_ARGS__ ) + +RascsiImage::RascsiImage() +{ + // ~/images is the default folder for device image files, for the root user it is /home/pi/images + int uid = getuid(); + const char *sudo_user = getenv("SUDO_UID"); + if (sudo_user) { + uid = stoi(sudo_user); + } + + const passwd *passwd = getpwuid(uid); + if (uid && passwd) { + default_image_folder = passwd->pw_dir; + default_image_folder += "/images"; + } + else { + default_image_folder = "/home/pi/images"; + } + + depth = 1; +} + +bool RascsiImage::CheckDepth(const string& filename) +{ + return count(filename.begin(), filename.end(), '/') <= depth; +} + +bool RascsiImage::CreateImageFolder(const CommandContext& context, const string& filename) +{ + size_t filename_start = filename.rfind('/'); + if (filename_start != string::npos) { + string folder = filename.substr(0, filename_start); + + // Checking for existence first prevents an error if the top-level folder is a softlink + struct stat st; + if (stat(folder.c_str(), &st)) { + std::error_code error; + filesystem::create_directories(folder, error); + if (error) { + ReturnStatus(context, false, "Can't create image folder '" + folder + "': " + strerror(errno)); + return false; + } + } + } + + return true; +} + +string RascsiImage::SetDefaultImageFolder(const string& f) +{ + if (f.empty()) { + return "Can't set default image folder: Missing folder name"; + } + + string folder = f; + + // If a relative path is specified the path is assumed to be relative to the user's home directory + if (folder[0] != '/') { + int uid = getuid(); + const char *sudo_user = getenv("SUDO_UID"); + if (sudo_user) { + uid = stoi(sudo_user); + } + + const passwd *passwd = getpwuid(uid); + if (passwd) { + folder = passwd->pw_dir; + folder += "/"; + folder += f; + } + } + else { + if (folder.find("/home/") != 0) { + return "Default image folder must be located in '/home/'"; + } + } + + struct stat info; + stat(folder.c_str(), &info); + if (!S_ISDIR(info.st_mode) || access(folder.c_str(), F_OK) == -1) { + return "Folder '" + f + "' does not exist or is not accessible"; + } + + default_image_folder = folder; + + LOGINFO("Default image folder set to '%s'", default_image_folder.c_str()); + + return ""; +} + +bool RascsiImage::IsValidSrcFilename(const string& filename) +{ + // Source file must exist and must be a regular file or a symlink + struct stat st; + return !stat(filename.c_str(), &st) && (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)); +} + +bool RascsiImage::IsValidDstFilename(const string& filename) +{ + // Destination file must not yet exist + struct stat st; + return stat(filename.c_str(), &st); +} + +bool RascsiImage::CreateImage(const CommandContext& context, const PbCommand& command) +{ + string filename = GetParam(command, "file"); + if (filename.empty()) { + return ReturnStatus(context, false, "Can't create image file: Missing image filename"); + } + + if (!CheckDepth(filename)) { + return ReturnStatus(context, false, ("Invalid folder hierarchy depth '" + filename + "'").c_str()); + } + + string full_filename = default_image_folder + "/" + filename; + if (!IsValidDstFilename(full_filename)) { + return ReturnStatus(context, false, "Can't create image file: '" + full_filename + "': File already exists"); + } + + const string size = GetParam(command, "size"); + if (size.empty()) { + return ReturnStatus(context, false, "Can't create image file '" + full_filename + "': Missing image size"); + } + + off_t len; + try { + len = stoull(size); + } + catch(const invalid_argument& e) { + return ReturnStatus(context, false, "Can't create image file '" + full_filename + "': Invalid file size " + size); + } + catch(const out_of_range& e) { + return ReturnStatus(context, false, "Can't create image file '" + full_filename + "': Invalid file size " + size); + } + if (len < 512 || (len & 0x1ff)) { + return ReturnStatus(context, false, "Invalid image file size " + to_string(len) + " (not a multiple of 512)"); + } + + if (!CreateImageFolder(context, full_filename)) { + return false; + } + + string permission = GetParam(command, "read_only"); + // Since rascsi is running as root ensure that others can access the file + int permissions = !strcasecmp(permission.c_str(), "true") ? + S_IRUSR | S_IRGRP | S_IROTH : S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + + int image_fd = open(full_filename.c_str(), O_CREAT|O_WRONLY, permissions); + if (image_fd == -1) { + return ReturnStatus(context, false, "Can't create image file '" + full_filename + "': " + string(strerror(errno))); + } + + if (fallocate(image_fd, 0, 0, len)) { + close(image_fd); + + unlink(full_filename.c_str()); + + return ReturnStatus(context, false, "Can't allocate space for image file '" + full_filename + "': " + string(strerror(errno))); + } + + close(image_fd); + + LOGINFO("%s", string("Created " + string(permissions & S_IWUSR ? "": "read-only ") + "image file '" + full_filename + + "' with a size of " + to_string(len) + " bytes").c_str()); + + return ReturnStatus(context); +} + +bool RascsiImage::DeleteImage(const CommandContext& context, const PbCommand& command) +{ + string filename = GetParam(command, "file"); + if (filename.empty()) { + return ReturnStatus(context, false, "Missing image filename"); + } + + if (!CheckDepth(filename)) { + return ReturnStatus(context, false, ("Invalid folder hierarchy depth '" + filename + "'").c_str()); + } + + string full_filename = default_image_folder + "/" + filename; + + int id; + int unit; + Filepath filepath; + filepath.SetPath(full_filename.c_str()); + if (FileSupport::GetIdsForReservedFile(filepath, id, unit)) { + return ReturnStatus(context, false, "Can't delete image file '" + full_filename + + "', it is currently being used by device ID " + to_string(id) + ", unit " + to_string(unit)); + } + + if (remove(full_filename.c_str())) { + return ReturnStatus(context, false, "Can't delete image file '" + full_filename + "': " + string(strerror(errno))); + } + + // Delete empty subfolders + size_t last_slash = filename.rfind('/'); + while (last_slash != string::npos) { + string folder = filename.substr(0, last_slash); + string full_folder = default_image_folder + "/" + folder; + + std::error_code error; + if (!filesystem::is_empty(full_folder, error) || error) { + break; + } + + if (remove(full_folder.c_str())) { + return ReturnStatus(context, false, "Can't delete empty image folder '" + full_folder + "'"); + } + + last_slash = folder.rfind('/'); + } + + LOGINFO("Deleted image file '%s'", full_filename.c_str()); + + return ReturnStatus(context); +} + +bool RascsiImage::RenameImage(const CommandContext& context, const PbCommand& command) +{ + string from = GetParam(command, "from"); + if (from.empty()) { + return ReturnStatus(context, false, "Can't rename/move image file: Missing source filename"); + } + + if (!CheckDepth(from)) { + return ReturnStatus(context, false, ("Invalid folder hierarchy depth '" + from + "'").c_str()); + } + + from = default_image_folder + "/" + from; + if (!IsValidSrcFilename(from)) { + return ReturnStatus(context, false, "Can't rename/move image file: '" + from + "': Invalid name or type"); + } + + string to = GetParam(command, "to"); + if (to.empty()) { + return ReturnStatus(context, false, "Can't rename/move image file '" + from + "': Missing destination filename"); + } + + if (!CheckDepth(to)) { + return ReturnStatus(context, false, ("Invalid folder hierarchy depth '" + to + "'").c_str()); + } + + to = default_image_folder + "/" + to; + if (!IsValidDstFilename(to)) { + return ReturnStatus(context, false, "Can't rename/move image file '" + from + "' to '" + to + "': File already exists"); + } + + if (!CreateImageFolder(context, to)) { + return false; + } + + if (rename(from.c_str(), to.c_str())) { + return ReturnStatus(context, false, "Can't rename/move image file '" + from + "' to '" + to + "': " + string(strerror(errno))); + } + + LOGINFO("Renamed/Moved image file '%s' to '%s'", from.c_str(), to.c_str()); + + return ReturnStatus(context); +} + +bool RascsiImage::CopyImage(const CommandContext& context, const PbCommand& command) +{ + string from = GetParam(command, "from"); + if (from.empty()) { + return ReturnStatus(context, false, "Can't copy image file: Missing source filename"); + } + + if (!CheckDepth(from)) { + return ReturnStatus(context, false, ("Invalid folder hierarchy depth '" + from + "'").c_str()); + } + + from = default_image_folder + "/" + from; + if (!IsValidSrcFilename(from)) { + return ReturnStatus(context, false, "Can't copy image file: '" + from + "': Invalid name or type"); + } + + string to = GetParam(command, "to"); + if (to.empty()) { + return ReturnStatus(context, false, "Can't copy image file '" + from + "': Missing destination filename"); + } + + if (!CheckDepth(to)) { + return ReturnStatus(context, false, ("Invalid folder hierarchy depth '" + to + "'").c_str()); + } + + to = default_image_folder + "/" + to; + if (!IsValidDstFilename(to)) { + return ReturnStatus(context, false, "Can't copy image file '" + from + "' to '" + to + "': File already exists"); + } + + struct stat st; + if (lstat(from.c_str(), &st)) { + return ReturnStatus(context, false, "Can't access source image file '" + from + "': " + string(strerror(errno))); + } + + if (!CreateImageFolder(context, to)) { + return false; + } + + // Symbolic links need a special handling + if ((st.st_mode & S_IFMT) == S_IFLNK) { + if (symlink(filesystem::read_symlink(from).c_str(), to.c_str())) { + return ReturnStatus(context, false, "Can't copy symlink '" + from + "': " + string(strerror(errno))); + } + + LOGINFO("Copied symlink '%s' to '%s'", from.c_str(), to.c_str()); + + return ReturnStatus(context); + } + + int fd_src = open(from.c_str(), O_RDONLY, 0); + if (fd_src == -1) { + return ReturnStatus(context, false, "Can't open source image file '" + from + "': " + string(strerror(errno))); + } + + string permission = GetParam(command, "read_only"); + // Since rascsi is running as root ensure that others can access the file + int permissions = !strcasecmp(permission.c_str(), "true") ? + S_IRUSR | S_IRGRP | S_IROTH : S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + + int fd_dst = open(to.c_str(), O_WRONLY | O_CREAT, permissions); + if (fd_dst == -1) { + close(fd_src); + + return ReturnStatus(context, false, "Can't open destination image file '" + to + "': " + string(strerror(errno))); + } + + if (sendfile(fd_dst, fd_src, 0, st.st_size) == -1) { + close(fd_dst); + close(fd_src); + + unlink(to.c_str()); + + return ReturnStatus(context, false, "Can't copy image file '" + from + "' to '" + to + "': " + string(strerror(errno))); + } + + close(fd_dst); + close(fd_src); + + LOGINFO("Copied image file '%s' to '%s'", from.c_str(), to.c_str()); + + return ReturnStatus(context); +} + +bool RascsiImage::SetImagePermissions(const CommandContext& context, const PbCommand& command) +{ + string filename = GetParam(command, "file"); + if (filename.empty()) { + return ReturnStatus(context, false, "Missing image filename"); + } + + if (!CheckDepth(filename)) { + return ReturnStatus(context, false, ("Invalid folder hierarchy depth '" + filename + "'").c_str()); + } + + filename = default_image_folder + "/" + filename; + if (!IsValidSrcFilename(filename)) { + return ReturnStatus(context, false, "Can't modify image file '" + filename + "': Invalid name or type"); + } + + bool protect = command.operation() == PROTECT_IMAGE; + + int permissions = protect ? S_IRUSR | S_IRGRP | S_IROTH : S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + + if (chmod(filename.c_str(), permissions) == -1) { + return ReturnStatus(context, false, "Can't " + string(protect ? "protect" : "unprotect") + " image file '" + filename + "': " + + strerror(errno)); + } + + if (protect) { + LOGINFO("Protected image file '%s'", filename.c_str()); + } + else { + LOGINFO("Unprotected image file '%s'", filename.c_str()); + } + + return ReturnStatus(context); +} diff --git a/src_old/raspberrypi/rascsi_image.h b/src_old/raspberrypi/rascsi_image.h new file mode 100644 index 00000000..be1d828a --- /dev/null +++ b/src_old/raspberrypi/rascsi_image.h @@ -0,0 +1,44 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2021 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "rascsi_interface.pb.h" +#include "command_context.h" +#include + +using namespace std; +using namespace rascsi_interface; + +class RascsiImage +{ +public: + + RascsiImage(); + ~RascsiImage() {}; + + void SetDepth(int depth) { this->depth = depth; } + int GetDepth() { return depth; } + bool CheckDepth(const string&); + bool CreateImageFolder(const CommandContext&, const string&); + string GetDefaultImageFolder() const { return default_image_folder; } + string SetDefaultImageFolder(const string&); + bool IsValidSrcFilename(const string&); + bool IsValidDstFilename(const string&); + bool CreateImage(const CommandContext&, const PbCommand&); + bool DeleteImage(const CommandContext&, const PbCommand&); + bool RenameImage(const CommandContext&, const PbCommand&); + bool CopyImage(const CommandContext&, const PbCommand&); + bool SetImagePermissions(const CommandContext&, const PbCommand&); + +private: + + string default_image_folder; + int depth; +}; diff --git a/src_old/raspberrypi/rascsi_interface.proto b/src_old/raspberrypi/rascsi_interface.proto new file mode 100644 index 00000000..5207b7a2 --- /dev/null +++ b/src_old/raspberrypi/rascsi_interface.proto @@ -0,0 +1,421 @@ +// +// Each rascsi message sent to the rascsi server is preceded by the magic string "RASCSI". +// A message starts with a little endian 32 bit header which contains the protobuf message size. +// Unless explicitly specified the order of repeated data returned is undefined. +// All operations accept an optional access token, specified by the "token" parameter. +// All operations also accept an optional locale, specified with the "locale" parameter. If there is +// a localized error message rascsi returns it, with English being the fallback language. +// + +syntax = "proto3"; + +package rascsi_interface; + +// The available device types +enum PbDeviceType { + UNDEFINED = 0; + // Non-removable SASI drive + SAHD = 1; + // Non-removable SCSI drive + SCHD = 2; + // Removable SCSI drive + SCRM = 3; + // Magnoto-Optical drive + SCMO = 4; + // CD-ROM drive + SCCD = 5; + // Network bridge + SCBR = 6; + // DaynaPort network adapter + SCDP = 7; + // Host services device + SCHS = 8; + // Printer device + SCLP = 9; +} + +// rascsi remote operations, returning PbResult +enum PbOperation { + NO_OPERATION = 0; + + // Attach devices and return the new device list (PbDevicesInfo) + // Parameters (depending on the device type): + // "file": The filename relative to the default image folder + // "interface": A prioritized comma-separated list of interfaces to create a network bridge for + // "inet": The IP address and netmask for the network bridge + // "cmd": The command to be used for printing, with "%f" as file placeholder + // "timeout": The timeout for printer reservations in seconds + ATTACH = 1; + + // Detach a device and return the new device list (PbDevicesInfo) + // Detaches all LUNs of that device which are equal to or higher than the LUN specified. + DETACH = 2; + + // Detach all devices, does not require a device list + DETACH_ALL = 3; + + // Start device + START = 4; + + // Stop device, e.g. park drive + STOP = 5; + + // Insert medium + // Parameters: + // "file": The filename, relative to the default image folder + INSERT = 6; + + // Eject medium + EJECT = 7; + + // Write-protect medium (not possible for read-only media) + PROTECT = 8; + + // Make medium writable (not possible for read-only media) + UNPROTECT = 9; + + // Gets the server information (PbServerInfo). Calling this operation should be avoided because it + // may return a lot of data. More specific other operations should be used instead. + // "folder_pattern": Optional filter, only folder names containing the case-insensitive pattern are returned + // "file_pattern": Optional filter, only filenames containing the case-insensitive pattern are returned + SERVER_INFO = 10; + + // Get rascsi server version (PbVersionInfo) + VERSION_INFO = 11; + + // Get information on attached devices (PbDevicesInfo) + // Returns data for all attached devices if the device list is empty. + DEVICES_INFO = 12; + + // Get device properties by device type (PbDeviceTypesInfo) + DEVICE_TYPES_INFO = 13; + + // Get information on available image files in the default image folder (PbImageFilesInfo) + // Parameters: + // "folder_pattern": Optional filter, only folder names containing the case-insensitive pattern are returned + // "file_pattern": Optional filter, only filenames containing the case-insensitive pattern are returned + DEFAULT_IMAGE_FILES_INFO = 14; + + // Get information on an image file (not necessarily in the default image folder). + // Parameters: + // "file": The filename. Either an absolute path or a path relative to the default image folder. + IMAGE_FILE_INFO = 15; + + // Get information on the available log levels and the current log level (PbLogLevelInfo) + LOG_LEVEL_INFO = 16; + + // Get the names of the available network interfaces (PbNetworkInterfacesInfo) + // Only lists interfaces that are up. + NETWORK_INTERFACES_INFO = 17; + + // Get the mapping of extensions to device types (PbMappingInfo) + MAPPING_INFO = 18; + + // Get the list of reserved device IDs (PbReservedIdsInfo) + RESERVED_IDS_INFO = 19; + + // Set the default folder for image files. This folder must be located in /home. + // Parameters: + // "folder": The path of the default folder, relative to the user's home folder if relative. + DEFAULT_FOLDER = 20; + + // Set the log level. + // Parameters: + // "level": The new log level + LOG_LEVEL = 21; + + // Block IDs from being used, usually the IDs of the initiators (computers) in the SCSI chain. + // Parameters: + // "ids": A comma-separated list of IDs to reserve, or an empty string in order not to reserve any ID. + RESERVE_IDS = 22; + + // Shut down the rascsi process or shut down/reboot the Raspberry Pi + // Parameters: + // "mode": The shutdown mode, one of "rascsi", "system", "reboot" + SHUT_DOWN = 23; + + // Create an image file. The image file must not yet exist. + // Parameters: + // "file": The filename, relative to the default image folder + // "size": The file size in bytes, must be a multiple of 512 + // "read_only": Optional, "true" (case-insensitive) in order to create a read-only file + CREATE_IMAGE = 24; + + // Delete an image file. + // Parameters: + // "file": The filename, relative to the default image folder. + DELETE_IMAGE = 25; + + // Rename/Move an image file. + // Parameters: + // "from": The old filename, relative to the default image folder + // "to": The new filename, relative to the default image folder + // The new filename must not yet exist. + RENAME_IMAGE = 26; + + // Copy an image file. + // Parameters: + // "from": The source filename, relative to the default image folder + // "to": The destination filename, relative to the default image folder + // "read_only": Optional, "true" (case-insensitive) in order to create a read-only file + // The destination filename must not yet exist. + COPY_IMAGE = 27; + + // Write-protect an image file. + // Parameters: + // "file": The filename, relative to the default image folder + PROTECT_IMAGE = 28; + + // Make an image file writable. + // Parameters: + // "file": The filename, relative to the default image folder + UNPROTECT_IMAGE = 29; + + // Check whether an authentication token is valid. A client can use this operation in order to + // find out whether rascsi authentication is enable or to use rascsi authentication for securing + // client-internal operations. + CHECK_AUTHENTICATION = 30; + + // Get operation meta data (PbOperationInfo) + OPERATION_INFO = 31; +} + +// The operation parameter meta data. The parameter data type is provided by the protobuf API. +message PbOperationParameter { + string name = 1; + // Optional short description + string description = 2; + // There is no specific set of permitted values if empty + repeated string permitted_values = 3; + // Optional default value + string default_value = 4; + // True if this parameter is mandatory + bool is_mandatory = 5; +} + +// The list of parameters supported by an operation +message PbOperationMetaData { + // The operation name at the time the server-side protobuf code was generated. + string server_side_name = 1; + // Optional short description + string description = 2; + repeated PbOperationParameter parameters = 3; +} + +// Mapping of operations to their ordinals +message PbOperationInfo { + map operations = 1; +} + +// rascsi special purpose error codes for cases where a textual error message may not be not sufficient. +// The client may react on this code, e.g. by prompting the user for an authentication token. +enum PbErrorCode { + // No error code available + NO_ERROR_CODE = 0; + // The client sent an operation code not supported by this server + UNKNOWN_OPERATION = 1; + // Authentication/Authorization error + UNAUTHORIZED = 2; +} + +// The supported file extensions mapped to their respective device types +message PbMappingInfo { + map mapping = 1; +} + +// The properties supported by a device +message PbDeviceProperties { + // Read-only media (e.g. CD-ROMs) are not protectable but permanently read-only + bool read_only = 1; + // Medium can be write-protected + bool protectable = 2; + // Device can be stopped, e.g. parked + bool stoppable = 3; + // Medium can be removed + bool removable = 4; + // Medium can be locked + bool lockable = 5; + // Device supports image file as a parameter + bool supports_file = 6; + // Device supports parameters other than a filename + bool supports_params = 7; + // List of default parameters, if any (requires supports_params to be true) + map default_params = 8; + // LUN numbers this device can represent. At least 1 (for LUN 0 only). Maxium is 32, for LUNs 0-31. + int32 luns = 9; + // Unordered list of permitted block sizes in bytes, empty if the block size is not configurable + repeated uint32 block_sizes = 10; +} + +// The status of a device, only meaningful if the respective feature is supported +message PbDeviceStatus { + // Medium is write-protected + bool protected = 1; + // Device is stopped, e.g. parked + bool stopped = 2; + // Medium is removed + bool removed = 3; + // Medium is locked + bool locked = 4; +} + +// Device properties by device type. (Note that a protobuf map cannot be used because enums cannot be map keys.) +message PbDeviceTypeProperties { + PbDeviceType type = 1; + PbDeviceProperties properties = 2; +} + +message PbDeviceTypesInfo { + repeated PbDeviceTypeProperties properties = 1; +} + +// The image file data +message PbImageFile { + string name = 1; + // The assumed device type, based on the filename extension + PbDeviceType type = 2; + // The file size in bytes, 0 for block devices + uint64 size = 3; + bool read_only = 4; +} + +// The default image folder and the image files it contains +message PbImageFilesInfo { + string default_image_folder = 1; + repeated PbImageFile image_files = 2; + // The maximum nesting depth, configured with the -R option + int32 depth = 3; +} + +// Log level information +message PbLogLevelInfo { + // List of available log levels, ordered by increasing by severity + repeated string log_levels = 2; + string current_log_level = 3; +} + +// The network interfaces information +message PbNetworkInterfacesInfo { + repeated string name = 1; +} + +// The device definition, sent from the client to the server +message PbDeviceDefinition { + int32 id = 1; + int32 unit = 2; + PbDeviceType type = 3; + // Device specific named parameters, e.g. the name of an image file + map params = 4; + // The optional block size in bytes per sector, must be one of the supported block sizes for SASI/SCSI + int32 block_size = 5; + // The device name components + string vendor = 6; + string product = 7; + string revision = 8; + // Create a write-protected device + bool protected = 9; +} + +// The device data, sent from the server to the client +message PbDevice { + int32 id = 1; + int32 unit = 2; + PbDeviceType type = 3; + PbDeviceProperties properties = 4; + PbDeviceStatus status = 5; + // Image file information, if the device supports image files + PbImageFile file = 6; + // Effective parameters the device was created with + map params = 7; + string vendor = 8; + string product = 9; + string revision = 10; + // Block size in bytes + int32 block_size = 11; + // Number of blocks + uint64 block_count = 12; +} + +message PbDevicesInfo { + repeated PbDevice devices = 1; +} + +// The reserved device IDs +message PbReservedIdsInfo { + repeated int32 ids = 1; +} + +// Rascsi server version information +message PbVersionInfo { + // The rascsi version + int32 major_version = 1; + int32 minor_version = 2; + // < 0 for a development version, = 0 if there is no patch yet + int32 patch_version = 3; +} + +// Commands rascsi can execute and their parameters +message PbCommand { + PbOperation operation = 1; + // The non-empty list of devices for this command + repeated PbDeviceDefinition devices = 2; + // The named parameters for the operation, e.g. a filename, or a network interface list + map params = 3; +} + +// The result of a command +message PbResult { + // false means that an error occurred + bool status = 1; + // An optional error or information message, depending on the status. A string without trailing CR/LF. + string msg = 2; + // An optional error code. Only to be used in cases where textual information is not sufficient. + PbErrorCode error_code = 14; + // Optional additional result data + oneof result { + // The result of a SERVER_INFO command + PbServerInfo server_info = 3; + // The result of a VERSION_INFO command + PbVersionInfo version_info = 4; + // The result of a LOG_LEVEL_INFO command + PbLogLevelInfo log_level_info = 5; + // The result of a DEVICES_INFO, ATTACH or DETACH command + PbDevicesInfo devices_info = 6; + // The result of a DEVICE_TYPES_INFO command + PbDeviceTypesInfo device_types_info = 7; + // The result of a DEFAULT_IMAGE_FILES_INFO command + PbImageFilesInfo image_files_info = 8; + // The result of an IMAGE_FILE_INFO command + PbImageFile image_file_info = 9; + // The result of a NETWORK_INTERFACES_INFO command + PbNetworkInterfacesInfo network_interfaces_info = 10; + // The result of an MAPPING_INFO command + PbMappingInfo mapping_info = 11; + // The result of a RESERVED_IDS_INFO command + PbReservedIdsInfo reserved_ids_info = 12; + // The result of an OPERATION_INFO command + PbOperationInfo operation_info = 13; + } +} + +// The rascsi server information. All data can also be requested with individual commands. +message PbServerInfo { + // The rascsi server version data + PbVersionInfo version_info = 1; + // The available log levels and the current log level + PbLogLevelInfo log_level_info = 2; + // Supported device types and their properties + PbDeviceTypesInfo device_types_info = 3; + // The image files in the default folder + PbImageFilesInfo image_files_info = 4; + // The available (up) network interfaces + PbNetworkInterfacesInfo network_interfaces_info = 5; + // The extensions to device types mapping + PbMappingInfo mapping_info = 6; + // The reserved device IDs + PbReservedIdsInfo reserved_ids_info = 7; + // The attached devices + PbDevicesInfo devices_info = 8; + // The operation meta data + PbOperationInfo operation_info = 9; +} diff --git a/src_old/raspberrypi/rascsi_response.cpp b/src_old/raspberrypi/rascsi_response.cpp new file mode 100644 index 00000000..8cda4412 --- /dev/null +++ b/src_old/raspberrypi/rascsi_response.cpp @@ -0,0 +1,529 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2021 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include "devices/file_support.h" +#include "devices/disk.h" +#include "devices/device_factory.h" +#include "devices/device.h" +#include "protobuf_util.h" +#include "rascsi_version.h" +#include "rascsi_interface.pb.h" +#include "rascsi_image.h" +#include "rascsi_response.h" + +using namespace rascsi_interface; +using namespace protobuf_util; + +RascsiResponse::RascsiResponse(DeviceFactory *device_factory, const RascsiImage *rascsi_image) +{ + this->device_factory = device_factory; + this->rascsi_image = rascsi_image; +} + +PbDeviceProperties *RascsiResponse::GetDeviceProperties(const Device *device) +{ + PbDeviceProperties *properties = new PbDeviceProperties(); + + properties->set_luns(device->GetSupportedLuns()); + properties->set_read_only(device->IsReadOnly()); + properties->set_protectable(device->IsProtectable()); + properties->set_stoppable(device->IsStoppable()); + properties->set_removable(device->IsRemovable()); + properties->set_lockable(device->IsLockable()); + properties->set_supports_file(dynamic_cast(device)); + properties->set_supports_params(device->SupportsParams()); + + PbDeviceType t = UNDEFINED; + PbDeviceType_Parse(device->GetType(), &t); + + if (device->SupportsParams()) { + for (const auto& param : device_factory->GetDefaultParams(t)) { + auto& map = *properties->mutable_default_params(); + map[param.first] = param.second; + } + } + + for (const auto& block_size : device_factory->GetSectorSizes(t)) { + properties->add_block_sizes(block_size); + } + + return properties; +} + +void RascsiResponse::GetDeviceTypeProperties(PbDeviceTypesInfo& device_types_info, PbDeviceType type) +{ + PbDeviceTypeProperties *type_properties = device_types_info.add_properties(); + type_properties->set_type(type); + Device *device = device_factory->CreateDevice(type, ""); + type_properties->set_allocated_properties(GetDeviceProperties(device)); + delete device; +} + +void RascsiResponse::GetAllDeviceTypeProperties(PbDeviceTypesInfo& device_types_info) +{ + int ordinal = 1; + while (PbDeviceType_IsValid(ordinal)) { + PbDeviceType type = UNDEFINED; + PbDeviceType_Parse(PbDeviceType_Name((PbDeviceType)ordinal), &type); + GetDeviceTypeProperties(device_types_info, type); + ordinal++; + } +} + +void RascsiResponse::GetDevice(const Device *device, PbDevice *pb_device) +{ + pb_device->set_id(device->GetId()); + pb_device->set_unit(device->GetLun()); + pb_device->set_vendor(device->GetVendor()); + pb_device->set_product(device->GetProduct()); + pb_device->set_revision(device->GetRevision()); + + PbDeviceType type = UNDEFINED; + PbDeviceType_Parse(device->GetType(), &type); + pb_device->set_type(type); + + pb_device->set_allocated_properties(GetDeviceProperties(device)); + + PbDeviceStatus *status = new PbDeviceStatus(); + pb_device->set_allocated_status(status); + status->set_protected_(device->IsProtected()); + status->set_stopped(device->IsStopped()); + status->set_removed(device->IsRemoved()); + status->set_locked(device->IsLocked()); + + if (device->SupportsParams()) { + for (const auto& param : device->GetParams()) { + AddParam(*pb_device, param.first, param.second); + } + } + + const Disk *disk = dynamic_cast(device); + if (disk) { + pb_device->set_block_size(device->IsRemoved()? 0 : disk->GetSectorSizeInBytes()); + pb_device->set_block_count(device->IsRemoved() ? 0: disk->GetBlockCount()); + } + + const FileSupport *file_support = dynamic_cast(device); + if (file_support) { + Filepath filepath; + file_support->GetPath(filepath); + PbImageFile *image_file = new PbImageFile(); + GetImageFile(image_file, device->IsRemovable() && !device->IsReady() ? "" : filepath.GetPath()); + pb_device->set_allocated_file(image_file); + } +} + +bool RascsiResponse::GetImageFile(PbImageFile *image_file, const string& filename) +{ + if (!filename.empty()) { + image_file->set_name(filename); + image_file->set_type(device_factory->GetTypeForFile(filename)); + + string f = filename[0] == '/' ? filename : rascsi_image->GetDefaultImageFolder() + "/" + filename; + + image_file->set_read_only(access(f.c_str(), W_OK)); + + struct stat st; + if (!stat(f.c_str(), &st) && !S_ISDIR(st.st_mode)) { + image_file->set_size(st.st_size); + return true; + } + } + + return false; +} + +void RascsiResponse::GetAvailableImages(PbImageFilesInfo& image_files_info, const string& default_image_folder, + const string& folder, const string& folder_pattern, const string& file_pattern, int scan_depth) { + string folder_pattern_lower = folder_pattern; + transform(folder_pattern_lower.begin(), folder_pattern_lower.end(), folder_pattern_lower.begin(), ::tolower); + + string file_pattern_lower = file_pattern; + transform(file_pattern_lower.begin(), file_pattern_lower.end(), file_pattern_lower.begin(), ::tolower); + + if (scan_depth-- >= 0) { + DIR *d = opendir(folder.c_str()); + if (d) { + struct dirent *dir; + while ((dir = readdir(d))) { + bool is_supported_type = dir->d_type == DT_REG || dir->d_type == DT_DIR || dir->d_type == DT_LNK || dir->d_type == DT_BLK; + if (is_supported_type && dir->d_name[0] != '.') { + string name_lower = dir->d_name; + if (!file_pattern.empty()) { + transform(name_lower.begin(), name_lower.end(), name_lower.begin(), ::tolower); + } + + string filename = folder + "/" + dir->d_name; + + struct stat st; + if (dir->d_type == DT_REG && !stat(filename.c_str(), &st)) { + if (!st.st_size) { + LOGWARN("File '%s' in image folder '%s' has a size of 0 bytes", dir->d_name, folder.c_str()); + continue; + } + } else if (dir->d_type == DT_LNK && stat(filename.c_str(), &st)) { + LOGWARN("Symlink '%s' in image folder '%s' is broken", dir->d_name, folder.c_str()); + continue; + } else if (dir->d_type == DT_DIR) { + if (folder_pattern_lower.empty() || name_lower.find(folder_pattern_lower) != string::npos) { + GetAvailableImages(image_files_info, default_image_folder, filename, folder_pattern, + file_pattern, scan_depth); + } + continue; + } + + if (file_pattern_lower.empty() || name_lower.find(file_pattern_lower) != string::npos) { + PbImageFile *image_file = new PbImageFile(); + if (GetImageFile(image_file, filename)) { + GetImageFile(image_files_info.add_image_files(), filename.substr(default_image_folder.length() + 1)); + } + delete image_file; + } + } + } + + closedir(d); + } + } +} + +PbImageFilesInfo *RascsiResponse::GetAvailableImages(PbResult& result, const string& folder_pattern, + const string& file_pattern, int scan_depth) +{ + PbImageFilesInfo *image_files_info = new PbImageFilesInfo(); + + string default_image_folder = rascsi_image->GetDefaultImageFolder(); + image_files_info->set_default_image_folder(default_image_folder); + image_files_info->set_depth(scan_depth); + + GetAvailableImages(*image_files_info, default_image_folder, default_image_folder, folder_pattern, + file_pattern, scan_depth); + + result.set_status(true); + + return image_files_info; +} + +void RascsiResponse::GetAvailableImages(PbResult& result, PbServerInfo& server_info, const string& folder_pattern, + const string& file_pattern, int scan_depth) +{ + PbImageFilesInfo *image_files_info = GetAvailableImages(result, folder_pattern, file_pattern, scan_depth); + image_files_info->set_default_image_folder(rascsi_image->GetDefaultImageFolder()); + server_info.set_allocated_image_files_info(image_files_info); + + result.set_status(true); +} + +PbReservedIdsInfo *RascsiResponse::GetReservedIds(PbResult& result, const set& ids) +{ + PbReservedIdsInfo *reserved_ids_info = new PbReservedIdsInfo(); + for (int id : ids) { + reserved_ids_info->add_ids(id); + } + + result.set_status(true); + + return reserved_ids_info; +} + +void RascsiResponse::GetDevices(PbServerInfo& server_info, const vector& devices) +{ + for (const Device *device : devices) { + // Skip if unit does not exist or is not assigned + if (device) { + PbDevice *pb_device = server_info.mutable_devices_info()->add_devices(); + GetDevice(device, pb_device); + } + } +} + +void RascsiResponse::GetDevicesInfo(PbResult& result, const PbCommand& command, const vector& devices, + int unit_count) +{ + set id_sets; + if (!command.devices_size()) { + for (const Device *device : devices) { + if (device) { + id_sets.insert(make_pair(device->GetId(), device->GetLun())); + } + } + } + else { + for (const auto& device : command.devices()) { + if (devices[device.id() * unit_count + device.unit()]) { + id_sets.insert(make_pair(device.id(), device.unit())); + } + else { + result.set_status(false); + result.set_msg("No device for ID " + to_string(device.id()) + ", unit " + to_string(device.unit())); + return; + } + } + } + + PbDevicesInfo *devices_info = new PbDevicesInfo(); + result.set_allocated_devices_info(devices_info); + + for (const auto& id_set : id_sets) { + const Device *device = devices[id_set.first * unit_count + id_set.second]; + GetDevice(device, devices_info->add_devices()); + } + + result.set_status(true); +} + +PbDeviceTypesInfo *RascsiResponse::GetDeviceTypesInfo(PbResult& result, const PbCommand& command) +{ + PbDeviceTypesInfo *device_types_info = new PbDeviceTypesInfo(); + + GetAllDeviceTypeProperties(*device_types_info); + + result.set_status(true); + + return device_types_info; +} + +PbServerInfo *RascsiResponse::GetServerInfo(PbResult& result, const vector& devices, const set& reserved_ids, + const string& current_log_level, const string& folder_pattern, const string& file_pattern, int scan_depth) +{ + PbServerInfo *server_info = new PbServerInfo(); + + server_info->set_allocated_version_info(GetVersionInfo(result)); + server_info->set_allocated_log_level_info(GetLogLevelInfo(result, current_log_level)); + GetAllDeviceTypeProperties(*server_info->mutable_device_types_info()); + GetAvailableImages(result, *server_info, folder_pattern, file_pattern, scan_depth); + server_info->set_allocated_network_interfaces_info(GetNetworkInterfacesInfo(result)); + server_info->set_allocated_mapping_info(GetMappingInfo(result)); + GetDevices(*server_info, devices); + server_info->set_allocated_reserved_ids_info(GetReservedIds(result, reserved_ids)); + server_info->set_allocated_operation_info(GetOperationInfo(result, scan_depth)); + + result.set_status(true); + + return server_info; +} + +PbVersionInfo *RascsiResponse::GetVersionInfo(PbResult& result) +{ + PbVersionInfo *version_info = new PbVersionInfo(); + + version_info->set_major_version(rascsi_major_version); + version_info->set_minor_version(rascsi_minor_version); + version_info->set_patch_version(rascsi_patch_version); + + result.set_status(true); + + return version_info; +} + +PbLogLevelInfo *RascsiResponse::GetLogLevelInfo(PbResult& result, const string& current_log_level) +{ + PbLogLevelInfo *log_level_info = new PbLogLevelInfo(); + + for (const auto& log_level : log_levels) { + log_level_info->add_log_levels(log_level); + } + + log_level_info->set_current_log_level(current_log_level); + + result.set_status(true); + + return log_level_info; +} + +PbNetworkInterfacesInfo *RascsiResponse::GetNetworkInterfacesInfo(PbResult& result) +{ + PbNetworkInterfacesInfo *network_interfaces_info = new PbNetworkInterfacesInfo(); + + for (const auto& network_interface : device_factory->GetNetworkInterfaces()) { + network_interfaces_info->add_name(network_interface); + } + + result.set_status(true); + + return network_interfaces_info; +} + +PbMappingInfo *RascsiResponse::GetMappingInfo(PbResult& result) +{ + PbMappingInfo *mapping_info = new PbMappingInfo(); + + for (const auto& mapping : device_factory->GetExtensionMapping()) { + (*mapping_info->mutable_mapping())[mapping.first] = mapping.second; + } + + result.set_status(true); + + return mapping_info; +} + +PbOperationInfo *RascsiResponse::GetOperationInfo(PbResult& result, int depth) +{ + PbOperationInfo *operation_info = new PbOperationInfo(); + + PbOperationMetaData *meta_data = new PbOperationMetaData(); + AddOperationParameter(meta_data, "name", "Image file name in case of a mass storage device"); + AddOperationParameter(meta_data, "interface", "Comma-separated prioritized network interface list"); + AddOperationParameter(meta_data, "inet", "IP address and netmask of the network bridge"); + AddOperationParameter(meta_data, "cmd", "Print command for the printer device"); + AddOperationParameter(meta_data, "timeout", "Reservation timeout for the printer device in seconds"); + CreateOperation(operation_info, meta_data, ATTACH, "Attach device, device-specific parameters are required"); + + meta_data = new PbOperationMetaData(); + CreateOperation(operation_info, meta_data, DETACH, "Detach device, device-specific parameters are required"); + + meta_data = new PbOperationMetaData(); + CreateOperation(operation_info, meta_data, DETACH_ALL, "Detach all devices"); + + meta_data = new PbOperationMetaData(); + CreateOperation(operation_info, meta_data, START, "Start device, device-specific parameters are required"); + + meta_data = new PbOperationMetaData(); + CreateOperation(operation_info, meta_data, STOP, "Stop device, device-specific parameters are required"); + + meta_data = new PbOperationMetaData(); + AddOperationParameter(meta_data, "file", "Image file name", "", true); + CreateOperation(operation_info, meta_data, INSERT, "Insert medium, device-specific parameters are required"); + + meta_data = new PbOperationMetaData(); + CreateOperation(operation_info, meta_data, EJECT, "Eject medium, device-specific parameters are required"); + + meta_data = new PbOperationMetaData(); + CreateOperation(operation_info, meta_data, PROTECT, "Protect medium, device-specific parameters are required"); + + meta_data = new PbOperationMetaData(); + CreateOperation(operation_info, meta_data, UNPROTECT, "Unprotect medium, device-specific parameters are required"); + + meta_data = new PbOperationMetaData(); + if (depth) { + AddOperationParameter(meta_data, "folder_pattern", "Pattern for filtering image folder names"); + } + AddOperationParameter(meta_data, "file_pattern", "Pattern for filtering image file names"); + CreateOperation(operation_info, meta_data, SERVER_INFO, "Get rascsi server information"); + + meta_data = new PbOperationMetaData(); + CreateOperation(operation_info, meta_data, VERSION_INFO, "Get rascsi server version"); + + meta_data = new PbOperationMetaData(); + CreateOperation(operation_info, meta_data, DEVICES_INFO, "Get information on attached devices"); + + meta_data = new PbOperationMetaData(); + CreateOperation(operation_info, meta_data, DEVICE_TYPES_INFO, "Get device properties by device type"); + + meta_data = new PbOperationMetaData(); + if (depth) { + AddOperationParameter(meta_data, "folder_pattern", "Pattern for filtering image folder names"); + } + AddOperationParameter(meta_data, "file_pattern", "Pattern for filtering image file names"); + CreateOperation(operation_info, meta_data, DEFAULT_IMAGE_FILES_INFO, "Get information on available image files"); + + meta_data = new PbOperationMetaData(); + AddOperationParameter(meta_data, "file", "Image file name", "", true); + CreateOperation(operation_info, meta_data, IMAGE_FILE_INFO, "Get information on image file"); + + meta_data = new PbOperationMetaData(); + CreateOperation(operation_info, meta_data, LOG_LEVEL_INFO, "Get log level information"); + + meta_data = new PbOperationMetaData(); + CreateOperation(operation_info, meta_data, NETWORK_INTERFACES_INFO, "Get the available network interfaces"); + + meta_data = new PbOperationMetaData(); + CreateOperation(operation_info, meta_data, MAPPING_INFO, "Get mapping of extensions to device types"); + + meta_data = new PbOperationMetaData(); + CreateOperation(operation_info, meta_data, RESERVED_IDS_INFO, "Get list of reserved device IDs"); + + meta_data = new PbOperationMetaData(); + AddOperationParameter(meta_data, "folder", "Default image file folder name", "", true); + CreateOperation(operation_info, meta_data, DEFAULT_FOLDER, "Set default image file folder"); + + meta_data = new PbOperationMetaData(); + AddOperationParameter(meta_data, "level", "New log level", "", true); + CreateOperation(operation_info, meta_data, LOG_LEVEL, "Set log level"); + + meta_data = new PbOperationMetaData(); + AddOperationParameter(meta_data, "ids", "Comma-separated device ID list", "", true); + CreateOperation(operation_info, meta_data, RESERVE_IDS, "Reserve device IDs"); + + meta_data = new PbOperationMetaData(); + PbOperationParameter *parameter = AddOperationParameter(meta_data, "mode", "Shutdown mode", "", true); + parameter->add_permitted_values("rascsi"); + // System shutdown/reboot requires root permissions + if (!getuid()) { + parameter->add_permitted_values("system"); + parameter->add_permitted_values("reboot"); + } + CreateOperation(operation_info, meta_data, SHUT_DOWN, "Shut down or reboot"); + + meta_data = new PbOperationMetaData(); + AddOperationParameter(meta_data, "file", "Image file name", "", true); + AddOperationParameter(meta_data, "size", "Image file size in bytes", "", true); + parameter = AddOperationParameter(meta_data, "read_only", "Read-only flag", "false"); + parameter->add_permitted_values("true"); + parameter->add_permitted_values("false"); + CreateOperation(operation_info, meta_data, CREATE_IMAGE, "Create an image file"); + + meta_data = new PbOperationMetaData(); + AddOperationParameter(meta_data, "file", "Image file name", "", true); + CreateOperation(operation_info, meta_data, DELETE_IMAGE, "Delete image file"); + + meta_data = new PbOperationMetaData(); + AddOperationParameter(meta_data, "from", "Source image file name", "", true); + AddOperationParameter(meta_data, "to", "Destination image file name", "", true); + CreateOperation(operation_info, meta_data, RENAME_IMAGE, "Rename image file"); + + meta_data = new PbOperationMetaData(); + AddOperationParameter(meta_data, "from", "Source image file name", "", true); + AddOperationParameter(meta_data, "to", "Destination image file name", "", true); + parameter = AddOperationParameter(meta_data, "read_only", "Read-only flag", "false"); + parameter->add_permitted_values("true"); + parameter->add_permitted_values("false"); + CreateOperation(operation_info, meta_data, COPY_IMAGE, "Copy image file"); + + meta_data = new PbOperationMetaData(); + AddOperationParameter(meta_data, "file", "Image file name", "", true); + CreateOperation(operation_info, meta_data, PROTECT_IMAGE, "Write-protect image file"); + + meta_data = new PbOperationMetaData(); + AddOperationParameter(meta_data, "file", "Image file name", "", true); + CreateOperation(operation_info, meta_data, UNPROTECT_IMAGE, "Make image file writable"); + + meta_data = new PbOperationMetaData(); + AddOperationParameter(meta_data, "token", "Authentication token to be checked", "", true); + CreateOperation(operation_info, meta_data, CHECK_AUTHENTICATION, "Check whether an authentication token is valid"); + + meta_data = new PbOperationMetaData(); + CreateOperation(operation_info, meta_data, OPERATION_INFO, "Get operation meta data"); + + result.set_status(true); + + return operation_info; +} + +void RascsiResponse::CreateOperation(PbOperationInfo *operation_info, PbOperationMetaData *meta_data, + const PbOperation& operation, const string& description) +{ + meta_data->set_server_side_name(PbOperation_Name(operation)); + meta_data->set_description(description); + int ordinal = PbOperation_descriptor()->FindValueByName(PbOperation_Name(operation))->index(); + (*operation_info->mutable_operations())[ordinal] = *meta_data; + delete meta_data; +} + +PbOperationParameter *RascsiResponse::AddOperationParameter(PbOperationMetaData *meta_data, const string& name, + const string& description, const string& default_value, bool is_mandatory) +{ + PbOperationParameter *parameter = meta_data->add_parameters(); + parameter->set_name(name); + parameter->set_description(description); + parameter->set_default_value(default_value); + parameter->set_is_mandatory(is_mandatory); + + return parameter; +} diff --git a/src_old/raspberrypi/rascsi_response.h b/src_old/raspberrypi/rascsi_response.h new file mode 100644 index 00000000..460d9925 --- /dev/null +++ b/src_old/raspberrypi/rascsi_response.h @@ -0,0 +1,61 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2021 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "devices/device_factory.h" +#include "rascsi_interface.pb.h" +#include +#include + +using namespace std; +using namespace rascsi_interface; + +class DeviceFactory; +class RascsiImage; +class Device; + +class RascsiResponse +{ +public: + + RascsiResponse(DeviceFactory *, const RascsiImage *); + ~RascsiResponse() {}; + + bool GetImageFile(PbImageFile *, const string&); + PbImageFilesInfo *GetAvailableImages(PbResult&, const string&, const string&, int); + PbReservedIdsInfo *GetReservedIds(PbResult&, const set&); + void GetDevices(PbServerInfo&, const vector&); + void GetDevicesInfo(PbResult&, const PbCommand&, const vector&, int); + PbDeviceTypesInfo *GetDeviceTypesInfo(PbResult&, const PbCommand&); + PbVersionInfo *GetVersionInfo(PbResult&); + PbServerInfo *GetServerInfo(PbResult&, const vector&, const set&, const string&, const string&, + const string&, int); + PbNetworkInterfacesInfo *GetNetworkInterfacesInfo(PbResult&); + PbMappingInfo *GetMappingInfo(PbResult&); + PbLogLevelInfo *GetLogLevelInfo(PbResult&, const string&); + PbOperationInfo *GetOperationInfo(PbResult&, int); + +private: + + DeviceFactory *device_factory; + const RascsiImage *rascsi_image; + + vector log_levels { "trace", "debug", "info", "warn", "err", "critical", "off" }; + + PbDeviceProperties *GetDeviceProperties(const Device *); + void GetDevice(const Device *, PbDevice *); + void GetAllDeviceTypeProperties(PbDeviceTypesInfo&); + void GetDeviceTypeProperties(PbDeviceTypesInfo&, PbDeviceType); + void GetAvailableImages(PbImageFilesInfo&, const string&, const string&, const string&, const string&, int); + void GetAvailableImages(PbResult& result, PbServerInfo&, const string&, const string&, int); + void CreateOperation(PbOperationInfo *, PbOperationMetaData *, const PbOperation&, const string&); + PbOperationParameter *AddOperationParameter(PbOperationMetaData *, const string&, const string&, + const string& = "", bool = false); +}; diff --git a/src_old/raspberrypi/rascsi_version.cpp b/src_old/raspberrypi/rascsi_version.cpp new file mode 100644 index 00000000..733b8c5f --- /dev/null +++ b/src_old/raspberrypi/rascsi_version.cpp @@ -0,0 +1,45 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2020 akuker +// [ Define the version string ] +// +//--------------------------------------------------------------------------- + +#include "rascsi_version.h" +#include + +// The following should be updated for each release +const int rascsi_major_version = 22; // Last two digits of year +const int rascsi_minor_version = 03; // Month +const int rascsi_patch_version = -1; // Patch number - increment for each update + +static char rascsi_version_string[30]; // Allow for string up to "XX.XX.XXX" + null character + "development build" + +//--------------------------------------------------------------------------- +// +// Get the RaSCSI version string +// +//--------------------------------------------------------------------------- +const char* rascsi_get_version_string() +{ + if(rascsi_patch_version < 0) + { + snprintf(rascsi_version_string, sizeof(rascsi_version_string), + "%02d.%02d --DEVELOPMENT BUILD--", + rascsi_major_version, + rascsi_minor_version); + } + else + { + snprintf(rascsi_version_string, sizeof(rascsi_version_string), "%02d.%02d.%d", + rascsi_major_version, + rascsi_minor_version, + rascsi_patch_version); + } + return rascsi_version_string; +} + + diff --git a/src_old/raspberrypi/rascsi_version.h b/src_old/raspberrypi/rascsi_version.h new file mode 100644 index 00000000..a39ced4e --- /dev/null +++ b/src_old/raspberrypi/rascsi_version.h @@ -0,0 +1,21 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2020 akuker +// [ Define the version string ] +// +//--------------------------------------------------------------------------- +#pragma once + +extern const int rascsi_major_version; // Last two digits of year +extern const int rascsi_minor_version; // Month +extern const int rascsi_patch_version; // Patch number + +//--------------------------------------------------------------------------- +// +// Fetch the version string +// +//--------------------------------------------------------------------------- +const char* rascsi_get_version_string(); diff --git a/src_old/raspberrypi/rasctl.cpp b/src_old/raspberrypi/rasctl.cpp new file mode 100644 index 00000000..09e746c1 --- /dev/null +++ b/src_old/raspberrypi/rasctl.cpp @@ -0,0 +1,469 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Powered by XM6 TypeG Technology. +// Copyright (C) 2016-2020 GIMONS +// Copyright (C) 2020-2021 Contributors to the RaSCSI project +// [ Send Control Command ] +// +//--------------------------------------------------------------------------- + +#include "os.h" +#include "rascsi_version.h" +#include "protobuf_util.h" +#include "rasutil.h" +#include "rasctl_commands.h" +#include "rascsi_interface.pb.h" +#include +#include +#include + +// Separator for the INQUIRY name components and for compound parameters +#define COMPONENT_SEPARATOR ':' + +using namespace std; +using namespace rascsi_interface; +using namespace ras_util; +using namespace protobuf_util; + +PbOperation ParseOperation(const char *optarg) +{ + switch (tolower(optarg[0])) { + case 'a': + return ATTACH; + + case 'd': + return DETACH; + + case 'i': + return INSERT; + + case 'e': + return EJECT; + + case 'p': + return PROTECT; + + case 'u': + return UNPROTECT; + + case 's': + return DEVICES_INFO; + + default: + return NO_OPERATION; + } +} + +PbDeviceType ParseType(const char *optarg) +{ + string t = optarg; + transform(t.begin(), t.end(), t.begin(), ::toupper); + + PbDeviceType type; + if (PbDeviceType_Parse(t, &type)) { + return type; + } + + // Parse convenience device types (shortcuts) + switch (tolower(optarg[0])) { + case 'c': + return SCCD; + + case 'b': + return SCBR; + + case 'd': + return SCDP; + + case 'h': + return SCHD; + + case 'm': + return SCMO; + + case 'r': + return SCRM; + + case 'l': + return SCLP; + + case 's': + return SCHS; + + default: + return UNDEFINED; + } +} + +void SetPatternParams(PbCommand& command, const string& patterns) +{ + string folder_pattern; + string file_pattern; + size_t separator_pos = patterns.find(COMPONENT_SEPARATOR); + if (separator_pos != string::npos) { + folder_pattern = patterns.substr(0, separator_pos); + file_pattern = patterns.substr(separator_pos + 1); + } + else { + file_pattern = patterns; + } + + AddParam(command, "folder_pattern", folder_pattern); + AddParam(command, "file_pattern", file_pattern); +} + +int main(int argc, char* argv[]) +{ + GOOGLE_PROTOBUF_VERIFY_VERSION; + + // Display help + if (argc < 2) { + cerr << "SCSI Target Emulator RaSCSI Controller" << endl; + cerr << "version " << rascsi_get_version_string() << " (" << __DATE__ << ", " << __TIME__ << ")" << endl; + cerr << "Usage: " << argv[0] << " -i ID [-u UNIT] [-c CMD] [-C FILE] [-t TYPE] [-b BLOCK_SIZE] [-n NAME] [-f FILE|PARAM] "; + cerr << "[-F IMAGE_FOLDER] [-L LOG_LEVEL] [-h HOST] [-p PORT] [-r RESERVED_IDS] "; + cerr << "[-C FILENAME:FILESIZE] [-d FILENAME] [-w FILENAME] [-R CURRENT_NAME:NEW_NAME] "; + cerr << "[-x CURRENT_NAME:NEW_NAME] [-z LOCALE] "; + cerr << "[-e] [-E FILENAME] [-D] [-I] [-l] [-L] [-m] [o] [-O] [-P] [-s] [-v] [-V] [-y] [-X]" << endl; + cerr << " where ID := {0-7}" << endl; + cerr << " UNIT := {0-31}, default is 0" << endl; + cerr << " CMD := {attach|detach|insert|eject|protect|unprotect|show}" << endl; + cerr << " TYPE := {sahd|schd|scrm|sccd|scmo|scbr|scdp} or convenience type {hd|rm|mo|cd|bridge|daynaport}" << endl; + cerr << " BLOCK_SIZE := {256|512|1024|2048|4096) bytes per hard disk drive block" << endl; + cerr << " NAME := name of device to attach (VENDOR:PRODUCT:REVISION)" << endl; + cerr << " FILE|PARAM := image file path or device-specific parameter" << endl; + cerr << " IMAGE_FOLDER := default location for image files, default is '~/images'" << endl; + cerr << " HOST := rascsi host to connect to, default is 'localhost'" << endl; + cerr << " PORT := rascsi port to connect to, default is 6868" << endl; + cerr << " RESERVED_IDS := comma-separated list of IDs to reserve" << endl; + cerr << " LOG_LEVEL := log level {trace|debug|info|warn|err|critical|off}, default is 'info'" << endl; + cerr << " If CMD is 'attach' or 'insert' the FILE parameter is required." << endl; + cerr << "Usage: " << argv[0] << " -l" << endl; + cerr << " Print device list." << endl; + + exit(EXIT_SUCCESS); + } + + PbCommand command; + list devices; + PbDeviceDefinition* device = command.add_devices(); + device->set_id(-1); + const char *hostname = "localhost"; + int port = 6868; + string param; + string log_level; + string default_folder; + string reserved_ids; + string image_params; + string filename; + string token; + bool list = false; + + string locale = setlocale(LC_MESSAGES, ""); + if (locale == "C") { + locale = "en"; + } + + opterr = 1; + int opt; + while ((opt = getopt(argc, argv, "e::lmos::vDINOTVXa:b:c:d:f:h:i:n:p:r:t:u:x:z:C:E:F:L:P::R:")) != -1) { + switch (opt) { + case 'i': { + int id; + if (!GetAsInt(optarg, id)) { + cerr << "Error: Invalid device ID " << optarg << endl; + exit(EXIT_FAILURE); + } + device->set_id(id); + break; + } + + case 'u': { + int unit; + if (!GetAsInt(optarg, unit)) { + cerr << "Error: Invalid unit " << optarg << endl; + exit(EXIT_FAILURE); + } + device->set_unit(unit); + break; + } + + case 'C': + command.set_operation(CREATE_IMAGE); + image_params = optarg; + break; + + case 'b': + int block_size; + if (!GetAsInt(optarg, block_size)) { + cerr << "Error: Invalid block size " << optarg << endl; + exit(EXIT_FAILURE); + } + device->set_block_size(block_size); + break; + + case 'c': + command.set_operation(ParseOperation(optarg)); + if (command.operation() == NO_OPERATION) { + cerr << "Error: Unknown operation '" << optarg << "'" << endl; + exit(EXIT_FAILURE); + } + break; + + case 'D': + command.set_operation(DETACH_ALL); + break; + + case 'd': + command.set_operation(DELETE_IMAGE); + image_params = optarg; + break; + + case 'E': + command.set_operation(IMAGE_FILE_INFO); + filename = optarg; + break; + + case 'e': + command.set_operation(DEFAULT_IMAGE_FILES_INFO); + if (optarg) { + SetPatternParams(command, optarg); + } + break; + + case 'F': + command.set_operation(DEFAULT_FOLDER); + default_folder = optarg; + break; + + case 'f': + param = optarg; + break; + + case 'h': + hostname = optarg; + break; + + case 'I': + command.set_operation(RESERVED_IDS_INFO); + break; + + case 'L': + command.set_operation(LOG_LEVEL); + log_level = optarg; + break; + + case 'l': + list = true; + break; + + case 'm': + command.set_operation(MAPPING_INFO); + break; + + case 'N': + command.set_operation(NETWORK_INTERFACES_INFO); + break; + + case 'O': + command.set_operation(LOG_LEVEL_INFO); + break; + + case 'o': + command.set_operation(OPERATION_INFO); + break; + + case 't': + device->set_type(ParseType(optarg)); + if (device->type() == UNDEFINED) { + cerr << "Error: Unknown device type '" << optarg << "'" << endl; + exit(EXIT_FAILURE); + } + break; + + case 'r': + command.set_operation(RESERVE_IDS); + reserved_ids = optarg; + break; + + case 'R': + command.set_operation(RENAME_IMAGE); + image_params = optarg; + break; + + case 'n': { + string vendor; + string product; + string revision; + + string s = optarg; + size_t separator_pos = s.find(COMPONENT_SEPARATOR); + if (separator_pos != string::npos) { + vendor = s.substr(0, separator_pos); + s = s.substr(separator_pos + 1); + separator_pos = s.find(COMPONENT_SEPARATOR); + if (separator_pos != string::npos) { + product = s.substr(0, separator_pos); + revision = s.substr(separator_pos + 1); + } + else { + product = s; + } + } + else { + vendor = s; + } + + device->set_vendor(vendor); + device->set_product(product); + device->set_revision(revision); + } + break; + + case 'p': + if (!GetAsInt(optarg, port) || port <= 0 || port > 65535) { + cerr << "Error: Invalid port " << optarg << ", port must be between 1 and 65535" << endl; + exit(EXIT_FAILURE); + } + break; + + case 's': + command.set_operation(SERVER_INFO); + if (optarg) { + SetPatternParams(command, optarg); + } + break; + + case 'v': + cout << "rasctl version: " << rascsi_get_version_string() << endl; + exit(EXIT_SUCCESS); + break; + + case 'P': + token = optarg ? optarg : getpass("Password: "); + break; + + case 'V': + command.set_operation(VERSION_INFO); + break; + + case 'x': + command.set_operation(COPY_IMAGE); + image_params = optarg; + break; + + case 'T': + command.set_operation(DEVICE_TYPES_INFO); + break; + + case 'X': + command.set_operation(SHUT_DOWN); + AddParam(command, "mode", "rascsi"); + break; + + case 'z': + locale = optarg; + break; + } + } + + if (optopt) { + exit(EXIT_FAILURE); + } + + // Listing devices is a special case (rasctl backwards compatibility) + if (list) { + PbCommand command_list; + command_list.set_operation(DEVICES_INFO); + RasctlCommands rasctl_commands(command_list, hostname, port, token, locale); + rasctl_commands.CommandDevicesInfo(); + exit(EXIT_SUCCESS); + } + + ParseParameters(*device, param); + + RasctlCommands rasctl_commands(command, hostname, port, token, locale); + + switch(command.operation()) { + case LOG_LEVEL: + rasctl_commands.CommandLogLevel(log_level); + break; + + case DEFAULT_FOLDER: + rasctl_commands.CommandDefaultImageFolder(default_folder); + break; + + case RESERVE_IDS: + rasctl_commands.CommandReserveIds(reserved_ids); + break; + + case CREATE_IMAGE: + rasctl_commands.CommandCreateImage(image_params); + break; + + case DELETE_IMAGE: + rasctl_commands.CommandDeleteImage(image_params); + break; + + case RENAME_IMAGE: + rasctl_commands.CommandRenameImage(image_params); + break; + + case COPY_IMAGE: + rasctl_commands.CommandCopyImage(image_params); + break; + + case DEVICES_INFO: + rasctl_commands.CommandDeviceInfo(); + break; + + case DEVICE_TYPES_INFO: + rasctl_commands.CommandDeviceTypesInfo(); + break; + + case VERSION_INFO: + rasctl_commands.CommandVersionInfo(); + break; + + case SERVER_INFO: + rasctl_commands.CommandServerInfo(); + break; + + case DEFAULT_IMAGE_FILES_INFO: + rasctl_commands.CommandDefaultImageFilesInfo(); + break; + + case IMAGE_FILE_INFO: + rasctl_commands.CommandImageFileInfo(filename); + break; + + case NETWORK_INTERFACES_INFO: + rasctl_commands.CommandNetworkInterfacesInfo(); + break; + + case LOG_LEVEL_INFO: + rasctl_commands.CommandLogLevelInfo(); + break; + + case RESERVED_IDS_INFO: + rasctl_commands.CommandReservedIdsInfo(); + break; + + case MAPPING_INFO: + rasctl_commands.CommandMappingInfo(); + break; + + case OPERATION_INFO: + rasctl_commands.CommandOperationInfo(); + break; + + default: + rasctl_commands.SendCommand(); + break; + } + + exit(EXIT_SUCCESS); +} diff --git a/src_old/raspberrypi/rasctl_commands.cpp b/src_old/raspberrypi/rasctl_commands.cpp new file mode 100644 index 00000000..f192432d --- /dev/null +++ b/src_old/raspberrypi/rasctl_commands.cpp @@ -0,0 +1,292 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2021 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include +#include "os.h" +#include "exceptions.h" +#include "protobuf_util.h" +#include "rasutil.h" +#include "rasctl_commands.h" +#include "rascsi_interface.pb.h" +#include +#include + +// Separator for the INQUIRY name components +#define COMPONENT_SEPARATOR ':' + +using namespace std; +using namespace rascsi_interface; +using namespace protobuf_util; + +RasctlCommands::RasctlCommands(PbCommand& command, const string& hostname, int port, const string& token, + const string& locale) +{ + this->command = command; + this->hostname = hostname; + this->port = port; + this->token = token; + this->locale = locale; +} + +void RasctlCommands::SendCommand() +{ + if (!token.empty()) { + AddParam(command, "token", token); + } + + if (!locale.empty()) { + AddParam(command, "locale", locale); + } + + // Send command + int fd = -1; + try { + struct hostent *host = gethostbyname(hostname.c_str()); + if (!host) { + throw io_exception("Can't resolve hostname '" + hostname + "'"); + } + + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) { + throw io_exception("Can't create socket"); + } + + struct sockaddr_in server; + memset(&server, 0, sizeof(server)); + server.sin_family = AF_INET; + server.sin_port = htons(port); + server.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + memcpy(&server.sin_addr.s_addr, host->h_addr, host->h_length); + + if (connect(fd, (struct sockaddr *)&server, sizeof(struct sockaddr_in)) < 0) { + throw io_exception("Can't connect to rascsi process on host '" + hostname + "', port " + + to_string(port)); + } + + if (write(fd, "RASCSI", 6) != 6) { + throw io_exception("Can't write magic"); + } + + SerializeMessage(fd, command); + } + catch(const io_exception& e) { + cerr << "Error: " << e.getmsg() << endl; + + if (fd >= 0) { + close(fd); + } + + exit(fd < 0 ? ENOTCONN : EXIT_FAILURE); + } + + // Receive result + try { + DeserializeMessage(fd, result); + + if (!result.status()) { + throw io_exception(result.msg()); + } + } + catch(const io_exception& e) { + close(fd); + + cerr << "Error: " << e.getmsg() << endl; + + exit(EXIT_FAILURE); + } + + close(fd); + + if (!result.msg().empty()) { + cout << result.msg() << endl; + } +} + +void RasctlCommands::CommandDevicesInfo() +{ + SendCommand(); + + rasctl_display.DisplayDevices(result.devices_info()); +} + +void RasctlCommands::CommandLogLevel(const string& log_level) +{ + AddParam(command, "level", log_level); + + SendCommand(); +} + +void RasctlCommands::CommandReserveIds(const string& reserved_ids) +{ + AddParam(command, "ids", reserved_ids); + + SendCommand(); +} + +void RasctlCommands::CommandCreateImage(const string& image_params) +{ + size_t separator_pos = image_params.find(COMPONENT_SEPARATOR); + if (separator_pos != string::npos) { + AddParam(command, "file", image_params.substr(0, separator_pos)); + AddParam(command, "size", image_params.substr(separator_pos + 1)); + } + else { + cerr << "Error: Invalid file descriptor '" << image_params << "', format is NAME:SIZE" << endl; + exit(EXIT_FAILURE); + } + + AddParam(command, "read_only", "false"); + + SendCommand(); +} + +void RasctlCommands::CommandDeleteImage(const string& filename) +{ + AddParam(command, "file", filename); + + SendCommand(); +} + +void RasctlCommands::CommandRenameImage(const string& image_params) +{ + size_t separator_pos = image_params.find(COMPONENT_SEPARATOR); + if (separator_pos != string::npos) { + AddParam(command, "from", image_params.substr(0, separator_pos)); + AddParam(command, "to", image_params.substr(separator_pos + 1)); + } + else { + cerr << "Error: Invalid file descriptor '" << image_params << "', format is CURRENT_NAME:NEW_NAME" << endl; + exit(EXIT_FAILURE); + } + + SendCommand(); +} + +void RasctlCommands::CommandCopyImage(const string& image_params) +{ + size_t separator_pos = image_params.find(COMPONENT_SEPARATOR); + if (separator_pos != string::npos) { + AddParam(command, "from", image_params.substr(0, separator_pos)); + AddParam(command, "to", image_params.substr(separator_pos + 1)); + } + else { + cerr << "Error: Invalid file descriptor '" << image_params << "', format is CURRENT_NAME:NEW_NAME" << endl; + exit(EXIT_FAILURE); + } + + SendCommand(); +} + +void RasctlCommands::CommandDefaultImageFolder(const string& folder) +{ + AddParam(command, "folder", folder); + + SendCommand(); +} + +void RasctlCommands::CommandDeviceInfo() +{ + SendCommand(); + + for (const auto& device : result.devices_info().devices()) { + rasctl_display.DisplayDeviceInfo(device); + } +} + +void RasctlCommands::CommandDeviceTypesInfo() +{ + SendCommand(); + + rasctl_display.DisplayDeviceTypesInfo(result.device_types_info()); +} + +void RasctlCommands::CommandVersionInfo() +{ + SendCommand(); + + rasctl_display.DisplayVersionInfo(result.version_info()); +} + +void RasctlCommands::CommandServerInfo() +{ + SendCommand(); + + PbServerInfo server_info = result.server_info(); + + rasctl_display.DisplayVersionInfo(server_info.version_info()); + rasctl_display.DisplayLogLevelInfo(server_info.log_level_info()); + rasctl_display.DisplayImageFiles(server_info.image_files_info()); + rasctl_display.DisplayMappingInfo(server_info.mapping_info()); + rasctl_display.DisplayNetworkInterfaces(server_info.network_interfaces_info()); + rasctl_display.DisplayDeviceTypesInfo(server_info.device_types_info()); + rasctl_display.DisplayReservedIdsInfo(server_info.reserved_ids_info()); + rasctl_display.DisplayOperationInfo(server_info.operation_info()); + + if (server_info.devices_info().devices_size()) { + list sorted_devices = { server_info.devices_info().devices().begin(), server_info.devices_info().devices().end() }; + sorted_devices.sort([](const auto& a, const auto& b) { return a.id() < b.id(); }); + + cout << "Attached devices:" << endl; + + for (const auto& device : sorted_devices) { + rasctl_display.DisplayDeviceInfo(device); + } + } +} + +void RasctlCommands::CommandDefaultImageFilesInfo() +{ + SendCommand(); + + rasctl_display.DisplayImageFiles(result.image_files_info()); +} + +void RasctlCommands::CommandImageFileInfo(const string& filename) +{ + AddParam(command, "file", filename); + + SendCommand(); + + rasctl_display.DisplayImageFile(result.image_file_info()); +} + +void RasctlCommands::CommandNetworkInterfacesInfo() +{ + SendCommand(); + + rasctl_display.DisplayNetworkInterfaces(result.network_interfaces_info()); +} + +void RasctlCommands::CommandLogLevelInfo() +{ + SendCommand(); + + rasctl_display.DisplayLogLevelInfo(result.log_level_info()); +} + +void RasctlCommands::CommandReservedIdsInfo() +{ + SendCommand(); + + rasctl_display.DisplayReservedIdsInfo(result.reserved_ids_info()); +} + +void RasctlCommands::CommandMappingInfo() +{ + SendCommand(); + + rasctl_display.DisplayMappingInfo(result.mapping_info()); +} + +void RasctlCommands::CommandOperationInfo() +{ + SendCommand(); + + rasctl_display.DisplayOperationInfo(result.operation_info()); +} diff --git a/src_old/raspberrypi/rasctl_commands.h b/src_old/raspberrypi/rasctl_commands.h new file mode 100644 index 00000000..35c3f8a2 --- /dev/null +++ b/src_old/raspberrypi/rasctl_commands.h @@ -0,0 +1,58 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2021 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "rascsi_interface.pb.h" +#include "rasctl_display.h" +#include + +using namespace std; +using namespace rascsi_interface; + +class RasctlCommands +{ +public: + + RasctlCommands(PbCommand&, const string&, int, const string&, const string&); + ~RasctlCommands() {}; + + void SendCommand(); + void CommandDevicesInfo(); + void CommandLogLevel(const string&); + void CommandReserveIds(const string&); + void CommandCreateImage(const string&); + void CommandDeleteImage(const string&); + void CommandRenameImage(const string&); + void CommandCopyImage(const string&); + void CommandDefaultImageFolder(const string&); + void CommandDeviceInfo(); + void CommandDeviceTypesInfo(); + void CommandVersionInfo(); + void CommandServerInfo(); + void CommandDefaultImageFilesInfo(); + void CommandImageFileInfo(const string&); + void CommandNetworkInterfacesInfo(); + void CommandLogLevelInfo(); + void CommandReservedIdsInfo(); + void CommandMappingInfo(); + void CommandOperationInfo(); + +private: + + PbCommand command; + string hostname; + int port; + string token; + string locale; + + PbResult result; + + RasctlDisplay rasctl_display; +}; diff --git a/src_old/raspberrypi/rasctl_display.cpp b/src_old/raspberrypi/rasctl_display.cpp new file mode 100644 index 00000000..92172589 --- /dev/null +++ b/src_old/raspberrypi/rasctl_display.cpp @@ -0,0 +1,339 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2021 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include "rascsi_interface.pb.h" +#include "rasutil.h" +#include "rasctl_display.h" +#include +#include + +using namespace std; +using namespace rascsi_interface; +using namespace ras_util; + +void RasctlDisplay::DisplayDevices(const PbDevicesInfo& devices_info) +{ + const list& devices = { devices_info.devices().begin(), devices_info.devices().end() }; + cout << ListDevices(devices) << endl; +} + +void RasctlDisplay::DisplayDeviceInfo(const PbDevice& pb_device) +{ + cout << " " << pb_device.id() << ":" << pb_device.unit() << " " << PbDeviceType_Name(pb_device.type()) + << " " << pb_device.vendor() << ":" << pb_device.product() << ":" << pb_device.revision(); + + if (pb_device.block_size()) { + cout << " " << pb_device.block_size() << " bytes per sector"; + if (pb_device.block_count()) { + cout << " " << pb_device.block_size() * pb_device.block_count() << " bytes capacity"; + } + } + + if (pb_device.properties().supports_file() && !pb_device.file().name().empty()) { + cout << " " << pb_device.file().name(); + } + + cout << " "; + bool hasProperty = false; + if (pb_device.properties().read_only()) { + cout << "read-only"; + hasProperty = true; + } + if (pb_device.properties().protectable() && pb_device.status().protected_()) { + if (hasProperty) { + cout << ", "; + } + cout << "protected"; + hasProperty = true; + } + if (pb_device.properties().stoppable() && pb_device.status().stopped()) { + if (hasProperty) { + cout << ", "; + } + cout << "stopped"; + hasProperty = true; + } + if (pb_device.properties().removable() && pb_device.status().removed()) { + if (hasProperty) { + cout << ", "; + } + cout << "removed"; + hasProperty = true; + } + if (pb_device.properties().lockable() && pb_device.status().locked()) { + if (hasProperty) { + cout << ", "; + } + cout << "locked"; + } + if (hasProperty) { + cout << " "; + } + + // Creates a sorted map + map params = { pb_device.params().begin(), pb_device.params().end() }; + bool isFirst = true; + for (const auto& param : params) { + if (!isFirst) { + cout << ":"; + } + isFirst = false; + cout << param.first << "=" << param.second; + } + + cout << endl; +} + +void RasctlDisplay::DisplayVersionInfo(const PbVersionInfo& version_info) +{ + cout << "rascsi server version: " << version_info.major_version() << "." << version_info.minor_version(); + if (version_info.patch_version() > 0) { + cout << "." << version_info.patch_version(); + } + else if (version_info.patch_version() < 0) { + cout << " (development version)"; + } + cout << endl; +} + +void RasctlDisplay::DisplayLogLevelInfo(const PbLogLevelInfo& log_level_info) +{ + if (!log_level_info.log_levels_size()) { + cout << " No log level settings available" << endl; + } + else { + cout << "rascsi log levels, sorted by severity:" << endl; + for (const auto& log_level : log_level_info.log_levels()) { + cout << " " << log_level << endl; + } + } + + cout << "Current rascsi log level: " << log_level_info.current_log_level() << endl; +} + +void RasctlDisplay::DisplayDeviceTypesInfo(const PbDeviceTypesInfo& device_types_info) +{ + cout << "Supported device types and their properties:" << endl; + for (const auto& device_type_info : device_types_info.properties()) { + cout << " " << PbDeviceType_Name(device_type_info.type()); + + const PbDeviceProperties& properties = device_type_info.properties(); + + cout << " Supported LUN numbers: 0"; + if (properties.luns() > 1) { + cout << "-" << (properties.luns() - 1); + } + cout << endl; + + if (properties.read_only() || properties.protectable() || properties.stoppable() || properties.read_only() + || properties.lockable()) { + cout << " Properties: "; + bool has_property = false; + if (properties.read_only()) { + cout << "read-only"; + has_property = true; + } + if (properties.protectable()) { + cout << (has_property ? ", " : "") << "protectable"; + has_property = true; + } + if (properties.stoppable()) { + cout << (has_property ? ", " : "") << "stoppable"; + has_property = true; + } + if (properties.removable()) { + cout << (has_property ? ", " : "") << "removable"; + has_property = true; + } + if (properties.lockable()) { + cout << (has_property ? ", " : "") << "lockable"; + } + cout << endl; + } + + if (properties.supports_file()) { + cout << " Image file support" << endl; + } + else if (properties.supports_params()) { + cout << " Parameter support" << endl; + } + + if (properties.supports_params() && properties.default_params_size()) { + // Creates a sorted map + map params = { properties.default_params().begin(), properties.default_params().end() }; + + cout << " Default parameters: "; + + bool isFirst = true; + for (const auto& param : params) { + if (!isFirst) { + cout << " "; + } + cout << param.first << "=" << param.second << endl; + + isFirst = false; + } + } + + if (properties.block_sizes_size()) { + // Creates a sorted set + set block_sizes = { properties.block_sizes().begin(), properties.block_sizes().end() }; + + cout << " Configurable block sizes in bytes: "; + + bool isFirst = true; + for (const auto& block_size : block_sizes) { + if (!isFirst) { + cout << ", "; + } + cout << block_size; + + isFirst = false; + } + cout << endl; + } + } +} + +void RasctlDisplay::DisplayReservedIdsInfo(const PbReservedIdsInfo& reserved_ids_info) +{ + if (reserved_ids_info.ids_size()) { + cout << "Reserved device IDs: "; + for (int i = 0; i < reserved_ids_info.ids_size(); i++) { + if(i) { + cout << ", "; + } + cout << reserved_ids_info.ids(i); + } + cout < image_files = { image_files_info.image_files().begin(), image_files_info.image_files().end() }; + image_files.sort([](const auto& a, const auto& b) { return a.name() < b.name(); }); + + cout << "Available image files:" << endl; + for (const auto& image_file : image_files) { + cout << " "; + DisplayImageFile(image_file); + } + } +} + +void RasctlDisplay::DisplayNetworkInterfaces(const PbNetworkInterfacesInfo& network_interfaces_info) +{ + // Creates a sorted list + const list interfaces = { network_interfaces_info.name().begin(), network_interfaces_info.name().end() }; + + cout << "Available (up) network interfaces:" << endl; + bool isFirst = true; + for (const auto& interface : interfaces) { + if (!isFirst) { + cout << ", "; + } + isFirst = false; + cout << interface; + } + cout << endl; +} + +void RasctlDisplay::DisplayMappingInfo(const PbMappingInfo& mapping_info) +{ + // Creates a sorted map + const map mappings = { mapping_info.mapping().begin(), mapping_info.mapping().end() }; + + cout << "Supported image file extension to device type mappings:" << endl; + for (const auto& mapping : mappings) { + cout << " " << mapping.first << "->" << PbDeviceType_Name(mapping.second) << endl; + } +} + +void RasctlDisplay::DisplayOperationInfo(const PbOperationInfo& operation_info) +{ + const map operations = { operation_info.operations().begin(), operation_info.operations().end() }; + + // Copies result into a map sorted by operation name + const PbOperationMetaData *unknown_operation = new PbOperationMetaData(); + map sorted_operations; + for (const auto& operation : operations) { + if (PbOperation_IsValid(static_cast(operation.first))) { + sorted_operations[PbOperation_Name(static_cast(operation.first))] = operation.second; + } + else { + // If the server-side operation is unknown for the client use the server-provided operation name + // No further operation information is available in this case + sorted_operations[operation.second.server_side_name()] = *unknown_operation; + } + } + + cout << "Operations supported by rascsi server and their parameters:" << endl; + for (const auto& operation : sorted_operations) { + if (!operation.second.server_side_name().empty()) { + cout << " " << operation.first; + if (!operation.second.description().empty()) { + cout << " (" << operation.second.description() << ")"; + } + cout << endl; + + list sorted_parameters = { operation.second.parameters().begin(), operation.second.parameters().end() }; + sorted_parameters.sort([](const auto& a, const auto& b) { return a.name() < b.name(); }); + + for (const auto& parameter : sorted_parameters) { + cout << " " << parameter.name() << ": " + << (parameter.is_mandatory() ? "mandatory" : "optional"); + if (!parameter.description().empty()) { + cout << " (" << parameter.description() << ")"; + } + cout << endl; + + if (parameter.permitted_values_size()) { + cout << " Permitted values: "; + bool isFirst = true; + for (const auto& permitted_value : parameter.permitted_values()) { + if (!isFirst) { + cout << ", "; + } + isFirst = false; + cout << permitted_value; + } + cout << endl; + } + + if (!parameter.default_value().empty()) { + cout << " Default value: " << parameter.default_value() << endl; + } + } + } + else { + cout << " " << operation.first << " (Unknown server-side operation)" << endl; + } + } +} diff --git a/src_old/raspberrypi/rasctl_display.h b/src_old/raspberrypi/rasctl_display.h new file mode 100644 index 00000000..864027a4 --- /dev/null +++ b/src_old/raspberrypi/rasctl_display.h @@ -0,0 +1,34 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2021 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "rascsi_interface.pb.h" + +using namespace rascsi_interface; + +class RasctlDisplay +{ +public: + + RasctlDisplay() {}; + ~RasctlDisplay() {}; + + void DisplayDevices(const PbDevicesInfo&); + void DisplayDeviceInfo(const PbDevice&); + void DisplayVersionInfo(const PbVersionInfo&); + void DisplayLogLevelInfo(const PbLogLevelInfo&); + void DisplayDeviceTypesInfo(const PbDeviceTypesInfo&); + void DisplayReservedIdsInfo(const PbReservedIdsInfo&); + void DisplayImageFile(const PbImageFile&); + void DisplayImageFiles(const PbImageFilesInfo&); + void DisplayNetworkInterfaces(const PbNetworkInterfacesInfo&); + void DisplayMappingInfo(const PbMappingInfo&); + void DisplayOperationInfo(const PbOperationInfo&); +}; diff --git a/src_old/raspberrypi/rasdump.cpp b/src_old/raspberrypi/rasdump.cpp new file mode 100644 index 00000000..d7cfe80d --- /dev/null +++ b/src_old/raspberrypi/rasdump.cpp @@ -0,0 +1,1005 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Powered by XM6 TypeG Technology. +// Copyright (C) 2016-2020 GIMONS +// [ HDD dump utility (initiator mode) ] +// +//--------------------------------------------------------------------------- + +#include +#include "os.h" +#include "fileio.h" +#include "filepath.h" +#include "gpiobus.h" +#include "rascsi_version.h" + +//--------------------------------------------------------------------------- +// +// Constant Declaration +// +//--------------------------------------------------------------------------- +#define BUFSIZE 1024 * 64 // Buffer size of about 64KB + +//--------------------------------------------------------------------------- +// +// Variable Declaration +// +//--------------------------------------------------------------------------- +GPIOBUS bus; // Bus +int targetid; // Target ID +int boardid; // Board ID (own ID) +Filepath hdsfile; // HDS file +bool restore; // Restore flag +BYTE buffer[BUFSIZE]; // Work Buffer +int result; // Result Code + +//--------------------------------------------------------------------------- +// +// Cleanup() Function declaration +// +//--------------------------------------------------------------------------- +void Cleanup(); + +//--------------------------------------------------------------------------- +// +// Signal processing +// +//--------------------------------------------------------------------------- +void KillHandler(int sig) +{ + // Stop running + Cleanup(); + exit(0); +} + +//--------------------------------------------------------------------------- +// +// Banner Output +// +//--------------------------------------------------------------------------- +bool Banner(int argc, char* argv[]) +{ + printf("RaSCSI hard disk dump utility "); + printf("version %s (%s, %s)\n", + rascsi_get_version_string(), + __DATE__, + __TIME__); + + if (argc < 2 || strcmp(argv[1], "-h") == 0) { + printf("Usage: %s -i ID [-b BID] -f FILE [-r]\n", argv[0]); + printf(" ID is target device SCSI ID {0|1|2|3|4|5|6|7}.\n"); + printf(" BID is rascsi board SCSI ID {0|1|2|3|4|5|6|7}. Default is 7.\n"); + printf(" FILE is HDS file path.\n"); + printf(" -r is restore operation.\n"); + return false; + } + + return true; +} + +//--------------------------------------------------------------------------- +// +// Initialization +// +//--------------------------------------------------------------------------- +bool Init() +{ + // Interrupt handler setting + if (signal(SIGINT, KillHandler) == SIG_ERR) { + return false; + } + if (signal(SIGHUP, KillHandler) == SIG_ERR) { + return false; + } + if (signal(SIGTERM, KillHandler) == SIG_ERR) { + return false; + } + + // GPIO Initialization + if (!bus.Init(BUS::INITIATOR)) { + return false; + } + + // Work Intitialization + targetid = -1; + boardid = 7; + restore = false; + + return true; +} + +//--------------------------------------------------------------------------- +// +// Cleanup +// +//--------------------------------------------------------------------------- +void Cleanup() +{ + // Cleanup the bus + bus.Cleanup(); +} + +//--------------------------------------------------------------------------- +// +// Reset +// +//--------------------------------------------------------------------------- +void Reset() +{ + // Reset the bus signal line + bus.Reset(); +} + +//--------------------------------------------------------------------------- +// +// Argument processing +// +//--------------------------------------------------------------------------- +bool ParseArgument(int argc, char* argv[]) +{ + int opt; + char *file; + + // Initialization + file = NULL; + + // Argument Parsing + opterr = 0; + while ((opt = getopt(argc, argv, "i:b:f:r")) != -1) { + switch (opt) { + case 'i': + targetid = optarg[0] - '0'; + break; + + case 'b': + boardid = optarg[0] - '0'; + break; + + case 'f': + file = optarg; + break; + + case 'r': + restore = true; + break; + } + } + + // TARGET ID check + if (targetid < 0 || targetid > 7) { + fprintf(stderr, + "Error : Invalid target id range\n"); + return false; + } + + // BOARD ID check + if (boardid < 0 || boardid > 7) { + fprintf(stderr, + "Error : Invalid board id range\n"); + return false; + } + + // Target and Board ID duplication check + if (targetid == boardid) { + fprintf(stderr, + "Error : Invalid target or board id\n"); + return false; + } + + // File Check + if (!file) { + fprintf(stderr, + "Error : Invalid file path\n"); + return false; + } + + hdsfile.SetPath(file); + + return true; +} + +//--------------------------------------------------------------------------- +// +// Wait Phase +// +//--------------------------------------------------------------------------- +bool WaitPhase(BUS::phase_t phase) +{ + DWORD now; + + // Timeout (3000ms) + now = SysTimer::GetTimerLow(); + while ((SysTimer::GetTimerLow() - now) < 3 * 1000 * 1000) { + bus.Aquire(); + if (bus.GetREQ() && bus.GetPhase() == phase) { + return true; + } + } + + return false; +} + +//--------------------------------------------------------------------------- +// +// Bus Free Phase +// +//--------------------------------------------------------------------------- +void BusFree() +{ + // Bus Reset + bus.Reset(); +} + +//--------------------------------------------------------------------------- +// +// Selection Phase +// +//--------------------------------------------------------------------------- +bool Selection(int id) +{ + BYTE data; + int count; + + // ID setting and SEL assert + data = 0; + data |= (1 << boardid); + data |= (1 << id); + bus.SetDAT(data); + bus.SetSEL(TRUE); + + // wait for busy + count = 10000; + do { + usleep(20); + bus.Aquire(); + if (bus.GetBSY()) { + break; + } + } while (count--); + + // SEL negate + bus.SetSEL(FALSE); + + // Success if the target is busy + return bus.GetBSY(); +} + +//--------------------------------------------------------------------------- +// +// Command Phase +// +//--------------------------------------------------------------------------- +bool Command(BYTE *buf, int length) +{ + int count; + + // Waiting for Phase + if (!WaitPhase(BUS::command)) { + return false; + } + + // Send Command + count = bus.SendHandShake(buf, length, BUS::SEND_NO_DELAY); + + // Success if the transmission result is the same as the number + // of requests + if (count == length) { + return true; + } + + // Return error + return false; +} + +//--------------------------------------------------------------------------- +// +// Data in phase +// +//--------------------------------------------------------------------------- +int DataIn(BYTE *buf, int length) +{ + // Wait for phase + if (!WaitPhase(BUS::datain)) { + return -1; + } + + // Data reception + return bus.ReceiveHandShake(buf, length); +} + +//--------------------------------------------------------------------------- +// +// Data out phase +// +//--------------------------------------------------------------------------- +int DataOut(BYTE *buf, int length) +{ + // Wait for phase + if (!WaitPhase(BUS::dataout)) { + return -1; + } + + // Data transmission + return bus.SendHandShake(buf, length, BUS::SEND_NO_DELAY); +} + +//--------------------------------------------------------------------------- +// +// Status Phase +// +//--------------------------------------------------------------------------- +int Status() +{ + BYTE buf[256]; + + // Wait for phase + if (!WaitPhase(BUS::status)) { + return -2; + } + + // Data reception + if (bus.ReceiveHandShake(buf, 1) == 1) { + return (int)buf[0]; + } + + // Return error + return -1; +} + +//--------------------------------------------------------------------------- +// +// Message in phase +// +//--------------------------------------------------------------------------- +int MessageIn() +{ + BYTE buf[256]; + + // Wait for phase + if (!WaitPhase(BUS::msgin)) { + return -2; + } + + // Data reception + if (bus.ReceiveHandShake(buf, 1) == 1) { + return (int)buf[0]; + } + + // Return error + return -1; +} + +//--------------------------------------------------------------------------- +// +// TEST UNIT READY +// +//--------------------------------------------------------------------------- +int TestUnitReady(int id) +{ + BYTE cmd[256]; + + // Result code initialization + result = 0; + + // SELECTION + if (!Selection(id)) { + result = -1; + goto exit; + } + + // COMMAND + memset(cmd, 0x00, 6); + cmd[0] = 0x00; + if (!Command(cmd, 6)) { + result = -2; + goto exit; + } + + // STATUS + if (Status() < 0) { + result = -4; + goto exit; + } + + // MESSAGE IN + if (MessageIn() < 0) { + result = -5; + goto exit; + } + +exit: + // Bus free + BusFree(); + + return result; +} + +//--------------------------------------------------------------------------- +// +// REQUEST SENSE +// +//--------------------------------------------------------------------------- +int RequestSense(int id, BYTE *buf) +{ + BYTE cmd[256]; + int count; + + // Result code initialization + result = 0; + count = 0; + + // SELECTION + if (!Selection(id)) { + result = -1; + goto exit; + } + + // COMMAND + memset(cmd, 0x00, 6); + cmd[0] = 0x03; + cmd[4] = 0xff; + if (!Command(cmd, 6)) { + result = -2; + goto exit; + } + + // DATAIN + memset(buf, 0x00, 256); + count = DataIn(buf, 256); + if (count <= 0) { + result = -3; + goto exit; + } + + // STATUS + if (Status() < 0) { + result = -4; + goto exit; + } + + // MESSAGE IN + if (MessageIn() < 0) { + result = -5; + goto exit; + } + +exit: + // Bus Free + BusFree(); + + // Returns the number of transfers if successful + if (result == 0) { + return count; + } + + return result; +} + +//--------------------------------------------------------------------------- +// +// MODE SENSE +// +//--------------------------------------------------------------------------- +int ModeSense(int id, BYTE *buf) +{ + BYTE cmd[256]; + int count; + + // Result code initialization + result = 0; + count = 0; + + // SELECTION + if (!Selection(id)) { + result = -1; + goto exit; + } + + // COMMAND + memset(cmd, 0x00, 6); + cmd[0] = 0x1a; + cmd[2] = 0x3f; + cmd[4] = 0xff; + if (!Command(cmd, 6)) { + result = -2; + goto exit; + } + + // DATAIN + memset(buf, 0x00, 256); + count = DataIn(buf, 256); + if (count <= 0) { + result = -3; + goto exit; + } + + // STATUS + if (Status() < 0) { + result = -4; + goto exit; + } + + // MESSAGE IN + if (MessageIn() < 0) { + result = -5; + goto exit; + } + +exit: + // Bus free + BusFree(); + + // Returns the number of transfers if successful + if (result == 0) { + return count; + } + + return result; +} + +//--------------------------------------------------------------------------- +// +// INQUIRY +// +//--------------------------------------------------------------------------- +int Inquiry(int id, BYTE *buf) +{ + BYTE cmd[256]; + int count; + + // Result code initialization + result = 0; + count = 0; + + // SELECTION + if (!Selection(id)) { + result = -1; + goto exit; + } + + // COMMAND + memset(cmd, 0x00, 6); + cmd[0] = 0x12; + cmd[4] = 0xff; + if (!Command(cmd, 6)) { + result = -2; + goto exit; + } + + // DATAIN + memset(buf, 0x00, 256); + count = DataIn(buf, 256); + if (count <= 0) { + result = -3; + goto exit; + } + + // STATUS + if (Status() < 0) { + result = -4; + goto exit; + } + + // MESSAGE IN + if (MessageIn() < 0) { + result = -5; + goto exit; + } + +exit: + // Bus free + BusFree(); + + // Returns the number of transfers if successful + if (result == 0) { + return count; + } + + return result; +} + +//--------------------------------------------------------------------------- +// +// READ CAPACITY +// +//--------------------------------------------------------------------------- +int ReadCapacity(int id, BYTE *buf) +{ + BYTE cmd[256]; + int count; + + // Result code initialization + result = 0; + count = 0; + + // SELECTION + if (!Selection(id)) { + result = -1; + goto exit; + } + + // COMMAND + memset(cmd, 0x00, 10); + cmd[0] = 0x25; + if (!Command(cmd, 10)) { + result = -2; + goto exit; + } + + // DATAIN + memset(buf, 0x00, 8); + count = DataIn(buf, 8); + if (count <= 0) { + result = -3; + goto exit; + } + + // STATUS + if (Status() < 0) { + result = -4; + goto exit; + } + + // MESSAGE IN + if (MessageIn() < 0) { + result = -5; + goto exit; + } + +exit: + // Bus free + BusFree(); + + // Returns the number of transfers if successful + if (result == 0) { + return count; + } + + return result; +} + +//--------------------------------------------------------------------------- +// +// READ10 +// +//--------------------------------------------------------------------------- +int Read10(int id, DWORD bstart, DWORD blength, DWORD length, BYTE *buf) +{ + BYTE cmd[256]; + int count; + + // Result code initialization + result = 0; + count = 0; + + // SELECTION + if (!Selection(id)) { + result = -1; + goto exit; + } + + // COMMAND + memset(cmd, 0x00, 10); + cmd[0] = 0x28; + cmd[2] = (BYTE)(bstart >> 24); + cmd[3] = (BYTE)(bstart >> 16); + cmd[4] = (BYTE)(bstart >> 8); + cmd[5] = (BYTE)bstart; + cmd[7] = (BYTE)(blength >> 8); + cmd[8] = (BYTE)blength; + if (!Command(cmd, 10)) { + result = -2; + goto exit; + } + + // DATAIN + count = DataIn(buf, length); + if (count <= 0) { + result = -3; + goto exit; + } + + // STATUS + if (Status() < 0) { + result = -4; + goto exit; + } + + // MESSAGE IN + if (MessageIn() < 0) { + result = -5; + goto exit; + } + +exit: + // Bus free + BusFree(); + + // Returns the number of transfers if successful + if (result == 0) { + return count; + } + + return result; +} + +//--------------------------------------------------------------------------- +// +// WRITE10 +// +//--------------------------------------------------------------------------- +int Write10(int id, DWORD bstart, DWORD blength, DWORD length, BYTE *buf) +{ + BYTE cmd[256]; + int count; + + // Result code initialization + result = 0; + count = 0; + + // SELECTION + if (!Selection(id)) { + result = -1; + goto exit; + } + + // COMMAND + memset(cmd, 0x00, 10); + cmd[0] = 0x2a; + cmd[2] = (BYTE)(bstart >> 24); + cmd[3] = (BYTE)(bstart >> 16); + cmd[4] = (BYTE)(bstart >> 8); + cmd[5] = (BYTE)bstart; + cmd[7] = (BYTE)(blength >> 8); + cmd[8] = (BYTE)blength; + if (!Command(cmd, 10)) { + result = -2; + goto exit; + } + + // DATAOUT + count = DataOut(buf, length); + if (count <= 0) { + result = -3; + goto exit; + } + + // STATUS + if (Status() < 0) { + result = -4; + goto exit; + } + + // MESSAGE IN + if (MessageIn() < 0) { + result = -5; + goto exit; + } + +exit: + // Bus free + BusFree(); + + // Returns the number of transfers if successful + if (result == 0) { + return count; + } + + return result; +} + +//--------------------------------------------------------------------------- +// +// Main process +// +//--------------------------------------------------------------------------- +int main(int argc, char* argv[]) +{ + int i; + int count; + char str[32]; + DWORD bsiz; + DWORD bnum; + DWORD duni; + DWORD dsiz; + DWORD dnum; + Fileio fio; + Fileio::OpenMode omode; + off_t size; + + // Banner output + if (!Banner(argc, argv)) { + exit(0); + } + + // Initialization + if (!Init()) { + fprintf(stderr, "Error : Initializing. Are you root?\n"); + + // Probably not root + exit(EPERM); + } + + // Prase Argument + if (!ParseArgument(argc, argv)) { + // Cleanup + Cleanup(); + + // Exit with invalid argument error + exit(EINVAL); + } + + // Reset the SCSI bus + Reset(); + + // File Open + if (restore) { + omode = Fileio::ReadOnly; + } else { + omode = Fileio::WriteOnly; + } + if (!fio.Open(hdsfile.GetPath(), omode)) { + fprintf(stderr, "Error : Can't open hds file\n"); + + // Cleanup + Cleanup(); + exit(EPERM); + } + + // Bus free + BusFree(); + + // Assert reset signal + bus.SetRST(TRUE); + usleep(1000); + bus.SetRST(FALSE); + + // Start dump + printf("TARGET ID : %d\n", targetid); + printf("BOARD ID : %d\n", boardid); + + // TEST UNIT READY + count = TestUnitReady(targetid); + if (count < 0) { + fprintf(stderr, "TEST UNIT READY ERROR %d\n", count); + goto cleanup_exit; + } + + // REQUEST SENSE(for CHECK CONDITION) + count = RequestSense(targetid, buffer); + if (count < 0) { + fprintf(stderr, "REQUEST SENSE ERROR %d\n", count); + goto cleanup_exit; + } + + // INQUIRY + count = Inquiry(targetid, buffer); + if (count < 0) { + fprintf(stderr, "INQUIRY ERROR %d\n", count); + goto cleanup_exit; + } + + // Display INQUIRY information + memset(str, 0x00, sizeof(str)); + memcpy(str, &buffer[8], 8); + printf("Vendor : %s\n", str); + memset(str, 0x00, sizeof(str)); + memcpy(str, &buffer[16], 16); + printf("Product : %s\n", str); + memset(str, 0x00, sizeof(str)); + memcpy(str, &buffer[32], 4); + printf("Revison : %s\n", str); + + // Get drive capacity + count = ReadCapacity(targetid, buffer); + if (count < 0) { + fprintf(stderr, "READ CAPACITY ERROR %d\n", count); + goto cleanup_exit; + } + + // Display block size and number of blocks + bsiz = + (buffer[4] << 24) | (buffer[5] << 16) | + (buffer[6] << 8) | buffer[7]; + bnum = + (buffer[0] << 24) | (buffer[1] << 16) | + (buffer[2] << 8) | buffer[3]; + bnum++; + printf("Number of blocks : %d Blocks\n", (int)bnum); + printf("Block length : %d Bytes\n", (int)bsiz); + printf("Unit Capacity : %d MBytes %d Bytes\n", + (int)(bsiz * bnum / 1024 / 1024), + (int)(bsiz * bnum)); + + // Get the restore file size + if (restore) { + size = fio.GetFileSize(); + printf("Restore file size : %d bytes", (int)size); + if (size > (off_t)(bsiz * bnum)) { + printf("(WARNING : File size is larger than disk size)"); + } else if (size < (off_t)(bsiz * bnum)) { + printf("(ERROR : File size is smaller than disk size)\n"); + goto cleanup_exit; + } + printf("\n"); + } + + // Dump by buffer size + duni = BUFSIZE; + duni /= bsiz; + dsiz = BUFSIZE; + dnum = bnum * bsiz; + dnum /= BUFSIZE; + + if (restore) { + printf("Restore progress : "); + } else { + printf("Dump progress : "); + } + + for (i = 0; i < (int)dnum; i++) { + if (i > 0) { + printf("\033[21D"); + printf("\033[0K"); + } + printf("%3d%%(%7d/%7d)", + (int)((i + 1) * 100 / dnum), + (int)(i * duni), + (int)bnum); + fflush(stdout); + + if (restore) { + if (fio.Read(buffer, dsiz)) { + if (Write10(targetid, i * duni, duni, dsiz, buffer) >= 0) { + continue; + } + } + } else { + if (Read10(targetid, i * duni, duni, dsiz, buffer) >= 0) { + if (fio.Write(buffer, dsiz)) { + continue; + } + } + } + + printf("\n"); + printf("Error occured and aborted... %d\n", result); + goto cleanup_exit; + } + + if (dnum > 0) { + printf("\033[21D"); + printf("\033[0K"); + } + + // Rounding on capacity + dnum = bnum % duni; + dsiz = dnum * bsiz; + if (dnum > 0) { + if (restore) { + if (fio.Read(buffer, dsiz)) { + Write10(targetid, i * duni, dnum, dsiz, buffer); + } + } else { + if (Read10(targetid, i * duni, dnum, dsiz, buffer) >= 0) { + fio.Write(buffer, dsiz); + } + } + } + + // Completion Message + printf("%3d%%(%7d/%7d)\n", 100, (int)bnum, (int)bnum); + +cleanup_exit: + // File close + fio.Close(); + + // Cleanup + Cleanup(); + + // end + exit(0); +} diff --git a/src_old/raspberrypi/rasutil.cpp b/src_old/raspberrypi/rasutil.cpp new file mode 100644 index 00000000..b7551d77 --- /dev/null +++ b/src_old/raspberrypi/rasutil.cpp @@ -0,0 +1,84 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2021 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include +#include +#include "rascsi_interface.pb.h" +#include "rasutil.h" + +using namespace std; +using namespace rascsi_interface; + +bool ras_util::GetAsInt(const string& value, int& result) +{ + if (value.find_first_not_of("0123456789") != string::npos) { + return false; + } + + try { + result = std::stoul(value); + } + catch(const invalid_argument& e) { + return false; + } + catch(const out_of_range& e) { + return false; + } + + return true; +} + +string ras_util::ListDevices(const list& pb_devices) +{ + if (pb_devices.empty()) { + return "No images currently attached."; + } + + ostringstream s; + s << "+----+-----+------+-------------------------------------" << endl + << "| ID | LUN | TYPE | IMAGE FILE" << endl + << "+----+-----+------+-------------------------------------" << endl; + + list devices = pb_devices; + devices.sort([](const auto& a, const auto& b) { return a.id() < b.id() && a.unit() < b.unit(); }); + + for (const auto& device : devices) { + string filename; + switch (device.type()) { + case SCBR: + filename = "X68000 HOST BRIDGE"; + break; + + case SCDP: + filename = "DaynaPort SCSI/Link"; + break; + + case SCHS: + filename = "Host Services"; + break; + + case SCLP: + filename = "SCSI Printer"; + break; + + default: + filename = device.file().name(); + break; + } + + s << "| " << device.id() << " | " << device.unit() << " | " << PbDeviceType_Name(device.type()) << " | " + << (filename.empty() ? "NO MEDIA" : filename) + << (!device.status().removed() && (device.properties().read_only() || device.status().protected_()) ? " (READ-ONLY)" : "") + << endl; + } + + s << "+----+-----+------+-------------------------------------"; + + return s.str(); +} diff --git a/src_old/raspberrypi/rasutil.h b/src_old/raspberrypi/rasutil.h new file mode 100644 index 00000000..dd5d52a1 --- /dev/null +++ b/src_old/raspberrypi/rasutil.h @@ -0,0 +1,22 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2021 Uwe Seimet +// +// Helper methods used by rascsi and rasctl +// +//--------------------------------------------------------------------------- + +#pragma once + +#include +#include +#include "rascsi_interface.pb.h" + +namespace ras_util +{ + bool GetAsInt(const std::string&, int&); + std::string ListDevices(const std::list&); +} diff --git a/src_old/raspberrypi/sasidump.cpp b/src_old/raspberrypi/sasidump.cpp new file mode 100644 index 00000000..92d6dc48 --- /dev/null +++ b/src_old/raspberrypi/sasidump.cpp @@ -0,0 +1,745 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Powered by XM6 TypeG Technology. +// Copyright (C) 2016-2020 GIMONS +// [ HDD dump utility (Initiator mode/SASI Version) ] +// +// SASI IMAGE EXAMPLE +// X68000 +// 10MB(10441728 BS=256 C=40788) +// 20MB(20748288 BS=256 C=81048) +// 40MB(41496576 BS=256 C=162096) +// +// MZ-2500/MZ-2800 MZ-1F23 +// 20MB(22437888 BS=1024 C=21912) +// +//--------------------------------------------------------------------------- + +#include +#include "os.h" +#include "fileio.h" +#include "filepath.h" +#include "gpiobus.h" +#include "rascsi_version.h" + +#define BUFSIZE 1024 * 64 // Maybe around 64KB? + +GPIOBUS bus; +int targetid; +int unitid; +int bsiz; // Block size +int bnum; // Number of blocks +Filepath hdffile; // HDF file +bool restore; // Restore flag +BYTE buffer[BUFSIZE]; // Work buffer +int result; // Result code + +void Cleanup(); + +//--------------------------------------------------------------------------- +// +// Signal processing +// +//--------------------------------------------------------------------------- +void KillHandler(int sig) +{ + // Stop instruction + Cleanup(); + exit(0); +} + +//--------------------------------------------------------------------------- +// +// Banner output +// +//--------------------------------------------------------------------------- +bool Banner(int argc, char* argv[]) +{ + printf("RaSCSI hard disk dump utility (SASI HDD) "); + printf("version %s (%s, %s)\n", + rascsi_get_version_string(), + __DATE__, + __TIME__); + + if (argc < 2 || strcmp(argv[1], "-h") == 0) { + printf("Usage: %s -i ID [-u UT] [-b BSIZE] -c COUNT -f FILE [-r]\n", argv[0]); + printf(" ID is target device SASI ID {0|1|2|3|4|5|6|7}.\n"); + printf(" UT is target unit ID {0|1}. Default is 0.\n"); + printf(" BSIZE is block size {256|512|1024}. Default is 256.\n"); + printf(" COUNT is block count.\n"); + printf(" FILE is HDF file path.\n"); + printf(" -r is restore operation.\n"); + return false; + } + + return true; +} + +bool Init() +{ + // Interrupt handler settings + if (signal(SIGINT, KillHandler) == SIG_ERR) { + return false; + } + if (signal(SIGHUP, KillHandler) == SIG_ERR) { + return false; + } + if (signal(SIGTERM, KillHandler) == SIG_ERR) { + return false; + } + + // GPIO initialization + if (!bus.Init(BUS::INITIATOR)) { + return false; + } + + // Work initialization + targetid = -1; + unitid = 0; + bsiz = 256; + bnum = -1; + restore = false; + + return true; +} + +void Cleanup() +{ + bus.Cleanup(); +} + +void Reset() +{ + // Reset bus signal line + bus.Reset(); +} + +bool ParseArgument(int argc, char* argv[]) +{ + int opt; + char *file; + + // Initialization + file = NULL; + + // Argument parsing + opterr = 0; + while ((opt = getopt(argc, argv, "i:u:b:c:f:r")) != -1) { + switch (opt) { + case 'i': + targetid = optarg[0] - '0'; + break; + + case 'u': + unitid = optarg[0] - '0'; + break; + + case 'b': + bsiz = atoi(optarg); + break; + + case 'c': + bnum = atoi(optarg); + break; + + case 'f': + file = optarg; + break; + + case 'r': + restore = true; + break; + } + } + + // TARGET ID check + if (targetid < 0 || targetid > 7) { + fprintf(stderr, + "Error : Invalid target id range\n"); + return false; + } + + // UNIT ID check + if (unitid < 0 || unitid > 1) { + fprintf(stderr, + "Error : Invalid unit id range\n"); + return false; + } + + // BSIZ check + if (bsiz != 256 && bsiz != 512 && bsiz != 1024) { + fprintf(stderr, + "Error : Invalid block size\n"); + return false; + } + + // BNUM check + if (bnum < 0) { + fprintf(stderr, + "Error : Invalid block count\n"); + return false; + } + + // File check + if (!file) { + fprintf(stderr, + "Error : Invalid file path\n"); + return false; + } + + hdffile.SetPath(file); + + return true; +} + +bool WaitPhase(BUS::phase_t phase) +{ + DWORD now; + + // Timeout (3000ms) + now = SysTimer::GetTimerLow(); + while ((SysTimer::GetTimerLow() - now) < 3 * 1000 * 1000) { + bus.Aquire(); + if (bus.GetREQ() && bus.GetPhase() == phase) { + return true; + } + } + + return false; +} + +//--------------------------------------------------------------------------- +// +// Bus free phase execution +// +//--------------------------------------------------------------------------- +void BusFree() +{ + bus.Reset(); +} + +//--------------------------------------------------------------------------- +// +// Selection phase execution +// +//--------------------------------------------------------------------------- +bool Selection(int id) +{ + BYTE data; + int count; + + // ID setting and SEL assertion + data = 0; + data |= (1 << id); + bus.SetDAT(data); + bus.SetSEL(TRUE); + + // Wait for BSY + count = 10000; + do { + usleep(20); + bus.Aquire(); + if (bus.GetBSY()) { + break; + } + } while (count--); + + // SEL negate + bus.SetSEL(FALSE); + + // Return true if target is busy + return bus.GetBSY(); +} + +//--------------------------------------------------------------------------- +// +// Command phase execution +// +//--------------------------------------------------------------------------- +bool Command(BYTE *buf, int length) +{ + int count; + + // Wait for phase + if (!WaitPhase(BUS::command)) { + return false; + } + + // Send command + count = bus.SendHandShake(buf, length, BUS::SEND_NO_DELAY); + + // Return true is send results match number of requests + if (count == length) { + return true; + } + + // Send error + return false; +} + +//--------------------------------------------------------------------------- +// +// Data in phase execution +// +//--------------------------------------------------------------------------- +int DataIn(BYTE *buf, int length) +{ + // Wait for phase + if (!WaitPhase(BUS::datain)) { + return -1; + } + + // Receive data + return bus.ReceiveHandShake(buf, length); +} + +//--------------------------------------------------------------------------- +// +// Data out phase execution +// +//--------------------------------------------------------------------------- +int DataOut(BYTE *buf, int length) +{ + // Wait for phase + if (!WaitPhase(BUS::dataout)) { + return -1; + } + + // Receive data + return bus.SendHandShake(buf, length, BUS::SEND_NO_DELAY); +} + +//--------------------------------------------------------------------------- +// +// Status phase execution +// +//--------------------------------------------------------------------------- +int Status() +{ + BYTE buf[256]; + + // Wait for phase + if (!WaitPhase(BUS::status)) { + return -2; + } + + // Receive data + if (bus.ReceiveHandShake(buf, 1) == 1) { + return (int)buf[0]; + } + + // Receive error + return -1; +} + +//--------------------------------------------------------------------------- +// +// Message in phase execution +// +//--------------------------------------------------------------------------- +int MessageIn() +{ + BYTE buf[256]; + + // Wait for phase + if (!WaitPhase(BUS::msgin)) { + return -2; + } + + // Receive data + if (bus.ReceiveHandShake(buf, 1) == 1) { + return (int)buf[0]; + } + + // Receive error + return -1; +} + +//--------------------------------------------------------------------------- +// +// TEST UNIT READY execution +// +//--------------------------------------------------------------------------- +int TestUnitReady(int id) +{ + BYTE cmd[256]; + + // Initialize result code + result = 0; + + if (!Selection(id)) { + result = -1; + goto exit; + } + + memset(cmd, 0x00, 6); + cmd[0] = 0x00; + cmd[1] = unitid << 5; + if (!Command(cmd, 6)) { + result = -2; + goto exit; + } + + if (Status() < 0) { + result = -4; + goto exit; + } + + if (MessageIn() < 0) { + result = -5; + goto exit; + } + +exit: + BusFree(); + + return result; +} + +//--------------------------------------------------------------------------- +// +// REQUEST SENSE execution +// +//--------------------------------------------------------------------------- +int RequestSense(int id, BYTE *buf) +{ + BYTE cmd[256]; + int count; + + // Initialize result codes + result = 0; + count = 0; + + if (!Selection(id)) { + result = -1; + goto exit; + } + + memset(cmd, 0x00, 6); + cmd[0] = 0x03; + cmd[1] = unitid << 5; + cmd[4] = 4; + if (!Command(cmd, 6)) { + result = -2; + goto exit; + } + + memset(buf, 0x00, 256); + count = DataIn(buf, 256); + if (count <= 0) { + result = -3; + goto exit; + } + + if (Status() < 0) { + result = -4; + goto exit; + } + + if (MessageIn() < 0) { + result = -5; + goto exit; + } + +exit: + BusFree(); + + // If successful, return number of transfers + if (result == 0) { + return count; + } + + return result; +} + +//--------------------------------------------------------------------------- +// +// READ6 execution +// +//--------------------------------------------------------------------------- +int Read6(int id, DWORD bstart, DWORD blength, DWORD length, BYTE *buf) +{ + BYTE cmd[256]; + int count; + + // Initialize result codes + result = 0; + count = 0; + + if (!Selection(id)) { + result = -1; + goto exit; + } + + memset(cmd, 0x00, 10); + cmd[0] = 0x8; + cmd[1] = (BYTE)(bstart >> 16); + cmd[1] &= 0x1f; + cmd[1] = unitid << 5; + cmd[2] = (BYTE)(bstart >> 8); + cmd[3] = (BYTE)bstart; + cmd[4] = (BYTE)blength; + if (!Command(cmd, 6)) { + result = -2; + goto exit; + } + + count = DataIn(buf, length); + if (count <= 0) { + result = -3; + goto exit; + } + + if (Status() < 0) { + result = -4; + goto exit; + } + + if (MessageIn() < 0) { + result = -5; + goto exit; + } + +exit: + BusFree(); + + // If successful, return number of transfers + if (result == 0) { + return count; + } + + return result; +} + +//--------------------------------------------------------------------------- +// +// WRITE6 execution +// +//--------------------------------------------------------------------------- +int Write6(int id, DWORD bstart, DWORD blength, DWORD length, BYTE *buf) +{ + BYTE cmd[256]; + int count; + + // Initialize result codes + result = 0; + count = 0; + + if (!Selection(id)) { + result = -1; + goto exit; + } + + memset(cmd, 0x00, 10); + cmd[0] = 0xa; + cmd[1] = (BYTE)(bstart >> 16); + cmd[1] &= 0x1f; + cmd[1] = unitid << 5; + cmd[2] = (BYTE)(bstart >> 8); + cmd[3] = (BYTE)bstart; + cmd[4] = (BYTE)blength; + if (!Command(cmd, 6)) { + result = -2; + goto exit; + } + + count = DataOut(buf, length); + if (count <= 0) { + result = -3; + goto exit; + } + + if (Status() < 0) { + result = -4; + goto exit; + } + + if (MessageIn() < 0) { + result = -5; + goto exit; + } + +exit: + BusFree(); + + // If successful, return number of transfers + if (result == 0) { + return count; + } + + return result; +} + +//--------------------------------------------------------------------------- +// +// Main processing +// +//--------------------------------------------------------------------------- +int main(int argc, char* argv[]) +{ + int i; + int count; + DWORD duni; + DWORD dsiz; + DWORD dnum; + Fileio fio; + Fileio::OpenMode omode; + off_t size; + + // Output banner + if (!Banner(argc, argv)) { + exit(0); + } + + if (!Init()) { + fprintf(stderr, "Error : Initializing\n"); + + // Probably not executing as root? + exit(EPERM); + } + + if (!ParseArgument(argc, argv)) { + Cleanup(); + + // Exit with argument error + exit(EINVAL); + } + + Reset(); + + // Open file + if (restore) { + omode = Fileio::ReadOnly; + } else { + omode = Fileio::WriteOnly; + } + if (!fio.Open(hdffile.GetPath(), omode)) { + fprintf(stderr, "Error : Can't open hdf file\n"); + + Cleanup(); + exit(EPERM); + } + + BusFree(); + + // Execute RESET signal + bus.SetRST(TRUE); + usleep(1000); + bus.SetRST(FALSE); + + // Start dump + printf("TARGET ID : %d\n", targetid); + printf("UNIT ID : %d\n", unitid); + + // TEST UNIT READY + count = TestUnitReady(targetid); + if (count < 0) { + fprintf(stderr, "TEST UNIT READY ERROR %d\n", count); + goto cleanup_exit; + } + + // REQUEST SENSE (for CHECK CONDITION) + count = RequestSense(targetid, buffer); + if (count < 0) { + fprintf(stderr, "REQUEST SENSE ERROR %d\n", count); + goto cleanup_exit; + } + + printf("Number of blocks : %d Blocks\n", bnum); + printf("Block length : %d Bytes\n", bsiz); + + // Display data size + printf("Total length : %d MBytes %d Bytes\n", + (bsiz * bnum / 1024 / 1024), + (bsiz * bnum)); + + // Get restore file size + if (restore) { + size = fio.GetFileSize(); + printf("Restore file size : %d bytes", (int)size); + if (size > (off_t)(bsiz * bnum)) { + printf("(WARNING : File size is larger than disk size)"); + } else if (size < (off_t)(bsiz * bnum)) { + printf("(ERROR : File size is smaller than disk size)\n"); + goto cleanup_exit; + } + printf("\n"); + } + + // Dump by buffer size + duni = BUFSIZE; + duni /= bsiz; + dsiz = BUFSIZE; + dnum = bnum * bsiz; + dnum /= BUFSIZE; + + if (restore) { + printf("Restore progress : "); + } else { + printf("Dump progress : "); + } + + for (i = 0; i < (int)dnum; i++) { + if (i > 0) { + printf("\033[21D"); + printf("\033[0K"); + } + printf("%3d%%(%7d/%7d)", + (int)((i + 1) * 100 / dnum), + (int)(i * duni), + bnum); + fflush(stdout); + + if (restore) { + if (fio.Read(buffer, dsiz)) { + if (Write6(targetid, i * duni, duni, dsiz, buffer) >= 0) { + continue; + } + } + } else { + if (Read6(targetid, i * duni, duni, dsiz, buffer) >= 0) { + if (fio.Write(buffer, dsiz)) { + continue; + } + } + } + + printf("\n"); + printf("Error occured and aborted... %d\n", result); + goto cleanup_exit; + } + + if (dnum > 0) { + printf("\033[21D"); + printf("\033[0K"); + } + + // Rounding of capacity + dnum = bnum % duni; + dsiz = dnum * bsiz; + + if (dnum > 0) { + if (restore) { + if (fio.Read(buffer, dsiz)) { + Write6(targetid, i * duni, dnum, dsiz, buffer); + } + } else { + if (Read6(targetid, i * duni, dnum, dsiz, buffer) >= 0) { + fio.Write(buffer, dsiz); + } + } + } + + // Completion message + printf("%3d%%(%7d/%7d)\n", 100, bnum, bnum); + +cleanup_exit: + fio.Close(); + + Cleanup(); + + exit(0); +} diff --git a/src_old/raspberrypi/scsi.cpp b/src_old/raspberrypi/scsi.cpp new file mode 100644 index 00000000..5fbd3243 --- /dev/null +++ b/src_old/raspberrypi/scsi.cpp @@ -0,0 +1,93 @@ +//--------------------------------------------------------------------------- +// +// X68000 EMULATOR "XM6" +// +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// +// [ SCSI Common Functionality ] +// +//--------------------------------------------------------------------------- + +#include "os.h" +#include "scsi.h" + +//--------------------------------------------------------------------------- +// +// Phase Acquisition +// +//--------------------------------------------------------------------------- +BUS::phase_t BUS::GetPhase() +{ + // Selection Phase + if (GetSEL()) { + return selection; + } + + // Bus busy phase + if (!GetBSY()) { + return busfree; + } + + // Get target phase from bus signal line + DWORD mci = GetMSG() ? 0x04 : 0x00; + mci |= GetCD() ? 0x02 : 0x00; + mci |= GetIO() ? 0x01 : 0x00; + return GetPhase(mci); +} + +//--------------------------------------------------------------------------- +// +// Determine Phase String phase enum +// +//--------------------------------------------------------------------------- +const char* BUS::GetPhaseStrRaw(phase_t current_phase){ + if(current_phase <= phase_t::reserved){ + return phase_str_table[current_phase]; + } + else + { + return "INVALID"; + } +} + +//--------------------------------------------------------------------------- +// +// Phase Table +// Reference Table 8: https://www.staff.uni-mainz.de/tacke/scsi/SCSI2-06.html +// This determines the phase based upon the Msg, C/D and I/O signals. +// +//--------------------------------------------------------------------------- +const BUS::phase_t BUS::phase_table[8] = { + // | MSG|C/D|I/O | + dataout, // | 0 | 0 | 0 | + datain, // | 0 | 0 | 1 | + command, // | 0 | 1 | 0 | + status, // | 0 | 1 | 1 | + reserved, // | 1 | 0 | 0 | + reserved, // | 1 | 0 | 1 | + msgout, // | 1 | 1 | 0 | + msgin // | 1 | 1 | 1 | +}; + + +//--------------------------------------------------------------------------- +// +// Phase Table +// This MUST be kept in sync with the phase_t enum type! +// +//--------------------------------------------------------------------------- +const char* BUS::phase_str_table[] = { + "busfree", + "arbitration", + "selection", + "reselection", + "command", + "execute", + "datain", + "dataout", + "status", + "msgin", + "msgout", + "reserved" +}; diff --git a/src_old/raspberrypi/scsi.h b/src_old/raspberrypi/scsi.h new file mode 100644 index 00000000..0c9ec109 --- /dev/null +++ b/src_old/raspberrypi/scsi.h @@ -0,0 +1,223 @@ +//--------------------------------------------------------------------------- +// +// X68000 EMULATOR "XM6" +// +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// +// [ SCSI Common Functionality ] +// +//--------------------------------------------------------------------------- + +#pragma once +#include "os.h" + +//=========================================================================== +// +// Status byte codes, Sense Keys and Additional Sense Codes +// (See https://www.t10.org/lists/1spc-lst.htm) +// +//=========================================================================== +class ERROR_CODES +{ +public: + enum status : int { + GOOD = 0x00, + CHECK_CONDITION = 0x02, + CONDITION_MET = 0x04, + BUSY = 0x08, + INTERMEDIATE = 0x10, + INTERMEDIATE_CONDITION_MET = 0x14, + RESERVATION_CONFLICT = 0x18, + COMMAND_TERMINATED = 0x22, + QUEUE_FULL = 0x28 + }; + + enum sense_key : int { + NO_SENSE = 0x00, + RECOVERED_ERROR = 0x01, + NOT_READY = 0x02, + MEDIUM_ERROR = 0x03, + HARDWARE_ERROR = 0x04, + ILLEGAL_REQUEST = 0x05, + UNIT_ATTENTION = 0x06, + DATA_PROTECT = 0x07, + BLANK_CHECK = 0x08, + VENDOR_SPECIFIC = 0x09, + COPY_ABORTED = 0x0a, + ABORTED_COMMAND = 0x0b, + VOLUME_OVERFLOW = 0x0d, + MISCOMPARE = 0x0e, + COMPLETED = 0x0f + }; + + enum asc : int { + NO_ADDITIONAL_SENSE_INFORMATION = 0x00, + INVALID_COMMAND_OPERATION_CODE = 0x20, + LBA_OUT_OF_RANGE = 0x21, + INVALID_FIELD_IN_CDB = 0x24, + INVALID_LUN = 0x25, + WRITE_PROTECTED = 0x27, + NOT_READY_TO_READY_CHANGE = 0x28, + MEDIUM_NOT_PRESENT = 0x3a + }; +}; + +//=========================================================================== +// +// SASI/SCSI Bus +// +//=========================================================================== +class BUS +{ +public: + // Operation modes definition + enum mode_e { + TARGET = 0, + INITIATOR = 1, + MONITOR = 2, + }; + + // Phase definitions + enum phase_t : BYTE { + busfree, + arbitration, + selection, + reselection, + command, + execute, // Execute phase is an extension of the command phase + datain, + dataout, + status, + msgin, + msgout, + reserved // Unused + }; + + BUS() { }; + virtual ~BUS() { }; + + // Basic Functions + virtual BOOL Init(mode_e mode) = 0; + virtual void Reset() = 0; + virtual void Cleanup() = 0; + phase_t GetPhase(); + + static phase_t GetPhase(DWORD mci) + { + return phase_table[mci]; + } + + static const char* GetPhaseStrRaw(phase_t current_phase); + // Get the string phase name, based upon the raw data + + // Extract as specific pin field from a raw data capture + static inline DWORD GetPinRaw(DWORD raw_data, DWORD pin_num) + { + return ((raw_data >> pin_num) & 1); + } + + virtual bool GetBSY() = 0; + virtual void SetBSY(bool ast) = 0; + + virtual BOOL GetSEL() = 0; + virtual void SetSEL(BOOL ast) = 0; + + virtual BOOL GetATN() = 0; + virtual void SetATN(BOOL ast) = 0; + + virtual BOOL GetACK() = 0; + virtual void SetACK(BOOL ast) = 0; + + virtual BOOL GetRST() = 0; + virtual void SetRST(BOOL ast) = 0; + + virtual BOOL GetMSG() = 0; + virtual void SetMSG(BOOL ast) = 0; + + virtual BOOL GetCD() = 0; + virtual void SetCD(BOOL ast) = 0; + + virtual BOOL GetIO() = 0; + virtual void SetIO(BOOL ast) = 0; + + virtual BOOL GetREQ() = 0; + virtual void SetREQ(BOOL ast) = 0; + + virtual BYTE GetDAT() = 0; + virtual void SetDAT(BYTE dat) = 0; + virtual BOOL GetDP() = 0; // Get parity signal + + virtual DWORD Aquire() = 0; + virtual int CommandHandShake(BYTE *buf) = 0; + virtual int ReceiveHandShake(BYTE *buf, int count) = 0; + virtual int SendHandShake(BYTE *buf, int count, int delay_after_bytes) = 0; + + virtual BOOL GetSignal(int pin) = 0; + // Get SCSI input signal value + virtual void SetSignal(int pin, BOOL ast) = 0; + // Set SCSI output signal value + static const int SEND_NO_DELAY = -1; + // Passed into SendHandShake when we don't want to delay +protected: + phase_t m_current_phase = phase_t::reserved; + +private: + static const phase_t phase_table[8]; + + static const char* phase_str_table[]; +}; + +namespace scsi_defs { + enum scsi_command : int { + eCmdTestUnitReady = 0x00, + eCmdRezero = 0x01, + eCmdRequestSense = 0x03, + eCmdFormat = 0x04, + eCmdReassign = 0x07, + eCmdRead6 = 0x08, + // DaynaPort specific command + eCmdRetrieveStats = 0x09, + eCmdWrite6 = 0x0A, + eCmdSeek6 = 0x0B, + // DaynaPort specific command + eCmdSetIfaceMode = 0x0C, + // DaynaPort specific command + eCmdSetMcastAddr = 0x0D, + // DaynaPort specific command + eCmdEnableInterface = 0x0E, + eCmdSynchronizeBuffer = 0x10, + eCmdInquiry = 0x12, + eCmdModeSelect6 = 0x15, + eCmdReserve6 = 0x16, + eCmdRelease6 = 0x17, + eCmdModeSense6 = 0x1A, + eCmdStartStop = 0x1B, + eCmdSendDiag = 0x1D, + eCmdRemoval = 0x1E, + // ICD specific command + eCmdIcd = 0x1F, + eCmdReadCapacity10 = 0x25, + eCmdRead10 = 0x28, + eCmdWrite10 = 0x2A, + eCmdSeek10 = 0x2B, + eCmdVerify10 = 0x2F, + eCmdSynchronizeCache10 = 0x35, + eCmdReadDefectData10 = 0x37, + eCmdReadLong10 = 0x3E, + eCmdWriteLong10 = 0x3F, + eCmdReadToc = 0x43, + eCmdGetEventStatusNotification = 0x4A, + eCmdModeSelect10 = 0x55, + eCmdReserve10 = 0x56, + eCmdRelease10 = 0x57, + eCmdModeSense10 = 0x5A, + eCmdRead16 = 0x88, + eCmdWrite16 = 0x8A, + eCmdVerify16 = 0x8F, + eCmdSynchronizeCache16 = 0x91, + eCmdReadCapacity16_ReadLong16 = 0x9E, + eCmdWriteLong16 = 0x9F, + eCmdReportLuns = 0xA0 + }; +}; diff --git a/src_old/raspberrypi/scsimon.cpp b/src_old/raspberrypi/scsimon.cpp new file mode 100644 index 00000000..092cc24d --- /dev/null +++ b/src_old/raspberrypi/scsimon.cpp @@ -0,0 +1,444 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Powered by XM6 TypeG Technology. +// Copyright (C) 2016-2020 GIMONS +// +//--------------------------------------------------------------------------- + +#include "os.h" +#include "log.h" +#include "gpiobus.h" +#include "rascsi_version.h" +#include "spdlog/spdlog.h" +#include +#include +#include +#include +#include +#include +#include "monitor/sm_reports.h" +#include "monitor/data_sample.h" + +using namespace std; + +//--------------------------------------------------------------------------- +// +// Constant declarations +// +//--------------------------------------------------------------------------- + +// Symbol definition for the VCD file +// These are just arbitrary symbols. They can be anything allowed by the VCD file format, +// as long as they're consistently used. +#define SYMBOL_PIN_DAT '#' +#define SYMBOL_PIN_ATN '+' +#define SYMBOL_PIN_RST '$' +#define SYMBOL_PIN_ACK '%' +#define SYMBOL_PIN_REQ '^' +#define SYMBOL_PIN_MSG '&' +#define SYMBOL_PIN_CD '*' +#define SYMBOL_PIN_IO '(' +#define SYMBOL_PIN_BSY ')' +#define SYMBOL_PIN_SEL '-' +#define SYMBOL_PIN_PHASE '=' + +//--------------------------------------------------------------------------- +// +// Variable declarations +// +//--------------------------------------------------------------------------- +static volatile bool running; // Running flag +GPIOBUS *bus; // GPIO Bus + +DWORD buff_size = 1000000; +data_capture *data_buffer; +DWORD data_idx = 0; + +double ns_per_loop; + +bool print_help = false; +bool import_data = false; + +// We don't really need to support 256 character file names - this causes +// all kinds of compiler warnings when the log filename can be up to 256 +// characters. _MAX_FNAME/2 is just an arbitrary value. +char file_base_name[_MAX_FNAME / 2] = "log"; +char vcd_file_name[_MAX_FNAME]; +char json_file_name[_MAX_FNAME]; +char html_file_name[_MAX_FNAME]; +char input_file_name[_MAX_FNAME]; + +//--------------------------------------------------------------------------- +// +// Signal Processing +// +//--------------------------------------------------------------------------- +void KillHandler(int sig) +{ + // Stop instruction + running = false; +} + +void parse_arguments(int argc, char *argv[]) +{ + int opt; + + while ((opt = getopt(argc, argv, "-Hhb:i:")) != -1) + { + switch (opt) + { + // The three options below are kind of a compound option with two letters + case 'h': + case 'H': + print_help = true; + break; + case 'b': + buff_size = atoi(optarg); + break; + case 'i': + strncpy(input_file_name, optarg, sizeof(input_file_name)-1); + import_data = true; + break; + case 1: + strncpy(file_base_name, optarg, sizeof(file_base_name) - 5); + break; + default: + cout << "default: " << optarg << endl; + break; + } + } + + /* Process any remaining command line arguments (not options). */ + if (optind < argc) + { + while (optind < argc) + strncpy(file_base_name, argv[optind++], sizeof(file_base_name)-1); + } + + strcpy(vcd_file_name, file_base_name); + strcat(vcd_file_name, ".vcd"); + strcpy(json_file_name, file_base_name); + strcat(json_file_name, ".json"); + strcpy(html_file_name, file_base_name); + strcat(html_file_name, ".html"); +} +//--------------------------------------------------------------------------- +// +// Copyright text +// +//--------------------------------------------------------------------------- +void print_copyright_text(int argc, char *argv[]) +{ + LOGINFO("SCSI Monitor Capture Tool - part of RaSCSI(*^..^*) "); + LOGINFO("version %s (%s, %s)", + rascsi_get_version_string(), + __DATE__, + __TIME__); + LOGINFO("Powered by XM6 TypeG Technology "); + LOGINFO("Copyright (C) 2016-2020 GIMONS"); + LOGINFO("Copyright (C) 2020-2021 Contributors to the RaSCSI project"); + LOGINFO(" "); +} + +//--------------------------------------------------------------------------- +// +// Help text +// +//--------------------------------------------------------------------------- +void print_help_text(int argc, char *argv[]) +{ + LOGINFO("%s -i [input file json] -b [buffer size] [output file]", argv[0]); + LOGINFO(" -i [input file json] - scsimon will parse the json file instead of capturing new data"); + LOGINFO(" If -i option is not specified, scsimon will read the gpio pins"); + LOGINFO(" -b [buffer size] - Override the default buffer size of %d.", buff_size); + LOGINFO(" [output file] - Base name of the output files. The file extension (ex: .json)"); + LOGINFO(" will be appended to this file name"); +} + +//--------------------------------------------------------------------------- +// +// Banner Output +// +//--------------------------------------------------------------------------- +void Banner(int argc, char *argv[]) +{ + if (import_data) + { + LOGINFO("Reading input file: %s", input_file_name); + } + else + { + LOGINFO("Reading live data from the GPIO pins"); + LOGINFO(" Connection type : %s", CONNECT_DESC); + } + LOGINFO(" Data buffer size: %u", buff_size); + LOGINFO(" "); + LOGINFO("Generating output files:"); + LOGINFO(" %s - Value Change Dump file that can be opened with GTKWave", vcd_file_name); + LOGINFO(" %s - JSON file with raw data", json_file_name); + LOGINFO(" %s - HTML file with summary of commands", html_file_name); +} + +//--------------------------------------------------------------------------- +// +// Initialization +// +//--------------------------------------------------------------------------- +bool Init() +{ + // Interrupt handler settings + if (signal(SIGINT, KillHandler) == SIG_ERR) + { + return FALSE; + } + if (signal(SIGHUP, KillHandler) == SIG_ERR) + { + return FALSE; + } + if (signal(SIGTERM, KillHandler) == SIG_ERR) + { + return FALSE; + } + + // GPIO Initialization + bus = new GPIOBUS(); + if (!bus->Init()) + { + LOGERROR("Unable to intiailize the GPIO bus. Exiting...."); + return false; + } + + // Bus Reset + bus->Reset(); + + // Other + running = false; + + return true; +} + +void Cleanup() +{ + if (!import_data) + { + LOGINFO("Stopping data collection...."); + } + LOGINFO(" "); + LOGINFO("Generating %s...", vcd_file_name); + scsimon_generate_value_change_dump(vcd_file_name, data_buffer, data_idx); + LOGINFO("Generating %s...", json_file_name); + scsimon_generate_json(json_file_name, data_buffer, data_idx); + LOGINFO("Generating %s...", html_file_name); + scsimon_generate_html(html_file_name, data_buffer, data_idx); + + if (bus) + { + // Cleanup the Bus + bus->Cleanup(); + delete bus; + } +} + +void Reset() +{ + // Reset the bus + bus->Reset(); +} + +//--------------------------------------------------------------------------- +// +// Pin the thread to a specific CPU (Only applies to Linux) +// +//--------------------------------------------------------------------------- +#ifdef __linux__ +void FixCpu(int cpu) +{ + // Get the number of CPUs + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + sched_getaffinity(0, sizeof(cpu_set_t), &cpuset); + int cpus = CPU_COUNT(&cpuset); + + // Set the thread affinity + if (cpu < cpus) + { + CPU_ZERO(&cpuset); + CPU_SET(cpu, &cpuset); + sched_setaffinity(0, sizeof(cpu_set_t), &cpuset); + } +} +#endif + +#ifdef DEBUG +static DWORD high_bits = 0x0; +static DWORD low_bits = 0xFFFFFFFF; +#endif + +//--------------------------------------------------------------------------- +// +// Main processing +// +//--------------------------------------------------------------------------- +int main(int argc, char *argv[]) +{ + +#ifdef DEBUG + spdlog::set_level(spdlog::level::trace); +#else + spdlog::set_level(spdlog::level::info); +#endif + spdlog::set_pattern("%^[%l]%$ %v"); + + print_copyright_text(argc, argv); + parse_arguments(argc, argv); + +#ifdef DEBUG + DWORD prev_high = high_bits; + DWORD prev_low = low_bits; +#endif + ostringstream s; + DWORD prev_sample = 0xFFFFFFFF; + DWORD this_sample = 0; + timeval start_time; + timeval stop_time; + uint64_t loop_count = 0; + timeval time_diff; + uint64_t elapsed_us; + + if (print_help) + { + print_help_text(argc, argv); + exit(0); + } + + // Output the Banner + Banner(argc, argv); + + data_buffer = (data_capture *)malloc(sizeof(data_capture_t) * buff_size); + bzero(data_buffer, sizeof(data_capture_t) * buff_size); + + if (import_data) + { + data_idx = scsimon_read_json(input_file_name, data_buffer, buff_size); + if (data_idx > 0) + { + LOGDEBUG("Read %d samples from %s", data_idx, input_file_name); + Cleanup(); + } + exit(0); + } + + LOGINFO(" "); + LOGINFO("Now collecting data.... Press CTRL-C to stop.") + LOGINFO(" "); + + // Initialize + int ret = 0; + if (!Init()) + { + ret = EPERM; + goto init_exit; + } + + // Reset + Reset(); + +#ifdef __linux__ + // Set the affinity to a specific processor core + FixCpu(3); + + // Scheduling policy setting (highest priority) + struct sched_param schparam; + schparam.sched_priority = sched_get_priority_max(SCHED_FIFO); + sched_setscheduler(0, SCHED_FIFO, &schparam); +#endif + + // Start execution + running = true; + bus->SetACT(FALSE); + + (void)gettimeofday(&start_time, NULL); + + LOGDEBUG("ALL_SCSI_PINS %08X\n", ALL_SCSI_PINS); + + // Main Loop + while (running) + { + // Work initialization + this_sample = (bus->Aquire() & ALL_SCSI_PINS); + loop_count++; + if (loop_count > LLONG_MAX - 1) + { + LOGINFO("Maximum amount of time has elapsed. SCSIMON is terminating."); + running = false; + } + if (data_idx >= (buff_size - 2)) + { + LOGINFO("Internal data buffer is full. SCSIMON is terminating."); + running = false; + } + + if (this_sample != prev_sample) + { + +#ifdef DEBUG + // This is intended to be a debug check to see if every pin is set + // high and low at some point. + high_bits |= this_sample; + low_bits &= this_sample; + if ((high_bits != prev_high) || (low_bits != prev_low)) + { + LOGDEBUG(" %08X %08X\n", high_bits, low_bits); + } + prev_high = high_bits; + prev_low = low_bits; + if ((data_idx % 1000) == 0) + { + s.str(""); + s << "Collected " << data_idx << " samples..."; + LOGDEBUG("%s", s.str().c_str()); + } +#endif + data_buffer[data_idx].data = this_sample; + data_buffer[data_idx].timestamp = loop_count; + data_idx++; + prev_sample = this_sample; + } + + continue; + } + + // Collect one last sample, otherwise it looks like the end of the data was cut off + if (data_idx < buff_size) + { + data_buffer[data_idx].data = this_sample; + data_buffer[data_idx].timestamp = loop_count; + data_idx++; + } + + (void)gettimeofday(&stop_time, NULL); + + timersub(&stop_time, &start_time, &time_diff); + + elapsed_us = ((time_diff.tv_sec * 1000000) + time_diff.tv_usec); + s.str(""); + s << "Elapsed time: " << elapsed_us << " microseconds (" << elapsed_us / 1000000 << " seconds)"; + LOGINFO("%s", s.str().c_str()); + s.str(""); + s << "Collected " << data_idx << " changes"; + LOGINFO("%s", s.str().c_str()); + + // Note: ns_per_loop is a global variable that is used by Cleanup() to printout the timestamps. + ns_per_loop = (elapsed_us * 1000) / (double)loop_count; + s.str(""); + s << "Read the SCSI bus " << loop_count << " times with an average of " << ns_per_loop << " ns for each read"; + LOGINFO("%s", s.str().c_str()); + + Cleanup(); + +init_exit: + exit(ret); +} diff --git a/src_old/x68k/RASCTL/Makefile b/src_old/x68k/RASCTL/Makefile new file mode 100644 index 00000000..b10bd182 --- /dev/null +++ b/src_old/x68k/RASCTL/Makefile @@ -0,0 +1,13 @@ +# +# Makefile for RASCTL.X +# + +CC = gcc -O2 -Wall +LK = hlk -l -x + +RASCTL.X : RASCTL.o + $(LK) -o $@ $^ libc.a libiocs.a libscsi.a libgnu.a + +RASCTL.o : RASCTL.C + $(CC) -c -o $@ $^ + \ No newline at end of file diff --git a/src_old/x68k/RASCTL/RASCTL.C b/src_old/x68k/RASCTL/RASCTL.C new file mode 100644 index 00000000..c711683f --- /dev/null +++ b/src_old/x68k/RASCTL/RASCTL.C @@ -0,0 +1,433 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// Powered by XM6 TypeG Technology. +// +// Copyright (C) 2016-2021 GIMONS(Twitter:@kugimoto0715) +// +// [ Sending commands ] +// +//--------------------------------------------------------------------------- + +#include +#include +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------------- +// +// Primitive definitions +// +//--------------------------------------------------------------------------- +typedef unsigned int UINT; +typedef unsigned char BYTE; +typedef unsigned short WORD; +typedef unsigned long DWORD; +typedef unsigned long long UL64; +typedef int BOOL; +typedef char TCHAR; +typedef char *LPTSTR; +typedef const char *LPCTSTR; +typedef const char *LPCSTR; + +#if !defined(FALSE) +#define FALSE 0 +#endif + +#if !defined(TRUE) +#define TRUE 1 +#endif + +//--------------------------------------------------------------------------- +// +// Constant definitions +// +//--------------------------------------------------------------------------- +#define ENOTCONN 107 + +//--------------------------------------------------------------------------- +// +// Struct definitions +// +//--------------------------------------------------------------------------- +typedef struct +{ + char DeviceType; + char RMB; + char ANSI_Ver; + char RDF; + char AddLen; + char RESV0; + char RESV1; + char OptFunc; + char VendorID[8]; + char ProductID[16]; + char FirmRev[4]; +} INQUIRY_T; + +typedef struct +{ + INQUIRY_T info; + char options[8]; +} INQUIRYOPT_T; + +//--------------------------------------------------------------------------- +// +// Global variables +// +//--------------------------------------------------------------------------- +int scsiid = -1; + +//--------------------------------------------------------------------------- +// +// Search for bridge device +// +//--------------------------------------------------------------------------- +BOOL SearchRaSCSI() +{ + int i; + INQUIRYOPT_T inq; + + for (i = 7; i >= 0; i--) { + // Search for bridge device + if (S_INQUIRY(sizeof(INQUIRY_T) , i, (struct INQUIRY*)&inq) < 0) { + continue; + } + + if (memcmp(&(inq.info.ProductID), "RASCSI BRIDGE", 13) != 0) { + continue; + } + + // Check if option functions are initialized + if (S_INQUIRY(sizeof(INQUIRYOPT_T) , i, (struct INQUIRY*)&inq) < 0) { + continue; + } + + if (inq.options[0] != '1') { + continue; + } + + // Confirm SCSI ID + scsiid = i; + return TRUE; + } + + return FALSE; +} + +//--------------------------------------------------------------------------- +// +// Get messages +// +//--------------------------------------------------------------------------- +BOOL ScsiGetMessage(BYTE *buf) +{ + unsigned char cmd[10]; + unsigned char sts; + unsigned char msg; + + if (S_SELECT(scsiid) != 0) { + return FALSE; + } + + cmd[0] = 0x28; + cmd[1] = 0; + cmd[2] = 0; + cmd[3] = 0; + cmd[4] = 0; + cmd[5] = 0; + cmd[6] = 0; + cmd[7] = 0x04; + cmd[8] = 0; + cmd[9] = 0; + if (S_CMDOUT(10, cmd) != 0) { + return FALSE; + } + + if (S_DATAIN_P(1024, buf) != 0) { + return FALSE; + } + + S_STSIN(&sts); + S_MSGIN(&msg); + + return TRUE; +} + +//--------------------------------------------------------------------------- +// +// Send messages +// +//--------------------------------------------------------------------------- +BOOL ScsiSendMessage(BYTE *buf) +{ + unsigned char cmd[10]; + unsigned char sts; + unsigned char msg; + + if (S_SELECT(scsiid) != 0) { + return FALSE; + } + + cmd[0] = 0x2a; + cmd[1] = 0; + cmd[2] = 0; + cmd[3] = 0; + cmd[4] = 0; + cmd[5] = 0; + cmd[6] = 0; + cmd[7] = 0x04; + cmd[8] = 0; + cmd[9] = 0; + if (S_CMDOUT(10, cmd) != 0) { + return FALSE; + } + + S_DATAOUT_P(1024, buf); + + S_STSIN(&sts); + S_MSGIN(&msg); + + return TRUE; +} + +//--------------------------------------------------------------------------- +// +// Send commands +// +//--------------------------------------------------------------------------- +BOOL SendCommand(char *buf) +{ + BYTE message[1024]; + + memset(message, 0x00, 1024); + memcpy(message, buf, strlen(buf)); + + if (ScsiSendMessage(message)) { + memset(message, 0x00, 1024); + if (ScsiGetMessage(message)) { + printf("%s", (char*)message); + return TRUE; + } + } + + return FALSE; +} + +//--------------------------------------------------------------------------- +// +// Main process +// +//--------------------------------------------------------------------------- +void main(int argc, char* argv[]) +{ + int opt; + int id; + int un; + int cmd; + int type; + char *file; + int len; + char *ext; + char buf[BUFSIZ]; + + id = -1; + un = 0; + cmd = -1; + type = -1; + file = NULL; + + // Display help + if (argc < 2) { + fprintf(stderr, "SCSI Target Emulator RaSCSI Controller\n"); + fprintf(stderr, + "Usage: %s -i ID [-u UNIT] [-c CMD] [-t TYPE] [-f FILE]\n", + argv[0]); + fprintf(stderr, " where ID := {0|1|2|3|4|5|6|7}\n"); + fprintf(stderr, " UNIT := {0|1} default setting is 0.\n"); + fprintf(stderr, " CMD := {attach|detach|insert|eject|protect}\n"); + fprintf(stderr, " TYPE := {hd|mo|cd|bridge}\n"); + fprintf(stderr, " FILE := image file path\n"); + fprintf(stderr, " CMD is 'attach' or 'insert' and FILE parameter is required.\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "Usage: %s -l\n\n", argv[0]); + fprintf(stderr, " Print device list.\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "Usage: %s --stop\n\n", argv[0]); + fprintf(stderr, " Stop rascsi prosess.\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "Usage: %s --shutdown\n\n", argv[0]); + fprintf(stderr, " Shutdown raspberry pi.\n"); + exit(0); + } + + // Look for bridge device + if (!SearchRaSCSI()) { + fprintf(stderr, "Error: bridge not found\n"); + exit(ENOTCONN); + } + + // Argument parsing + opterr = 0; + while ((opt = getopt(argc, argv, "i:u:c:t:f:l-:")) != -1) { + switch (opt) { + case 'i': + id = optarg[0] - '0'; + break; + + case 'u': + un = optarg[0] - '0'; + break; + + case 'c': + switch (optarg[0]) { + case 'a': // ATTACH + case 'A': + cmd = 0; + break; + case 'd': // DETACH + case 'D': + cmd = 1; + break; + case 'i': // INSERT + case 'I': + cmd = 2; + break; + case 'e': // EJECT + case 'E': + cmd = 3; + break; + case 'p': // PROTECT + case 'P': + cmd = 4; + break; + } + break; + + case 't': + switch (optarg[0]) { + case 's': // HD(SASI) + case 'S': + case 'h': // HD(SCSI) + case 'H': + type = 0; + break; + case 'm': // MO + case 'M': + type = 2; + break; + case 'c': // CD + case 'C': + type = 3; + break; + case 'b': // BRIDGE + case 'B': + type = 4; + break; + } + break; + + case 'f': + file = optarg; + break; + + case 'l': + sprintf(buf, "list\n"); + SendCommand(buf); + exit(0); + + case '-': + if (strcmp(optarg, "shutdown") == 0) { + sprintf(buf, "shutdown\n"); + SendCommand(buf); + exit(0); + } else if (strcmp(optarg, "stop") == 0) { + sprintf(buf, "stop\n"); + SendCommand(buf); + exit(0); + } + break; + } + } + + // ID check + if (id < 0 || id > 7) { + fprintf(stderr, "Error : Invalid ID\n"); + exit(EINVAL); + } + + // Unit check + if (un < 0 || un > 1) { + fprintf(stderr, "Error : Invalid UNIT\n"); + exit(EINVAL); + } + + // Command check + if (cmd < 0) { + cmd = 0; // The default is 'attach' + } + + // Type check + if (cmd == 0 && type < 0) { + + // Attempt type detection from extension + len = file ? strlen(file) : 0; + if (len > 4 && file[len - 4] == '.') { + ext = &file[len - 3]; + if (strcasecmp(ext, "hdf") == 0 || + strcasecmp(ext, "hds") == 0 || + strcasecmp(ext, "hdn") == 0 || + strcasecmp(ext, "hdi") == 0 || + strcasecmp(ext, "nhd") == 0 || + strcasecmp(ext, "hda") == 0) { + // HD(SASI/SCSI) + type = 0; + } else if (strcasecmp(ext, "mos") == 0) { + // MO + type = 2; + } else if (strcasecmp(ext, "iso") == 0) { + // CD + type = 3; + } + } + + if (type < 0) { + fprintf(stderr, "Error : Invalid type\n"); + exit(EINVAL); + } + } + + // File check (the command is 'attach' with type HD) + if (cmd == 0 && type >= 0 && type <= 1) { + if (!file) { + fprintf(stderr, "Error : Invalid file path\n"); + exit(EINVAL); + } + } + + // File check (the command is 'insert') + if (cmd == 2) { + if (!file) { + fprintf(stderr, "Error : Invalid file path\n"); + exit(EINVAL); + } + } + + // Useless types are set to 0 + if (type < 0) { + type = 0; + } + + // Create command to send + sprintf(buf, "%d %d %d %d %s\n", id, un, cmd, type, file ? file : "-"); + if (!SendCommand(buf)) { + exit(ENOTCONN); + } + + // Quit + exit(0); +} diff --git a/src_old/x68k/RASDRV/BRIDGE.C b/src_old/x68k/RASDRV/BRIDGE.C new file mode 100644 index 00000000..c9c85e71 --- /dev/null +++ b/src_old/x68k/RASDRV/BRIDGE.C @@ -0,0 +1,2430 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Powered by XM6 TypeG Technology. +// Copyright (C) 2016-2017 GIMONS +// [ Host Filesystem Bridge Driver ] +// +//--------------------------------------------------------------------------- + +#include +#include +#include +#include +#include +#include "bridge.h" + +//--------------------------------------------------------------------------- +// +// Variable definitions +// +//--------------------------------------------------------------------------- +volatile BYTE *request; // Request header address +DWORD command; // Command number +DWORD unit; // Unit number + +//=========================================================================== +// +/// File system (SCSI integration) +// +//=========================================================================== +typedef struct +{ + char DeviceType; + char RMB; + char ANSI_Ver; + char RDF; + char AddLen; + char RESV0; + char RESV1; + char OptFunc; + char VendorID[8]; + char ProductID[16]; + char FirmRev[4]; +} INQUIRY_T; + +int scsiid; // SCSI ID + +//--------------------------------------------------------------------------- +// +// Initialize +// +//--------------------------------------------------------------------------- +BOOL SCSI_Init(void) +{ + int i; + INQUIRY_T inq; + + // SCSI ID not set + scsiid = -1; + + for (i = 0; i <= 7; i++) { + if (S_INQUIRY(sizeof(INQUIRY_T) , i, (struct INQUIRY*)&inq) < 0) { + continue; + } + + if (memcmp(&(inq.ProductID), "RASCSI BRIDGE", 13) != 0) { + continue; + } + + // SCSI ID set + scsiid = i; + return TRUE; + } + + return FALSE; +} + +//--------------------------------------------------------------------------- +// +// Send commands +// +//--------------------------------------------------------------------------- +int SCSI_SendCmd(BYTE *buf, int len) +{ + int ret; + BYTE cmdbuf[10]; + BYTE sts; + BYTE msg; + BYTE retbuf[4]; + + ret = FS_FATAL_MEDIAOFFLINE; + + cmdbuf[0] = 0x2a; + cmdbuf[1] = 0; + cmdbuf[2] = 2; + cmdbuf[3] = command; + cmdbuf[4] = 0; + cmdbuf[5] = 0; + cmdbuf[6] = (BYTE)(len >> 16); + cmdbuf[7] = (BYTE)(len >> 8); + cmdbuf[8] = (BYTE)len; + cmdbuf[9] = 0; + + // Retry when select times out + if (S_SELECT(scsiid) != 0) { + S_RESET(); + if (S_SELECT(scsiid) != 0) { + return ret; + } + } + + if (S_CMDOUT(10, cmdbuf) != 0) { + return ret; + } + + if (S_DATAOUT_P(len, buf) != 0) { + return ret; + } + + if (S_STSIN(&sts) != 0) { + return ret; + } + + if (S_MSGIN(&msg) != 0) { + return ret; + } + + cmdbuf[0] = 0x28; + cmdbuf[1] = 0; + cmdbuf[2] = 2; + cmdbuf[3] = command; + cmdbuf[4] = 0; + cmdbuf[5] = 0; + cmdbuf[6] = 0; + cmdbuf[7] = 0; + cmdbuf[8] = 4; + cmdbuf[9] = 0; + + // Retry when select times out + if (S_SELECT(scsiid) != 0) { + S_RESET(); + if (S_SELECT(scsiid) != 0) { + return ret; + } + } + + if (S_CMDOUT(10, cmdbuf) != 0) { + return ret; + } + + if (S_DATAIN_P(4, retbuf) != 0) { + return ret; + } + + if (S_STSIN(&sts) != 0) { + return ret; + } + + if (S_MSGIN(&msg) != 0) { + return ret; + } + + ret = *(int*)retbuf; + return ret; +} + +//--------------------------------------------------------------------------- +// +// Call commands +// +//--------------------------------------------------------------------------- +int SCSI_CalCmd(BYTE *buf, int len, BYTE *outbuf, int outlen) +{ + int ret; + BYTE cmdbuf[10]; + BYTE sts; + BYTE msg; + BYTE retbuf[4]; + + ret = FS_FATAL_MEDIAOFFLINE; + + cmdbuf[0] = 0x2a; + cmdbuf[1] = 0; + cmdbuf[2] = 2; + cmdbuf[3] = command; + cmdbuf[4] = 0; + cmdbuf[5] = 0; + cmdbuf[6] = (BYTE)(len >> 16); + cmdbuf[7] = (BYTE)(len >> 8); + cmdbuf[8] = (BYTE)len; + cmdbuf[9] = 0; + + // Retry when select times out + if (S_SELECT(scsiid) != 0) { + S_RESET(); + if (S_SELECT(scsiid) != 0) { + return ret; + } + } + + if (S_CMDOUT(10, cmdbuf) != 0) { + return ret; + } + + if (S_DATAOUT_P(len, buf) != 0) { + return ret; + } + + if (S_STSIN(&sts) != 0) { + return ret; + } + + if (S_MSGIN(&msg) != 0) { + return ret; + } + + cmdbuf[0] = 0x28; + cmdbuf[1] = 0; + cmdbuf[2] = 2; + cmdbuf[3] = command; + cmdbuf[4] = 0; + cmdbuf[5] = 0; + cmdbuf[6] = 0; + cmdbuf[7] = 0; + cmdbuf[8] = 4; + cmdbuf[9] = 0; + + // Retry when select times out + if (S_SELECT(scsiid) != 0) { + S_RESET(); + if (S_SELECT(scsiid) != 0) { + return ret; + } + } + + if (S_CMDOUT(10, cmdbuf) != 0) { + return ret; + } + + if (S_DATAIN_P(4, retbuf) != 0) { + return ret; + } + + if (S_STSIN(&sts) != 0) { + return ret; + } + + if (S_MSGIN(&msg) != 0) { + return ret; + } + + // Stop receiving return data on error + ret = *(int*)retbuf; + if (ret < 0) { + return ret; + } + + cmdbuf[0] = 0x28; + cmdbuf[1] = 0; + cmdbuf[2] = 2; + cmdbuf[3] = command; + cmdbuf[4] = 0; + cmdbuf[5] = 0; + cmdbuf[6] = (BYTE)(outlen >> 16); + cmdbuf[7] = (BYTE)(outlen >> 8); + cmdbuf[8] = (BYTE)outlen; + cmdbuf[9] = 1; + + // Retry when select times out + if (S_SELECT(scsiid) != 0) { + S_RESET(); + if (S_SELECT(scsiid) != 0) { + return ret; + } + } + + if (S_CMDOUT(10, cmdbuf) != 0) { + return ret; + } + + if (S_DATAIN_P(outlen, outbuf) != 0) { + return ret; + } + + if (S_STSIN(&sts) != 0) { + return ret; + } + + if (S_MSGIN(&msg) != 0) { + return ret; + } + + ret = *(int*)retbuf; + return ret; +} + +//--------------------------------------------------------------------------- +// +// Get option data +// +//--------------------------------------------------------------------------- +BOOL SCSI_ReadOpt(BYTE *buf, int len) +{ + BYTE cmdbuf[10]; + BYTE sts; + BYTE msg; + + cmdbuf[0] = 0x28; + cmdbuf[1] = 0; + cmdbuf[2] = 2; + cmdbuf[3] = command; + cmdbuf[4] = 0; + cmdbuf[5] = 0; + cmdbuf[6] = (BYTE)(len >> 16); + cmdbuf[7] = (BYTE)(len >> 8); + cmdbuf[8] = (BYTE)len; + cmdbuf[9] = 2; + + // Retry when select times out + if (S_SELECT(scsiid) != 0) { + S_RESET(); + if (S_SELECT(scsiid) != 0) { + return FALSE; + } + } + + if (S_CMDOUT(10, cmdbuf) != 0) { + return FALSE; + } + + if (S_DATAIN_P(len, buf) != 0) { + return FALSE; + } + + if (S_STSIN(&sts) != 0) { + return FALSE; + } + + if (S_MSGIN(&msg) != 0) { + return FALSE; + } + + return TRUE; +} + +//--------------------------------------------------------------------------- +// +// Write option data +// +//--------------------------------------------------------------------------- +BOOL SCSI_WriteOpt(BYTE *buf, int len) +{ + BYTE cmdbuf[10]; + BYTE sts; + BYTE msg; + + cmdbuf[0] = 0x2a; + cmdbuf[1] = 0; + cmdbuf[2] = 2; + cmdbuf[3] = command; + cmdbuf[4] = 0; + cmdbuf[5] = 0; + cmdbuf[6] = (BYTE)(len >> 16); + cmdbuf[7] = (BYTE)(len >> 8); + cmdbuf[8] = (BYTE)len; + cmdbuf[9] = 1; + + // Retry when select times out + if (S_SELECT(scsiid) != 0) { + S_RESET(); + if (S_SELECT(scsiid) != 0) { + return FALSE; + } + } + + if (S_CMDOUT(10, cmdbuf) != 0) { + return FALSE; + } + + if (S_DATAOUT_P(len, buf) != 0) { + return FALSE; + } + + if (S_STSIN(&sts) != 0) { + return FALSE; + } + + if (S_MSGIN(&msg) != 0) { + return FALSE; + } + + return TRUE; +} + +//=========================================================================== +// +/// File System +// +//=========================================================================== + +//--------------------------------------------------------------------------- +// +// $40 - Device startup +// +//--------------------------------------------------------------------------- +DWORD FS_InitDevice(const argument_t* pArgument) +{ + return (DWORD)SCSI_SendCmd((BYTE*)pArgument, sizeof(argument_t)); +} + +//--------------------------------------------------------------------------- +// +// $41 - Directory check +// +//--------------------------------------------------------------------------- +int FS_CheckDir(const namests_t* pNamests) +{ + BYTE buf[256]; + DWORD *dp; + int i; + + i = 0; + dp = (DWORD*)buf; + *dp = unit; + i += sizeof(DWORD); + + memcpy(&buf[i], pNamests, sizeof(namests_t)); + i += sizeof(namests_t); + + return SCSI_SendCmd(buf, i); +} + +//--------------------------------------------------------------------------- +// +// $42 - Create directory +// +//--------------------------------------------------------------------------- +int FS_MakeDir(const namests_t* pNamests) +{ + BYTE buf[256]; + DWORD *dp; + int i; + + i = 0; + dp = (DWORD*)buf; + *dp = unit; + i += sizeof(DWORD); + + memcpy(&buf[i], pNamests, sizeof(namests_t)); + i += sizeof(namests_t); + + return SCSI_SendCmd(buf, i); +} + +//--------------------------------------------------------------------------- +// +// $43 - Delete directory +// +//--------------------------------------------------------------------------- +int FS_RemoveDir(const namests_t* pNamests) +{ + BYTE buf[256]; + DWORD *dp; + int i; + + i = 0; + dp = (DWORD*)buf; + *dp = unit; + i += sizeof(DWORD); + + memcpy(&buf[i], pNamests, sizeof(namests_t)); + i += sizeof(namests_t); + + return SCSI_SendCmd(buf, i); +} + +//--------------------------------------------------------------------------- +// +// $44 - Change file name +// +//--------------------------------------------------------------------------- +int FS_Rename(const namests_t* pNamests, const namests_t* pNamestsNew) +{ + BYTE buf[256]; + DWORD *dp; + int i; + + i = 0; + dp = (DWORD*)buf; + *dp = unit; + i += sizeof(DWORD); + + memcpy(&buf[i], pNamests, sizeof(namests_t)); + i += sizeof(namests_t); + + memcpy(&buf[i], pNamestsNew, sizeof(namests_t)); + i += sizeof(namests_t); + + return SCSI_SendCmd(buf, i); +} + +//--------------------------------------------------------------------------- +// +// $45 - Delete file +// +//--------------------------------------------------------------------------- +int FS_Delete(const namests_t* pNamests) +{ + BYTE buf[256]; + DWORD *dp; + int i; + + i = 0; + dp = (DWORD*)buf; + *dp = unit; + i += sizeof(DWORD); + + memcpy(&buf[i], pNamests, sizeof(namests_t)); + i += sizeof(namests_t); + + return SCSI_SendCmd(buf, i); +} + +//--------------------------------------------------------------------------- +// +// $46 - Get/set file attribute +// +//--------------------------------------------------------------------------- +int FS_Attribute(const namests_t* pNamests, DWORD nHumanAttribute) +{ + BYTE buf[256]; + DWORD *dp; + int i; + + i = 0; + dp = (DWORD*)buf; + *dp = unit; + i += sizeof(DWORD); + + memcpy(&buf[i], pNamests, sizeof(namests_t)); + i += sizeof(namests_t); + + dp = (DWORD*)&buf[i]; + *dp = nHumanAttribute; + i += sizeof(DWORD); + + return SCSI_SendCmd(buf, i); +} + +//--------------------------------------------------------------------------- +// +// $47 - File search +// +//--------------------------------------------------------------------------- +int FS_Files(DWORD nKey, + const namests_t* pNamests, files_t* info) +{ + BYTE buf[256]; + DWORD *dp; + int i; + + i = 0; + dp = (DWORD*)buf; + *dp = unit; + i += sizeof(DWORD); + + dp = (DWORD*)&buf[i]; + *dp = nKey; + i += sizeof(DWORD); + + memcpy(&buf[i], pNamests, sizeof(namests_t)); + i += sizeof(namests_t); + + memcpy(&buf[i], info, sizeof(files_t)); + i += sizeof(files_t); + + return SCSI_CalCmd(buf, i, (BYTE*)info, sizeof(files_t)); +} + +//--------------------------------------------------------------------------- +// +// $48 - Search next file +// +//--------------------------------------------------------------------------- +int FS_NFiles(DWORD nKey, files_t* info) +{ + BYTE buf[256]; + DWORD *dp; + int i; + + i = 0; + dp = (DWORD*)buf; + *dp = unit; + i += sizeof(DWORD); + + dp = (DWORD*)&buf[i]; + *dp = nKey; + i += sizeof(DWORD); + + memcpy(&buf[i], info, sizeof(files_t)); + i += sizeof(files_t); + + return SCSI_CalCmd(buf, i, (BYTE*)info, sizeof(files_t)); +} + +//--------------------------------------------------------------------------- +// +// $49 - Create new file +// +//--------------------------------------------------------------------------- +int FS_Create(DWORD nKey, + const namests_t* pNamests, fcb_t* pFcb, DWORD nAttribute, BOOL bForce) +{ + BYTE buf[256]; + DWORD *dp; + BOOL *bp; + int i; + + i = 0; + dp = (DWORD*)buf; + *dp = unit; + i += sizeof(DWORD); + + dp = (DWORD*)&buf[i]; + *dp = nKey; + i += sizeof(DWORD); + + memcpy(&buf[i], pNamests, sizeof(namests_t)); + i += sizeof(namests_t); + + memcpy(&buf[i], pFcb, sizeof(fcb_t)); + i += sizeof(fcb_t); + + dp = (DWORD*)&buf[i]; + *dp = nAttribute; + i += sizeof(DWORD); + + bp = (BOOL*)&buf[i]; + *bp = bForce; + i += sizeof(BOOL); + + return SCSI_CalCmd(buf, i, (BYTE*)pFcb, sizeof(fcb_t)); +} + +//--------------------------------------------------------------------------- +// +// $4A - File open +// +//--------------------------------------------------------------------------- +int FS_Open(DWORD nKey, + const namests_t* pNamests, fcb_t* pFcb) +{ + BYTE buf[256]; + DWORD *dp; + int i; + + i = 0; + dp = (DWORD*)buf; + *dp = unit; + i += sizeof(DWORD); + + dp = (DWORD*)&buf[i]; + *dp = nKey; + i += sizeof(DWORD); + + memcpy(&buf[i], pNamests, sizeof(namests_t)); + i += sizeof(namests_t); + + memcpy(&buf[i], pFcb, sizeof(fcb_t)); + i += sizeof(fcb_t); + + return SCSI_CalCmd(buf, i, (BYTE*)pFcb, sizeof(fcb_t)); +} + +//--------------------------------------------------------------------------- +// +// $4B - File close +// +//--------------------------------------------------------------------------- +int FS_Close(DWORD nKey, fcb_t* pFcb) +{ + BYTE buf[256]; + DWORD *dp; + int i; + + i = 0; + dp = (DWORD*)buf; + *dp = unit; + i += sizeof(DWORD); + + dp = (DWORD*)&buf[i]; + *dp = nKey; + i += sizeof(DWORD); + + memcpy(&buf[i], pFcb, sizeof(fcb_t)); + i += sizeof(fcb_t); + + return SCSI_CalCmd(buf, i, (BYTE*)pFcb, sizeof(fcb_t)); +} + +//--------------------------------------------------------------------------- +// +// $4C - Read file +// +//--------------------------------------------------------------------------- +int FS_Read(DWORD nKey, fcb_t* pFcb, BYTE* pAddress, DWORD nSize) +{ + BYTE buf[256]; + DWORD *dp; + int i; + int nResult; + + i = 0; + dp = (DWORD*)buf; + *dp = nKey; + i += sizeof(DWORD); + + memcpy(&buf[i], pFcb, sizeof(fcb_t)); + i += sizeof(fcb_t); + + dp = (DWORD*)&buf[i]; + *dp = nSize; + i += sizeof(DWORD); + + nResult = SCSI_CalCmd(buf, i, (BYTE*)pFcb, sizeof(fcb_t)); + + if (nResult > 0) { + SCSI_ReadOpt(pAddress, nResult); + } + + return nResult; +} + +//--------------------------------------------------------------------------- +// +// $4D - Write file +// +//--------------------------------------------------------------------------- +int FS_Write(DWORD nKey, fcb_t* pFcb, BYTE* pAddress, DWORD nSize) +{ + BYTE buf[256]; + DWORD *dp; + int i; + + i = 0; + dp = (DWORD*)buf; + *dp = nKey; + i += sizeof(DWORD); + + memcpy(&buf[i], pFcb, sizeof(fcb_t)); + i += sizeof(fcb_t); + + dp = (DWORD*)&buf[i]; + *dp = nSize; + i += sizeof(DWORD); + + if (nSize != 0) { + if (!SCSI_WriteOpt(pAddress, nSize)) { + return FS_FATAL_MEDIAOFFLINE; + } + } + + return SCSI_SendCmd(buf, i); +} + +//--------------------------------------------------------------------------- +// +// $4E - File seek +// +//--------------------------------------------------------------------------- +int FS_Seek(DWORD nKey, fcb_t* pFcb, DWORD nMode, int nOffset) +{ + BYTE buf[256]; + DWORD *dp; + int i; + + i = 0; + dp = (DWORD*)buf; + *dp = nKey; + i += sizeof(DWORD); + + memcpy(&buf[i], pFcb, sizeof(fcb_t)); + i += sizeof(fcb_t); + + dp = (DWORD*)&buf[i]; + *dp = nMode; + i += sizeof(DWORD); + + dp = (DWORD*)&buf[i]; + *dp = nOffset; + i += sizeof(int); + + return SCSI_CalCmd(buf, i, (BYTE*)pFcb, sizeof(fcb_t)); +} + +//--------------------------------------------------------------------------- +// +// $4F - Get/set file time stamp +// +//--------------------------------------------------------------------------- +DWORD FS_TimeStamp(DWORD nKey, + fcb_t* pFcb, DWORD nHumanTime) +{ + BYTE buf[256]; + DWORD *dp; + int i; + + i = 0; + dp = (DWORD*)buf; + *dp = unit; + i += sizeof(DWORD); + + dp = (DWORD*)&buf[i]; + *dp = nKey; + i += sizeof(DWORD); + + memcpy(&buf[i], pFcb, sizeof(fcb_t)); + i += sizeof(fcb_t); + + dp = (DWORD*)&buf[i]; + *dp = nHumanTime; + i += sizeof(DWORD); + + return (DWORD)SCSI_CalCmd(buf, i, (BYTE*)pFcb, sizeof(fcb_t)); +} + +//--------------------------------------------------------------------------- +// +// $50 - Get capacity +// +//--------------------------------------------------------------------------- +int FS_GetCapacity(capacity_t* cap) +{ + BYTE buf[256]; + DWORD *dp; + int i; + + i = 0; + dp = (DWORD*)buf; + *dp = unit; + i += sizeof(DWORD); + + return SCSI_CalCmd(buf, i, (BYTE*)cap, sizeof(capacity_t)); +} + +//--------------------------------------------------------------------------- +// +// $51 - Inspect/control drive status +// +//--------------------------------------------------------------------------- +int FS_CtrlDrive(ctrldrive_t* pCtrlDrive) +{ +#if 1 + // Do tentative processing here due to high load + switch (pCtrlDrive->status) { + case 0: // Inspect status + case 9: // Inspect status 2 + pCtrlDrive->status = 0x42; + return pCtrlDrive->status; + case 1: // Eject + case 2: // Eject forbidden 1 (not implemented) + case 3: // Eject allowed 1 (not implemented) + case 4: // Flash LED when media is not inserted (not implemented) + case 5: // Turn off LED when media is not inserted (not implemented) + case 6: // Eject forbidden 2 (not implemented) + case 7: // Eject allowed 2 (not implemented) + return 0; + + case 8: // Eject inspection + return 1; + } + + return -1; +#else + BYTE buf[256]; + DWORD *dp; + int i; + + i = 0; + dp = (DWORD*)buf; + *dp = unit; + i += sizeof(DWORD); + + memcpy(&buf[i], pCtrlDrive, sizeof(ctrldrive_t)); + i += sizeof(ctrldrive_t); + + return SCSI_CalCmd(buf, i, (BYTE*)pCtrlDrive, sizeof(ctrldrive_t)); +#endif +} + +//--------------------------------------------------------------------------- +// +// $52 - Get DPB +// +//--------------------------------------------------------------------------- +int FS_GetDPB(dpb_t* pDpb) +{ + BYTE buf[256]; + DWORD *dp; + int i; + + i = 0; + dp = (DWORD*)buf; + *dp = unit; + i += sizeof(DWORD); + + return SCSI_CalCmd(buf, i, (BYTE*)pDpb, sizeof(dpb_t)); +} + +//--------------------------------------------------------------------------- +// +// $53 - Read sector +// +//--------------------------------------------------------------------------- +int FS_DiskRead(BYTE* pBuffer, DWORD nSector, DWORD nSize) +{ + BYTE buf[256]; + DWORD *dp; + int i; + + i = 0; + dp = (DWORD*)buf; + *dp = unit; + i += sizeof(DWORD); + + dp = (DWORD*)&buf[i]; + *dp = nSector; + i += sizeof(DWORD); + + dp = (DWORD*)&buf[i]; + *dp = nSize; + i += sizeof(DWORD); + + return SCSI_CalCmd(buf, i, (BYTE*)pBuffer, 0x200); +} + +//--------------------------------------------------------------------------- +// +// $54 - Write sector +// +//--------------------------------------------------------------------------- +int FS_DiskWrite() +{ + BYTE buf[256]; + DWORD *dp; + + dp = (DWORD*)buf; + *dp = unit; + + return SCSI_SendCmd(buf, 4); +} + +//--------------------------------------------------------------------------- +// +// $55 - IOCTRL +// +//--------------------------------------------------------------------------- +int FS_Ioctrl(DWORD nFunction, ioctrl_t* pIoctrl) +{ + BYTE buf[256]; + DWORD *dp; + int i; + + i = 0; + dp = (DWORD*)buf; + *dp = unit; + i += sizeof(DWORD); + + dp = (DWORD*)&buf[i]; + *dp = nFunction; + i += sizeof(DWORD); + + memcpy(&buf[i], pIoctrl, sizeof(ioctrl_t)); + i += sizeof(ioctrl_t); + + return SCSI_CalCmd(buf, i, (BYTE*)pIoctrl, sizeof(ioctrl_t)); +} + +//--------------------------------------------------------------------------- +// +// $56 - Flush +// +//--------------------------------------------------------------------------- +int FS_Flush() +{ +#if 1 + // Not supported + return 0; +#else + BYTE buf[256]; + DWORD *dp; + + dp = (DWORD*)buf; + *dp = unit; + + return SCSI_SendCmd(buf, 4); +#endif +} + +//--------------------------------------------------------------------------- +// +// $57 - Media change check +// +//--------------------------------------------------------------------------- +int FS_CheckMedia() +{ +#if 1 + // Do tentative processing due to high load + return 0; +#else + BYTE buf[256]; + DWORD *dp; + + dp = (DWORD*)buf; + *dp = unit; + + return SCSI_SendCmd(buf, 4); +#endif +} + +//--------------------------------------------------------------------------- +// +// $58 - Lock +// +//--------------------------------------------------------------------------- +int FS_Lock() +{ +#if 1 + // Not supported + return 0; +#else + BYTE buf[256]; + DWORD *dp; + + dp = (DWORD*)buf; + *dp = unit; + + return SCSI_SendCmd(buf, 4); +#endif +} + +//=========================================================================== +// +// Command handlers +// +//=========================================================================== +#define GetReqByte(a) (request[a]) +#define SetReqByte(a,d) (request[a] = (d)) +#define GetReqWord(a) (*((WORD*)&request[a])) +#define SetReqWord(a,d) (*((WORD*)&request[a]) = (d)) +#define GetReqLong(a) (*((DWORD*)&request[a])) +#define SetReqLong(a,d) (*((DWORD*)&request[a]) = (d)) +#define GetReqAddr(a) ((BYTE*)((*((DWORD*)&request[a])) & 0x00ffffff)) + +//--------------------------------------------------------------------------- +// +// Read NAMESTS +// +//--------------------------------------------------------------------------- +void GetNameStsPath(BYTE *addr, namests_t* pNamests) +{ + DWORD i; + + ASSERT(this); + ASSERT(pNamests); + + // Wildcard data + pNamests->wildcard = *addr; + + // Drive number + pNamests->drive = addr[1]; + + // Pathname + for (i = 0; i < sizeof(pNamests->path); i++) { + pNamests->path[i] = addr[2 + i]; + } + + // Filename 1 + memset(pNamests->name, 0x20, sizeof(pNamests->name)); + + // File ending + memset(pNamests->ext, 0x20, sizeof(pNamests->ext)); + + // Filename 2 + memset(pNamests->add, 0, sizeof(pNamests->add)); +} + +//--------------------------------------------------------------------------- +// +// Read NAMESTS +// +//--------------------------------------------------------------------------- +void GetNameSts(BYTE *addr, namests_t* pNamests) +{ + DWORD i; + + ASSERT(this); + ASSERT(pNamests); + ASSERT(addr <= 0xFFFFFF); + + // Wildcard data + pNamests->wildcard = *addr; + + // Drive number + pNamests->drive = addr[1]; + + // Pathname + for (i = 0; i < sizeof(pNamests->path); i++) { + pNamests->path[i] = addr[2 + i]; + } + + // Filename 1 + for (i = 0; i < sizeof(pNamests->name); i++) { + pNamests->name[i] = addr[67 + i]; + } + + // File ending + for (i = 0; i < sizeof(pNamests->ext); i++) { + pNamests->ext[i] = addr[75 + i]; + } + + // Filename 2 + for (i = 0; i < sizeof(pNamests->add); i++) { + pNamests->add[i] = addr[78 + i]; + } +} + +//--------------------------------------------------------------------------- +// +// Read FILES +// +//--------------------------------------------------------------------------- +void GetFiles(BYTE *addr, files_t* pFiles) +{ + ASSERT(this); + ASSERT(pFiles); + ASSERT(addr <= 0xFFFFFF); + + // Search data + pFiles->fatr = *addr; + pFiles->sector = *((DWORD*)&addr[2]); + pFiles->offset = *((WORD*)&addr[8]);; + + pFiles->attr = 0; + pFiles->time = 0; + pFiles->date = 0; + pFiles->size = 0; + memset(pFiles->full, 0, sizeof(pFiles->full)); +} + +//--------------------------------------------------------------------------- +// +// Write FILES +// +//--------------------------------------------------------------------------- +void SetFiles(BYTE *addr, const files_t* pFiles) +{ + DWORD i; + + ASSERT(this); + ASSERT(pFiles); + + *((DWORD*)&addr[2]) = pFiles->sector; + *((WORD*)&addr[8]) = pFiles->offset; + + // File data + addr[21] = pFiles->attr; + *((WORD*)&addr[22]) = pFiles->time; + *((WORD*)&addr[24]) = pFiles->date; + *((DWORD*)&addr[26]) = pFiles->size; + + // Full filename + addr += 30; + for (i = 0; i < sizeof(pFiles->full); i++) { + *addr = pFiles->full[i]; + addr++; + } +} + +//--------------------------------------------------------------------------- +// +// Read FCB +// +//--------------------------------------------------------------------------- +void GetFcb(BYTE *addr, fcb_t* pFcb) +{ + ASSERT(this); + ASSERT(pFcb); + + // FCB data + pFcb->fileptr = *((DWORD*)&addr[6]); + pFcb->mode = *((WORD*)&addr[14]); + + // Attribute + pFcb->attr = addr[47]; + + // FCB data + pFcb->time = *((WORD*)&addr[58]); + pFcb->date = *((WORD*)&addr[60]); + pFcb->size = *((DWORD*)&addr[64]); +} + +//--------------------------------------------------------------------------- +// +// Write FCB +// +//--------------------------------------------------------------------------- +void SetFcb(BYTE *addr, const fcb_t* pFcb) +{ + ASSERT(this); + ASSERT(pFcb); + + // FCB data + *((DWORD*)&addr[6]) = pFcb->fileptr; + *((WORD*)&addr[14]) = pFcb->mode; + + // Attribute + addr[47] = pFcb->attr; + + // FCB data + *((WORD*)&addr[58]) = pFcb->time; + *((WORD*)&addr[60]) = pFcb->date; + *((DWORD*)&addr[64]) = pFcb->size; +} + +//--------------------------------------------------------------------------- +// +// Write CAPACITY +// +//--------------------------------------------------------------------------- +void SetCapacity(BYTE *addr, const capacity_t* pCapacity) +{ + ASSERT(this); + ASSERT(pCapacity); + + *((WORD*)&addr[0]) = pCapacity->freearea; + *((WORD*)&addr[2]) = pCapacity->clusters; + *((WORD*)&addr[4]) = pCapacity->sectors; + *((WORD*)&addr[6]) = pCapacity->bytes; +} + +//--------------------------------------------------------------------------- +// +// Write DPB +// +//--------------------------------------------------------------------------- +void SetDpb(BYTE *addr, const dpb_t* pDpb) +{ + ASSERT(this); + ASSERT(pDpb); + + // DPB data + *((WORD*)&addr[0]) = pDpb->sector_size; + addr[2] = pDpb->cluster_size; + addr[3] = pDpb->shift; + *((WORD*)&addr[4]) = pDpb->fat_sector; + addr[6] = pDpb->fat_max; + addr[7] = pDpb->fat_size; + *((WORD*)&addr[8]) = pDpb->file_max; + *((WORD*)&addr[10]) = pDpb->data_sector; + *((WORD*)&addr[12]) = pDpb->cluster_max; + *((WORD*)&addr[14]) = pDpb->root_sector; + addr[20] = pDpb->media; +} + +//--------------------------------------------------------------------------- +// +// Read IOCTRL +// +//--------------------------------------------------------------------------- +void GetIoctrl(DWORD param, DWORD func, ioctrl_t* pIoctrl) +{ + DWORD *lp; + + ASSERT(this); + ASSERT(pIoctrl); + + switch (func) { + case 2: + // Re-identify media + pIoctrl->param = param; + return; + + case -2: + // Configure options + lp = (DWORD*)param; + pIoctrl->param = *lp; + return; + } +} + +//--------------------------------------------------------------------------- +// +// Write IOCTRL +// +//--------------------------------------------------------------------------- +void SetIoctrl(DWORD param, DWORD func, ioctrl_t* pIoctrl) +{ + DWORD i; + BYTE *bp; + WORD *wp; + DWORD *lp; + + ASSERT(this); + ASSERT(pIoctrl); + + switch (func) { + case 0: + // Acquire media ID + wp = (WORD*)param; + *wp = pIoctrl->media; + return; + + case 1: + // Dummy for Human68k compatibility + lp = (DWORD*)param; + *lp = pIoctrl->param; + return; + + case -1: + // Resident evaluation + bp = (BYTE*)param; + for (i = 0; i < 8; i++) { + *bp = pIoctrl->buffer[i]; + bp++; + } + return; + + case -3: + // Acquire options + lp = (DWORD*)param; + *lp = pIoctrl->param; + return; + } +} + +//--------------------------------------------------------------------------- +// +// Read ARGUMENT +// +// When this exceeds the buffer size, interrupt transfer and exit. +// +//--------------------------------------------------------------------------- +void GetArgument(BYTE *addr, argument_t* pArgument) +{ + BOOL bMode; + BYTE *p; + DWORD i; + BYTE c; + + ASSERT(this); + ASSERT(pArgument); + + bMode = FALSE; + p = pArgument->buf; + for (i = 0; i < sizeof(pArgument->buf) - 2; i++) { + c = addr[i]; + *p++ = c; + if (bMode == 0) { + if (c == '\0') + return; + bMode = TRUE; + } else { + if (c == '\0') + bMode = FALSE; + } + } + + *p++ = '\0'; + *p = '\0'; +} + +//--------------------------------------------------------------------------- +// +// Write return value +// +//--------------------------------------------------------------------------- +void SetResult(DWORD nResult) +{ + DWORD code; + + ASSERT(this); + + // Handle fatal errors + switch (nResult) { + case FS_FATAL_INVALIDUNIT: + code = 0x5001; + goto fatal; + case FS_FATAL_INVALIDCOMMAND: + code = 0x5003; + goto fatal; + case FS_FATAL_WRITEPROTECT: + code = 0x700D; + goto fatal; + case FS_FATAL_MEDIAOFFLINE: + code = 0x7002; + fatal: + SetReqByte(3, (BYTE)code); + SetReqByte(4, code >> 8); + // @note When returning retryability, never overwrite with (a5 + 18). + // If you do that, the system will start behaving erratically if you hit Retry at the system error screen. + if (code & 0x2000) + break; + nResult = FS_INVALIDFUNC; + default: + SetReqLong(18, nResult); + break; + } +} + +//--------------------------------------------------------------------------- +// +// $40 - Device startup +// +// in (offset size) +// 0 1.b constant (22) +// 2 1.b command ($40/$c0) +// 18 1.l parameter address +// 22 1.b drive number +// out (offset size) +// 3 1.b error code (lower) +// 4 1.b '' (upper) +// 13 1.b number of units +// 14 1.l device driver exit address + 1 +// +// This is called command 0 when the driver is loaded similarly to a local drive, +// but there is no need to prepare BPB or its pointer array. +// Unlike other commands, only this command does not include a valid a5 + 1, +// since it doesn't expect 0 initialization etc., so be careful. +// +//--------------------------------------------------------------------------- +DWORD InitDevice(void) +{ + argument_t arg; + DWORD units; + + ASSERT(this); + ASSERT(fs); + + // Get option contents + GetArgument(GetReqAddr(18), &arg); + + // Contstruct the filesystem to the extent Human68k can support number of drivers etc. + units = FS_InitDevice(&arg); + + // Return number of drives + SetReqByte(13, units); + + return 0; +} + +//--------------------------------------------------------------------------- +// +// $41 - Directory check +// +// in (offset size) +// 14 1.L NAMESTS struct address +// +//--------------------------------------------------------------------------- +DWORD CheckDir(void) +{ + namests_t ns; + int nResult; + + ASSERT(this); + ASSERT(fs); + + // Get filename to search + GetNameStsPath(GetReqAddr(14), &ns); + + // Call filesystem + nResult = FS_CheckDir(&ns); + + return nResult; +} + +//--------------------------------------------------------------------------- +// +// $42 - Create directory +// +// in (offset size) +// 14 1.L NAMESTS struct address +// +//--------------------------------------------------------------------------- +DWORD MakeDir(void) +{ + namests_t ns; + int nResult; + + ASSERT(this); + ASSERT(fs); + + // Get filename to search + GetNameSts(GetReqAddr(14), &ns); + + // Call filesystem + nResult = FS_MakeDir(&ns); + + return nResult; +} + +//--------------------------------------------------------------------------- +// +// $43 - Delete directory +// +// in (offset size) +// 14 1.L NAMESTS struct address +// +//--------------------------------------------------------------------------- +DWORD RemoveDir(void) +{ + namests_t ns; + int nResult; + + ASSERT(this); + ASSERT(fs); + + // Get filename to search + GetNameSts(GetReqAddr(14), &ns); + + // Call filesystem + nResult = FS_RemoveDir(&ns); + + return nResult; +} + +//--------------------------------------------------------------------------- +// +// $44 - Change filename +// +// in (offset size) +// 14 1.L NAMESTS struct address old filename +// 18 1.L NAMESTS struct address new filename +// +//--------------------------------------------------------------------------- +DWORD Rename(void) +{ + namests_t ns; + namests_t ns_new; + int nResult; + + ASSERT(this); + ASSERT(fs); + + // Get filename to search + GetNameSts(GetReqAddr(14), &ns); + GetNameSts(GetReqAddr(18), &ns_new); + + // Call filesystem + nResult = FS_Rename(&ns, &ns_new); + + return nResult; +} + +//--------------------------------------------------------------------------- +// +// $45 - Delete file +// +// in (offset size) +// 14 1.L NAMESTS struct address +// +//--------------------------------------------------------------------------- +DWORD Delete(void) +{ + namests_t ns; + int nResult; + + ASSERT(this); + ASSERT(fs); + + // Get filename to search + GetNameSts(GetReqAddr(14), &ns); + + // Call filesystem + nResult = FS_Delete(&ns); + + return nResult; +} + +//--------------------------------------------------------------------------- +// +// $46 - Get/set file attribute +// +// in (offset size) +// 12 1.B Note that this becomes 0x01 when read from memory +// 13 1.B attribute; read from memory when $FF +// 14 1.L NAMESTS struct address +// +//--------------------------------------------------------------------------- +DWORD Attribute(void) +{ + namests_t ns; + DWORD attr; + int nResult; + + ASSERT(this); + ASSERT(fs); + + // Get filename to search + GetNameSts(GetReqAddr(14), &ns); + + // Target attribute + attr = GetReqByte(13); + + // Call filesystem + nResult = FS_Attribute(&ns, attr); + + return nResult; +} + +//--------------------------------------------------------------------------- +// +// $47 - File search +// +// in (offset size) +// 0 1.b constant (26) +// 1 1.b unit number +// 2 1.b command ($47/$c7) +// 13 1.b search attribute (Unused in WindrvXM. Written directly to search buffer.) +// 14 1.l Filename buffer (namests format) +// 18 1.l Search buffer (files format) Search data in progress and search results are written to this buffer. +// out (offset size) +// 3 1.b error code (lower) +// 4 1.b '' (upper) +// 18 1.l result status +// +// Search files from specified directory. Called from DOS _FILES. +// When a search fails, or succeeds but not using wildcards, +// we write -1 to the search buffer offset to make the next search fail. +// When a file is found, we set its data as well as the following data +// for the next search: sector number, offset, and in the case of the +// root directory, also the remaining sectors. The search drive, attribute, +// and path name are set in the DOS call processing, so writing is not needed. +// +// +// (offset size) +// 0 1.b NAMWLD 0: no wildcard -1:no specified file +// (number of chars of wildcard) +// 1 1.b NAMDRV drive number (A=0,B=1,...,Z=25) +// 2 65.b NAMPTH path ('\' + optional subdirectory + '\') +// 67 8.b NAMNM1 filename (first 8 chars) +// 75 3.b NAMEXT file ending +// 78 10.b NAMNM2 filename (remaining 10 chars) +// +// Note that 0x09 (TAB) is used for path separator, rather than 0x2F (/) or 0x5C (\) +// +//--------------------------------------------------------------------------- +DWORD Files(void) +{ + BYTE *files; + files_t info; + namests_t ns; + int nResult; + + ASSERT(this); + ASSERT(fs); + + // Memory for storing search in progress + files = GetReqAddr(18); + GetFiles(files, &info); + + // Get filename to search + GetNameSts(GetReqAddr(14), &ns); + + // Call filesystem + nResult = FS_Files((DWORD)files, &ns, &info); + + // Apply search results + if (nResult >= 0) { + SetFiles(files, &info); + } + + return nResult; +} + +//--------------------------------------------------------------------------- +// +// $48 - Search next file +// +// in (offset size) +// 18 1.L FILES struct address +// +//--------------------------------------------------------------------------- +DWORD NFiles(void) +{ + BYTE *files; + files_t info; + int nResult; + + ASSERT(this); + ASSERT(fs); + + // Read work memory + files = GetReqAddr(18); + GetFiles(files, &info); + + // Call filesystem + nResult = FS_NFiles((DWORD)files, &info); + + // Apply search results + if (nResult >= 0) { + SetFiles(files, &info); + } + + return nResult; +} + +//--------------------------------------------------------------------------- +// +// $49 - Create file (Create) +// +// in (offset size) +// 1 1.B unit number +// 13 1.B attribute +// 14 1.L NAMESTS struct address +// 18 1.L mode (0:_NEWFILE 1:_CREATE) +// 22 1.L FCB struct address +// +//--------------------------------------------------------------------------- +DWORD Create(void) +{ + namests_t ns; + BYTE *pFcb; + fcb_t fcb; + DWORD attr; + BOOL force; + int nResult; + + ASSERT(this); + ASSERT(fs); + + // Get target filename + GetNameSts(GetReqAddr(14), &ns); + + // Get FCB + pFcb = GetReqAddr(22); + GetFcb(pFcb, &fcb); + + // Attribute + attr = GetReqByte(13); + + // Forced overwrite mode + force = (BOOL)GetReqLong(18); + + // Call filesystem + nResult = FS_Create((DWORD)pFcb, &ns, &fcb, attr, force); + + // Apply results + if (nResult >= 0) { + SetFcb(pFcb, &fcb); + } + + return nResult; +} + +//--------------------------------------------------------------------------- +// +// $4A - File open +// +// in (offset size) +// 1 1.B unit number +// 14 1.L NAMESTS struct address +// 22 1.L FCB struct address +// Most parameters are already set in FCB. +// Overwrite time and date at the moment of opening. +// Overwrite if size is 0. +// +//--------------------------------------------------------------------------- +DWORD Open(void) +{ + namests_t ns; + BYTE *pFcb; + fcb_t fcb; + int nResult; + + ASSERT(this); + ASSERT(fs); + + // Get target filename + GetNameSts(GetReqAddr(14), &ns); + + // Get FCB + pFcb = GetReqAddr(22); + GetFcb(pFcb, &fcb); + + // Call filesystem + nResult = FS_Open((DWORD)pFcb, &ns, &fcb); + + // Apply results + if (nResult >= 0) { + SetFcb(pFcb, &fcb); + } + + return nResult; +} + +//--------------------------------------------------------------------------- +// +// $4B - File close +// +// in (offset size) +// 22 1.L FCB struct address +// +//--------------------------------------------------------------------------- +DWORD Close(void) +{ + BYTE *pFcb; + fcb_t fcb; + int nResult; + + ASSERT(this); + ASSERT(fs); + + // Get FCB + pFcb = GetReqAddr(22); + GetFcb(pFcb, &fcb); + + // Call filesystem + nResult = FS_Close((DWORD)pFcb, &fcb); + + // Apply results + if (nResult >= 0) { + SetFcb(pFcb, &fcb); + } + + return nResult; +} + +//--------------------------------------------------------------------------- +// +// $4C - Read file +// +// in (offset size) +// 14 1.L Reading buffer; Read the file contents here. +// 18 1.L Size; It may be set to values exceeding 16MB +// 22 1.L FCB struct address +// +// No guarantee for behavior when passing address where bus error occurs. +// +// In the 20th century, a small number of apps used a trick where reading a +// negative value meant "read all the files!" Wh, what? This file is over 16MB! +// +// Back then, anything over 4~12MB was treated as "inifity" and often truncated +// out of helpfulness, which is not needed in this day and age. +// On the other hand, it might be a good idea to truncate at 12MB or 16MB. +// +//--------------------------------------------------------------------------- +DWORD Read(void) +{ + BYTE *pFcb; + fcb_t fcb; + BYTE *pAddress; + DWORD nSize; + int nResult; + + ASSERT(this); + ASSERT(fs); + + // Get FCB + pFcb = GetReqAddr(22); + GetFcb(pFcb, &fcb); + + // Read buffer + pAddress = GetReqAddr(14); + + // Read size + nSize = GetReqLong(18); + + // Clipping + if (nSize >= WINDRV_CLIPSIZE_MAX) { + nSize = WINDRV_CLIPSIZE_MAX; + } + + // Call filesystem + nResult = FS_Read((DWORD)pFcb, &fcb, pAddress, nSize); + + // Apply results + if (nResult >= 0) { + SetFcb(pFcb, &fcb); + } + + return nResult; +} + +//--------------------------------------------------------------------------- +// +// $4D - Write file +// +// in (offset size) +// 14 1.L Write buffer; Write file contents here +// 18 1.L Size; If a negative number, treated the same as specifying file size +// 22 1.L FCB struct address +// +// No guarantee for behavior when passing address where bus error occurs. +// +//--------------------------------------------------------------------------- +DWORD Write(void) +{ + BYTE *pFcb; + fcb_t fcb; + BYTE *pAddress; + DWORD nSize; + int nResult; + + ASSERT(this); + ASSERT(fs); + + // Get FCB + pFcb = GetReqAddr(22); + GetFcb(pFcb, &fcb); + + // Write buffer + pAddress = GetReqAddr(14); + + // Write size + nSize = GetReqLong(18); + + // Clipping + if (nSize >= WINDRV_CLIPSIZE_MAX) { + nSize = WINDRV_CLIPSIZE_MAX; + } + + // Call filesystem + nResult = FS_Write((DWORD)pFcb, &fcb, pAddress, nSize); + + // Apply results + if (nResult >= 0) { + SetFcb(pFcb, &fcb); + } + + return nResult; +} + +//--------------------------------------------------------------------------- +// +// $4E - File seek +// +// in (offset size) +// 12 1.B This becomes 0x2B sometimes, and 0 other times +// 13 1.B mode +// 18 1.L offset +// 22 1.L FCB struct address +// +//--------------------------------------------------------------------------- +DWORD Seek(void) +{ + BYTE *pFcb; + fcb_t fcb; + DWORD nMode; + DWORD nOffset; + int nResult; + + ASSERT(this); + ASSERT(fs); + + // Get FCB + pFcb = GetReqAddr(22); + GetFcb(pFcb, &fcb); + + // Seek mode + nMode = GetReqByte(13); + + // Seek offset + nOffset = GetReqLong(18); + + // Call filesystem + nResult = FS_Seek((DWORD)pFcb, &fcb, nMode, nOffset); + + // Apply results + if (nResult >= 0) { + SetFcb(pFcb, &fcb); + } + + return nResult; +} + +//--------------------------------------------------------------------------- +// +// $4F - Get/set file timestamp +// +// in (offset size) +// 18 1.W DATE +// 20 1.W TIME +// 22 1.L FCB struct address +// +// Possible to change settings when FCB is opened in read mode too. +// Note: Cannot make the judgement of read-only with only FCB +// +//--------------------------------------------------------------------------- +DWORD TimeStamp(void) +{ + BYTE *pFcb; + fcb_t fcb; + DWORD nTime; + DWORD nResult; + + ASSERT(this); + ASSERT(fs); + + // Get FCB + pFcb = GetReqAddr(22); + GetFcb(pFcb, &fcb); + + // Get timestamp + nTime = GetReqLong(18); + + // Call filesystem + nResult = FS_TimeStamp((DWORD)pFcb, &fcb, nTime); + + // Apply results + if (nResult < 0xFFFF0000) { + SetFcb(pFcb, &fcb); + } + + return nResult; +} + +//--------------------------------------------------------------------------- +// +// $50 - Get capacity +// +// in (offset size) +// 0 1.b constant (26) +// 1 1.b unit number +// 2 1.b command ($50/$d0) +// 14 1.l buffer address +// out (offset size) +// 3 1.b error code (lower) +// 4 1.b '' (upper) +// 18 1.l result status +// +// Get the total and available media capacity, as well as cluster / sector size. +// The contents to write to buffer follows. Returns number of bytes that can be +// used for result status. +// +// (offset size) +// 0 1.w available number of clusters +// 2 1.w total number of clusters +// 4 1.w number of sectors per 1 cluster +// 6 1.w number of bytes per 1 sector +// +//--------------------------------------------------------------------------- +DWORD GetCapacity(void) +{ + BYTE *pCapacity; + capacity_t cap; + int nResult; + + ASSERT(this); + ASSERT(fs); + + // Get buffer + pCapacity = GetReqAddr(14); + +#if 0 + // Call filesystem + nResult = FS_GetCapacity(&cap); +#else + // Try skipping since the contents are always returned + cap.freearea = 0xFFFF; + cap.clusters = 0xFFFF; + cap.sectors = 64; + cap.bytes = 512; + nResult = 0x7FFF8000; +#endif + + // Apply results + if (nResult >= 0) { + SetCapacity(pCapacity, &cap); + } + + return nResult; +} + +//--------------------------------------------------------------------------- +// +// $51 - Inspect/control drive status +// +// in (offset size) +// 1 1.B unit number +// 13 1.B status 0: status inspection 1: eject +// +//--------------------------------------------------------------------------- +DWORD CtrlDrive(void) +{ + ctrldrive_t ctrl; + int nResult; + + ASSERT(this); + ASSERT(fs); + + // Get drive status + ctrl.status = GetReqByte(13); + + // Call filesystem + nResult = FS_CtrlDrive(&ctrl); + + // Apply results + if (nResult >= 0) { + SetReqByte(13, ctrl.status); + } + + return nResult; +} + +//--------------------------------------------------------------------------- +// +// $52 - Get DPB +// +// in (offset size) +// 0 1.b constant (26) +// 1 1.b unit number +// 2 1.b command ($52/$d2) +// 14 1.l buffer address (points at starting address + 2) +// out (offset size) +// 3 1.b error code (lower) +// 4 1.b '' (upper) +// 18 1.l result status +// +// Specified media data is returned as DPB v1. Data needed to be set +// for this command are as follows (parantheses indicate what's set by DOS calls.) +// Note that the buffer adress is what's pointed at by offset 2. +// +// (offset size) +// 0 1.b (drive number) +// 1 1.b (unit number) +// 2 1.w number of bytes per 1 sector +// 4 1.b number of sectors - 1 per 1 cluster +// 5 1.b Number of cluster -> sector +// bit 7 = 1 in MS-DOS format FAT (16bit Intel array) +// 6 1.w FAT first sector number +// 8 1.b Number of FAT allocations +// 9 1.b Number of FAT controlled sectors (excluding duplicates) +// 10 1.w Number of files in the root directory +// 12 1.w First sector number of data memory +// 14 1.w Total number of clusters + 1 +// 16 1.w First sector number of root directory +// 18 1.l (Driver head address) +// 22 1.b (Physical drive name in lower-case) +// 23 1.b (DPB usage flag: normally 0) +// 24 1.l (Next DPB address) +// 28 1.w (Cluster number of current directory: normally 0) +// 30 64.b (Current directory name) +// +//--------------------------------------------------------------------------- +DWORD GetDPB(void) +{ + BYTE *pDpb; + dpb_t dpb; + int nResult; + + ASSERT(this); + ASSERT(fs); + + // Get DPB + pDpb = GetReqAddr(14); + + // Call filesystem + nResult = FS_GetDPB(&dpb); + + // Apply results + if (nResult >= 0) { + SetDpb(pDpb, &dpb); + } + + return nResult; +} + +//--------------------------------------------------------------------------- +// +// $53 - Read sector +// +// in (offset size) +// 1 1.B unit number +// 14 1.L buffer address +// 18 1.L number of sectors +// 22 1.L sector number +// +//--------------------------------------------------------------------------- +DWORD DiskRead(void) +{ + BYTE *pAddress; + DWORD nSize; + DWORD nSector; + BYTE buffer[0x200]; + int nResult; + int i; + + ASSERT(this); + ASSERT(fs); + + pAddress = GetReqAddr(14); // Address (upper bit is extension flag) + nSize = GetReqLong(18); // Number of sectors + nSector = GetReqLong(22); // Sector number + + // Call filesystem + nResult = FS_DiskRead(buffer, nSector, nSize); + + // Apply results + if (nResult >= 0) { + for (i = 0; i < sizeof(buffer); i++) { + *pAddress = buffer[i]; + pAddress++; + } + } + + return nResult; +} + +//--------------------------------------------------------------------------- +// +// $54 - Write sector +// +// in (offset size) +// 1 1.B unit number +// 14 1.L buffer address +// 18 1.L number of sectors +// 22 1.L sector number +// +//--------------------------------------------------------------------------- +DWORD DiskWrite(void) +{ + BYTE *pAddress; + DWORD nSize; + DWORD nSector; + int nResult; + + ASSERT(this); + ASSERT(fs); + + pAddress = GetReqAddr(14); // Address (upper bit is extension flag) + nSize = GetReqLong(18); // Number of sectors + nSector = GetReqLong(22); // Sector number + + // Call filesystem + nResult = FS_DiskWrite(); + + return nResult; +} + +//--------------------------------------------------------------------------- +// +// $55 - IOCTRL +// +// in (offset size) +// 1 1.B unit number +// 14 1.L parameter +// 18 1.W feature number +// +//--------------------------------------------------------------------------- +DWORD Ioctrl(void) +{ + DWORD param; + DWORD func; + ioctrl_t ioctrl; + int nResult; + + ASSERT(this); + ASSERT(fs); + + // Get IOCTRL + param = GetReqLong(14); // Parameter + func = GetReqWord(18); // Feature number + GetIoctrl(param, func, &ioctrl); + + // Call filesystem + nResult = FS_Ioctrl(func, &ioctrl); + + // Apply results + if (nResult >= 0) + SetIoctrl(param, func, &ioctrl); + + return nResult; +} + +//--------------------------------------------------------------------------- +// +// $56 - Flush +// +// in (offset size) +// 1 1.b unit number +// +//--------------------------------------------------------------------------- +DWORD Flush(void) +{ + int nResult; + + ASSERT(this); + ASSERT(fs); + + // Call filesystem + nResult = FS_Flush(); + + return nResult; +} + +//--------------------------------------------------------------------------- +// +// $57 - Media change check +// +// in (offset size) +// 0 1.b constant (26) +// 1 1.b unit number +// 2 1.b Command ($57/$d7) +// out (offset size) +// 3 1.b error code (lower) +// 4 1.b '' (upper) +// 18 1.l result status +// +// Checks if the media has been changed or not. Format if it has changed. +// The verification is done within this command. +// +//--------------------------------------------------------------------------- +DWORD CheckMedia(void) +{ + int nResult; + + ASSERT(this); + ASSERT(fs); + + // Call filesystem + nResult = FS_CheckMedia(); + + return nResult; +} + +//--------------------------------------------------------------------------- +// +// $58 - Lock +// +// in (offset size) +// 1 1.b unit number +// +//--------------------------------------------------------------------------- +DWORD Lock(void) +{ + int nResult; + + ASSERT(this); + ASSERT(fs); + + // Call filesystem + nResult = FS_Lock(); + + return nResult; +} + +//--------------------------------------------------------------------------- +// +// Execute command +// +//--------------------------------------------------------------------------- +DWORD ExecuteCommand() +{ + ASSERT(this); + + // Clear error data + SetReqByte(3, 0); + SetReqByte(4, 0); + + // Command number + command = (DWORD)GetReqByte(2); // Bit 7 is verify flag + + // Unit number + unit = GetReqByte(1); + + // Command branching + switch (command & 0x7F) { + case 0x40: return InitDevice(); // $40 - Device startup + case 0x41: return CheckDir(); // $41 - Directory check + case 0x42: return MakeDir(); // $42 - Create directory + case 0x43: return RemoveDir(); // $43 - Delete directory + case 0x44: return Rename(); // $44 - Change file name + case 0x45: return Delete(); // $45 - Delete file + case 0x46: return Attribute(); // $46 - Get / set file attribute + case 0x47: return Files(); // $47 - Find file + case 0x48: return NFiles(); // $48 - Find next file + case 0x49: return Create(); // $49 - Create file + case 0x4A: return Open(); // $4A - Open file + case 0x4B: return Close(); // $4B - Close file + case 0x4C: return Read(); // $4C - Read file + case 0x4D: return Write(); // $4D - Write file + case 0x4E: return Seek(); // $4E - Seek file + case 0x4F: return TimeStamp(); // $4F - Get / set file timestamp + case 0x50: return GetCapacity();// $50 - Get capacity + case 0x51: return CtrlDrive(); // $51 - Inspect / control drive status + case 0x52: return GetDPB(); // $52 - Get DPB + case 0x53: return DiskRead(); // $53 - Read sectors + case 0x54: return DiskWrite(); // $54 - Write sectors + case 0x55: return Ioctrl(); // $55 - IOCTRL + case 0x56: return Flush(); // $56 - Flush + case 0x57: return CheckMedia(); // $57 - Media change check + case 0x58: return Lock(); // $58 - Lock + } + + return FS_FATAL_INVALIDCOMMAND; +} + +//--------------------------------------------------------------------------- +// +// Initialization +// +//--------------------------------------------------------------------------- +BOOL Init() +{ + ASSERT(this); + + return SCSI_Init(); +} + +//--------------------------------------------------------------------------- +// +// Execution +// +//--------------------------------------------------------------------------- +void Process(DWORD nA5) +{ + ASSERT(this); + ASSERT(nA5 <= 0xFFFFFF); + ASSERT(m_bAlloc); + ASSERT(m_bFree); + + // Request header address + request = (BYTE*)nA5; + + // Command execution + SetResult(ExecuteCommand()); +} diff --git a/src_old/x68k/RASDRV/BRIDGE.H b/src_old/x68k/RASDRV/BRIDGE.H new file mode 100644 index 00000000..1d531583 --- /dev/null +++ b/src_old/x68k/RASDRV/BRIDGE.H @@ -0,0 +1,255 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Powered by XM6 TypeG Technology. +// Copyright (C) 2016-2017 GIMONS +// [ Host Filesystem Bridge Driver ] +// +//--------------------------------------------------------------------------- + +#ifndef bridge_h +#define bridge_h + +typedef unsigned char BYTE; +typedef unsigned short WORD; +typedef unsigned long DWORD; +typedef int BOOL; + +#if !defined(FALSE) +#define FALSE 0 +#endif + +#if !defined(TRUE) +#define TRUE 1 +#endif + +#define FASTCALL +#define ASSERT(x) + +//--------------------------------------------------------------------------- +// +// Constant definitions +// +//--------------------------------------------------------------------------- +#define FILEPATH_MAX _MAX_PATH +#define WINDRV_CLIPSIZE_MAX 0xC00000 + +//--------------------------------------------------------------------------- +// +// Status code definitions +// +//--------------------------------------------------------------------------- +#define FS_INVALIDFUNC 0xFFFFFFFF // Executed an invalid function +#define FS_FILENOTFND 0xFFFFFFFE // The selected file can not be found +#define FS_DIRNOTFND 0xFFFFFFFD // The selected directory can not be found +#define FS_OVEROPENED 0xFFFFFFFC // There are too many files open +#define FS_CANTACCESS 0xFFFFFFFB // Can not access the direcory or volume +#define FS_NOTOPENED 0xFFFFFFFA // The selected handle is not opened +#define FS_INVALIDMEM 0xFFFFFFF9 // Memory management has been destroyed +#define FS_OUTOFMEM 0xFFFFFFF8 // Insufficient memory for execution +#define FS_INVALIDPTR 0xFFFFFFF7 // Selected an invalid memory management pointer +#define FS_INVALIDENV 0xFFFFFFF6 // Selected an invalid environment +#define FS_ILLEGALFMT 0xFFFFFFF5 // The exeucted file is in an invalid format +#define FS_ILLEGALMOD 0xFFFFFFF4 // Invalid open access mode +#define FS_INVALIDPATH 0xFFFFFFF3 // Mistake in selected file name +#define FS_INVALIDPRM 0xFFFFFFF2 // Called with an invalid parameter +#define FS_INVALIDDRV 0xFFFFFFF1 // Mistake in selected drive +#define FS_DELCURDIR 0xFFFFFFF0 // Unable to delete the current directory +#define FS_NOTIOCTRL 0xFFFFFFEF // Unable to use IOCTRL with the device +#define FS_LASTFILE 0xFFFFFFEE // Can not find any more files +#define FS_CANTWRITE 0xFFFFFFED // Selected file can not be written +#define FS_DIRALREADY 0xFFFFFFEC // Selected directory is already registered +#define FS_CANTDELETE 0xFFFFFFEB // Can not delete because of a file +#define FS_CANTRENAME 0xFFFFFFEA // Can not rename because of a file +#define FS_DISKFULL 0xFFFFFFE9 // Can not create a file because the disk is full +#define FS_DIRFULL 0xFFFFFFE8 // Can not create a file because the directory is full +#define FS_CANTSEEK 0xFFFFFFE7 // Can not seek in the selected location +#define FS_SUPERVISOR 0xFFFFFFE6 // Selected supervisor in supervisor mode +#define FS_THREADNAME 0xFFFFFFE5 // A thread with this name already exists +#define FS_BUFWRITE 0xFFFFFFE4 // Writing to inter-process communication buffers is disallowed +#define FS_BACKGROUND 0xFFFFFFE3 // Unable to start a background process +#define FS_OUTOFLOCK 0xFFFFFFE0 // Insufficient lock space +#define FS_LOCKED 0xFFFFFFDF // Can not access because it is locked +#define FS_DRIVEOPENED 0xFFFFFFDE // Selected drive has an open handler +#define FS_LINKOVER 0xFFFFFFDD // The symbolic link is nested over 16 times +#define FS_FILEEXIST 0xFFFFFFB0 // The file exists + +#define FS_FATAL_MEDIAOFFLINE 0xFFFFFFA3 // No media inserted +#define FS_FATAL_WRITEPROTECT 0xFFFFFFA2 // Write protected +#define FS_FATAL_INVALIDCOMMAND 0xFFFFFFA1 // Invalid command number +#define FS_FATAL_INVALIDUNIT 0xFFFFFFA0 // Invalid unit number + +#define HUMAN68K_PATH_MAX 96 // Longest path allowed in Human68k + +//=========================================================================== +// +/// Human68k name space +// +//=========================================================================== +/// File attribute bit +enum attribute_t { + AT_READONLY = 0x01, // Read only attribute + AT_HIDDEN = 0x02, // Hidden attribute + AT_SYSTEM = 0x04, // System attribute + AT_VOLUME = 0x08, // Volume label attribute + AT_DIRECTORY = 0x10, // Directory attribute + AT_ARCHIVE = 0x20, // Archive attribute + AT_ALL = 0xFF, // All attribute bits are 1 +}; + +/// File open modes +enum open_t { + OP_READ = 0, // Read + OP_WRITE = 1, // Write + OP_FULL = 2, // Read/Write + OP_MASK = 0x0F, // Decision mask + OP_SHARE_NONE = 0x10, // Sharing forbidden + OP_SHARE_READ = 0x20, // Read sharing + OP_SHARE_WRITE = 0x30, // Write sharing + OP_SHARE_FULL = 0x40, // Read/Write sharing + OP_SHARE_MASK = 0x70, // Sharing decision mask + OP_SPECIAL = 0x100, // Dictionary access +}; + +/// Seek types +enum seek_t { + SK_BEGIN = 0, // From the beginning of a file + SK_CURRENT = 1, // From the current location + SK_END = 2, // From the end of the file +}; + +/// Media byte +enum media_t { + MEDIA_2DD_10 = 0xE0, // 2DD/10 sector + MEDIA_1D_9 = 0xE5, // 1D/9 sector + MEDIA_2D_9 = 0xE6, // 2D/9 sector + MEDIA_1D_8 = 0xE7, // 1D/8 sector + MEDIA_2D_8 = 0xE8, // 2D/8 sector + MEDIA_2HT = 0xEA, // 2HT + MEDIA_2HS = 0xEB, // 2HS + MEDIA_2HDE = 0xEC, // 2DDE + MEDIA_1DD_9 = 0xEE, // 1DD/9 sector + MEDIA_1DD_8 = 0xEF, // 1DD/8 sector + MEDIA_MANUAL = 0xF1, // Remote drive (manual eject) + MEDIA_REMOVABLE = 0xF2, // Remote drive (removable) + MEDIA_REMOTE = 0xF3, // Remote drive + MEDIA_DAT = 0xF4, // SCSI-DAT + MEDIA_CDROM = 0xF5, // SCSI-CDROM + MEDIA_MO = 0xF6, // SCSI-MO + MEDIA_SCSI_HD = 0xF7, // SCSI-HD + MEDIA_SASI_HD = 0xF8, // SASI-HD + MEDIA_RAMDISK = 0xF9, // RAM disk + MEDIA_2HQ = 0xFA, // 2HQ + MEDIA_2DD_8 = 0xFB, // 2DD/8 sector + MEDIA_2DD_9 = 0xFC, // 2DD/9 sector + MEDIA_2HC = 0xFD, // 2HC + MEDIA_2HD = 0xFE, // 2HD +}; + +/// namests struct +typedef struct { + BYTE wildcard; // Wildcard character length + BYTE drive; // Drive number + BYTE path[65]; // Path (subdirectory +/) + BYTE name[8]; // File name (PADDING 0x20) + BYTE ext[3]; // Extension (PADDING 0x20) + BYTE add[10]; // File name addition (PADDING 0x00) +} namests_t; + +/// files struct +typedef struct { + BYTE fatr; // + 0 search attribute; read-only + BYTE pad1[3]; // padding +// BYTE drive; // + 1 drive number; read-only + DWORD sector; // + 2 directory sector; DOS _FILES first address substitute +// WORD cluster; // + 6 directory cluster; details unknown (unused) + WORD offset; // + 8 directory entry; write-only +// BYTE name[8]; // +10 working file name; write-only (unused) +// BYTE ext[3]; // +18 working extension; write-only (unused) + BYTE attr; // +21 file attribute; write-only + BYTE pad2; // padding + WORD time; // +22 last change time of day; write-only + WORD date; // +24 last change date; write-only + DWORD size; // +26 file size; write-only + BYTE full[23]; // +30 full name; write-only + BYTE pad3; // padding +} files_t; + +/// FCB\ +typedef struct { +// BYTE pad00[6]; // + 0~+ 5 (unused) + DWORD fileptr; // + 6~+ 9 file pointer +// BYTE pad01[4]; // +10~+13 (unused) + WORD mode; // +14~+15 open mode +// BYTE pad02[16]; // +16~+31 (unused) +// DWORD zero; // +32~+35 zeros are written when opened (unused) +// BYTE name[8]; // +36~+43 file name (PADDING 0x20) (unused) +// BYTE ext[3]; // +44~+46 extension (PADDING 0x20) (unused) + BYTE attr; // +47 file attribute +// BYTE add[10]; // +48~+57 file name addition (PADDING 0x00) (unused) + WORD time; // +58~+59 last change time of day + WORD date; // +60~+61 last change date +// WORD cluster; // +62~+63 cluster number (unused) + DWORD size; // +64~+67 file size +// BYTE pad03[28]; // +68~+95 FAT cache (unused) +} fcb_t; + +/// capacity struct +typedef struct { + WORD freearea; // + 0 Number of available clusters + WORD clusters; // + 2 Total number of clusters + WORD sectors; // + 4 Number of sectors per cluster + WORD bytes; // + 6 Number of bytes per sector +} capacity_t; + +/// ctrldrive struct +typedef struct { + BYTE status; // +13 status + BYTE pad[3]; // padding +} ctrldrive_t; + +/// DPB struct +typedef struct { + WORD sector_size; // + 0 Number of bytes in one sector + BYTE cluster_size; // + 2 Number sectors in one cluster -1 + BYTE shift; // + 3 Number of clustersector shifts + WORD fat_sector; // + 4 FAT first sector number + BYTE fat_max; // + 6 FAT storage quantity + BYTE fat_size; // + 7 FAT controlled sector number (excluding duplicates) + WORD file_max; // + 8 Number of files in the root directory + WORD data_sector; // +10 First sector number of data storage + WORD cluster_max; // +12 Total number of clusters +1 + WORD root_sector; // +14 First sector number of root directory +// DWORD driverentry; // +16 Device driver pointer (unused) + BYTE media; // +20 Media identifier +// BYTE flag; // +21 Flag used by DPB (unused) + BYTE pad; // padding +} dpb_t; + +/// Directory entry struct +typedef struct { + BYTE name[8]; // + 0 File name (PADDING 0x20) + BYTE ext[3]; // + 8 Extension (PADDING 0x20) + BYTE attr; // +11 File attribute + BYTE add[10]; // +12 File name addition (PADDING 0x00) + WORD time; // +22 Last change time of day + WORD date; // +24 Last change date + WORD cluster; // +26 Cluster number + DWORD size; // +28 File size +} dirent_t; + +/// IOCTRL parameter union +typedef union { + BYTE buffer[8]; // Access in byte units + DWORD param; // Parameter (First 4 bytes) + WORD media; // Media byte (First 2 bytes) +} ioctrl_t; + +/// Command line parameter struct +typedef struct { + BYTE buf[256]; // Command line argument +} argument_t; + +#endif // bridge_h diff --git a/src_old/x68k/RASDRV/Makefile b/src_old/x68k/RASDRV/Makefile new file mode 100644 index 00000000..4a449de3 --- /dev/null +++ b/src_old/x68k/RASDRV/Makefile @@ -0,0 +1,17 @@ +# +# Makefile for RASDRV.SYS +# + +CC = gcc -O2 -Wall -fomit-frame-pointer +AS = has +LK = hlk -l -x + +RASDRV.SYS : RASDRV.o BRIDGE.o + $(LK) -o $@ $^ libc.a libiocs.a libscsi.a + +RASDRV.o : RASDRV.S + $(AS) $^ + +BRIDGE.o : BRIDGE.C + $(CC) -c -o $@ $^ + \ No newline at end of file diff --git a/src_old/x68k/RASDRV/RASDRV.S b/src_old/x68k/RASDRV/RASDRV.S new file mode 100644 index 00000000..6eb0a848 --- /dev/null +++ b/src_old/x68k/RASDRV/RASDRV.S @@ -0,0 +1,193 @@ +*--------------------------------------------------------------------------- +* +* SCSI Target Emulator RaSCSI (*^..^*) +* for Raspberry Pi +* +* Powered by XM6 TypeG Technology. +* Copyright (C) 2016-2019 GIMONS +* [ Host Filesystem Bridge Driver ] +* +* Based on +* X68k Emulator Host Filesystem Driver version 0.27 +* Programmed By co +* +*--------------------------------------------------------------------------- + + CPU 68010 + + INCLUDE DOSCALL.MAC + INCLUDE IOCSCALL.MAC + +.XREF _Init, _Process ;bridge.c + +COMMAND EQU 2 Command number +ERRLOW EQU 3 Error code lower 8 bits +ERRHIGH EQU 4 Error code upper 8 bits +MXUNIT EQU 13 Number of units +DEVEND EQU 14 Driver exit address +BDEVNO EQU 22 Base drive number + +DDHEADER: + DC.L -1 +$00 Link pointer + DC.W $2040 +$04 Device attribute + DC.L DDSTRATEGY +$06 + DC.L DDENTRY_FIRST +$0A + DC.B '*EMUHOST' +$0E Device name + +DDREQUEST: + DC.L 0 + +DDSTRATEGY: + MOVE.L A5,DDREQUEST + RTS + +*Device driver entry +DDENTRY: + MOVE.L SP,(STACK_BUFF) + LEA (DEF_STACK),SP + MOVEM.L D1-D3/A0-A2,-(SP) + MOVEM.L DDREQUEST(PC),D0 D0.L: Request header address + MOVE.L D0,-(SP) + BSR _Process + ADDQ.L #4,SP + MOVEM.L (SP)+,D1-D3/A0-A2 + MOVEA.L (STACK_BUFF),SP + RTS + +KEEP_HERE: + +*First time only device driver entry +DDENTRY_FIRST: + MOVEM.L D1-D3/A0-A2/A5,-(SP) + MOVEA.L DDREQUEST(PC),A5 A5.L: Request header address +* CMPI.B #$40,COMMAND(A5) No need to check when Human68k is running +* BNE UNSUPPORTED Error: Not supported + +*Show title + PEA MESSAGE_TITLE(PC) + DOS _PRINT + ADDQ.L #4,SP + +*Device check +DEVICE_CHECK: + BSR _Init + + TST.L D0 + BEQ NOTFOUND Error: Device not found + +*Resident program start + LEA DDENTRY(PC),A1 + + LEA DDHEADER+$0A(PC),A0 + MOVE.L A1,(A0) Configure driver entry + MOVE.L #PROG_END,DEVEND(A5) Configure resident exit address + + ST.B MXUNIT(A5) Set negative number since port is considered unimplemented + JSR (A1) Execute driver entry + + TST.B MXUNIT(A5) Verify number of units + BLE NODRIVE Error: No drive + + MOVE.B BDEVNO(A5),D0 + MOVE.B MXUNIT(A5),D1 + LEA FIRSTDRIVE(PC),A0 + ADD.B D0,(A0) Overwrite start drive name + ADD.B D1,D0 Overwrite stop drive name + ADD.B D0,LASTDRIVE-FIRSTDRIVE(A0) + + PEA MESSAGE_DRIVENAME(PC) Normal: Drive A: + DOS _PRINT + + PEA MESSAGE_DRIVENAME2(PC) Normal: to Z: + SUBQ.B #2,D1 + BCS @F + DOS _PRINT +@@ ADDQ.L #8,SP + + PEA MESSAGE_DRIVENAME3(PC) Normal: was registered + BRA QUIT + +NOTFOUND: + PEA MESSAGE_NOTFOUND(PC) Error: Device not found + BRA ABORT + +UNSUPPORTED: + PEA MESSAGE_UNSUPPORTED(PC) Error: Not supported + BRA ABORT + +NODRIVE: + PEA MESSAGE_NODRIVE(PC) Error: No drive + +ABORT: + MOVE.B #$0D,ERRLOW(A5) + MOVE.B #$70,ERRHIGH(A5) + +QUIT: + DOS _PRINT + ADDQ.L #4,SP + + MOVEM.L (SP)+,D1-D3/A0-A2/A5 + RTS + +SECRET: + PEA MESSAGE_TITLE2(PC) + DOS _PRINT + PEA CREDIT(PC) + DOS _PRINT + ADDQ.L #8,SP + DOS _EXIT + + DATA + +*Error messages +MESSAGE_NOTFOUND: + DC.B 'Device not found',$0D,$0A,0 + +MESSAGE_UNSUPPORTED: + DC.B 'Not supported',$0D,$0A,0 + +MESSAGE_NODRIVE: + DC.B 'No drive',$0D,$0A,0 + +*Registration messages +MESSAGE_DRIVENAME: + DC.B 'Drive' +FIRSTDRIVE: + DC.B 'A:',0 +MESSAGE_DRIVENAME2: + DC.B ' to ' +LASTDRIVE: + DC.B '@:',0 +MESSAGE_DRIVENAME3: + DC.B ' was registered',$0D,$0A,0 + +*Title +MESSAGE_TITLE: + DC.B $0D,$0A +MESSAGE_TITLE2: + DC.B 'RaSCSI FileSystem Driver version 1.42',$0D,$0A,0 + +*Thanks! +CREDIT: + DC.B 'Coded by GIMONS',$0D,$0A + DC.B 'Special thanks to',$0D,$0A + DC.B 9,'co',$0D,$0A + DC.B 9,'PI.',$0D,$0A + DC.B 9,'Tachibana@Kuwashima Giken',$0D,$0A + DC.B 0 + + BSS + QUAD + +STACK_BUFF: + .DS.L 1 + + STACK + QUAD + + .DS.B 1024*2 +DEF_STACK: + + +PROG_END: + END SECRET diff --git a/src_old/x68k/RASETHER/Makefile b/src_old/x68k/RASETHER/Makefile new file mode 100644 index 00000000..92557b33 --- /dev/null +++ b/src_old/x68k/RASETHER/Makefile @@ -0,0 +1,33 @@ +# +# Makefile for RASETHER.SYS +# + +TARGET = RASETHER.SYS + +OBJS = re.o asmsub.o main.o scsictl.o +LIBS = libc.a libgnu.a libscsi.a + +CC = gcc -O2 -Wall -fomit-frame-pointer +CFLAGS = -D__DOS_INLINE__ -D__IOCS_INLINE__ +#CFLAGS = -D__DOS_INLINE__ -D__IOCS_INLINE__ -DUSE_DMA + +AS = has +ASFLAGS = +LD = hlk +LDFLAGS = -l -x + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(LD) $(LDFLAGS) -o $@ $^ $(LIBS) + +scsictl.o: scsictl.c scsictl.h + $(COMPILE.c) $< + +main.o: main.c main.h scsictl.h + $(COMPILE.c) $< + +asmsub.o: asmsub.s + +# EOF + \ No newline at end of file diff --git a/src_old/x68k/RASETHER/asmsub.s b/src_old/x68k/RASETHER/asmsub.s new file mode 100644 index 00000000..87d85957 --- /dev/null +++ b/src_old/x68k/RASETHER/asmsub.s @@ -0,0 +1,22 @@ +* asmsub.s + + .xdef _DI, _EI * ne2000.c nagai. + + .text + .even + + + +_DI: + move.w sr,sendp_sr + ori.w #$700,sr + rts +_EI: + move.w sendp_sr,sr + rts + +sendp_sr: + .ds.w 1 + + + .even diff --git a/src_old/x68k/RASETHER/main.c b/src_old/x68k/RASETHER/main.c new file mode 100644 index 00000000..17cf4bce --- /dev/null +++ b/src_old/x68k/RASETHER/main.c @@ -0,0 +1,282 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Powered by XM6 TypeG Technology. +// Copyright (C) 2016-2017 GIMONS +// [ RaSCSI Ethernet Main ] +// +// Based on +// Neptune-X board driver for Human-68k(ESP-X) version 0.03 +// Programed 1996-7 by Shi-MAD. +// Special thanks to Niggle, FIRST, yamapu ... +// +//--------------------------------------------------------------------------- + +#include +#include +#include +#include "main.h" +#include "scsictl.h" + +unsigned int scsiid; +int trap_no; +int num_of_prt; +struct prt PRT_LIST[NPRT]; + +// Multicast (not supported) +#ifdef MULTICAST +int num_of_multicast; +struct eaddr multicast_array[NMULTICAST]; +#endif + +/************************************************ + * * + ************************************************/ +static int sprint_eaddr(unsigned char* dst, void* e) +{ + unsigned char* p = e; + + return sprintf( + dst, + "%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x", + p[0], p[1], p[2], p[3], p[4], p[5]); +} + +/************************************************ + * Check if TRAP n can be used * + ************************************************/ +static int is_valid_trap(int trap_no) +{ + unsigned int addr; + + if (trap_no < 0 || 7 < trap_no) { + return 0; + } + + // Check if in use + addr = (unsigned int)_dos_intvcg(TRAP_VECNO(trap_no)); + + // Unused if the uppermost byte of the process address contains the vector number + if (addr & 0xff000000) { + return -1; + } + + return 0; +} + +/************************************************ + * Search for unused TRAP n * + ************************************************/ +static int search_trap_no(int def) +{ + int i; + + // If def is usable, choose that + if (is_valid_trap(def)) { + return def; + } + + for (i = 0; i <= 6; i++) { + if (is_valid_trap(i)) { + return i; + } + } + + return -1; +} + +/************************************************ + * vector set * + ************************************************/ +static void* trap_vector(int trap_no, void *func) +{ + return _dos_intvcs(TRAP_VECNO(trap_no), func); +} + +/************************************************ + * Init Function (call with ne.s initialize) * + ************************************************/ +int Initialize(void) +{ + unsigned char buff[128]; + unsigned char buff2[32]; + struct eaddr ether_addr; + + if (SearchRaSCSI()) + { + Print("Could not locate the RaSCSI Ethernet Adapter\r\n"); + return -1; + } + + if (InitList(NPRT) + || InitRaSCSI()) + { + Print("Failed to initialize the RaSCSI Ethernet Adapter Driver\r\n"); + return -1; + } + + memset(ðer_addr, 0x00, sizeof(ether_addr)); + GetMacAddr(ðer_addr); + + // Inspect unused trap number (prioritize specified number) + if (trap_no >= 0) { + trap_no = search_trap_no(trap_no); + } + + if (trap_no >= 0) { + // Hook the trap + trap_vector(trap_no, (void*)trap_entry); + sprintf(buff, " API trap number:%d ", trap_no); + } else { + sprintf(buff, " API trap number:n/a "); + } + Print(buff); + + sprintf(buff, "SCSI ID:%d ", scsiid); + Print(buff); + + sprint_eaddr(buff2, ether_addr.eaddr); + sprintf(buff, "MAC Addr:%s\r\n", buff2); + Print(buff); + + // Start polling + RegisterIntProcess(poll_interval); + + return 0; +} + +/************************************************ + * Initialize Protocol List * + ************************************************/ +int InitList(int n) +{ + struct prt* p; + int i; + + p = &PRT_LIST[0]; + for (i = 0; i < NPRT; i++, p++) { + p->type = -1; + p->func = 0; + p->malloc = (malloc_func)-1; + } + num_of_prt = 0; + +#ifdef MULTICAST + num_of_multicast = 0; +#endif + + return 0; +} + +/************************************************ + * Add Receive Handler (protocol) * + ************************************************/ +int AddList(int type, int_handler handler) +{ + struct prt* p; + int i, result; + + if (type == -1) { + return -1; + } + + result = -1; + + // overwrite if already exist + p = &PRT_LIST[0]; + for (i = 0; i < NPRT; i++, p++) { + if ((p->type == type && p->malloc != (malloc_func)-1) + || p->type == -1) + { + if (p->type == -1) + num_of_prt++; + + p->type = type; + p->func = handler; + p->malloc = (malloc_func)-1; + i++; + p++; + result = 0; + break; + } + } + + // clear if exist more + for (; i < NPRT; i++, p++) { + if (p->type == type && p->malloc != (malloc_func)-1) { + p->type = -1; + } + } + + return result; +} + +/************************************************ + * Delete Receive Handler (protocol) * + ************************************************/ +int DeleteList(int type) +{ + struct prt* p; + int i, result; + + if (type == -1) { + return -1; + } + + result = -1; + for (i = 0, p = &PRT_LIST[0]; i < NPRT; i++, p++) { + if (p->type == type && p->malloc != (malloc_func)-1) { + p->type = -1; + result = 0; + num_of_prt--; + } + } + + return result; +} + +/************************************************ + * Search Receive Handler (protocol) * + ************************************************/ +int_handler SearchList(int type) +{ + struct prt* p; + int i; + + if (type == -1) { + return 0; + } + + for (i = 0, p = &PRT_LIST[0]; i < NPRT; i++, p++) { + if (p->type == type) { + return p->func; + } + } + + return 0; +} + + +/************************************************ + * Search Receive Handler (protocol) * + ************************************************/ +int_handler SearchList2(int type, int n) +{ + struct prt *p; + int i, cur; + + if (type == -1) { + return NULL; + } + + cur = 0; + for (i = 0, p = &PRT_LIST[0]; i < NPRT; i++, p++) { + if (p->type == type && cur++ == n) { + return p->func; + } + } + + return NULL; +} diff --git a/src_old/x68k/RASETHER/main.h b/src_old/x68k/RASETHER/main.h new file mode 100644 index 00000000..68a41cee --- /dev/null +++ b/src_old/x68k/RASETHER/main.h @@ -0,0 +1,81 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Powered by XM6 TypeG Technology. +// Copyright (C) 2016-2017 GIMONS +// [ RaSCSI Ethernet Main ] +// +// Based on +// Neptune-X board driver for Human-68k(ESP-X) version 0.03 +// Programed 1996-7 by Shi-MAD. +// Special thanks to Niggle, FIRST, yamapu ... +// +//--------------------------------------------------------------------------- + +#ifndef main_h +#define main_h + +#define ID_EN0 (('e'<<24)+('n'<<16)+('0'<<8)) +#undef MULTICAST + +// number of protocol type we can handle **EDIT this** +#define NPRT (16) + +// number of multicast address we can handle **EDIT this** +#define NMULTICAST (64) +/* However, multicast is not yet supported */ + +struct eaddr { + unsigned char eaddr [6]; +}; + +typedef void (*int_handler) (int, void*, int); +typedef void (*malloc_func) (unsigned int*); + +struct prt { + int type; + int_handler func; + malloc_func malloc; +}; + + +// Global variables +extern unsigned int scsiid; +extern int trap_no; +extern int num_of_prt; +extern struct prt PRT_LIST [NPRT]; + +#ifdef MULTICAST +extern int num_of_multicast; +extern struct eaddr multicast_array [NMULTICAST]; +#endif + + +// Prototype declarations +extern int Initialize (void); +extern int InitList (int); +extern int AddList (int, int_handler); +extern int DeleteList (int); +extern int_handler SearchList (int); +extern int_handler SearchList2 (int, int); +extern malloc_func GetMallocFunc (int, int); + +#ifdef MULTICAST +extern int AddMulticastArray (struct eaddr*); +extern void DelMulticastArray (struct eaddr*); +extern void MakeMulticastTable (unsigned char*); +#endif + + +// Function within ne.s +extern void trap_entry (void); + + +#include +#define Print _iocs_b_print + +#define TRAP_VECNO(n) (0x20 + n) + +#endif // main_h diff --git a/src_old/x68k/RASETHER/re.s b/src_old/x68k/RASETHER/re.s new file mode 100644 index 00000000..1ff0c65b --- /dev/null +++ b/src_old/x68k/RASETHER/re.s @@ -0,0 +1,574 @@ +**--------------------------------------------------------------------------- +** +** SCSI Target Emulator RaSCSI (*^..^*) +** for Raspberry Pi +** +** Powered by XM6 TypeG Technology. +** Copyright (C) 2016-2017 GIMONS +** [ RaSCSI Ethernet Driver ] +** +** Based on +** Neptune-X board driver for Human-68k(ESP-X) version 0.03 +** Programed 1996-7 by Shi-MAD. +** Special thanks to Niggle, FIRST, yamapu ... +** +**--------------------------------------------------------------------------- + +* Include Files ----------------------- * + + .include doscall.mac + .include iocscall.mac + + +* Global Symbols ---------------------- * + +* +* For C language: external declarations +* + .xref _Initialize, _AddList, _SearchList, _DeleteList ;main.c + .xref _GetMacAddr, _SetMacAddr ;scsictl.c + .xref _SendPacket, _SetPacketReception ;scsictl.c +* .xref _AddMulticastAddr, _DelMulticastAddr ;not implemented + +* +* For C language: external functions +* + .xref _num_of_prt ;main.c Number of registered protocols + .xref _trap_no ;Used trap number + .xref _trans_counter ;scsictl.c Number of send/receive bytes + .xref _intr_type ;scsictl.c Type of interrupt + .xref _poll_interval ;scsictl.c Polling interval + + +* Text Section -------------------------------- * + + .cpu 68000 + .text + + +* +* Device Header +* +device_header: + .dc.l -1 ;link pointer + .dc $8000 ;device att. + .dc.l strategy_entry ;strategy entry + .dc.l interupt_entry ;interrupt entry + .dc.b '/dev/en0' ;device name + .dc.b 'EthD' ;for etherlib.a + .dc.b 'RASC' ;driver name (put in entry later) + +* Don not put anything between 'RASC' and superjsr_entry + +* +* Ether Driver Function Entry ( for DOS _SUPERJSR ) +* in: d0: command number +* a0: args +* +superjsr_entry: + movem.l d1-d7/a1-a7,-(sp) + +* move.l d0,-(sp) +* pea (mes11,pc) +* DOS _PRINT +* addq.l #4,sp +* move.l (sp)+,d0 +* .data +*mes11: .dc.b 'en0:spjEntry',13,10,0 +* .text + + bsr do_command + movem.l (sp)+,d1-d7/a1-a7 + rts ;normal return + + +* +* Ether Driver Function Entry ( for trap #n ) +* in: d0: command number +* a0: args +* +_trap_entry:: + movem.l d1-d7/a1-a7,-(sp) + +* move.l d0,-(sp) +* pea (mes10,pc) +* DOS _PRINT +* addq.l #4,sp +* move.l (sp)+,d0 +* .data +*mes10: .dc.b 'en0:trapEntry',13,10,0 +* .text + + bsr do_command + movem.l (sp)+,d1-d7/a1-a7 + rte ;interrupt return + + +* +* Assign each command +* +do_command: + moveq #FUNC_MIN,d1 + cmp.l d0,d1 + bgt error ;d0<-2 is an unsupported command number + moveq #FUNC_MAX,d1 + cmp.l d1,d0 + bgt error ;9) + .dc get_statistics-jumptable ;09 ... (Read out statistics ) +FUNC_MAX: .equ ($-jumptable)/2-1 + + +* +* Command -2: Return transfer counter address +* return: address +* +get_cnt_addr: + pea (_trans_counter,pc) + move.l (sp)+,d0 + rts + + +* +* Command -1: Return used trap number +* return: trap number to use (-1:use SUPERJSR) +* +driver_entry: +* pea (mesff,pc) +* DOS _PRINT +* addq.l #4,sp +* .data +*mesff: .dc.b 'DriverEntry',13,10,0 +* .text + + move.l (_trap_no,pc),d0 ;trap_no ... main.c variable + rts + + +* +* Command 00: Return driver version +* return: version number (Ex... for ver1.00 return 100) +* +get_driver_version: +* pea (mes00,pc) +* DOS _PRINT +* addq.l #4,sp +* .data +*mes00: .dc.b 'GetDriverVersion',13,10,0 +* .text + + moveq #3,d0 ;ver 0.03 + rts + + +* +* Command 01: Get current MAC address +* return: same as *dst +* +get_mac_addr: +* pea (mes01,pc) +* DOS _PRINT +* addq.l #4,sp +* .data +*mes01: .dc.b 'GetMacAddr',13,10,0 +* .text + + pea (a0) + pea (a0) + bsr _GetMacAddr ;scsictl.c function + addq.l #4,sp + move.l (sp)+,d0 ;Return d0 for parameter a0 + rts + +* +* Command 02: Get MAC address written to EEPROM +* return: same as *dst +* +get_prom_addr: +* pea (mes02,pc) +* DOS _PRINT +* addq.l #4,sp +* .data +*mes02: .dc.b 'GePromAddr',13,10,0 +* .text + + pea (a0) + pea (a0) + bsr _GetMacAddr ;scsictl.c function + addq.l #4,sp + move.l (sp)+,d0 ;Return d0 for parameter a0 + rts + +* +* Command 03: Set MAC address +* return: 0 (if no errors) +* +set_mac_addr: +* pea (mes03,pc) +* DOS _PRINT +* addq.l #4,sp +* .data +*mes03: .dc.b 'SetMacAddr',13,10,0 +* .text + + pea (a0) + bsr _SetMacAddr ;scsictl.c function + addq.l #4,sp + rts + + +* +* Command 04: Send packet +* packet contents: +* Distination MAC: 6 bytes +* Source(own) MAC: 6 bytes +* Protcol type: 2 bytes +* Data: ? bytes +* return: 0 (if no errors) +* +send_packet: +* pea (mes04,pc) +* DOS _PRINT +* addq.l #4,sp + + move.l (a0)+,d0 ;packet size + move.l (a0),-(sp) ;packet address + move.l d0,-(sp) + bsr _SendPacket ;scsictl.c function + addq.l #8,sp + +* move.l d0,-(sp) +* pea (mes04e,pc) +* DOS _PRINT +* addq.l #4,sp +* move.l (sp)+,d0 +* .data +*mes04: .dc.b 13,10,'SendPacket,13,10',0 +*mes04e:.dc.b 13,10,'SendPacket finished',13,10,0 +* .text + + rts + + +* +* Command 05: Set / add receive interrupt handler +* type: 0x00000800 IP packet +* 0x00000806 ARP packet +* return: 0 (if no errors) +* +set_int_addr: +* pea (mes05,pc) +* DOS _PRINT +* addq.l #4,sp +* .data +*mes05: .dc.b 'SetIntAddr',13,10,0 +* .text + + move.l (a0)+,d0 ;protocol number + move.l (a0),-(sp) ;address to handler function + move.l d0,-(sp) + bsr _AddList ;main.c function + addq.l #8,sp + tst.l d0 + bmi set_int_addr_rts ;Registration failed + + cmpi.l #1,(_num_of_prt) ;Permit interrupt if number of handlers is 1 + bne set_int_addr_rts + + pea (1) ;1= + bsr _SetPacketReception ;interrupt permitted ... scsictl.c + addq.l #4,sp + +* moveq #0,d0 ;SetPacketReception() always returns 0 so bypass +set_int_addr_rts: + rts + + +* +* Command 06: Get interrupt handler and address +* return: interupt address +* +get_int_addr: +* pea (mes07,pc) +* DOS _PRINT +* addq.l #4,sp +* .data +*mes07: .dc.b 'GetIntAddr',13,10,0 +* .text + + pea (a0) + bsr _SearchList + addq.l #4,sp + rts + + +* +* Command 07: Delete interrupt handler +* return: 0 (if no errors) +* +del_int_addr: +* pea (mes06,pc) +* DOS _PRINT +* addq.l #4,sp +* .data +*mes06: .dc.b 'DelIntAddr',13,10,0 +* .text + + pea (a0) + bsr _DeleteList ;main.c function + move.l d0,(sp)+ + bmi del_int_addr_ret ;Delete failed + + tst.l (_num_of_prt) ;Forbid interrupts if handlers are gone + bne del_int_addr_ret + + clr.l -(sp) ;0= + bsr _SetPacketReception ;Interrupt forbitten ... scsictl.c + addq.l #4,sp + +* moveq #0,d0 ;SetPacketReception() always returns 0 so bypass +del_int_addr_ret: + rts + + +* +* Command 08: Set multicast address +* +set_multicast_addr: +* pea (mes08,pc) +* .data +* DOS _PRINT +* addq.l #4,sp +*mes08: .dc.b 'SetMulticastAddr',13,10,0 +* .text + + moveq #0,d0 + rts + + +* +* Command 09: Read out statistics +* +get_statistics: +* pea (mes09,pc) +* DOS _PRINT +* addq.l #4,sp +* .data +*mes09: .dc.b 'GetStatistics',13,10,0 +* .text + + moveq #0,d0 + rts + +* +* Device Driver Entry +* +strategy_entry: + move.l a5,(request_buffer) + rts + + +interupt_entry: + move.l sp,(stack_buff) ;Use own stack area + lea (def_stack),sp ; + + movem.l d1-d7/a0-a5,-(sp) + movea.l (request_buffer,pc),a5 + tst.b (2,a5) + bne errorret + + pea (mestitle,pc) + DOS _PRINT + addq.l #4,sp + movea.l (18,a5),a4 +@@: + tst.b (a4)+ + bne @b + + moveq #0,d0 + move.l d0,(_trap_no) + move.l d0,(_intr_type) + moveq #1,d0 + move.l d0,(_poll_interval) + +arg_loop: + move.b (a4)+,d0 + beq arg_end + cmpi.b #'-',d0 + beq @f + cmpi.b #'/',d0 + bne param_errret +@@: + move.b (a4)+,d0 + beq param_errret +opt_loop: + andi.b #$df,d0 + cmpi.b #'I',d0 + beq opt_i + cmpi.b #'P',d0 + beq opt_p + cmpi.b #'T',d0 + beq opt_t + cmpi.b #'N',d0 + beq opt_n + bra param_errret + +opt_n: + moveq #-1,d0 + move.l d0,(_trap_no) + move.b (a4)+,d0 + beq arg_loop + bra opt_loop + +opt_t: + bsr get_num + tst.b d0 + bne param_errret + cmpi #6,d2 + bgt param_errret + move.l d2,(_trap_no) + move.b (a4)+,d0 + beq arg_loop + bra opt_loop + +opt_p: + bsr get_num + tst.b d0 + bne param_errret + cmpi #1,d2 + blt param_errret + cmpi #8,d2 + bgt param_errret + move.l d2,(_poll_interval) + move.b (a4)+,d0 + beq arg_loop + bra opt_loop + +opt_i: + bsr get_num + tst.b d0 + bne param_errret + cmpi #1,d2 + bgt param_errret + move.l d2,(_intr_type) + move.b (a4)+,d0 + beq arg_loop + bra opt_loop + +arg_end: + bsr _Initialize ;main.c function + ;Set I/O address + ;Get MAC address + ;Init protocol list + ;Init SCSICTL + ;Interrupt handler (set vector) + ;trap service (set vector) + tst.l d0 + bne errorret + +* moveq #0,d0 + move.l #prog_end,(14,a5) + bra intret + + +param_errret: + pea (mesparam_err,pc) + DOS _PRINT + addq.l #4,sp +errorret: + move #$5003,d0 +intret: + move.b d0,(4,a5) + lsr #8,d0 + move.b d0,(3,a5) + movem.l (sp)+,d1-d7/a0-a5 + + movea.l (stack_buff,pc),sp ;Restore stack pointer + rts + +get_num: + moveq #1,d0 + moveq #0,d1 + moveq #0,d2 +@@: + move.b (a4),d1 + subi.b #'0',d1 + bcs @f + cmpi.b #9,d1 + bgt @f + move.b #0,d0 + andi #$f,d1 + mulu #10,d2 + add d1,d2 + addq.l #1,a4 + bra @b +@@: + rts + + +* Data Section ------------------------ * + + .data +mestitle: + .dc.b 13,10 + .dc.b 'RaSCSI Ethernet Driver version 1.20 / Based on ether_ne.sys+M01L12',13,10 + .dc.b 0 +mesparam_err: + .dc.b 'Invalid parameter',13,10,0 + .even + + +* BSS --------------------------------- * + + .bss + .quad + +stack_buff: + .ds.l 1 +request_buffer: + .ds.l 1 +stack_buff_i: + .ds.l 1 + + +* Stack Section ----------------------- * + + .stack + .quad + +* +* Stack area +* + .ds.b 1024*8 +def_stack: + +prog_end: + .end + + +* EOF --------------------------------- * diff --git a/src_old/x68k/RASETHER/scsictl.c b/src_old/x68k/RASETHER/scsictl.c new file mode 100644 index 00000000..9051710b --- /dev/null +++ b/src_old/x68k/RASETHER/scsictl.c @@ -0,0 +1,537 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Powered by XM6 TypeG Technology. +// Copyright (C) 2016-2017 GIMONS +// [ RaSCSI Ethernet SCSI Control Department ] +// +// Based on +// Neptune-X board driver for Human-68k(ESP-X) version 0.03 +// Programed 1996-7 by Shi-MAD. +// Special thanks to Niggle, FIRST, yamapu ... +// +//--------------------------------------------------------------------------- + +#include +#include +#include +#include +#include +#include "main.h" +#include "scsictl.h" + +typedef struct +{ + char DeviceType; + char RMB; + char ANSI_Ver; + char RDF; + char AddLen; + char RESV0; + char RESV1; + char OptFunc; + char VendorID[8]; + char ProductID[16]; + char FirmRev[4]; +} INQUIRY_T; + +typedef struct +{ + INQUIRY_T info; + char options[8]; +} INQUIRYOPT_T; + +#define MFP_AEB 0xe88003 +#define MFP_IERB 0xe88009 +#define MFP_IMRB 0xe88015 + +// Subroutine in asmsub.s +extern void DI(); +extern void EI(); + +volatile short* iocsexec = (short*)0xa0e; // Work when executin IOCS +struct trans_counter trans_counter; // Transfer counter +unsigned char rx_buff[2048]; // Receive buffer +int imr; // Interrupt permission flag +int scsistop; // SCSI stopped flag +int intr_type; // Interrupt type (0:V-DISP 1:TimerA) +int poll_interval; // Polling interval (configure) +int poll_current; // Polling interval (current) +int idle; // Idle counter + +#define POLLING_SLEEP 255 // 4-5s + +/************************************************ + * Execute command to get MAC address * + ************************************************/ +int SCSI_GETMACADDR(unsigned char *mac) +{ + unsigned char cmd[10]; + unsigned char sts; + unsigned char msg; + + if (S_SELECT(scsiid) != 0) { + return 0; + } + + cmd[0] = 0x28; + cmd[1] = 0; + cmd[2] = 1; + cmd[3] = 0; + cmd[4] = 0; + cmd[5] = 0; + cmd[6] = 0; + cmd[7] = 0; + cmd[8] = 6; + cmd[9] = 0; + if (S_CMDOUT(10, cmd) != 0) { + return 0; + } + +#ifdef USE_DMA + if (S_DATAIN(6, mac) != 0) { +#else + if (S_DATAIN_P(6, mac) != 0) { +#endif + return 0; + } + + S_STSIN(&sts); + S_MSGIN(&msg); + + return 1; +} + +/************************************************ + * Execute command to configure MAC address * + ************************************************/ +int SCSI_SETMACADDR(const unsigned char *mac) +{ + unsigned char cmd[10]; + unsigned char sts; + unsigned char msg; + + if (S_SELECT(scsiid) != 0) { + return 0; + } + + cmd[0] = 0x2a; + cmd[1] = 0; + cmd[2] = 1; + cmd[3] = 0; + cmd[4] = 0; + cmd[5] = 0; + cmd[6] = 0; + cmd[7] = 0; + cmd[8] = 6; + cmd[9] = 0; + if (S_CMDOUT(10, cmd) != 0) { + return 0; + } + +#ifdef USE_DMA + S_DATAOUT(6, mac); +#else + S_DATAOUT_P(6, mac); +#endif + + S_STSIN(&sts); + S_MSGIN(&msg); + + return 1; +} + +/************************************************ + * Execute command to get received packet size* + ************************************************/ +int SCSI_GETPACKETLEN(int *len) +{ + unsigned char cmd[10]; + unsigned char buf[2]; + unsigned char sts; + unsigned char msg; + + if (S_SELECT(scsiid) != 0) { + return 0; + } + + cmd[0] = 0x28; + cmd[1] = 0; + cmd[2] = 1; + cmd[3] = 1; + cmd[4] = 0; + cmd[5] = 0; + cmd[6] = 0; + cmd[7] = 0; + cmd[8] = 2; + cmd[9] = 0; + if (S_CMDOUT(10, cmd) != 0) { + return 0; + } + +#ifdef USE_DMA + if (S_DATAIN(2, buf) != 0) { +#else + if (S_DATAIN_P(2, buf) != 0) { +#endif + return 0; + } + + S_STSIN(&sts); + S_MSGIN(&msg); + + *len = (int)(buf[0] << 8) + (int)(buf[1]); + + return 1; +} + +/************************************************ + * Execute receive packet command * + ************************************************/ +int SCSI_GETPACKETBUF(unsigned char *buf, int len) +{ + unsigned char cmd[10]; + unsigned char sts; + unsigned char msg; + + if (S_SELECT(scsiid) != 0) { + return 0; + } + + cmd[0] = 0x28; + cmd[1] = 0; + cmd[2] = 1; + cmd[3] = 1; + cmd[4] = 0; + cmd[5] = 0; + cmd[6] = (unsigned char)(len >> 16); + cmd[7] = (unsigned char)(len >> 8); + cmd[8] = (unsigned char)len; + cmd[9] = 1; + if (S_CMDOUT(10, cmd) != 0) { + return 0; + } + +#ifdef USE_DMA + if (S_DATAIN(len, buf) != 0) { +#else + if (S_DATAIN_P(len, buf) != 0) { +#endif + return 0; + } + + S_STSIN(&sts); + S_MSGIN(&msg); + + return 1; +} + +/************************************************ + * Execute packet send command * + ************************************************/ +int SCSI_SENDPACKET(const unsigned char *buf, int len) +{ + unsigned char cmd[10]; + unsigned char sts; + unsigned char msg; + + if (S_SELECT(scsiid) != 0) { + return 0; + } + + cmd[0] = 0x2a; + cmd[1] = 0; + cmd[2] = 1; + cmd[3] = 1; + cmd[4] = 0; + cmd[5] = 0; + cmd[6] = (unsigned char)(len >> 16); + cmd[7] = (unsigned char)(len >> 8); + cmd[8] = (unsigned char)len; + cmd[9] = 0; + if (S_CMDOUT(10, cmd) != 0) { + return 0; + } + +#ifdef USE_DMA + S_DATAOUT(len, buf); +#else + S_DATAOUT_P(len, buf); +#endif + S_STSIN(&sts); + S_MSGIN(&msg); + + return 1; +} + +/************************************************ + * Get MAC address * + ************************************************/ +int GetMacAddr(struct eaddr* buf) +{ + if (SCSI_GETMACADDR(buf->eaddr) != 0) { + return 0; + } else { + return -1; + } +} + +/************************************************ + * Set MAC address * + ************************************************/ +int SetMacAddr(const struct eaddr* data) +{ + if (SCSI_SETMACADDR(data->eaddr) != 0) { + return 0; + } else { + return -1; + } +} + +/************************************************ + * Search RaSCSI * + ************************************************/ +int SearchRaSCSI() +{ + int i; + INQUIRYOPT_T inq; + + for (i = 0; i <= 7; i++) { + // Search for BRIDGE device + if (S_INQUIRY(sizeof(INQUIRY_T) , i, (struct INQUIRY*)&inq) < 0) { + continue; + } + + if (memcmp(&(inq.info.ProductID), "RASCSI BRIDGE", 13) != 0) { + continue; + } + + // Get TAP initialization status + if (S_INQUIRY(sizeof(INQUIRYOPT_T) , i, (struct INQUIRY*)&inq) < 0) { + continue; + } + + if (inq.options[1] != '1') { + continue; + } + + // Configure SCSI ID + scsiid = i; + return 0; + } + + return -1; +} + +/************************************************ + * Init RaSCSI method * + ************************************************/ +int InitRaSCSI(void) +{ +#ifdef MULTICAST + unsigned char multicast_table[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + imr = 0; + scsistop = 0; + poll_current = -1; + idle = 0; + + return 0; +} + +/************************************************ + * RaSCSI interrupt handler function (polling) * + ************************************************/ +void interrupt IntProcess(void) +{ + unsigned char phase; + unsigned int len; + int type; + int_handler func; + int i; + + // V-DISP GPIP interrupt idle count control + if (intr_type == 0) { + // Increment idle + idle++; + + // Skip if not yet next scheduled processing + if (idle < poll_current) { + return; + } + + // Clear idle counter + idle = 0; + } + + // Start interrupt + + // Only when interrupt is permitted + if (imr == 0) { + return; + } + + // Skip if executing IOCS + if (*iocsexec != -1) { + return; + } + + // Interrupt forbidden if receiving data + DI (); + + // Only in bus free phase + phase = S_PHASE(); + if (phase != 0) { + // Exit + goto ei_exit; + } + + // Receive data + if (SCSI_GETPACKETLEN(&len) == 0) { + // RaSCSI is stopped + scsistop = 1; + + // Reset polling interval (sleep) + UpdateIntProcess(POLLING_SLEEP); + + // Exit + goto ei_exit; + } + + // RaSCSI is stopped + if (scsistop) { + scsistop = 0; + + // Reset polling interval (hurry) + UpdateIntProcess(poll_interval); + } + + // Packets did not arrive + if (len == 0) { + // Exit + goto ei_exit; + } + + // Tranfer packets to receive buffer memory + if (SCSI_GETPACKETBUF(rx_buff, len) == 0) { + // Fail + goto ei_exit; + } + + // Interrupt permitted + EI (); + + // Split data by packet type + type = rx_buff[12] * 256 + rx_buff[13]; + i = 0; + while ((func = SearchList2(type, i))) { + i++; + func(len, (void*)&rx_buff, ID_EN0); + } + trans_counter.recv_byte += len; + return; + +ei_exit: + // Interrupt permitted + EI (); +} + +/************************************************ + * RaSCSI Send Packets Function (from ne.s) * + ************************************************/ +int SendPacket(int len, const unsigned char* data) +{ + if (len < 1) { + return 0; + } + + if (len > 1514) { // 6 + 6 + 2 + 1500 + return -1; // Error + } + + // If RaSCSI seems to be stopped, throw an error + if (scsistop) { + return -1; + } + + // Interrupt is not permitted during sending + DI (); + + // Send processing and raise send flag + if (SCSI_SENDPACKET(data, len) == 0) { + // Interrupt permitted + EI (); + return -1; + } + + // Interrupt permitted + EI (); + + // Finished requesting send + trans_counter.send_byte += len; + + return 0; +} + +/************************************************ + * RaSCSI Interrupt Permission Setting Function* + ************************************************/ +int SetPacketReception(int i) +{ + imr = i; + return 0; +} + +/************************************************ + * RaSCSI Interrupt Processing Registration * + ************************************************/ +void RegisterIntProcess(int n) +{ + volatile unsigned char *p; + + // Update polling interval (current) and clear idle counter + poll_current = n; + idle = 0; + + if (intr_type == 0) { + // Overwrite V-DISP GPIP interrupt vectors + // and enable interrupt + B_INTVCS(0x46, (int)IntProcess); + p = (unsigned char *)MFP_AEB; + *p = *p | 0x10; + p = (unsigned char *)MFP_IERB; + *p = *p | 0x40; + p = (unsigned char *)MFP_IMRB; + *p = *p | 0x40; + } else if (intr_type == 1) { + // Set TimerA counter mode + VDISPST(NULL, 0, 0); + VDISPST(IntProcess, 0, poll_current); + } +} + +/************************************************ + * RaSCSI Interrupt Processing Update * + ************************************************/ +void UpdateIntProcess(int n) +{ + // Do not update if polling interval (current) is the same + if (n == poll_current) { + return; + } + + // Update polling interval (current) and clear idle counter + poll_current = n; + idle = 0; + + if (intr_type == 1) { + // TimerA requires re-registering + VDISPST(NULL, 0, 0); + VDISPST(IntProcess, 0, poll_current); + } +} + +// EOF diff --git a/src_old/x68k/RASETHER/scsictl.h b/src_old/x68k/RASETHER/scsictl.h new file mode 100644 index 00000000..a13ea178 --- /dev/null +++ b/src_old/x68k/RASETHER/scsictl.h @@ -0,0 +1,40 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Powered by XM6 TypeG Technology. +// Copyright (C) 2016-2017 GIMONS +// [ RaSCSI Ethernet SCSI Control Department ] +// +// Based on +// Neptune-X board driver for Human-68k(ESP-X) version 0.03 +// Programed 1996-7 by Shi-MAD. +// Special thanks to Niggle, FIRST, yamapu ... +// +//--------------------------------------------------------------------------- + +#ifndef scsictl_h +#define scsictl_h + +// Global variables +extern int intr_type; +extern int poll_interval; + +// Transfer counter +struct trans_counter { + unsigned int send_byte; + unsigned int recv_byte; +}; +extern struct trans_counter trans_counter; + +extern int SearchRaSCSI(); +extern int InitRaSCSI(void); +extern int GetMacAddr(struct eaddr* buf); +extern int SetMacAddr(const struct eaddr* data); +extern int SetPacketReception(int i); +extern int SendPacket(int len, const unsigned char* data); +extern void RegisterIntProcess(int n); +extern void UpdateIntProcess(int n); + +#endif // scsictl_h