mirror of https://github.com/akuker/RASCSI.git
pre-merge
This commit is contained in:
parent
103de2912d
commit
8443e08114
|
@ -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;
|
||||||
|
}
|
|
@ -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);
|
||||||
|
};
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
};
|
|
@ -0,0 +1,575 @@
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// X68000 EMULATOR "XM6"
|
||||||
|
//
|
||||||
|
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||||
|
// Copyright (C) 2014-2020 GIMONS
|
||||||
|
//
|
||||||
|
// XM6i
|
||||||
|
// Copyright (C) 2010-2015 isaki@NetBSD.org
|
||||||
|
// Copyright (C) 2010 Y.Sugahara
|
||||||
|
//
|
||||||
|
// Imported sava's Anex86/T98Next image and MO format support patch.
|
||||||
|
// Comments translated to english by akuker.
|
||||||
|
//
|
||||||
|
// [ DiskTrack and DiskCache ]
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#include "os.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "fileio.h"
|
||||||
|
#include "disk_track_cache.h"
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// Disk Track
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
DiskTrack::DiskTrack()
|
||||||
|
{
|
||||||
|
// Initialization of internal information
|
||||||
|
dt.track = 0;
|
||||||
|
dt.size = 0;
|
||||||
|
dt.sectors = 0;
|
||||||
|
dt.raw = FALSE;
|
||||||
|
dt.init = FALSE;
|
||||||
|
dt.changed = FALSE;
|
||||||
|
dt.length = 0;
|
||||||
|
dt.buffer = NULL;
|
||||||
|
dt.maplen = 0;
|
||||||
|
dt.changemap = NULL;
|
||||||
|
dt.imgoffset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
DiskTrack::~DiskTrack()
|
||||||
|
{
|
||||||
|
// Release memory, but do not save automatically
|
||||||
|
if (dt.buffer) {
|
||||||
|
free(dt.buffer);
|
||||||
|
dt.buffer = NULL;
|
||||||
|
}
|
||||||
|
if (dt.changemap) {
|
||||||
|
free(dt.changemap);
|
||||||
|
dt.changemap = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DiskTrack::Init(int track, int size, int sectors, BOOL raw, off_t imgoff)
|
||||||
|
{
|
||||||
|
ASSERT(track >= 0);
|
||||||
|
ASSERT((sectors > 0) && (sectors <= 0x100));
|
||||||
|
ASSERT(imgoff >= 0);
|
||||||
|
|
||||||
|
// Set Parameters
|
||||||
|
dt.track = track;
|
||||||
|
dt.size = size;
|
||||||
|
dt.sectors = sectors;
|
||||||
|
dt.raw = raw;
|
||||||
|
|
||||||
|
// Not initialized (needs to be loaded)
|
||||||
|
dt.init = FALSE;
|
||||||
|
|
||||||
|
// Not Changed
|
||||||
|
dt.changed = FALSE;
|
||||||
|
|
||||||
|
// Offset to actual data
|
||||||
|
dt.imgoffset = imgoff;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DiskTrack::Load(const Filepath& path)
|
||||||
|
{
|
||||||
|
// Not needed if already loaded
|
||||||
|
if (dt.init) {
|
||||||
|
ASSERT(dt.buffer);
|
||||||
|
ASSERT(dt.changemap);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate offset (previous tracks are considered to hold 256 sectors)
|
||||||
|
off_t offset = ((off_t)dt.track << 8);
|
||||||
|
if (dt.raw) {
|
||||||
|
ASSERT(dt.size == 11);
|
||||||
|
offset *= 0x930;
|
||||||
|
offset += 0x10;
|
||||||
|
} else {
|
||||||
|
offset <<= dt.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add offset to real image
|
||||||
|
offset += dt.imgoffset;
|
||||||
|
|
||||||
|
// Calculate length (data size of this track)
|
||||||
|
int length = dt.sectors << dt.size;
|
||||||
|
|
||||||
|
// Allocate buffer memory
|
||||||
|
ASSERT((dt.sectors > 0) && (dt.sectors <= 0x100));
|
||||||
|
|
||||||
|
if (dt.buffer == NULL) {
|
||||||
|
if (posix_memalign((void **)&dt.buffer, 512, ((length + 511) / 512) * 512)) {
|
||||||
|
LOGWARN("%s posix_memalign failed", __PRETTY_FUNCTION__);
|
||||||
|
}
|
||||||
|
dt.length = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dt.buffer) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reallocate if the buffer length is different
|
||||||
|
if (dt.length != (DWORD)length) {
|
||||||
|
free(dt.buffer);
|
||||||
|
if (posix_memalign((void **)&dt.buffer, 512, ((length + 511) / 512) * 512)) {
|
||||||
|
LOGWARN("%s posix_memalign failed", __PRETTY_FUNCTION__);
|
||||||
|
}
|
||||||
|
dt.length = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reserve change map memory
|
||||||
|
if (dt.changemap == NULL) {
|
||||||
|
dt.changemap = (BOOL *)malloc(dt.sectors * sizeof(BOOL));
|
||||||
|
dt.maplen = dt.sectors;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dt.changemap) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reallocate if the buffer length is different
|
||||||
|
if (dt.maplen != (DWORD)dt.sectors) {
|
||||||
|
free(dt.changemap);
|
||||||
|
dt.changemap = (BOOL *)malloc(dt.sectors * sizeof(BOOL));
|
||||||
|
dt.maplen = dt.sectors;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear changemap
|
||||||
|
memset(dt.changemap, 0x00, dt.sectors * sizeof(BOOL));
|
||||||
|
|
||||||
|
// Read from File
|
||||||
|
Fileio fio;
|
||||||
|
if (!fio.OpenDIO(path, Fileio::ReadOnly)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (dt.raw) {
|
||||||
|
// Split Reading
|
||||||
|
for (int i = 0; i < dt.sectors; i++) {
|
||||||
|
// Seek
|
||||||
|
if (!fio.Seek(offset)) {
|
||||||
|
fio.Close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read
|
||||||
|
if (!fio.Read(&dt.buffer[i << dt.size], 1 << dt.size)) {
|
||||||
|
fio.Close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next offset
|
||||||
|
offset += 0x930;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Continuous reading
|
||||||
|
if (!fio.Seek(offset)) {
|
||||||
|
fio.Close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!fio.Read(dt.buffer, length)) {
|
||||||
|
fio.Close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fio.Close();
|
||||||
|
|
||||||
|
// Set a flag and end normally
|
||||||
|
dt.init = TRUE;
|
||||||
|
dt.changed = FALSE;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DiskTrack::Save(const Filepath& path)
|
||||||
|
{
|
||||||
|
// Not needed if not initialized
|
||||||
|
if (!dt.init) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not needed unless changed
|
||||||
|
if (!dt.changed) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to write
|
||||||
|
ASSERT(dt.buffer);
|
||||||
|
ASSERT(dt.changemap);
|
||||||
|
ASSERT((dt.sectors > 0) && (dt.sectors <= 0x100));
|
||||||
|
|
||||||
|
// Writing in RAW mode is not allowed
|
||||||
|
ASSERT(!dt.raw);
|
||||||
|
|
||||||
|
// Calculate offset (previous tracks are considered to hold 256 sectors)
|
||||||
|
off_t offset = ((off_t)dt.track << 8);
|
||||||
|
offset <<= dt.size;
|
||||||
|
|
||||||
|
// Add offset to real image
|
||||||
|
offset += dt.imgoffset;
|
||||||
|
|
||||||
|
// Calculate length per sector
|
||||||
|
int length = 1 << dt.size;
|
||||||
|
|
||||||
|
// Open file
|
||||||
|
Fileio fio;
|
||||||
|
if (!fio.Open(path, Fileio::ReadWrite)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Partial write loop
|
||||||
|
int total;
|
||||||
|
for (int i = 0; i < dt.sectors;) {
|
||||||
|
// If changed
|
||||||
|
if (dt.changemap[i]) {
|
||||||
|
// Initialize write size
|
||||||
|
total = 0;
|
||||||
|
|
||||||
|
// Seek
|
||||||
|
if (!fio.Seek(offset + ((off_t)i << dt.size))) {
|
||||||
|
fio.Close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consectutive sector length
|
||||||
|
int j;
|
||||||
|
for (j = i; j < dt.sectors; j++) {
|
||||||
|
// end when interrupted
|
||||||
|
if (!dt.changemap[j]) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add one sector
|
||||||
|
total += length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write
|
||||||
|
if (!fio.Write(&dt.buffer[i << dt.size], total)) {
|
||||||
|
fio.Close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// To unmodified sector
|
||||||
|
i = j;
|
||||||
|
} else {
|
||||||
|
// Next Sector
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close
|
||||||
|
fio.Close();
|
||||||
|
|
||||||
|
// Drop the change flag and exit
|
||||||
|
memset(dt.changemap, 0x00, dt.sectors * sizeof(BOOL));
|
||||||
|
dt.changed = FALSE;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DiskTrack::ReadSector(BYTE *buf, int sec) const
|
||||||
|
{
|
||||||
|
ASSERT(buf);
|
||||||
|
ASSERT((sec >= 0) & (sec < 0x100));
|
||||||
|
|
||||||
|
LOGTRACE("%s reading sector: %d", __PRETTY_FUNCTION__,sec);
|
||||||
|
// Error if not initialized
|
||||||
|
if (!dt.init) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// // Error if the number of sectors exceeds the valid number
|
||||||
|
if (sec >= dt.sectors) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy
|
||||||
|
ASSERT(dt.buffer);
|
||||||
|
ASSERT((dt.sectors > 0) && (dt.sectors <= 0x100));
|
||||||
|
memcpy(buf, &dt.buffer[(off_t)sec << dt.size], (off_t)1 << dt.size);
|
||||||
|
|
||||||
|
// Success
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DiskTrack::WriteSector(const BYTE *buf, int sec)
|
||||||
|
{
|
||||||
|
ASSERT(buf);
|
||||||
|
ASSERT((sec >= 0) & (sec < 0x100));
|
||||||
|
ASSERT(!dt.raw);
|
||||||
|
|
||||||
|
// Error if not initialized
|
||||||
|
if (!dt.init) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// // Error if the number of sectors exceeds the valid number
|
||||||
|
if (sec >= dt.sectors) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate offset and length
|
||||||
|
int offset = sec << dt.size;
|
||||||
|
int length = 1 << dt.size;
|
||||||
|
|
||||||
|
// Compare
|
||||||
|
ASSERT(dt.buffer);
|
||||||
|
ASSERT((dt.sectors > 0) && (dt.sectors <= 0x100));
|
||||||
|
if (memcmp(buf, &dt.buffer[offset], length) == 0) {
|
||||||
|
// Exit normally since it's attempting to write the same thing
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy, change
|
||||||
|
memcpy(&dt.buffer[offset], buf, length);
|
||||||
|
dt.changemap[sec] = TRUE;
|
||||||
|
dt.changed = TRUE;
|
||||||
|
|
||||||
|
// Success
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// Disk Cache
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
DiskCache::DiskCache(const Filepath& path, int size, uint32_t blocks, off_t imgoff) : DiskImageHandle(path, size, blocks, imgoff)
|
||||||
|
{
|
||||||
|
ASSERT(blocks > 0);
|
||||||
|
ASSERT(imgoff >= 0);
|
||||||
|
|
||||||
|
// Cache work
|
||||||
|
for (int i = 0; i < CacheMax; i++) {
|
||||||
|
cache[i].disktrk = NULL;
|
||||||
|
cache[i].serial = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
DiskCache::~DiskCache()
|
||||||
|
{
|
||||||
|
// Clear the track
|
||||||
|
Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DiskCache::Save()
|
||||||
|
{
|
||||||
|
// Save track
|
||||||
|
for (int i = 0; i < CacheMax; i++) {
|
||||||
|
// Is it a valid track?
|
||||||
|
if (cache[i].disktrk) {
|
||||||
|
// Save
|
||||||
|
if (!cache[i].disktrk->Save(sec_path)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Get disk cache information
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
bool DiskCache::GetCache(int index, int& track, DWORD& aserial) const
|
||||||
|
{
|
||||||
|
ASSERT((index >= 0) && (index < CacheMax));
|
||||||
|
|
||||||
|
// false if unused
|
||||||
|
if (!cache[index].disktrk) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set track and serial
|
||||||
|
track = cache[index].disktrk->GetTrack();
|
||||||
|
aserial = cache[index].serial;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DiskCache::Clear()
|
||||||
|
{
|
||||||
|
// Free the cache
|
||||||
|
for (int i = 0; i < CacheMax; i++) {
|
||||||
|
if (cache[i].disktrk) {
|
||||||
|
delete cache[i].disktrk;
|
||||||
|
cache[i].disktrk = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DiskCache::ReadSector(BYTE *buf, int block)
|
||||||
|
{
|
||||||
|
ASSERT(sec_size != 0);
|
||||||
|
|
||||||
|
// Update first
|
||||||
|
UpdateSerialNumber();
|
||||||
|
|
||||||
|
// Calculate track (fixed to 256 sectors/track)
|
||||||
|
int track = block >> 8;
|
||||||
|
|
||||||
|
// Get the track data
|
||||||
|
DiskTrack *disktrk = Assign(track);
|
||||||
|
if (!disktrk) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the track data to the cache
|
||||||
|
return disktrk->ReadSector(buf, block & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DiskCache::WriteSector(const BYTE *buf, int block)
|
||||||
|
{
|
||||||
|
ASSERT(sec_size != 0);
|
||||||
|
|
||||||
|
// Update first
|
||||||
|
UpdateSerialNumber();
|
||||||
|
|
||||||
|
// Calculate track (fixed to 256 sectors/track)
|
||||||
|
int track = block >> 8;
|
||||||
|
|
||||||
|
// Get that track data
|
||||||
|
DiskTrack *disktrk = Assign(track);
|
||||||
|
if (!disktrk) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the data to the cache
|
||||||
|
return disktrk->WriteSector(buf, block & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Track Assignment
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
DiskTrack* DiskCache::Assign(int track)
|
||||||
|
{
|
||||||
|
ASSERT(sec_size != 0);
|
||||||
|
ASSERT(track >= 0);
|
||||||
|
|
||||||
|
// First, check if it is already assigned
|
||||||
|
for (int i = 0; i < CacheMax; i++) {
|
||||||
|
if (cache[i].disktrk) {
|
||||||
|
if (cache[i].disktrk->GetTrack() == track) {
|
||||||
|
// Track match
|
||||||
|
cache[i].serial = serial;
|
||||||
|
return cache[i].disktrk;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next, check for empty
|
||||||
|
for (int i = 0; i < CacheMax; i++) {
|
||||||
|
if (!cache[i].disktrk) {
|
||||||
|
// Try loading
|
||||||
|
if (Load(i, track)) {
|
||||||
|
// Success loading
|
||||||
|
cache[i].serial = serial;
|
||||||
|
return cache[i].disktrk;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load failed
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, find the youngest serial number and delete it
|
||||||
|
|
||||||
|
// Set index 0 as candidate c
|
||||||
|
DWORD s = cache[0].serial;
|
||||||
|
int c = 0;
|
||||||
|
|
||||||
|
// Compare candidate with serial and update to smaller one
|
||||||
|
for (int i = 0; i < CacheMax; i++) {
|
||||||
|
ASSERT(cache[i].disktrk);
|
||||||
|
|
||||||
|
// Compare and update the existing serial
|
||||||
|
if (cache[i].serial < s) {
|
||||||
|
s = cache[i].serial;
|
||||||
|
c = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save this track
|
||||||
|
if (!cache[c].disktrk->Save(sec_path)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete this track
|
||||||
|
DiskTrack *disktrk = cache[c].disktrk;
|
||||||
|
cache[c].disktrk = NULL;
|
||||||
|
|
||||||
|
if (Load(c, track, disktrk)) {
|
||||||
|
// Successful loading
|
||||||
|
cache[c].serial = serial;
|
||||||
|
return cache[c].disktrk;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load failed
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Load cache
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
bool DiskCache::Load(int index, int track, DiskTrack *disktrk)
|
||||||
|
{
|
||||||
|
ASSERT((index >= 0) && (index < CacheMax));
|
||||||
|
ASSERT(track >= 0);
|
||||||
|
ASSERT(!cache[index].disktrk);
|
||||||
|
|
||||||
|
// Get the number of sectors on this track
|
||||||
|
int sectors = sec_blocks - (track << 8);
|
||||||
|
ASSERT(sectors > 0);
|
||||||
|
if (sectors > 0x100) {
|
||||||
|
sectors = 0x100;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a disk track
|
||||||
|
if (disktrk == NULL) {
|
||||||
|
disktrk = new DiskTrack();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize disk track
|
||||||
|
disktrk->Init(track, sec_size, sectors, cd_raw, imgoffset);
|
||||||
|
|
||||||
|
// Try loading
|
||||||
|
if (!disktrk->Load(sec_path)) {
|
||||||
|
// Failure
|
||||||
|
delete disktrk;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocation successful, work set
|
||||||
|
cache[index].disktrk = disktrk;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DiskCache::UpdateSerialNumber()
|
||||||
|
{
|
||||||
|
// Update and do nothing except 0
|
||||||
|
serial++;
|
||||||
|
if (serial != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear serial of all caches (loop in 32bit)
|
||||||
|
for (int i = 0; i < CacheMax; i++) {
|
||||||
|
cache[i].serial = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// X68000 EMULATOR "XM6"
|
||||||
|
//
|
||||||
|
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||||
|
// Copyright (C) 2014-2020 GIMONS
|
||||||
|
//
|
||||||
|
// XM6i
|
||||||
|
// Copyright (C) 2010-2015 isaki@NetBSD.org
|
||||||
|
//
|
||||||
|
// Imported sava's Anex86/T98Next image and MO format support patch.
|
||||||
|
// Comments translated to english by akuker.
|
||||||
|
//
|
||||||
|
// [ DiskTrack and DiskCache ]
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "filepath.h"
|
||||||
|
#include "disk_image/disk_image_handle.h"
|
||||||
|
|
||||||
|
// Number of tracks to cache
|
||||||
|
#define CacheMax 16
|
||||||
|
|
||||||
|
class DiskTrack
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
struct {
|
||||||
|
int track; // Track Number
|
||||||
|
int size; // Sector Size (8=256, 9=512, 10=1024, 11=2048, 12=4096)
|
||||||
|
int sectors; // Number of sectors(<0x100)
|
||||||
|
DWORD length; // Data buffer length
|
||||||
|
BYTE *buffer; // Data buffer
|
||||||
|
BOOL init; // Is it initilized?
|
||||||
|
BOOL changed; // Changed flag
|
||||||
|
DWORD maplen; // Changed map length
|
||||||
|
BOOL *changemap; // Changed map
|
||||||
|
BOOL raw; // RAW mode flag
|
||||||
|
off_t imgoffset; // Offset to actual data
|
||||||
|
} dt;
|
||||||
|
|
||||||
|
public:
|
||||||
|
DiskTrack();
|
||||||
|
~DiskTrack();
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class DiskCache;
|
||||||
|
|
||||||
|
void Init(int track, int size, int sectors, BOOL raw = FALSE, off_t imgoff = 0);
|
||||||
|
bool Load(const Filepath& path);
|
||||||
|
bool Save(const Filepath& path);
|
||||||
|
|
||||||
|
// Read / Write
|
||||||
|
bool ReadSector(BYTE *buf, int sec) const; // Sector Read
|
||||||
|
bool WriteSector(const BYTE *buf, int sec); // Sector Write
|
||||||
|
|
||||||
|
int GetTrack() const { return dt.track; } // Get track
|
||||||
|
};
|
||||||
|
|
||||||
|
class DiskCache : public DiskImageHandle
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// Internal data definition
|
||||||
|
typedef struct {
|
||||||
|
DiskTrack *disktrk; // Disk Track
|
||||||
|
DWORD serial; // Serial
|
||||||
|
} cache_t;
|
||||||
|
|
||||||
|
public:
|
||||||
|
DiskCache(const Filepath& path, int size, uint32_t blocks, off_t imgoff = 0);
|
||||||
|
~DiskCache();
|
||||||
|
|
||||||
|
// Access
|
||||||
|
bool Save() override; // Save and release all
|
||||||
|
bool ReadSector(BYTE *buf, int block) override; // Sector Read
|
||||||
|
bool WriteSector(const BYTE *buf, int block) override; // Sector Write
|
||||||
|
bool GetCache(int index, int& track, DWORD& serial) const override; // Get cache information
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Internal Management
|
||||||
|
void Clear(); // Clear all tracks
|
||||||
|
DiskTrack* Assign(int track); // Load track
|
||||||
|
bool Load(int index, int track, DiskTrack *disktrk = NULL); // Load track
|
||||||
|
void UpdateSerialNumber(); // Update serial number
|
||||||
|
|
||||||
|
// Internal data
|
||||||
|
cache_t cache[CacheMax]; // Cache management
|
||||||
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
};
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
};
|
|
@ -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
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
};
|
|
@ -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
|
@ -0,0 +1,204 @@
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// SCSI Target Emulator RaSCSI (*^..^*)
|
||||||
|
// for Raspberry Pi
|
||||||
|
//
|
||||||
|
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||||
|
// Copyright (C) 2014-2020 GIMONS
|
||||||
|
// Copyright (C) akuker
|
||||||
|
//
|
||||||
|
// Licensed under the BSD 3-Clause License.
|
||||||
|
// See LICENSE file in the project root folder.
|
||||||
|
//
|
||||||
|
// [ SASI device controller ]
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../config.h"
|
||||||
|
#include "os.h"
|
||||||
|
#include "scsi.h"
|
||||||
|
#include "fileio.h"
|
||||||
|
|
||||||
|
class Device;
|
||||||
|
class PrimaryDevice;
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// SASI Controller
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
class SASIDEV
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
enum scsi_message_code : BYTE {
|
||||||
|
eMsgCodeAbort = 0x06,
|
||||||
|
eMsgCodeAbortTag = 0x0D,
|
||||||
|
eMsgCodeBusDeviceReset = 0x0C,
|
||||||
|
eMsgCodeClearQueue = 0x0E,
|
||||||
|
eMsgCodeCommandComplete = 0x00,
|
||||||
|
eMsgCodeDisconnect = 0x04,
|
||||||
|
eMsgCodeIdentify = 0x80,
|
||||||
|
eMsgCodeIgnoreWideResidue = 0x23, // (Two Bytes)
|
||||||
|
eMsgCodeInitiateRecovery = 0x0F,
|
||||||
|
eMsgCodeInitiatorDetectedError = 0x05,
|
||||||
|
eMsgCodeLinkedCommandComplete = 0x0A,
|
||||||
|
eMsgCodeLinkedCommandCompleteWithFlag = 0x0B,
|
||||||
|
eMsgCodeMessageParityError = 0x09,
|
||||||
|
eMsgCodeMessageReject = 0x07,
|
||||||
|
eMsgCodeNoOperation = 0x08,
|
||||||
|
eMsgCodeHeadOfQueueTag = 0x21,
|
||||||
|
eMsgCodeOrderedQueueTag = 0x22,
|
||||||
|
eMsgCodeSimpleQueueTag = 0x20,
|
||||||
|
eMsgCodeReleaseRecovery = 0x10,
|
||||||
|
eMsgCodeRestorePointers = 0x03,
|
||||||
|
eMsgCodeSaveDataPointer = 0x02,
|
||||||
|
eMsgCodeTerminateIOProcess = 0x11
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum sasi_command : int {
|
||||||
|
eCmdTestUnitReady = 0x00,
|
||||||
|
eCmdRezero = 0x01,
|
||||||
|
eCmdRequestSense = 0x03,
|
||||||
|
eCmdFormat = 0x06,
|
||||||
|
eCmdReassign = 0x07,
|
||||||
|
eCmdRead6 = 0x08,
|
||||||
|
eCmdWrite6 = 0x0A,
|
||||||
|
eCmdSeek6 = 0x0B,
|
||||||
|
eCmdSetMcastAddr = 0x0D, // DaynaPort specific command
|
||||||
|
eCmdModeSelect6 = 0x15,
|
||||||
|
eCmdReserve6 = 0x16,
|
||||||
|
eCmdRelease6 = 0x17,
|
||||||
|
eCmdRead10 = 0x28,
|
||||||
|
eCmdWrite10 = 0x2A,
|
||||||
|
eCmdVerify10 = 0x2E,
|
||||||
|
eCmdVerify = 0x2F,
|
||||||
|
eCmdModeSelect10 = 0x55,
|
||||||
|
eCmdRead16 = 0x88,
|
||||||
|
eCmdWrite16 = 0x8A,
|
||||||
|
eCmdVerify16 = 0x8F,
|
||||||
|
eCmdWriteLong10 = 0x3F,
|
||||||
|
eCmdWriteLong16 = 0x9F,
|
||||||
|
eCmdInvalid = 0xC2,
|
||||||
|
eCmdSasiCmdAssign = 0x0E
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum {
|
||||||
|
UnitMax = 32 // Maximum number of logical units
|
||||||
|
};
|
||||||
|
|
||||||
|
const int UNKNOWN_SCSI_ID = -1;
|
||||||
|
const int DEFAULT_BUFFER_SIZE = 0x1000;
|
||||||
|
// TODO Remove this duplicate
|
||||||
|
const int DAYNAPORT_BUFFER_SIZE = 0x1000000;
|
||||||
|
|
||||||
|
// For timing adjustments
|
||||||
|
enum {
|
||||||
|
min_exec_time_sasi = 100, // SASI BOOT/FORMAT 30:NG 35:OK
|
||||||
|
min_exec_time_scsi = 50
|
||||||
|
};
|
||||||
|
|
||||||
|
// Internal data definition
|
||||||
|
typedef struct {
|
||||||
|
// General
|
||||||
|
BUS::phase_t phase; // Transition phase
|
||||||
|
int m_scsi_id; // Controller ID (0-7)
|
||||||
|
BUS *bus; // Bus
|
||||||
|
|
||||||
|
// commands
|
||||||
|
DWORD cmd[16]; // Command data
|
||||||
|
DWORD status; // Status data
|
||||||
|
DWORD message; // Message data
|
||||||
|
|
||||||
|
// Run
|
||||||
|
DWORD execstart; // Execution start time
|
||||||
|
|
||||||
|
// Transfer
|
||||||
|
BYTE *buffer; // Transfer data buffer
|
||||||
|
int bufsize; // Transfer data buffer size
|
||||||
|
uint32_t blocks; // Number of transfer block
|
||||||
|
DWORD next; // Next record
|
||||||
|
DWORD offset; // Transfer offset
|
||||||
|
DWORD length; // Transfer remaining length
|
||||||
|
|
||||||
|
// Logical unit
|
||||||
|
PrimaryDevice *unit[UnitMax];
|
||||||
|
|
||||||
|
// The current device
|
||||||
|
PrimaryDevice *device;
|
||||||
|
|
||||||
|
// The LUN from the IDENTIFY message
|
||||||
|
int lun;
|
||||||
|
} ctrl_t;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Basic Functions
|
||||||
|
SASIDEV();
|
||||||
|
virtual ~SASIDEV(); // Destructor
|
||||||
|
virtual void Reset(); // Device Reset
|
||||||
|
|
||||||
|
// External API
|
||||||
|
virtual BUS::phase_t Process(int); // Run
|
||||||
|
|
||||||
|
// Connect
|
||||||
|
void Connect(int id, BUS *sbus); // Controller connection
|
||||||
|
Device* GetUnit(int no); // Get logical unit
|
||||||
|
void SetUnit(int no, PrimaryDevice *dev); // Logical unit setting
|
||||||
|
bool HasUnit(); // Has a valid logical unit
|
||||||
|
|
||||||
|
// Other
|
||||||
|
BUS::phase_t GetPhase() {return ctrl.phase;} // Get the phase
|
||||||
|
|
||||||
|
int GetSCSIID() {return ctrl.m_scsi_id;} // Get the ID
|
||||||
|
ctrl_t* GetCtrl() { return &ctrl; } // Get the internal information address
|
||||||
|
virtual bool IsSASI() const { return true; } // SASI Check
|
||||||
|
virtual bool IsSCSI() const { return false; } // SCSI check
|
||||||
|
|
||||||
|
public:
|
||||||
|
void DataIn(); // Data in phase
|
||||||
|
void Status(); // Status phase
|
||||||
|
void MsgIn(); // Message in phase
|
||||||
|
void DataOut(); // Data out phase
|
||||||
|
|
||||||
|
// Get LUN based on IDENTIFY message, with LUN from the CDB as fallback
|
||||||
|
int GetEffectiveLun() const;
|
||||||
|
|
||||||
|
virtual void Error(ERROR_CODES::sense_key sense_key = ERROR_CODES::sense_key::NO_SENSE,
|
||||||
|
ERROR_CODES::asc = ERROR_CODES::asc::NO_ADDITIONAL_SENSE_INFORMATION,
|
||||||
|
ERROR_CODES::status = ERROR_CODES::status::CHECK_CONDITION); // Common error handling
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Phase processing
|
||||||
|
virtual void BusFree(); // Bus free phase
|
||||||
|
virtual void Selection(); // Selection phase
|
||||||
|
virtual void Command(); // Command phase
|
||||||
|
virtual void Execute(); // Execution phase
|
||||||
|
|
||||||
|
// Commands
|
||||||
|
void CmdTestUnitReady(); // TEST UNIT READY command
|
||||||
|
void CmdRezero(); // REZERO UNIT command
|
||||||
|
void CmdRequestSense(); // REQUEST SENSE command
|
||||||
|
void CmdFormat(); // FORMAT command
|
||||||
|
void CmdReassignBlocks(); // REASSIGN BLOCKS command
|
||||||
|
void CmdReserveUnit(); // RESERVE UNIT command
|
||||||
|
void CmdReleaseUnit(); // RELEASE UNIT command
|
||||||
|
void CmdRead6(); // READ(6) command
|
||||||
|
void CmdWrite6(); // WRITE(6) command
|
||||||
|
void CmdSeek6(); // SEEK(6) command
|
||||||
|
void CmdAssign(); // ASSIGN command
|
||||||
|
void CmdSpecify(); // SPECIFY command
|
||||||
|
|
||||||
|
// Data transfer
|
||||||
|
virtual void Send(); // Send data
|
||||||
|
virtual void Receive(); // Receive data
|
||||||
|
|
||||||
|
bool XferIn(BYTE* buf); // Data transfer IN
|
||||||
|
virtual bool XferOut(bool cont); // Data transfer OUT
|
||||||
|
|
||||||
|
// Special operations
|
||||||
|
void FlushUnit(); // Flush the logical unit
|
||||||
|
|
||||||
|
ctrl_t ctrl; // Internal data
|
||||||
|
};
|
|
@ -0,0 +1,907 @@
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// SCSI Target Emulator RaSCSI (*^..^*)
|
||||||
|
// for Raspberry Pi
|
||||||
|
//
|
||||||
|
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||||
|
// Copyright (C) 2014-2020 GIMONS
|
||||||
|
// Copyright (C) akuker
|
||||||
|
//
|
||||||
|
// Licensed under the BSD 3-Clause License.
|
||||||
|
// See LICENSE file in the project root folder.
|
||||||
|
//
|
||||||
|
// [ SCSI device controller ]
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
#include "log.h"
|
||||||
|
#include "controllers/scsidev_ctrl.h"
|
||||||
|
#include "gpiobus.h"
|
||||||
|
#include "devices/scsi_daynaport.h"
|
||||||
|
#include "devices/scsi_printer.h"
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// SCSI Device
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
SCSIDEV::SCSIDEV() : SASIDEV()
|
||||||
|
{
|
||||||
|
scsi.is_byte_transfer = false;
|
||||||
|
scsi.bytes_to_transfer = 0;
|
||||||
|
shutdown_mode = NONE;
|
||||||
|
|
||||||
|
// Synchronous transfer work initialization
|
||||||
|
scsi.syncenable = FALSE;
|
||||||
|
scsi.syncperiod = 50;
|
||||||
|
scsi.syncoffset = 0;
|
||||||
|
scsi.atnmsg = false;
|
||||||
|
scsi.msc = 0;
|
||||||
|
memset(scsi.msb, 0x00, sizeof(scsi.msb));
|
||||||
|
}
|
||||||
|
|
||||||
|
SCSIDEV::~SCSIDEV()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void SCSIDEV::Reset()
|
||||||
|
{
|
||||||
|
scsi.is_byte_transfer = false;
|
||||||
|
scsi.bytes_to_transfer = 0;
|
||||||
|
|
||||||
|
// Work initialization
|
||||||
|
scsi.atnmsg = false;
|
||||||
|
scsi.msc = 0;
|
||||||
|
memset(scsi.msb, 0x00, sizeof(scsi.msb));
|
||||||
|
|
||||||
|
super::Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
BUS::phase_t SCSIDEV::Process(int initiator_id)
|
||||||
|
{
|
||||||
|
// Do nothing if not connected
|
||||||
|
if (ctrl.m_scsi_id < 0 || ctrl.bus == NULL) {
|
||||||
|
return ctrl.phase;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get bus information
|
||||||
|
ctrl.bus->Aquire();
|
||||||
|
|
||||||
|
// Check to see if the reset signal was asserted
|
||||||
|
if (ctrl.bus->GetRST()) {
|
||||||
|
LOGWARN("RESET signal received!");
|
||||||
|
|
||||||
|
// Reset the controller
|
||||||
|
Reset();
|
||||||
|
|
||||||
|
// Reset the bus
|
||||||
|
ctrl.bus->Reset();
|
||||||
|
return ctrl.phase;
|
||||||
|
}
|
||||||
|
|
||||||
|
scsi.initiator_id = initiator_id;
|
||||||
|
|
||||||
|
// Phase processing
|
||||||
|
switch (ctrl.phase) {
|
||||||
|
// Bus free phase
|
||||||
|
case BUS::busfree:
|
||||||
|
BusFree();
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Selection
|
||||||
|
case BUS::selection:
|
||||||
|
Selection();
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Data out (MCI=000)
|
||||||
|
case BUS::dataout:
|
||||||
|
DataOut();
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Data in (MCI=001)
|
||||||
|
case BUS::datain:
|
||||||
|
DataIn();
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Command (MCI=010)
|
||||||
|
case BUS::command:
|
||||||
|
Command();
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Status (MCI=011)
|
||||||
|
case BUS::status:
|
||||||
|
Status();
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Message out (MCI=110)
|
||||||
|
case BUS::msgout:
|
||||||
|
MsgOut();
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Message in (MCI=111)
|
||||||
|
case BUS::msgin:
|
||||||
|
MsgIn();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
assert(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctrl.phase;
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Bus free phase
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
void SCSIDEV::BusFree()
|
||||||
|
{
|
||||||
|
// Phase change
|
||||||
|
if (ctrl.phase != BUS::busfree) {
|
||||||
|
LOGTRACE("%s Bus free phase", __PRETTY_FUNCTION__);
|
||||||
|
|
||||||
|
// Phase setting
|
||||||
|
ctrl.phase = BUS::busfree;
|
||||||
|
|
||||||
|
// Set Signal lines
|
||||||
|
ctrl.bus->SetREQ(FALSE);
|
||||||
|
ctrl.bus->SetMSG(FALSE);
|
||||||
|
ctrl.bus->SetCD(FALSE);
|
||||||
|
ctrl.bus->SetIO(FALSE);
|
||||||
|
ctrl.bus->SetBSY(false);
|
||||||
|
|
||||||
|
// Initialize status and message
|
||||||
|
ctrl.status = 0x00;
|
||||||
|
ctrl.message = 0x00;
|
||||||
|
|
||||||
|
// Initialize ATN message reception status
|
||||||
|
scsi.atnmsg = false;
|
||||||
|
|
||||||
|
ctrl.lun = -1;
|
||||||
|
|
||||||
|
scsi.is_byte_transfer = false;
|
||||||
|
scsi.bytes_to_transfer = 0;
|
||||||
|
|
||||||
|
// When the bus is free RaSCSI or the Pi may be shut down
|
||||||
|
switch(shutdown_mode) {
|
||||||
|
case STOP_RASCSI:
|
||||||
|
LOGINFO("RaSCSI shutdown requested");
|
||||||
|
exit(0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case STOP_PI:
|
||||||
|
LOGINFO("Raspberry Pi shutdown requested");
|
||||||
|
if (system("init 0") == -1) {
|
||||||
|
LOGERROR("Raspberry Pi shutdown failed: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RESTART_PI:
|
||||||
|
LOGINFO("Raspberry Pi restart requested");
|
||||||
|
if (system("init 6") == -1) {
|
||||||
|
LOGERROR("Raspberry Pi restart failed: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to selection phase
|
||||||
|
if (ctrl.bus->GetSEL() && !ctrl.bus->GetBSY()) {
|
||||||
|
Selection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Selection Phase
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
void SCSIDEV::Selection()
|
||||||
|
{
|
||||||
|
// Phase change
|
||||||
|
if (ctrl.phase != BUS::selection) {
|
||||||
|
// invalid if IDs do not match
|
||||||
|
int id = 1 << ctrl.m_scsi_id;
|
||||||
|
if ((ctrl.bus->GetDAT() & id) == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return if there is no valid LUN
|
||||||
|
if (!HasUnit()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGTRACE("%s Selection Phase ID=%d (with device)", __PRETTY_FUNCTION__, (int)ctrl.m_scsi_id);
|
||||||
|
|
||||||
|
if (scsi.initiator_id != UNKNOWN_SCSI_ID) {
|
||||||
|
LOGTRACE("%s Initiator ID is %d", __PRETTY_FUNCTION__, scsi.initiator_id);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LOGTRACE("%s Initiator ID is unknown", __PRETTY_FUNCTION__);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase setting
|
||||||
|
ctrl.phase = BUS::selection;
|
||||||
|
|
||||||
|
// Raise BSY and respond
|
||||||
|
ctrl.bus->SetBSY(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selection completed
|
||||||
|
if (!ctrl.bus->GetSEL() && ctrl.bus->GetBSY()) {
|
||||||
|
// Message out phase if ATN=1, otherwise command phase
|
||||||
|
if (ctrl.bus->GetATN()) {
|
||||||
|
MsgOut();
|
||||||
|
} else {
|
||||||
|
Command();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Execution Phase
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
void SCSIDEV::Execute()
|
||||||
|
{
|
||||||
|
LOGTRACE("%s Execution phase command $%02X", __PRETTY_FUNCTION__, (unsigned int)ctrl.cmd[0]);
|
||||||
|
|
||||||
|
// Phase Setting
|
||||||
|
ctrl.phase = BUS::execute;
|
||||||
|
|
||||||
|
// Initialization for data transfer
|
||||||
|
ctrl.offset = 0;
|
||||||
|
ctrl.blocks = 1;
|
||||||
|
ctrl.execstart = SysTimer::GetTimerLow();
|
||||||
|
|
||||||
|
// Discard pending sense data from the previous command if the current command is not REQUEST SENSE
|
||||||
|
if ((scsi_defs::scsi_command)ctrl.cmd[0] != scsi_defs::eCmdRequestSense) {
|
||||||
|
ctrl.status = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGDEBUG("++++ CMD ++++ %s Executing command $%02X", __PRETTY_FUNCTION__, (unsigned int)ctrl.cmd[0]);
|
||||||
|
|
||||||
|
int lun = GetEffectiveLun();
|
||||||
|
if (!ctrl.unit[lun]) {
|
||||||
|
if ((scsi_defs::scsi_command)ctrl.cmd[0] != scsi_defs::eCmdInquiry &&
|
||||||
|
(scsi_defs::scsi_command)ctrl.cmd[0] != scsi_defs::eCmdRequestSense) {
|
||||||
|
LOGDEBUG("Invalid LUN %d for ID %d", lun, GetSCSIID());
|
||||||
|
|
||||||
|
Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::INVALID_LUN);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Use LUN 0 for INQUIRY and REQUEST SENSE because LUN0 is assumed to be always available.
|
||||||
|
// INQUIRY and REQUEST SENSE have a special LUN handling of their own, required by the SCSI standard.
|
||||||
|
else {
|
||||||
|
assert(ctrl.unit[0]);
|
||||||
|
|
||||||
|
lun = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctrl.device = ctrl.unit[lun];
|
||||||
|
|
||||||
|
// Discard pending sense data from the previous command if the current command is not REQUEST SENSE
|
||||||
|
if ((scsi_defs::scsi_command)ctrl.cmd[0] != scsi_defs::eCmdRequestSense) {
|
||||||
|
ctrl.device->SetStatusCode(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ctrl.device->Dispatch(this)) {
|
||||||
|
LOGTRACE("ID %d LUN %d received unsupported command: $%02X", GetSCSIID(), lun, (BYTE)ctrl.cmd[0]);
|
||||||
|
|
||||||
|
Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::INVALID_COMMAND_OPERATION_CODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SCSI-2 p.104 4.4.3 Incorrect logical unit handling
|
||||||
|
if ((scsi_defs::scsi_command)ctrl.cmd[0] == scsi_defs::eCmdInquiry && !ctrl.unit[lun]) {
|
||||||
|
lun = GetEffectiveLun();
|
||||||
|
|
||||||
|
LOGTRACE("Reporting LUN %d for device ID %d as not supported", lun, ctrl.device->GetId());
|
||||||
|
|
||||||
|
ctrl.buffer[0] = 0x7f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Message out phase
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
void SCSIDEV::MsgOut()
|
||||||
|
{
|
||||||
|
LOGTRACE("%s ID %d",__PRETTY_FUNCTION__, GetSCSIID());
|
||||||
|
|
||||||
|
// Phase change
|
||||||
|
if (ctrl.phase != BUS::msgout) {
|
||||||
|
LOGTRACE("Message Out Phase");
|
||||||
|
|
||||||
|
// process the IDENTIFY message
|
||||||
|
if (ctrl.phase == BUS::selection) {
|
||||||
|
scsi.atnmsg = true;
|
||||||
|
scsi.msc = 0;
|
||||||
|
memset(scsi.msb, 0x00, sizeof(scsi.msb));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase Setting
|
||||||
|
ctrl.phase = BUS::msgout;
|
||||||
|
|
||||||
|
// Signal line operated by the target
|
||||||
|
ctrl.bus->SetMSG(TRUE);
|
||||||
|
ctrl.bus->SetCD(TRUE);
|
||||||
|
ctrl.bus->SetIO(FALSE);
|
||||||
|
|
||||||
|
// Data transfer is 1 byte x 1 block
|
||||||
|
ctrl.offset = 0;
|
||||||
|
ctrl.length = 1;
|
||||||
|
ctrl.blocks = 1;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Receive();
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Common Error Handling
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
void SCSIDEV::Error(ERROR_CODES::sense_key sense_key, ERROR_CODES::asc asc, ERROR_CODES::status status)
|
||||||
|
{
|
||||||
|
// Get bus information
|
||||||
|
ctrl.bus->Aquire();
|
||||||
|
|
||||||
|
// Reset check
|
||||||
|
if (ctrl.bus->GetRST()) {
|
||||||
|
// Reset the controller
|
||||||
|
Reset();
|
||||||
|
|
||||||
|
// Reset the bus
|
||||||
|
ctrl.bus->Reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bus free for status phase and message in phase
|
||||||
|
if (ctrl.phase == BUS::status || ctrl.phase == BUS::msgin) {
|
||||||
|
BusFree();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lun = GetEffectiveLun();
|
||||||
|
if (!ctrl.unit[lun] || asc == ERROR_CODES::INVALID_LUN) {
|
||||||
|
lun = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sense_key || asc) {
|
||||||
|
// Set Sense Key and ASC for a subsequent REQUEST SENSE
|
||||||
|
ctrl.unit[lun]->SetStatusCode((sense_key << 16) | (asc << 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
ctrl.status = status;
|
||||||
|
ctrl.message = 0x00;
|
||||||
|
|
||||||
|
LOGTRACE("%s Error (to status phase)", __PRETTY_FUNCTION__);
|
||||||
|
|
||||||
|
Status();
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Send data
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
void SCSIDEV::Send()
|
||||||
|
{
|
||||||
|
ASSERT(!ctrl.bus->GetREQ());
|
||||||
|
ASSERT(ctrl.bus->GetIO());
|
||||||
|
|
||||||
|
if (ctrl.length != 0) {
|
||||||
|
LOGTRACE("%s%s", __PRETTY_FUNCTION__, (" Sending handhake with offset " + to_string(ctrl.offset) + ", length "
|
||||||
|
+ to_string(ctrl.length)).c_str());
|
||||||
|
|
||||||
|
int len = ctrl.bus->SendHandShake(&ctrl.buffer[ctrl.offset], ctrl.length, ctrl.unit[0]->GetSendDelay());
|
||||||
|
|
||||||
|
// If you cannot send all, move to status phase
|
||||||
|
if (len != (int)ctrl.length) {
|
||||||
|
Error();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// offset and length
|
||||||
|
ctrl.offset += ctrl.length;
|
||||||
|
ctrl.length = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block subtraction, result initialization
|
||||||
|
ctrl.blocks--;
|
||||||
|
bool result = true;
|
||||||
|
|
||||||
|
// Processing after data collection (read/data-in only)
|
||||||
|
if (ctrl.phase == BUS::datain) {
|
||||||
|
if (ctrl.blocks != 0) {
|
||||||
|
// set next buffer (set offset, length)
|
||||||
|
result = XferIn(ctrl.buffer);
|
||||||
|
LOGTRACE("%s%s", __PRETTY_FUNCTION__, (" Processing after data collection. Blocks: " + to_string(ctrl.blocks)).c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If result FALSE, move to status phase
|
||||||
|
if (!result) {
|
||||||
|
Error();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue sending if block !=0
|
||||||
|
if (ctrl.blocks != 0){
|
||||||
|
LOGTRACE("%s%s", __PRETTY_FUNCTION__, (" Continuing to send. Blocks: " + to_string(ctrl.blocks)).c_str());
|
||||||
|
ASSERT(ctrl.length > 0);
|
||||||
|
ASSERT(ctrl.offset == 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to next phase
|
||||||
|
LOGTRACE("%s Move to next phase %s (%d)", __PRETTY_FUNCTION__, BUS::GetPhaseStrRaw(ctrl.phase), ctrl.phase);
|
||||||
|
switch (ctrl.phase) {
|
||||||
|
// Message in phase
|
||||||
|
case BUS::msgin:
|
||||||
|
// Completed sending response to extended message of IDENTIFY message
|
||||||
|
if (scsi.atnmsg) {
|
||||||
|
// flag off
|
||||||
|
scsi.atnmsg = false;
|
||||||
|
|
||||||
|
// command phase
|
||||||
|
Command();
|
||||||
|
} else {
|
||||||
|
// Bus free phase
|
||||||
|
BusFree();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Data-in Phase
|
||||||
|
case BUS::datain:
|
||||||
|
// status phase
|
||||||
|
Status();
|
||||||
|
break;
|
||||||
|
|
||||||
|
// status phase
|
||||||
|
case BUS::status:
|
||||||
|
// Message in phase
|
||||||
|
ctrl.length = 1;
|
||||||
|
ctrl.blocks = 1;
|
||||||
|
ctrl.buffer[0] = (BYTE)ctrl.message;
|
||||||
|
MsgIn();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
assert(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Receive Data
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
void SCSIDEV::Receive()
|
||||||
|
{
|
||||||
|
if (scsi.is_byte_transfer) {
|
||||||
|
ReceiveBytes();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int len;
|
||||||
|
BYTE data;
|
||||||
|
|
||||||
|
LOGTRACE("%s",__PRETTY_FUNCTION__);
|
||||||
|
|
||||||
|
// REQ is low
|
||||||
|
ASSERT(!ctrl.bus->GetREQ());
|
||||||
|
ASSERT(!ctrl.bus->GetIO());
|
||||||
|
|
||||||
|
// Length != 0 if received
|
||||||
|
if (ctrl.length != 0) {
|
||||||
|
LOGTRACE("%s Length is %d bytes", __PRETTY_FUNCTION__, (int)ctrl.length);
|
||||||
|
// Receive
|
||||||
|
len = ctrl.bus->ReceiveHandShake(&ctrl.buffer[ctrl.offset], ctrl.length);
|
||||||
|
|
||||||
|
// If not able to receive all, move to status phase
|
||||||
|
if (len != (int)ctrl.length) {
|
||||||
|
LOGERROR("%s Not able to receive %d bytes of data, only received %d. Going to error",__PRETTY_FUNCTION__, (int)ctrl.length, len);
|
||||||
|
Error();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Offset and Length
|
||||||
|
ctrl.offset += ctrl.length;
|
||||||
|
ctrl.length = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block subtraction, result initialization
|
||||||
|
ctrl.blocks--;
|
||||||
|
bool result = true;
|
||||||
|
|
||||||
|
// Processing after receiving data (by phase)
|
||||||
|
LOGTRACE("%s ctrl.phase: %d (%s)",__PRETTY_FUNCTION__, (int)ctrl.phase, BUS::GetPhaseStrRaw(ctrl.phase));
|
||||||
|
switch (ctrl.phase) {
|
||||||
|
|
||||||
|
// Data out phase
|
||||||
|
case BUS::dataout:
|
||||||
|
if (ctrl.blocks == 0) {
|
||||||
|
// End with this buffer
|
||||||
|
result = XferOut(false);
|
||||||
|
} else {
|
||||||
|
// Continue to next buffer (set offset, length)
|
||||||
|
result = XferOut(true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Message out phase
|
||||||
|
case BUS::msgout:
|
||||||
|
ctrl.message = ctrl.buffer[0];
|
||||||
|
if (!XferMsg(ctrl.message)) {
|
||||||
|
// Immediately free the bus if message output fails
|
||||||
|
BusFree();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear message data in preparation for message-in
|
||||||
|
ctrl.message = 0x00;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If result FALSE, move to status phase
|
||||||
|
if (!result) {
|
||||||
|
Error();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue to receive if block !=0
|
||||||
|
if (ctrl.blocks != 0){
|
||||||
|
ASSERT(ctrl.length > 0);
|
||||||
|
ASSERT(ctrl.offset == 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to next phase
|
||||||
|
switch (ctrl.phase) {
|
||||||
|
// Command phase
|
||||||
|
case BUS::command:
|
||||||
|
len = GPIOBUS::GetCommandByteCount(ctrl.buffer[0]);
|
||||||
|
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
ctrl.cmd[i] = ctrl.buffer[i];
|
||||||
|
LOGTRACE("%s Command Byte %d: $%02X",__PRETTY_FUNCTION__, i, ctrl.cmd[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execution Phase
|
||||||
|
Execute();
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Message out phase
|
||||||
|
case BUS::msgout:
|
||||||
|
// Continue message out phase as long as ATN keeps asserting
|
||||||
|
if (ctrl.bus->GetATN()) {
|
||||||
|
// Data transfer is 1 byte x 1 block
|
||||||
|
ctrl.offset = 0;
|
||||||
|
ctrl.length = 1;
|
||||||
|
ctrl.blocks = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parsing messages sent by ATN
|
||||||
|
if (scsi.atnmsg) {
|
||||||
|
int i = 0;
|
||||||
|
while (i < scsi.msc) {
|
||||||
|
// Message type
|
||||||
|
data = scsi.msb[i];
|
||||||
|
|
||||||
|
// ABORT
|
||||||
|
if (data == 0x06) {
|
||||||
|
LOGTRACE("Message code ABORT $%02X", data);
|
||||||
|
BusFree();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// BUS DEVICE RESET
|
||||||
|
if (data == 0x0C) {
|
||||||
|
LOGTRACE("Message code BUS DEVICE RESET $%02X", data);
|
||||||
|
scsi.syncoffset = 0;
|
||||||
|
BusFree();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDENTIFY
|
||||||
|
if (data >= 0x80) {
|
||||||
|
ctrl.lun = data & 0x1F;
|
||||||
|
LOGTRACE("Message code IDENTIFY $%02X, LUN %d selected", data, ctrl.lun);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extended Message
|
||||||
|
if (data == 0x01) {
|
||||||
|
LOGTRACE("Message code EXTENDED MESSAGE $%02X", data);
|
||||||
|
|
||||||
|
// Check only when synchronous transfer is possible
|
||||||
|
if (!scsi.syncenable || scsi.msb[i + 2] != 0x01) {
|
||||||
|
ctrl.length = 1;
|
||||||
|
ctrl.blocks = 1;
|
||||||
|
ctrl.buffer[0] = 0x07;
|
||||||
|
MsgIn();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer period factor (limited to 50 x 4 = 200ns)
|
||||||
|
scsi.syncperiod = scsi.msb[i + 3];
|
||||||
|
if (scsi.syncperiod > 50) {
|
||||||
|
scsi.syncperiod = 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
// REQ/ACK offset(limited to 16)
|
||||||
|
scsi.syncoffset = scsi.msb[i + 4];
|
||||||
|
if (scsi.syncoffset > 16) {
|
||||||
|
scsi.syncoffset = 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
// STDR response message generation
|
||||||
|
ctrl.length = 5;
|
||||||
|
ctrl.blocks = 1;
|
||||||
|
ctrl.buffer[0] = 0x01;
|
||||||
|
ctrl.buffer[1] = 0x03;
|
||||||
|
ctrl.buffer[2] = 0x01;
|
||||||
|
ctrl.buffer[3] = (BYTE)scsi.syncperiod;
|
||||||
|
ctrl.buffer[4] = (BYTE)scsi.syncoffset;
|
||||||
|
MsgIn();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// next
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize ATN message reception status
|
||||||
|
scsi.atnmsg = false;
|
||||||
|
|
||||||
|
// Command phase
|
||||||
|
Command();
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Data out phase
|
||||||
|
case BUS::dataout:
|
||||||
|
FlushUnit();
|
||||||
|
|
||||||
|
// status phase
|
||||||
|
Status();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
assert(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Transfer MSG
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
bool SCSIDEV::XferMsg(int msg)
|
||||||
|
{
|
||||||
|
ASSERT(ctrl.phase == BUS::msgout);
|
||||||
|
|
||||||
|
// Save message out data
|
||||||
|
if (scsi.atnmsg) {
|
||||||
|
scsi.msb[scsi.msc] = (BYTE)msg;
|
||||||
|
scsi.msc++;
|
||||||
|
scsi.msc %= 256;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SCSIDEV::ReceiveBytes()
|
||||||
|
{
|
||||||
|
uint32_t len;
|
||||||
|
BYTE data;
|
||||||
|
|
||||||
|
LOGTRACE("%s",__PRETTY_FUNCTION__);
|
||||||
|
|
||||||
|
// REQ is low
|
||||||
|
ASSERT(!ctrl.bus->GetREQ());
|
||||||
|
ASSERT(!ctrl.bus->GetIO());
|
||||||
|
|
||||||
|
if (ctrl.length) {
|
||||||
|
LOGTRACE("%s Length is %d bytes", __PRETTY_FUNCTION__, ctrl.length);
|
||||||
|
|
||||||
|
len = ctrl.bus->ReceiveHandShake(&ctrl.buffer[ctrl.offset], ctrl.length);
|
||||||
|
|
||||||
|
// If not able to receive all, move to status phase
|
||||||
|
if (len != ctrl.length) {
|
||||||
|
LOGERROR("%s Not able to receive %d bytes of data, only received %d. Going to error",
|
||||||
|
__PRETTY_FUNCTION__, ctrl.length, len);
|
||||||
|
Error();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctrl.offset += ctrl.length;
|
||||||
|
scsi.bytes_to_transfer = ctrl.length;
|
||||||
|
ctrl.length = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result initialization
|
||||||
|
bool result = true;
|
||||||
|
|
||||||
|
// Processing after receiving data (by phase)
|
||||||
|
LOGTRACE("%s ctrl.phase: %d (%s)",__PRETTY_FUNCTION__, (int)ctrl.phase, BUS::GetPhaseStrRaw(ctrl.phase));
|
||||||
|
switch (ctrl.phase) {
|
||||||
|
|
||||||
|
case BUS::dataout:
|
||||||
|
result = XferOut(false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BUS::msgout:
|
||||||
|
ctrl.message = ctrl.buffer[0];
|
||||||
|
if (!XferMsg(ctrl.message)) {
|
||||||
|
// Immediately free the bus if message output fails
|
||||||
|
BusFree();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear message data in preparation for message-in
|
||||||
|
ctrl.message = 0x00;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If result FALSE, move to status phase
|
||||||
|
if (!result) {
|
||||||
|
Error();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to next phase
|
||||||
|
switch (ctrl.phase) {
|
||||||
|
case BUS::command:
|
||||||
|
len = GPIOBUS::GetCommandByteCount(ctrl.buffer[0]);
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < len; i++) {
|
||||||
|
ctrl.cmd[i] = ctrl.buffer[i];
|
||||||
|
LOGTRACE("%s Command Byte %d: $%02X",__PRETTY_FUNCTION__, i, ctrl.cmd[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Execute();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BUS::msgout:
|
||||||
|
// Continue message out phase as long as ATN keeps asserting
|
||||||
|
if (ctrl.bus->GetATN()) {
|
||||||
|
// Data transfer is 1 byte x 1 block
|
||||||
|
ctrl.offset = 0;
|
||||||
|
ctrl.length = 1;
|
||||||
|
ctrl.blocks = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parsing messages sent by ATN
|
||||||
|
if (scsi.atnmsg) {
|
||||||
|
int i = 0;
|
||||||
|
while (i < scsi.msc) {
|
||||||
|
// Message type
|
||||||
|
data = scsi.msb[i];
|
||||||
|
|
||||||
|
// ABORT
|
||||||
|
if (data == 0x06) {
|
||||||
|
LOGTRACE("Message code ABORT $%02X", data);
|
||||||
|
BusFree();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// BUS DEVICE RESET
|
||||||
|
if (data == 0x0C) {
|
||||||
|
LOGTRACE("Message code BUS DEVICE RESET $%02X", data);
|
||||||
|
scsi.syncoffset = 0;
|
||||||
|
BusFree();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDENTIFY
|
||||||
|
if (data >= 0x80) {
|
||||||
|
ctrl.lun = data & 0x1F;
|
||||||
|
LOGTRACE("Message code IDENTIFY $%02X, LUN %d selected", data, ctrl.lun);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extended Message
|
||||||
|
if (data == 0x01) {
|
||||||
|
LOGTRACE("Message code EXTENDED MESSAGE $%02X", data);
|
||||||
|
|
||||||
|
// Check only when synchronous transfer is possible
|
||||||
|
if (!scsi.syncenable || scsi.msb[i + 2] != 0x01) {
|
||||||
|
ctrl.length = 1;
|
||||||
|
ctrl.blocks = 1;
|
||||||
|
ctrl.buffer[0] = 0x07;
|
||||||
|
MsgIn();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer period factor (limited to 50 x 4 = 200ns)
|
||||||
|
scsi.syncperiod = scsi.msb[i + 3];
|
||||||
|
if (scsi.syncperiod > 50) {
|
||||||
|
scsi.syncoffset = 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
// REQ/ACK offset(limited to 16)
|
||||||
|
scsi.syncoffset = scsi.msb[i + 4];
|
||||||
|
if (scsi.syncoffset > 16) {
|
||||||
|
scsi.syncoffset = 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
// STDR response message generation
|
||||||
|
ctrl.length = 5;
|
||||||
|
ctrl.blocks = 1;
|
||||||
|
ctrl.buffer[0] = 0x01;
|
||||||
|
ctrl.buffer[1] = 0x03;
|
||||||
|
ctrl.buffer[2] = 0x01;
|
||||||
|
ctrl.buffer[3] = (BYTE)scsi.syncperiod;
|
||||||
|
ctrl.buffer[4] = (BYTE)scsi.syncoffset;
|
||||||
|
MsgIn();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// next
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize ATN message reception status
|
||||||
|
scsi.atnmsg = false;
|
||||||
|
|
||||||
|
Command();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BUS::dataout:
|
||||||
|
Status();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
assert(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SCSIDEV::XferOut(bool cont)
|
||||||
|
{
|
||||||
|
if (!scsi.is_byte_transfer) {
|
||||||
|
return super::XferOut(cont);
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT(ctrl.phase == BUS::dataout);
|
||||||
|
|
||||||
|
scsi.is_byte_transfer = false;
|
||||||
|
|
||||||
|
PrimaryDevice *device = dynamic_cast<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;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// SCSI Target Emulator RaSCSI (*^..^*)
|
||||||
|
// for Raspberry Pi
|
||||||
|
//
|
||||||
|
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||||
|
// Copyright (C) 2014-2020 GIMONS
|
||||||
|
// Copyright (C) akuker
|
||||||
|
//
|
||||||
|
// Licensed under the BSD 3-Clause License.
|
||||||
|
// See LICENSE file in the project root folder.
|
||||||
|
//
|
||||||
|
// [ SCSI device controller ]
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "controllers/sasidev_ctrl.h"
|
||||||
|
#include <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
|
@ -0,0 +1,944 @@
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// SCSI Target Emulator RaSCSI (*^..^*)
|
||||||
|
// for Raspberry Pi
|
||||||
|
//
|
||||||
|
// Powered by XM6 TypeG Technology.
|
||||||
|
// Copyright (C) 2016-2020 GIMONS
|
||||||
|
// [ Host File System for the X68000 ]
|
||||||
|
//
|
||||||
|
// Note: This functionality is specific to the X68000 operating system.
|
||||||
|
// It is highly unlikely that this will work for other platforms.
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Status code definitions
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
#define FS_INVALIDFUNC 0xFFFFFFFF ///< Executed an invalid function
|
||||||
|
#define FS_FILENOTFND 0xFFFFFFFE ///< The selected file can not be found
|
||||||
|
#define FS_DIRNOTFND 0xFFFFFFFD ///< The selected directory can not be found
|
||||||
|
#define FS_OVEROPENED 0xFFFFFFFC ///< There are too many files open
|
||||||
|
#define FS_CANTACCESS 0xFFFFFFFB ///< Can not access the direcory or volume
|
||||||
|
#define FS_NOTOPENED 0xFFFFFFFA ///< The selected handle is not opened
|
||||||
|
#define FS_INVALIDMEM 0xFFFFFFF9 ///< Memory management has been destroyed
|
||||||
|
#define FS_OUTOFMEM 0xFFFFFFF8 ///< Insufficient memory for execution
|
||||||
|
#define FS_INVALIDPTR 0xFFFFFFF7 ///< Selected an invalid memory management pointer
|
||||||
|
#define FS_INVALIDENV 0xFFFFFFF6 ///< Selected an invalid environment
|
||||||
|
#define FS_ILLEGALFMT 0xFFFFFFF5 ///< The exeucted file is in an invalid format
|
||||||
|
#define FS_ILLEGALMOD 0xFFFFFFF4 ///< Invalid open access mode
|
||||||
|
#define FS_INVALIDPATH 0xFFFFFFF3 ///< Mistake in selected file name
|
||||||
|
#define FS_INVALIDPRM 0xFFFFFFF2 ///< Called with an invalid parameter
|
||||||
|
#define FS_INVALIDDRV 0xFFFFFFF1 ///< Mistake in selected drive
|
||||||
|
#define FS_DELCURDIR 0xFFFFFFF0 ///< Unable to delete the current directory
|
||||||
|
#define FS_NOTIOCTRL 0xFFFFFFEF ///< Unable to use IOCTRL with the device
|
||||||
|
#define FS_LASTFILE 0xFFFFFFEE ///< Can not find any more files
|
||||||
|
#define FS_CANTWRITE 0xFFFFFFED ///< Selected file can not be written
|
||||||
|
#define FS_DIRALREADY 0xFFFFFFEC ///< Selected directory is already registered
|
||||||
|
#define FS_CANTDELETE 0xFFFFFFEB ///< Can not delete because of a file
|
||||||
|
#define FS_CANTRENAME 0xFFFFFFEA ///< Can not rename because of a file
|
||||||
|
#define FS_DISKFULL 0xFFFFFFE9 ///< Can not create a file because the disk is full
|
||||||
|
#define FS_DIRFULL 0xFFFFFFE8 ///< Can not create a file because the directory is full
|
||||||
|
#define FS_CANTSEEK 0xFFFFFFE7 ///< Can not seek in the selected location
|
||||||
|
#define FS_SUPERVISOR 0xFFFFFFE6 ///< Selected supervisor in supervisor mode
|
||||||
|
#define FS_THREADNAME 0xFFFFFFE5 ///< A thread with this name already exists
|
||||||
|
#define FS_BUFWRITE 0xFFFFFFE4 ///< Writing to inter-process communication buffers is disallowed
|
||||||
|
#define FS_BACKGROUND 0xFFFFFFE3 ///< Unable to start a background process
|
||||||
|
#define FS_OUTOFLOCK 0xFFFFFFE0 ///< Insufficient lock space
|
||||||
|
#define FS_LOCKED 0xFFFFFFDF ///< Can not access because it is locked
|
||||||
|
#define FS_DRIVEOPENED 0xFFFFFFDE ///< Selected drive has an open handler
|
||||||
|
#define FS_LINKOVER 0xFFFFFFDD ///< The symbolic link is nested over 16 times
|
||||||
|
#define FS_FILEEXIST 0xFFFFFFB0 ///< The file exists
|
||||||
|
|
||||||
|
#define FS_FATAL_MEDIAOFFLINE 0xFFFFFFA3 ///< No media inserted
|
||||||
|
#define FS_FATAL_WRITEPROTECT 0xFFFFFFA2 ///< Write protected
|
||||||
|
#define FS_FATAL_INVALIDCOMMAND 0xFFFFFFA1 ///< Invalid command number
|
||||||
|
#define FS_FATAL_INVALIDUNIT 0xFFFFFFA0 ///< Invalid unit number
|
||||||
|
|
||||||
|
#define HUMAN68K_PATH_MAX 96 ///< Longest path allowed in Human68k
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
/// Human68k name space
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
namespace Human68k {
|
||||||
|
/// File attribute bit
|
||||||
|
enum attribute_t {
|
||||||
|
AT_READONLY = 0x01, ///< Read only attribute
|
||||||
|
AT_HIDDEN = 0x02, ///< Hidden attribute
|
||||||
|
AT_SYSTEM = 0x04, ///< System attribute
|
||||||
|
AT_VOLUME = 0x08, ///< Volume label attribute
|
||||||
|
AT_DIRECTORY = 0x10, ///< Directory attribute
|
||||||
|
AT_ARCHIVE = 0x20, ///< Archive attribute
|
||||||
|
AT_ALL = 0xFF, ///< All attribute bits are 1
|
||||||
|
};
|
||||||
|
|
||||||
|
/// File open modes
|
||||||
|
enum open_t {
|
||||||
|
OP_READ = 0, ///< Read
|
||||||
|
OP_WRITE = 1, ///< Write
|
||||||
|
OP_FULL = 2, ///< Read/Write
|
||||||
|
OP_MASK = 0x0F, ///< Decision mask
|
||||||
|
OP_SHARE_NONE = 0x10, ///< Sharing forbidden
|
||||||
|
OP_SHARE_READ = 0x20, ///< Read sharing
|
||||||
|
OP_SHARE_WRITE = 0x30, ///< Write sharing
|
||||||
|
OP_SHARE_FULL = 0x40, ///< Read/Write sharing
|
||||||
|
OP_SHARE_MASK = 0x70, ///< Sharing decision mask
|
||||||
|
OP_SPECIAL = 0x100, ///< Dictionary access
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Seek types
|
||||||
|
enum seek_t {
|
||||||
|
SK_BEGIN = 0, ///< From the beginning of a file
|
||||||
|
SK_CURRENT = 1, ///< From the current location
|
||||||
|
SK_END = 2, ///< From the end of the file
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Media byte
|
||||||
|
enum media_t {
|
||||||
|
MEDIA_2DD_10 = 0xE0, ///< 2DD/10 sector
|
||||||
|
MEDIA_1D_9 = 0xE5, ///< 1D/9 sector
|
||||||
|
MEDIA_2D_9 = 0xE6, ///< 2D/9 sector
|
||||||
|
MEDIA_1D_8 = 0xE7, ///< 1D/8 sector
|
||||||
|
MEDIA_2D_8 = 0xE8, ///< 2D/8 sector
|
||||||
|
MEDIA_2HT = 0xEA, ///< 2HT
|
||||||
|
MEDIA_2HS = 0xEB, ///< 2HS
|
||||||
|
MEDIA_2HDE = 0xEC, ///< 2DDE
|
||||||
|
MEDIA_1DD_9 = 0xEE, ///< 1DD/9 sector
|
||||||
|
MEDIA_1DD_8 = 0xEF, ///< 1DD/8 sector
|
||||||
|
MEDIA_MANUAL = 0xF1, ///< Remote drive (manual eject)
|
||||||
|
MEDIA_REMOVABLE = 0xF2, ///< Remote drive (removable)
|
||||||
|
MEDIA_REMOTE = 0xF3, ///< Remote drive
|
||||||
|
MEDIA_DAT = 0xF4, ///< SCSI-DAT
|
||||||
|
MEDIA_CDROM = 0xF5, ///< SCSI-CDROM
|
||||||
|
MEDIA_MO = 0xF6, ///< SCSI-MO
|
||||||
|
MEDIA_SCSI_HD = 0xF7, ///< SCSI-HD
|
||||||
|
MEDIA_SASI_HD = 0xF8, ///< SASI-HD
|
||||||
|
MEDIA_RAMDISK = 0xF9, ///< RAM disk
|
||||||
|
MEDIA_2HQ = 0xFA, ///< 2HQ
|
||||||
|
MEDIA_2DD_8 = 0xFB, ///< 2DD/8 sector
|
||||||
|
MEDIA_2DD_9 = 0xFC, ///< 2DD/9 sector
|
||||||
|
MEDIA_2HC = 0xFD, ///< 2HC
|
||||||
|
MEDIA_2HD = 0xFE, ///< 2HD
|
||||||
|
};
|
||||||
|
|
||||||
|
struct namests_t {
|
||||||
|
BYTE wildcard; ///< Wildcard character length
|
||||||
|
BYTE drive; ///< Drive number
|
||||||
|
BYTE path[65]; ///< Path (subdirectory +/)
|
||||||
|
BYTE name[8]; ///< File name (PADDING 0x20)
|
||||||
|
BYTE ext[3]; ///< Extension (PADDING 0x20)
|
||||||
|
BYTE add[10]; ///< File name addition (PADDING 0x00)
|
||||||
|
|
||||||
|
void GetCopyPath(BYTE* szPath) const;
|
||||||
|
void GetCopyFilename(BYTE* szFilename) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct files_t {
|
||||||
|
BYTE fatr; ///< + 0 search attribute; read-only
|
||||||
|
// BYTE drive; ///< + 1 drive number; read-only
|
||||||
|
DWORD sector; ///< + 2 directory sector; DOS _FILES first address substitute
|
||||||
|
// WORD cluster; ///< + 6 directory cluster; details unknown (unused)
|
||||||
|
WORD offset; ///< + 8 directory entry; write-only
|
||||||
|
// BYTE name[8]; ///< +10 working file name; write-only (unused)
|
||||||
|
// BYTE ext[3]; ///< +18 working extension; write-only (unused)
|
||||||
|
BYTE attr; ///< +21 file attribute; write-only
|
||||||
|
WORD time; ///< +22 last change time of day; write-only
|
||||||
|
WORD date; ///< +24 last change date; write-only
|
||||||
|
DWORD size; ///< +26 file size; write-only
|
||||||
|
BYTE full[23]; ///< +30 full name; write-only
|
||||||
|
};
|
||||||
|
|
||||||
|
struct fcb_t {
|
||||||
|
// BYTE pad00[6]; ///< + 0~+ 5 (unused)
|
||||||
|
DWORD fileptr; ///< + 6~+ 9 file pointer
|
||||||
|
// BYTE pad01[4]; ///< +10~+13 (unused)
|
||||||
|
WORD mode; ///< +14~+15 open mode
|
||||||
|
// BYTE pad02[16]; ///< +16~+31 (unused)
|
||||||
|
// DWORD zero; ///< +32~+35 zeros are written when opened (unused)
|
||||||
|
// BYTE name[8]; ///< +36~+43 file name (PADDING 0x20) (unused)
|
||||||
|
// BYTE ext[3]; ///< +44~+46 extension (PADDING 0x20) (unused)
|
||||||
|
BYTE attr; ///< +47 file attribute
|
||||||
|
// BYTE add[10]; ///< +48~+57 file name addition (PADDING 0x00) (unused)
|
||||||
|
WORD time; ///< +58~+59 last change time of day
|
||||||
|
WORD date; ///< +60~+61 last change date
|
||||||
|
// WORD cluster; ///< +62~+63 cluster number (unused)
|
||||||
|
DWORD size; ///< +64~+67 file size
|
||||||
|
// BYTE pad03[28]; ///< +68~+95 FAT cache (unused)
|
||||||
|
};
|
||||||
|
|
||||||
|
struct capacity_t {
|
||||||
|
WORD freearea; ///< + 0 Number of available clusters
|
||||||
|
WORD clusters; ///< + 2 Total number of clusters
|
||||||
|
WORD sectors; ///< + 4 Number of sectors per cluster
|
||||||
|
WORD bytes; ///< + 6 Number of bytes per sector
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ctrldrive_t {
|
||||||
|
BYTE status; ///< +13 status
|
||||||
|
BYTE pad[3]; ///< Padding
|
||||||
|
};
|
||||||
|
|
||||||
|
struct dpb_t {
|
||||||
|
WORD sector_size; ///< + 0 Number of bytes in one sector
|
||||||
|
BYTE cluster_size; ///< + 2 Number sectors in one cluster -1
|
||||||
|
BYTE shift; ///< + 3 Number of cluster→sector shifts
|
||||||
|
WORD fat_sector; ///< + 4 FAT first sector number
|
||||||
|
BYTE fat_max; ///< + 6 FAT storage quantity
|
||||||
|
BYTE fat_size; ///< + 7 FAT controlled sector number (excluding duplicates)
|
||||||
|
WORD file_max; ///< + 8 Number of files in the root directory
|
||||||
|
WORD data_sector; ///< +10 First sector number of data storage
|
||||||
|
WORD cluster_max; ///< +12 Total number of clusters +1
|
||||||
|
WORD root_sector; ///< +14 First sector number of root directory
|
||||||
|
// DWORD driverentry; ///< +16 Device driver pointer (unused)
|
||||||
|
BYTE media; ///< +20 Media identifier
|
||||||
|
// BYTE flag; ///< +21 Flag used by DPB (unused)
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Directory entry struct
|
||||||
|
struct dirent_t {
|
||||||
|
BYTE name[8]; ///< + 0 File name (PADDING 0x20)
|
||||||
|
BYTE ext[3]; ///< + 8 Extension (PADDING 0x20)
|
||||||
|
BYTE attr; ///< +11 File attribute
|
||||||
|
BYTE add[10]; ///< +12 File name addition (PADDING 0x00)
|
||||||
|
WORD time; ///< +22 Last change time of day
|
||||||
|
WORD date; ///< +24 Last change date
|
||||||
|
WORD cluster; ///< +26 Cluster number
|
||||||
|
DWORD size; ///< +28 File size
|
||||||
|
};
|
||||||
|
|
||||||
|
/// IOCTRL parameter union
|
||||||
|
union ioctrl_t {
|
||||||
|
BYTE buffer[8]; ///< Access in byte units
|
||||||
|
DWORD param; ///< Parameter (First 4 bytes)
|
||||||
|
WORD media; ///< Media byte (First 2 bytes)
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Command line parameter struct
|
||||||
|
/**
|
||||||
|
The driver itself is included in the beginning of the argument,
|
||||||
|
so setting to a length longer than HUMAN68K_PATH_MAX
|
||||||
|
*/
|
||||||
|
struct argument_t {
|
||||||
|
BYTE buf[256]; ///< Command line argument
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Number of FILES buffers
|
||||||
|
/**
|
||||||
|
Under normal circumstances it's enough with just a few buffers,
|
||||||
|
but Human68k multitasking may lead to multiple threads working
|
||||||
|
deeply in the system, which is why this value is set this high.
|
||||||
|
|
||||||
|
Default is 20 buffers.
|
||||||
|
*/
|
||||||
|
#define XM6_HOST_FILES_MAX 20
|
||||||
|
|
||||||
|
/// Number of FCB buffers
|
||||||
|
/**
|
||||||
|
This decides how many files can be opened at the same time.
|
||||||
|
|
||||||
|
Default is 100 files.
|
||||||
|
*/
|
||||||
|
#define XM6_HOST_FCB_MAX 100
|
||||||
|
|
||||||
|
/// Max number of virtual clusters and sectors
|
||||||
|
/**
|
||||||
|
Number of virtual sectors used for accessing the first sector of a file entity.
|
||||||
|
Allocating a generous amount to exceed the number of threads lzdsys uses for access.
|
||||||
|
|
||||||
|
Default is 10 sectors.
|
||||||
|
*/
|
||||||
|
#define XM6_HOST_PSEUDO_CLUSTER_MAX 10
|
||||||
|
|
||||||
|
/// Number of caches for directory entries
|
||||||
|
/**
|
||||||
|
Human68k carries out a large number of checks of directory entries when doing an operation
|
||||||
|
inside a subdirectory. This specifies the number of caches used to speed up this operation.
|
||||||
|
Cache is allocated per drive. The more you add the faster it gets, but use too many
|
||||||
|
and the host OS gets under a heavy load, so be careful.
|
||||||
|
|
||||||
|
Default is 16.
|
||||||
|
*/
|
||||||
|
#define XM6_HOST_DIRENTRY_CACHE_MAX 16
|
||||||
|
|
||||||
|
/// Max number of entries that can be stored per directory
|
||||||
|
/**
|
||||||
|
When a large number of files are stored in a directory, a larger amount of data than
|
||||||
|
contemporanous applications can handle will be returned. This may lead to errors such as
|
||||||
|
partial data being recognized, performance dropping significantly, or OOM crashes.
|
||||||
|
To guard against this, an upper limit is defined here. In the case of a particular
|
||||||
|
file manager, the upper limit is 2560 files. This is one good example to use as reference.
|
||||||
|
|
||||||
|
Default is around 60000 entries. (Upper limit of the FAT root directory)
|
||||||
|
*/
|
||||||
|
#define XM6_HOST_DIRENTRY_FILE_MAX 65535
|
||||||
|
|
||||||
|
/// Max number of patterns for file name deduplication
|
||||||
|
/**
|
||||||
|
The file names on the Human68k side are automatically created based on the file system on
|
||||||
|
the host side. However, Human68k have stricter file name length restrictions than the host has.
|
||||||
|
Because of this, there is a risk that file name duplication will occur. When this happens,
|
||||||
|
WindrvXM will use a certain renaming heuristic to generate alternate file names to resolve
|
||||||
|
the duplication. Theoretically, there are over 60 million (36^5) unique file names that
|
||||||
|
can be generated by this method. However, in reality any more than a few hundred
|
||||||
|
deduplications will take excessive processing time. So here an upper limit to deduplication
|
||||||
|
is set in order to maintain system performance. If a system is operated with common sense,
|
||||||
|
you should only need a few dozen deduplication patterns, so this value can be kept low
|
||||||
|
to further improve performance. In the case deduplication is not carried out, multiple files
|
||||||
|
with the same name will be created. When trying to access such files,
|
||||||
|
only the first entry will ever be accessed.
|
||||||
|
|
||||||
|
Default is 36 patterns.
|
||||||
|
*/
|
||||||
|
#define XM6_HOST_FILENAME_PATTERN_MAX 36
|
||||||
|
|
||||||
|
/// Duplicate file identification mark
|
||||||
|
/**
|
||||||
|
A symbol used to distinguish between host and Human68k files.
|
||||||
|
Do not use a command shell escape character, or similar protected symbol.
|
||||||
|
|
||||||
|
Default is '@'.
|
||||||
|
*/
|
||||||
|
#define XM6_HOST_FILENAME_MARK '@'
|
||||||
|
|
||||||
|
/// WINDRV operational flags
|
||||||
|
/**
|
||||||
|
Normally set to 0. When put in the OS trash can for deletion, it is set to 1.
|
||||||
|
Other values are reserved for future use.
|
||||||
|
Can be used for future extentions such as internal operational flags or mock media byte.
|
||||||
|
*/
|
||||||
|
enum {
|
||||||
|
WINDRV_OPT_REMOVE = 0x00000001, ///< Bit 0: File delete process 0:Directly 1:Trash can
|
||||||
|
WINDRV_OPT_ALPHABET = 0x00000020, ///< Bit 5: File name comparison; Alphabet distinction 0:No 1:Yes 0:-C 1:+C
|
||||||
|
WINDRV_OPT_COMPARE_LENGTH = 0x00000040, ///< Bit 6: File name comparison; String length (unimplemented) 0:18+3 1:8+3 0:+T 1:-T
|
||||||
|
WINDRV_OPT_CONVERT_LENGTH = 0x00000080, ///< Bit 7: File name conversion; String length 0:18+3 1:8+3 0:-A 1:+A
|
||||||
|
WINDRV_OPT_CONVERT_SPACE = 0x00000100, ///< Bit 8: File name conversion; Space 0:No 1:'_'
|
||||||
|
WINDRV_OPT_CONVERT_BADCHAR = 0x00000200, ///< Bit 9: File name conversion; Invalid char 0:No 1:'_'
|
||||||
|
WINDRV_OPT_CONVERT_HYPHENS = 0x00000400, ///< Bit10: File name conversion; Middle hyphen 0:No 1:'_'
|
||||||
|
WINDRV_OPT_CONVERT_HYPHEN = 0x00000800, ///< Bit11: File name conversion; Initial hyphen 0:No 1:'_'
|
||||||
|
WINDRV_OPT_CONVERT_PERIODS = 0x00001000, ///< Bit12: File name conversion; Middle period 0:No 1:'_'
|
||||||
|
WINDRV_OPT_CONVERT_PERIOD = 0x00002000, ///< Bit13: File name conversion; Initial period 0:No 1:'_'
|
||||||
|
WINDRV_OPT_REDUCED_SPACE = 0x00010000, ///< Bit16: File name reduction; Space 0:No 1:Reduced
|
||||||
|
WINDRV_OPT_REDUCED_BADCHAR = 0x00020000, ///< Bit17: File name reduction; Invalid char 0:No 1:Reduced
|
||||||
|
WINDRV_OPT_REDUCED_HYPHENS = 0x00040000, ///< Bit18: File name reduction Middle hyphen 0:No 1:Reduced
|
||||||
|
WINDRV_OPT_REDUCED_HYPHEN = 0x00080000, ///< Bit19: File name reduction Initial hyphen 0:No 1:Reduced
|
||||||
|
WINDRV_OPT_REDUCED_PERIODS = 0x00100000, ///< Bit20: File name reduction Middle period 0:No 1:Reduced
|
||||||
|
WINDRV_OPT_REDUCED_PERIOD = 0x00200000, ///< Bit21: File name reduction Initial period 0:No 1:Reduced
|
||||||
|
// Bit24~30 Duplicate file identification mark 0:Automatic 1~127:Chars
|
||||||
|
};
|
||||||
|
|
||||||
|
/// File system operational flag
|
||||||
|
/**
|
||||||
|
Normal is 0. Becomes 1 if attempting to mount in read-only mode.
|
||||||
|
Reserving the other values for future use.
|
||||||
|
Insurance against hard-to-detect devices such as homemade USB storage.
|
||||||
|
*/
|
||||||
|
enum {
|
||||||
|
FSFLAG_WRITE_PROTECT = 0x00000001, ///< Bit0: Force write protect
|
||||||
|
FSFLAG_REMOVABLE = 0x00000002, ///< Bit1: Force removable media
|
||||||
|
FSFLAG_MANUAL = 0x00000004, ///< Bit2: Force manual eject
|
||||||
|
};
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
/// Full ring list
|
||||||
|
///
|
||||||
|
/// First (root.next) is the most recent object.
|
||||||
|
/// Last (root.prev) is the oldest / unused object.
|
||||||
|
/// For code optimization purposes, always upcast the pointer when deleting.
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
class CRing {
|
||||||
|
public:
|
||||||
|
CRing() { Init(); }
|
||||||
|
~CRing() { Remove(); }
|
||||||
|
void Init() { next = prev = this; }
|
||||||
|
|
||||||
|
CRing* Next() const { return next; } ///< Get the next element
|
||||||
|
CRing* Prev() const { return prev; } ///< Get the previous element
|
||||||
|
|
||||||
|
void Insert(CRing* pRoot)
|
||||||
|
{
|
||||||
|
// Separate the relevant objects
|
||||||
|
ASSERT(next);
|
||||||
|
ASSERT(prev);
|
||||||
|
next->prev = prev;
|
||||||
|
prev->next = next;
|
||||||
|
// Insert into the beginning of the ring
|
||||||
|
ASSERT(pRoot);
|
||||||
|
ASSERT(pRoot->next);
|
||||||
|
next = pRoot->next;
|
||||||
|
prev = pRoot;
|
||||||
|
pRoot->next->prev = this;
|
||||||
|
pRoot->next = this;
|
||||||
|
}
|
||||||
|
///< Separate objects & insert into the beginning of the ring
|
||||||
|
|
||||||
|
void InsertTail(CRing* pRoot)
|
||||||
|
{
|
||||||
|
// Separate the relevant objects
|
||||||
|
ASSERT(next);
|
||||||
|
ASSERT(prev);
|
||||||
|
next->prev = prev;
|
||||||
|
prev->next = next;
|
||||||
|
// Insert into the end of the ring
|
||||||
|
ASSERT(pRoot);
|
||||||
|
ASSERT(pRoot->prev);
|
||||||
|
next = pRoot;
|
||||||
|
prev = pRoot->prev;
|
||||||
|
pRoot->prev->next = this;
|
||||||
|
pRoot->prev = this;
|
||||||
|
}
|
||||||
|
///< Separate objects & insert into the end of the ring
|
||||||
|
|
||||||
|
void InsertRing(CRing* pRoot)
|
||||||
|
{
|
||||||
|
if (next == prev) return;
|
||||||
|
|
||||||
|
// Insert into the beginning of the ring
|
||||||
|
ASSERT(pRoot);
|
||||||
|
ASSERT(pRoot->next);
|
||||||
|
pRoot->next->prev = prev;
|
||||||
|
prev->next = pRoot->next;
|
||||||
|
pRoot->next = next;
|
||||||
|
next->prev = pRoot;
|
||||||
|
|
||||||
|
// Empty self
|
||||||
|
next = prev = this;
|
||||||
|
}
|
||||||
|
///< Separate objects except self & insert into the beginning of the ring
|
||||||
|
|
||||||
|
void Remove()
|
||||||
|
{
|
||||||
|
// Separate the relevant objects
|
||||||
|
ASSERT(next);
|
||||||
|
ASSERT(prev);
|
||||||
|
next->prev = prev;
|
||||||
|
prev->next = next;
|
||||||
|
// To be safe, assign self (nothing stops you from separating any number of times)
|
||||||
|
next = prev = this;
|
||||||
|
}
|
||||||
|
///< Separate objects
|
||||||
|
|
||||||
|
private:
|
||||||
|
CRing* next; ///< Next element
|
||||||
|
CRing* prev; ///< Previous element
|
||||||
|
};
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
/// Directory Entry: File Name
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
class CHostFilename {
|
||||||
|
public:
|
||||||
|
CHostFilename();
|
||||||
|
static size_t Offset() { return offsetof(CHostFilename, m_szHost); } ///< Get offset location
|
||||||
|
|
||||||
|
void SetHost(const TCHAR* szHost); ///< Set the name of the host
|
||||||
|
const TCHAR* GetHost() const { return m_szHost; } ///< Get the name of the host
|
||||||
|
void ConvertHuman(int nCount = -1); ///< Convert the Human68k name
|
||||||
|
void CopyHuman(const BYTE* szHuman); ///< Copy the Human68k name
|
||||||
|
BOOL isReduce() const; ///< Inspect if the Human68k name is generated
|
||||||
|
BOOL isCorrect() const { return m_bCorrect; } ///< Inspect if the Human68k file name adhers to naming rules
|
||||||
|
const BYTE* GetHuman() const { return m_szHuman; } ///< Get Human68k file name
|
||||||
|
const BYTE* GetHumanLast() const
|
||||||
|
{ return m_pszHumanLast; } ///< Get Human68k file name
|
||||||
|
const BYTE* GetHumanExt() const { return m_pszHumanExt; }///< Get Human68k file name
|
||||||
|
void SetEntryName(); ///< Set Human68k directory entry
|
||||||
|
void SetEntryAttribute(BYTE nHumanAttribute)
|
||||||
|
{ m_dirHuman.attr = nHumanAttribute; } ///< Set Human68k directory entry
|
||||||
|
void SetEntrySize(DWORD nHumanSize)
|
||||||
|
{ m_dirHuman.size = nHumanSize; } ///< Set Human68k directory entry
|
||||||
|
void SetEntryDate(WORD nHumanDate)
|
||||||
|
{ m_dirHuman.date = nHumanDate; } ///< Set Human68k directory entry
|
||||||
|
void SetEntryTime(WORD nHumanTime)
|
||||||
|
{ m_dirHuman.time = nHumanTime; } ///< Set Human68k directory entry
|
||||||
|
void SetEntryCluster(WORD nHumanCluster)
|
||||||
|
{ m_dirHuman.cluster = nHumanCluster; } ///< Set Human68k directory entry
|
||||||
|
const Human68k::dirent_t* GetEntry() const
|
||||||
|
{ return &m_dirHuman; } ///< Get Human68k directory entry
|
||||||
|
BOOL CheckAttribute(DWORD nHumanAttribute) const; ///< Determine Human68k directory entry attributes
|
||||||
|
BOOL isSameEntry(const Human68k::dirent_t* pdirHuman) const
|
||||||
|
{ ASSERT(pdirHuman); return memcmp(&m_dirHuman, pdirHuman, sizeof(m_dirHuman)) == 0; }
|
||||||
|
///< Determine Human68k directory entry match
|
||||||
|
|
||||||
|
// Path name operations
|
||||||
|
static const BYTE* SeparateExt(const BYTE* szHuman); ///< Extract extension from Human68k file name
|
||||||
|
|
||||||
|
private:
|
||||||
|
static BYTE* CopyName(BYTE* pWrite, const BYTE* pFirst, const BYTE* pLast);
|
||||||
|
///< Copy Human68k file name elements
|
||||||
|
|
||||||
|
const BYTE* m_pszHumanLast; ///< Last position of the Human68k internal name of the relevant entry
|
||||||
|
const BYTE* m_pszHumanExt; ///< Position of the extension of the Human68k internal name of the relevant entry
|
||||||
|
BOOL m_bCorrect; ///< TRUE if the relevant entry of the Human68k internal name is correct
|
||||||
|
BYTE m_szHuman[24]; ///< Human68k internal name of the relevant entry
|
||||||
|
Human68k::dirent_t m_dirHuman; ///< All information for the Human68k relevant entry
|
||||||
|
TCHAR m_szHost[FILEPATH_MAX]; ///< The host name of the relevant entry (variable length)
|
||||||
|
};
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
/// Directory entry: path name
|
||||||
|
///
|
||||||
|
/// A file path in Human68k always begings with / and ends with /
|
||||||
|
/// They don't hold unit numbers.
|
||||||
|
/// Include the base path part of the name on the host side for a performance boost.
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
/** @note
|
||||||
|
Most Human68k applications are written in a way that expects time stamps not to
|
||||||
|
get updated for new directories created as a result of file operations, which
|
||||||
|
triggers updates to directory entires.
|
||||||
|
However, on the host file system, new directories do typically get an updated time stamp.
|
||||||
|
|
||||||
|
The unfortunate outcome is that when copying a directory for instance, the time stamp
|
||||||
|
will get overwritten even if the application did not intend for the time stamp to get updated.
|
||||||
|
|
||||||
|
Here follows an implementation of a directory cache FAT time stamp emulation feature.
|
||||||
|
At the time of a file system update on the host side, time stamp information will be restored
|
||||||
|
in order to achieve expected behavior on the Human68k side.
|
||||||
|
*/
|
||||||
|
class CHostPath: public CRing {
|
||||||
|
/// For memory management
|
||||||
|
struct ring_t {
|
||||||
|
CRing r;
|
||||||
|
CHostFilename f;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Search buffer
|
||||||
|
struct find_t {
|
||||||
|
DWORD count; ///< Search execution count + 1 (When 0 the below value is invalid)
|
||||||
|
DWORD id; ///< Entry unique ID for the path of the next search
|
||||||
|
const ring_t* pos; ///< Position of the next search (When identical to unique ID)
|
||||||
|
Human68k::dirent_t entry; ///< Contents of the next seach entry
|
||||||
|
|
||||||
|
void Clear() { count = 0; } ///< Initialize
|
||||||
|
};
|
||||||
|
|
||||||
|
CHostPath();
|
||||||
|
~CHostPath();
|
||||||
|
void Clean(); ///< Initialialize for reuse
|
||||||
|
|
||||||
|
void SetHuman(const BYTE* szHuman); ///< Directly specify the name on the Human68k side
|
||||||
|
void SetHost(const TCHAR* szHost); ///< Directly specify the name on the host side
|
||||||
|
BOOL isSameHuman(const BYTE* szHuman) const; ///< Compare the name on the Human68k side
|
||||||
|
BOOL isSameChild(const BYTE* szHuman) const; ///< Compare the name on the Human68k side
|
||||||
|
const TCHAR* GetHost() const { return m_szHost; } ///< Obtain the name on the host side
|
||||||
|
const CHostFilename* FindFilename(const BYTE* szHuman, DWORD nHumanAttribute = Human68k::AT_ALL) const;
|
||||||
|
///< Find file name
|
||||||
|
const CHostFilename* FindFilenameWildcard(const BYTE* szHuman, DWORD nHumanAttribute, find_t* pFind) const;
|
||||||
|
///< Find file name (with support for wildcards)
|
||||||
|
BOOL isRefresh(); ///< Check that the file change has been done
|
||||||
|
void Refresh(); ///< Refresh file
|
||||||
|
void Backup(); /// Backup the time stamp on the host side
|
||||||
|
void Restore() const; /// Restore the time stamp on the host side
|
||||||
|
void Release(); ///< Update
|
||||||
|
|
||||||
|
// CHostEntry is an external API that we use
|
||||||
|
static void InitId() { g_nId = 0; } ///< Initialize the counter for the unique ID generation
|
||||||
|
|
||||||
|
private:
|
||||||
|
static ring_t* Alloc(size_t nLength); ///< Allocate memory for the file name
|
||||||
|
static void Free(ring_t* pRing); ///< Release memory for the file name
|
||||||
|
static int Compare(const BYTE* pFirst, const BYTE* pLast, const BYTE* pBufFirst, const BYTE* pBufLast);
|
||||||
|
///< Compare string (with support for wildcards)
|
||||||
|
|
||||||
|
CRing m_cRing; ///< For CHostFilename linking
|
||||||
|
time_t m_tBackup; ///< For time stamp restoration
|
||||||
|
BOOL m_bRefresh; ///< Refresh flag
|
||||||
|
DWORD m_nId; ///< Unique ID (When the value has changed, it means an update has been made)
|
||||||
|
BYTE m_szHuman[HUMAN68K_PATH_MAX]; ///< The internal Human68k name for the relevant entry
|
||||||
|
TCHAR m_szHost[FILEPATH_MAX]; ///< The host side name for the relevant entry
|
||||||
|
|
||||||
|
static DWORD g_nId; ///< Counter for the unique ID generation
|
||||||
|
};
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
/// File search processing
|
||||||
|
///
|
||||||
|
/// It's pretty much impossible to process Human68k file names as Unicode internally.
|
||||||
|
/// So, we carry out binary conversion for processing. We leave it up to the
|
||||||
|
/// directory entry cache to handle the conversion, which allows WINDRV to read
|
||||||
|
/// everything as Shift-JIS. Additionally, it allows Human68k names to be
|
||||||
|
/// fully independent of base path assignments.
|
||||||
|
///
|
||||||
|
/// We create directory entry cache just before file handling.
|
||||||
|
/// Since creating directory entires is very costly, we try to reuse created entries
|
||||||
|
/// as much as humanly possible.
|
||||||
|
///
|
||||||
|
/// There are three kinds of file search. They are all processed in CHostFiles::Find()
|
||||||
|
/// 1. Search by path name only; the only attribute is 'directory'; _CHKDIR _CREATE
|
||||||
|
/// 2. Path + file name + attribute search; _OPEN
|
||||||
|
/// 3. Path + wildcard + attribute search; _FILES _NFILES
|
||||||
|
/// The search results are kept as directory entry data.
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
class CHostFiles {
|
||||||
|
public:
|
||||||
|
CHostFiles() { SetKey(0); Init(); }
|
||||||
|
void Init();
|
||||||
|
|
||||||
|
void SetKey(DWORD nKey) { m_nKey = nKey; } ///< Set search key
|
||||||
|
BOOL isSameKey(DWORD nKey) const { return m_nKey == nKey; } ///< Compare search key
|
||||||
|
void SetPath(const Human68k::namests_t* pNamests); ///< Create path and file name internally
|
||||||
|
BOOL isRootPath() const { return m_szHumanPath[1] == '\0'; } ///< Check if root directory
|
||||||
|
void SetPathWildcard() { m_nHumanWildcard = 1; } ///< Enable file search using wildcards
|
||||||
|
void SetPathOnly() { m_nHumanWildcard = 0xFF; } ///< Enable only path names
|
||||||
|
BOOL isPathOnly() const { return m_nHumanWildcard == 0xFF; } ///< Check if set to only path names
|
||||||
|
void SetAttribute(DWORD nHumanAttribute) { m_nHumanAttribute = nHumanAttribute; }
|
||||||
|
///< Set search attribute
|
||||||
|
BOOL Find(DWORD nUnit, class CHostEntry* pEntry); ///< Find files on the Human68k side, generating data on the host side
|
||||||
|
const CHostFilename* Find(CHostPath* pPath); ///< Find file name
|
||||||
|
void SetEntry(const CHostFilename* pFilename); ///< Store search results on the Human68k side
|
||||||
|
void SetResult(const TCHAR* szPath); ///< Set names on the host side
|
||||||
|
void AddResult(const TCHAR* szPath); ///< Add file name to the name on the host side
|
||||||
|
void AddFilename(); ///< Add the new Human68k file name to the name on the host side
|
||||||
|
|
||||||
|
const TCHAR* GetPath() const { return m_szHostResult; } ///< Get the name on the host side
|
||||||
|
|
||||||
|
const Human68k::dirent_t* GetEntry() const { return &m_dirHuman; }///< Get Human68k directory entry
|
||||||
|
|
||||||
|
DWORD GetAttribute() const { return m_dirHuman.attr; } ///< Get Human68k attribute
|
||||||
|
WORD GetDate() const { return m_dirHuman.date; } ///< Get Human68k date
|
||||||
|
WORD GetTime() const { return m_dirHuman.time; } ///< Get Human68k time
|
||||||
|
DWORD GetSize() const { return m_dirHuman.size; } ///< Get Human68k file size
|
||||||
|
const BYTE* GetHumanFilename() const { return m_szHumanFilename; }///< Get Human68k file name
|
||||||
|
const BYTE* GetHumanResult() const { return m_szHumanResult; } ///< Get Human68k file name search results
|
||||||
|
const BYTE* GetHumanPath() const { return m_szHumanPath; } ///< Get Human68k path name
|
||||||
|
|
||||||
|
private:
|
||||||
|
DWORD m_nKey; ///< FILES buffer address for Human68k; 0 is unused
|
||||||
|
DWORD m_nHumanWildcard; ///< Human68k wildcard data
|
||||||
|
DWORD m_nHumanAttribute; ///< Human68k search attribute
|
||||||
|
CHostPath::find_t m_findNext; ///< Next search location data
|
||||||
|
Human68k::dirent_t m_dirHuman; ///< Search results: Human68k file data
|
||||||
|
BYTE m_szHumanFilename[24]; ///< Human68k file name
|
||||||
|
BYTE m_szHumanResult[24]; ///< Search results: Human68k file name
|
||||||
|
BYTE m_szHumanPath[HUMAN68K_PATH_MAX];
|
||||||
|
///< Human68k path name
|
||||||
|
TCHAR m_szHostResult[FILEPATH_MAX]; ///< Search results: host's full path name
|
||||||
|
};
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
/// File search memory manager
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
class CHostFilesManager {
|
||||||
|
public:
|
||||||
|
#ifdef _DEBUG
|
||||||
|
~CHostFilesManager();
|
||||||
|
#endif // _DEBUG
|
||||||
|
void Init(); ///< Initialization (when the driver is installed)
|
||||||
|
void Clean(); ///< Release (when starting up or resetting)
|
||||||
|
|
||||||
|
CHostFiles* Alloc(DWORD nKey);
|
||||||
|
CHostFiles* Search(DWORD nKey);
|
||||||
|
void Free(CHostFiles* pFiles);
|
||||||
|
private:
|
||||||
|
/// For memory management
|
||||||
|
struct ring_t {
|
||||||
|
CRing r;
|
||||||
|
CHostFiles f;
|
||||||
|
};
|
||||||
|
|
||||||
|
CRing m_cRing; ///< For attaching to CHostFiles
|
||||||
|
};
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
/// FCB processing
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
class CHostFcb {
|
||||||
|
public:
|
||||||
|
CHostFcb() { SetKey(0); Init(); }
|
||||||
|
~CHostFcb() { Close(); }
|
||||||
|
void Init();
|
||||||
|
|
||||||
|
void SetKey(DWORD nKey) { m_nKey = nKey; } ///< Set search key
|
||||||
|
BOOL isSameKey(DWORD nKey) const { return m_nKey == nKey; } ///< Compare search key
|
||||||
|
void SetUpdate() { m_bUpdate = TRUE; } ///< Update
|
||||||
|
BOOL isUpdate() const { return m_bUpdate; } ///< Get update state
|
||||||
|
BOOL SetMode(DWORD nHumanMode); ///< Set file open mode
|
||||||
|
void SetFilename(const TCHAR* szFilename); ///< Set file name
|
||||||
|
void SetHumanPath(const BYTE* szHumanPath); ///< Set Human68k path name
|
||||||
|
const BYTE* GetHumanPath() const { return m_szHumanPath; } ///< Get Human68k path name
|
||||||
|
|
||||||
|
BOOL Create(Human68k::fcb_t* pFcb, DWORD nHumanAttribute, BOOL bForce); ///< Create file
|
||||||
|
BOOL Open(); ///< Open file
|
||||||
|
BOOL Rewind(DWORD nOffset); ///< Seek file
|
||||||
|
DWORD Read(BYTE* pBuffer, DWORD nSize); ///< Read file
|
||||||
|
DWORD Write(const BYTE* pBuffer, DWORD nSize); ///< Write file
|
||||||
|
BOOL Truncate(); ///< Truncate file
|
||||||
|
DWORD Seek(DWORD nOffset, DWORD nHumanSeek); ///< Seek file
|
||||||
|
BOOL TimeStamp(DWORD nHumanTime); ///< Set file time stamp
|
||||||
|
BOOL Close(); ///< Close file
|
||||||
|
|
||||||
|
private:
|
||||||
|
DWORD m_nKey; ///< Human68k FCB buffer address (0 if unused)
|
||||||
|
BOOL m_bUpdate; ///< Update flag
|
||||||
|
FILE* m_pFile; ///< Host side file object
|
||||||
|
const char* m_pszMode; ///< Host side file open mode
|
||||||
|
bool m_bFlag; ///< Host side file open flag
|
||||||
|
BYTE m_szHumanPath[HUMAN68K_PATH_MAX];
|
||||||
|
///< Human68k path name
|
||||||
|
TCHAR m_szFilename[FILEPATH_MAX]; ///< Host side file name
|
||||||
|
};
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
/// FCB processing manager
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
class CHostFcbManager {
|
||||||
|
public:
|
||||||
|
#ifdef _DEBUG
|
||||||
|
~CHostFcbManager();
|
||||||
|
#endif // _DEBUG
|
||||||
|
void Init(); ///< Initialization (when the driver is installed)
|
||||||
|
void Clean(); ///< Release (when starting up or resetting)
|
||||||
|
|
||||||
|
CHostFcb* Alloc(DWORD nKey);
|
||||||
|
CHostFcb* Search(DWORD nKey);
|
||||||
|
void Free(CHostFcb* p);
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// For memory management
|
||||||
|
struct ring_t {
|
||||||
|
CRing r;
|
||||||
|
CHostFcb f;
|
||||||
|
};
|
||||||
|
|
||||||
|
CRing m_cRing; ///< For attaching to CHostFcb
|
||||||
|
};
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
/// Host side drive
|
||||||
|
///
|
||||||
|
/// Keeps the required data for each drive, managed in CHostEntry.
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
class CHostDrv
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CHostDrv();
|
||||||
|
~CHostDrv();
|
||||||
|
void Init(const TCHAR* szBase, DWORD nFlag); ///< Initialization (device startup and load)
|
||||||
|
|
||||||
|
BOOL isWriteProtect() const { return m_bWriteProtect; }
|
||||||
|
BOOL isEnable() const { return m_bEnable; } ///< Is it accessible?
|
||||||
|
BOOL isMediaOffline();
|
||||||
|
BYTE GetMediaByte() const;
|
||||||
|
DWORD GetStatus() const;
|
||||||
|
void SetEnable(BOOL bEnable); ///< Set media status
|
||||||
|
BOOL CheckMedia(); ///< Check if media was changed
|
||||||
|
void Update(); ///< Update media status
|
||||||
|
void Eject();
|
||||||
|
void GetVolume(TCHAR* szLabel); ///< Get volume label
|
||||||
|
BOOL GetVolumeCache(TCHAR* szLabel) const; ///< Get volume label from cache
|
||||||
|
DWORD GetCapacity(Human68k::capacity_t* pCapacity);
|
||||||
|
BOOL GetCapacityCache(Human68k::capacity_t* pCapacity) const; ///< Get capacity from cache
|
||||||
|
|
||||||
|
// Cache operations
|
||||||
|
void CleanCache(); ///< Update all cache
|
||||||
|
void CleanCache(const BYTE* szHumanPath); ///< Update cache for the specified path
|
||||||
|
void CleanCacheChild(const BYTE* szHumanPath); ///< Update all cache below the specified path
|
||||||
|
void DeleteCache(const BYTE* szHumanPath); ///< Delete the cache for the specified path
|
||||||
|
CHostPath* FindCache(const BYTE* szHuman); ///< Inspect if the specified path is cached
|
||||||
|
CHostPath* CopyCache(CHostFiles* pFiles); ///< Acquire the host side name on the basis of cache information
|
||||||
|
CHostPath* MakeCache(CHostFiles* pFiles); ///< Get all required data to construct a host side name
|
||||||
|
BOOL Find(CHostFiles* pFiles); ///< Find host side name (path + file name (can be abbreviated) + attribute)
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Path name operations
|
||||||
|
static const BYTE* SeparateCopyFilename(const BYTE* szHuman, BYTE* szBuffer);
|
||||||
|
///< Split and copy the first element of the Human68k full path name
|
||||||
|
|
||||||
|
void Lock() {}
|
||||||
|
void Unlock() {}
|
||||||
|
|
||||||
|
/// For memory management
|
||||||
|
struct ring_t {
|
||||||
|
CRing r;
|
||||||
|
CHostPath f;
|
||||||
|
};
|
||||||
|
|
||||||
|
BOOL m_bWriteProtect; ///< TRUE if write-protected
|
||||||
|
BOOL m_bEnable; ///< TRUE if media is usable
|
||||||
|
DWORD m_nRing; ///< Number of stored path names
|
||||||
|
CRing m_cRing; ///< For attaching to CHostPath
|
||||||
|
Human68k::capacity_t m_capCache; ///< Sector data cache: if "sectors == 0" then not cached
|
||||||
|
BOOL m_bVolumeCache; ///< TRUE if the volume label has been read
|
||||||
|
TCHAR m_szVolumeCache[24]; ///< Volume label cache
|
||||||
|
TCHAR m_szBase[FILEPATH_MAX]; ///< Base path
|
||||||
|
};
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
/// Directory entry management
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
class CHostEntry {
|
||||||
|
public:
|
||||||
|
CHostEntry();
|
||||||
|
~CHostEntry();
|
||||||
|
void Init(); ///< Initialization (when the driver is installed)
|
||||||
|
void Clean(); ///< Release (when starting up or resetting)
|
||||||
|
|
||||||
|
// Cache operations
|
||||||
|
void CleanCache(); ///< Update all cache
|
||||||
|
void CleanCache(DWORD nUnit); ///< Update cache for the specified unit
|
||||||
|
void CleanCache(DWORD nUnit, const BYTE* szHumanPath); ///< Update cache for the specified path
|
||||||
|
void CleanCacheChild(DWORD nUnit, const BYTE* szHumanPath); ///< Update cache below the specified path
|
||||||
|
void DeleteCache(DWORD nUnit, const BYTE* szHumanPath); ///< Delete cache for the specified path
|
||||||
|
BOOL Find(DWORD nUnit, CHostFiles* pFiles); ///< Find host side name (path + file name (can be abbreviated) + attribute)
|
||||||
|
void ShellNotify(DWORD nEvent, const TCHAR* szPath); ///< Notify status change in the host side file system
|
||||||
|
|
||||||
|
// Drive object operations
|
||||||
|
void SetDrv(DWORD nUnit, CHostDrv* pDrv);
|
||||||
|
BOOL isWriteProtect(DWORD nUnit) const;
|
||||||
|
BOOL isEnable(DWORD nUnit) const; ///< Is it accessible?
|
||||||
|
BOOL isMediaOffline(DWORD nUnit);
|
||||||
|
BYTE GetMediaByte(DWORD nUnit) const;
|
||||||
|
DWORD GetStatus(DWORD nUnit) const; ///< Get drive status
|
||||||
|
BOOL CheckMedia(DWORD nUnit); ///< Media change check
|
||||||
|
void Eject(DWORD nUnit);
|
||||||
|
void GetVolume(DWORD nUnit, TCHAR* szLabel); ///< Get volume label
|
||||||
|
BOOL GetVolumeCache(DWORD nUnit, TCHAR* szLabel) const; ///< Get volume label from cache
|
||||||
|
DWORD GetCapacity(DWORD nUnit, Human68k::capacity_t* pCapacity);
|
||||||
|
BOOL GetCapacityCache(DWORD nUnit, Human68k::capacity_t* pCapacity) const;
|
||||||
|
///< Get cluster size from cache
|
||||||
|
|
||||||
|
enum {
|
||||||
|
DriveMax = 10 ///< Max number of drive candidates
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
CHostDrv* m_pDrv[DriveMax]; ///< Host side drive object
|
||||||
|
DWORD m_nTimeout; ///< Last time a timeout check was carried out
|
||||||
|
};
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
/// Host side file system
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
/** @note
|
||||||
|
Current state of affairs:
|
||||||
|
|
||||||
|
While it violates the design philosophy of XM6, we should find a way for
|
||||||
|
'class Windrv' and 'class CWindrv' to have a direct pointer to 'class CFileSys'.
|
||||||
|
This way, we get the following benefits.
|
||||||
|
|
||||||
|
Benefit no. 1
|
||||||
|
Makes it possible to manage a large number of command handler methods in one place.
|
||||||
|
There is a high chance the command handlers will change drastically because of
|
||||||
|
host system architectural changes, so we will save a huge amount of maintenance work
|
||||||
|
in the long run.
|
||||||
|
|
||||||
|
Benefit no. 2
|
||||||
|
We would get rid of virtual funcion code for processing table creation and lookup.
|
||||||
|
It is not feasible to implement code in XM6 for simultaneous use of file system objects.
|
||||||
|
Therefore file system object polymorphism is a waste of CPU cycles.
|
||||||
|
|
||||||
|
I made the change as an experiment. Performance did improve.
|
||||||
|
The improvement was obvious from looking at the source the compiler spit out
|
||||||
|
after changing the FILESYS_FAST_STRUCTURE value in windrv.h.
|
||||||
|
You may understand now why I decided to rant here.
|
||||||
|
|
||||||
|
The easy solution is to put the content of 'class CFileSys' into 'class CWindrv'.
|
||||||
|
(To be honest, I really want to deprecate 'class CHost'... I wonder if there's a good way...)
|
||||||
|
*/
|
||||||
|
class CFileSys
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CFileSys();
|
||||||
|
virtual ~CFileSys() {};
|
||||||
|
|
||||||
|
void Reset(); ///< Reset (close all)
|
||||||
|
void Init(); ///< Initialization (device startup and load)
|
||||||
|
|
||||||
|
// Command handlers
|
||||||
|
DWORD InitDevice(const Human68k::argument_t* pArgument); ///< $40 - Device startup
|
||||||
|
int CheckDir(DWORD nUnit, const Human68k::namests_t* pNamests); ///< $41 - Directory check
|
||||||
|
int MakeDir(DWORD nUnit, const Human68k::namests_t* pNamests); ///< $42 - Create directory
|
||||||
|
int RemoveDir(DWORD nUnit, const Human68k::namests_t* pNamests); ///< $43 - Delete directory
|
||||||
|
int Rename(DWORD nUnit, const Human68k::namests_t* pNamests, const Human68k::namests_t* pNamestsNew);
|
||||||
|
///< $44 - Change file name
|
||||||
|
int Delete(DWORD nUnit, const Human68k::namests_t* pNamests); ///< $45 - Delete file
|
||||||
|
int Attribute(DWORD nUnit, const Human68k::namests_t* pNamests, DWORD nHumanAttribute);
|
||||||
|
///< $46 - Get / set file attribute
|
||||||
|
int Files(DWORD nUnit, DWORD nKey, const Human68k::namests_t* pNamests, Human68k::files_t* pFiles);
|
||||||
|
///< $47 - Find file
|
||||||
|
int NFiles(DWORD nUnit, DWORD nKey, Human68k::files_t* pFiles); ///< $48 - Find next file
|
||||||
|
int Create(DWORD nUnit, DWORD nKey, const Human68k::namests_t* pNamests, Human68k::fcb_t* pFcb, DWORD nHumanAttribute, BOOL bForce);
|
||||||
|
///< $49 - Create file
|
||||||
|
int Open(DWORD nUnit, DWORD nKey, const Human68k::namests_t* pNamests, Human68k::fcb_t* pFcb);
|
||||||
|
///< $4A - Open file
|
||||||
|
int Close(DWORD nUnit, DWORD nKey, Human68k::fcb_t* pFcb); ///< $4B - Close file
|
||||||
|
int Read(DWORD nKey, Human68k::fcb_t* pFcb, BYTE* pAddress, DWORD nSize);
|
||||||
|
///< $4C - Read file
|
||||||
|
int Write(DWORD nKey, Human68k::fcb_t* pFcb, const BYTE* pAddress, DWORD nSize);
|
||||||
|
///< $4D - Write file
|
||||||
|
int Seek(DWORD nKey, Human68k::fcb_t* pFcb, DWORD nSeek, int nOffset); ///< $4E - Seek file
|
||||||
|
DWORD TimeStamp(DWORD nUnit, DWORD nKey, Human68k::fcb_t* pFcb, DWORD nHumanTime);
|
||||||
|
///< $4F - Get / set file timestamp
|
||||||
|
int GetCapacity(DWORD nUnit, Human68k::capacity_t* pCapacity); ///< $50 - Get capacity
|
||||||
|
int CtrlDrive(DWORD nUnit, Human68k::ctrldrive_t* pCtrlDrive); ///< $51 - Inspect / control drive status
|
||||||
|
int GetDPB(DWORD nUnit, Human68k::dpb_t* pDpb); ///< $52 - Get DPB
|
||||||
|
int DiskRead(DWORD nUnit, BYTE* pBuffer, DWORD nSector, DWORD nSize); ///< $53 - Read sectors
|
||||||
|
int DiskWrite(DWORD nUnit); ///< $54 - Write sectors
|
||||||
|
int Ioctrl(DWORD nUnit, DWORD nFunction, Human68k::ioctrl_t* pIoctrl); ///< $55 - IOCTRL
|
||||||
|
int Flush(DWORD nUnit); ///< $56 - Flush
|
||||||
|
int CheckMedia(DWORD nUnit); ///< $57 - Media change check
|
||||||
|
int Lock(DWORD nUnit); ///< $58 - Lock
|
||||||
|
|
||||||
|
void SetOption(DWORD nOption); ///< Set option
|
||||||
|
DWORD GetOption() const { return m_nOption; } ///< Get option
|
||||||
|
DWORD GetDefault() const { return m_nOptionDefault; } ///< Get default options
|
||||||
|
static DWORD GetFileOption() { return g_nOption; } ///< Get file name change option
|
||||||
|
void ShellNotify(DWORD nEvent, const TCHAR* szPath)
|
||||||
|
{ m_cEntry.ShellNotify(nEvent, szPath); } ///< Notify host side file system status change
|
||||||
|
|
||||||
|
enum {
|
||||||
|
DriveMax = CHostEntry::DriveMax ///< Max number of drive candidates
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
void InitOption(const Human68k::argument_t* pArgument);
|
||||||
|
BOOL FilesVolume(DWORD nUnit, Human68k::files_t* pFiles); ///< Get volume label
|
||||||
|
|
||||||
|
DWORD m_nUnits; ///< Number of current drive objects (Changes for every resume)
|
||||||
|
|
||||||
|
DWORD m_nOption; ///< Current runtime flag
|
||||||
|
DWORD m_nOptionDefault; ///< Runtime flag at reset
|
||||||
|
|
||||||
|
DWORD m_nDrives; ///< Number of candidates for base path status restoration (scan every time if 0)
|
||||||
|
|
||||||
|
DWORD m_nKernel; ///< Counter for kernel check
|
||||||
|
DWORD m_nKernelSearch; ///< Initial address for NUL device
|
||||||
|
|
||||||
|
DWORD m_nHostSectorCount; ///< Virtual sector identifier
|
||||||
|
|
||||||
|
CHostFilesManager m_cFiles; ///< File search memory
|
||||||
|
CHostFcbManager m_cFcb; ///< FCB operation memory
|
||||||
|
CHostEntry m_cEntry; ///< Drive object and directory entry
|
||||||
|
|
||||||
|
DWORD m_nHostSectorBuffer[XM6_HOST_PSEUDO_CLUSTER_MAX];
|
||||||
|
///< Entity that the virtual sector points to
|
||||||
|
|
||||||
|
DWORD m_nFlag[DriveMax]; ///< Candidate runtime flag for base path restoration
|
||||||
|
TCHAR m_szBase[DriveMax][FILEPATH_MAX]; ///< Candidate for base path restoration
|
||||||
|
static DWORD g_nOption; ///< File name change flag
|
||||||
|
};
|
|
@ -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);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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"; }
|
||||||
|
};
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
@ -0,0 +1,159 @@
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// X68000 EMULATOR "XM6"
|
||||||
|
//
|
||||||
|
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||||
|
// Copyright (C) 2014-2020 GIMONS
|
||||||
|
//
|
||||||
|
// XM6i
|
||||||
|
// Copyright (C) 2010-2015 isaki@NetBSD.org
|
||||||
|
//
|
||||||
|
// Imported sava's Anex86/T98Next image and MO format support patch.
|
||||||
|
// Comments translated to english by akuker.
|
||||||
|
//
|
||||||
|
// [ Disk ]
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "log.h"
|
||||||
|
#include "scsi.h"
|
||||||
|
#include "controllers/scsidev_ctrl.h"
|
||||||
|
#include "device.h"
|
||||||
|
#include "device_factory.h"
|
||||||
|
#include "disk_image/disk_image_handle_factory.h"
|
||||||
|
#include "file_support.h"
|
||||||
|
#include "filepath.h"
|
||||||
|
#include "interfaces/scsi_block_commands.h"
|
||||||
|
#include "mode_page_device.h"
|
||||||
|
#include <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);
|
||||||
|
};
|
|
@ -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;
|
||||||
|
}
|
||||||
|
};
|
|
@ -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();
|
||||||
|
}
|
|
@ -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;
|
||||||
|
};
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
};
|
|
@ -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;
|
||||||
|
};
|
|
@ -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;
|
||||||
|
};
|
|
@ -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;
|
||||||
|
};
|
|
@ -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;
|
||||||
|
};
|
|
@ -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);
|
||||||
|
}
|
|
@ -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();
|
||||||
|
};
|
|
@ -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;
|
||||||
|
}
|
|
@ -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 *);
|
||||||
|
};
|
|
@ -0,0 +1,107 @@
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// SCSI Target Emulator RaSCSI (*^..^*)
|
||||||
|
// for Raspberry Pi
|
||||||
|
//
|
||||||
|
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||||
|
// Copyright (C) 2014-2020 GIMONS
|
||||||
|
// Copyright (C) akuker
|
||||||
|
//
|
||||||
|
// Licensed under the BSD 3-Clause License.
|
||||||
|
// See LICENSE file in the project root folder.
|
||||||
|
//
|
||||||
|
// [ SASI hard disk ]
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#include "sasihd.h"
|
||||||
|
#include "fileio.h"
|
||||||
|
#include "exceptions.h"
|
||||||
|
#include "../config.h"
|
||||||
|
|
||||||
|
SASIHD::SASIHD(const set<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;
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// SCSI Target Emulator RaSCSI (*^..^*)
|
||||||
|
// for Raspberry Pi
|
||||||
|
//
|
||||||
|
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||||
|
// Copyright (C) 2014-2020 GIMONS
|
||||||
|
// Copyright (C) akuker
|
||||||
|
//
|
||||||
|
// Licensed under the BSD 3-Clause License.
|
||||||
|
// See LICENSE file in the project root folder.
|
||||||
|
//
|
||||||
|
// [ SASI hard disk ]
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "os.h"
|
||||||
|
#include "disk.h"
|
||||||
|
#include "filepath.h"
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// SASI Hard Disk
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
class SASIHD : public Disk, public FileSupport
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SASIHD(const set<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;
|
||||||
|
};
|
|
@ -0,0 +1,593 @@
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// SCSI Target Emulator RaSCSI (*^..^*)
|
||||||
|
// for Raspberry Pi
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020 akuker
|
||||||
|
// Copyright (C) 2014-2020 GIMONS
|
||||||
|
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||||
|
//
|
||||||
|
// Licensed under the BSD 3-Clause License.
|
||||||
|
// See LICENSE file in the project root folder.
|
||||||
|
//
|
||||||
|
// [ Emulation of the DaynaPort SCSI Link Ethernet interface ]
|
||||||
|
//
|
||||||
|
// This design is derived from the SLINKCMD.TXT file, as well as David Kuder's
|
||||||
|
// Tiny SCSI Emulator
|
||||||
|
// - SLINKCMD: http://www.bitsavers.org/pdf/apple/scsi/dayna/daynaPORT/SLINKCMD.TXT
|
||||||
|
// - Tiny SCSI : https://hackaday.io/project/18974-tiny-scsi-emulator
|
||||||
|
//
|
||||||
|
// Additional documentation and clarification is available at the
|
||||||
|
// following link:
|
||||||
|
// - https://github.com/akuker/RASCSI/wiki/Dayna-Port-SCSI-Link
|
||||||
|
//
|
||||||
|
// This does NOT include the file system functionality that is present
|
||||||
|
// in the Sharp X68000 host bridge.
|
||||||
|
//
|
||||||
|
// Note: This requires a DaynaPort SCSI Link driver.
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#include "scsi_daynaport.h"
|
||||||
|
|
||||||
|
using namespace scsi_defs;
|
||||||
|
|
||||||
|
const BYTE SCSIDaynaPort::m_bcast_addr[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
||||||
|
const BYTE SCSIDaynaPort::m_apple_talk_addr[6] = { 0x09, 0x00, 0x07, 0xff, 0xff, 0xff };
|
||||||
|
|
||||||
|
// TODO Disk should not be the superclass
|
||||||
|
SCSIDaynaPort::SCSIDaynaPort() : Disk("SCDP")
|
||||||
|
{
|
||||||
|
m_tap = NULL;
|
||||||
|
m_bTapEnable = false;
|
||||||
|
|
||||||
|
dispatcher.AddCommand(eCmdTestUnitReady, "TestUnitReady", &SCSIDaynaPort::TestUnitReady);
|
||||||
|
dispatcher.AddCommand(eCmdRead6, "Read6", &SCSIDaynaPort::Read6);
|
||||||
|
dispatcher.AddCommand(eCmdWrite6, "Write6", &SCSIDaynaPort::Write6);
|
||||||
|
dispatcher.AddCommand(eCmdRetrieveStats, "RetrieveStats", &SCSIDaynaPort::RetrieveStatistics);
|
||||||
|
dispatcher.AddCommand(eCmdSetIfaceMode, "SetIfaceMode", &SCSIDaynaPort::SetInterfaceMode);
|
||||||
|
dispatcher.AddCommand(eCmdSetMcastAddr, "SetMcastAddr", &SCSIDaynaPort::SetMcastAddr);
|
||||||
|
dispatcher.AddCommand(eCmdEnableInterface, "EnableInterface", &SCSIDaynaPort::EnableInterface);
|
||||||
|
}
|
||||||
|
|
||||||
|
SCSIDaynaPort::~SCSIDaynaPort()
|
||||||
|
{
|
||||||
|
// TAP driver release
|
||||||
|
if (m_tap) {
|
||||||
|
m_tap->Cleanup();
|
||||||
|
delete m_tap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SCSIDaynaPort::Dispatch(SCSIDEV *controller)
|
||||||
|
{
|
||||||
|
// The superclass class handles the less specific commands
|
||||||
|
return dispatcher.Dispatch(this, controller) ? true : super::Dispatch(controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SCSIDaynaPort::Init(const map<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;
|
||||||
|
}
|
|
@ -0,0 +1,143 @@
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// SCSI Target Emulator RaSCSI (*^..^*)
|
||||||
|
// for Raspberry Pi
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020 akuker
|
||||||
|
// Copyright (C) 2014-2020 GIMONS
|
||||||
|
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||||
|
//
|
||||||
|
// Licensed under the BSD 3-Clause License.
|
||||||
|
// See LICENSE file in the project root folder.
|
||||||
|
//
|
||||||
|
// [ Emulation of the DaynaPort SCSI Link Ethernet interface ]
|
||||||
|
//
|
||||||
|
// This design is derived from the SLINKCMD.TXT file, as well as David Kuder's
|
||||||
|
// Tiny SCSI Emulator
|
||||||
|
// - SLINKCMD: http://www.bitsavers.org/pdf/apple/scsi/dayna/daynaPORT/SLINKCMD.TXT
|
||||||
|
// - Tiny SCSI : https://hackaday.io/project/18974-tiny-scsi-emulator
|
||||||
|
//
|
||||||
|
// Special thanks to @PotatoFi for loaning me his Farallon EtherMac for
|
||||||
|
// this development. (Farallon's EtherMac is a re-branded DaynaPort
|
||||||
|
// SCSI/Link-T).
|
||||||
|
//
|
||||||
|
// This does NOT include the file system functionality that is present
|
||||||
|
// in the Sharp X68000 host bridge.
|
||||||
|
//
|
||||||
|
// Note: This requires the DaynaPort SCSI Link driver.
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "os.h"
|
||||||
|
#include "disk.h"
|
||||||
|
#include "ctapdriver.h"
|
||||||
|
#include <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
|
@ -0,0 +1,107 @@
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// SCSI Target Emulator RaSCSI (*^..^*)
|
||||||
|
// for Raspberry Pi
|
||||||
|
//
|
||||||
|
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||||
|
// Copyright (C) 2014-2020 GIMONS
|
||||||
|
// Copyright (C) akuker
|
||||||
|
//
|
||||||
|
// Licensed under the BSD 3-Clause License.
|
||||||
|
// See LICENSE file in the project root folder.
|
||||||
|
//
|
||||||
|
// [ SCSI Host Bridge for the Sharp X68000 ]
|
||||||
|
//
|
||||||
|
// Note: This requires a special driver on the host system and will only
|
||||||
|
// work with the Sharp X68000 operating system.
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "os.h"
|
||||||
|
#include "disk.h"
|
||||||
|
#include <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
|
||||||
|
};
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
};
|
|
@ -0,0 +1,738 @@
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// SCSI Target Emulator RaSCSI (*^..^*)
|
||||||
|
// for Raspberry Pi
|
||||||
|
//
|
||||||
|
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||||
|
// Copyright (C) 2014-2020 GIMONS
|
||||||
|
// Copyright (C) akuker
|
||||||
|
//
|
||||||
|
// Licensed under the BSD 3-Clause License.
|
||||||
|
// See LICENSE file in the project root folder.
|
||||||
|
//
|
||||||
|
// [ SCSI CD-ROM ]
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#include "scsicd.h"
|
||||||
|
#include "fileio.h"
|
||||||
|
#include "exceptions.h"
|
||||||
|
#include "disk_image/disk_image_handle_factory.h"
|
||||||
|
|
||||||
|
using namespace scsi_defs;
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// CD Track
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
CDTrack::CDTrack(SCSICD *scsicd)
|
||||||
|
{
|
||||||
|
ASSERT(scsicd);
|
||||||
|
|
||||||
|
// Set parent CD-ROM device
|
||||||
|
cdrom = scsicd;
|
||||||
|
|
||||||
|
// Track defaults to disabled
|
||||||
|
valid = false;
|
||||||
|
|
||||||
|
// Initialize other data
|
||||||
|
track_no = -1;
|
||||||
|
first_lba = 0;
|
||||||
|
last_lba = 0;
|
||||||
|
audio = false;
|
||||||
|
raw = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CDTrack::Init(int track, DWORD first, DWORD last)
|
||||||
|
{
|
||||||
|
ASSERT(!valid);
|
||||||
|
ASSERT(track >= 1);
|
||||||
|
ASSERT(first < last);
|
||||||
|
|
||||||
|
// Set and enable track number
|
||||||
|
track_no = track;
|
||||||
|
valid = TRUE;
|
||||||
|
|
||||||
|
// Remember LBA
|
||||||
|
first_lba = first;
|
||||||
|
last_lba = last;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CDTrack::SetPath(bool cdda, const Filepath& path)
|
||||||
|
{
|
||||||
|
ASSERT(valid);
|
||||||
|
|
||||||
|
// CD-DA or data
|
||||||
|
audio = cdda;
|
||||||
|
|
||||||
|
// Remember the path
|
||||||
|
imgpath = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CDTrack::GetPath(Filepath& path) const
|
||||||
|
{
|
||||||
|
ASSERT(valid);
|
||||||
|
|
||||||
|
// Return the path (by reference)
|
||||||
|
path = imgpath;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CDTrack::AddIndex(int index, DWORD lba)
|
||||||
|
{
|
||||||
|
ASSERT(valid);
|
||||||
|
ASSERT(index > 0);
|
||||||
|
ASSERT(first_lba <= lba);
|
||||||
|
ASSERT(lba <= last_lba);
|
||||||
|
|
||||||
|
// Currently does not support indexes
|
||||||
|
ASSERT(FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Gets the start of LBA
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
DWORD CDTrack::GetFirst() const
|
||||||
|
{
|
||||||
|
ASSERT(valid);
|
||||||
|
ASSERT(first_lba < last_lba);
|
||||||
|
|
||||||
|
return first_lba;
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Get the end of LBA
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
DWORD CDTrack::GetLast() const
|
||||||
|
{
|
||||||
|
ASSERT(valid);
|
||||||
|
ASSERT(first_lba < last_lba);
|
||||||
|
|
||||||
|
return last_lba;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD CDTrack::GetBlocks() const
|
||||||
|
{
|
||||||
|
ASSERT(valid);
|
||||||
|
ASSERT(first_lba < last_lba);
|
||||||
|
|
||||||
|
// Calculate from start LBA and end LBA
|
||||||
|
return (DWORD)(last_lba - first_lba + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int CDTrack::GetTrackNo() const
|
||||||
|
{
|
||||||
|
ASSERT(valid);
|
||||||
|
ASSERT(track_no >= 1);
|
||||||
|
|
||||||
|
return track_no;
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Is valid block
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
bool CDTrack::IsValid(DWORD lba) const
|
||||||
|
{
|
||||||
|
// FALSE if the track itself is invalid
|
||||||
|
if (!valid) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the block is BEFORE the first block
|
||||||
|
if (lba < first_lba) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the block is AFTER the last block
|
||||||
|
if (last_lba < lba) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This track is valid
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Is audio track
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
bool CDTrack::IsAudio() const
|
||||||
|
{
|
||||||
|
ASSERT(valid);
|
||||||
|
|
||||||
|
return audio;
|
||||||
|
}
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// SCSI CD-ROM
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
SCSICD::SCSICD(const set<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;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// SCSI Target Emulator RaSCSI (*^..^*)
|
||||||
|
// for Raspberry Pi
|
||||||
|
//
|
||||||
|
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||||
|
// Copyright (C) 2014-2020 GIMONS
|
||||||
|
// Copyright (C) akuker
|
||||||
|
//
|
||||||
|
// Licensed under the BSD 3-Clause License.
|
||||||
|
// See LICENSE file in the project root folder.
|
||||||
|
//
|
||||||
|
// [ SCSI CD-ROM ]
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "os.h"
|
||||||
|
#include "disk.h"
|
||||||
|
#include "filepath.h"
|
||||||
|
#include "interfaces/scsi_mmc_commands.h"
|
||||||
|
#include "interfaces/scsi_primary_commands.h"
|
||||||
|
|
||||||
|
class SCSICD;
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// CD-ROM Track
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
class CDTrack
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class SCSICD;
|
||||||
|
|
||||||
|
CDTrack(SCSICD *scsicd);
|
||||||
|
virtual ~CDTrack() {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
void Init(int track, DWORD first, DWORD last);
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
void SetPath(bool cdda, const Filepath& path); // Set the path
|
||||||
|
void GetPath(Filepath& path) const; // Get the path
|
||||||
|
void AddIndex(int index, DWORD lba); // Add index
|
||||||
|
DWORD GetFirst() const; // Get the start LBA
|
||||||
|
DWORD GetLast() const; // Get the last LBA
|
||||||
|
DWORD GetBlocks() const; // Get the number of blocks
|
||||||
|
int GetTrackNo() const; // Get the track number
|
||||||
|
bool IsValid(DWORD lba) const; // Is this a valid LBA?
|
||||||
|
bool IsAudio() const; // Is this an audio track?
|
||||||
|
|
||||||
|
private:
|
||||||
|
SCSICD *cdrom; // Parent device
|
||||||
|
bool valid; // Valid track
|
||||||
|
int track_no; // Track number
|
||||||
|
DWORD first_lba; // First LBA
|
||||||
|
DWORD last_lba; // Last LBA
|
||||||
|
bool audio; // Audio track flag
|
||||||
|
bool raw; // RAW data flag
|
||||||
|
Filepath imgpath; // Image file path
|
||||||
|
};
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// SCSI CD-ROM
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
class SCSICD : public Disk, public ScsiMmcCommands, public FileSupport
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum {
|
||||||
|
TrackMax = 96 // Maximum number of tracks
|
||||||
|
};
|
||||||
|
|
||||||
|
SCSICD(const set<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
|
||||||
|
};
|
|
@ -0,0 +1,228 @@
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// SCSI Target Emulator RaSCSI (*^..^*)
|
||||||
|
// for Raspberry Pi
|
||||||
|
//
|
||||||
|
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||||
|
// Copyright (C) 2014-2020 GIMONS
|
||||||
|
// Copyright (C) akuker
|
||||||
|
//
|
||||||
|
// Licensed under the BSD 3-Clause License.
|
||||||
|
// See LICENSE file in the project root folder.
|
||||||
|
//
|
||||||
|
// [ SCSI hard disk ]
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
#include "scsihd.h"
|
||||||
|
#include "fileio.h"
|
||||||
|
#include "exceptions.h"
|
||||||
|
#include <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;
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// SCSI Target Emulator RaSCSI (*^..^*)
|
||||||
|
// for Raspberry Pi
|
||||||
|
//
|
||||||
|
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||||
|
// Copyright (C) 2014-2020 GIMONS
|
||||||
|
// Copyright (C) akuker
|
||||||
|
//
|
||||||
|
// Licensed under the BSD 3-Clause License.
|
||||||
|
// See LICENSE file in the project root folder.
|
||||||
|
//
|
||||||
|
// [ SCSI hard disk ]
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "os.h"
|
||||||
|
#include "disk.h"
|
||||||
|
#include "filepath.h"
|
||||||
|
|
||||||
|
class SCSIHD : public Disk, public FileSupport
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SCSIHD(const set<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;
|
||||||
|
};
|
|
@ -0,0 +1,214 @@
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// SCSI Target Emulator RaSCSI (*^..^*)
|
||||||
|
// for Raspberry Pi
|
||||||
|
//
|
||||||
|
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||||
|
// Copyright (C) 2014-2020 GIMONS
|
||||||
|
// Copyright (C) akuker
|
||||||
|
//
|
||||||
|
// Licensed under the BSD 3-Clause License.
|
||||||
|
// See LICENSE file in the project root folder.
|
||||||
|
//
|
||||||
|
// [ SCSI NEC "Genuine" Hard Disk]
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#include "scsihd_nec.h"
|
||||||
|
#include "fileio.h"
|
||||||
|
#include "exceptions.h"
|
||||||
|
|
||||||
|
SCSIHD_NEC::SCSIHD_NEC(const set<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;
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// SCSI Target Emulator RaSCSI (*^..^*)
|
||||||
|
// for Raspberry Pi
|
||||||
|
//
|
||||||
|
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||||
|
// Copyright (C) 2014-2020 GIMONS
|
||||||
|
// Copyright (C) akuker
|
||||||
|
//
|
||||||
|
// Licensed under the BSD 3-Clause License.
|
||||||
|
// See LICENSE file in the project root folder.
|
||||||
|
//
|
||||||
|
// [ SCSI NEC "Genuine" Hard Disk]
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "scsihd.h"
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// SCSI hard disk (PC-9801-55 NEC genuine / Anex86 / T98Next)
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
class SCSIHD_NEC : public SCSIHD
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SCSIHD_NEC(const set<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;
|
||||||
|
};
|
|
@ -0,0 +1,289 @@
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// SCSI Target Emulator RaSCSI (*^..^*)
|
||||||
|
// for Raspberry Pi
|
||||||
|
//
|
||||||
|
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||||
|
// Copyright (C) 2014-2020 GIMONS
|
||||||
|
// Copyright (C) akuker
|
||||||
|
//
|
||||||
|
// Licensed under the BSD 3-Clause License.
|
||||||
|
// See LICENSE file in the project root folder.
|
||||||
|
//
|
||||||
|
// [ SCSI Magneto-Optical Disk]
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#include "scsimo.h"
|
||||||
|
|
||||||
|
#include "fileio.h"
|
||||||
|
#include "exceptions.h"
|
||||||
|
|
||||||
|
SCSIMO::SCSIMO(const set<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;
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// SCSI Target Emulator RaSCSI (*^..^*)
|
||||||
|
// for Raspberry Pi
|
||||||
|
//
|
||||||
|
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||||
|
// Copyright (C) 2014-2020 GIMONS
|
||||||
|
// Copyright (C) akuker
|
||||||
|
//
|
||||||
|
// Licensed under the BSD 3-Clause License.
|
||||||
|
// See LICENSE file in the project root folder.
|
||||||
|
//
|
||||||
|
// [ SCSI Magneto-Optical Disk]
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "os.h"
|
||||||
|
#include "disk.h"
|
||||||
|
#include "filepath.h"
|
||||||
|
|
||||||
|
class SCSIMO : public Disk, public FileSupport
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SCSIMO(const set<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;
|
||||||
|
};
|
|
@ -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;
|
||||||
|
}
|
|
@ -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);
|
||||||
|
};
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
};
|
|
@ -0,0 +1,575 @@
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// X68000 EMULATOR "XM6"
|
||||||
|
//
|
||||||
|
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||||
|
// Copyright (C) 2014-2020 GIMONS
|
||||||
|
//
|
||||||
|
// XM6i
|
||||||
|
// Copyright (C) 2010-2015 isaki@NetBSD.org
|
||||||
|
// Copyright (C) 2010 Y.Sugahara
|
||||||
|
//
|
||||||
|
// Imported sava's Anex86/T98Next image and MO format support patch.
|
||||||
|
// Comments translated to english by akuker.
|
||||||
|
//
|
||||||
|
// [ DiskTrack and DiskCache ]
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#include "os.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "fileio.h"
|
||||||
|
#include "disk_track_cache.h"
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// Disk Track
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
DiskTrack::DiskTrack()
|
||||||
|
{
|
||||||
|
// Initialization of internal information
|
||||||
|
dt.track = 0;
|
||||||
|
dt.size = 0;
|
||||||
|
dt.sectors = 0;
|
||||||
|
dt.raw = FALSE;
|
||||||
|
dt.init = FALSE;
|
||||||
|
dt.changed = FALSE;
|
||||||
|
dt.length = 0;
|
||||||
|
dt.buffer = NULL;
|
||||||
|
dt.maplen = 0;
|
||||||
|
dt.changemap = NULL;
|
||||||
|
dt.imgoffset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
DiskTrack::~DiskTrack()
|
||||||
|
{
|
||||||
|
// Release memory, but do not save automatically
|
||||||
|
if (dt.buffer) {
|
||||||
|
free(dt.buffer);
|
||||||
|
dt.buffer = NULL;
|
||||||
|
}
|
||||||
|
if (dt.changemap) {
|
||||||
|
free(dt.changemap);
|
||||||
|
dt.changemap = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DiskTrack::Init(int track, int size, int sectors, BOOL raw, off_t imgoff)
|
||||||
|
{
|
||||||
|
ASSERT(track >= 0);
|
||||||
|
ASSERT((sectors > 0) && (sectors <= 0x100));
|
||||||
|
ASSERT(imgoff >= 0);
|
||||||
|
|
||||||
|
// Set Parameters
|
||||||
|
dt.track = track;
|
||||||
|
dt.size = size;
|
||||||
|
dt.sectors = sectors;
|
||||||
|
dt.raw = raw;
|
||||||
|
|
||||||
|
// Not initialized (needs to be loaded)
|
||||||
|
dt.init = FALSE;
|
||||||
|
|
||||||
|
// Not Changed
|
||||||
|
dt.changed = FALSE;
|
||||||
|
|
||||||
|
// Offset to actual data
|
||||||
|
dt.imgoffset = imgoff;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DiskTrack::Load(const Filepath& path)
|
||||||
|
{
|
||||||
|
// Not needed if already loaded
|
||||||
|
if (dt.init) {
|
||||||
|
ASSERT(dt.buffer);
|
||||||
|
ASSERT(dt.changemap);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate offset (previous tracks are considered to hold 256 sectors)
|
||||||
|
off_t offset = ((off_t)dt.track << 8);
|
||||||
|
if (dt.raw) {
|
||||||
|
ASSERT(dt.size == 11);
|
||||||
|
offset *= 0x930;
|
||||||
|
offset += 0x10;
|
||||||
|
} else {
|
||||||
|
offset <<= dt.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add offset to real image
|
||||||
|
offset += dt.imgoffset;
|
||||||
|
|
||||||
|
// Calculate length (data size of this track)
|
||||||
|
int length = dt.sectors << dt.size;
|
||||||
|
|
||||||
|
// Allocate buffer memory
|
||||||
|
ASSERT((dt.sectors > 0) && (dt.sectors <= 0x100));
|
||||||
|
|
||||||
|
if (dt.buffer == NULL) {
|
||||||
|
if (posix_memalign((void **)&dt.buffer, 512, ((length + 511) / 512) * 512)) {
|
||||||
|
LOGWARN("%s posix_memalign failed", __PRETTY_FUNCTION__);
|
||||||
|
}
|
||||||
|
dt.length = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dt.buffer) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reallocate if the buffer length is different
|
||||||
|
if (dt.length != (DWORD)length) {
|
||||||
|
free(dt.buffer);
|
||||||
|
if (posix_memalign((void **)&dt.buffer, 512, ((length + 511) / 512) * 512)) {
|
||||||
|
LOGWARN("%s posix_memalign failed", __PRETTY_FUNCTION__);
|
||||||
|
}
|
||||||
|
dt.length = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reserve change map memory
|
||||||
|
if (dt.changemap == NULL) {
|
||||||
|
dt.changemap = (BOOL *)malloc(dt.sectors * sizeof(BOOL));
|
||||||
|
dt.maplen = dt.sectors;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dt.changemap) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reallocate if the buffer length is different
|
||||||
|
if (dt.maplen != (DWORD)dt.sectors) {
|
||||||
|
free(dt.changemap);
|
||||||
|
dt.changemap = (BOOL *)malloc(dt.sectors * sizeof(BOOL));
|
||||||
|
dt.maplen = dt.sectors;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear changemap
|
||||||
|
memset(dt.changemap, 0x00, dt.sectors * sizeof(BOOL));
|
||||||
|
|
||||||
|
// Read from File
|
||||||
|
Fileio fio;
|
||||||
|
if (!fio.OpenDIO(path, Fileio::ReadOnly)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (dt.raw) {
|
||||||
|
// Split Reading
|
||||||
|
for (int i = 0; i < dt.sectors; i++) {
|
||||||
|
// Seek
|
||||||
|
if (!fio.Seek(offset)) {
|
||||||
|
fio.Close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read
|
||||||
|
if (!fio.Read(&dt.buffer[i << dt.size], 1 << dt.size)) {
|
||||||
|
fio.Close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next offset
|
||||||
|
offset += 0x930;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Continuous reading
|
||||||
|
if (!fio.Seek(offset)) {
|
||||||
|
fio.Close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!fio.Read(dt.buffer, length)) {
|
||||||
|
fio.Close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fio.Close();
|
||||||
|
|
||||||
|
// Set a flag and end normally
|
||||||
|
dt.init = TRUE;
|
||||||
|
dt.changed = FALSE;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DiskTrack::Save(const Filepath& path)
|
||||||
|
{
|
||||||
|
// Not needed if not initialized
|
||||||
|
if (!dt.init) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not needed unless changed
|
||||||
|
if (!dt.changed) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to write
|
||||||
|
ASSERT(dt.buffer);
|
||||||
|
ASSERT(dt.changemap);
|
||||||
|
ASSERT((dt.sectors > 0) && (dt.sectors <= 0x100));
|
||||||
|
|
||||||
|
// Writing in RAW mode is not allowed
|
||||||
|
ASSERT(!dt.raw);
|
||||||
|
|
||||||
|
// Calculate offset (previous tracks are considered to hold 256 sectors)
|
||||||
|
off_t offset = ((off_t)dt.track << 8);
|
||||||
|
offset <<= dt.size;
|
||||||
|
|
||||||
|
// Add offset to real image
|
||||||
|
offset += dt.imgoffset;
|
||||||
|
|
||||||
|
// Calculate length per sector
|
||||||
|
int length = 1 << dt.size;
|
||||||
|
|
||||||
|
// Open file
|
||||||
|
Fileio fio;
|
||||||
|
if (!fio.Open(path, Fileio::ReadWrite)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Partial write loop
|
||||||
|
int total;
|
||||||
|
for (int i = 0; i < dt.sectors;) {
|
||||||
|
// If changed
|
||||||
|
if (dt.changemap[i]) {
|
||||||
|
// Initialize write size
|
||||||
|
total = 0;
|
||||||
|
|
||||||
|
// Seek
|
||||||
|
if (!fio.Seek(offset + ((off_t)i << dt.size))) {
|
||||||
|
fio.Close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consectutive sector length
|
||||||
|
int j;
|
||||||
|
for (j = i; j < dt.sectors; j++) {
|
||||||
|
// end when interrupted
|
||||||
|
if (!dt.changemap[j]) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add one sector
|
||||||
|
total += length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write
|
||||||
|
if (!fio.Write(&dt.buffer[i << dt.size], total)) {
|
||||||
|
fio.Close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// To unmodified sector
|
||||||
|
i = j;
|
||||||
|
} else {
|
||||||
|
// Next Sector
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close
|
||||||
|
fio.Close();
|
||||||
|
|
||||||
|
// Drop the change flag and exit
|
||||||
|
memset(dt.changemap, 0x00, dt.sectors * sizeof(BOOL));
|
||||||
|
dt.changed = FALSE;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DiskTrack::ReadSector(BYTE *buf, int sec) const
|
||||||
|
{
|
||||||
|
ASSERT(buf);
|
||||||
|
ASSERT((sec >= 0) & (sec < 0x100));
|
||||||
|
|
||||||
|
LOGTRACE("%s reading sector: %d", __PRETTY_FUNCTION__,sec);
|
||||||
|
// Error if not initialized
|
||||||
|
if (!dt.init) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// // Error if the number of sectors exceeds the valid number
|
||||||
|
if (sec >= dt.sectors) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy
|
||||||
|
ASSERT(dt.buffer);
|
||||||
|
ASSERT((dt.sectors > 0) && (dt.sectors <= 0x100));
|
||||||
|
memcpy(buf, &dt.buffer[(off_t)sec << dt.size], (off_t)1 << dt.size);
|
||||||
|
|
||||||
|
// Success
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DiskTrack::WriteSector(const BYTE *buf, int sec)
|
||||||
|
{
|
||||||
|
ASSERT(buf);
|
||||||
|
ASSERT((sec >= 0) & (sec < 0x100));
|
||||||
|
ASSERT(!dt.raw);
|
||||||
|
|
||||||
|
// Error if not initialized
|
||||||
|
if (!dt.init) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// // Error if the number of sectors exceeds the valid number
|
||||||
|
if (sec >= dt.sectors) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate offset and length
|
||||||
|
int offset = sec << dt.size;
|
||||||
|
int length = 1 << dt.size;
|
||||||
|
|
||||||
|
// Compare
|
||||||
|
ASSERT(dt.buffer);
|
||||||
|
ASSERT((dt.sectors > 0) && (dt.sectors <= 0x100));
|
||||||
|
if (memcmp(buf, &dt.buffer[offset], length) == 0) {
|
||||||
|
// Exit normally since it's attempting to write the same thing
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy, change
|
||||||
|
memcpy(&dt.buffer[offset], buf, length);
|
||||||
|
dt.changemap[sec] = TRUE;
|
||||||
|
dt.changed = TRUE;
|
||||||
|
|
||||||
|
// Success
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// Disk Cache
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
DiskCache::DiskCache(const Filepath& path, int size, uint32_t blocks, off_t imgoff) : DiskImageHandle(path, size, blocks, imgoff)
|
||||||
|
{
|
||||||
|
ASSERT(blocks > 0);
|
||||||
|
ASSERT(imgoff >= 0);
|
||||||
|
|
||||||
|
// Cache work
|
||||||
|
for (int i = 0; i < CacheMax; i++) {
|
||||||
|
cache[i].disktrk = NULL;
|
||||||
|
cache[i].serial = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
DiskCache::~DiskCache()
|
||||||
|
{
|
||||||
|
// Clear the track
|
||||||
|
Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DiskCache::Save()
|
||||||
|
{
|
||||||
|
// Save track
|
||||||
|
for (int i = 0; i < CacheMax; i++) {
|
||||||
|
// Is it a valid track?
|
||||||
|
if (cache[i].disktrk) {
|
||||||
|
// Save
|
||||||
|
if (!cache[i].disktrk->Save(sec_path)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Get disk cache information
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
bool DiskCache::GetCache(int index, int& track, DWORD& aserial) const
|
||||||
|
{
|
||||||
|
ASSERT((index >= 0) && (index < CacheMax));
|
||||||
|
|
||||||
|
// false if unused
|
||||||
|
if (!cache[index].disktrk) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set track and serial
|
||||||
|
track = cache[index].disktrk->GetTrack();
|
||||||
|
aserial = cache[index].serial;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DiskCache::Clear()
|
||||||
|
{
|
||||||
|
// Free the cache
|
||||||
|
for (int i = 0; i < CacheMax; i++) {
|
||||||
|
if (cache[i].disktrk) {
|
||||||
|
delete cache[i].disktrk;
|
||||||
|
cache[i].disktrk = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DiskCache::ReadSector(BYTE *buf, int block)
|
||||||
|
{
|
||||||
|
ASSERT(sec_size != 0);
|
||||||
|
|
||||||
|
// Update first
|
||||||
|
UpdateSerialNumber();
|
||||||
|
|
||||||
|
// Calculate track (fixed to 256 sectors/track)
|
||||||
|
int track = block >> 8;
|
||||||
|
|
||||||
|
// Get the track data
|
||||||
|
DiskTrack *disktrk = Assign(track);
|
||||||
|
if (!disktrk) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the track data to the cache
|
||||||
|
return disktrk->ReadSector(buf, block & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DiskCache::WriteSector(const BYTE *buf, int block)
|
||||||
|
{
|
||||||
|
ASSERT(sec_size != 0);
|
||||||
|
|
||||||
|
// Update first
|
||||||
|
UpdateSerialNumber();
|
||||||
|
|
||||||
|
// Calculate track (fixed to 256 sectors/track)
|
||||||
|
int track = block >> 8;
|
||||||
|
|
||||||
|
// Get that track data
|
||||||
|
DiskTrack *disktrk = Assign(track);
|
||||||
|
if (!disktrk) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the data to the cache
|
||||||
|
return disktrk->WriteSector(buf, block & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Track Assignment
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
DiskTrack* DiskCache::Assign(int track)
|
||||||
|
{
|
||||||
|
ASSERT(sec_size != 0);
|
||||||
|
ASSERT(track >= 0);
|
||||||
|
|
||||||
|
// First, check if it is already assigned
|
||||||
|
for (int i = 0; i < CacheMax; i++) {
|
||||||
|
if (cache[i].disktrk) {
|
||||||
|
if (cache[i].disktrk->GetTrack() == track) {
|
||||||
|
// Track match
|
||||||
|
cache[i].serial = serial;
|
||||||
|
return cache[i].disktrk;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next, check for empty
|
||||||
|
for (int i = 0; i < CacheMax; i++) {
|
||||||
|
if (!cache[i].disktrk) {
|
||||||
|
// Try loading
|
||||||
|
if (Load(i, track)) {
|
||||||
|
// Success loading
|
||||||
|
cache[i].serial = serial;
|
||||||
|
return cache[i].disktrk;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load failed
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, find the youngest serial number and delete it
|
||||||
|
|
||||||
|
// Set index 0 as candidate c
|
||||||
|
DWORD s = cache[0].serial;
|
||||||
|
int c = 0;
|
||||||
|
|
||||||
|
// Compare candidate with serial and update to smaller one
|
||||||
|
for (int i = 0; i < CacheMax; i++) {
|
||||||
|
ASSERT(cache[i].disktrk);
|
||||||
|
|
||||||
|
// Compare and update the existing serial
|
||||||
|
if (cache[i].serial < s) {
|
||||||
|
s = cache[i].serial;
|
||||||
|
c = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save this track
|
||||||
|
if (!cache[c].disktrk->Save(sec_path)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete this track
|
||||||
|
DiskTrack *disktrk = cache[c].disktrk;
|
||||||
|
cache[c].disktrk = NULL;
|
||||||
|
|
||||||
|
if (Load(c, track, disktrk)) {
|
||||||
|
// Successful loading
|
||||||
|
cache[c].serial = serial;
|
||||||
|
return cache[c].disktrk;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load failed
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Load cache
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
bool DiskCache::Load(int index, int track, DiskTrack *disktrk)
|
||||||
|
{
|
||||||
|
ASSERT((index >= 0) && (index < CacheMax));
|
||||||
|
ASSERT(track >= 0);
|
||||||
|
ASSERT(!cache[index].disktrk);
|
||||||
|
|
||||||
|
// Get the number of sectors on this track
|
||||||
|
int sectors = sec_blocks - (track << 8);
|
||||||
|
ASSERT(sectors > 0);
|
||||||
|
if (sectors > 0x100) {
|
||||||
|
sectors = 0x100;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a disk track
|
||||||
|
if (disktrk == NULL) {
|
||||||
|
disktrk = new DiskTrack();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize disk track
|
||||||
|
disktrk->Init(track, sec_size, sectors, cd_raw, imgoffset);
|
||||||
|
|
||||||
|
// Try loading
|
||||||
|
if (!disktrk->Load(sec_path)) {
|
||||||
|
// Failure
|
||||||
|
delete disktrk;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocation successful, work set
|
||||||
|
cache[index].disktrk = disktrk;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DiskCache::UpdateSerialNumber()
|
||||||
|
{
|
||||||
|
// Update and do nothing except 0
|
||||||
|
serial++;
|
||||||
|
if (serial != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear serial of all caches (loop in 32bit)
|
||||||
|
for (int i = 0; i < CacheMax; i++) {
|
||||||
|
cache[i].serial = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// X68000 EMULATOR "XM6"
|
||||||
|
//
|
||||||
|
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||||
|
// Copyright (C) 2014-2020 GIMONS
|
||||||
|
//
|
||||||
|
// XM6i
|
||||||
|
// Copyright (C) 2010-2015 isaki@NetBSD.org
|
||||||
|
//
|
||||||
|
// Imported sava's Anex86/T98Next image and MO format support patch.
|
||||||
|
// Comments translated to english by akuker.
|
||||||
|
//
|
||||||
|
// [ DiskTrack and DiskCache ]
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "filepath.h"
|
||||||
|
#include "disk_image/disk_image_handle.h"
|
||||||
|
|
||||||
|
// Number of tracks to cache
|
||||||
|
#define CacheMax 16
|
||||||
|
|
||||||
|
class DiskTrack
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
struct {
|
||||||
|
int track; // Track Number
|
||||||
|
int size; // Sector Size (8=256, 9=512, 10=1024, 11=2048, 12=4096)
|
||||||
|
int sectors; // Number of sectors(<0x100)
|
||||||
|
DWORD length; // Data buffer length
|
||||||
|
BYTE *buffer; // Data buffer
|
||||||
|
BOOL init; // Is it initilized?
|
||||||
|
BOOL changed; // Changed flag
|
||||||
|
DWORD maplen; // Changed map length
|
||||||
|
BOOL *changemap; // Changed map
|
||||||
|
BOOL raw; // RAW mode flag
|
||||||
|
off_t imgoffset; // Offset to actual data
|
||||||
|
} dt;
|
||||||
|
|
||||||
|
public:
|
||||||
|
DiskTrack();
|
||||||
|
~DiskTrack();
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class DiskCache;
|
||||||
|
|
||||||
|
void Init(int track, int size, int sectors, BOOL raw = FALSE, off_t imgoff = 0);
|
||||||
|
bool Load(const Filepath& path);
|
||||||
|
bool Save(const Filepath& path);
|
||||||
|
|
||||||
|
// Read / Write
|
||||||
|
bool ReadSector(BYTE *buf, int sec) const; // Sector Read
|
||||||
|
bool WriteSector(const BYTE *buf, int sec); // Sector Write
|
||||||
|
|
||||||
|
int GetTrack() const { return dt.track; } // Get track
|
||||||
|
};
|
||||||
|
|
||||||
|
class DiskCache : public DiskImageHandle
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// Internal data definition
|
||||||
|
typedef struct {
|
||||||
|
DiskTrack *disktrk; // Disk Track
|
||||||
|
DWORD serial; // Serial
|
||||||
|
} cache_t;
|
||||||
|
|
||||||
|
public:
|
||||||
|
DiskCache(const Filepath& path, int size, uint32_t blocks, off_t imgoff = 0);
|
||||||
|
~DiskCache();
|
||||||
|
|
||||||
|
// Access
|
||||||
|
bool Save() override; // Save and release all
|
||||||
|
bool ReadSector(BYTE *buf, int block) override; // Sector Read
|
||||||
|
bool WriteSector(const BYTE *buf, int block) override; // Sector Write
|
||||||
|
bool GetCache(int index, int& track, DWORD& serial) const override; // Get cache information
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Internal Management
|
||||||
|
void Clear(); // Clear all tracks
|
||||||
|
DiskTrack* Assign(int track); // Load track
|
||||||
|
bool Load(int index, int track, DiskTrack *disktrk = NULL); // Load track
|
||||||
|
void UpdateSerialNumber(); // Update serial number
|
||||||
|
|
||||||
|
// Internal data
|
||||||
|
cache_t cache[CacheMax]; // Cache management
|
||||||
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
};
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
};
|
|
@ -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() {}
|
||||||
|
};
|
|
@ -0,0 +1,241 @@
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// X68000 EMULATOR "XM6"
|
||||||
|
//
|
||||||
|
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||||
|
// Copyright (C) 2010-2020 GIMONS
|
||||||
|
// [ File I/O (Subset for RaSCSI) ]
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#include "os.h"
|
||||||
|
#include "filepath.h"
|
||||||
|
#include "fileio.h"
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// File I/O
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
Fileio::Fileio()
|
||||||
|
{
|
||||||
|
// Initialize work
|
||||||
|
handle = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Fileio::~Fileio()
|
||||||
|
{
|
||||||
|
ASSERT(handle == -1);
|
||||||
|
|
||||||
|
// Safety measure for Release
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL Fileio::Load(const Filepath& path, void *buffer, int size)
|
||||||
|
{
|
||||||
|
ASSERT(buffer);
|
||||||
|
ASSERT(size > 0);
|
||||||
|
ASSERT(handle < 0);
|
||||||
|
|
||||||
|
if (!Open(path, ReadOnly)) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Read(buffer, size)) {
|
||||||
|
Close();
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
Close();
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL Fileio::Save(const Filepath& path, void *buffer, int size)
|
||||||
|
{
|
||||||
|
ASSERT(buffer);
|
||||||
|
ASSERT(size > 0);
|
||||||
|
ASSERT(handle < 0);
|
||||||
|
|
||||||
|
if (!Open(path, WriteOnly)) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Write(buffer, size)) {
|
||||||
|
Close();
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
Close();
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL Fileio::Open(const char *fname, OpenMode mode, BOOL directIO)
|
||||||
|
{
|
||||||
|
mode_t omode;
|
||||||
|
|
||||||
|
ASSERT(fname);
|
||||||
|
ASSERT(handle < 0);
|
||||||
|
|
||||||
|
// Always fail a read from a null array
|
||||||
|
if (fname[0] == _T('\0')) {
|
||||||
|
handle = -1;
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default mode
|
||||||
|
omode = directIO ? O_DIRECT : 0;
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case ReadOnly:
|
||||||
|
handle = open(fname, O_RDONLY | omode);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WriteOnly:
|
||||||
|
handle = open(fname, O_CREAT | O_WRONLY | O_TRUNC | omode, 0666);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ReadWrite:
|
||||||
|
// Make sure RW does not succeed when reading from CD-ROM
|
||||||
|
if (access(fname, 0x06) != 0) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
handle = open(fname, O_RDWR | omode);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
ASSERT(FALSE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate results
|
||||||
|
if (handle == -1) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT(handle >= 0);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL Fileio::Open(const char *fname, OpenMode mode)
|
||||||
|
{
|
||||||
|
|
||||||
|
return Open(fname, mode, FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL Fileio::Open(const Filepath& path, OpenMode mode)
|
||||||
|
{
|
||||||
|
|
||||||
|
return Open(path.GetPath(), mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL Fileio::OpenDIO(const char *fname, OpenMode mode)
|
||||||
|
{
|
||||||
|
|
||||||
|
// Open with included O_DIRECT
|
||||||
|
if (!Open(fname, mode, TRUE)) {
|
||||||
|
// Normal mode retry (tmpfs etc.)
|
||||||
|
return Open(fname, mode, FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL Fileio::OpenDIO(const Filepath& path, OpenMode mode)
|
||||||
|
{
|
||||||
|
|
||||||
|
return OpenDIO(path.GetPath(), mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL Fileio::Read(void *buffer, int size)
|
||||||
|
{
|
||||||
|
int count;
|
||||||
|
|
||||||
|
ASSERT(buffer);
|
||||||
|
ASSERT(size > 0);
|
||||||
|
ASSERT(handle >= 0);
|
||||||
|
|
||||||
|
count = read(handle, buffer, size);
|
||||||
|
if (count != size) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL Fileio::Write(const void *buffer, int size)
|
||||||
|
{
|
||||||
|
int count;
|
||||||
|
|
||||||
|
ASSERT(buffer);
|
||||||
|
ASSERT(size > 0);
|
||||||
|
ASSERT(handle >= 0);
|
||||||
|
|
||||||
|
count = write(handle, buffer, size);
|
||||||
|
if (count != size) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL Fileio::Seek(off_t offset, BOOL relative)
|
||||||
|
{
|
||||||
|
ASSERT(handle >= 0);
|
||||||
|
ASSERT(offset >= 0);
|
||||||
|
|
||||||
|
// Add current position in case of relative seek
|
||||||
|
if (relative) {
|
||||||
|
offset += GetFilePos();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lseek(handle, offset, SEEK_SET) != offset) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
off_t Fileio::GetFileSize()
|
||||||
|
{
|
||||||
|
off_t cur;
|
||||||
|
off_t end;
|
||||||
|
|
||||||
|
ASSERT(handle >= 0);
|
||||||
|
|
||||||
|
// Get file position in 64bit
|
||||||
|
cur = GetFilePos();
|
||||||
|
|
||||||
|
// Get file size in64bitで
|
||||||
|
end = lseek(handle, 0, SEEK_END);
|
||||||
|
|
||||||
|
// Return to start position
|
||||||
|
Seek(cur);
|
||||||
|
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
|
||||||
|
off_t Fileio::GetFilePos() const
|
||||||
|
{
|
||||||
|
off_t pos;
|
||||||
|
|
||||||
|
ASSERT(handle >= 0);
|
||||||
|
|
||||||
|
// Get file position in 64bit
|
||||||
|
pos = lseek(handle, 0, SEEK_CUR);
|
||||||
|
return pos;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Fileio::Close()
|
||||||
|
{
|
||||||
|
|
||||||
|
if (handle != -1) {
|
||||||
|
close(handle);
|
||||||
|
handle = -1;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// X68000 EMULATOR "XM6"
|
||||||
|
//
|
||||||
|
// Copyright (C) 2001-2005 PI.(ytanaka@ipc-tokai.or.jp)
|
||||||
|
// Copyright (C) 2013-2020 GIMONS
|
||||||
|
// [ File I/O (Subset for RaSCSI) ]
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#if !defined(fileio_h)
|
||||||
|
#define fileio_h
|
||||||
|
|
||||||
|
#include "filepath.h"
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// Macros (for Load, Save)
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
#define PROP_IMPORT(f, p) \
|
||||||
|
if (!f->Read(&(p), sizeof(p))) {\
|
||||||
|
return FALSE;\
|
||||||
|
}\
|
||||||
|
|
||||||
|
#define PROP_EXPORT(f, p) \
|
||||||
|
if (!f->Write(&(p), sizeof(p))) {\
|
||||||
|
return FALSE;\
|
||||||
|
}\
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// File I/O
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
class Fileio
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum OpenMode {
|
||||||
|
ReadOnly,
|
||||||
|
WriteOnly,
|
||||||
|
ReadWrite
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
Fileio();
|
||||||
|
virtual ~Fileio();
|
||||||
|
BOOL Load(const Filepath& path, void *buffer, int size); // Load ROM, RAM
|
||||||
|
BOOL Save(const Filepath& path, void *buffer, int size); // Save RAM
|
||||||
|
|
||||||
|
BOOL Open(const char *fname, OpenMode mode);
|
||||||
|
BOOL Open(const Filepath& path, OpenMode mode);
|
||||||
|
BOOL OpenDIO(const char *fname, OpenMode mode);
|
||||||
|
BOOL OpenDIO(const Filepath& path, OpenMode mode);
|
||||||
|
BOOL Seek(off_t offset, BOOL relative = FALSE);
|
||||||
|
BOOL Read(void *buffer, int size);
|
||||||
|
BOOL Write(const void *buffer, int size);
|
||||||
|
off_t GetFileSize();
|
||||||
|
off_t GetFilePos() const;
|
||||||
|
void Close();
|
||||||
|
BOOL IsValid() const { return (BOOL)(handle != -1); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
BOOL Open(const char *fname, OpenMode mode, BOOL directIO);
|
||||||
|
|
||||||
|
int handle; // File handle
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // fileio_h
|
|
@ -0,0 +1,140 @@
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// X68000 EMULATOR "XM6"
|
||||||
|
//
|
||||||
|
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||||
|
// Copyright (C) 2012-2020 GIMONS
|
||||||
|
// [ File path (subset) ]
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#include "os.h"
|
||||||
|
#include "filepath.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "fileio.h"
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// File path
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
Filepath::Filepath()
|
||||||
|
{
|
||||||
|
// Clear
|
||||||
|
Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
Filepath::~Filepath()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Filepath& Filepath::operator=(const Filepath& path)
|
||||||
|
{
|
||||||
|
// Set path (split internally)
|
||||||
|
SetPath(path.GetPath());
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Filepath::Clear()
|
||||||
|
{
|
||||||
|
|
||||||
|
// Clear the path and each part
|
||||||
|
m_szPath[0] = _T('\0');
|
||||||
|
m_szDir[0] = _T('\0');
|
||||||
|
m_szFile[0] = _T('\0');
|
||||||
|
m_szExt[0] = _T('\0');
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// File settings (user) for MBCS
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
void Filepath::SetPath(const char *path)
|
||||||
|
{
|
||||||
|
ASSERT(path);
|
||||||
|
ASSERT(strlen(path) < _MAX_PATH);
|
||||||
|
|
||||||
|
// Copy pathname
|
||||||
|
strcpy(m_szPath, (char *)path);
|
||||||
|
|
||||||
|
// Split
|
||||||
|
Split();
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Split paths
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
void Filepath::Split()
|
||||||
|
{
|
||||||
|
// Initialize the parts
|
||||||
|
m_szDir[0] = _T('\0');
|
||||||
|
m_szFile[0] = _T('\0');
|
||||||
|
m_szExt[0] = _T('\0');
|
||||||
|
|
||||||
|
// Split
|
||||||
|
char *pDir = strdup(m_szPath);
|
||||||
|
char *pDirName = dirname(pDir);
|
||||||
|
char *pBase = strdup(m_szPath);
|
||||||
|
char *pBaseName = basename(pBase);
|
||||||
|
char *pExtName = strrchr(pBaseName, '.');
|
||||||
|
|
||||||
|
// Transmit
|
||||||
|
if (pDirName) {
|
||||||
|
strcpy(m_szDir, pDirName);
|
||||||
|
strcat(m_szDir, "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pExtName) {
|
||||||
|
strcpy(m_szExt, pExtName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pBaseName) {
|
||||||
|
strcpy(m_szFile, pBaseName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release
|
||||||
|
free(pDir);
|
||||||
|
free(pBase);
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// File name + extension acquisition
|
||||||
|
// The returned pointer is temporary. Copy immediately.
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
const char *Filepath::GetFileExt() const
|
||||||
|
{
|
||||||
|
|
||||||
|
// Merge into static buffer
|
||||||
|
strcpy(FileExt, m_szExt);
|
||||||
|
|
||||||
|
// Return as LPCTSTR
|
||||||
|
return (const char *)FileExt;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL Filepath::Save(Fileio *fio, int /*ver*/)
|
||||||
|
{
|
||||||
|
ASSERT(fio);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL Filepath::Load(Fileio *fio, int /*ver*/)
|
||||||
|
{
|
||||||
|
ASSERT(fio);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Filename and extension
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
TCHAR Filepath::FileExt[_MAX_FNAME + _MAX_DIR];
|
|
@ -0,0 +1,52 @@
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// X68000 EMULATOR "XM6"
|
||||||
|
//
|
||||||
|
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||||
|
// Copyright (C) 2012-2020 GIMONS
|
||||||
|
// [ File path (subset) ]
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "os.h"
|
||||||
|
|
||||||
|
class Fileio;
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Constant definition
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
#define FILEPATH_MAX _MAX_PATH
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// File path
|
||||||
|
// Assignment operators are prepared here
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
class Filepath
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Filepath();
|
||||||
|
virtual ~Filepath();
|
||||||
|
Filepath& operator=(const Filepath& path);
|
||||||
|
|
||||||
|
void Clear();
|
||||||
|
void SetPath(const char *path); // File settings (user) for MBCS
|
||||||
|
const char *GetPath() const { return m_szPath; } // Get path name
|
||||||
|
const char *GetFileExt() const; // Get short name (LPCTSTR)
|
||||||
|
BOOL Save(Fileio *fio, int ver);
|
||||||
|
BOOL Load(Fileio *fio, int ver);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Split(); // Split the path
|
||||||
|
TCHAR m_szPath[_MAX_PATH]; // File path
|
||||||
|
TCHAR m_szDir[_MAX_DIR]; // Directory
|
||||||
|
TCHAR m_szFile[_MAX_FNAME]; // File
|
||||||
|
TCHAR m_szExt[_MAX_EXT]; // Extension
|
||||||
|
|
||||||
|
static TCHAR FileExt[_MAX_FNAME + _MAX_DIR]; // Short name (TCHAR)
|
||||||
|
};
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,698 @@
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// SCSI Target Emulator RaSCSI (*^..^*)
|
||||||
|
// for Raspberry Pi
|
||||||
|
//
|
||||||
|
// Powered by XM6 TypeG Technology.
|
||||||
|
// Copyright (C) 2016-2020 GIMONS
|
||||||
|
// [ GPIO-SCSI bus ]
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#if !defined(gpiobus_h)
|
||||||
|
#define gpiobus_h
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "scsi.h"
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Connection method definitions
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//#define CONNECT_TYPE_STANDARD // Standard (SCSI logic, standard pin assignment)
|
||||||
|
//#define CONNECT_TYPE_FULLSPEC // Full spec (SCSI logic, standard pin assignment)
|
||||||
|
//#define CONNECT_TYPE_AIBOM // AIBOM version (positive logic, unique pin assignment)
|
||||||
|
//#define CONNECT_TYPE_GAMERNIUM // GAMERnium.com version (standard logic, unique pin assignment)
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Signal control logic and pin assignment customization
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// SIGNAL_CONTROL_MODE: Signal control mode selection
|
||||||
|
// You can customize the signal control logic from Version 1.22
|
||||||
|
//
|
||||||
|
// 0:SCSI logical specification
|
||||||
|
// Conversion board using 74LS641-1 etc. directly connected or published on HP
|
||||||
|
// True : 0V
|
||||||
|
// False : Open collector output (disconnect from bus)
|
||||||
|
//
|
||||||
|
// 1:Negative logic specification (when using conversion board for negative logic -> SCSI logic)
|
||||||
|
// There is no conversion board with this specification at this time
|
||||||
|
// True : 0V -> (CONVERT) -> 0V
|
||||||
|
// False : 3.3V -> (CONVERT) -> Open collector output
|
||||||
|
//
|
||||||
|
// 2:Positive logic specification (when using the conversion board for positive logic -> SCSI logic)
|
||||||
|
// RaSCSI Adapter Rev.C @132sync etc.
|
||||||
|
//
|
||||||
|
// True : 3.3V -> (CONVERT) -> 0V
|
||||||
|
// False : 0V -> (CONVERT) -> Open collector output
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Control signal pin assignment setting
|
||||||
|
// GPIO pin mapping table for control signals.
|
||||||
|
//
|
||||||
|
// Control signal:
|
||||||
|
// PIN_ACT
|
||||||
|
// Signal that indicates the status of processing SCSI command.
|
||||||
|
// PIN_ENB
|
||||||
|
// Signal that indicates the valid signal from start to finish.
|
||||||
|
// PIN_TAD
|
||||||
|
// Signal that indicates the input/output direction of the target signal (BSY,IO,CD,MSG,REG).
|
||||||
|
// PIN_IND
|
||||||
|
// Signal that indicates the input/output direction of the initiator signal (SEL, ATN, RST, ACK).
|
||||||
|
// PIN_DTD
|
||||||
|
// Signal that indicates the input/output direction of the data lines (DT0...DT7,DP).
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Control signal output logic
|
||||||
|
// 0V:FALSE 3.3V:TRUE
|
||||||
|
//
|
||||||
|
// ACT_ON
|
||||||
|
// PIN_ACT signal
|
||||||
|
// ENB_ON
|
||||||
|
// PIN_ENB signal
|
||||||
|
// TAD_IN
|
||||||
|
// PIN_TAD This is the logic when inputting.
|
||||||
|
// IND_IN
|
||||||
|
// PIN_ENB This is the logic when inputting.
|
||||||
|
// DTD_IN
|
||||||
|
// PIN_ENB This is the logic when inputting.
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// SCSI signal pin assignment setting
|
||||||
|
// GPIO pin mapping table for SCSI signals.
|
||||||
|
// PIN_DT0~PIN_SEL
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#ifdef CONNECT_TYPE_STANDARD
|
||||||
|
//
|
||||||
|
// RaSCSI standard (SCSI logic, standard pin assignment)
|
||||||
|
//
|
||||||
|
#define CONNECT_DESC "STANDARD" // Startup message
|
||||||
|
|
||||||
|
// Select signal control mode
|
||||||
|
#define SIGNAL_CONTROL_MODE 0 // SCSI logical specification
|
||||||
|
|
||||||
|
// Control signal pin assignment (-1 means no control)
|
||||||
|
#define PIN_ACT 4 // ACTIVE
|
||||||
|
#define PIN_ENB 5 // ENABLE
|
||||||
|
#define PIN_IND -1 // INITIATOR CTRL DIRECTION
|
||||||
|
#define PIN_TAD -1 // TARGET CTRL DIRECTION
|
||||||
|
#define PIN_DTD -1 // DATA DIRECTION
|
||||||
|
|
||||||
|
// Control signal output logic
|
||||||
|
#define ACT_ON TRUE // ACTIVE SIGNAL ON
|
||||||
|
#define ENB_ON TRUE // ENABLE SIGNAL ON
|
||||||
|
#define IND_IN FALSE // INITIATOR SIGNAL INPUT
|
||||||
|
#define TAD_IN FALSE // TARGET SIGNAL INPUT
|
||||||
|
#define DTD_IN TRUE // DATA SIGNAL INPUT
|
||||||
|
|
||||||
|
// SCSI signal pin assignment
|
||||||
|
#define PIN_DT0 10 // Data 0
|
||||||
|
#define PIN_DT1 11 // Data 1
|
||||||
|
#define PIN_DT2 12 // Data 2
|
||||||
|
#define PIN_DT3 13 // Data 3
|
||||||
|
#define PIN_DT4 14 // Data 4
|
||||||
|
#define PIN_DT5 15 // Data 5
|
||||||
|
#define PIN_DT6 16 // Data 6
|
||||||
|
#define PIN_DT7 17 // Data 7
|
||||||
|
#define PIN_DP 18 // Data parity
|
||||||
|
#define PIN_ATN 19 // ATN
|
||||||
|
#define PIN_RST 20 // RST
|
||||||
|
#define PIN_ACK 21 // ACK
|
||||||
|
#define PIN_REQ 22 // REQ
|
||||||
|
#define PIN_MSG 23 // MSG
|
||||||
|
#define PIN_CD 24 // CD
|
||||||
|
#define PIN_IO 25 // IO
|
||||||
|
#define PIN_BSY 26 // BSY
|
||||||
|
#define PIN_SEL 27 // SEL
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONNECT_TYPE_FULLSPEC
|
||||||
|
//
|
||||||
|
// RaSCSI standard (SCSI logic, standard pin assignment)
|
||||||
|
//
|
||||||
|
#define CONNECT_DESC "FULLSPEC" // Startup message
|
||||||
|
|
||||||
|
// Select signal control mode
|
||||||
|
#define SIGNAL_CONTROL_MODE 0 // SCSI logical specification
|
||||||
|
|
||||||
|
// Control signal pin assignment (-1 means no control)
|
||||||
|
#define PIN_ACT 4 // ACTIVE
|
||||||
|
#define PIN_ENB 5 // ENABLE
|
||||||
|
#define PIN_IND 6 // INITIATOR CTRL DIRECTION
|
||||||
|
#define PIN_TAD 7 // TARGET CTRL DIRECTION
|
||||||
|
#define PIN_DTD 8 // DATA DIRECTION
|
||||||
|
|
||||||
|
// Control signal output logic
|
||||||
|
#define ACT_ON TRUE // ACTIVE SIGNAL ON
|
||||||
|
#define ENB_ON TRUE // ENABLE SIGNAL ON
|
||||||
|
#define IND_IN FALSE // INITIATOR SIGNAL INPUT
|
||||||
|
#define TAD_IN FALSE // TARGET SIGNAL INPUT
|
||||||
|
#define DTD_IN TRUE // DATA SIGNAL INPUT
|
||||||
|
|
||||||
|
// SCSI signal pin assignment
|
||||||
|
#define PIN_DT0 10 // Data 0
|
||||||
|
#define PIN_DT1 11 // Data 1
|
||||||
|
#define PIN_DT2 12 // Data 2
|
||||||
|
#define PIN_DT3 13 // Data 3
|
||||||
|
#define PIN_DT4 14 // Data 4
|
||||||
|
#define PIN_DT5 15 // Data 5
|
||||||
|
#define PIN_DT6 16 // Data 6
|
||||||
|
#define PIN_DT7 17 // Data 7
|
||||||
|
#define PIN_DP 18 // Data parity
|
||||||
|
#define PIN_ATN 19 // ATN
|
||||||
|
#define PIN_RST 20 // RST
|
||||||
|
#define PIN_ACK 21 // ACK
|
||||||
|
#define PIN_REQ 22 // REQ
|
||||||
|
#define PIN_MSG 23 // MSG
|
||||||
|
#define PIN_CD 24 // CD
|
||||||
|
#define PIN_IO 25 // IO
|
||||||
|
#define PIN_BSY 26 // BSY
|
||||||
|
#define PIN_SEL 27 // SEL
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONNECT_TYPE_AIBOM
|
||||||
|
//
|
||||||
|
// RaSCSI Adapter Aibom version
|
||||||
|
//
|
||||||
|
|
||||||
|
#define CONNECT_DESC "AIBOM PRODUCTS version" // Startup message
|
||||||
|
|
||||||
|
// Select signal control mode
|
||||||
|
#define SIGNAL_CONTROL_MODE 2 // SCSI positive logic specification
|
||||||
|
|
||||||
|
// Control signal output logic
|
||||||
|
#define ACT_ON TRUE // ACTIVE SIGNAL ON
|
||||||
|
#define ENB_ON TRUE // ENABLE SIGNAL ON
|
||||||
|
#define IND_IN FALSE // INITIATOR SIGNAL INPUT
|
||||||
|
#define TAD_IN FALSE // TARGET SIGNAL INPUT
|
||||||
|
#define DTD_IN FALSE // DATA SIGNAL INPUT
|
||||||
|
|
||||||
|
// Control signal pin assignment (-1 means no control)
|
||||||
|
#define PIN_ACT 4 // ACTIVE
|
||||||
|
#define PIN_ENB 17 // ENABLE
|
||||||
|
#define PIN_IND 27 // INITIATOR CTRL DIRECTION
|
||||||
|
#define PIN_TAD -1 // TARGET CTRL DIRECTION
|
||||||
|
#define PIN_DTD 18 // DATA DIRECTION
|
||||||
|
|
||||||
|
// SCSI signal pin assignment
|
||||||
|
#define PIN_DT0 6 // Data 0
|
||||||
|
#define PIN_DT1 12 // Data 1
|
||||||
|
#define PIN_DT2 13 // Data 2
|
||||||
|
#define PIN_DT3 16 // Data 3
|
||||||
|
#define PIN_DT4 19 // Data 4
|
||||||
|
#define PIN_DT5 20 // Data 5
|
||||||
|
#define PIN_DT6 26 // Data 6
|
||||||
|
#define PIN_DT7 21 // Data 7
|
||||||
|
#define PIN_DP 5 // Data parity
|
||||||
|
#define PIN_ATN 22 // ATN
|
||||||
|
#define PIN_RST 25 // RST
|
||||||
|
#define PIN_ACK 10 // ACK
|
||||||
|
#define PIN_REQ 7 // REQ
|
||||||
|
#define PIN_MSG 9 // MSG
|
||||||
|
#define PIN_CD 11 // CD
|
||||||
|
#define PIN_IO 23 // IO
|
||||||
|
#define PIN_BSY 24 // BSY
|
||||||
|
#define PIN_SEL 8 // SEL
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONNECT_TYPE_GAMERNIUM
|
||||||
|
//
|
||||||
|
// RaSCSI Adapter GAMERnium.com version
|
||||||
|
//
|
||||||
|
|
||||||
|
#define CONNECT_DESC "GAMERnium.com version"// Startup message
|
||||||
|
|
||||||
|
// Select signal control mode
|
||||||
|
#define SIGNAL_CONTROL_MODE 0 // SCSI logical specification
|
||||||
|
|
||||||
|
// Control signal output logic
|
||||||
|
#define ACT_ON TRUE // ACTIVE SIGNAL ON
|
||||||
|
#define ENB_ON TRUE // ENABLE SIGNAL ON
|
||||||
|
#define IND_IN FALSE // INITIATOR SIGNAL INPUT
|
||||||
|
#define TAD_IN FALSE // TARGET SIGNAL INPUT
|
||||||
|
#define DTD_IN TRUE // DATA SIGNAL INPUT
|
||||||
|
|
||||||
|
// Control signal pin assignment (-1 means no control)
|
||||||
|
#define PIN_ACT 14 // ACTIVE
|
||||||
|
#define PIN_ENB 6 // ENABLE
|
||||||
|
#define PIN_IND 7 // INITIATOR CTRL DIRECTION
|
||||||
|
#define PIN_TAD 8 // TARGET CTRL DIRECTION
|
||||||
|
#define PIN_DTD 5 // DATA DIRECTION
|
||||||
|
|
||||||
|
// SCSI signal pin assignment
|
||||||
|
#define PIN_DT0 21 // Data 0
|
||||||
|
#define PIN_DT1 26 // Data 1
|
||||||
|
#define PIN_DT2 20 // Data 2
|
||||||
|
#define PIN_DT3 19 // Data 3
|
||||||
|
#define PIN_DT4 16 // Data 4
|
||||||
|
#define PIN_DT5 13 // Data 5
|
||||||
|
#define PIN_DT6 12 // Data 6
|
||||||
|
#define PIN_DT7 11 // Data 7
|
||||||
|
#define PIN_DP 25 // Data parity
|
||||||
|
#define PIN_ATN 10 // ATN
|
||||||
|
#define PIN_RST 22 // RST
|
||||||
|
#define PIN_ACK 24 // ACK
|
||||||
|
#define PIN_REQ 15 // REQ
|
||||||
|
#define PIN_MSG 17 // MSG
|
||||||
|
#define PIN_CD 18 // CD
|
||||||
|
#define PIN_IO 4 // IO
|
||||||
|
#define PIN_BSY 27 // BSY
|
||||||
|
#define PIN_SEL 23 // SEL
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define ALL_SCSI_PINS \
|
||||||
|
((1<<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
|
|
@ -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 "$@"
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
};
|
|
@ -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
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
|
@ -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();
|
||||||
|
}
|
|
@ -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, ×tamp_str[strlen(timestamp_label)], 16);
|
||||||
|
timestamp[16] = '\0';
|
||||||
|
timestamp_uint = strtoull(timestamp, &ptr, 16);
|
||||||
|
|
||||||
|
data_str = strstr(str_buf, data_label);
|
||||||
|
if (!data_str)
|
||||||
|
continue;
|
||||||
|
strncpy(data, &data_str[strlen(data_label)], 8);
|
||||||
|
data[8] = '\0';
|
||||||
|
data_uint = strtoul(data, &ptr, 16);
|
||||||
|
|
||||||
|
// printf("Time: %016llX Data: %08X\n", timestamp_uint, data_uint);
|
||||||
|
|
||||||
|
data_capture_array[sample_count].timestamp = timestamp_uint;
|
||||||
|
data_capture_array[sample_count].data = data_uint;
|
||||||
|
sample_count++;
|
||||||
|
if (sample_count >= max_sz)
|
||||||
|
{
|
||||||
|
LOGWARN("File exceeds maximum buffer size. Some data may be missing.");
|
||||||
|
LOGWARN("Try re-running the tool with a larger buffer size");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
return sample_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Generate JSON Output File
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
void scsimon_generate_json(const char *filename, const data_capture *data_capture_array, DWORD capture_count)
|
||||||
|
{
|
||||||
|
LOGTRACE("Creating JSON file (%s)", filename);
|
||||||
|
ofstream json_ofstream;
|
||||||
|
json_ofstream.open(filename, ios::out);
|
||||||
|
|
||||||
|
json_ofstream << "[" << endl;
|
||||||
|
|
||||||
|
DWORD i = 0;
|
||||||
|
while (i < capture_count)
|
||||||
|
{
|
||||||
|
json_ofstream << fmt::format("{{\"id\": \"{0:d}\", \"timestamp\":\"{1:#016x}\", \"data\":\"{2:#08x}\"}}", i, data_capture_array[i].timestamp, data_capture_array[i].data);
|
||||||
|
|
||||||
|
if (i != (capture_count - 1))
|
||||||
|
{
|
||||||
|
json_ofstream << ",";
|
||||||
|
}
|
||||||
|
json_ofstream << endl;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
json_ofstream << "]" << endl;
|
||||||
|
json_ofstream.close();
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
|
@ -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();
|
||||||
|
}
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,2 @@
|
||||||
|
if $programname == 'RASCSI' then /var/log/rascsi.log
|
||||||
|
& stop
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
@ -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);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
};
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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);
|
||||||
|
};
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
|
@ -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
Loading…
Reference in New Issue