pre-merge

This commit is contained in:
Tony Kuker 2023-01-08 20:04:49 -06:00
parent 103de2912d
commit 8443e08114
124 changed files with 34412 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,575 @@
//---------------------------------------------------------------------------
//
// X68000 EMULATOR "XM6"
//
// Copyright (C) 2001-2006 (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;
}
}

View File

@ -0,0 +1,90 @@
//---------------------------------------------------------------------------
//
// X68000 EMULATOR "XM6"
//
// Copyright (C) 2001-2006 (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
};

View File

@ -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 <sys/mman.h>
#include <errno.h>
#include <unistd.h>
//===========================================================================
//
// 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;
}

View File

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

View File

@ -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 <sys/mman.h>
#include <errno.h>
#include <unistd.h>
//===========================================================================
//
// 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;
}

View File

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

22
src_old/raspberrypi/.gitignore vendored Normal file
View File

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

View File

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

View File

@ -0,0 +1,17 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// for Raspberry Pi
//
// Copyright (C) 2021 Uwe Seimet
//
//---------------------------------------------------------------------------
#pragma once
#include <string>
struct CommandContext {
int fd;
std::string locale;
};

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,204 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// for Raspberry Pi
//
// Copyright (C) 2001-2006 (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
};

View File

@ -0,0 +1,907 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// for Raspberry Pi
//
// Copyright (C) 2001-2006 (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<PrimaryDevice *>(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;
}

View File

@ -0,0 +1,100 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// for Raspberry Pi
//
// Copyright (C) 2001-2006 (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 <map>
//===========================================================================
//
// 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;
};

File diff suppressed because it is too large Load Diff

View File

@ -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
// Bit2430 Duplicate file identification mark 0:Automatic 1127: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
};

View File

@ -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 <unistd.h>
#include <arpa/inet.h>
#ifdef __linux__
#include <net/if.h>
#include <sys/ioctl.h>
#include <linux/sockios.h>
#endif
#include "os.h"
#include "ctapdriver.h"
#include "log.h"
#include "rasutil.h"
#include "exceptions.h"
#include <sstream>
#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<string, string>& const_params)
{
map<string, string> 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);
}

View File

@ -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 <pcap/pcap.h>
#include "filepath.h"
#include <map>
#include <vector>
#include <string>
#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<string, string>&);
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<string> interfaces;
string inet;
};

View File

@ -0,0 +1,179 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// for Raspberry Pi
//
// Copyright (C) 2021 Uwe Seimet
//
//---------------------------------------------------------------------------
#include <cassert>
#include "rascsi_version.h"
#include "os.h"
#include "log.h"
#include "exceptions.h"
#include "device.h"
set<Device *> 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<string, string>& 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;
}

View File

@ -0,0 +1,187 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// for Raspberry Pi
//
// Copyright (C) 2021 Uwe Seimet
//
//---------------------------------------------------------------------------
#pragma once
#include <set>
#include <map>
#include <string>
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<string, string> params;
// The default parameters
map<string, string> default_params;
// Sense Key, ASC and ASCQ
int status_code;
protected:
static set<Device *> 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<string, string>&) { 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<string, string> GetParams() const { return params; }
const string GetParam(const string&);
void SetParams(const map<string, string>&);
const map<string, string> GetDefaultParams() const { return default_params; }
void SetDefaultParams(const map<string, string>& 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"; }
};

View File

@ -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 <ifaddrs.h>
#include <set>
#include <map>
#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<uint32_t>& DeviceFactory::GetSectorSizes(const string& type)
{
PbDeviceType t = UNDEFINED;
PbDeviceType_Parse(type, &t);
return sector_sizes[t];
}
const set<uint64_t> DeviceFactory::GetCapacities(PbDeviceType type)
{
set<uint64_t> keys;
for (const auto& geometry : geometries[type]) {
keys.insert(geometry.first);
}
return keys;
}
const list<string> DeviceFactory::GetNetworkInterfaces() const
{
list<string> 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;
}

View File

@ -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 <set>
#include <list>
#include <map>
#include <string>
#include "rascsi_interface.pb.h"
using namespace std;
using namespace rascsi_interface;
typedef pair<uint32_t, uint32_t> Geometry;
class Device;
class DeviceFactory
{
private:
DeviceFactory();
~DeviceFactory() {};
public:
static DeviceFactory& instance();
Device *CreateDevice(PbDeviceType, const string&);
PbDeviceType GetTypeForFile(const string&);
const set<uint32_t>& GetSectorSizes(PbDeviceType type) { return sector_sizes[type]; }
const set<uint32_t>& GetSectorSizes(const string&);
const set<uint64_t> GetCapacities(PbDeviceType);
const map<string, string>& GetDefaultParams(PbDeviceType type) { return default_params[type]; }
const list<string> GetNetworkInterfaces() const;
const map<string, PbDeviceType> GetExtensionMapping() const { return extension_mapping; }
private:
map<PbDeviceType, set<uint32_t>> sector_sizes;
// Optional mapping of drive capacities to drive geometries
map<PbDeviceType, map<uint64_t, Geometry>> geometries;
map<PbDeviceType, map<string, string>> default_params;
map<string, PbDeviceType> extension_mapping;
string GetExtension(const string&) const;
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,159 @@
//---------------------------------------------------------------------------
//
// X68000 EMULATOR "XM6"
//
// Copyright (C) 2001-2006 (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 <string>
#include <set>
#include <map>
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<uint32_t> 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<uint64_t, Geometry> 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<Disk, SASIDEV> 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<uint32_t> GetSectorSizes() const;
void SetSectorSizes(const set<uint32_t>&);
uint32_t GetConfiguredSectorSize() const;
bool SetConfiguredSectorSize(uint32_t);
void SetGeometries(const map<uint64_t, Geometry>&);
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, vector<BYTE>>&, int, bool) const override;
virtual void AddErrorPage(map<int, vector<BYTE>>&, bool) const;
virtual void AddFormatPage(map<int, vector<BYTE>>&, bool) const;
virtual void AddDrivePage(map<int, vector<BYTE>>&, bool) const;
void AddCachePage(map<int, vector<BYTE>>&, bool) const;
virtual void AddVendorPage(map<int, vector<BYTE>>&, 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);
};

