mirror of
https://github.com/akuker/RASCSI.git
synced 2024-12-22 15:30:09 +00:00
621cc7d5a2
* Unit test updates * Lambda syntax cleanup * Use new-style casts * Use std::none_of when saving the cache * Use to_integer instead of casts * Use accessors for getting CDB data * Made ctrl_t private * Improved encapsulation * Replaced pointers by references * Removed all remaining occurrences of DWORD and BYTE, making os.h obsolete
414 lines
9.1 KiB
C++
414 lines
9.1 KiB
C++
//---------------------------------------------------------------------------
|
||
//
|
||
// SCSI Target Emulator RaSCSI Reloaded
|
||
// 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 "rascsi_exceptions.h"
|
||
#include "scsi_command_util.h"
|
||
#include "dispatcher.h"
|
||
#include "scsicd.h"
|
||
#include <array>
|
||
#include <fstream>
|
||
|
||
using namespace scsi_defs;
|
||
using namespace scsi_command_util;
|
||
|
||
SCSICD::SCSICD(int lun, const unordered_set<uint32_t>& sector_sizes) : Disk(SCCD, lun)
|
||
{
|
||
SetSectorSizes(sector_sizes);
|
||
|
||
dispatcher.Add(scsi_command::eCmdReadToc, "ReadToc", &SCSICD::ReadToc);
|
||
|
||
SetReadOnly(true);
|
||
SetRemovable(true);
|
||
SetLockable(true);
|
||
}
|
||
|
||
bool SCSICD::Dispatch(scsi_command cmd)
|
||
{
|
||
// The superclass class handles the less specific commands
|
||
return dispatcher.Dispatch(this, cmd) ? true : super::Dispatch(cmd);
|
||
}
|
||
|
||
void SCSICD::Open()
|
||
{
|
||
assert(!IsReady());
|
||
|
||
// Initialization, track clear
|
||
SetBlockCount(0);
|
||
rawfile = false;
|
||
ClearTrack();
|
||
|
||
// Default sector size is 2048 bytes
|
||
SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 2048);
|
||
|
||
if (GetFilename()[0] == '\\') {
|
||
OpenPhysical();
|
||
} else {
|
||
// Judge whether it is a CUE sheet or an ISO file
|
||
array<char, 4> cue;
|
||
ifstream in(GetFilename(), ios::binary);
|
||
in.read(cue.data(), cue.size());
|
||
if (!in.good()) {
|
||
throw io_exception("Can't read header of CD-ROM file '" + GetFilename() + "'");
|
||
}
|
||
|
||
// If it starts with FILE consider it a CUE sheet
|
||
if (!strncasecmp(cue.data(), "FILE", cue.size())) {
|
||
throw io_exception("CUE CD-ROM files are not supported");
|
||
} else {
|
||
OpenIso();
|
||
}
|
||
}
|
||
|
||
super::ValidateFile();
|
||
|
||
SetUpCache(0, rawfile);
|
||
|
||
SetReadOnly(true);
|
||
SetProtectable(false);
|
||
|
||
// Attention if ready
|
||
if (IsReady()) {
|
||
SetAttn(true);
|
||
}
|
||
}
|
||
|
||
void SCSICD::OpenIso()
|
||
{
|
||
const off_t size = GetFileSize();
|
||
if (size < 2048) {
|
||
throw io_exception("ISO CD-ROM file size must be at least 2048 bytes");
|
||
}
|
||
|
||
// Validate header
|
||
array<char, 16> header;
|
||
ifstream in(GetFilename(), ios::binary);
|
||
in.read(header.data(), header.size());
|
||
if (!in.good()) {
|
||
throw io_exception("Can't read header of ISO CD-ROM file");
|
||
}
|
||
|
||
// Check if it is in RAW format
|
||
array<char, 12> sync = {};
|
||
// 00,FFx10,00 is presumed to be RAW format
|
||
fill_n(sync.begin() + 1, 10, 0xff);
|
||
rawfile = false;
|
||
|
||
if (memcmp(header.data(), sync.data(), sync.size()) == 0) {
|
||
// Supports MODE1/2048 or MODE1/2352 only
|
||
if (header[15] != 0x01) {
|
||
// Different mode
|
||
throw io_exception("Illegal raw ISO CD-ROM file header");
|
||
}
|
||
|
||
// Set to RAW file
|
||
rawfile = true;
|
||
}
|
||
|
||
if (rawfile) {
|
||
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");
|
||
}
|
||
|
||
SetBlockCount(static_cast<uint32_t>(size / 2352));
|
||
} else {
|
||
SetBlockCount(static_cast<uint32_t>(size >> GetSectorSizeShiftCount()));
|
||
}
|
||
|
||
CreateDataTrack();
|
||
}
|
||
|
||
// TODO This code is only executed if the filename starts with a `\`, but fails to open files starting with `\`.
|
||
void SCSICD::OpenPhysical()
|
||
{
|
||
// Get size
|
||
off_t size = GetFileSize();
|
||
if (size < 2048) {
|
||
throw io_exception("CD-ROM file size must be at least 2048 bytes");
|
||
}
|
||
|
||
// Effective size must be a multiple of 512
|
||
size = (size / 512) * 512;
|
||
|
||
// Set the number of blocks
|
||
SetBlockCount(static_cast<uint32_t>(size >> GetSectorSizeShiftCount()));
|
||
|
||
CreateDataTrack();
|
||
}
|
||
|
||
void SCSICD::CreateDataTrack()
|
||
{
|
||
// Create only one data track
|
||
assert(!tracks.size());
|
||
auto track = make_unique<CDTrack>();
|
||
track->Init(1, 0, static_cast<int>(GetBlockCount()) - 1);
|
||
track->SetPath(false, GetFilename());
|
||
tracks.push_back(move(track));
|
||
dataindex = 0;
|
||
}
|
||
|
||
void SCSICD::ReadToc()
|
||
{
|
||
controller->SetLength(ReadTocInternal(controller->GetCmd(), controller->GetBuffer()));
|
||
|
||
EnterDataInPhase();
|
||
}
|
||
|
||
vector<byte> SCSICD::InquiryInternal() const
|
||
{
|
||
return HandleInquiry(device_type::CD_ROM, scsi_level::SCSI_2, true);
|
||
}
|
||
|
||
void SCSICD::SetUpModePages(map<int, vector<byte>>& pages, int page, bool changeable) const
|
||
{
|
||
super::SetUpModePages(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] = (byte)0x05;
|
||
|
||
// MSF multiples are 60 and 75 respectively
|
||
buf[5] = (byte)60;
|
||
buf[7] = (byte)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;
|
||
}
|
||
|
||
void SCSICD::AddVendorPage(map<int, vector<byte>>& pages, int page, bool changeable) const
|
||
{
|
||
// Page code 48
|
||
if (page == 0x30 || page == 0x3f) {
|
||
AddAppleVendorModePage(pages, changeable);
|
||
}
|
||
}
|
||
|
||
int SCSICD::Read(const vector<int>& cdb, vector<uint8_t>& buf, uint64_t block)
|
||
{
|
||
CheckReady();
|
||
|
||
// Search for the track
|
||
const int index = SearchTrack(static_cast<int>(block));
|
||
|
||
// If invalid, out of range
|
||
if (index < 0) {
|
||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE);
|
||
}
|
||
|
||
assert(tracks[index]);
|
||
|
||
// If different from the current data track
|
||
if (dataindex != index) {
|
||
// Reset the number of blocks
|
||
SetBlockCount(tracks[index]->GetBlocks());
|
||
assert(GetBlockCount() > 0);
|
||
|
||
// Re-assign disk cache (no need to save)
|
||
ResizeCache(tracks[index]->GetPath(), rawfile);
|
||
|
||
// Reset data index
|
||
dataindex = index;
|
||
}
|
||
|
||
// Base class
|
||
assert(dataindex >= 0);
|
||
return super::Read(cdb, buf, block);
|
||
}
|
||
|
||
int SCSICD::ReadTocInternal(const vector<int>& cdb, vector<uint8_t>& buf)
|
||
{
|
||
CheckReady();
|
||
|
||
// If ready, there is at least one track
|
||
assert(tracks.size() > 0);
|
||
assert(tracks[0]);
|
||
|
||
// Get allocation length, clear buffer
|
||
const int length = GetInt16(cdb, 7);
|
||
fill_n(buf.data(), length, 0);
|
||
|
||
// Get MSF Flag
|
||
const bool msf = cdb[1] & 0x02;
|
||
|
||
// Get and check the last track number
|
||
const int last = tracks[tracks.size() - 1]->GetTrackNo();
|
||
// Except for AA
|
||
if (cdb[6] > last && cdb[6] != 0xaa) {
|
||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
|
||
}
|
||
|
||
// Check start index
|
||
int index = 0;
|
||
if (cdb[6] != 0x00) {
|
||
// Advance the track until the track numbers match
|
||
while (tracks[index]) {
|
||
if (cdb[6] == tracks[index]->GetTrackNo()) {
|
||
break;
|
||
}
|
||
index++;
|
||
}
|
||
|
||
// AA if not found or internal error
|
||
if (!tracks[index]) {
|
||
if (cdb[6] != 0xaa) {
|
||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
|
||
}
|
||
|
||
// Returns the final LBA+1 because it is AA
|
||
buf[0] = 0x00;
|
||
buf[1] = 0x0a;
|
||
buf[2] = (uint8_t)tracks[0]->GetTrackNo();
|
||
buf[3] = (uint8_t)last;
|
||
buf[6] = 0xaa;
|
||
const uint32_t lba = tracks[tracks.size() - 1]->GetLast() + 1;
|
||
if (msf) {
|
||
LBAtoMSF(lba, &buf[8]);
|
||
} else {
|
||
SetInt16(buf, 10, lba);
|
||
}
|
||
|
||
return length;
|
||
}
|
||
}
|
||
|
||
// Number of track descriptors returned this time (number of loops)
|
||
const int loop = last - tracks[index]->GetTrackNo() + 1;
|
||
assert(loop >= 1);
|
||
|
||
// Create header
|
||
SetInt16(buf, 0, (loop << 3) + 2);
|
||
buf[2] = (uint8_t)tracks[0]->GetTrackNo();
|
||
buf[3] = (uint8_t)last;
|
||
|
||
int offset = 4;
|
||
|
||
// Loop....
|
||
for (int i = 0; i < loop; i++) {
|
||
// ADR and Control
|
||
if (tracks[index]->IsAudio()) {
|
||
// audio track
|
||
buf[offset + 1] = 0x10;
|
||
} else {
|
||
// data track
|
||
buf[offset + 1] = 0x14;
|
||
}
|
||
|
||
// track number
|
||
buf[offset + 2] = (uint8_t)tracks[index]->GetTrackNo();
|
||
|
||
// track address
|
||
if (msf) {
|
||
LBAtoMSF(tracks[index]->GetFirst(), &buf[offset + 4]);
|
||
} else {
|
||
SetInt16(buf, offset + 6, tracks[index]->GetFirst());
|
||
}
|
||
|
||
// Advance buffer pointer and index
|
||
offset += 8;
|
||
index++;
|
||
}
|
||
|
||
// Always return only the allocation length
|
||
return length;
|
||
}
|
||
|
||
//---------------------------------------------------------------------------
|
||
//
|
||
// LBA→MSF Conversion
|
||
//
|
||
//---------------------------------------------------------------------------
|
||
void SCSICD::LBAtoMSF(uint32_t lba, uint8_t *msf) const
|
||
{
|
||
// 75 and 75*60 get the remainder
|
||
uint32_t m = lba / (75 * 60);
|
||
uint32_t s = lba % (75 * 60);
|
||
const uint32_t 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] = (uint8_t)m;
|
||
msf[2] = (uint8_t)s;
|
||
msf[3] = (uint8_t)f;
|
||
}
|
||
|
||
void SCSICD::ClearTrack()
|
||
{
|
||
tracks.clear();
|
||
|
||
// No settings for data and audio
|
||
dataindex = -1;
|
||
audioindex = -1;
|
||
}
|
||
|
||
//---------------------------------------------------------------------------
|
||
//
|
||
// Track Search
|
||
// * Returns -1 if not found
|
||
//
|
||
//---------------------------------------------------------------------------
|
||
int SCSICD::SearchTrack(uint32_t lba) const
|
||
{
|
||
// Track loop
|
||
for (size_t i = 0; i < tracks.size(); i++) {
|
||
// Listen to the track
|
||
assert(tracks[i]);
|
||
if (tracks[i]->IsValid(lba)) {
|
||
return static_cast<int>(i);
|
||
}
|
||
}
|
||
|
||
// Track wasn't found
|
||
return -1;
|
||
}
|
||
|