RASCSI/src/raspberrypi/devices/scsicd.cpp

739 lines
15 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 (*^..^*)
// for Raspberry Pi
//
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
// [ SCSI CD-ROM ]
//
//---------------------------------------------------------------------------
#include "scsicd.h"
#include "fileio.h"
#include "exceptions.h"
#include "disk_image/disk_image_handle_factory.h"
using namespace scsi_defs;
//===========================================================================
//
// CD Track
//
//===========================================================================
CDTrack::CDTrack(SCSICD *scsicd)
{
ASSERT(scsicd);
// Set parent CD-ROM device
cdrom = scsicd;
// Track defaults to disabled
valid = false;
// Initialize other data
track_no = -1;
first_lba = 0;
last_lba = 0;
audio = false;
raw = false;
}
void CDTrack::Init(int track, DWORD first, DWORD last)
{
ASSERT(!valid);
ASSERT(track >= 1);
ASSERT(first < last);
// Set and enable track number
track_no = track;
valid = TRUE;
// Remember LBA
first_lba = first;
last_lba = last;
}
void CDTrack::SetPath(bool cdda, const Filepath& path)
{
ASSERT(valid);
// CD-DA or data
audio = cdda;
// Remember the path
imgpath = path;
}
void CDTrack::GetPath(Filepath& path) const
{
ASSERT(valid);
// Return the path (by reference)
path = imgpath;
}
void CDTrack::AddIndex(int index, DWORD lba)
{
ASSERT(valid);
ASSERT(index > 0);
ASSERT(first_lba <= lba);
ASSERT(lba <= last_lba);
// Currently does not support indexes
ASSERT(FALSE);
}
//---------------------------------------------------------------------------
//
// Gets the start of LBA
//
//---------------------------------------------------------------------------
DWORD CDTrack::GetFirst() const
{
ASSERT(valid);
ASSERT(first_lba < last_lba);
return first_lba;
}
//---------------------------------------------------------------------------
//
// Get the end of LBA
//
//---------------------------------------------------------------------------
DWORD CDTrack::GetLast() const
{
ASSERT(valid);
ASSERT(first_lba < last_lba);
return last_lba;
}
DWORD CDTrack::GetBlocks() const
{
ASSERT(valid);
ASSERT(first_lba < last_lba);
// Calculate from start LBA and end LBA
return (DWORD)(last_lba - first_lba + 1);
}
int CDTrack::GetTrackNo() const
{
ASSERT(valid);
ASSERT(track_no >= 1);
return track_no;
}
//---------------------------------------------------------------------------
//
// Is valid block
//
//---------------------------------------------------------------------------
bool CDTrack::IsValid(DWORD lba) const
{
// FALSE if the track itself is invalid
if (!valid) {
return false;
}
// If the block is BEFORE the first block
if (lba < first_lba) {
return false;
}
// If the block is AFTER the last block
if (last_lba < lba) {
return false;
}
// This track is valid
return true;
}
//---------------------------------------------------------------------------
//
// Is audio track
//
//---------------------------------------------------------------------------
bool CDTrack::IsAudio() const
{
ASSERT(valid);
return audio;
}
//===========================================================================
//
// SCSI CD-ROM
//
//===========================================================================
SCSICD::SCSICD(const set<uint32_t>& sector_sizes) : Disk("SCCD"), ScsiMmcCommands(), FileSupport()
{
SetSectorSizes(sector_sizes);
// NOT in raw format
rawfile = false;
// Frame initialization
frame = 0;
// Track initialization
for (int i = 0; i < TrackMax; i++) {
track[i] = NULL;
}
tracks = 0;
dataindex = -1;
audioindex = -1;
dispatcher.AddCommand(eCmdReadToc, "ReadToc", &SCSICD::ReadToc);
dispatcher.AddCommand(eCmdGetEventStatusNotification, "GetEventStatusNotification", &SCSICD::GetEventStatusNotification);
}
SCSICD::~SCSICD()
{
// Clear track
ClearTrack();
}
bool SCSICD::Dispatch(SCSIDEV *controller)
{
// The superclass class handles the less specific commands
return dispatcher.Dispatch(this, controller) ? true : super::Dispatch(controller);
}
void SCSICD::Open(const Filepath& path)
{
off_t size;
ASSERT(!IsReady());
// Initialization, track clear
SetBlockCount(0);
rawfile = false;
ClearTrack();
// Open as read-only
Fileio fio;
if (!fio.Open(path, Fileio::ReadOnly)) {
throw file_not_found_exception("Can't open CD-ROM file");
}
// Default sector size is 2048 bytes
SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 2048, false);
// Close and transfer for physical CD access
if (path.GetPath()[0] == _T('\\')) {
// Close
fio.Close();
// Open physical CD
OpenPhysical(path);
} else {
// Get file size
size = fio.GetFileSize();
if (size <= 4) {
fio.Close();
throw io_exception("CD-ROM file size must be at least 4 bytes");
}
// Judge whether it is a CUE sheet or an ISO file
TCHAR file[5];
fio.Read(file, 4);
file[4] = '\0';
fio.Close();
// If it starts with FILE, consider it as a CUE sheet
if (!strncasecmp(file, _T("FILE"), 4)) {
// Open as CUE
OpenCue(path);
} else {
// Open as ISO
OpenIso(path);
}
}
// Successful opening
ASSERT(GetBlockCount() > 0);
super::Open(path);
FileSupport::SetPath(path);
// Set RAW flag
ASSERT(disk.dcache);
disk.dcache->SetRawMode(rawfile);
// Attention if ready
if (IsReady()) {
SetAttn(true);
}
}
void SCSICD::OpenCue(const Filepath& /*path*/)
{
throw io_exception("Opening CUE CD-ROM files is not supported");
}
void SCSICD::OpenIso(const Filepath& path)
{
// Open as read-only
Fileio fio;
if (!fio.Open(path, Fileio::ReadOnly)) {
throw io_exception("Can't open ISO CD-ROM file");
}
// Get file size
off_t size = fio.GetFileSize();
if (size < 0x800) {
fio.Close();
throw io_exception("ISO CD-ROM file size must be at least 2048 bytes");
}
// Read the first 12 bytes and close
BYTE header[12];
if (!fio.Read(header, sizeof(header))) {
fio.Close();
throw io_exception("Can't read header of ISO CD-ROM file");
}
// Check if it is RAW format
BYTE sync[12];
memset(sync, 0xff, sizeof(sync));
sync[0] = 0x00;
sync[11] = 0x00;
rawfile = false;
if (memcmp(header, sync, sizeof(sync)) == 0) {
// 00,FFx10,00, so it is presumed to be RAW format
if (!fio.Read(header, 4)) {
fio.Close();
throw io_exception("Can't read header of raw ISO CD-ROM file");
}
// Supports MODE1/2048 or MODE1/2352 only
if (header[3] != 0x01) {
// Different mode
fio.Close();
throw io_exception("Illegal raw ISO CD-ROM file header");
}
// Set to RAW file
rawfile = true;
}
fio.Close();
if (rawfile) {
// Size must be a multiple of 2536
if (size % 2536) {
throw io_exception("Raw ISO CD-ROM file size must be a multiple of 2536 bytes but is "
+ to_string(size) + " bytes");
}
// Set the number of blocks
SetBlockCount((DWORD)(size / 0x930));
} else {
// Set the number of blocks
SetBlockCount((DWORD)(size >> GetSectorSizeShiftCount()));
}
// Create only one data track
ASSERT(!track[0]);
track[0] = new CDTrack(this);
track[0]->Init(1, 0, GetBlockCount() - 1);
track[0]->SetPath(false, path);
tracks = 1;
dataindex = 0;
}
void SCSICD::OpenPhysical(const Filepath& path)
{
// Open as read-only
Fileio fio;
if (!fio.Open(path, Fileio::ReadOnly)) {
throw io_exception("Can't open CD-ROM file");
}
// Get size
off_t size = fio.GetFileSize();
if (size < 0x800) {
fio.Close();
throw io_exception("CD-ROM file size must be at least 2048 bytes");
}
// Close
fio.Close();
// Effective size must be a multiple of 512
size = (size / 512) * 512;
// Set the number of blocks
SetBlockCount((DWORD)(size >> GetSectorSizeShiftCount()));
// Create only one data track
ASSERT(!track[0]);
track[0] = new CDTrack(this);
track[0]->Init(1, 0, GetBlockCount() - 1);
track[0]->SetPath(false, path);
tracks = 1;
dataindex = 0;
}
void SCSICD::ReadToc(SASIDEV *controller)
{
ctrl->length = ReadToc(ctrl->cmd, ctrl->buffer);
if (ctrl->length <= 0) {
// Failure (Error)
controller->Error();
return;
}
controller->DataIn();
}
int SCSICD::Inquiry(const DWORD *cdb, BYTE *buf)
{
// EVPD check
if (cdb[1] & 0x01) {
SetStatusCode(STATUS_INVALIDCDB);
return 0;
}
// Basic data
// buf[0] ... CD-ROM Device
// buf[1] ... Removable
// buf[2] ... SCSI-2 compliant command system
// buf[3] ... SCSI-2 compliant Inquiry response
// buf[4] ... Inquiry additional data
memset(buf, 0, 8);
buf[0] = 0x05;
buf[1] = 0x80;
buf[2] = 0x02;
buf[3] = 0x02;
buf[4] = 0x1F;
// Fill with blanks
memset(&buf[8], 0x20, buf[4] - 3);
// Padded vendor, product, revision
memcpy(&buf[8], GetPaddedName().c_str(), 28);
//
// The following code worked with the modified Apple CD-ROM drivers. Need to
// test with the original code to see if it works as well....
// buf[4] = 42; // Required
//
// // Fill with blanks
// memset(&buf[8], 0x20, buf[4] - 3);
//
// // Vendor name
// memcpy(&buf[8], BENDER_SIGNATURE, strlen(BENDER_SIGNATURE));
//
// // Product name
// memcpy(&buf[16], "CD-ROM CDU-8003A", 16);
//
// // Revision (XM6 version number)
//// sprintf(rev, "1.9a",
// //// (int)major, (int)(minor >> 4), (int)(minor & 0x0f));
// memcpy(&buf[32], "1.9a", 4);
//
// //strcpy(&buf[35],"A1.9a");
// buf[36]=0x20;
// memcpy(&buf[37],"1999/01/01",10);
// Size of data that can be returned
int size = (buf[4] + 5);
// Limit if the other buffer is small
if (size > (int)cdb[4]) {
size = (int)cdb[4];
}
return size;
}
void SCSICD::AddModePages(map<int, vector<BYTE>>& pages, int page, bool changeable) const
{
super::AddModePages(pages, page, changeable);
// Page code 13
if (page == 0x0d || page == 0x3f) {
AddCDROMPage(pages, changeable);
}
// Page code 14
if (page == 0x0e || page == 0x3f) {
AddCDDAPage(pages, changeable);
}
}
void SCSICD::AddCDROMPage(map<int, vector<BYTE>>& pages, bool changeable) const
{
vector<BYTE> buf(8);
// No changeable area
if (!changeable) {
// 2 seconds for inactive timer
buf[3] = 0x05;
// MSF multiples are 60 and 75 respectively
buf[5] = 60;
buf[7] = 75;
}
pages[13] = buf;
}
void SCSICD::AddCDDAPage(map<int, vector<BYTE>>& pages, bool) const
{
vector<BYTE> buf(16);
// Audio waits for operation completion and allows
// PLAY across multiple tracks
pages[14] = buf;
}
int SCSICD::Read(const DWORD *cdb, BYTE *buf, uint64_t block)
{
ASSERT(buf);
// Status check
if (!CheckReady()) {
return 0;
}
// Search for the track
int index = SearchTrack(block);
// if invalid, out of range
if (index < 0) {
SetStatusCode(STATUS_INVALIDLBA);
return 0;
}
ASSERT(track[index]);
// If different from the current data track
if (dataindex != index) {
// Delete current disk cache (no need to save)
delete disk.dcache;
disk.dcache = NULL;
// Reset the number of blocks
SetBlockCount(track[index]->GetBlocks());
ASSERT(GetBlockCount() > 0);
// Recreate the disk cache
Filepath path;
track[index]->GetPath(path);
disk.dcache = DiskImageHandleFactory::CreateDiskImageHandle(path, GetSectorSizeShiftCount(), GetBlockCount());
disk.dcache->SetRawMode(rawfile);
// Reset data index
dataindex = index;
}
// Base class
ASSERT(dataindex >= 0);
return super::Read(cdb, buf, block);
}
int SCSICD::ReadToc(const DWORD *cdb, BYTE *buf)
{
ASSERT(cdb);
ASSERT(buf);
// Check if ready
if (!CheckReady()) {
return 0;
}
// If ready, there is at least one track
ASSERT(tracks > 0);
ASSERT(track[0]);
// Get allocation length, clear buffer
int length = cdb[7] << 8;
length |= cdb[8];
memset(buf, 0, length);
// Get MSF Flag
bool msf = cdb[1] & 0x02;
// Get and check the last track number
int last = track[tracks - 1]->GetTrackNo();
if ((int)cdb[6] > last) {
// Except for AA
if (cdb[6] != 0xaa) {
SetStatusCode(STATUS_INVALIDCDB);
return 0;
}
}
// Check start index
int index = 0;
if (cdb[6] != 0x00) {
// Advance the track until the track numbers match
while (track[index]) {
if ((int)cdb[6] == track[index]->GetTrackNo()) {
break;
}
index++;
}
// AA if not found or internal error
if (!track[index]) {
if (cdb[6] == 0xaa) {
// Returns the final LBA+1 because it is AA
buf[0] = 0x00;
buf[1] = 0x0a;
buf[2] = (BYTE)track[0]->GetTrackNo();
buf[3] = (BYTE)last;
buf[6] = 0xaa;
DWORD lba = track[tracks - 1]->GetLast() + 1;
if (msf) {
LBAtoMSF(lba, &buf[8]);
} else {
buf[10] = (BYTE)(lba >> 8);
buf[11] = (BYTE)lba;
}
return length;
}
// Otherwise, error
SetStatusCode(STATUS_INVALIDCDB);
return 0;
}
}
// Number of track descriptors returned this time (number of loops)
int loop = last - track[index]->GetTrackNo() + 1;
ASSERT(loop >= 1);
// Create header
buf[0] = (BYTE)(((loop << 3) + 2) >> 8);
buf[1] = (BYTE)((loop << 3) + 2);
buf[2] = (BYTE)track[0]->GetTrackNo();
buf[3] = (BYTE)last;
buf += 4;
// Loop....
for (int i = 0; i < loop; i++) {
// ADR and Control
if (track[index]->IsAudio()) {
// audio track
buf[1] = 0x10;
} else {
// data track
buf[1] = 0x14;
}
// track number
buf[2] = (BYTE)track[index]->GetTrackNo();
// track address
if (msf) {
LBAtoMSF(track[index]->GetFirst(), &buf[4]);
} else {
buf[6] = (BYTE)(track[index]->GetFirst() >> 8);
buf[7] = (BYTE)(track[index]->GetFirst());
}
// Advance buffer pointer and index
buf += 8;
index++;
}
// Always return only the allocation length
return length;
}
void SCSICD::GetEventStatusNotification(SASIDEV *controller)
{
if (!(ctrl->cmd[1] & 0x01)) {
// Asynchronous notification is optional and not supported by rascsi
controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::INVALID_FIELD_IN_CDB);
return;
}
LOGTRACE("Received request for event polling, which is currently not supported");
controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::INVALID_FIELD_IN_CDB);
}
//---------------------------------------------------------------------------
//
// LBA→MSF Conversion
//
//---------------------------------------------------------------------------
void SCSICD::LBAtoMSF(DWORD lba, BYTE *msf) const
{
// 75 and 75*60 get the remainder
DWORD m = lba / (75 * 60);
DWORD s = lba % (75 * 60);
DWORD f = s % 75;
s /= 75;
// The base point is M=0, S=2, F=0
s += 2;
if (s >= 60) {
s -= 60;
m++;
}
// Store
ASSERT(m < 0x100);
ASSERT(s < 60);
ASSERT(f < 75);
msf[0] = 0x00;
msf[1] = (BYTE)m;
msf[2] = (BYTE)s;
msf[3] = (BYTE)f;
}
void SCSICD::ClearTrack()
{
// delete the track object
for (int i = 0; i < TrackMax; i++) {
if (track[i]) {
delete track[i];
track[i] = NULL;
}
}
// Number of tracks is 0
tracks = 0;
// No settings for data and audio
dataindex = -1;
audioindex = -1;
}
//---------------------------------------------------------------------------
//
// Track Search
// * Returns -1 if not found
//
//---------------------------------------------------------------------------
int SCSICD::SearchTrack(DWORD lba) const
{
// Track loop
for (int i = 0; i < tracks; i++) {
// Listen to the track
ASSERT(track[i]);
if (track[i]->IsValid(lba)) {
return i;
}
}
// Track wasn't found
return -1;
}