View File

@ -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 <map>
class SASIDEV;
class SCSIDEV;
using namespace std;
using namespace scsi_defs;
template<class T, class U>
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<scsi_command, command_t*> 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<scsi_command>(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;
}
};

View File

@ -0,0 +1,43 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// for Raspberry Pi
//
// Copyright (C) 2021 Uwe Seimet
//
//---------------------------------------------------------------------------
#include <map>
#include <string>
#include "file_support.h"
using namespace std;
map<string, id_set> 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();
}

View File

@ -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 <map>
#include <string>
#include "filepath.h"
using namespace std;
typedef pair<int, int> id_set;
class FileSupport
{
private:
Filepath diskpath;
// The list of image files in use and the IDs and LUNs using these files
static map<string, id_set> reserved_files;
public:
FileSupport() {};
virtual ~FileSupport() {};
void GetPath(Filepath& path) const { path = diskpath; }
void SetPath(const Filepath& path) { diskpath = path; }
static const map<string, id_set> GetReservedFiles(){ return reserved_files; }
static void SetReservedFiles(const map<string, id_set>& 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;
};

View File

@ -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<Disk *>(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<int, vector<BYTE>>& pages, int page, bool changeable) const
{
if (page == 0x20 || page == 0x3f) {
AddRealtimeClockPage(pages, changeable);
}
}
void HostServices::AddRealtimeClockPage(map<int, vector<BYTE>>& pages, bool changeable) const
{
vector<BYTE> 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;
}

View File

@ -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 <vector>
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<HostServices, SCSIDEV> dispatcher;
void AddModePages(map<int, vector<BYTE>>&, int, bool) const override;
void AddRealtimeClockPage(map<int, vector<BYTE>>&, bool) const;
};

View File

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

View File

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

View File

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

View File

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

View File

@ -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<int, vector<BYTE>> 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<BYTE> 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);
}

View File

@ -0,0 +1,53 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
//
//---------------------------------------------------------------------------
#pragma once
#include "primary_device.h"
#include <string>
#include <vector>
#include <map>
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, vector<BYTE>>&, int, bool) const = 0;
private:
typedef PrimaryDevice super;
Dispatcher<ModePageDevice, SASIDEV> dispatcher;
void ModeSense6(SASIDEV *);
void ModeSense10(SASIDEV *);
void ModeSelect6(SASIDEV *);
void ModeSelect10(SASIDEV *);
int ModeSelectCheck(int);
int ModeSelectCheck6();
int ModeSelectCheck10();
};

View File

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

View File

@ -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 <string>
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<PrimaryDevice, SASIDEV> dispatcher;
void Inquiry(SASIDEV *);
void ReportLuns(SASIDEV *);
};

View File

@ -0,0 +1,107 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// for Raspberry Pi
//
// Copyright (C) 2001-2006 (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<uint32_t>& 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;
}

View File

@ -0,0 +1,39 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// for Raspberry Pi
//
// Copyright (C) 2001-2006 (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<uint32_t>&);
~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;
};

View File

