RASCSI/cpp/devices/scsicd.cpp
Uwe Seimet 621cc7d5a2
Code cleanup, especially casts, lambdas, data types, encapsulation (#952)
* 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
2022-11-02 07:36:25 +01:00

414 lines
9.1 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi
//
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
// [ SCSI CD-ROM ]
//
//---------------------------------------------------------------------------
#include "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;
}