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