@ -0,0 +1,593 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// for Raspberry Pi
//
// Copyright (C) 2020 akuker
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) 2001-2006 (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<string, string>& 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;
}

View File

@ -0,0 +1,143 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// for Raspberry Pi
//
// Copyright (C) 2020 akuker
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) 2001-2006 (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 <map>
#include <string>
//===========================================================================
//
// DaynaPort SCSI Link
//
//===========================================================================
class SCSIDaynaPort: public Disk
{
public:
SCSIDaynaPort();
~SCSIDaynaPort();
bool Init(const map<string, string>&) 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<SCSIDaynaPort, SASIDEV> 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];
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,107 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// for Raspberry Pi
//
// Copyright (C) 2001-2006 (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 <string>
//===========================================================================
//
// SCSI Host Bridge
//
//===========================================================================
class CTapDriver;
class CFileSys;
class SCSIBR : public Disk
{
public:
SCSIBR();
~SCSIBR();
bool Init(const map<string, string>&) 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<SCSIBR, SASIDEV> 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
};

View File

@ -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 <sys/stat.h>
#include "controllers/scsidev_ctrl.h"
#include "../rasutil.h"
#include "scsi_printer.h"
#include <string>
#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<string, string>& 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);
}
}

View File

@ -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 <string>
#include <map>
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<string, string>&);
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<SCSIPrinter, SCSIDEV> dispatcher;
char filename[sizeof(TMP_FILE_PATTERN) + 1];
int fd;
int reserving_initiator;
time_t reservation_time;
int timeout;
};

View File

@ -0,0 +1,738 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// for Raspberry Pi
//
// Copyright (C) 2001-2006 (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<uint32_t>& 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<int, vector<BYTE>>& 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<int, vector<BYTE>>& pages, bool changeable) const
{
vector<BYTE> 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<int, vector<BYTE>>& pages, bool) const
{
vector<BYTE> 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;
}

View File

@ -0,0 +1,124 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// for Raspberry Pi
//
// Copyright (C) 2001-2006 (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<uint32_t>&);
~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, vector<BYTE>>&, int, bool) const override;
private:
typedef Disk super;
Dispatcher<SCSICD, SASIDEV> dispatcher;
void AddCDROMPage(map<int, vector<BYTE>>&, bool) const;
void AddCDDAPage(map<int, vector<BYTE>>&, 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
};

View File

@ -0,0 +1,228 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// for Raspberry Pi
//
// Copyright (C) 2001-2006 (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 <sstream>
#define DEFAULT_PRODUCT "SCSI HD"
//===========================================================================
//
// SCSI Hard Disk
//
//===========================================================================
SCSIHD::SCSIHD(const set<uint32_t>& 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<length; i++)
{
printf("%02X ", buf[i]);
}
printf("\n");
break;
// Other page
default:
printf("Unknown Mode Select page code received: %02X\n",page);
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;
}
//---------------------------------------------------------------------------
//
// Add Vendor special page to make drive Apple compatible
//
//---------------------------------------------------------------------------
void SCSIHD::AddVendorPage(map<int, vector<BYTE>>& pages, int page, bool changeable) const
{
// Page code 48
if (page != 0x30 && page != 0x3f) {
return;
}
vector<BYTE> buf(30);
// No changeable area
if (!changeable) {
memcpy(&buf.data()[0xa], "APPLE COMPUTER, INC.", 20);
}
pages[48] = buf;
}

View File

@ -0,0 +1,38 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// for Raspberry Pi
//
// Copyright (C) 2001-2006 (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<uint32_t>&, 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, vector<BYTE>>&, int, bool) const override;
};

View File

@ -0,0 +1,214 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// for Raspberry Pi
//
// Copyright (C) 2001-2006 (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<uint32_t>& 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<int, vector<BYTE>>& pages, bool) const
{
vector<BYTE> 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<int, vector<BYTE>>& pages, bool changeable) const
{
vector<BYTE> 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<int, vector<BYTE>>& pages, bool changeable) const
{
vector<BYTE> 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;
}

View File

@ -0,0 +1,45 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// for Raspberry Pi
//
// Copyright (C) 2001-2006 (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<uint32_t>&);
~SCSIHD_NEC() {}
void Open(const Filepath& path) override;
// Commands
int Inquiry(const DWORD *cdb, BYTE *buf) override;
void AddErrorPage(map<int, vector<BYTE>>&, bool) const override;
void AddFormatPage(map<int, vector<BYTE>>&, bool) const override;
void AddDrivePage(map<int, vector<BYTE>>&, bool) const override;
private:
// Geometry data
int cylinders;
int heads;
int sectors;
};

View File

