From 5640ee8a23e0167a6bb7c18ef3db4f5d1a0b3690 Mon Sep 17 00:00:00 2001 From: TheLastRar Date: Mon, 16 Nov 2020 20:15:03 +0000 Subject: [PATCH] DEV9: Add ATA emulation & Hdd Creator code --- pcsx2/CMakeLists.txt | 14 +- pcsx2/DEV9/ATA/ATA.h | 276 +++++++++++ pcsx2/DEV9/ATA/ATA_Info.cpp | 392 +++++++++++++++ pcsx2/DEV9/ATA/ATA_State.cpp | 446 ++++++++++++++++++ pcsx2/DEV9/ATA/ATA_Transfer.cpp | 250 ++++++++++ pcsx2/DEV9/ATA/Commands/ATA_CmdDMA.cpp | 177 +++++++ .../ATA/Commands/ATA_CmdExecuteDeviceDiag.cpp | 62 +++ pcsx2/DEV9/ATA/Commands/ATA_CmdNoData.cpp | 234 +++++++++ pcsx2/DEV9/ATA/Commands/ATA_CmdPIOData.cpp | 155 ++++++ pcsx2/DEV9/ATA/Commands/ATA_CmdSMART.cpp | 157 ++++++ pcsx2/DEV9/ATA/Commands/ATA_Command.cpp | 183 +++++++ pcsx2/DEV9/ATA/Commands/ATA_SCE.cpp | 56 +++ pcsx2/DEV9/ATA/HddCreate.cpp | 107 +++++ pcsx2/DEV9/{ata.h => ATA/HddCreate.h} | 38 +- pcsx2/windows/VCprojects/pcsx2.vcxproj | 14 +- .../windows/VCprojects/pcsx2.vcxproj.filters | 46 +- 16 files changed, 2592 insertions(+), 15 deletions(-) create mode 100644 pcsx2/DEV9/ATA/ATA.h create mode 100644 pcsx2/DEV9/ATA/ATA_Info.cpp create mode 100644 pcsx2/DEV9/ATA/ATA_State.cpp create mode 100644 pcsx2/DEV9/ATA/ATA_Transfer.cpp create mode 100644 pcsx2/DEV9/ATA/Commands/ATA_CmdDMA.cpp create mode 100644 pcsx2/DEV9/ATA/Commands/ATA_CmdExecuteDeviceDiag.cpp create mode 100644 pcsx2/DEV9/ATA/Commands/ATA_CmdNoData.cpp create mode 100644 pcsx2/DEV9/ATA/Commands/ATA_CmdPIOData.cpp create mode 100644 pcsx2/DEV9/ATA/Commands/ATA_CmdSMART.cpp create mode 100644 pcsx2/DEV9/ATA/Commands/ATA_Command.cpp create mode 100644 pcsx2/DEV9/ATA/Commands/ATA_SCE.cpp create mode 100644 pcsx2/DEV9/ATA/HddCreate.cpp rename pcsx2/DEV9/{ata.h => ATA/HddCreate.h} (59%) diff --git a/pcsx2/CMakeLists.txt b/pcsx2/CMakeLists.txt index 2ad3adc112..a1132f4366 100644 --- a/pcsx2/CMakeLists.txt +++ b/pcsx2/CMakeLists.txt @@ -315,6 +315,17 @@ compile_gresources( pcsx2DEV9UIHeaders # DEV9 sources set(pcsx2DEV9Sources + DEV9/ATA/Commands/ATA_Command.cpp + DEV9/ATA/Commands/ATA_CmdDMA.cpp + DEV9/ATA/Commands/ATA_CmdExecuteDeviceDiag.cpp + DEV9/ATA/Commands/ATA_CmdNoData.cpp + DEV9/ATA/Commands/ATA_CmdPIOData.cpp + DEV9/ATA/Commands/ATA_CmdSMART.cpp + DEV9/ATA/Commands/ATA_SCE.cpp + DEV9/ATA/ATA_Info.cpp + DEV9/ATA/ATA_State.cpp + DEV9/ATA/ATA_Transfer.cpp + DEV9/ATA/HddCreate.cpp DEV9/smap.cpp DEV9/DEV9.cpp DEV9/flash.cpp @@ -327,8 +338,9 @@ set(pcsx2DEV9Sources # DEV9 headers set(pcsx2DEV9Headers + DEV9/ATA/ATA.h + DEV9/ATA/HddCreate.h DEV9/DEV9.h - DEV9/ata.h DEV9/net.h DEV9/pcap_io.h DEV9/smap.h diff --git a/pcsx2/DEV9/ATA/ATA.h b/pcsx2/DEV9/ATA/ATA.h new file mode 100644 index 0000000000..60221a198d --- /dev/null +++ b/pcsx2/DEV9/ATA/ATA.h @@ -0,0 +1,276 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2020 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once + +#include +#include +#include +#include +#include +#include "ghc/filesystem.h" +#include + +#include "PS2Edefs.h" +#include "PS2Eext.h" + +class ATA +{ +public: + //Transfer + bool dmaReady = false; + int nsector = 0; //sector count + int nsectorLeft = 0; //sectors left to transfer +private: + const bool lba48Supported = false; + + std::fstream hddImage; + u64 hddImageSize; + + int pioMode; + int sdmaMode; + int mdmaMode; + int udmaMode; + + //Info + u8 curHeads = 16; + u8 curSectors = 63; + u16 curCylinders = 0; + + u8 curMultipleSectorsSetting = 128; + + u8 identifyData[512] = { 0 }; + + //LBA48 in use? + bool lba48 = false; + + //Enable/disable features + bool fetSmartEnabled = true; + bool fetSecurityEnabled = false; + bool fetWriteCacheEnabled = true; + bool fetHostProtectedAreaEnabled = false; + + //Regs + u16 regCommand; //WriteOnly, Only to be written BSY and DRQ are cleared, DMACK is not set and device is not sleeping, except for DEVICE RESET + //PIO Read/Write, Only to be written DMACK is not set and DRQ is 1 + //COMMAND REG (WriteOnly) Only to be written DMACK is not set + //Bit 0 = 0 + bool regControlEnableIRQ = false; //Bit 1 = 1 Disable Interrupt + //Bit 2 = 1 Software Reset + bool regControlHOBRead = false; //Bit 7 = HOB (cleared by any write to RegCommand, Sets if Low order or High order bytes are read in ATAread16) + //End COMMAND REG + u8 regError; //ReadOnly + + //DEVICE REG (Read/Write) + u8 regSelect; + //Bit 0-3: LBA Bits 24-27 (Unused in 48bit) or Command Dependent + //Bit 4: Selected Device + //Bit 5: Obsolete (All?) + //Bit 6: Command Dependent + //Bit 7: Obsolete (All?) + //End COMMAND REG + u8 regFeature; //WriteOnly, Only to be written BSY and DRQ are cleared and DMACK is not set + u8 regFeatureHOB; + + //Following regs are Read/Write, Only to be written BSY and DRQ are cleared and DMACK is not set + u8 regSector; //Sector Number or LBA Low + u8 regSectorHOB; + u8 regLcyl; //LBA Mid + u8 regLcylHOB; + u8 regHcyl; //LBA High + u8 regHcylHOB; + //TODO handle nsector code + u8 regNsector; + u8 regNsectorHOB; + + u8 regStatus; //ReadOnly. When read via AlternateStatus pending interrupts are not cleared + + //Transfer + //Write Buffer(s) + bool awaitFlush = false; + u8* currentWrite; //array + u32 currentWriteLength; + u64 currentWriteSectors; + + struct WriteQueueEntry + { + std::atomic_bool ready{false}; + WriteQueueEntry* next; + u8* data; + u32 length; + u64 sector; + }; + + WriteQueueEntry* head = nullptr; + WriteQueueEntry* tail = nullptr; + + std::thread ioThread; + bool ioRunning = false; + std::mutex ioMutex; + + std::condition_variable ioThreadIdle_cv; + bool ioThreadIdle_bool = false; + + std::condition_variable ioReady; + std::atomic_bool ioClose{false}; + bool ioWrite; + bool ioRead; + void (ATA::*waitingCmd)() = nullptr; + //Write Buffer(s) + + //Read Buffer + int rdTransferred = 0; + int wrTransferred = 0; + //Max tranfer on 24bit is 256*512 = 128KB + //Max tranfer on 48bit is 65536*512 = 32MB + int readBufferLen; + u8* readBuffer = nullptr; + //Read Buffer + + //PIO Buffer + int pioPtr; + int pioEnd; + u8 pioBuffer[512]; + + int sectorsPerInterrupt; + void (ATA::*pioDRQEndTransferFunc)() = nullptr; + //PIO Buffer + + //Smart + bool smartAutosave = true; + bool smartErrors = false; + u8 smartSelfTestCount = 0; + //Smart + + u8 sceSec[256 * 2] = {0}; + +public: + ATA(); + + int Open(ghc::filesystem::path hddPath); + void Close(); + + void ATA_HardReset(); + + u16 Read16(u32 addr); + void Write16(u32 addr, u16 value); + + void Async(u32 cycles); + + void ATAreadDMA8Mem(u8* pMem, int size); + void ATAwriteDMA8Mem(u8* pMem, int size); + + u16 ATAreadPIO(); + //ATAwritePIO; + + private: + //Info + void CreateHDDinfo(int sizeMb); + void CreateHDDinfoCsum(); + + //State + void ResetBegin(); + void ResetEnd(bool hard); + + u8 GetSelectedDevice() + { + return (regSelect >> 4) & 1; + } + void SetSelectedDevice(u8 value) + { + if (value == 1) + regSelect |= (1 << 4); + else + regSelect &= ~(1 << 4); + } + + s64 HDD_GetLBA(); + void HDD_SetLBA(s64 sectorNum); + + bool HDD_CanSeek(); + bool HDD_CanAccess(int* sectors); + + void ClearHOB(); + + //Transfer + void IO_Thread(); + void IO_Read(); + bool IO_Write(); + void HDD_ReadAsync(void (ATA::*drqCMD)()); + void HDD_ReadSync(void (ATA::*drqCMD)()); + bool HDD_CanAssessOrSetError(); + void HDD_SetErrorAtTransferEnd(); + + void QueueWrite(u64 sector, u8* data, u32 length); + bool DequeueWrite(u64* sector, u8** data, u32* length); + bool IsQueueEmpty(); + + //Commands + void IDE_ExecCmd(u16 value); + + bool PreCmd(); + void HDD_Unk(); + + void IDE_CmdLBA48Transform(bool islba48); + + void DRQCmdDMADataToHost(); + void PostCmdDMADataToHost(); + void DRQCmdDMADataFromHost(); + void PostCmdDMADataFromHost(); + void HDD_ReadDMA(bool isLBA48); + void HDD_WriteDMA(bool isLBA48); + + void PreCmdExecuteDeviceDiag(); + void PostCmdExecuteDeviceDiag(); + void HDD_ExecuteDeviceDiag(); + + void PostCmdNoData(); + void CmdNoDataAbort(); + void HDD_FlushCache(); + void HDD_InitDevParameters(); + void HDD_ReadVerifySectors(bool isLBA48); + void HDD_SeekCmd(); + void HDD_SetFeatures(); + void HDD_SetMultipleMode(); + void HDD_Nop(); + void HDD_Idle(); + + void DRQCmdPIODataToHost(u8* buff, int buffLen, int buffIndex, int size, bool sendIRQ); + void PostCmdPIODataToHost(); + void HDD_IdentifyDevice(); + + void HDD_ReadMultiple(bool isLBA48); + void HDD_ReadSectors(bool isLBA48); + void HDD_ReadPIO(bool isLBA48); + void HDD_ReadPIOS2(); + void HDD_ReadPIOEndBlock(); + //HDD_Write* + + void HDD_Smart(); + void SMART_SetAutoSaveAttribute(); + void SMART_ExecuteOfflineImmediate(); + void SMART_EnableOps(bool enable); + void SMART_ReturnStatus(); + + void HDD_SCE(); + void SCE_IDENTIFY_DRIVE(); + + //In here temporally + static void WriteUInt16(u8* data, int* index, u16 value); + static void WriteUInt32(u8* data, int* index, u32 value); + static void WriteUInt64(u8* data, int* index, u64 value); + static void WritePaddedString(u8* data, int* index, std::string value, u32 len); +}; + diff --git a/pcsx2/DEV9/ATA/ATA_Info.cpp b/pcsx2/DEV9/ATA/ATA_Info.cpp new file mode 100644 index 0000000000..22517e19b9 --- /dev/null +++ b/pcsx2/DEV9/ATA/ATA_Info.cpp @@ -0,0 +1,392 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2020 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "ATA.h" +#include "../DEV9.h" + +void ATA::WriteUInt16(u8* data, int* index, u16 value) +{ + *(u16*)&data[*index] = value; + *index += sizeof(value); +} + +void ATA::WriteUInt32(u8* data, int* index, u32 value) +{ + *(u32*)&data[*index] = value; + *index += sizeof(value); +} + +void ATA::WriteUInt64(u8* data, int* index, u64 value) +{ + *(u64*)&data[*index] = value; + *index += sizeof(value); +} + +//No null char +void ATA::WritePaddedString(u8* data, int* index, std::string value, u32 len) +{ + memset(&data[*index], (u8)' ', len); + memcpy(&data[*index], value.c_str(), value.length() < len ? value.length() : len); + *index += len; +} + +void ATA::CreateHDDinfo(int sizeMb) +{ + const u16 sectorSize = 512; + DEV9_LOG_VERB("HddSize : %i\n", config.HddSize); + const u64 nbSectors = ((u64)(sizeMb / sectorSize) * 1024 * 1024); + DEV9_LOG_VERB("nbSectors : %i\n", nbSectors); + + memset(&identifyData, 0, sizeof(identifyData)); + //Defualt CHS translation + const u16 defHeads = 16; + const u16 defSectors = 63; + u64 cylinderslong = std::min(nbSectors, 16514064) / defHeads / defSectors; + const u16 defCylinders = (u16)std::min(cylinderslong, UINT16_MAX); + + //Curent CHS translation + cylinderslong = std::min(nbSectors, 16514064) / curHeads / curSectors; + curCylinders = (u16)std::min(cylinderslong, UINT16_MAX); + + const int curOldsize = curCylinders * curHeads * curSectors; + //SET MAX ADDRESS will set the nbSectors reported + + //General configuration bit-significant information: + /* + * 0x848A is for CFA devices + * bit 0: Resv (all?) + * bit 1: Hard Sectored (ATA-1) + * bit 2: Soft Sectored (ATA-1) / Response incomplete (ATA-5,6,7,8) + * bit 3: Not MFM encoded (ATA-1) + * bit 4: Head switch time > 15 usec (ATA-1) + * bit 5: Spindle motor control option implemented (ATA-1) + * bit 6: Non-Removable (Obsolete) (ATA-1,2,3,4,5) + * bit 7: Removable (ATA-1,2,3,4,5,6,7,8) + * bit 8: disk transfer rate > 10Mbs (ATA-1) + * bit 9: disk transfer rate > 5Mbs but <= 10Mbs (ATA-1) + * bit 10: disk transfer rate <= 5Mbs (ATA-1) + * bit 11: rotational speed tolerance is > 0.5% (ATA-1) + * bit 12: data strobe offset option available (ATA-1) + * bit 13: track offset option available (ATA-1) + * bit 14: format speed tolerance gap required (ATA-1) + * bit 15: 0 = ATA dev (All?) + */ + int index = 0; + WriteUInt16(identifyData, &index, 0x0040); //word 0 + //Default Num of cylinders + WriteUInt16(identifyData, &index, defCylinders); //word 1 + //Specific configuration + index += 1 * 2; //word 2 + //Default Num of heads (Retired) + WriteUInt16(identifyData, &index, defHeads); //word 3 + //Number of unformatted bytes per track (Retired) + WriteUInt16(identifyData, &index, (u16)(sectorSize * defSectors)); //word 4 + //Number of unformatted bytes per sector (Retired) + WriteUInt16(identifyData, &index, sectorSize); //word 5 + //Default Number of sectors per track (Retired) + WriteUInt16(identifyData, &index, defSectors); //word 6 + //Reserved for assignment by the CompactFlash™ Association + index += 2 * 2; //word 7-8 + //Retired + index += 1 * 2; //word 9 + //Serial number (20 ASCII characters) + WritePaddedString(identifyData, &index, "PCSX2-DEV9-ATA-HDD", 20); //word 10-19 + //Buffer(cache) type (Retired) + WriteUInt16(identifyData, &index, /*3*/ 0); //word 20 + //Buffer(cache) size in sectors (Retired) + WriteUInt16(identifyData, &index, /*512*/ 0); //word 21 + //Number of ECC bytes available on read / write long commands (Obsolete) + WriteUInt16(identifyData, &index, /*4*/ 0); //word 22 + //Firmware revision (8 ASCII characters) + WritePaddedString(identifyData, &index, "FIRM100", 8); //word 23-26 + //Model number (40 ASCII characters) + WritePaddedString(identifyData, &index, "PCSX2-DEV9-ATA-HDD", 40); //word 27-46 + //READ/WRITE MULI max sectors + WriteUInt16(identifyData, &index, 128 & (0x80 << 8)); //word 47 + //Dword IO supported + WriteUInt16(identifyData, &index, 1); //word 48 + //Capabilities + /* + * bits 7-0: Retired + * bit 8: DMA supported + * bit 9: LBA supported + * bit 10:IORDY may be disabled + * bit 11:IORDY supported + * bit 12:Reserved + * bit 13:Standby timer values as specified in this standard are supported + */ + WriteUInt16(identifyData, &index, ((1 << 11) | (1 << 9) | (1 << 8))); //word 49 + //Capabilities (0-Shall be set to one to indicate a device specific Standby timer value minimum) + index += 1 * 2; //word 50 + //PIO data transfer cycle timing mode (Obsolete) + WriteUInt16(identifyData, &index, (u8)((pioMode > 2 ? pioMode : 2) << 8)); //word 51 + //DMA data transfer cycle timing mode (Obsolete) + WriteUInt16(identifyData, &index, 0); //word 52 + // + /* + * bit 0: Fields in 54:58 are valid (CHS sizes)(Obsolete) + * bit 1: Fields in 64:70 are valid (pio3,4 and MWDMA info) + * bit 2: Fields in 88 are valid (UDMA modes) + */ + WriteUInt16(identifyData, &index, (1 | (1 << 1) | (1 << 2))); //word 53 + //Number of current cylinders + WriteUInt16(identifyData, &index, curCylinders); //word 54 + //Number of current heads + WriteUInt16(identifyData, &index, curHeads); //word 55 + //Number of current sectors per track + WriteUInt16(identifyData, &index, curSectors); //word 56 + //Current capacity in sectors + WriteUInt32(identifyData, &index, (u32)curOldsize); //word 57-58 + //PIO READ/WRITE Multiple setting + /* + * bit 7-0: Current setting for number of logical sectors that shall be transferred per DRQ + * data block on READ/WRITE Multiple commands + * bit 8: Multiple sector setting is valid + */ + WriteUInt16(identifyData, &index, (u16)(curMultipleSectorsSetting | (1 << 8))); //word 59 + //Total number of user addressable logical sectors + WriteUInt32(identifyData, &index, (u32)(nbSectors < 268435456 ? nbSectors : 268435456)); //word 60-61 + //DMA modes + /* + * bits 0-7: Singleword modes supported (0,1,2) + * bits 8-15: Transfer mode active + */ + if (sdmaMode > 0) + WriteUInt16(identifyData, &index, (u16)(0x07 | (1 << (sdmaMode + 8)))); //word 62 + else + WriteUInt16(identifyData, &index, 0x07); //word 62 + //DMA Modes + /* + * bits 0-7: Multiword modes supported (0,1,2) + * bits 8-15: Transfer mode active + */ + if (mdmaMode > 0) + WriteUInt16(identifyData, &index, (u16)(0x07 | (1 << (mdmaMode + 8)))); //word 63 + else + WriteUInt16(identifyData, &index, 0x07); //word 63 + //Bit 0-7-PIO modes supported (0,1,2,3,4) + WriteUInt16(identifyData, &index, 0x1F); //word 64 (pio3,4 supported) selection not reported here + //Minimum Multiword DMA transfer cycle time per word + WriteUInt16(identifyData, &index, 80); //word 65 + //Manufacturer’s recommended Multiword DMA transfer cycle time + WriteUInt16(identifyData, &index, 80); //word 66 + //Minimum PIO transfer cycle time without flow control + WriteUInt16(identifyData, &index, 120); //word 67 + //Minimum PIO transfer cycle time with IORDY flow control + WriteUInt16(identifyData, &index, 120); //word 68 + //Reserved + //69-70 + //Reserved + //71-74 + //Queue depth (4bit, Maximum queue depth - 1) + //75 + //Reserved + //76-79 + index = 80 * 2; + //Major revision number (1-3-Obsolete, 4-7-ATA4-7 supported) + WriteUInt16(identifyData, &index, 0x70); //word 80 + //Minor revision number + WriteUInt16(identifyData, &index, 0); //word 81 + //Supported Feature Sets (82) + /* + * bit 0: Smart + * bit 1: Security Mode + * bit 2: Removable media feature set + * bit 3: Power management + * bit 4: Packet (the CD features) + * bit 5: Write cache + * bit 6: Look-ahead + * bit 7: Release interrupt + * bit 8: SERVICE interrupt + * bit 9: DEVICE RESET interrupt + * bit 10: Host Protected Area + * bit 11: (Obsolete) + * bit 12: WRITE BUFFER command + * bit 13: READ BUFFER command + * bit 14: NOP + * bit 15: (Obsolete) + */ + WriteUInt16(identifyData, &index, (u16)((1 << 14) | (1 << 5) | /*(1 << 1) | (1 << 10) |*/ 1)); //word 82 + //Supported Feature Sets (83) + /* + * bit 0: DOWNLOAD MICROCODE + * bit 1: READ/WRITE DMA QUEUED + * bit 2: CFA (Card reader) + * bit 3: Advanced Power Management + * bit 4: Removable Media Status Notifications + * bit 5: Power-Up Standby + * bit 6: SET FEATURES required to spin up after power-up + * bit 7: ?? + * bit 8: SET MAX security extension + * bit 9: Automatic Acoustic Management + * bit 10: 48bit LBA + * bit 11: Device Configuration Overlay + * bit 12: FLUSH CACHE + * bit 13: FLUSH CACHE EXT + * bit 14: 1 + */ + WriteUInt16(identifyData, &index, (u16)((1 << 14) | (1 << 13) | (1 << 12) /*| (1 << 8)*/ | ((lba48Supported ? 1 : 0) << 10))); //word 83 + //Supported Feature Sets (84) + /* + * bit 0: Smart error logging + * bit 1: smart self-test + * bit 2: Media serial number + * bit 3: Media Card Pass Though + * bit 4: Streaming feature set + * bit 5: General Purpose Logging + * bit 6: WRITE DMA FUA EXT & WRITE MULTIPLE FUA EXT + * bit 7: WRITE DMA QUEUED FUA EXT + * bit 8: 64bit World Wide Name + * bit 9: URG bit supported for WRITE STREAM DMA EXT amd WRITE STREAM EXT + * bit 10: URG bit supported for READ STREAM DMA EXT amd READ STREAM EXT + * bit 13: IDLE IMMEDIATE with UNLOAD FEATURE + * bit 14: 1 + */ + WriteUInt16(identifyData, &index, (u16)((1 << 14) | (1 << 1) | 1)); //word 84 + //Command set/feature enabled/supported (See word 82) + WriteUInt16(identifyData, &index, (u16)((fetSmartEnabled << 0) | (fetSecurityEnabled << 1) | (fetWriteCacheEnabled << 5) | (fetHostProtectedAreaEnabled << 10) | (1 << 14))); //Fixed //word 85 + //Command set/feature enabled/supported (See word 83) + WriteUInt16(identifyData, &index, (u16)( + /*(1 << 8) | //SET MAX */ + ((lba48Supported ? 1 : 0) << 10) | //Fixed + (1 << 12) | //Fixed + (1 << 13))); //Fixed //word 86 + //Command set/feature enabled/supported (See word 84) + WriteUInt16(identifyData, &index, (u16)((1 << 14) | (1 << 1) | 1)); + WriteUInt16(identifyData, &index, (u16)( + (1) | //Fixed + ((1) << 1))); //Fixed //word 87 + //UDMA modes + /* + * bits 0-7: ultraword modes supported (0,1,2,4,5,6,7) + * bits 8-15: Transfer mode active + */ + if (udmaMode > 0) + WriteUInt16(identifyData, &index, (u16)(0x7f | (1 << (udmaMode + 8)))); //word 88 + else + WriteUInt16(identifyData, &index, 0x7f); //word 88 + //Time required for security erase unit completion + //89 + //Time required for Enhanced security erase completion + //90 + //Current advanced power management value + //91 + //Master Password Identifier + //92 + //Hardware reset result. The contents of bits (12:0) of this word shall change only during the execution of a hardware reset. + /* + * bit 0: 1 + * bit 1-2: How Dev0 determined Dev number (11 = unk) + * bit 3: Dev 0 Passes Diag + * bit 4: Dev 0 Detected assertion of PDIAG + * bit 5: Dev 0 Detected assertion of DSAP + * bit 6: Dev 0 Responds when Dev1 is selected + * bit 7: Reserved + * bit 8: 1 + * bit 9-10: How Dev1 determined Dev number + * bit 11: Dev1 asserted 1 + * bit 12: Reserved + * bit 13: Dev detected CBLID above Vih + * bit 14: 1 + */ + index = 93 * 2; + WriteUInt16(identifyData, &index, (u16)(1 | (1 << 14) | 0x2000)); //word 93 + //Vendor’s recommended acoustic management value. + //94 + //Stream Minimum Request Size + //95 + //Streaming Transfer Time - DMA + //96 + //Streaming Access Latency - DMA and PIO + //97 + //Streaming Performance Granularity + //98-99 + //Total Number of User Addressable Sectors for the 48-bit Address feature set. + index = 100 * 2; + WriteUInt64(identifyData, &index, (u16)nbSectors); + index -= 2; + WriteUInt16(identifyData, &index, 0); //truncate to 48bits + //Streaming Transfer Time - PIO + //104 + //Reserved + //105 + //Physical sector size / Logical Sector Size + /* + * bit 0-3: 2^X logical sectors per physical sector + * bit 12: Logical sector longer than 512 bytes + * bit 13: multiple logical sectors per physical sector + * bit 14: 1 + */ + index = 106 * 2; + WriteUInt16(identifyData, &index, (u16)((1 << 14) | 0)); + //Inter-seek delay for ISO-7779acoustic testing in microseconds + //107 + //WNN + //108-111 + //Reserved + //112-115 + //Reserved + //116 + //Words per Logical Sector + //117-118 + //Reserved + //119-126 + //Removable Media Status Notification feature support + //127 + //Security status + /* + * bit 0: Security supported + * bit 1: Security enabled + * bit 2: Security locked + * bit 3: Security frozen + * bit 4: Security count expired + * bit 5: Enhanced erase supported + * bit 6-7: reserved + * bit 8: is Maximum Security + */ + //Vendor Specific + //129-159 + //CFA power mode 1 + //160 + //Reserved for CFA + //161-175 + //Current media serial number (60 ASCII characters) + //176-205 + //Reserved + //206-254 + //Integrity word + //15:8 Checksum, 7:0 Signature + CreateHDDinfoCsum(); +} +void ATA::CreateHDDinfoCsum() //Is this correct? +{ + u8 counter = 0; + + for (int i = 0; i < (512 - 1); i++) + counter += identifyData[i]; + + counter += 0xA5; + + identifyData[510] = 0xA5; + identifyData[511] = (u8)(255 - counter + 1); + counter = 0; + + for (int i = 0; i < (512); i++) + counter += identifyData[i]; + + DEV9_LOG_VERB("%s\n", counter); +} diff --git a/pcsx2/DEV9/ATA/ATA_State.cpp b/pcsx2/DEV9/ATA/ATA_State.cpp new file mode 100644 index 0000000000..9ce3b5f891 --- /dev/null +++ b/pcsx2/DEV9/ATA/ATA_State.cpp @@ -0,0 +1,446 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2020 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "ATA.h" +#include "../DEV9.h" +#include "HddCreate.h" + +ATA::ATA() +{ + //Power on, Would do self-Diag + Hardware Init + ResetBegin(); + ResetEnd(true); +} + +int ATA::Open(ghc::filesystem::path hddPath) +{ + readBufferLen = 256 * 512; + readBuffer = new u8[readBufferLen]; + + CreateHDDinfo(config.HddSize); + + //Open File + if (ghc::filesystem::exists(hddPath)) + hddImage = ghc::filesystem::fstream(hddPath, std::ios::in | std::ios::out | std::ios::binary); + else + { + HddCreate hddCreator; + hddCreator.filePath = hddPath; + hddCreator.neededSize = config.HddSize; + hddCreator.Start(); + + if (hddCreator.errored) + return -1; + + hddImage = ghc::filesystem::fstream(hddPath); + } + + //Store HddImage size for later check + hddImage.seekg(0, std::ios::end); + hddImageSize = hddImage.tellg(); + + //Setup Write queue + tail = new WriteQueueEntry(); + head = tail; + + { + std::lock_guard ioSignallock(ioMutex); + ioRead = false; + ioWrite = false; + } + + ioThread = std::thread(&ATA::IO_Thread, this); + ioRunning = true; + + return 0; +} + +void ATA::Close() +{ + //Wait for async code to finish + if (ioRunning) + { + ioClose.store(true); + { + std::lock_guard ioSignallock(ioMutex); + ioWrite = true; + } + ioReady.notify_all(); + + ioThread.join(); + ioRunning = false; + } + //Delete queue + if (head != nullptr) + { + if (!IsQueueEmpty()) + { + DEV9_LOG_ERROR("Write queue not empty"); + pxAssert(false); + abort(); //All data must be written at this point + } + + delete head; + head = nullptr; + tail = nullptr; + } + //Close File Handle + if (hddImage.is_open()) + hddImage.close(); + + delete[] readBuffer; + readBuffer = nullptr; +} + +void ATA::ResetBegin() +{ + PreCmdExecuteDeviceDiag(); +} +void ATA::ResetEnd(bool hard) +{ + curHeads = 16; + curSectors = 63; + curCylinders = 0; + curMultipleSectorsSetting = 128; + + //UDMA Mode setting is preserved + //across SRST + if (hard) + { + pioMode = 4; + sdmaMode = -1; + mdmaMode = 2; + udmaMode = -1; + } + else + { + pioMode = 4; + if (udmaMode == -1) + { + sdmaMode = -1; + mdmaMode = 2; + } + } + + regControlEnableIRQ = false; + HDD_ExecuteDeviceDiag(); + regControlEnableIRQ = true; +} + +void ATA::ATA_HardReset() +{ + DEV9_LOG_VERB("*ATA_HARD RESET\n"); + ResetBegin(); + ResetEnd(true); +} + +u16 ATA::Read16(u32 addr) +{ + switch (addr) + { + case ATA_R_DATA: + return ATAreadPIO(); + case ATA_R_ERROR: + DEV9_LOG_VERB("*ATA_R_ERROR 16bit read at address %x, value %x, Active %s\n", addr, regError, (GetSelectedDevice() == 0) ? "True" : "False"); + if (GetSelectedDevice() != 0) + return 0; + return regError; + case ATA_R_NSECTOR: + DEV9_LOG_VERB("*ATA_R_NSECTOR 16bit read at address %x, value %x, Active %s\n", addr, nsector, (GetSelectedDevice() == 0) ? "True" : "False"); + if (GetSelectedDevice() != 0) + return 0; + if (!regControlHOBRead) + return regNsector; + else + return regNsectorHOB; + case ATA_R_SECTOR: + DEV9_LOG_VERB("*ATA_R_NSECTOR 16bit read at address %x, value %x, Active %s\n", addr, regSector, (GetSelectedDevice() == 0) ? "True" : "False"); + if (GetSelectedDevice() != 0) + return 0; + if (!regControlHOBRead) + return regSector; + else + return regSectorHOB; + case ATA_R_LCYL: + DEV9_LOG_VERB("*ATA_R_LCYL 16bit read at address %x, value %x, Active %s\n", addr, regLcyl, (GetSelectedDevice() == 0) ? "True" : "False"); + if (GetSelectedDevice() != 0) + return 0; + if (!regControlHOBRead) + return regLcyl; + else + return regLcylHOB; + case ATA_R_HCYL: + DEV9_LOG_VERB("*ATA_R_HCYL 16bit read at address % x, value % x, Active %s\n", addr, regHcyl, (GetSelectedDevice() == 0) ? " True " : " False "); + if (GetSelectedDevice() != 0) + return 0; + if (!regControlHOBRead) + return regHcyl; + else + return regHcylHOB; + case ATA_R_SELECT: + DEV9_LOG_VERB("*ATA_R_SELECT 16bit read at address % x, value % x, Active %s\n", addr, regSelect, (GetSelectedDevice() == 0) ? " True " : " False "); + return regSelect; + case ATA_R_STATUS: + DEV9_LOG_VERB("*ATA_R_STATUS (Fallthough to ATA_R_ALT_STATUS)\n"); + //Clear irqcause + dev9.irqcause &= ~ATA_INTR_INTRQ; + [[fallthrough]]; + case ATA_R_ALT_STATUS: + DEV9_LOG_VERB("*ATA_R_ALT_STATUS 16bit read at address % x, value % x, Active %s\n", addr, regStatus, (GetSelectedDevice() == 0) ? " True " : " False "); + //raise IRQ? + if (GetSelectedDevice() != 0) + return 0; + return regStatus; + default: + DEV9_LOG_ERROR("*Unknown 16bit read at address %x\n", addr); + return 0xff; + } +} + +void ATA::Write16(u32 addr, u16 value) +{ + if (addr != ATA_R_CMD && (regStatus & (ATA_STAT_BUSY | ATA_STAT_DRQ)) != 0) + { + DEV9_LOG_ERROR("*DEVICE BUSY, DROPPING WRITE\n"); + return; + } + switch (addr) + { + case ATA_R_FEATURE: + DEV9_LOG_VERB("*ATA_R_FEATURE 16bit write at address %x, value %x\n", addr, value); + ClearHOB(); + regFeatureHOB = regFeature; + regFeature = (u8)value; + break; + case ATA_R_NSECTOR: + DEV9_LOG_VERB("*ATA_R_NSECTOR 16bit write at address %x, value %x\n", addr, value); + ClearHOB(); + regNsectorHOB = regNsector; + regNsector = (u8)value; + break; + case ATA_R_SECTOR: + DEV9_LOG_VERB("*ATA_R_SECTOR 16bit write at address %x, value %x\n", addr, value); + ClearHOB(); + regSectorHOB = regSector; + regSector = (u8)value; + break; + case ATA_R_LCYL: + DEV9_LOG_VERB("*ATA_R_LCYL 16bit write at address %x, value %x\n", addr, value); + ClearHOB(); + regLcylHOB = regLcyl; + regLcyl = (u8)value; + break; + case ATA_R_HCYL: + DEV9_LOG_VERB("*ATA_R_HCYL 16bit write at address %x, value %x\n", addr, value); + ClearHOB(); + regHcylHOB = regHcyl; + regHcyl = (u8)value; + break; + case ATA_R_SELECT: + DEV9_LOG_VERB("*ATA_R_SELECT 16bit write at address %x, value %x\n", addr, value); + regSelect = (u8)value; + //bus->ifs[0].select = (val & ~0x10) | 0xa0; + //bus->ifs[1].select = (val | 0x10) | 0xa0; + break; + case ATA_R_CONTROL: + DEV9_LOG_VERB("*ATA_R_CONTROL 16bit write at address %x, value %x\n", addr, value); + //dev9Ru16(ATA_R_CONTROL) = value; + if ((value & 0x2) != 0) + { + //Supress all IRQ + dev9.irqcause &= ~ATA_INTR_INTRQ; + regControlEnableIRQ = false; + } + else + regControlEnableIRQ = true; + + if ((value & 0x4) != 0) + { + DEV9_LOG_VERB("*ATA_R_CONTROL RESET\n"); + ResetBegin(); + ResetEnd(false); + } + if ((value & 0x80) != 0) + regControlHOBRead = true; + + break; + case ATA_R_CMD: + DEV9_LOG_VERB("*ATA_R_CMD 16bit write at address %x, value %x\n", addr, value); + regCommand = value; + regControlHOBRead = false; + dev9.irqcause &= ~ATA_INTR_INTRQ; + IDE_ExecCmd(value); + break; + default: + DEV9_LOG_ERROR("*UNKOWN 16bit write at address %x, value %x\n", addr, value); + break; + } +} + +void ATA::Async(uint cycles) +{ + if (!hddImage.is_open()) + return; + + if ((regStatus & (ATA_STAT_BUSY | ATA_STAT_DRQ)) == 0 || + awaitFlush || (waitingCmd != nullptr)) + { + { + std::lock_guard ioSignallock(ioMutex); + if (ioRead || ioWrite) + //IO Running + return; + } + + //Note, ioThread may still be working. + if (waitingCmd != nullptr) //Are we waiting to continue a command? + { + //Log_Info("Running waiting command"); + void(ATA::*cmd)() = waitingCmd; + waitingCmd = nullptr; + (this->*cmd)(); + } + else if (!IsQueueEmpty()) //Flush cache + { + //Log_Info("Starting async write"); + { + std::lock_guard ioSignallock(ioMutex); + ioWrite = true; + } + ioReady.notify_all(); + } + else if (awaitFlush) //Fire IRQ on flush completion? + { + //Log_Info("Flush done, raise IRQ"); + awaitFlush = false; + PostCmdNoData(); + } + } +} + +s64 ATA::HDD_GetLBA() +{ + if ((regSelect & 0x40) != 0) + { + if (!lba48) + { + return (regSector | + (regLcyl << 8) | + (regHcyl << 16) | + ((regSelect & 0x0f) << 24)); + } + else + { + return ((s64)regHcylHOB << 40) | + ((s64)regLcylHOB << 32) | + ((s64)regSectorHOB << 24) | + ((s64)regHcyl << 16) | + ((s64)regLcyl << 8) | + regSector; + } + } + else + { + regStatus |= (u8)ATA_STAT_ERR; + regError |= (u8)ATA_ERR_ABORT; + + DEV9_LOG_ERROR("DEV9 ERROR : tried to get LBA address while LBA mode disabled\n"); + //(c.Nh + h).Ns+(s-1) + //s64 CHSasLBA = ((regLcyl + (regHcyl << 8)) * curHeads + (regSelect & 0x0F)) * curSectors + (regSector - 1); + return -1; + } +} + +void ATA::HDD_SetLBA(s64 sectorNum) +{ + if ((regSelect & 0x40) != 0) + { + if (!lba48) + { + regSelect = (u8)((regSelect & 0xf0) | (int)((sectorNum >> 24) & 0x0f)); + regHcyl = (u8)(sectorNum >> 16); + regLcyl = (u8)(sectorNum >> 8); + regSector = (u8)(sectorNum); + } + else + { + regSector = (u8)sectorNum; + regLcyl = (u8)(sectorNum >> 8); + regHcyl = (u8)(sectorNum >> 16); + regSectorHOB = (u8)(sectorNum >> 24); + regLcylHOB = (u8)(sectorNum >> 32); + regHcylHOB = (u8)(sectorNum >> 40); + } + } + else + { + regStatus |= ATA_STAT_ERR; + regError |= ATA_ERR_ABORT; + + DEV9_LOG_ERROR("DEV9 ERROR : tried to get LBA address while LBA mode disabled\n"); + } +} + +bool ATA::HDD_CanSeek() +{ + int sectors = 0; + return HDD_CanAccess(§ors); +} + +bool ATA::HDD_CanAccess(int* sectors) +{ + s64 lba; + s64 posStart; + s64 posEnd; + s64 maxLBA; + + maxLBA = std::min((s64)config.HddSize * 1024 * 1024 / 512, hddImageSize); + if ((regSelect & 0x40) == 0) //CHS mode + maxLBA = std::min(maxLBA, curCylinders * curHeads * curSectors); + + lba = HDD_GetLBA(); + if (lba == -1) + return false; + + DEV9_LOG_VERB("LBA :%i\n", lba); + posStart = lba; + + if (posStart > maxLBA) + { + *sectors = -1; + return false; + } + + posEnd = posStart + *sectors; + + if (posEnd > maxLBA) + { + const s64 overshoot = posEnd - maxLBA; + s64 space = *sectors - overshoot; + *sectors = (int)space; + return false; + } + + return true; +} + +//QEMU stuff +void ATA::ClearHOB() +{ + /* any write clears HOB high bit of device control register */ + regControlHOBRead = false; +} diff --git a/pcsx2/DEV9/ATA/ATA_Transfer.cpp b/pcsx2/DEV9/ATA/ATA_Transfer.cpp new file mode 100644 index 0000000000..c7e6871b5d --- /dev/null +++ b/pcsx2/DEV9/ATA/ATA_Transfer.cpp @@ -0,0 +1,250 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2020 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "ATA.h" +#include "../DEV9.h" + +void ATA::IO_Thread() +{ + std::unique_lock ioWaitHandle(ioMutex); + ioThreadIdle_bool = false; + ioWaitHandle.unlock(); + + while (true) + { + ioWaitHandle.lock(); + ioThreadIdle_bool = true; + ioThreadIdle_cv.notify_all(); + + ioReady.wait(ioWaitHandle, [&] { return ioRead | ioWrite; }); + ioThreadIdle_bool = false; + + int ioType = -1; + if (ioRead) + ioType = 0; + else if (ioWrite) + ioType = 1; + + ioWaitHandle.unlock(); + + //Read or Write + if (ioType == 0) + IO_Read(); + else if (ioType == 1) + { + if (!IO_Write()) + { + if (ioClose.load()) + { + ioClose.store(false); + ioWaitHandle.lock(); + ioThreadIdle_bool = true; + ioWaitHandle.unlock(); + return; + } + } + } + } +} + +void ATA::IO_Read() +{ + const s64 lba = HDD_GetLBA(); + + if (lba == -1) + { + DEV9_LOG_ERROR("Invalid LBA"); + pxAssert(false); + abort(); + } + + const u64 pos = lba * 512; + hddImage.seekg(pos, std::ios::beg); + if (hddImage.fail()) + { + DEV9_LOG_ERROR("Read error"); + pxAssert(false); + abort(); + } + hddImage.read((char*)readBuffer, (u64)nsector * 512); + { + std::lock_guard ioSignallock(ioMutex); + ioRead = false; + } +} + +bool ATA::IO_Write() +{ + u64 sector = 0; + u8* data = nullptr; + u32 len = 0; + if (!DequeueWrite(§or, &data, &len)) + { + std::lock_guard ioSignallock(ioMutex); + ioWrite = false; + return false; + } + + hddImage.seekp(sector * 512, std::ios::beg); + hddImage.write((char*)data, len); + if (hddImage.fail()) + { + DEV9_LOG_ERROR("Write error"); + pxAssert(false); + abort(); + } + hddImage.flush(); + delete[] data; + return true; +} + +void ATA::HDD_ReadAsync(void (ATA::*drqCMD)()) +{ + nsectorLeft = 0; + + if (!HDD_CanAssessOrSetError()) + return; + + nsectorLeft = nsector; + if (readBufferLen < nsector * 512) + { + delete readBuffer; + readBuffer = new u8[nsector * 512]; + readBufferLen = nsector * 512; + } + waitingCmd = drqCMD; + + { + std::lock_guard ioSignallock(ioMutex); + ioRead = true; + } + ioReady.notify_all(); +} + +//Note, we don't expect both Async & Sync Reads +//Do one of the other +void ATA::HDD_ReadSync(void (ATA::*drqCMD)()) +{ + //unique_lock instead of lock_guard as also used for cv + std::unique_lock ioWaitHandle(ioMutex); + //Set ioWrite false to prevent reading & writing at the same time + const bool ioWritePaused = ioWrite; + ioWrite = false; + + //wait until thread waiting + ioThreadIdle_cv.wait(ioWaitHandle, [&] { return ioThreadIdle_bool; }); + ioWaitHandle.unlock(); + + nsectorLeft = 0; + + if (!HDD_CanAssessOrSetError()) + { + if (ioWritePaused) + { + ioWaitHandle.lock(); + ioWrite = true; + ioWaitHandle.unlock(); + ioReady.notify_all(); + } + return; + } + + nsectorLeft = nsector; + if (readBufferLen < nsector * 512) + { + delete[] readBuffer; + readBuffer = new u8[nsector * 512]; + readBufferLen = nsector * 512; + } + + IO_Read(); + + if (ioWritePaused) + { + ioWaitHandle.lock(); + ioWrite = true; + ioWaitHandle.unlock(); + ioReady.notify_all(); + } + + (this->*drqCMD)(); +} + +bool ATA::HDD_CanAssessOrSetError() +{ + if (!HDD_CanAccess(&nsector)) + { + //Read what we can + regStatus |= (u8)ATA_STAT_ERR; + regError |= (u8)ATA_ERR_ID; + if (nsector == -1) + { + PostCmdNoData(); + return false; + } + } + return true; +} +void ATA::HDD_SetErrorAtTransferEnd() +{ + u64 currSect = HDD_GetLBA(); + currSect += nsector; + if ((regStatus & ATA_STAT_ERR) != 0) + { + //Error condition + //Write errored sector to LBA + currSect++; + HDD_SetLBA(currSect); + } +} + +//Used by EE thread only +void ATA::QueueWrite(u64 sector, u8* data, u32 length) +{ + WriteQueueEntry* newEntry = head; + newEntry->data = data; + newEntry->length = length; + newEntry->sector = sector; + + //Allocate Next entry + newEntry->next = new WriteQueueEntry(); + head = newEntry->next; + //Set ready + newEntry->ready.store(true); +} + +//Used by IO thread only +bool ATA::DequeueWrite(u64* sector, u8** data, u32* length) +{ + if (!tail->ready.load()) + return false; + + WriteQueueEntry* entry = tail; + tail = entry->next; + + *sector = entry->sector; + *data = entry->data; + *length = entry->length; + delete entry; + return true; +} + +//Used by EE thread only +bool ATA::IsQueueEmpty() +{ + return !tail->ready.load(); +} diff --git a/pcsx2/DEV9/ATA/Commands/ATA_CmdDMA.cpp b/pcsx2/DEV9/ATA/Commands/ATA_CmdDMA.cpp new file mode 100644 index 0000000000..babcb7eadc --- /dev/null +++ b/pcsx2/DEV9/ATA/Commands/ATA_CmdDMA.cpp @@ -0,0 +1,177 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2020 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "../ATA.h" +#include "../../DEV9.h" + +void ATA::DRQCmdDMADataToHost() +{ + //Ready to Start DMA + regStatus &= ~ATA_STAT_BUSY; + regStatus |= ATA_STAT_DRQ; + dmaReady = true; + _DEV9irq(SPD_INTR_ATA_FIFO_DATA, 1); + //PCSX2 will Start DMA +} +void ATA::PostCmdDMADataToHost() +{ + //readBuffer = null; + nsectorLeft = 0; + + regStatus &= ~ATA_STAT_DRQ; + regStatus &= ~ATA_STAT_BUSY; + dmaReady = false; + + dev9.irqcause &= ~SPD_INTR_ATA_FIFO_DATA; + if (regControlEnableIRQ) + _DEV9irq(ATA_INTR_INTRQ, 1); + //PCSX2 Will Start DMA +} + +void ATA::DRQCmdDMADataFromHost() +{ + //Ready to Start DMA + if (!HDD_CanAssessOrSetError()) + return; + + nsectorLeft = nsector; + currentWrite = new u8[nsector * 512]; + currentWriteLength = nsector * 512; + currentWriteSectors = HDD_GetLBA(); + + + regStatus &= ~ATA_STAT_BUSY; + regStatus |= ATA_STAT_DRQ; + dmaReady = true; + _DEV9irq(SPD_INTR_ATA_FIFO_DATA, 1); + //PCSX2 will Start DMA +} +void ATA::PostCmdDMADataFromHost() +{ + QueueWrite(currentWriteSectors, currentWrite, currentWriteLength); + currentWrite = nullptr; + currentWriteLength = 0; + currentWriteSectors = 0; + nsectorLeft = 0; + + regStatus &= ~ATA_STAT_DRQ; + dmaReady = false; + + dev9.irqcause &= ~SPD_INTR_ATA_FIFO_DATA; + + if (fetWriteCacheEnabled) + { + regStatus &= ~ATA_STAT_BUSY; + if (regControlEnableIRQ) + _DEV9irq(ATA_INTR_INTRQ, 1); //0x6C + } + else + awaitFlush = true; + + Async(-1); +} + +void ATA::ATAreadDMA8Mem(u8* pMem, int size) +{ + if ((udmaMode >= 0) && + (dev9.if_ctrl & SPD_IF_ATA_DMAEN) != 0) + { + if (size == 0) + return; + + DEV9_LOG_VERB("DMA read, size %i, transferred %i, total size %i\n", size, rdTransferred, nsector * 512); + + //read + memcpy(pMem, &readBuffer[rdTransferred], size); + + rdTransferred += size; + + if (rdTransferred >= nsector * 512) + { + HDD_SetErrorAtTransferEnd(); + + nsector = 0; + rdTransferred = 0; + PostCmdDMADataToHost(); + } + } +} + +void ATA::ATAwriteDMA8Mem(u8* pMem, int size) +{ + if ((udmaMode >= 0) && + (dev9.if_ctrl & SPD_IF_ATA_DMAEN) != 0) + { + DEV9_LOG_VERB("DMA write, size %i, transferred %i, total size %i\n", size, wrTransferred, nsector * 512); + + //write + memcpy(¤tWrite[wrTransferred], pMem, size); + + wrTransferred += size; + + if (wrTransferred >= nsector * 512) + { + HDD_SetErrorAtTransferEnd(); + + nsector = 0; + wrTransferred = 0; + PostCmdDMADataFromHost(); + } + } +} + +//GENRAL FEATURE SET + +void ATA::HDD_ReadDMA(bool isLBA48) +{ + if (!PreCmd()) + return; + DEV9_LOG_VERB("HDD_ReadDMA\n"); + + IDE_CmdLBA48Transform(isLBA48); + + if (!HDD_CanSeek()) + { + regStatus |= ATA_STAT_ERR; + regError |= ATA_ERR_ID; + PostCmdNoData(); + return; + } + + //Do Sync Read + HDD_ReadSync(&ATA::DRQCmdDMADataToHost); +} + +void ATA::HDD_WriteDMA(bool isLBA48) +{ + if (!PreCmd()) + return; + DEV9_LOG_VERB("HDD_WriteDMA\n"); + + IDE_CmdLBA48Transform(isLBA48); + + if (!HDD_CanSeek()) + { + regStatus |= ATA_STAT_ERR; + regError |= ATA_ERR_ID; + PostCmdNoData(); + return; + } + + //Do Async write + DRQCmdDMADataFromHost(); +} diff --git a/pcsx2/DEV9/ATA/Commands/ATA_CmdExecuteDeviceDiag.cpp b/pcsx2/DEV9/ATA/Commands/ATA_CmdExecuteDeviceDiag.cpp new file mode 100644 index 0000000000..2e7272807a --- /dev/null +++ b/pcsx2/DEV9/ATA/Commands/ATA_CmdExecuteDeviceDiag.cpp @@ -0,0 +1,62 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2020 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "../ATA.h" +#include "../../DEV9.h" + +void ATA::PreCmdExecuteDeviceDiag() +{ + regStatus |= ATA_STAT_BUSY; + regStatus &= ~ATA_STAT_READY; + dev9.irqcause &= ~ATA_INTR_INTRQ; + //dev9.spd.regIntStat &= unchecked((UInt16)~DEV9Header.ATA_INTR_DMA_RDY); //Is this correct? +} + +void ATA::PostCmdExecuteDeviceDiag() +{ + regStatus &= ~ATA_STAT_BUSY; + regStatus |= ATA_STAT_READY; + + SetSelectedDevice(0); + + if (regControlEnableIRQ) + _DEV9irq(ATA_INTR_INTRQ, 1); +} + +//GENRAL FEATURE SET + +void ATA::HDD_ExecuteDeviceDiag() +{ + PreCmdExecuteDeviceDiag(); + //Perform Self Diag + //Log_Error("ExecuteDeviceDiag"); + //Would check both drives, but the PS2 would only have 1 + regError &= ~ATA_ERR_ICRC; + //Passed self-Diag + regError = (0x01 | (regError & ATA_ERR_ICRC)); + + regNsector = 1; + regSector = 1; + regLcyl = 0; + regHcyl = 0; + + regStatus &= ~ATA_STAT_DRQ; + regStatus &= ~ATA_STAT_ECC; + regStatus &= ~ATA_STAT_ERR; + + PostCmdExecuteDeviceDiag(); +} diff --git a/pcsx2/DEV9/ATA/Commands/ATA_CmdNoData.cpp b/pcsx2/DEV9/ATA/Commands/ATA_CmdNoData.cpp new file mode 100644 index 0000000000..e3b0d47d45 --- /dev/null +++ b/pcsx2/DEV9/ATA/Commands/ATA_CmdNoData.cpp @@ -0,0 +1,234 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2020 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "../ATA.h" +#include "../../DEV9.h" + +void ATA::PostCmdNoData() +{ + regStatus &= ~ATA_STAT_BUSY; + + if (regControlEnableIRQ) + _DEV9irq(ATA_INTR_INTRQ, 1); +} + +void ATA::CmdNoDataAbort() +{ + PreCmd(); + + regError |= ATA_ERR_ABORT; + regStatus |= ATA_STAT_ERR; + PostCmdNoData(); +} + +//GENRAL FEATURE SET + +void ATA::HDD_FlushCache() //Can't when DRQ set +{ + if (!PreCmd()) + return; + DEV9_LOG_VERB("HDD_FlushCache\n"); + + awaitFlush = true; + Async(-1); +} + +void ATA::HDD_InitDevParameters() +{ + PreCmd(); //Ignore DRDY bit + DEV9_LOG_INFO("HDD_InitDevParameters\n"); + + curSectors = regNsector; + curHeads = (u8)((regSelect & 0x7) + 1); + PostCmdNoData(); +} + +void ATA::HDD_ReadVerifySectors(bool isLBA48) +{ + if (!PreCmd()) + return; + DEV9_LOG_VERB("HDD_ReadVerifySectors\n"); + + IDE_CmdLBA48Transform(isLBA48); + + HDD_CanAssessOrSetError(); + + PostCmdNoData(); +} + +void ATA::HDD_SeekCmd() +{ + if (!PreCmd()) + return; + DEV9_LOG_INFO("HDD_SeekCmd\n"); + + regStatus &= ~ATA_STAT_SEEK; + + if (HDD_CanSeek()) + { + regStatus |= ATA_STAT_ERR; + regError |= ATA_ERR_ID; + } + else + regStatus |= ATA_STAT_SEEK; + + PostCmdNoData(); +} + +void ATA::HDD_SetFeatures() +{ + if (!PreCmd()) + return; + DEV9_LOG_VERB("HDD_SetFeatures\n"); + + switch (regFeature) + { + case 0x02: + fetWriteCacheEnabled = true; + break; + case 0x82: + fetWriteCacheEnabled = false; + awaitFlush = true; //Flush Cache + return; + case 0x03: //Set transfer mode + { + const u16 xferMode = (u16)regNsector; //Set Transfer mode + + const int mode = xferMode & 0x07; + switch ((xferMode) >> 3) + { + case 0x00: //pio default + //if mode = 1, disable IORDY + DEV9_LOG_ERROR("PIO Default\n"); + pioMode = 4; + sdmaMode = -1; + mdmaMode = -1; + udmaMode = -1; + break; + case 0x01: //pio mode (3,4) + DEV9_LOG_ERROR("PIO Mode %i\n", mode); + pioMode = mode; + sdmaMode = -1; + mdmaMode = -1; + udmaMode = -1; + break; + case 0x02: //Single word dma mode (0,1,2) + DEV9_LOG_ERROR("SDMA Mode %i\n", mode); + //pioMode = -1; + sdmaMode = mode; + mdmaMode = -1; + udmaMode = -1; + break; + case 0x04: //Multi word dma mode (0,1,2) + DEV9_LOG_ERROR("MDMA Mode %i\n", mode); + //pioMode = -1; + sdmaMode = -1; + mdmaMode = mode; + udmaMode = -1; + break; + case 0x08: //Ulta dma mode (0,1,2,3,4,5,6) + DEV9_LOG_ERROR("UDMA Mode %i\n", mode); + //pioMode = -1; + sdmaMode = -1; + mdmaMode = -1; + udmaMode = mode; + break; + default: + DEV9_LOG_ERROR("Unkown transfer mode\n"); + CmdNoDataAbort(); + break; + } + } + break; + default: + DEV9_LOG_ERROR("Unkown feature mode\n"); + break; + } + PostCmdNoData(); +} + +void ATA::HDD_SetMultipleMode() +{ + if (!PreCmd()) + return; + DEV9_LOG_INFO("HDD_SetMultipleMode\n"); + + curMultipleSectorsSetting = regNsector; + + PostCmdNoData(); +} + +void ATA::HDD_Nop() +{ + if (!PreCmd()) + return; + + DEV9_LOG_INFO("HDD_Nop\n"); + + if (regFeature == 0) + { + //This would abort queues if the + //PS2 HDD supported them. + } + //Always ends in error + regError |= ATA_ERR_ABORT; + regStatus |= ATA_STAT_ERR; + PostCmdNoData(); +} + +//Other Feature Sets + +void ATA::HDD_Idle() +{ + if (!PreCmd()) + return; + + DEV9_LOG_VERB("HDD_Idle\n"); + + long idleTime = 0; //in seconds + if (regNsector >= 1 && regNsector <= 240) + idleTime = 5 * regNsector; + else if (regNsector >= 241 && regNsector <= 251) + idleTime = 30 * (regNsector - 240) * 60; + else + { + switch (regNsector) + { + case 0: + idleTime = 0; + break; + case 252: + idleTime = 21 * 60; + break; + case 253: //bettween 8 and 12 hrs + idleTime = 10 * 60 * 60; + break; + case 254: //reserved + idleTime = -1; + break; + case 255: + idleTime = 21 * 60 + 15; + break; + default: + idleTime = 0; + break; + } + } + + DEV9_LOG_VERB("HDD_Idle for %is\n", idleTime); + PostCmdNoData(); +} diff --git a/pcsx2/DEV9/ATA/Commands/ATA_CmdPIOData.cpp b/pcsx2/DEV9/ATA/Commands/ATA_CmdPIOData.cpp new file mode 100644 index 0000000000..5aa2188467 --- /dev/null +++ b/pcsx2/DEV9/ATA/Commands/ATA_CmdPIOData.cpp @@ -0,0 +1,155 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2020 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "../ATA.h" +#include "../../DEV9.h" + +void ATA::DRQCmdPIODataToHost(u8* buff, int buffLen, int buffIndex, int size, bool sendIRQ) +{ + //Data in PIO ready to be sent + pioPtr = 0; + pioEnd = size >> 1; + + memcpy(pioBuffer, &buff[buffIndex], size < (buffLen - buffIndex) ? size : (buffLen - buffIndex)); + + regStatus &= ~ATA_STAT_BUSY; + regStatus |= ATA_STAT_DRQ; + + if (regControlEnableIRQ && sendIRQ) + _DEV9irq(ATA_INTR_INTRQ, 1); //0x6c cycles before +} +void ATA::PostCmdPIODataToHost() +{ + pioPtr = 0; + pioEnd = 0; + //AnyMoreData? + if (pioDRQEndTransferFunc != nullptr) + { + regStatus |= ATA_STAT_BUSY; + regStatus &= ~ATA_STAT_DRQ; + //Call cmd to retrive more data + (this->*pioDRQEndTransferFunc)(); + } + else + regStatus &= ~ATA_STAT_DRQ; +} + +//FromHost +u16 ATA::ATAreadPIO() +{ + DEV9_LOG_VERB("*ATA_R_DATA 16bit read, pio_count %i, pio_size %i\n", pioPtr, pioEnd); + if (pioPtr < pioEnd) + { + const u16 ret = *(u16*)&pioBuffer[pioPtr * 2]; + DEV9_LOG_VERB("*ATA_R_DATA returned value is %x\n", ret); + pioPtr++; + if (pioPtr >= pioEnd) //Fnished transfer (Changed from MegaDev9) + PostCmdPIODataToHost(); + + return ret; + } + return 0xFF; +} +//ATAwritePIO + +void ATA::HDD_IdentifyDevice() +{ + if (!PreCmd()) + return; + DEV9_LOG_VERB("HddidentifyDevice\n"); + + //IDE transfer start + CreateHDDinfo(config.HddSize); + + pioDRQEndTransferFunc = nullptr; + DRQCmdPIODataToHost(identifyData, 256 * 2, 0, 256 * 2, true); +} + +//Read Buffer + +void ATA::HDD_ReadMultiple(bool isLBA48) +{ + sectorsPerInterrupt = curMultipleSectorsSetting; + HDD_ReadPIO(isLBA48); +} + +void ATA::HDD_ReadSectors(bool isLBA48) +{ + sectorsPerInterrupt = 1; + HDD_ReadPIO(isLBA48); +} + +void ATA::HDD_ReadPIO(bool isLBA48) +{ + //Log_Info("HDD_ReadPIO"); + if (!PreCmd()) + return; + + if (sectorsPerInterrupt == 0) + { + CmdNoDataAbort(); + return; + } + + IDE_CmdLBA48Transform(isLBA48); + + if (!HDD_CanSeek()) + { + regStatus |= ATA_STAT_ERR; + regError |= ATA_ERR_ID; + PostCmdNoData(); + return; + } + + HDD_ReadSync(&ATA::HDD_ReadPIOS2); +} + +void ATA::HDD_ReadPIOS2() +{ + //Log_Info("HDD_ReadPIO Stage 2"); + pioDRQEndTransferFunc = &ATA::HDD_ReadPIOEndBlock; + DRQCmdPIODataToHost(readBuffer, readBufferLen, 0, 256 * 2, true); +} + +void ATA::HDD_ReadPIOEndBlock() +{ + //Log_Info("HDD_ReadPIO End Block"); + rdTransferred += 512; + if (rdTransferred >= nsector * 512) + { + //Log_Info("HDD_ReadPIO Done"); + HDD_SetErrorAtTransferEnd(); + regStatus &= ~ATA_STAT_BUSY; + pioDRQEndTransferFunc = nullptr; + rdTransferred = 0; + } + else + { + if ((rdTransferred / 512) % sectorsPerInterrupt == 0) + DRQCmdPIODataToHost(readBuffer, readBufferLen, rdTransferred, 256 * 2, true); + else + DRQCmdPIODataToHost(readBuffer, readBufferLen, rdTransferred, 256 * 2, false); + } +} + +//Write Buffer + +//Write Multiple + +//Write Sectors + +//Download Microcode (Used for FW updates) diff --git a/pcsx2/DEV9/ATA/Commands/ATA_CmdSMART.cpp b/pcsx2/DEV9/ATA/Commands/ATA_CmdSMART.cpp new file mode 100644 index 0000000000..2883eb930b --- /dev/null +++ b/pcsx2/DEV9/ATA/Commands/ATA_CmdSMART.cpp @@ -0,0 +1,157 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2020 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "../ATA.h" +#include "../../DEV9.h" + +void ATA::HDD_Smart() +{ + DEV9_LOG_VERB("HDD_Smart\n"); + + if ((regStatus & ATA_STAT_READY) == 0) + return; + + if (regHcyl != 0xC2 || regLcyl != 0x4F) + { + CmdNoDataAbort(); + return; + } + + if (!fetSmartEnabled && regFeature != 0xD8) + { + CmdNoDataAbort(); + return; + } + + switch (regFeature) + { + case 0xD9: //SMART_DISABLE + SMART_EnableOps(false); + return; + case 0xD8: //SMART_ENABLE + SMART_EnableOps(true); + return; + case 0xD2: //SMART_ATTR_AUTOSAVE + SMART_SetAutoSaveAttribute(); + return; + case 0xD3: //SMART_ATTR_SAVE + return; + case 0xDA: //SMART_STATUS (is fault in disk?) + SMART_ReturnStatus(); + return; + case 0xD1: //SMART_READ_THRESH + DEV9_LOG_ERROR("DEV9 : SMART_READ_THRESH Not Impemented\n"); + CmdNoDataAbort(); + return; + case 0xD0: //SMART_READ_DATA + DEV9_LOG_ERROR("DEV9 : SMART_READ_DATA Not Impemented\n"); + CmdNoDataAbort(); + return; + case 0xD5: //SMART_READ_LOG + DEV9_LOG_ERROR("DEV9 : SMART_READ_LOG Not Impemented\n"); + CmdNoDataAbort(); + return; + case 0xD4: //SMART_EXECUTE_OFFLINE + SMART_ExecuteOfflineImmediate(); + return; + default: + DEV9_LOG_ERROR("DEV9 : Unknown SMART command %x\n", regFeature); + CmdNoDataAbort(); + return; + } +} + +void ATA::SMART_SetAutoSaveAttribute() +{ + PreCmd(); + switch (regSector) + { + case 0x00: + smartAutosave = false; + break; + case 0xF1: + smartAutosave = true; + break; + default: + DEV9_LOG_ERROR("DEV9 : Unknown SMART_ATTR_AUTOSAVE command %s\n", regSector); + CmdNoDataAbort(); + return; + } + PostCmdNoData(); +} + +void ATA::SMART_ExecuteOfflineImmediate() +{ + PreCmd(); + int n = 0; + switch (regSector) + { + case 0: /* off-line routine */ + case 1: /* short self test */ + case 2: /* extended self test */ + smartSelfTestCount++; + if (smartSelfTestCount > 21) + smartSelfTestCount = 1; + + n = 2 + (smartSelfTestCount - 1) * 24; + //s->smart_selftest_data[n] = s->sector; + //s->smart_selftest_data[n + 1] = 0x00; /* OK and finished */ + //s->smart_selftest_data[n + 2] = 0x34; /* hour count lsb */ + //s->smart_selftest_data[n + 3] = 0x12; /* hour count msb */ + break; + case 127: /* abort off-line routine */ + break; + case 129: /* short self test, which holds BSY until complete */ + case 130: /* extended self test, which holds BSY until complete */ + smartSelfTestCount++; + if (smartSelfTestCount > 21) + { + smartSelfTestCount = 1; + } + n = 2 + (smartSelfTestCount - 1) * 24; + + SMART_ReturnStatus(); + return; + default: + CmdNoDataAbort(); + return; + } + PostCmdNoData(); +} + +void ATA::SMART_EnableOps(bool enable) +{ + PreCmd(); + fetSmartEnabled = enable; + PostCmdNoData(); +} + +void ATA::SMART_ReturnStatus() +{ + PreCmd(); + if (!smartErrors) + { + regHcyl = 0xC2; + regLcyl = 0x4F; + } + else + { + regHcyl = 0x2C; + regLcyl = 0xF4; + } + PostCmdNoData(); +} diff --git a/pcsx2/DEV9/ATA/Commands/ATA_Command.cpp b/pcsx2/DEV9/ATA/Commands/ATA_Command.cpp new file mode 100644 index 0000000000..f15095448a --- /dev/null +++ b/pcsx2/DEV9/ATA/Commands/ATA_Command.cpp @@ -0,0 +1,183 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2020 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "../ATA.h" +#include "../../DEV9.h" + +void ATA::IDE_ExecCmd(u16 value) +{ + switch (value) + { + case 0x00: + HDD_Nop(); + break; + case 0x20: + HDD_ReadSectors(false); + break; + //0x21 + case 0x40: + HDD_ReadVerifySectors(false); + break; + //0x41 + case 0x70: + HDD_SeekCmd(); + break; + case 0x90: + HDD_ExecuteDeviceDiag(); + break; + case 0x91: + HDD_InitDevParameters(); + break; + case 0xB0: + HDD_Smart(); + break; + case 0xC4: + HDD_ReadMultiple(false); + break; + case 0xC8: + HDD_ReadDMA(false); + break; + //0xC9 + case 0xCA: + HDD_WriteDMA(false); + break; + //0xCB + //0x25 = HDDreadDMA48; + //0x35 = HDDwriteDMA48;*/ + case 0xE3: + HDD_Idle(); + break; + case 0xE7: + HDD_FlushCache(); + break; + //0xEA = HDDflushCache48 + case 0xEC: + HDD_IdentifyDevice(); + break; + //0xA1 = HDDidentifyPktDevice + case 0xEF: + HDD_SetFeatures(); + break; + + //0xF1 = HDDsecSetPassword + //0xF2 = HDDsecUnlock + //0xF3 = HDDsecErasePrepare; + //0xF4 = HDDsecEraseUnit; + + /* This command is Sony-specific and isn't part of the IDE standard */ + /* The Sony HDD has a modified firmware that supports this command */ + /* Sending this command to a standard HDD will give an error */ + /* We roughly emulate it to make programs think the HDD is a Sony one */ + /* However, we only send null, if anyting checks the returned data */ + /* it will fail */ + case 0x8E: + HDD_SCE(); + break; + + default: + HDD_Unk(); + break; + } +} + +void ATA::HDD_Unk() +{ + DEV9_LOG_ERROR("DEV9 HDD error : unknown cmd %x\n", regCommand); + + PreCmd(); + + regError |= ATA_ERR_ABORT; + regStatus |= ATA_STAT_ERR; + PostCmdNoData(); +} + +bool ATA::PreCmd() +{ + if ((regStatus & ATA_STAT_READY) == 0) + { + //Ignore CMD write except for EXECUTE DEVICE DIAG and INITIALIZE DEVICE PARAMETERS + return false; + } + regStatus |= ATA_STAT_BUSY; + + regStatus &= ~ATA_STAT_WRERR; + regStatus &= ~ATA_STAT_DRQ; + regStatus &= ~ATA_STAT_ERR; + + regStatus &= ~ATA_STAT_SEEK; + + regError = 0; + + return true; +} + +void ATA::IDE_CmdLBA48Transform(bool islba48) +{ + lba48 = islba48; + //TODO + /* handle the 'magic' 0 nsector count conversion here. to avoid + * fiddling with the rest of the read logic, we just store the + * full sector count in ->nsector + */ + if (!lba48) + { + if (regNsector == 0) + nsector = 256; + else + nsector = regNsector; + } + else + { + if (regNsector == 0 && regNsectorHOB == 0) + nsector = 65536; + else + { + const int lo = regNsector; + const int hi = regNsectorHOB; + + nsector = (hi << 8) | lo; + } + } +} + +//OTHER FEATURE SETS BELOW (TODO?) + +//CFA ERASE SECTORS +//WRITE MULTIPLE +//SET MULTIPLE + +//CFA WRITE MULTIPLE WITHOUT ERASE +//GET MEDIA STATUS +//MEDIA LOCK +//MEDIA UNLOCK +//STANDBY IMMEDIAYTE +//IDLE IMMEDIATE +//STANBY + +//CHECK POWER MODE +//SLEEP + +//MEDIA EJECT + +//SECURITY SET PASSWORD +//SECURITY UNLOCK +//SECUTIRY ERASE PREPARE +//SECURITY ERASE UNIT +//SECURITY FREEZE LOCK +//SECURITY DIABLE PASSWORD +//READ NATIVE MAX ADDRESS +//SET MAX ADDRESS diff --git a/pcsx2/DEV9/ATA/Commands/ATA_SCE.cpp b/pcsx2/DEV9/ATA/Commands/ATA_SCE.cpp new file mode 100644 index 0000000000..907e9c075d --- /dev/null +++ b/pcsx2/DEV9/ATA/Commands/ATA_SCE.cpp @@ -0,0 +1,56 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2020 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "../ATA.h" +#include "../../DEV9.h" + +void ATA::HDD_SCE() +{ + DEV9_LOG_INFO("DEV9 : SONY-SPECIFIC SECURITY CONTROL COMMAND %x\n", regFeature); + + switch (regFeature) + { + case 0xEC: + SCE_IDENTIFY_DRIVE(); + break; + default: + DEV9_LOG_ERROR("DEV9 : Unknown SCE command %x\n", regFeature); + CmdNoDataAbort(); + return; + } +} +//Has +//ATA_SCE_IDENTIFY_DRIVE @ 0xEC + +//ATA_SCE_SECURITY_ERASE_PREPARE @ 0xF1 +//ATA_SCE_SECURITY_ERASE_UNIT +//ATA_SCE_SECURITY_FREEZE_LOCK +//ATA_SCE_SECURITY_SET_PASSWORD +//ATA_SCE_SECURITY_UNLOCK + +//ATA_SCE_SECURITY_WRITE_ID @ 0x20 +//ATA_SCE_SECURITY_READ_ID @ 0x30 + +void ATA::SCE_IDENTIFY_DRIVE() +{ + PreCmd(); + + //HDD_IdentifyDevice(); //Maybe? + + pioDRQEndTransferFunc = nullptr; + DRQCmdPIODataToHost(sceSec, 256 * 2, 0, 256 * 2, true); +} diff --git a/pcsx2/DEV9/ATA/HddCreate.cpp b/pcsx2/DEV9/ATA/HddCreate.cpp new file mode 100644 index 0000000000..93b2648e27 --- /dev/null +++ b/pcsx2/DEV9/ATA/HddCreate.cpp @@ -0,0 +1,107 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2020 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include +#include "HddCreate.h" + +void HddCreate::Start() +{ + fileThread = std::thread(&HddCreate::WriteImage, this, filePath, neededSize); + fileThread.join(); +} + +void HddCreate::WriteImage(ghc::filesystem::path hddPath, int reqSizeMiB) +{ + constexpr int buffsize = 4 * 1024; + u8 buff[buffsize] = {0}; //4kb + + if (ghc::filesystem::exists(hddPath)) + { + SetError(); + return; + } + + std::fstream newImage = ghc::filesystem::fstream(hddPath, std::ios::out | std::ios::binary); + + if (newImage.fail()) + { + SetError(); + return; + } + + //Size file + newImage.seekp(((u64)reqSizeMiB) * 1024 * 1024 - 1, std::ios::beg); + const char zero = 0; + newImage.write(&zero, 1); + + if (newImage.fail()) + { + newImage.close(); + ghc::filesystem::remove(filePath); + SetError(); + return; + } + + lastUpdate = std::chrono::steady_clock::now(); + + newImage.seekp(0, std::ios::beg); + + for (int iMiB = 0; iMiB < reqSizeMiB; iMiB++) + { + for (int i4kb = 0; i4kb < 256; i4kb++) + { + newImage.write((char*)buff, buffsize); + if (newImage.fail()) + { + newImage.close(); + ghc::filesystem::remove(filePath); + SetError(); + return; + } + } + SetFileProgress(iMiB + 1); + } + newImage.flush(); + newImage.close(); + + SetDone(); +} + +void HddCreate::SetFileProgress(int currentSize) +{ + std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + + if (std::chrono::duration_cast(now - lastUpdate).count() >= 1) + { + lastUpdate = now; + fprintf(stdout, "%i / %i MiB\n", currentSize, neededSize); + } +} + +void HddCreate::SetError() +{ + fprintf(stderr, "Unable to create file\n"); + errored.store(true); + completed.store(true); +} + +void HddCreate::SetDone() +{ + fprintf(stdout, "%i / %i MiB\n", neededSize, neededSize); + fprintf(stdout, "Done\n"); + completed.store(true); +} \ No newline at end of file diff --git a/pcsx2/DEV9/ata.h b/pcsx2/DEV9/ATA/HddCreate.h similarity index 59% rename from pcsx2/DEV9/ata.h rename to pcsx2/DEV9/ATA/HddCreate.h index e69f7ec57f..ec4139b1c3 100644 --- a/pcsx2/DEV9/ata.h +++ b/pcsx2/DEV9/ATA/HddCreate.h @@ -14,17 +14,33 @@ */ #pragma once -#include "DEV9.h" -void ata_init(); -void ata_term(); +#include +#include +#include +#include +#include "ghc/filesystem.h" -template -void ata_write(u32 addr, u32 value); -template -u8 ata_read(u32 addr); +class HddCreate +{ +public: + ghc::filesystem::path filePath; + int neededSize; -EXPORT_C_(void) -ata_readDMA8Mem(u32* pMem, int size); -EXPORT_C_(void) -ata_writeDMA8Mem(u32* pMem, int size); + std::atomic_bool errored{false}; + +private: + std::thread fileThread; + std::atomic_bool completed{false}; + + std::chrono::steady_clock::time_point lastUpdate; + +public: + void Start(); + +private: + void SetFileProgress(int currentSize); + void SetError(); + void SetDone(); + void WriteImage(ghc::filesystem::path hddPath, int reqSizeMB); +}; diff --git a/pcsx2/windows/VCprojects/pcsx2.vcxproj b/pcsx2/windows/VCprojects/pcsx2.vcxproj index d2eb1657c6..f68856007e 100644 --- a/pcsx2/windows/VCprojects/pcsx2.vcxproj +++ b/pcsx2/windows/VCprojects/pcsx2.vcxproj @@ -267,6 +267,17 @@ + + + + + + + + + + + @@ -628,7 +639,8 @@ - + + diff --git a/pcsx2/windows/VCprojects/pcsx2.vcxproj.filters b/pcsx2/windows/VCprojects/pcsx2.vcxproj.filters index 2f7e85bab9..1c13e4cf41 100644 --- a/pcsx2/windows/VCprojects/pcsx2.vcxproj.filters +++ b/pcsx2/windows/VCprojects/pcsx2.vcxproj.filters @@ -172,6 +172,12 @@ {8d5454f9-590c-4c53-aae1-8391c6465e50} + + {bc20f567-851d-4440-a3fd-ef470241962e} + + + {9e9b52d7-7b1c-44b2-82d8-1e0d8085e7e0} + {df9de75c-2272-4f73-b2a0-4f9f492ba1e9} @@ -1054,6 +1060,39 @@ System\Ps2\SPU2 + + System\Ps2\DEV9\ATA\Commands + + + System\Ps2\DEV9\ATA\Commands + + + System\Ps2\DEV9\ATA\Commands + + + System\Ps2\DEV9\ATA\Commands + + + System\Ps2\DEV9\ATA\Commands + + + System\Ps2\DEV9\ATA\Commands + + + System\Ps2\DEV9\ATA\Commands + + + System\Ps2\DEV9\ATA + + + System\Ps2\DEV9\ATA + + + System\Ps2\DEV9\ATA + + + System\Ps2\DEV9\ATA + System\Ps2\DEV9 @@ -1788,8 +1827,11 @@ Recording\Utilities - - System\Ps2\DEV9 + + System\Ps2\DEV9\ATA + + + System\Ps2\DEV9\ATA System\Ps2\DEV9