RASCSI/src/raspberrypi/devices/disk.cpp
Uwe Seimet 5622694701
Host services (SCHS) with realtime clock and shutdown, improved device inheritance (#647)
* Initial RTC skeleton

* Added device info

* Added TEST UNIT READY

* Fixed command dispatcher

* First untested naive implementation

* Comment update

* Code cleanup

* More code cleanup

* Updated date/time encoding

* Updated versioning

* Use standard RaSCSI INQUIRY version for SCRT device

* Manpage update

* Added shortcut for SCRT type

* Added support for rtc "filename"

* RTC supports LUNs > 0

* Fixed LUN count

* Renaming

* Renaming

* Manpage update

* Initial naive implementation

* SCRA is removable

* Updated command list

* Added controller field

* Shut down works, bus free phase is not yet entered

* Clear caches on shutdown

* Expose BusFree()

* Moved code

* Logging update

* Moved code

* Moved code

* Added comment

* Logging update

* Code cleanup

* Service device is not removable anymore (was only needed for testing)

* Manpage update

* Added comment

* Comment update

* Version update

* Renaming

* Comment update

* Comment update

* Renaming

* Fixed typo

* Added convenience method

* Property handling optimization

* Code cleanup

* Code cleanup

* Code cleanup, introduced base class

* Added TODO

* More code cleanup

* Removed unnecessary assignments

* Moved code

* Removed forward declaration

* Added base class

* INclude cleanup

* Moved scsi_command enum

* Fixed warnings

* Addressing circular dependencies

* Removed duplicate enum

* include file cleanup

* Include cleanup

* Reduced dependencies to Disk class (replaced by Device), fixed TODO

* Include cleanup

* PrimaryDevice implements ReportLuns

* Inheritance update

* Removed duplicate code

* Moved code to base class

* Cleanup

* Removed duplicate field

* Updated command dispatchign

* Comment update

* Moved code

* Updated method visibilities

* Moved MODE SENSE/MODE SELECT base code
2022-02-10 12:54:48 -06:00

1466 lines
32 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.

//---------------------------------------------------------------------------
//
// X68000 EMULATOR "XM6"
//
// Copyright (C) 2001-2006 (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.
// Imported NetBSD support and some optimisation patch by Rin Okuyama.
// Comments translated to english by akuker.
//
//---------------------------------------------------------------------------
#include "os.h"
#include "device_factory.h"
#include "exceptions.h"
#include "disk.h"
#include "mode_page_device.h"
#include <sstream>
Disk::Disk(const std::string id) : ModePageDevice(id), ScsiBlockCommands()
{
disks.insert(this);
// Work initialization
configured_sector_size = 0;
disk.size = 0;
disk.blocks = 0;
disk.dcache = NULL;
disk.image_offset = 0;
AddCommand(ScsiDefs::eCmdRezero, "Rezero", &Disk::Rezero);
AddCommand(ScsiDefs::eCmdFormat, "FormatUnit", &Disk::FormatUnit);
AddCommand(ScsiDefs::eCmdReassign, "ReassignBlocks", &Disk::ReassignBlocks);
AddCommand(ScsiDefs::eCmdRead6, "Read6", &Disk::Read6);
AddCommand(ScsiDefs::eCmdWrite6, "Write6", &Disk::Write6);
AddCommand(ScsiDefs::eCmdSeek6, "Seek6", &Disk::Seek6);
AddCommand(ScsiDefs::eCmdReserve6, "Reserve6", &Disk::Reserve6);
AddCommand(ScsiDefs::eCmdRelease6, "Release6", &Disk::Release6);
AddCommand(ScsiDefs::eCmdStartStop, "StartStopUnit", &Disk::StartStopUnit);
AddCommand(ScsiDefs::eCmdSendDiag, "SendDiagnostic", &Disk::SendDiagnostic);
AddCommand(ScsiDefs::eCmdRemoval, "PreventAllowMediumRemoval", &Disk::PreventAllowMediumRemoval);
AddCommand(ScsiDefs::eCmdReadCapacity10, "ReadCapacity10", &Disk::ReadCapacity10);
AddCommand(ScsiDefs::eCmdRead10, "Read10", &Disk::Read10);
AddCommand(ScsiDefs::eCmdWrite10, "Write10", &Disk::Write10);
AddCommand(ScsiDefs::eCmdReadLong10, "ReadLong10", &Disk::ReadLong10);
AddCommand(ScsiDefs::eCmdWriteLong10, "WriteLong10", &Disk::WriteLong10);
AddCommand(ScsiDefs::eCmdWriteLong16, "WriteLong16", &Disk::WriteLong16);
AddCommand(ScsiDefs::eCmdSeek10, "Seek10", &Disk::Seek10);
AddCommand(ScsiDefs::eCmdVerify10, "Verify10", &Disk::Verify10);
AddCommand(ScsiDefs::eCmdSynchronizeCache10, "SynchronizeCache10", &Disk::SynchronizeCache10);
AddCommand(ScsiDefs::eCmdSynchronizeCache16, "SynchronizeCache16", &Disk::SynchronizeCache16);
AddCommand(ScsiDefs::eCmdReadDefectData10, "ReadDefectData10", &Disk::ReadDefectData10);
AddCommand(ScsiDefs::eCmdReserve10, "Reserve10", &Disk::Reserve10);
AddCommand(ScsiDefs::eCmdRelease10, "Release10", &Disk::Release10);
AddCommand(ScsiDefs::eCmdRead16, "Read16", &Disk::Read16);
AddCommand(ScsiDefs::eCmdWrite16, "Write16", &Disk::Write16);
AddCommand(ScsiDefs::eCmdVerify16, "Verify16", &Disk::Verify16);
AddCommand(ScsiDefs::eCmdReadCapacity16_ReadLong16, "ReadCapacity16/ReadLong16", &Disk::ReadCapacity16_ReadLong16);
}
Disk::~Disk()
{
// Save disk cache
if (IsReady()) {
// Only if ready...
if (disk.dcache) {
disk.dcache->Save();
}
}
// Clear disk cache
if (disk.dcache) {
delete disk.dcache;
disk.dcache = NULL;
}
for (auto const& command : commands) {
delete command.second;
}
disks.erase(this);
}
void Disk::AddCommand(ScsiDefs::scsi_command opcode, const char* name, void (Disk::*execute)(SASIDEV *))
{
commands[opcode] = new command_t(name, execute);
}
bool Disk::Dispatch(SCSIDEV *controller)
{
ctrl = controller->GetCtrl();
if (commands.count(static_cast<ScsiDefs::scsi_command>(ctrl->cmd[0]))) {
command_t *command = commands[static_cast<ScsiDefs::scsi_command>(ctrl->cmd[0])];
LOGDEBUG("%s Executing %s ($%02X)", __PRETTY_FUNCTION__, command->name, (unsigned int)ctrl->cmd[0]);
(this->*command->execute)(controller);
return true;
}
LOGTRACE("%s Calling base class for dispatching $%02X", __PRETTY_FUNCTION__, (unsigned int)ctrl->cmd[0]);
// The base class handles the less specific commands
return ModePageDevice::Dispatch(controller);
}
//---------------------------------------------------------------------------
//
// Open
// * Call as a post-process after successful opening in a derived class
//
//---------------------------------------------------------------------------
void Disk::Open(const Filepath& path)
{
ASSERT(disk.blocks > 0);
SetReady(true);
// Cache initialization
assert (!disk.dcache);
disk.dcache = new DiskCache(path, disk.size, disk.blocks, disk.image_offset);
// Can read/write open
Fileio fio;
if (fio.Open(path, Fileio::ReadWrite)) {
// Write permission
fio.Close();
} else {
// Permanently write-protected
SetReadOnly(true);
SetProtectable(false);
SetProtected(false);
}
SetStopped(false);
SetRemoved(false);
SetLocked(false);
}
void Disk::Rezero(SASIDEV *controller)
{
if (!CheckReady()) {
controller->Error();
return;
}
controller->Status();
}
void Disk::FormatUnit(SASIDEV *controller)
{
if (!Format(ctrl->cmd)) {
controller->Error();
return;
}
controller->Status();
}
void Disk::ReassignBlocks(SASIDEV *controller)
{
if (!CheckReady()) {
controller->Error();
return;
}
controller->Status();
}
void Disk::Read(SASIDEV *controller, uint64_t record)
{
ctrl->length = Read(ctrl->cmd, ctrl->buffer, record);
LOGTRACE("%s ctrl.length is %d", __PRETTY_FUNCTION__, (int)ctrl->length);
if (ctrl->length <= 0) {
controller->Error();
return;
}
// Set next block
ctrl->next = record + 1;
controller->DataIn();
}
void Disk::Read6(SASIDEV *controller)
{
uint64_t start;
if (GetStartAndCount(controller, start, ctrl->blocks, RW6)) {
LOGDEBUG("%s READ(6) command record=$%08X blocks=%d", __PRETTY_FUNCTION__, (uint32_t)start, ctrl->blocks);
Read(controller, start);
}
}
void Disk::Read10(SASIDEV *controller)
{
uint64_t start;
if (GetStartAndCount(controller, start, ctrl->blocks, RW10)) {
LOGDEBUG("%s READ(10) command record=$%08X blocks=%d", __PRETTY_FUNCTION__, (uint32_t)start, ctrl->blocks);
Read(controller, start);
}
}
void Disk::Read16(SASIDEV *controller)
{
uint64_t start;
if (GetStartAndCount(controller, start, ctrl->blocks, RW16)) {
LOGDEBUG("%s READ(16) command record=$%08X blocks=%d", __PRETTY_FUNCTION__, (uint32_t)start, ctrl->blocks);
Read(controller, start);
}
}
void Disk::ReadWriteLong10(SASIDEV *controller)
{
// Transfer lengths other than 0 are not supported, which is compliant with the SCSI standard
if (ctrl->cmd[7] || ctrl->cmd[8]) {
controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::INVALID_FIELD_IN_CDB);
return;
}
if (CheckBlockAddress(controller, RW10)) {
controller->Status();
}
}
void Disk::ReadLong10(SASIDEV *controller)
{
ReadWriteLong10(controller);
}
void Disk::ReadWriteLong16(SASIDEV *controller)
{
// Transfer lengths other than 0 are not supported, which is compliant with the SCSI standard
if (ctrl->cmd[12] || ctrl->cmd[13]) {
controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::INVALID_FIELD_IN_CDB);
return;
}
if (CheckBlockAddress(controller, RW16)) {
controller->Status();
}
}
void Disk::ReadLong16(SASIDEV *controller)
{
ReadWriteLong16(controller);
}
void Disk::Write(SASIDEV *controller, uint64_t record)
{
ctrl->length = WriteCheck(record);
if (ctrl->length == 0) {
controller->Error(ERROR_CODES::sense_key::NOT_READY, ERROR_CODES::asc::NO_ADDITIONAL_SENSE_INFORMATION);
return;
}
else if (ctrl->length < 0) {
controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::WRITE_PROTECTED);
return;
}
// Set next block
ctrl->next = record + 1;
controller->DataOut();
}
void Disk::Write6(SASIDEV *controller)
{
uint64_t start;
if (GetStartAndCount(controller, start, ctrl->blocks, RW6)) {
LOGDEBUG("%s WRITE(6) command record=$%08X blocks=%d", __PRETTY_FUNCTION__, (uint32_t)start, ctrl->blocks);
Write(controller, start);
}
}
void Disk::Write10(SASIDEV *controller)
{
uint64_t start;
if (GetStartAndCount(controller, start, ctrl->blocks, RW10)) {
LOGDEBUG("%s WRITE(10) command record=$%08X blocks=%d",__PRETTY_FUNCTION__, (uint32_t)start, ctrl->blocks);
Write(controller, start);
}
}
void Disk::Write16(SASIDEV *controller)
{
uint64_t start;
if (GetStartAndCount(controller, start, ctrl->blocks, RW16)) {
LOGDEBUG("%s WRITE(16) command record=$%08X blocks=%d",__PRETTY_FUNCTION__, (uint32_t)start, ctrl->blocks);
Write(controller, start);
}
}
void Disk::WriteLong10(SASIDEV *controller)
{
ReadWriteLong10(controller);
}
void Disk::WriteLong16(SASIDEV *controller)
{
ReadWriteLong16(controller);
}
void Disk::Verify(SASIDEV *controller, uint64_t record)
{
// if BytChk=0
if ((ctrl->cmd[1] & 0x02) == 0) {
Seek(controller);
return;
}
// Test loading
ctrl->length = Read(ctrl->cmd, ctrl->buffer, record);
if (ctrl->length <= 0) {
controller->Error();
return;
}
// Set next block
ctrl->next = record + 1;
controller->DataOut();
}
void Disk::Verify10(SASIDEV *controller)
{
// Get record number and block number
uint64_t record;
if (GetStartAndCount(controller, record, ctrl->blocks, RW10)) {
LOGDEBUG("%s VERIFY(10) command record=$%08X blocks=%d",__PRETTY_FUNCTION__, (uint32_t)record, ctrl->blocks);
Verify(controller, record);
}
}
void Disk::Verify16(SASIDEV *controller)
{
// Get record number and block number
uint64_t record;
if (GetStartAndCount(controller, record, ctrl->blocks, RW16)) {
LOGDEBUG("%s VERIFY(16) command record=$%08X blocks=%d",__PRETTY_FUNCTION__, (uint32_t)record, ctrl->blocks);
Verify(controller, record);
}
}
void Disk::StartStopUnit(SASIDEV *controller)
{
if (!StartStop(ctrl->cmd)) {
controller->Error();
return;
}
controller->Status();
}
void Disk::SendDiagnostic(SASIDEV *controller)
{
if (!SendDiag(ctrl->cmd)) {
controller->Error();
return;
}
controller->Status();
}
void Disk::PreventAllowMediumRemoval(SASIDEV *controller)
{
if (!CheckReady()) {
controller->Error();
return;
}
bool lock = ctrl->cmd[4] & 0x01;
LOGTRACE("%s", lock ? "Locking medium" : "Unlocking medium");
SetLocked(lock);
controller->Status();
}
void Disk::SynchronizeCache10(SASIDEV *controller)
{
// Flush the RaSCSI cache
disk.dcache->Save();
controller->Status();
}
void Disk::SynchronizeCache16(SASIDEV *controller)
{
return SynchronizeCache10(controller);
}
void Disk::ReadDefectData10(SASIDEV *controller)
{
ctrl->length = ReadDefectData10(ctrl->cmd, ctrl->buffer);
if (ctrl->length <= 4) {
controller->Error();
return;
}
controller->DataIn();
}
bool Disk::Eject(bool force)
{
bool status = Device::Eject(force);
if (status) {
// Remove disk cache
disk.dcache->Save();
delete disk.dcache;
disk.dcache = NULL;
// The image file for this drive is not in use anymore
FileSupport *file_support = dynamic_cast<FileSupport *>(this);
if (file_support) {
file_support->UnreserveFile();
}
}
return status;
}
int Disk::ModeSense6(const DWORD *cdb, BYTE *buf)
{
// Get length, clear buffer
int length = (int)cdb[4];
memset(buf, 0, length);
// Get changeable flag
bool change = (cdb[2] & 0xc0) == 0x40;
// Get page code (0x00 is valid from the beginning)
int page = cdb[2] & 0x3f;
bool valid = page == 0x00;
LOGTRACE("%s Requesting mode page $%02X", __PRETTY_FUNCTION__, page);
// Basic information
int size = 4;
// MEDIUM TYPE
if (IsMo()) {
// Optical reversible or erasable
buf[1] = 0x03;
}
// DEVICE SPECIFIC PARAMETER
if (IsProtected()) {
buf[2] = 0x80;
}
// Add block descriptor if DBD is 0
if ((cdb[1] & 0x08) == 0) {
// Mode parameter header, block descriptor length
buf[3] = 0x08;
// Only if ready
if (IsReady()) {
// Short LBA mode parameter block descriptor (number of blocks and block length)
uint64_t disk_blocks = GetBlockCount();
buf[4] = disk_blocks >> 24;
buf[5] = disk_blocks >> 16;
buf[6] = disk_blocks >> 8;
buf[7] = disk_blocks;
// Block descriptor (block length)
uint32_t disk_size = GetSectorSizeInBytes();
buf[9] = disk_size >> 16;
buf[10] = disk_size >> 8;
buf[11] = disk_size;
}
size = 12;
}
// Page code 1 (read-write error recovery)
if (page == 0x01 || page == 0x3f) {
size += AddErrorPage(change, &buf[size]);
valid = true;
}
// Page code 3 (format device)
if (page == 0x03 || page == 0x3f) {
size += AddFormatPage(change, &buf[size]);
valid = true;
}
// Page code 4 (drive parameter)
if (page == 0x04 || page == 0x3f) {
size += AddDrivePage(change, &buf[size]);
valid = true;
}
// Page code 6 (optical)
if (IsMo()) {
if (page == 0x06 || page == 0x3f) {
size += AddOptionPage(change, &buf[size]);
valid = true;
}
}
// Page code 8 (caching)
if (page == 0x08 || page == 0x3f) {
size += AddCachePage(change, &buf[size]);
valid = true;
}
// Page code 13 (CD-ROM)
if (IsCdRom()) {
if (page == 0x0d || page == 0x3f) {
size += AddCDROMPage(change, &buf[size]);
valid = true;
}
}
// Page code 14 (CD-DA)
if (IsCdRom()) {
if (page == 0x0e || page == 0x3f) {
size += AddCDDAPage(change, &buf[size]);
valid = true;
}
}
// Page (vendor special)
int ret = AddVendorPage(page, change, &buf[size]);
if (ret > 0) {
size += ret;
valid = true;
}
if (!valid) {
LOGTRACE("%s Unsupported mode page $%02X", __PRETTY_FUNCTION__, page);
SetStatusCode(STATUS_INVALIDCDB);
return 0;
}
// Do not return more than ALLOCATION LENGTH bytes
if (size > length) {
LOGTRACE("%s %d bytes available, %d bytes requested", __PRETTY_FUNCTION__, size, length);
size = length;
}
// Final setting of mode data length
buf[0] = size;
return size;
}
int Disk::ModeSense10(const DWORD *cdb, BYTE *buf)
{
// Get length, clear buffer
int length = cdb[7];
length <<= 8;
length |= cdb[8];
if (length > 0x800) {
length = 0x800;
}
memset(buf, 0, length);
// Get changeable flag
bool change = (cdb[2] & 0xc0) == 0x40;
// Get page code (0x00 is valid from the beginning)
int page = cdb[2] & 0x3f;
bool valid = page == 0x00;
LOGTRACE("%s Requesting mode page $%02X", __PRETTY_FUNCTION__, page);
// Basic Information
int size = 8;
// MEDIUM TYPE
if (IsMo()) {
// Optical reversible or erasable
buf[2] = 0x03;
}
// DEVICE SPECIFIC PARAMETER
if (IsProtected()) {
buf[3] = 0x80;
}
// Add block descriptor if DBD is 0
if ((cdb[1] & 0x08) == 0) {
// Only if ready
if (IsReady()) {
uint64_t disk_blocks = GetBlockCount();
uint32_t disk_size = GetSectorSizeInBytes();
// Check LLBAA for short or long block descriptor
if ((cdb[1] & 0x10) == 0 || disk_blocks <= 0xFFFFFFFF) {
// Mode parameter header, block descriptor length
buf[7] = 0x08;
// Short LBA mode parameter block descriptor (number of blocks and block length)
buf[8] = disk_blocks >> 24;
buf[9] = disk_blocks >> 16;
buf[10] = disk_blocks >> 8;
buf[11] = disk_blocks;
buf[13] = disk_size >> 16;
buf[14] = disk_size >> 8;
buf[15] = disk_size;
size = 16;
}
else {
// Mode parameter header, LONGLBA
buf[4] = 0x01;
// Mode parameter header, block descriptor length
buf[7] = 0x10;
// Long LBA mode parameter block descriptor (number of blocks and block length)
buf[8] = disk_blocks >> 56;
buf[9] = disk_blocks >> 48;
buf[10] = disk_blocks >> 40;
buf[11] = disk_blocks >> 32;
buf[12] = disk_blocks >> 24;
buf[13] = disk_blocks >> 16;
buf[14] = disk_blocks >> 8;
buf[15] = disk_blocks;
buf[20] = disk_size >> 24;
buf[21] = disk_size >> 16;
buf[22] = disk_size >> 8;
buf[23] = disk_size;
size = 24;
}
}
}
// Page code 1 (read-write error recovery)
if (page == 0x01 || page == 0x3f) {
size += AddErrorPage(change, &buf[size]);
valid = true;
}
// Page code 3 (format device)
if (page == 0x03 || page == 0x3f) {
size += AddFormatPage(change, &buf[size]);
valid = true;
}
// Page code 4 (drive parameter)
if (page == 0x04 || page == 0x3f) {
size += AddDrivePage(change, &buf[size]);
valid = true;
}
// Page code 6 (optical)
if (IsMo()) {
if (page == 0x06 || page == 0x3f) {
size += AddOptionPage(change, &buf[size]);
valid = true;
}
}
// Page code 8 (caching)
if (page == 0x08 || page == 0x3f) {
size += AddCachePage(change, &buf[size]);
valid = true;
}
// Page code 13 (CD-ROM)
if (IsCdRom()) {
if (page == 0x0d || page == 0x3f) {
size += AddCDROMPage(change, &buf[size]);
valid = true;
}
}
// Page code 14 (CD-DA)
if (IsCdRom()) {
if (page == 0x0e || page == 0x3f) {
size += AddCDDAPage(change, &buf[size]);
valid = true;
}
}
// Page (vendor special)
int ret = AddVendorPage(page, change, &buf[size]);
if (ret > 0) {
size += ret;
valid = true;
}
if (!valid) {
LOGTRACE("%s Unsupported mode page $%02X", __PRETTY_FUNCTION__, page);
SetStatusCode(STATUS_INVALIDCDB);
return 0;
}
// Do not return more than ALLOCATION LENGTH bytes
if (size > length) {
LOGTRACE("%s %d bytes available, %d bytes requested", __PRETTY_FUNCTION__, size, length);
size = length;
}
// Final setting of mode data length
buf[0] = size >> 8;
buf[1] = size;
return size;
}
int Disk::AddErrorPage(bool change, BYTE *buf)
{
// Set the message length
buf[0] = 0x01;
buf[1] = 0x0a;
// Retry count is 0, limit time uses internal default value
return 12;
}
int Disk::AddFormatPage(bool change, BYTE *buf)
{
// Set the message length
buf[0] = 0x80 | 0x03;
buf[1] = 0x16;
// Show the number of bytes in the physical sector as changeable
// (though it cannot be changed in practice)
if (change) {
buf[0xc] = 0xff;
buf[0xd] = 0xff;
return 24;
}
if (IsReady()) {
// Set the number of tracks in one zone to 8 (TODO)
buf[0x3] = 0x08;
// Set sector/track to 25 (TODO)
buf[0xa] = 0x00;
buf[0xb] = 0x19;
// 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 attribute
if (IsRemovable()) {
buf[20] = 0x20;
}
return 24;
}
int Disk::AddDrivePage(bool change, BYTE *buf)
{
// Set the message length
buf[0] = 0x04;
buf[1] = 0x16;
// No changeable area
if (change) {
return 24;
}
if (IsReady()) {
// Set the number of cylinders (total number of blocks
// divided by 25 sectors/track and 8 heads)
uint32_t cylinder = disk.blocks;
cylinder >>= 3;
cylinder /= 25;
buf[0x2] = (BYTE)(cylinder >> 16);
buf[0x3] = (BYTE)(cylinder >> 8);
buf[0x4] = (BYTE)cylinder;
// Fix the head at 8
buf[0x5] = 0x8;
}
return 24;
}
int Disk::AddOptionPage(bool change, BYTE *buf)
{
// Set the message length
buf[0] = 0x06;
buf[1] = 0x02;
// Do not report update blocks
return 4;
}
int Disk::AddCachePage(bool change, BYTE *buf)
{
// Set the message length
buf[0] = 0x08;
buf[1] = 0x0a;
// Only read cache is valid, no prefetch
return 12;
}
int Disk::AddCDROMPage(bool change, BYTE *buf)
{
// Set the message length
buf[0] = 0x0d;
buf[1] = 0x06;
// No changeable area
if (change) {
return 8;
}
// 2 seconds for inactive timer
buf[3] = 0x05;
// MSF multiples are 60 and 75 respectively
buf[5] = 60;
buf[7] = 75;
return 8;
}
int Disk::AddCDDAPage(bool change, BYTE *buf)
{
// Set the message length
buf[0] = 0x0e;
buf[1] = 0x0e;
// Audio waits for operation completion and allows
// PLAY across multiple tracks
return 16;
}
int Disk::AddVendorPage(int /*page*/, bool /*change*/, BYTE *buf)
{
ASSERT(buf);
return 0;
}
int Disk::ReadDefectData10(const DWORD *cdb, BYTE *buf)
{
ASSERT(cdb);
ASSERT(buf);
// Get length, clear buffer
DWORD length = cdb[7];
length <<= 8;
length |= cdb[8];
if (length > 0x800) {
length = 0x800;
}
ASSERT((length >= 0) && (length < 0x800));
memset(buf, 0, length);
// P/G/FORMAT
buf[1] = (cdb[1] & 0x18) | 5;
buf[3] = 8;
buf[4] = 0xff;
buf[5] = 0xff;
buf[6] = 0xff;
buf[7] = 0xff;
buf[8] = 0xff;
buf[9] = 0xff;
buf[10] = 0xff;
buf[11] = 0xff;
// no list
SetStatusCode(STATUS_NODEFECT);
return 4;
}
//---------------------------------------------------------------------------
//
// FORMAT UNIT
// *Opcode $06 for SASI, Opcode $04 for SCSI
//
//---------------------------------------------------------------------------
bool Disk::Format(const DWORD *cdb)
{
if (!CheckReady()) {
return false;
}
// FMTDATA=1 is not supported (but OK if there is no DEFECT LIST)
if ((cdb[1] & 0x10) != 0 && cdb[4] != 0) {
SetStatusCode(STATUS_INVALIDCDB);
return false;
}
// FORMAT Success
return true;
}
//---------------------------------------------------------------------------
//
// READ
//
//---------------------------------------------------------------------------
// TODO Read more than one block in a single call. Currently blocked by the SASI code (missing early range check)
// and the track-oriented cache.
int Disk::Read(const DWORD *cdb, BYTE *buf, uint64_t block)
{
ASSERT(buf);
LOGTRACE("%s", __PRETTY_FUNCTION__);
if (!CheckReady()) {
return 0;
}
// Error if the total number of blocks is exceeded
if (block >= disk.blocks) {
SetStatusCode(STATUS_INVALIDLBA);
return 0;
}
// leave it to the cache
if (!disk.dcache->ReadSector(buf, block)) {
SetStatusCode(STATUS_READFAULT);
return 0;
}
// Success
return (1 << disk.size);
}
//---------------------------------------------------------------------------
//
// WRITE check
//
//---------------------------------------------------------------------------
int Disk::WriteCheck(DWORD block)
{
// Status check
if (!CheckReady()) {
LOGDEBUG("WriteCheck failed (not ready)");
return 0;
}
// Error if the total number of blocks is exceeded
if (block >= disk.blocks) {
LOGDEBUG("WriteCheck failed (capacity exceeded)");
return 0;
}
// Error if write protected
if (IsProtected()) {
LOGDEBUG("WriteCheck failed (protected)");
return -1;
}
// Success
return (1 << disk.size);
}
//---------------------------------------------------------------------------
//
// WRITE
//
//---------------------------------------------------------------------------
// TODO Write more than one block in a single call. Currently blocked by the SASI code (missing early range check)
// and the track-oriented cache.
bool Disk::Write(const DWORD *cdb, const BYTE *buf, DWORD block)
{
ASSERT(buf);
LOGTRACE("%s", __PRETTY_FUNCTION__);
// Error if not ready
if (!IsReady()) {
SetStatusCode(STATUS_NOTREADY);
return false;
}
// Error if the total number of blocks is exceeded
if (block >= disk.blocks) {
SetStatusCode(STATUS_INVALIDLBA);
return false;
}
// Error if write protected
if (IsProtected()) {
SetStatusCode(STATUS_WRITEPROTECT);
return false;
}
// Leave it to the cache
if (!disk.dcache->WriteSector(buf, block)) {
SetStatusCode(STATUS_WRITEFAULT);
return false;
}
return true;
}
void Disk::Seek(SASIDEV *controller)
{
if (!CheckReady()) {
controller->Error();
return;
}
controller->Status();
}
//---------------------------------------------------------------------------
//
// SEEK(6)
// Does not check LBA (SASI IOCS)
//
//---------------------------------------------------------------------------
void Disk::Seek6(SASIDEV *controller)
{
Seek(controller);
}
void Disk::Seek10(SASIDEV *controller)
{
Seek(controller);
}
bool Disk::StartStop(const DWORD *cdb)
{
bool start = cdb[4] & 0x01;
bool load = cdb[4] & 0x02;
if (load) {
LOGTRACE("%s", start ? "Loading medium" : "Ejecting medium");
}
else {
LOGTRACE("%s", start ? "Starting unit" : "Stopping unit");
SetStopped(!start);
}
if (!start) {
// Flush the cache when stopping
disk.dcache->Save();
// Look at the eject bit and eject if necessary
if (load) {
if (IsLocked()) {
// Cannot be ejected because it is locked
SetStatusCode(STATUS_PREVENT);
return false;
}
// Eject
return Eject(false);
}
}
return true;
}
bool Disk::SendDiag(const DWORD *cdb)
{
// Do not support PF bit
if (cdb[1] & 0x10) {
SetStatusCode(STATUS_INVALIDCDB);
return false;
}
// Do not support parameter list
if ((cdb[3] != 0) || (cdb[4] != 0)) {
SetStatusCode(STATUS_INVALIDCDB);
return false;
}
return true;
}
void Disk::ReadCapacity10(SASIDEV *controller)
{
BYTE *buf = ctrl->buffer;
memset(buf, 0, 8);
if (!CheckReady()) {
controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::MEDIUM_NOT_PRESENT);
return;
}
if (disk.blocks <= 0) {
controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::MEDIUM_NOT_PRESENT);
LOGWARN("%s Capacity not available, medium may not be present", __PRETTY_FUNCTION__);
return;
}
// Create end of logical block address (disk.blocks-1)
uint32_t blocks = disk.blocks - 1;
buf[0] = (BYTE)(blocks >> 24);
buf[1] = (BYTE)(blocks >> 16);
buf[2] = (BYTE)(blocks >> 8);
buf[3] = (BYTE)blocks;
// Create block length (1 << disk.size)
uint32_t length = 1 << disk.size;
buf[4] = (BYTE)(length >> 24);
buf[5] = (BYTE)(length >> 16);
buf[6] = (BYTE)(length >> 8);
buf[7] = (BYTE)length;
// the size
ctrl->length = 8;
controller->DataIn();
}
void Disk::ReadCapacity16(SASIDEV *controller)
{
BYTE *buf = ctrl->buffer;
memset(buf, 0, 14);
if (!CheckReady() || disk.blocks <= 0) {
controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::MEDIUM_NOT_PRESENT);
return;
}
// Create end of logical block address (disk.blocks-1)
uint64_t blocks = disk.blocks - 1;
buf[0] = (BYTE)(blocks >> 56);
buf[1] = (BYTE)(blocks >> 48);
buf[2] = (BYTE)(blocks >> 40);
buf[3] = (BYTE)(blocks >> 32);
buf[4] = (BYTE)(blocks >> 24);
buf[5] = (BYTE)(blocks >> 16);
buf[6] = (BYTE)(blocks >> 8);
buf[7] = (BYTE)blocks;
// Create block length (1 << disk.size)
uint32_t length = 1 << disk.size;
buf[8] = (BYTE)(length >> 24);
buf[9] = (BYTE)(length >> 16);
buf[10] = (BYTE)(length >> 8);
buf[11] = (BYTE)length;
// Logical blocks per physical block: not reported (1 or more)
buf[13] = 0;
// the size
ctrl->length = 14;
controller->DataIn();
}
void Disk::ReadCapacity16_ReadLong16(SASIDEV *controller)
{
// The service action determines the actual command
switch (ctrl->cmd[1] & 0x1f) {
case 0x10:
ReadCapacity16(controller);
break;
case 0x11:
ReadLong16(controller);
break;
default:
controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::INVALID_FIELD_IN_CDB);
break;
}
}
//---------------------------------------------------------------------------
//
// RESERVE(6)
//
// The reserve/release commands are only used in multi-initiator
// environments. RaSCSI doesn't support this use case. However, some old
// versions of Solaris will issue the reserve/release commands. We will
// just respond with an OK status.
//
//---------------------------------------------------------------------------
void Disk::Reserve6(SASIDEV *controller)
{
controller->Status();
}
//---------------------------------------------------------------------------
//
// RESERVE(10)
//
// The reserve/release commands are only used in multi-initiator
// environments. RaSCSI doesn't support this use case. However, some old
// versions of Solaris will issue the reserve/release commands. We will
// just respond with an OK status.
//
//---------------------------------------------------------------------------
void Disk::Reserve10(SASIDEV *controller)
{
controller->Status();
}
//---------------------------------------------------------------------------
//
// RELEASE(6)
//
// The reserve/release commands are only used in multi-initiator
// environments. RaSCSI doesn't support this use case. However, some old
// versions of Solaris will issue the reserve/release commands. We will
// just respond with an OK status.
//
//---------------------------------------------------------------------------
void Disk::Release6(SASIDEV *controller)
{
controller->Status();
}
//---------------------------------------------------------------------------
//
// RELEASE(10)
//
// The reserve/release commands are only used in multi-initiator
// environments. RaSCSI doesn't support this use case. However, some old
// versions of Solaris will issue the reserve/release commands. We will
// just respond with an OK status.
//
//---------------------------------------------------------------------------
void Disk::Release10(SASIDEV *controller)
{
controller->Status();
}
//---------------------------------------------------------------------------
//
// Check/Get start sector and sector count for a READ/WRITE or READ/WRITE LONG operation
//
//---------------------------------------------------------------------------
bool Disk::CheckBlockAddress(SASIDEV *controller, access_mode mode)
{
uint64_t block = ctrl->cmd[2];
block <<= 8;
block |= ctrl->cmd[3];
block <<= 8;
block |= ctrl->cmd[4];
block <<= 8;
block |= ctrl->cmd[5];
if (mode == RW16) {
block <<= 8;
block |= ctrl->cmd[6];
block <<= 8;
block |= ctrl->cmd[7];
block <<= 8;
block |= ctrl->cmd[8];
block <<= 8;
block |= ctrl->cmd[9];
}
uint64_t capacity = GetBlockCount();
if (block > capacity) {
ostringstream s;
s << "Capacity of " << capacity << " blocks exceeded: " << "Trying to access block " << block;
LOGTRACE("%s", s.str().c_str());
controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::LBA_OUT_OF_RANGE);
return false;
}
return true;
}
bool Disk::GetStartAndCount(SASIDEV *controller, uint64_t& start, uint32_t& count, access_mode mode)
{
if (mode == RW6) {
start = ctrl->cmd[1] & 0x1f;
start <<= 8;
start |= ctrl->cmd[2];
start <<= 8;
start |= ctrl->cmd[3];
count = ctrl->cmd[4];
if (!count) {
count= 0x100;
}
}
else {
start = ctrl->cmd[2];
start <<= 8;
start |= ctrl->cmd[3];
start <<= 8;
start |= ctrl->cmd[4];
start <<= 8;
start |= ctrl->cmd[5];
if (mode == RW16) {
start <<= 8;
start |= ctrl->cmd[6];
start <<= 8;
start |= ctrl->cmd[7];
start <<= 8;
start |= ctrl->cmd[8];
start <<= 8;
start |= ctrl->cmd[9];
}
if (mode == RW16) {
count = ctrl->cmd[10];
count <<= 8;
count |= ctrl->cmd[11];
count <<= 8;
count |= ctrl->cmd[12];
count <<= 8;
count |= ctrl->cmd[13];
}
else {
count = ctrl->cmd[7];
count <<= 8;
count |= ctrl->cmd[8];
}
}
// Check capacity
uint64_t capacity = GetBlockCount();
if (start > capacity || start + count > capacity) {
ostringstream s;
s << "Capacity of " << capacity << " blocks exceeded: "
<< "Trying to read block " << start << ", block count " << ctrl->blocks;
LOGTRACE("%s", s.str().c_str());
controller->Error(ERROR_CODES::sense_key::ILLEGAL_REQUEST, ERROR_CODES::asc::LBA_OUT_OF_RANGE);
return false;
}
// Do not process 0 blocks
if (!count) {
LOGTRACE("NOT processing 0 blocks");
controller->Status();
return false;
}
return true;
}
uint32_t Disk::GetSectorSizeInBytes() const
{
return disk.size ? 1 << disk.size : 0;
}
void Disk::SetSectorSizeInBytes(uint32_t size, bool sasi)
{
set<uint32_t> sector_sizes = DeviceFactory::instance().GetSectorSizes(GetType());
if (!sector_sizes.empty() && sector_sizes.find(size) == sector_sizes.end()) {
stringstream error;
error << "Invalid block size of " << size << " bytes";
throw io_exception(error.str());
}
switch (size) {
case 256:
disk.size = 8;
break;
case 512:
disk.size = 9;
break;
case 1024:
disk.size = 10;
break;
case 2048:
disk.size = 11;
break;
case 4096:
disk.size = 12;
break;
default:
assert(false);
break;
}
}
uint32_t Disk::GetSectorSizeShiftCount() const
{
return disk.size;
}
void Disk::SetSectorSizeShiftCount(uint32_t size)
{
disk.size = size;
}
bool Disk::IsSectorSizeConfigurable() const
{
return !sector_sizes.empty();
}
void Disk::SetSectorSizes(const set<uint32_t>& sector_sizes)
{
this->sector_sizes = sector_sizes;
}
uint32_t Disk::GetConfiguredSectorSize() const
{
return configured_sector_size;
}
bool Disk::SetConfiguredSectorSize(uint32_t configured_sector_size)
{
DeviceFactory& device_factory = DeviceFactory::instance();
set<uint32_t> sector_sizes = device_factory.GetSectorSizes(GetType());
if (sector_sizes.find(configured_sector_size) == sector_sizes.end()) {
return false;
}
this->configured_sector_size = configured_sector_size;
return true;
}
void Disk::SetGeometries(const map<uint64_t, Geometry>& geometries)
{
this->geometries = geometries;
}
bool Disk::SetGeometryForCapacity(uint64_t capacity) {
const auto& geometry = geometries.find(capacity);
if (geometry != geometries.end()) {
SetSectorSizeInBytes(geometry->second.first, false);
SetBlockCount(geometry->second.second);
return true;
}
return false;
}
uint64_t Disk::GetBlockCount() const
{
return disk.blocks;
}
void Disk::SetBlockCount(uint32_t blocks)
{
disk.blocks = blocks;
}