@ -0,0 +1,289 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// for Raspberry Pi
//
// Copyright (C) 2001-2006 (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<uint32_t>& sector_sizes, const map<uint64_t, Geometry>& 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<int, vector<BYTE>>& 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<int, vector<BYTE>>& pages, bool) const
{
vector<BYTE> 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<int, vector<BYTE>>& pages, int page, bool changeable) const
{
// Page code 20h
if (page != 0x20 && page != 0x3f) {
return;
}
vector<BYTE> 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;
}

View File

@ -0,0 +1,44 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// for Raspberry Pi
//
// Copyright (C) 2001-2006 (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<uint32_t>&, const map<uint64_t, Geometry>&);
~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, vector<BYTE>>&, int, bool) const override;
void AddVendorPage(map<int, vector<BYTE>>&, int, bool) const override;
private:
void AddOptionPage(map<int, vector<BYTE>>&, bool) const;
};

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,575 @@
//---------------------------------------------------------------------------
//
// X68000 EMULATOR "XM6"
//
// Copyright (C) 2001-2006 (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;
}
}

View File

@ -0,0 +1,90 @@
//---------------------------------------------------------------------------
//
// X68000 EMULATOR "XM6"
//
// Copyright (C) 2001-2006 (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
};

View File

@ -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 <sys/mman.h>
#include <errno.h>
#include <unistd.h>
//===========================================================================
//
// 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;
}

View File

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

View File

@ -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 <sys/mman.h>
#include <errno.h>
#include <unistd.h>
//===========================================================================
//
// 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;
}

View File

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

View File

@ -0,0 +1,49 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// for Raspberry Pi
//
// Copyright (C) 2021 Uwe Seimet
//
// Various exceptions
//
//---------------------------------------------------------------------------
#pragma once
#include <exception>
#include <string>
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() {}
};

View File

@ -0,0 +1,241 @@
//---------------------------------------------------------------------------
//
// X68000 EMULATOR "XM6"
//
// Copyright (C) 2001-2006 (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;
}
}

View File

@ -0,0 +1,69 @@
//---------------------------------------------------------------------------
//
// X68000 EMULATOR "XM6"
//
// Copyright (C) 2001-2005 (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

View File

@ -0,0 +1,140 @@
//---------------------------------------------------------------------------
//
// X68000 EMULATOR "XM6"
//
// Copyright (C) 2001-2006 (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];

View File

@ -0,0 +1,52 @@
//---------------------------------------------------------------------------
//
// X68000 EMULATOR "XM6"
//
// Copyright (C) 2001-2006 (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)
};

File diff suppressed because it is too large Load Diff

View File

