Basic DVD image reader

Implemented SCSI READ(10) command
This commit is contained in:
StrikerX3 2018-12-09 11:37:53 -02:00
parent 52b2549ac5
commit d94bdf2225
10 changed files with 349 additions and 39 deletions

View file

@ -207,6 +207,8 @@ void ATAChannel::WriteCommand(uint8_t value) {
return;
}
//log_spew("ATAChannel::WriteCommand: Processing command 0x%x for channel %d, device %d\n", cmd, m_channel, devIndex);
// Instantiate the command
auto factory = kCmdFactories.at(cmd);
m_currentCommand = factory(*dev);

View file

@ -11,6 +11,9 @@
// [m] SCSI Multimedia Commands - 3 (MMC-3) Revision 10g
// https://www.rockbox.org/wiki/pub/Main/DataSheets/mmc2r11a.pdf
//
// [b] SCSI Block Commands - 3 (SBC-3) Revision 25
// http://www.13thmonkey.org/documentation/SCSI/sbc3r25.pdf
//
// [a] SCSI Architecture Model - 3 (SAM-3) Revision 13
// http://www.csit-sun.pub.ro/~cpop/Documentatie_SMP/Standarde_magistrale/SCSI/sam3r13.pdf
//
@ -31,6 +34,12 @@ namespace vixen {
namespace hw {
namespace atapi {
// --- Sizes and capacities ---------------------------------------------------
const uint16_t kDVDSectorSize = 2048;
const uint32_t kMaxSectorsDVDSingleLayer = 2298496; // 4.7 GiB
const uint32_t kMaxSectorsDVDDualLayer = 4171712; // 8.5 GiB
// --- Command Descriptor Block (CDB) [p 4.3] ---------------------------------
// [p 4.3.4.1] Operation Code
@ -109,25 +118,42 @@ union CommandDescriptorBlock {
// [p 6.10 table 99] [s 3.12 table 75]
// CDB for the MODE SENSE (10) command
struct ModeSense10 {
OperationCode opCode; // byte 0 Operation Code (0x5A)
uint8_t _reserved1 : 3; // byte 1 [2:0] Reserved
uint8_t disableBlockDescriptors : 1; // byte 1 [3] (DBD) Disable Block Descriptors
uint8_t longLBAAccepted : 1; // byte 1 [4] (LLBAA) Long LBA Accepted
uint8_t _reserved2 : 3; // byte 1 [7:5] Reserved
uint8_t pageCode : 6; // byte 2 [5:0] Page code
uint8_t pageControl : 2; // byte 2 [7:6] (PC) Page control
uint8_t subpageCode; // byte 3 Subpage code
uint8_t _reserved3[3]; // byte 4-6 Reserved
uint8_t length[2]; // byte 7-8 Allocation Length
uint8_t control; // byte 9 Control
OperationCode opCode; // byte 0 Operation Code (0x5A)
uint8_t _reserved1 : 3; // byte 1 [2:0] Reserved
uint8_t disableBlockDescriptors : 1; // byte 1 [3] (DBD) Disable Block Descriptors
uint8_t longLBAAccepted : 1; // byte 1 [4] (LLBAA) Long LBA Accepted
uint8_t _reserved2 : 3; // byte 1 [7:5] Reserved
uint8_t pageCode : 6; // byte 2 [5:0] Page code
uint8_t pageControl : 2; // byte 2 [7:6] (PC) Page control
uint8_t subpageCode; // byte 3 Subpage code
uint8_t _reserved3[3]; // byte 4-6 Reserved
uint8_t length[2]; // byte 7-8 Allocation Length
uint8_t control; // byte 9 Control
} modeSense10;
// [b 5.11 table 56]
// CDB for the READ (10) command
struct Read10 {
OperationCode opCode; // byte 0 Operation Code (0x28)
uint8_t _obsolete1 : 1; // byte 1 [0] Obsolete
uint8_t forceUnitAccessNVCache : 1; // byte 1 [1] (FUA_NV) Force unit access non-volatile cache (read from/write to block cache before medium)
uint8_t _reserved1 : 1; // byte 1 [2] Reserved
uint8_t forceUnitAccess : 1; // byte 1 [3] (FUA) Force unit access (force access to media instead of cache)
uint8_t disablePageOut : 1; // byte 1 [4] (DPO) Disable page out (do not cache block)
uint8_t readProtect : 3; // byte 1 [7:5] (RDPROTECT) Read protect
uint8_t lba[4]; // byte 2-5 Logical Block Address
uint8_t groupNumber : 5; // byte 6 [4:0] Group number
uint8_t _reserved2 : 3; // byte 6 [7:5] Reserved
uint8_t length[2]; // byte 7-8 Transfer length
uint8_t control; // byte 9 Control
} read10;
// [m 5.16 table 144]
// CDB for the READ CAPACITY command
struct ReadCapacity {
OperationCode opCode; // byte 0 Operation Code (0x25)
uint8_t _reservedOrObsolete[8]; // byte 1-8 Reserved or obsolete fields
uint8_t control; // byte 9 Control
OperationCode opCode; // byte 0 Operation Code (0x25)
uint8_t _reservedOrObsolete[8]; // byte 1-8 Reserved or obsolete fields
uint8_t control; // byte 9 Control
} readCapacity;
// [p 6.27 table 169] [s 3.37 table 164]
@ -162,6 +188,7 @@ enum PageControl : uint8_t {
// Operation Codes
enum Operations : uint8_t {
OpModeSense10 = 0x5A, // (0x5A) MODE SENSE (10 bytes)
OpRead10 = 0x28, // (0x28) READ (10 bytes)
OpReadCapacity = 0x25, // (0x25) READ CAPACITY
OpRequestSense = 0x03, // (0x03) REQUEST SENSE
OpTestUnitReady = 0x00, // (0x00) TEST UNIT READY
@ -209,6 +236,7 @@ enum PacketOperationType {
// Maps operation codes to their types
const std::unordered_map<uint8_t, PacketOperationType, std::hash<uint8_t>> kOperationTypes = {
{ OpModeSense10, PktOpDataIn },
{ OpRead10, PktOpDataIn },
{ OpReadCapacity, PktOpDataIn },
{ OpRequestSense, PktOpDataIn },
{ OpTestUnitReady, PktOpNonData },

View file

@ -173,7 +173,7 @@ void PacketProtocolCommand::WriteData(uint8_t *value, uint32_t size) {
}
void PacketProtocolCommand::ProcessPacket() {
log_debug("PacketProtocolCommand::ProcessPacket: Processing packet\n");
//log_spew("PacketProtocolCommand::ProcessPacket: Processing packet\n");
m_regs.status |= StBusy;
m_regs.status &= ~StDataRequest;

View file

@ -28,6 +28,18 @@ BaseDVDDriveATADeviceDriver::BaseDVDDriveATADeviceDriver() {
BaseDVDDriveATADeviceDriver::~BaseDVDDriveATADeviceDriver() {
}
bool BaseDVDDriveATADeviceDriver::Read(uint64_t byteAddress, uint8_t * buffer, uint32_t size) {
// Always fail; this function should never be called on devices that support the PACKET Command feature set
log_warning("BaseDVDDriveATADeviceDriver::Read: Unexpected ATA read\n");
return false;
}
bool BaseDVDDriveATADeviceDriver::Write(uint64_t byteAddress, uint8_t * buffer, uint32_t size) {
// Always fail; this function should never be called on devices that support the PACKET Command feature set
log_warning("BaseDVDDriveATADeviceDriver::Write: Unexpected ATA write\n");
return false;
}
void BaseDVDDriveATADeviceDriver::IdentifyDevice(IdentifyDeviceData *data) {
// Should never be invoked since this device supports the PACKET Command feature set
// Fill with zeros just in case

View file

@ -42,8 +42,8 @@ public:
// ----- Data access ------------------------------------------------------
virtual bool Read(uint64_t byteAddress, uint8_t *buffer, uint32_t size) override = 0;
virtual bool Write(uint64_t byteAddress, uint8_t *buffer, uint32_t size) override = 0;
bool Read(uint64_t byteAddress, uint8_t *buffer, uint32_t size) override;
bool Write(uint64_t byteAddress, uint8_t *buffer, uint32_t size) override;
// ----- ATAPI ------------------------------------------------------------

View file

@ -32,19 +32,6 @@ DummyDVDDriveATADeviceDriver::DummyDVDDriveATADeviceDriver() {
DummyDVDDriveATADeviceDriver::~DummyDVDDriveATADeviceDriver() {
}
bool DummyDVDDriveATADeviceDriver::Read(uint64_t byteAddress, uint8_t *buffer, uint32_t size) {
// Fill with zeros, as if the disk was blank
memset(buffer, 0, size);
// Always succeed
return true;
}
bool DummyDVDDriveATADeviceDriver::Write(uint64_t byteAddress, uint8_t *buffer, uint32_t size) {
// Lie about writing, always fail
return false;
}
bool DummyDVDDriveATADeviceDriver::ValidateATAPIPacket(PacketInformation& packetInfo) {
log_debug("DummyDVDDriveATADeviceDriver::ValidateATAPIPacket: Operation code 0x%x\n", packetInfo.cdb.opCode.u8, packetInfo.cdb.opCode.fields.commandCode, packetInfo.cdb.opCode.fields.groupCode);
@ -74,7 +61,7 @@ bool DummyDVDDriveATADeviceDriver::ProcessATAPIPacketDataRead(PacketInformation&
switch (packetInfo.cdb.modeSense10.pageCode) {
case kPageCodeAuthentication:
{
// TODO: handle partial reads
// TODO: handle partial reads (if those ever happen here)
if (byteCountLimit < sizeof(XboxDVDAuthentication)) {
packetInfo.result.aborted = true;
packetInfo.result.deviceFault = true;
@ -96,6 +83,15 @@ bool DummyDVDDriveATADeviceDriver::ProcessATAPIPacketDataRead(PacketInformation&
log_debug("DummyDVDDriveATADeviceDriver::ProcessATAPIPacketDataRead: Unimplemented page code 0x%x for MODE SENSE(10)\n", packetInfo.cdb.modeSense10.pageCode);
return false;
}
case OpRead10:
{
// Say that there is no disc in the drive
packetInfo.result.status = StCheckCondition;
packetInfo.result.senseKey = SKNotReady;
packetInfo.result.additionalSenseCode = ASCMediumNotPresent;
return true;
}
case OpReadCapacity:
{
// Say that there is no disc in the drive

View file

@ -27,11 +27,6 @@ public:
DummyDVDDriveATADeviceDriver();
~DummyDVDDriveATADeviceDriver() override;
// ----- Data access ------------------------------------------------------
bool Read(uint64_t byteAddress, uint8_t *buffer, uint32_t size) override;
bool Write(uint64_t byteAddress, uint8_t *buffer, uint32_t size) override;
// ----- ATAPI ------------------------------------------------------------
bool ValidateATAPIPacket(atapi::PacketInformation& packetInfo) override;

View file

@ -0,0 +1,208 @@
// ATA/ATAPI-4 emulation for the Original Xbox
// (C) Ivan "StrikerX3" Oliveira
//
// This code aims to implement a subset of the ATA/ATAPI-4 specification
// that satisifies the requirements of an IDE interface for the Original Xbox.
//
// Specification:
// http://www.t13.org/documents/UploadedDocuments/project/d1153r18-ATA-ATAPI-4.pdf
//
// References to particular items in the specification are denoted between brackets
// optionally followed by a quote from the specification.
#include "drv_vdvd_image.h"
#include "vixen/log.h"
#include "vixen/io.h"
#include "vixen/hw/ata/atapi_defs.h"
#include "vixen/hw/ata/atapi_xbox.h"
#include "vixen/hw/ata/atapi_utils.h"
namespace vixen {
namespace hw {
namespace ata {
using namespace atapi;
ImageDVDDriveATADeviceDriver::ImageDVDDriveATADeviceDriver() {
strcpy(m_serialNumber, "9876543210");
strcpy(m_firmwareRevision, "1.00");
strcpy(m_modelNumber, "IMAGE DVD 12345");
}
ImageDVDDriveATADeviceDriver::~ImageDVDDriveATADeviceDriver() {
}
bool ImageDVDDriveATADeviceDriver::LoadImageFile(const char *imagePath, bool copyOnWrite) {
// TODO: Refactor image management into a class hierarchy:
// IDiskImageProvider <<interface>>
// XISODiskImageProvider
// ...
// TODO: Use memory-mapped I/O
// Try to load the image file
m_fpImage = NULL;
auto err = fopen_s(&m_fpImage, imagePath, "rb");
if (err) {
log_fatal("ImageDVDDriveATADeviceDriver::LoadImage: Could not open image \"%s\": error code 0x%x\n", imagePath, err);
return false;
}
// Determine image file size
fseek(m_fpImage, 0, SEEK_END);
uint64_t imageSize = _ftelli64(m_fpImage);
uint64_t imageSizeInSectors = imageSize / kDVDSectorSize;
log_info("ImageDVDDriveATADeviceDriver::LoadImage: Loaded image \"%s\": %llu bytes -> %llu sectors\n", imagePath, imageSize, imageSizeInSectors);
if (imageSizeInSectors > kMaxSectorsDVDDualLayer) {
log_warning("ImageDVDDriveATADeviceDriver::LoadImage: Image is too big; limiting to the first %u sectors\n", kMaxSectorsDVDDualLayer);
imageSizeInSectors = kMaxSectorsDVDDualLayer;
}
m_sectorCapacity = imageSizeInSectors;
return true;
}
bool ImageDVDDriveATADeviceDriver::EjectMedia() {
if (m_fpImage == NULL) {
log_warning("ImageDVDDriveATADeviceDriver::EjectMedia: No media to eject\n");
return false;
}
log_info("ImageDVDDriveATADeviceDriver::EjectMedia: Media ejected\n");
fclose(m_fpImage);
m_fpImage = NULL;
// TODO: notify media removal
return true;
}
bool ImageDVDDriveATADeviceDriver::ValidateATAPIPacket(PacketInformation& packetInfo) {
log_debug("ImageDVDDriveATADeviceDriver::ValidateATAPIPacket: Operation code 0x%x\n", packetInfo.cdb.opCode.u8, packetInfo.cdb.opCode.fields.commandCode, packetInfo.cdb.opCode.fields.groupCode);
// TODO: device-specific validation
// Check if the command is supported and has valid parameters.
return ValidateCommand(packetInfo);
}
bool ImageDVDDriveATADeviceDriver::ProcessATAPIPacketNonData(PacketInformation& packetInfo) {
switch (packetInfo.cdb.opCode.u8) {
case OpTestUnitReady:
// If there is no disc in the drive, return the expected sense key and parameters.
// The default values tell that there is media in the drive and the device is ready to accept commands.
if (!HasMedia()) {
packetInfo.result.status = StCheckCondition;
packetInfo.result.senseKey = SKNotReady;
packetInfo.result.additionalSenseCode = ASCMediumNotPresent;
}
return true;
default:
log_debug("ImageDVDDriveATADeviceDriver::ProcessATAPIPacketNonData: Unimplemented operation code 0x%x\n", packetInfo.cdb.opCode.u8);
return false;
}
}
bool ImageDVDDriveATADeviceDriver::ProcessATAPIPacketDataRead(PacketInformation& packetInfo, uint8_t *packetDataBuffer, uint16_t byteCountLimit, uint32_t *packetDataSize) {
switch (packetInfo.cdb.opCode.u8) {
case OpModeSense10:
switch (packetInfo.cdb.modeSense10.pageCode) {
case kPageCodeAuthentication:
{
// TODO: handle partial reads (if those ever happen here)
if (byteCountLimit < sizeof(XboxDVDAuthentication)) {
packetInfo.result.aborted = true;
packetInfo.result.deviceFault = true;
return false;
}
// Fill in just enough information to pass basic authentication checks on modified kernels
// TODO: Research Xbox DVD authentication
// https://multimedia.cx/eggs/xbox-sphinx-protocol/
XboxDVDAuthentication *dvdAuth = reinterpret_cast<XboxDVDAuthentication *>(packetDataBuffer);
dvdAuth->CDFValid = 1;
dvdAuth->PartitionArea = 1;
dvdAuth->Authentication = 1;
*packetDataSize = sizeof(XboxDVDAuthentication);
return true;
}
default:
log_debug("ImageDVDDriveATADeviceDriver::ProcessATAPIPacketDataRead: Unimplemented page code 0x%x for MODE SENSE(10)\n", packetInfo.cdb.modeSense10.pageCode);
return false;
}
case OpRead10:
{
if (HasMedia()) {
uint32_t lba = B2L32(packetInfo.cdb.read10.lba);
uint16_t transferLength = B2L16(packetInfo.cdb.read10.length);
packetInfo.transferSize = transferLength * kDVDSectorSize;
// If this is the first read, fill in transfer data
if (!m_transfer) {
m_currentByte = lba * kDVDSectorSize;
m_lastByte = m_currentByte + transferLength * kDVDSectorSize;
}
// TODO: maybe handle caching? Could improve performance if accessing real media on supported drives
// Read from media
uint16_t readLen = (byteCountLimit < m_lastByte - m_currentByte)
? byteCountLimit
: m_lastByte - m_currentByte;
_fseeki64(m_fpImage, m_currentByte, SEEK_SET);
*packetDataSize = fread(packetDataBuffer, 1, readLen, m_fpImage);
// Update position
m_currentByte += *packetDataSize;
if (m_currentByte >= m_lastByte || *packetDataSize < readLen) {
m_transfer = false;
}
}
else {
// Say that there is no disc in the drive
packetInfo.result.status = StCheckCondition;
packetInfo.result.senseKey = SKNotReady;
packetInfo.result.additionalSenseCode = ASCMediumNotPresent;
}
return true;
}
case OpReadCapacity:
{
ReadCapacityData *capData = reinterpret_cast<ReadCapacityData *>(packetDataBuffer);
if (HasMedia()) {
L2B32(capData->lba, m_sectorCapacity);
L2B32(capData->blockLength, kDVDSectorSize);
}
else {
// Say that there is no disc in the drive
packetInfo.result.status = StCheckCondition;
packetInfo.result.senseKey = SKNotReady;
packetInfo.result.additionalSenseCode = ASCMediumNotPresent;
L2B32(capData->lba, 0);
L2B32(capData->blockLength, 0);
}
*packetDataSize = sizeof(ReadCapacityData);
return true;
}
default:
log_debug("ImageDVDDriveATADeviceDriver::ProcessATAPIPacketDataRead: Unimplemented operation code 0x%x\n", packetInfo.cdb.opCode.u8);
return false;
}
}
bool ImageDVDDriveATADeviceDriver::ProcessATAPIPacketDataWrite(PacketInformation& packetInfo, uint8_t *packetDataBuffer, uint16_t byteCountLimit) {
log_debug("ImageDVDDriveATADeviceDriver::ProcessATAPIPacketDataWrite: Unimplemented operation code 0x%x\n", packetInfo.cdb.opCode.u8);
return false;
}
}
}
}

View file

@ -0,0 +1,63 @@
// ATA/ATAPI-4 emulation for the Original Xbox
// (C) Ivan "StrikerX3" Oliveira
//
// This code aims to implement a subset of the ATA/ATAPI-4 specification
// that satisifies the requirements of an IDE interface for the Original Xbox.
//
// Specification:
// http://www.t13.org/documents/UploadedDocuments/project/d1153r18-ATA-ATAPI-4.pdf
//
// References to particular items in the specification are denoted between brackets
// optionally followed by a quote from the specification.
#pragma once
#include <cstdint>
#include "drv_vdvd_base.h"
namespace vixen {
namespace hw {
namespace ata {
/*!
* A virtual DVD ATA device driver based on an image file.
*
* It can read/write directly to the image file or use write-on-copy, in which
* case all writes done on a temporary file and subsequent reads to overwritten
* sectors are redirected to the temporary file.
*/
class ImageDVDDriveATADeviceDriver : public BaseDVDDriveATADeviceDriver {
public:
ImageDVDDriveATADeviceDriver();
~ImageDVDDriveATADeviceDriver() override;
// ----- Virtual DVD image management -------------------------------------
bool LoadImageFile(const char *imagePath, bool copyOnWrite);
bool EjectMedia();
// ----- ATAPI ------------------------------------------------------------
bool ValidateATAPIPacket(atapi::PacketInformation& packetInfo) override;
bool ProcessATAPIPacketNonData(atapi::PacketInformation& packetInfo) override;
bool ProcessATAPIPacketDataRead(atapi::PacketInformation& packetInfo, uint8_t* packetDataBuffer, uint16_t byteCountLimit, uint32_t *packetDataSize) override;
bool ProcessATAPIPacketDataWrite(atapi::PacketInformation& packetInfo, uint8_t* packetDataBuffer, uint16_t byteCountLimit) override;
private:
FILE *m_fpImage = NULL;
bool m_copyOnWrite;
inline bool HasMedia() { return m_fpImage != NULL; }
uint64_t m_sectorCapacity;
// ----- Transfer state ---------------------------------------------------
bool m_transfer = false;
uint64_t m_currentByte;
uint64_t m_lastByte;
};
}
}
}

View file

@ -17,7 +17,7 @@
#include "vixen/hw/ata/drvs/drv_vhd_image.h"
#include "vixen/hw/ata/drvs/drv_vdvd_dummy.h"
//#include "vixen/hw/ata/drvs/drv_vdvd_image.h"
#include "vixen/hw/ata/drvs/drv_vdvd_image.h"
#ifdef __linux__
#include <sys/mman.h>
@ -395,9 +395,15 @@ EmulatorStatus Xbox::InitHardware() {
m_ataDrivers[0][1] = new hw::ata::DummyDVDDriveATADeviceDriver();
break;
case VDVD_Image:
// TODO: implement
m_ataDrivers[0][1] = new hw::ata::NullATADeviceDriver();
{
auto imageVDVD = new hw::ata::ImageDVDDriveATADeviceDriver();
if (!imageVDVD->LoadImageFile(m_settings.vdvd_parameters.image.path, m_settings.vdvd_parameters.image.preserveImage)) {
log_fatal("Failed to load virtual DVD image file\n");
return EMUS_INIT_DVD_DRIVE_INIT_FAILED;
}
m_ataDrivers[0][1] = imageVDVD;
break;
}
default:
log_fatal("Invalid virtual DVD drive type specified: %d\n", m_settings.vdvd_type);
return EMUS_INIT_INVALID_DVD_DRIVE_TYPE;