@ -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_DT0PIN_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<<PIN_DT0)|\
(1<<PIN_DT1)|\
(1<<PIN_DT2)|\
(1<<PIN_DT3)|\
(1<<PIN_DT4)|\
(1<<PIN_DT5)|\
(1<<PIN_DT6)|\
(1<<PIN_DT7)|\
(1<<PIN_DP)|\
(1<<PIN_ATN)|\
(1<<PIN_RST)|\
(1<<PIN_ACK)|\
(1<<PIN_REQ)|\
(1<<PIN_MSG)|\
(1<<PIN_CD)|\
(1<<PIN_IO)|\
(1<<PIN_BSY)|\
(1<<PIN_SEL))
//---------------------------------------------------------------------------
//
// Constant declarations (GPIO)
//
//---------------------------------------------------------------------------
#define SYST_OFFSET 0x00003000
#define IRPT_OFFSET 0x0000B200
#define ARMT_OFFSET 0x0000B400
#define PADS_OFFSET 0x00100000
#define GPIO_OFFSET 0x00200000
#define QA7_OFFSET 0x01000000
#define GPIO_INPUT 0
#define GPIO_OUTPUT 1
#define GPIO_PULLNONE 0
#define GPIO_PULLDOWN 1
#define GPIO_PULLUP 2
#define GPIO_FSEL_0 0
#define GPIO_FSEL_1 1
#define GPIO_FSEL_2 2
#define GPIO_FSEL_3 3
#define GPIO_SET_0 7
#define GPIO_CLR_0 10
#define GPIO_LEV_0 13
#define GPIO_EDS_0 16
#define GPIO_REN_0 19
#define GPIO_FEN_0 22
#define GPIO_HEN_0 25
#define GPIO_LEN_0 28
#define GPIO_AREN_0 31
#define GPIO_AFEN_0 34
#define GPIO_PUD 37
#define GPIO_CLK_0 38
#define GPIO_GPPINMUXSD 52
#define GPIO_PUPPDN0 57
#define GPIO_PUPPDN1 58
#define GPIO_PUPPDN3 59
#define GPIO_PUPPDN4 60
#define PAD_0_27 11
#define SYST_CS 0
#define SYST_CLO 1
#define SYST_CHI 2
#define SYST_C0 3
#define SYST_C1 4
#define SYST_C2 5
#define SYST_C3 6
#define ARMT_LOAD 0
#define ARMT_VALUE 1
#define ARMT_CTRL 2
#define ARMT_CLRIRQ 3
#define ARMT_RAWIRQ 4
#define ARMT_MSKIRQ 5
#define ARMT_RELOAD 6
#define ARMT_PREDIV 7
#define ARMT_FREERUN 8
#define IRPT_PND_IRQ_B 0
#define IRPT_PND_IRQ_1 1
#define IRPT_PND_IRQ_2 2
#define IRPT_FIQ_CNTL 3
#define IRPT_ENB_IRQ_1 4
#define IRPT_ENB_IRQ_2 5
#define IRPT_ENB_IRQ_B 6
#define IRPT_DIS_IRQ_1 7
#define IRPT_DIS_IRQ_2 8
#define IRPT_DIS_IRQ_B 9
#define QA7_CORE0_TINTC 16
#define GPIO_IRQ (32 + 20) // GPIO3
#define GPIO_INEDGE ((1 << PIN_BSY) | \
(1 << PIN_SEL) | \
(1 << PIN_ATN) | \
(1 << PIN_ACK) | \
(1 << PIN_RST))
#define GPIO_MCI ((1 << PIN_MSG) | \
(1 << PIN_CD) | \
(1 << PIN_IO))
//---------------------------------------------------------------------------
//
// Constant declarations (GIC)
//
//---------------------------------------------------------------------------
#define ARM_GICD_BASE 0xFF841000
#define ARM_GICC_BASE 0xFF842000
#define ARM_GIC_END 0xFF847FFF
#define GICD_CTLR 0x000
#define GICD_IGROUPR0 0x020
#define GICD_ISENABLER0 0x040
#define GICD_ICENABLER0 0x060
#define GICD_ISPENDR0 0x080
#define GICD_ICPENDR0 0x0A0
#define GICD_ISACTIVER0 0x0C0
#define GICD_ICACTIVER0 0x0E0
#define GICD_IPRIORITYR0 0x100
#define GICD_ITARGETSR0 0x200
#define GICD_ICFGR0 0x300
#define GICD_SGIR 0x3C0
#define GICC_CTLR 0x000
#define GICC_PMR 0x001
#define GICC_IAR 0x003
#define GICC_EOIR 0x004
//---------------------------------------------------------------------------
//
// Constant declarations (GIC IRQ)
//
//---------------------------------------------------------------------------
#define GIC_IRQLOCAL0 (16 + 14)
#define GIC_GPIO_IRQ (32 + 116) // GPIO3
//---------------------------------------------------------------------------
//
// Constant declarations (Control signals)
//
//---------------------------------------------------------------------------
#define ACT_OFF !ACT_ON
#define ENB_OFF !ENB_ON
#define TAD_OUT !TAD_IN
#define IND_OUT !IND_IN
#define DTD_OUT !DTD_IN
//---------------------------------------------------------------------------
//
// Constant declarations (SCSI)
//
//---------------------------------------------------------------------------
#define IN GPIO_INPUT
#define OUT GPIO_OUTPUT
#define ON TRUE
#define OFF FALSE
//---------------------------------------------------------------------------
//
// Constant declarations (bus control timing)
//
//---------------------------------------------------------------------------
// SCSI Bus timings taken from:
// https://www.staff.uni-mainz.de/tacke/scsi/SCSI2-05.html
#define SCSI_DELAY_ARBITRATION_DELAY_NS 2400
#define SCSI_DELAY_ASSERTION_PERIOD_NS 90
#define SCSI_DELAY_BUS_CLEAR_DELAY_NS 800
#define SCSI_DELAY_BUS_FREE_DELAY_NS 800
#define SCSI_DELAY_BUS_SET_DELAY_NS 1800
#define SCSI_DELAY_BUS_SETTLE_DELAY_NS 400
#define SCSI_DELAY_CABLE_SKEW_DELAY_NS 10
#define SCSI_DELAY_DATA_RELEASE_DELAY_NS 400
#define SCSI_DELAY_DESKEW_DELAY_NS 45
#define SCSI_DELAY_DISCONNECTION_DELAY_US 200
#define SCSI_DELAY_HOLD_TIME_NS 45
#define SCSI_DELAY_NEGATION_PERIOD_NS 90
#define SCSI_DELAY_POWER_ON_TO_SELECTION_TIME_S 10 // (recommended)
#define SCSI_DELAY_RESET_TO_SELECTION_TIME_US (250*1000) // (recommended)
#define SCSI_DELAY_RESET_HOLD_TIME_US 25
#define SCSI_DELAY_SELECTION_ABORT_TIME_US 200
#define SCSI_DELAY_SELECTION_TIMEOUT_DELAY_NS (250*1000) // (recommended)
#define SCSI_DELAY_FAST_ASSERTION_PERIOD_NS 30
#define SCSI_DELAY_FAST_CABLE_SKEW_DELAY_NS 5
#define SCSI_DELAY_FAST_DESKEW_DELAY_NS 20
#define SCSI_DELAY_FAST_HOLD_TIME_NS 10
#define SCSI_DELAY_FAST_NEGATION_PERIOD_NS 30
// The DaynaPort SCSI Link do a short delay in the middle of transfering
// a packet. This is the number of uS that will be delayed between the
// header and the actual data.
#define SCSI_DELAY_SEND_DATA_DAYNAPORT_US 100
//---------------------------------------------------------------------------
//
// Class definition
//
//---------------------------------------------------------------------------
class GPIOBUS : public BUS
{
public:
// Basic Functions
GPIOBUS();
// Constructor
virtual ~GPIOBUS();
// Destructor
BOOL Init(mode_e mode = TARGET);
// Initialization
void Reset();
// Reset
void Cleanup();
// Cleanup
//---------------------------------------------------------------------------
//
// Bus signal acquisition
//
//---------------------------------------------------------------------------
inline DWORD Aquire() override
{
#if defined(__x86_64__) || defined(__X86__)
// Only used for development/debugging purposes. Isn't really applicable
// to any real-world RaSCSI application
return 0;
#else
signals = *level;
#if SIGNAL_CONTROL_MODE < 2
// Invert if negative logic (internal processing is unified to positive logic)
signals = ~signals;
#endif // SIGNAL_CONTROL_MODE
return signals;
#endif // ifdef __x86_64__ || __X86__
}
void SetENB(BOOL ast);
// Set ENB signal
bool GetBSY() override;
// Get BSY signal
void SetBSY(bool ast) override;
// Set BSY signal
BOOL GetSEL() override;
// Get SEL signal
void SetSEL(BOOL ast) override;
// Set SEL signal
BOOL GetATN() override;
// Get ATN signal
void SetATN(BOOL ast) override;
// Set ATN signal
BOOL GetACK() override;
// Get ACK signal
void SetACK(BOOL ast) override;
// Set ACK signal
BOOL GetACT();
// Get ACT signal
void SetACT(BOOL ast);
// Set ACT signal
BOOL GetRST() override;
// Get RST signal
void SetRST(BOOL ast) override;
// Set RST signal
BOOL GetMSG() override;
// Get MSG signal
void SetMSG(BOOL ast) override;
// Set MSG signal
BOOL GetCD() override;
// Get CD signal
void SetCD(BOOL ast) override;
// Set CD signal
BOOL GetIO() override;
// Get IO signal
void SetIO(BOOL ast) override;
// Set IO signal
BOOL GetREQ() override;
// Get REQ signal
void SetREQ(BOOL ast) override;
// Set REQ signal
BYTE GetDAT() override;
// Get DAT signal
void SetDAT(BYTE dat) override;
// Set DAT signal
BOOL GetDP() override;
// Get Data parity signal
int CommandHandShake(BYTE *buf) override;
// Command receive handshake
int ReceiveHandShake(BYTE *buf, int count) override;
// Data receive handshake
int SendHandShake(BYTE *buf, int count, int delay_after_bytes) override;
// Data transmission handshake
static BUS::phase_t GetPhaseRaw(DWORD raw_data);
// Get the phase based on raw data
static int GetCommandByteCount(BYTE opcode);
#ifdef USE_SEL_EVENT_ENABLE
// SEL signal interrupt
int PollSelectEvent();
// SEL signal event polling
void ClearSelectEvent();
// Clear SEL signal event
#endif // USE_SEL_EVENT_ENABLE
private:
// SCSI I/O signal control
void MakeTable();
// Create work data
void SetControl(int pin, BOOL ast);
// Set Control Signal
void SetMode(int pin, int mode);
// Set SCSI I/O mode
BOOL GetSignal(int pin);
// Get SCSI input signal value
void SetSignal(int pin, BOOL ast);
// Set SCSI output signal value
BOOL WaitSignal(int pin, BOOL ast);
// Wait for a signal to change
// Interrupt control
void DisableIRQ();
// IRQ Disabled
void EnableIRQ();
// IRQ Enabled
// GPIO pin functionality settings
void PinConfig(int pin, int mode);
// GPIO pin direction setting
void PullConfig(int pin, int mode);
// GPIO pin pull up/down resistor setting
void PinSetSignal(int pin, BOOL ast);
// Set GPIO output signal
void DrvConfig(DWORD drive);
// Set GPIO drive strength
mode_e actmode; // Operation mode
DWORD baseaddr; // Base address
int rpitype; // Type of Raspberry Pi
volatile DWORD *gpio; // GPIO register
volatile DWORD *pads; // PADS register
volatile DWORD *level; // GPIO input level
volatile DWORD *irpctl; // Interrupt control register
volatile DWORD irptenb; // Interrupt enabled state
volatile DWORD *qa7regs; // QA7 register
volatile int tintcore; // Interupt control target CPU.
volatile DWORD tintctl; // Interupt control
volatile DWORD giccpmr; // GICC priority setting
volatile DWORD *gicd; // GIC Interrupt distributor register
volatile DWORD *gicc; // GIC CPU interface register
DWORD gpfsel[4]; // GPFSEL0-4 backup values
DWORD signals; // All bus signals
#ifdef USE_SEL_EVENT_ENABLE
struct gpioevent_request selevreq = {}; // SEL signal event request
int epfd; // epoll file descriptor
#endif // USE_SEL_EVENT_ENABLE
#if SIGNAL_CONTROL_MODE == 0
DWORD tblDatMsk[3][256]; // Data mask table
DWORD tblDatSet[3][256]; // Data setting table
#else
DWORD tblDatMsk[256]; // Data mask table
DWORD tblDatSet[256]; // Table setting table
#endif
static const int SignalTable[19]; // signal table
};
//===========================================================================
//
// System timer
//
//===========================================================================
class SysTimer
{
public:
static void Init(DWORD *syst, DWORD *armt);
// Initialization
static DWORD GetTimerLow();
// Get system timer low byte
static DWORD GetTimerHigh();
// Get system timer high byte
static void SleepNsec(DWORD nsec);
// Sleep for N nanoseconds
static void SleepUsec(DWORD usec);
// Sleep for N microseconds
private:
static volatile DWORD *systaddr;
// System timer address
static volatile DWORD *armtaddr;
// ARM timer address
static volatile DWORD corefreq;
// Core frequency
};
#endif // gpiobus_h

View File

@ -0,0 +1,3 @@
# This is used for debugging. VisualStudio code will call this file when launching
# the debugger, instead of directly calling GDB. That way we can add the pkexec
sudo /usr/bin/gdb "$@"

View File

@ -0,0 +1,172 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// for Raspberry Pi
//
// Copyright (C) 2021 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "localizer.h"
#include <cassert>
#include <string>
#include <map>
#include <algorithm>
#include <regex>
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<LocalizationKey, string> 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;
}

View File

@ -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 <string>
#include <set>
#include <map>
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<string, map<LocalizationKey, string>> localized_messages;
set<string> supported_languages;
};

33
src_old/raspberrypi/log.h Normal file
View File

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

View File

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

View File

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

View File

@ -0,0 +1,205 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// for Raspberry Pi
//
// Powered by XM6 TypeG Technology.
// Copyright (C) 2016-2020 GIMONS
//
//---------------------------------------------------------------------------
#include <stdio.h>
#include <iostream>
#include <fstream>
#include "os.h"
#include "log.h"
#include "sm_reports.h"
#include "rascsi_version.h"
using namespace std;
const static string html_header = R"(
<html>
<head>
<style>
table, th, td {
border: 1px solid black;
border-collapse: collapse;
}
h1 {
text-align: center;
}
h2 {
text-align: center;
}
h3 {
text-align: center;
}
pre {
text-align: center;
}
.collapsible {
background-color: #777;
color: white;
cursor: pointer;
width: 100%;
border: none;
text-align: left;
outline: none;
}
.active, .collapsible:hover {
background-color: #555;
}
.content {
padding: 0;
display: none;
overflow: hidden;
background-color: #f1f1f1;
}
</style>
</head>
)";
static void print_copyright_info(ofstream& html_fp)
{
html_fp << "<table>" << endl \
<< "<h1>RaSCSI scsimon Capture Tool</h1>" << endl \
<< "<pre>Version " << rascsi_get_version_string() \
<< __DATE__ << " " << __TIME__ << endl \
<< "Copyright (C) 2016-2020 GIMONS" << endl \
<< "Copyright (C) 2020-2021 Contributors to the RaSCSI project" << endl \
<< "</pre></table>" << endl \
<< "<br>" << endl;
}
static const string html_footer = R"(
</table>
<script>
var coll = document.getElementsByClassName("collapsible");
var i;
for (i = 0; i < coll.length; i++) {
coll[i].addEventListener("click", function() {
this.classList.toggle("active");
var content = this.nextElementSibling;
if (content.style.display === "block") {
content.style.display = "none";
} else {
content.style.display = "block";
}
});
}
</script>
</html>
)";
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 << "<table>" << 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 << "</code></div>";
}
else if (button_active)
{
html_fp << "</code></button>";
}
html_fp << "</td>";
if (data_space_count < 1)
{
html_fp << "<td>--</td>";
}
else
{
html_fp << "<td>wc: " << std::dec << "(0x" << std::hex << data_space_count << ")</td>";
}
html_fp << "</tr>" << endl;
data_space_count = 0;
}
html_fp << "<tr>";
close_row = true; // Close the row the next time around
html_fp << "<td>" << (double)data->timestamp / 100000 << "</td>";
html_fp << "<td>" << GetPhaseStr(data) << "</td>";
html_fp << "<td>" << std::hex << selected_id << "</td>";
html_fp << "<td>";
}
if (curr_data_valid && !prev_data_valid)
{
if (data_space_count == 0)
{
button_active = true;
html_fp << "<button type=\"button\" class=\"collapsible\"><code>";
}
if ((data_space_count % 16) == 0)
{
html_fp << std::hex << data_space_count << ": ";
}
html_fp << fmt::format("{0:02X}", GetData(data));
data_space_count++;
if ((data_space_count % 4) == 0)
{
html_fp << " ";
}
if (data_space_count == 16)
{
html_fp << "</code></button><div class=\"content\"><code>" << endl;
collapsible_div_active = true;
button_active = false;
}
if (((data_space_count % 16) == 0) && (data_space_count > 17))
{
html_fp << "<br>" << 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();
}

View File

@ -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 <iostream>
#include <fstream>
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, &timestamp_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();
}

View File

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

View File

@ -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 <sstream>
#include <iostream>
#include <fstream>
#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();
}

116
src_old/raspberrypi/os.h Normal file
View File

@ -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 <cstdio>
#include <cstdlib>
#include <cstddef>
#include <cstdarg>
#include <cstring>
#include <csignal>
#include <cassert>
#include <unistd.h>
#include <utime.h>
#include <fcntl.h>
#include <sched.h>
#include <pthread.h>
#include <iconv.h>
#include <libgen.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <poll.h>
#include <dirent.h>
#include <pwd.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <linux/gpio.h>
#include <linux/if.h>
#include <linux/if_tun.h>
//---------------------------------------------------------------------------
//
// 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

View File

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

View File

@ -0,0 +1,2 @@
if $programname == 'RASCSI' then /var/log/rascsi.log
& stop

View File

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

View File

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

View File

@ -0,0 +1,199 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// for Raspberry Pi
//
// Copyright (C) 2021 Uwe Seimet
//
//---------------------------------------------------------------------------
#include <unistd.h>
#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;
}

View File

@ -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 <sstream>
#include <string>
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);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,406 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// for Raspberry Pi
//
// Copyright (C) 2021 Uwe Seimet
//
//---------------------------------------------------------------------------
#include <unistd.h>
#include <sys/sendfile.h>
#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 <string>
#include <filesystem>
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);
}

View File

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

View File

@ -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<int32, PbOperationMetaData> 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<string, PbDeviceType> 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<string, string> 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<string, string> 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<string, string> 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<string, string> 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;
}

View File

@ -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<const FileSupport *>(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<const Disk*>(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<const FileSupport *>(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<int>& 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<Device *>& 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<Device *>& devices,
int unit_count)
{
set<id_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<Device *>& devices, const set<int>& 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;
}

View File

@ -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 <vector>
#include <string>
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<int>&);
void GetDevices(PbServerInfo&, const vector<Device *>&);
void GetDevicesInfo(PbResult&, const PbCommand&, const vector<Device *>&, int);
PbDeviceTypesInfo *GetDeviceTypesInfo(PbResult&, const PbCommand&);
PbVersionInfo *GetVersionInfo(PbResult&);
PbServerInfo *GetServerInfo(PbResult&, const vector<Device *>&, const set<int>&, 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<string> 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);
};

View File

@ -0,0 +1,45 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// for Raspberry Pi
//
// Copyright (C) 2020 akuker
// [ Define the version string ]
//
//---------------------------------------------------------------------------
#include "rascsi_version.h"
#include <cstdio>
// 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;
}

View File

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

View File

@ -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 <clocale>
#include <iostream>
#include <list>
// 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<PbDeviceDefinition> 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);
}

Some files were not shown because too many files have changed in this diff Show More