Merge pull request #8394 from Pokechu22/misc-di-gpio

Various DI improvements
This commit is contained in:
JosJuice 2020-01-13 17:17:24 +01:00 committed by GitHub
commit 966e1b31ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1554 additions and 517 deletions

View File

@ -8,6 +8,7 @@
#include <climits>
#include <cstddef>
#include <cstring>
#include <initializer_list>
#include <type_traits>
namespace Common
@ -299,4 +300,44 @@ void SetBit(T& value, size_t bit_number, bool bit_value)
value &= ~(T{1} << bit_number);
}
template <typename T>
class FlagBit
{
public:
FlagBit(std::underlying_type_t<T>& bits, T bit) : m_bits(bits), m_bit(bit) {}
explicit operator bool() const
{
return (m_bits & static_cast<std::underlying_type_t<T>>(m_bit)) != 0;
}
FlagBit& operator=(const bool rhs)
{
if (rhs)
m_bits |= static_cast<std::underlying_type_t<T>>(m_bit);
else
m_bits &= ~static_cast<std::underlying_type_t<T>>(m_bit);
return *this;
}
private:
std::underlying_type_t<T>& m_bits;
T m_bit;
};
template <typename T>
class Flags
{
public:
constexpr Flags() = default;
constexpr Flags(std::initializer_list<T> bits)
{
for (auto bit : bits)
{
m_hex |= static_cast<std::underlying_type_t<T>>(bit);
}
}
FlagBit<T> operator[](T bit) { return FlagBit(m_hex, bit); }
std::underlying_type_t<T> m_hex = 0;
};
} // namespace Common

View File

@ -19,6 +19,7 @@
#include <deque>
#include <list>
#include <map>
#include <optional>
#include <set>
#include <string>
#include <type_traits>
@ -144,6 +145,36 @@ public:
Do(x.second);
}
template <typename T>
void Do(std::optional<T>& x)
{
bool present = x.has_value();
Do(present);
switch (mode)
{
case MODE_READ:
if (present)
{
x = std::make_optional<T>();
Do(x.value());
}
else
{
x = std::nullopt;
}
break;
case MODE_WRITE:
case MODE_MEASURE:
case MODE_VERIFY:
if (present)
Do(x.value());
break;
}
}
template <typename T, std::size_t N>
void DoArray(std::array<T, N>& x)
{

View File

@ -132,9 +132,16 @@ void DolphinAnalytics::ReportGameStart()
}
// Keep in sync with enum class GameQuirk definition.
constexpr std::array<const char*, 2> GAME_QUIRKS_NAMES{
constexpr std::array<const char*, 9> GAME_QUIRKS_NAMES{
"icache-matters",
"directly-reads-wiimote-input",
"uses-DVDLowStopLaser",
"uses-DVDLowOffset",
"uses-DVDLowReadDiskBca",
"uses-DVDLowRequestDiscStatus",
"uses-DVDLowRequestRetryNumber",
"uses-DVDLowSerMeasControl",
"uses-different-partition-command",
};
static_assert(GAME_QUIRKS_NAMES.size() == static_cast<u32>(GameQuirk::COUNT),
"Game quirks names and enum definition are out of sync.");

View File

@ -29,6 +29,18 @@ enum class GameQuirk
// "read" extension or IR data. This would break our current TAS/NetPlay implementation.
DIRECTLY_READS_WIIMOTE_INPUT,
// Several Wii DI commands that are rarely/never used and not implemented by Dolphin
USES_DVD_LOW_STOP_LASER,
USES_DVD_LOW_OFFSET,
USES_DVD_LOW_READ_DISK_BCA, // NSMBW known to use this
USES_DVD_LOW_REQUEST_DISC_STATUS,
USES_DVD_LOW_REQUEST_RETRY_NUMBER,
USES_DVD_LOW_SER_MEAS_CONTROL,
// Dolphin only implements the simple DVDLowOpenPartition, not any of the variants where some
// already-read data is provided
USES_DIFFERENT_PARTITION_COMMAND,
COUNT,
};

View File

@ -244,6 +244,17 @@ bool CBoot::DVDRead(const DiscIO::VolumeDisc& disc, u64 dvd_offset, u32 output_a
return true;
}
bool CBoot::DVDReadDiscID(const DiscIO::VolumeDisc& disc, u32 output_address)
{
std::array<u8, 0x20> buffer;
if (!disc.Read(0, buffer.size(), buffer.data(), DiscIO::PARTITION_NONE))
return false;
Memory::CopyToEmu(output_address, buffer.data(), buffer.size());
// Clear ERROR_NO_DISKID_L, probably should check if that's currently set
DVDInterface::SetLowError(DVDInterface::ERROR_READY);
return true;
}
void CBoot::UpdateDebugger_MapLoaded()
{
Host_NotifyMapLoaded();

View File

@ -104,6 +104,7 @@ public:
private:
static bool DVDRead(const DiscIO::VolumeDisc& disc, u64 dvd_offset, u32 output_address,
u32 length, const DiscIO::Partition& partition);
static bool DVDReadDiscID(const DiscIO::VolumeDisc& disc, u32 output_address);
static void RunFunction(u32 address);
static void UpdateDebugger_MapLoaded();

View File

@ -19,8 +19,10 @@
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/HLE/HLE.h"
#include "Core/HW/DVD/DVDInterface.h"
#include "Core/HW/EXI/EXI_DeviceIPL.h"
#include "Core/HW/Memmap.h"
#include "Core/IOS/DI/DI.h"
#include "Core/IOS/ES/ES.h"
#include "Core/IOS/ES/Formats.h"
#include "Core/IOS/FS/FileSystem.h"
@ -209,7 +211,22 @@ bool CBoot::EmulatedBS2_GC(const DiscIO::VolumeDisc& volume)
SetupGCMemory();
DVDRead(volume, /*offset*/ 0x00000000, /*address*/ 0x00000000, 0x20, DiscIO::PARTITION_NONE);
DVDReadDiscID(volume, 0x00000000);
bool streaming = Memory::Read_U8(0x80000008);
if (streaming)
{
u8 streaming_size = Memory::Read_U8(0x80000009);
// If the streaming buffer size is 0, then BS2 uses a default size of 10 instead.
// No known game uses a size other than the default.
if (streaming_size == 0)
streaming_size = 10;
DVDInterface::AudioBufferConfig(true, streaming_size);
}
else
{
DVDInterface::AudioBufferConfig(false, 0);
}
const bool ntsc = DiscIO::IsNTSC(SConfig::GetInstance().m_region);
@ -390,6 +407,9 @@ bool CBoot::EmulatedBS2_Wii(const DiscIO::VolumeDisc& volume)
state->discstate = 0x01;
});
// The system menu clears the RTC flags
ExpansionInterface::g_rtc_flags.m_hex = 0;
// While reading a disc, the system menu reads the first partition table
// (0x20 bytes from 0x00040020) and stores a pointer to the data partition entry.
// When launching the disc game, it copies the partition type and offset to 0x3194
@ -402,7 +422,13 @@ bool CBoot::EmulatedBS2_Wii(const DiscIO::VolumeDisc& volume)
if (!SetupWiiMemory(console_type) || !IOS::HLE::GetIOS()->BootIOS(tmd.GetIOSId()))
return false;
DVDRead(volume, 0x00000000, 0x00000000, 0x20, DiscIO::PARTITION_NONE); // Game Code
auto di = std::static_pointer_cast<IOS::HLE::Device::DI>(
IOS::HLE::GetIOS()->GetDeviceByName("/dev/di"));
di->InitializeIfFirstTime();
di->ChangePartition(data_partition);
DVDReadDiscID(volume, 0x00000000);
// This is some kind of consistency check that is compared to the 0x00
// values as the game boots. This location keeps the 4 byte ID for as long

File diff suppressed because it is too large Load Diff

View File

@ -24,46 +24,67 @@ class Mapping;
namespace DVDInterface
{
enum DICommand
enum class DICommand : u8
{
DVDLowInquiry = 0x12,
DVDLowReadDiskID = 0x70,
DVDLowRead = 0x71,
DVDLowWaitForCoverClose = 0x79,
DVDLowGetCoverReg = 0x7a, // DVDLowPrepareCoverRegister?
DVDLowNotifyReset = 0x7e,
DVDLowReadDvdPhysical = 0x80,
DVDLowReadDvdCopyright = 0x81,
DVDLowReadDvdDiscKey = 0x82,
DVDLowClearCoverInterrupt = 0x86,
DVDLowGetCoverStatus = 0x88,
DVDLowReset = 0x8a,
DVDLowOpenPartition = 0x8b,
DVDLowClosePartition = 0x8c,
DVDLowUnencryptedRead = 0x8d,
DVDLowEnableDvdVideo = 0x8e,
DVDLowReportKey = 0xa4,
DVDLowSeek = 0xab,
DVDLowReadDvd = 0xd0,
DVDLowReadDvdConfig = 0xd1,
DVDLowStopLaser = 0xd2,
DVDLowOffset = 0xd9,
DVDLowReadDiskBca = 0xda,
DVDLowRequestDiscStatus = 0xdb,
DVDLowRequestRetryNumber = 0xdc,
DVDLowSetMaximumRotation = 0xdd,
DVDLowSerMeasControl = 0xdf,
DVDLowRequestError = 0xe0,
DVDLowStopMotor = 0xe3,
DVDLowAudioBufferConfig = 0xe4
Inquiry = 0x12,
ReportKey = 0xa4,
Read = 0xa8,
Seek = 0xab,
ReadDVDMetadata = 0xad,
ReadDVD = 0xd0,
ReadDVDConfig = 0xd1,
StopLaser = 0xd2,
Offset = 0xd9,
ReadBCA = 0xda,
RequestDiscStatus = 0xdb,
RequestRetryNumber = 0xdc,
SetMaximumRotation = 0xdd,
SerMeasControl = 0xdf,
RequestError = 0xe0,
AudioStream = 0xe1,
RequestAudioStatus = 0xe2,
StopMotor = 0xe3,
AudioBufferConfig = 0xe4,
Debug = 0xfe,
DebugUnlock = 0xff,
Unknown55 = 0x55,
UnknownEE = 0xee,
};
enum DIInterruptType : int
// "low" error codes
constexpr u32 ERROR_READY = 0x0000000; // Ready.
constexpr u32 ERROR_COVER = 0x01000000; // Cover is opened.
constexpr u32 ERROR_CHANGE_DISK = 0x02000000; // Disk change.
constexpr u32 ERROR_NO_DISK_L = 0x03000000; // No disk.
constexpr u32 ERROR_MOTOR_STOP_L = 0x04000000; // Motor stop.
constexpr u32 ERROR_NO_DISKID_L = 0x05000000; // Disk ID not read.
constexpr u32 LOW_ERROR_MASK = 0xff000000;
// "high" error codes
constexpr u32 ERROR_NONE = 0x000000; // No error.
constexpr u32 ERROR_MOTOR_STOP_H = 0x020400; // Motor stopped.
constexpr u32 ERROR_NO_DISKID_H = 0x020401; // Disk ID not read.
constexpr u32 ERROR_NO_DISK_H = 0x023a00; // Medium not present / Cover opened.
constexpr u32 ERROR_SEEK_NDONE = 0x030200; // No seek complete.
constexpr u32 ERROR_READ = 0x031100; // Unrecovered read error.
constexpr u32 ERROR_PROTOCOL = 0x040800; // Transfer protocol error.
constexpr u32 ERROR_INV_CMD = 0x052000; // Invalid command operation code.
constexpr u32 ERROR_AUDIO_BUF = 0x052001; // Audio Buffer not set.
constexpr u32 ERROR_BLOCK_OOB = 0x052100; // Logical block address out of bounds.
constexpr u32 ERROR_INV_FIELD = 0x052400; // Invalid field in command packet.
constexpr u32 ERROR_INV_AUDIO = 0x052401; // Invalid audio command.
constexpr u32 ERROR_INV_PERIOD = 0x052402; // Configuration out of permitted period.
constexpr u32 ERROR_END_USR_AREA = 0x056300; // End of user area encountered on this track.
constexpr u32 ERROR_MEDIUM = 0x062800; // Medium may have changed.
constexpr u32 ERROR_MEDIUM_REQ = 0x0b5a01; // Operator medium removal request.
constexpr u32 HIGH_ERROR_MASK = 0x00ffffff;
enum class DIInterruptType : int
{
INT_DEINT = 0,
INT_TCINT = 1,
INT_BRKINT = 2,
INT_CVRINT = 3,
DEINT = 0,
TCINT = 1,
BRKINT = 2,
CVRINT = 3,
};
enum class ReplyType : u32
@ -71,11 +92,17 @@ enum class ReplyType : u32
NoReply,
Interrupt,
IOS,
DTK
DTK,
};
enum class EjectCause
{
User,
Software,
};
void Init();
void Reset();
void Reset(bool spinup = true);
void Shutdown();
void DoState(PointerWrap& p);
@ -84,7 +111,7 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base);
void SetDisc(std::unique_ptr<DiscIO::VolumeDisc> disc,
std::optional<std::vector<std::string>> auto_disc_change_paths);
bool IsDiscInside();
void EjectDisc(); // Must only be called on the CPU thread
void EjectDisc(EjectCause cause); // Must only be called on the CPU thread
void ChangeDisc(const std::vector<std::string>& paths); // Must only be called on the CPU thread
void ChangeDisc(const std::string& new_path); // Must only be called on the CPU thread
bool AutoChangeDisc(); // Must only be called on the CPU thread
@ -97,12 +124,21 @@ bool UpdateRunningGameMetadata(std::optional<u64> title_id = {});
// Direct access to DI for IOS HLE (simpler to implement than how real IOS accesses DI,
// and lets us skip encrypting/decrypting in some cases)
void ChangePartition(const DiscIO::Partition& partition);
void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_address,
u32 output_length, bool reply_to_ios);
void ExecuteCommand(ReplyType reply_type);
void PerformDecryptingRead(u32 position, u32 length, u32 output_address,
const DiscIO::Partition& partition, ReplyType reply_type);
// Exposed for use by emulated BS2; does not perform any checks on drive state
void AudioBufferConfig(bool enable_dtk, u8 dtk_buffer_length);
void SetLowError(u32 low_error);
void SetHighError(u32 high_error);
// Used by DVDThread
void FinishExecutingCommand(ReplyType reply_type, DIInterruptType interrupt_type, s64 cycles_late,
const std::vector<u8>& data = std::vector<u8>());
// Used by IOS HLE
void SetInterruptEnabled(DIInterruptType interrupt, bool enabled);
void ClearInterrupt(DIInterruptType interrupt);
} // end of namespace DVDInterface

View File

@ -342,20 +342,34 @@ static void FinishRead(u64 id, s64 cycles_late)
(CoreTiming::GetTicks() - request.time_started_ticks) /
(SystemTimers::GetTicksPerSecond() / 1000000));
DVDInterface::DIInterruptType interrupt;
if (buffer.size() != request.length)
{
PanicAlertT("The disc could not be read (at 0x%" PRIx64 " - 0x%" PRIx64 ").",
request.dvd_offset, request.dvd_offset + request.length);
if (request.dvd_offset != 0x118280000 && request.dvd_offset != 0x1FB500000)
{
PanicAlertT("The disc could not be read (at 0x%" PRIx64 " - 0x%" PRIx64 ").",
request.dvd_offset, request.dvd_offset + request.length);
}
else
{
// Part of the error 001 check.
INFO_LOG(DVDINTERFACE, "Ignoring out of bounds test read (at 0x%" PRIx64 " - 0x%" PRIx64 ")",
request.dvd_offset, request.dvd_offset + request.length);
}
DVDInterface::SetHighError(DVDInterface::ERROR_BLOCK_OOB);
interrupt = DVDInterface::DIInterruptType::DEINT;
}
else
{
if (request.copy_to_ram)
Memory::CopyToEmu(request.output_address, buffer.data(), request.length);
interrupt = DVDInterface::DIInterruptType::TCINT;
}
// Notify the emulated software that the command has been executed
DVDInterface::FinishExecutingCommand(request.reply_type, DVDInterface::INT_TCINT, cycles_late,
buffer);
DVDInterface::FinishExecutingCommand(request.reply_type, interrupt, cycles_late, buffer);
}
static void DVDThread()

View File

@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include "Core/HW/EXI/EXI_DeviceIPL.h"
#include "Core/HW/DVD/DVDInterface.h"
#include <cstring>
#include <string>
@ -43,6 +44,8 @@ static const char iplverPAL[0x100] = "(C) 1999-2001 Nintendo. All rights reserv
static const char iplverNTSC[0x100] = "(C) 1999-2001 Nintendo. All rights reserved."
"(C) 1999 ArtX Inc. All rights reserved.";
Common::Flags<RTCFlag> g_rtc_flags;
// bootrom descrambler reversed by segher
// Copyright 2008 Segher Boessenkool <segher@kernel.crashing.org>
void CEXIIPL::Descrambler(u8* data, u32 size)
@ -149,6 +152,7 @@ CEXIIPL::~CEXIIPL()
void CEXIIPL::DoState(PointerWrap& p)
{
p.Do(g_SRAM.rtc);
p.Do(g_rtc_flags);
p.Do(m_command);
p.Do(m_command_bytes_received);
p.Do(m_cursor);
@ -361,10 +365,12 @@ void CEXIIPL::TransferByte(u8& data)
break;
}
}
else if (IN_RANGE(WII_RTC))
else if (IN_RANGE(WII_RTC) && DEV_ADDR(WII_RTC) == 0x20)
{
// Wii only RTC flags... afaik only the Wii Menu initializes it
// Seems to be 4bytes at dev_addr 0x20
if (m_command.is_write())
g_rtc_flags.m_hex = data;
else
data = g_rtc_flags.m_hex;
}
else if (IN_RANGE(EUART))
{

View File

@ -7,6 +7,7 @@
#include <array>
#include <string>
#include "Common/BitUtils.h"
#include "Core/HW/EXI/EXI_Device.h"
class PointerWrap;
@ -78,4 +79,17 @@ private:
static std::string FindIPLDump(const std::string& path_prefix);
};
// Used to indicate disc changes on the Wii, as insane as that sounds.
// However, the name is definitely RTCFlag, as the code that gets it is __OSGetRTCFlags and
// __OSClearRTCFlags in OSRtc.o (based on symbols from Kirby's Dream Collection)
// This may simply be a single byte that gets repeated 4 times by some EXI quirk,
// as reading it gives the value repeated 4 times but code only checks the first bit.
enum class RTCFlag : u32
{
EjectButton = 0x01010101,
DiscChanged = 0x02020202,
};
extern Common::Flags<RTCFlag> g_rtc_flags;
} // namespace ExpansionInterface

View File

@ -8,6 +8,7 @@
#include "Common/CommonTypes.h"
#include "Common/Logging/Log.h"
#include "Core/CoreTiming.h"
#include "Core/HW/DVD/DVDInterface.h"
#include "Core/HW/MMIO.h"
#include "Core/HW/ProcessorInterface.h"
#include "Core/IOS/IOS.h"
@ -97,7 +98,11 @@ static u32 ppc_irq_masks;
static u32 arm_irq_flags;
static u32 arm_irq_masks;
static u32 sensorbar_power; // do we need to care about this?
// Indicates which pins are accessible by broadway. Writable by starlet only.
static constexpr Common::Flags<GPIO> gpio_owner = {GPIO::SLOT_LED, GPIO::SLOT_IN, GPIO::SENSOR_BAR,
GPIO::DO_EJECT, GPIO::AVE_SCL, GPIO::AVE_SDA};
static Common::Flags<GPIO> gpio_dir;
Common::Flags<GPIO> g_gpio_out;
static CoreTiming::EventType* updateInterrupts;
static void UpdateInterrupts(u64 = 0, s64 cyclesLate = 0);
@ -111,7 +116,7 @@ void DoState(PointerWrap& p)
p.Do(ppc_irq_masks);
p.Do(arm_irq_flags);
p.Do(arm_irq_masks);
p.Do(sensorbar_power);
p.Do(g_gpio_out);
}
static void InitState()
@ -125,7 +130,9 @@ static void InitState()
arm_irq_flags = 0;
arm_irq_masks = 0;
sensorbar_power = 0;
// The only input broadway has is SLOT_IN; all the others it has access to are outputs
gpio_dir = {GPIO::SLOT_LED, GPIO::SENSOR_BAR, GPIO::DO_EJECT, GPIO::AVE_SCL, GPIO::AVE_SDA};
g_gpio_out = {};
ppc_irq_masks |= INT_CAUSE_IPC_BROADWAY;
}
@ -181,14 +188,29 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base)
CoreTiming::ScheduleEvent(0, updateInterrupts, 0);
}));
mmio->Register(base | GPIOB_OUT, MMIO::Constant<u32>(0),
MMIO::DirectWrite<u32>(&sensorbar_power));
mmio->Register(base | GPIOB_OUT, MMIO::DirectRead<u32>(&g_gpio_out.m_hex),
MMIO::ComplexWrite<u32>([](u32, u32 val) {
g_gpio_out.m_hex = val & gpio_owner.m_hex;
if (g_gpio_out[GPIO::DO_EJECT])
{
INFO_LOG(WII_IPC, "Ejecting disc due to GPIO write");
DVDInterface::EjectDisc(DVDInterface::EjectCause::Software);
}
// SENSOR_BAR is checked by WiimoteEmu::CameraLogic
// TODO: AVE, SLOT_LED
}));
mmio->Register(base | GPIOB_DIR, MMIO::DirectRead<u32>(&gpio_dir.m_hex),
MMIO::DirectWrite<u32>(&gpio_dir.m_hex));
mmio->Register(base | GPIOB_IN, MMIO::ComplexRead<u32>([](u32) {
Common::Flags<GPIO> gpio_in;
gpio_in[GPIO::SLOT_IN] = DVDInterface::IsDiscInside();
return gpio_in.m_hex;
}),
MMIO::Nop<u32>());
// Register some stubbed/unknown MMIOs required to make Wii games work.
mmio->Register(base | PPCSPEED, MMIO::InvalidRead<u32>(), MMIO::Nop<u32>());
mmio->Register(base | VISOLID, MMIO::InvalidRead<u32>(), MMIO::Nop<u32>());
mmio->Register(base | GPIOB_DIR, MMIO::Constant<u32>(0), MMIO::Nop<u32>());
mmio->Register(base | GPIOB_IN, MMIO::Constant<u32>(0), MMIO::Nop<u32>());
mmio->Register(base | UNK_180, MMIO::Constant<u32>(0), MMIO::Nop<u32>());
mmio->Register(base | UNK_1CC, MMIO::Constant<u32>(0), MMIO::Nop<u32>());
mmio->Register(base | UNK_1D0, MMIO::Constant<u32>(0), MMIO::Nop<u32>());

View File

@ -4,6 +4,7 @@
#pragma once
#include "Common/BitUtils.h"
#include "Common/CommonTypes.h"
class PointerWrap;
@ -35,6 +36,36 @@ enum StarletInterruptCause
INT_CAUSE_IPC_STARLET = 0x80000000
};
enum class GPIO : u32
{
POWER = 0x1,
SHUTDOWN = 0x2,
FAN = 0x4,
DC_DC = 0x8,
DI_SPIN = 0x10,
SLOT_LED = 0x20,
EJECT_BTN = 0x40,
SLOT_IN = 0x80,
SENSOR_BAR = 0x100,
DO_EJECT = 0x200,
EEP_CS = 0x400,
EEP_CLK = 0x800,
EEP_MOSI = 0x1000,
EEP_MISO = 0x2000,
AVE_SCL = 0x4000,
AVE_SDA = 0x8000,
DEBUG0 = 0x10000,
DEBUG1 = 0x20000,
DEBUG2 = 0x40000,
DEBUG3 = 0x80000,
DEBUG4 = 0x100000,
DEBUG5 = 0x200000,
DEBUG6 = 0x400000,
DEBUG7 = 0x800000,
};
extern Common::Flags<GPIO> g_gpio_out;
void Init();
void Reset();
void Shutdown();

View File

@ -12,6 +12,7 @@
#include "Common/MathUtil.h"
#include "Common/Matrix.h"
#include "Core/HW/WII_IPC.h"
#include "Core/HW/WiimoteCommon/WiimoteReport.h"
namespace WiimoteEmu
@ -103,23 +104,31 @@ void CameraLogic::Update(const Common::Matrix44& transform)
std::array<CameraPoint, leds.size()> camera_points;
std::transform(leds.begin(), leds.end(), camera_points.begin(), [&](const Vec3& v) {
const auto point = camera_view * Vec4(v, 1.0);
if (IOS::g_gpio_out[IOS::GPIO::SENSOR_BAR])
{
std::transform(leds.begin(), leds.end(), camera_points.begin(), [&](const Vec3& v) {
const auto point = camera_view * Vec4(v, 1.0);
if (point.z > 0)
{
// FYI: Casting down vs. rounding seems to produce more symmetrical output.
const auto x = s32((1 - point.x / point.w) * CAMERA_WIDTH / 2);
const auto y = s32((1 - point.y / point.w) * CAMERA_HEIGHT / 2);
if (point.z > 0)
{
// FYI: Casting down vs. rounding seems to produce more symmetrical output.
const auto x = s32((1 - point.x / point.w) * CAMERA_WIDTH / 2);
const auto y = s32((1 - point.y / point.w) * CAMERA_HEIGHT / 2);
const auto point_size = std::lround(MAX_POINT_SIZE / point.w / 2);
const auto point_size = std::lround(MAX_POINT_SIZE / point.w / 2);
if (x >= 0 && y >= 0 && x < CAMERA_WIDTH && y < CAMERA_HEIGHT)
return CameraPoint{u16(x), u16(y), u8(point_size)};
}
if (x >= 0 && y >= 0 && x < CAMERA_WIDTH && y < CAMERA_HEIGHT)
return CameraPoint{u16(x), u16(y), u8(point_size)};
}
return INVISIBLE_POINT;
});
return INVISIBLE_POINT;
});
}
else
{
// Sensor bar is off
camera_points.fill(INVISIBLE_POINT);
}
// IR data is read from offset 0x37 on real hardware
auto& data = reg_data.camera_data;

View File

@ -13,15 +13,37 @@
#include "Common/CommonTypes.h"
#include "Common/Logging/Log.h"
#include "Common/MsgHandler.h"
#include "Core/Analytics.h"
#include "Core/CoreTiming.h"
#include "Core/HW/DVD/DVDInterface.h"
#include "Core/HW/DVD/DVDThread.h"
#include "Core/HW/MMIO.h"
#include "Core/HW/Memmap.h"
#include "Core/IOS/ES/ES.h"
#include "Core/IOS/ES/Formats.h"
#include "DiscIO/Volume.h"
template <u32 addr>
class RegisterWrapper
{
public:
operator u32() const { return Memory::mmio_mapping->Read<u32>(addr); }
void operator=(u32 rhs) { Memory::mmio_mapping->Write(addr, rhs); }
};
static RegisterWrapper<0x0D806000> DISR;
static RegisterWrapper<0x0D806004> DICVR;
static RegisterWrapper<0x0D806008> DICMDBUF0;
static RegisterWrapper<0x0D80600C> DICMDBUF1;
static RegisterWrapper<0x0D806010> DICMDBUF2;
static RegisterWrapper<0x0D806014> DIMAR;
static RegisterWrapper<0x0D806018> DILENGTH;
static RegisterWrapper<0x0D80601C> DICR;
static RegisterWrapper<0x0D806020> DIIMMBUF;
namespace IOS::HLE::Device
{
CoreTiming::EventType* DI::s_finish_executing_di_command;
DI::DI(Kernel& ios, const std::string& device_name) : Device(ios, device_name)
{
}
@ -30,40 +52,50 @@ void DI::DoState(PointerWrap& p)
{
DoStateShared(p);
p.Do(m_commands_to_execute);
p.Do(m_executing_command);
p.Do(m_current_partition);
p.Do(m_has_initialized);
p.Do(m_last_length);
}
IPCCommandResult DI::Open(const OpenRequest& request)
{
InitializeIfFirstTime();
return Device::Open(request);
}
IPCCommandResult DI::IOCtl(const IOCtlRequest& request)
{
// DI IOCtls are handled in a special way by Dolphin
// compared to other IOS functions.
// This is a wrapper around DVDInterface's ExecuteCommand,
// which will execute commands more or less asynchronously.
// Only one command can be executed at a time, so commands
// are queued until DVDInterface is ready to handle them.
InitializeIfFirstTime();
bool ready_to_execute = m_commands_to_execute.empty();
// DI IOCtls are handled in a special way by Dolphin compared to other IOS functions.
// Many of them use DVDInterface's ExecuteCommand, which will execute commands more or less
// asynchronously. The rest are also treated as async to match this. Only one command can be
// executed at a time, so commands are queued until DVDInterface is ready to handle them.
const u8 command = Memory::Read_U8(request.buffer_in);
if (request.request != command)
{
WARN_LOG(
IOS_DI,
"IOCtl: Received conflicting commands: ioctl 0x%02x, buffer 0x%02x. Using ioctl command.",
request.request, command);
}
bool ready_to_execute = !m_executing_command.has_value();
m_commands_to_execute.push_back(request.address);
if (ready_to_execute)
StartIOCtl(request);
// DVDInterface handles the timing and will call FinishIOCtl after the command
// has been executed to reply to the request, so we shouldn't reply here.
if (ready_to_execute)
{
ProcessQueuedIOCtl();
}
// FinishIOCtl will be called after the command has been executed
// to reply to the request, so we shouldn't reply here.
return GetNoReply();
}
void DI::StartIOCtl(const IOCtlRequest& request)
{
const u32 command_0 = Memory::Read_U32(request.buffer_in);
const u32 command_1 = Memory::Read_U32(request.buffer_in + 4);
const u32 command_2 = Memory::Read_U32(request.buffer_in + 8);
// DVDInterface's ExecuteCommand handles most of the work.
// The IOCtl callback is used to generate a reply afterwards.
DVDInterface::ExecuteCommand(command_0, command_1, command_2, request.buffer_out,
request.buffer_out_size, true);
}
void DI::FinishIOCtl(DVDInterface::DIInterruptType interrupt_type)
void DI::ProcessQueuedIOCtl()
{
if (m_commands_to_execute.empty())
{
@ -71,52 +103,629 @@ void DI::FinishIOCtl(DVDInterface::DIInterruptType interrupt_type)
return;
}
// This command has been executed, so it's removed from the queue
u32 command_address = m_commands_to_execute.front();
m_executing_command = {m_commands_to_execute.front()};
m_commands_to_execute.pop_front();
m_ios.EnqueueIPCReply(IOCtlRequest{command_address}, interrupt_type);
IOCtlRequest request{m_executing_command->m_request_address};
auto finished = StartIOCtl(request);
if (finished)
{
CoreTiming::ScheduleEvent(2700 * SystemTimers::TIMER_RATIO, s_finish_executing_di_command,
static_cast<u64>(finished.value()));
return;
}
}
std::optional<DI::DIResult> DI::WriteIfFits(const IOCtlRequest& request, u32 value)
{
if (request.buffer_out_size < 4)
{
WARN_LOG(IOS_DI, "Output buffer is too small to contain result; returning security error");
return DIResult::SecurityError;
}
else
{
Memory::Write_U32(value, request.buffer_out);
return DIResult::Success;
}
}
std::optional<DI::DIResult> DI::StartIOCtl(const IOCtlRequest& request)
{
if (request.buffer_in_size != 0x20)
{
ERROR_LOG(IOS_DI, "IOCtl: Received bad input buffer size 0x%02x, should be 0x20",
request.buffer_in_size);
return DIResult::SecurityError;
}
// DVDInterface's ExecuteCommand handles most of the work for most of these.
// The IOCtl callback is used to generate a reply afterwards.
switch (static_cast<DIIoctl>(request.request))
{
case DIIoctl::DVDLowInquiry:
INFO_LOG(IOS_DI, "DVDLowInquiry");
DICMDBUF0 = 0x12000000;
DICMDBUF1 = 0;
return StartDMATransfer(0x20, request);
case DIIoctl::DVDLowReadDiskID:
INFO_LOG(IOS_DI, "DVDLowReadDiskID");
DICMDBUF0 = 0xA8000040;
DICMDBUF1 = 0;
DICMDBUF2 = 0x20;
return StartDMATransfer(0x20, request);
// TODO: Include an additional read that happens on Wii discs, or at least
// emulate its side effect of disabling DTK configuration
// if (Memory::Read_U32(request.buffer_out + 24) == 0x5d1c9ea3) { // Wii Magic
// if (!m_has_read_encryption_info) {
// // Read 0x44 (=> 0x60) bytes starting from offset 8 or byte 0x20;
// // byte 0x60 is disable hashing and byte 0x61 is disable encryption
// }
// }
case DIIoctl::DVDLowRead:
{
const u32 length = Memory::Read_U32(request.buffer_in + 4);
const u32 position = Memory::Read_U32(request.buffer_in + 8);
INFO_LOG(IOS_DI, "DVDLowRead: offset 0x%08x (byte 0x%09" PRIx64 "), length 0x%x", position,
static_cast<u64>(position) << 2, length);
if (m_current_partition == DiscIO::PARTITION_NONE)
{
ERROR_LOG(IOS_DI, "Attempted to perform a decrypting read when no partition is open!");
return DIResult::SecurityError;
}
if (request.buffer_out_size < length)
{
WARN_LOG(IOS_DI,
"Output buffer is too small for the result of the read (%d bytes given, needed at "
"least %d); returning security error",
request.buffer_out_size, length);
return DIResult::SecurityError;
}
m_last_length = position; // An actual mistake in IOS
DVDInterface::PerformDecryptingRead(position, length, request.buffer_out, m_current_partition,
DVDInterface::ReplyType::IOS);
return {};
}
case DIIoctl::DVDLowWaitForCoverClose:
// This is a bit awkward to implement, as it doesn't return for a long time, so just act as if
// the cover was immediately closed
INFO_LOG(IOS_DI, "DVDLowWaitForCoverClose - skipping");
return DIResult::CoverClosed;
case DIIoctl::DVDLowGetCoverRegister:
{
u32 dicvr = DICVR;
DEBUG_LOG(IOS_DI, "DVDLowGetCoverRegister 0x%08x", dicvr);
return WriteIfFits(request, dicvr);
}
case DIIoctl::DVDLowNotifyReset:
INFO_LOG(IOS_DI, "DVDLowNotifyReset");
ResetDIRegisters();
return DIResult::Success;
case DIIoctl::DVDLowSetSpinupFlag:
ERROR_LOG(IOS_DI, "DVDLowSetSpinupFlag - not a valid command, rejecting");
return DIResult::BadArgument;
case DIIoctl::DVDLowReadDvdPhysical:
{
const u8 position = Memory::Read_U8(request.buffer_in + 7);
INFO_LOG(IOS_DI, "DVDLowReadDvdPhysical: position 0x%02x", position);
DICMDBUF0 = 0xAD000000 | (position << 8);
DICMDBUF1 = 0;
DICMDBUF2 = 0;
return StartDMATransfer(0x800, request);
}
case DIIoctl::DVDLowReadDvdCopyright:
{
const u8 position = Memory::Read_U8(request.buffer_in + 7);
INFO_LOG(IOS_DI, "DVDLowReadDvdCopyright: position 0x%02x", position);
DICMDBUF0 = 0xAD010000 | (position << 8);
DICMDBUF1 = 0;
DICMDBUF2 = 0;
return StartImmediateTransfer(request);
}
case DIIoctl::DVDLowReadDvdDiscKey:
{
const u8 position = Memory::Read_U8(request.buffer_in + 7);
INFO_LOG(IOS_DI, "DVDLowReadDvdDiscKey: position 0x%02x", position);
DICMDBUF0 = 0xAD020000 | (position << 8);
DICMDBUF1 = 0;
DICMDBUF2 = 0;
return StartDMATransfer(0x800, request);
}
case DIIoctl::DVDLowGetLength:
INFO_LOG(IOS_DI, "DVDLowGetLength 0x%08x", m_last_length);
return WriteIfFits(request, m_last_length);
case DIIoctl::DVDLowGetImmBuf:
{
u32 diimmbuf = DIIMMBUF;
INFO_LOG(IOS_DI, "DVDLowGetImmBuf 0x%08x", diimmbuf);
return WriteIfFits(request, diimmbuf);
}
case DIIoctl::DVDLowUnmaskCoverInterrupt:
INFO_LOG(IOS_DI, "DVDLowUnmaskCoverInterrupt");
DVDInterface::SetInterruptEnabled(DVDInterface::DIInterruptType::CVRINT, false);
return DIResult::Success;
case DIIoctl::DVDLowClearCoverInterrupt:
DEBUG_LOG(IOS_DI, "DVDLowClearCoverInterrupt");
DVDInterface::ClearInterrupt(DVDInterface::DIInterruptType::CVRINT);
return DIResult::Success;
case DIIoctl::DVDLowGetCoverStatus:
// TODO: handle resetting case
INFO_LOG(IOS_DI, "DVDLowGetCoverStatus: Disc %sInserted",
DVDInterface::IsDiscInside() ? "" : "Not ");
return WriteIfFits(request, DVDInterface::IsDiscInside() ? 2 : 1);
case DIIoctl::DVDLowEnableCoverInterrupt:
INFO_LOG(IOS_DI, "DVDLowEnableCoverInterrupt");
DVDInterface::SetInterruptEnabled(DVDInterface::DIInterruptType::CVRINT, true);
return DIResult::Success;
case DIIoctl::DVDLowReset:
{
const bool spinup = Memory::Read_U32(request.address + 4);
INFO_LOG(IOS_DI, "DVDLowReset %s spinup", spinup ? "with" : "without");
DVDInterface::Reset(spinup);
ResetDIRegisters();
return DIResult::Success;
}
case DIIoctl::DVDLowOpenPartition:
ERROR_LOG(IOS_DI, "DVDLowOpenPartition as an ioctl - rejecting");
DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DIFFERENT_PARTITION_COMMAND);
return DIResult::SecurityError;
case DIIoctl::DVDLowClosePartition:
INFO_LOG(IOS_DI, "DVDLowClosePartition");
ChangePartition(DiscIO::PARTITION_NONE);
return DIResult::Success;
case DIIoctl::DVDLowUnencryptedRead:
{
const u32 length = Memory::Read_U32(request.buffer_in + 4);
const u32 position = Memory::Read_U32(request.buffer_in + 8);
const u32 end = position + (length >> 2); // a 32-bit offset
INFO_LOG(IOS_DI, "DVDLowUnencryptedRead: offset 0x%08x (byte 0x%09" PRIx64 "), length 0x%x",
position, static_cast<u64>(position) << 2, length);
// (start, end) as 32-bit offsets
// Older IOS versions only accept the first range. Later versions added the extra ranges to
// check how the drive responds to out of bounds reads (an error 001 check).
constexpr std::array<std::pair<u32, u32>, 3> valid_ranges = {
std::make_pair(0, 0x14000), // "System area"
std::make_pair(0x460A0000, 0x460A0008),
std::make_pair(0x7ED40000, 0x7ED40008),
};
for (auto range : valid_ranges)
{
if (range.first <= position && position <= range.second && range.first <= end &&
end <= range.second)
{
DICMDBUF0 = 0xA8000000;
DICMDBUF1 = position;
DICMDBUF2 = length;
return StartDMATransfer(length, request);
}
}
WARN_LOG(IOS_DI, "DVDLowUnencryptedRead: trying to read from an illegal region!");
return DIResult::SecurityError;
}
case DIIoctl::DVDLowEnableDvdVideo:
ERROR_LOG(IOS_DI, "DVDLowEnableDvdVideo - rejecting");
return DIResult::SecurityError;
// There are a bunch of ioctlvs that are also (unintentionally) usable as ioctls; reject these in
// Dolphin as games are unlikely to use them.
case DIIoctl::DVDLowGetNoDiscOpenPartitionParams:
ERROR_LOG(IOS_DI, "DVDLowGetNoDiscOpenPartitionParams as an ioctl - rejecting");
DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DIFFERENT_PARTITION_COMMAND);
return DIResult::SecurityError;
case DIIoctl::DVDLowNoDiscOpenPartition:
ERROR_LOG(IOS_DI, "DVDLowNoDiscOpenPartition as an ioctl - rejecting");
DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DIFFERENT_PARTITION_COMMAND);
return DIResult::SecurityError;
case DIIoctl::DVDLowGetNoDiscBufferSizes:
ERROR_LOG(IOS_DI, "DVDLowGetNoDiscBufferSizes as an ioctl - rejecting");
DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DIFFERENT_PARTITION_COMMAND);
return DIResult::SecurityError;
case DIIoctl::DVDLowOpenPartitionWithTmdAndTicket:
ERROR_LOG(IOS_DI, "DVDLowOpenPartitionWithTmdAndTicket as an ioctl - rejecting");
DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DIFFERENT_PARTITION_COMMAND);
return DIResult::SecurityError;
case DIIoctl::DVDLowOpenPartitionWithTmdAndTicketView:
ERROR_LOG(IOS_DI, "DVDLowOpenPartitionWithTmdAndTicketView as an ioctl - rejecting");
DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DIFFERENT_PARTITION_COMMAND);
return DIResult::SecurityError;
case DIIoctl::DVDLowGetStatusRegister:
{
u32 disr = DISR;
INFO_LOG(IOS_DI, "DVDLowGetStatusRegister: 0x%08x", disr);
return WriteIfFits(request, disr);
}
case DIIoctl::DVDLowGetControlRegister:
{
u32 dicr = DICR;
INFO_LOG(IOS_DI, "DVDLowGetControlRegister: 0x%08x", dicr);
return WriteIfFits(request, dicr);
}
case DIIoctl::DVDLowReportKey:
{
const u8 param1 = Memory::Read_U8(request.buffer_in + 7);
const u32 param2 = Memory::Read_U32(request.buffer_in + 8);
INFO_LOG(IOS_DI, "DVDLowReportKey: param1 0x%02x, param2 0x%06x", param1, param2);
DICMDBUF0 = 0xA4000000 | (param1 << 16);
DICMDBUF1 = param2 & 0xFFFFFF;
DICMDBUF2 = 0;
return StartDMATransfer(0x20, request);
}
case DIIoctl::DVDLowSeek:
{
const u32 position = Memory::Read_U32(request.buffer_in + 4); // 32-bit offset
INFO_LOG(IOS_DI, "DVDLowSeek: position 0x%08x, translated to 0x%08x", position,
position); // TODO: do partition translation!
DICMDBUF0 = 0xAB000000;
DICMDBUF1 = position;
return StartImmediateTransfer(request, false);
}
case DIIoctl::DVDLowReadDvd:
{
const u8 flag1 = Memory::Read_U8(request.buffer_in + 7);
const u8 flag2 = Memory::Read_U8(request.buffer_in + 11);
const u32 length = Memory::Read_U32(request.buffer_in + 12);
const u32 position = Memory::Read_U32(request.buffer_in + 16);
INFO_LOG(IOS_DI, "DVDLowReadDvd(%d, %d): position 0x%06x, length 0x%06x", flag1, flag2,
position, length);
DICMDBUF0 = 0xD0000000 | ((flag1 & 1) << 7) | ((flag2 & 1) << 6);
DICMDBUF1 = position & 0xFFFFFF;
DICMDBUF2 = length & 0xFFFFFF;
return StartDMATransfer(0x800 * length, request);
}
case DIIoctl::DVDLowReadDvdConfig:
{
const u8 flag1 = Memory::Read_U8(request.buffer_in + 7);
const u8 param2 = Memory::Read_U8(request.buffer_in + 11);
const u32 position = Memory::Read_U32(request.buffer_in + 12);
INFO_LOG(IOS_DI, "DVDLowReadDvdConfig(%d, %d): position 0x%06x", flag1, param2, position);
DICMDBUF0 = 0xD1000000 | ((flag1 & 1) << 16) | param2;
DICMDBUF1 = position & 0xFFFFFF;
DICMDBUF2 = 0;
return StartImmediateTransfer(request);
}
case DIIoctl::DVDLowStopLaser:
INFO_LOG(IOS_DI, "DVDLowStopLaser");
DICMDBUF0 = 0xD2000000;
return StartImmediateTransfer(request);
case DIIoctl::DVDLowOffset:
{
const u8 flag = Memory::Read_U8(request.buffer_in + 7);
const u32 offset = Memory::Read_U32(request.buffer_in + 8);
INFO_LOG(IOS_DI, "DVDLowOffset(%d): offset 0x%08x", flag, offset);
DICMDBUF0 = 0xD9000000 | ((flag & 1) << 16);
DICMDBUF1 = offset;
return StartImmediateTransfer(request);
}
case DIIoctl::DVDLowReadDiskBca:
INFO_LOG(IOS_DI, "DVDLowReadDiskBca");
DICMDBUF0 = 0xDA000000;
return StartDMATransfer(0x40, request);
case DIIoctl::DVDLowRequestDiscStatus:
INFO_LOG(IOS_DI, "DVDLowRequestDiscStatus");
DICMDBUF0 = 0xDB000000;
return StartImmediateTransfer(request);
case DIIoctl::DVDLowRequestRetryNumber:
INFO_LOG(IOS_DI, "DVDLowRequestRetryNumber");
DICMDBUF0 = 0xDC000000;
return StartImmediateTransfer(request);
case DIIoctl::DVDLowSetMaximumRotation:
{
const u8 speed = Memory::Read_U8(request.buffer_in + 7);
INFO_LOG(IOS_DI, "DVDLowSetMaximumRotation: speed %d", speed);
DICMDBUF0 = 0xDD000000 | ((speed & 3) << 16);
return StartImmediateTransfer(request, false);
}
case DIIoctl::DVDLowSerMeasControl:
{
const u8 flag1 = Memory::Read_U8(request.buffer_in + 7);
const u8 flag2 = Memory::Read_U8(request.buffer_in + 11);
INFO_LOG(IOS_DI, "DVDLowSerMeasControl(%d, %d)", flag1, flag2);
DICMDBUF0 = 0xDF000000 | ((flag1 & 1) << 17) | ((flag2 & 1) << 16);
return StartDMATransfer(0x20, request);
}
case DIIoctl::DVDLowRequestError:
INFO_LOG(IOS_DI, "DVDLowRequestError");
DICMDBUF0 = 0xE0000000;
return StartImmediateTransfer(request);
case DIIoctl::DVDLowAudioStream:
{
const u8 mode = Memory::Read_U8(request.buffer_in + 7);
const u32 length = Memory::Read_U32(request.buffer_in + 8);
const u32 position = Memory::Read_U32(request.buffer_in + 12);
INFO_LOG(IOS_DI, "DVDLowAudioStream(%d): offset 0x%08x (byte 0x%09" PRIx64 "), length 0x%x",
mode, position, static_cast<u64>(position) << 2, length);
DICMDBUF0 = 0xE1000000 | ((mode & 3) << 16);
DICMDBUF1 = position;
DICMDBUF2 = length;
return StartImmediateTransfer(request, false);
}
case DIIoctl::DVDLowRequestAudioStatus:
{
const u8 mode = Memory::Read_U8(request.buffer_in + 7);
INFO_LOG(IOS_DI, "DVDLowRequestAudioStatus(%d)", mode);
DICMDBUF0 = 0xE2000000 | ((mode & 3) << 16);
DICMDBUF1 = 0;
// Note that this command does not copy the value written to DIIMMBUF, which makes it rather
// useless (to actually use it, DVDLowGetImmBuf would need to be used afterwards)
return StartImmediateTransfer(request, false);
}
case DIIoctl::DVDLowStopMotor:
{
const u8 eject = Memory::Read_U8(request.buffer_in + 7);
const u8 kill = Memory::Read_U8(request.buffer_in + 11);
INFO_LOG(IOS_DI, "DVDLowStopMotor(%d, %d)", eject, kill);
DICMDBUF0 = 0xE3000000 | ((eject & 1) << 17) | ((kill & 1) << 20);
DICMDBUF1 = 0;
return StartImmediateTransfer(request);
}
case DIIoctl::DVDLowAudioBufferConfig:
{
const u8 enable = Memory::Read_U8(request.buffer_in + 7);
const u8 buffer_size = Memory::Read_U8(request.buffer_in + 11);
INFO_LOG(IOS_DI, "DVDLowAudioBufferConfig: %s, buffer size %d", enable ? "enabled" : "disabled",
buffer_size);
DICMDBUF0 = 0xE4000000 | ((enable & 1) << 16) | (buffer_size & 0xf);
DICMDBUF1 = 0;
// On the other hand, this command *does* copy DIIMMBUF, but the actual code in the drive never
// writes anything to it, so it just copies over a stale value (e.g. from DVDLowRequestError).
return StartImmediateTransfer(request);
}
default:
ERROR_LOG(IOS_DI, "Unknown ioctl 0x%02x", request.request);
return DIResult::SecurityError;
}
}
std::optional<DI::DIResult> DI::StartDMATransfer(u32 command_length, const IOCtlRequest& request)
{
if (request.buffer_out_size < command_length)
{
// Actual /dev/di will still send a command, but won't write the length or output address,
// causing it to eventually time out after 15 seconds. Just immediately time out here,
// instead.
WARN_LOG(IOS_DI,
"Output buffer is too small for the result of the command (%d bytes given, needed at "
"least %d); returning read timed out (immediately, instead of waiting)",
request.buffer_out_size, command_length);
return DIResult::ReadTimedOut;
}
if ((command_length & 0x1f) != 0 || (request.buffer_out & 0x1f) != 0)
{
// In most cases, the actual DI driver just hangs for unaligned data, but let's be a bit more
// gentle.
WARN_LOG(IOS_DI,
"Output buffer or length is incorrectly aligned (buffer 0x%08x, buffer length %x, "
"command length %x)",
request.buffer_out, request.buffer_out_size, command_length);
return DIResult::BadArgument;
}
DIMAR = request.buffer_out;
m_last_length = command_length;
DILENGTH = command_length;
DVDInterface::ExecuteCommand(DVDInterface::ReplyType::IOS);
// Reply will be posted when done by FinishIOCtl.
return {};
}
std::optional<DI::DIResult> DI::StartImmediateTransfer(const IOCtlRequest& request,
bool write_to_buf)
{
if (write_to_buf && request.buffer_out_size < 4)
{
WARN_LOG(IOS_DI,
"Output buffer size is too small for an immediate transfer (%d bytes, should be at "
"least 4). Performing transfer anyways.",
request.buffer_out_size);
}
m_executing_command->m_copy_diimmbuf = write_to_buf;
DVDInterface::ExecuteCommand(DVDInterface::ReplyType::IOS);
// Reply will be posted when done by FinishIOCtl.
return {};
}
void DI::InterruptFromDVDInterface(DVDInterface::DIInterruptType interrupt_type)
{
DIResult result;
switch (interrupt_type)
{
case DVDInterface::DIInterruptType::TCINT:
result = DIResult::Success;
break;
case DVDInterface::DIInterruptType::DEINT:
result = DIResult::DriveError;
break;
default:
PanicAlert("IOS::HLE::Device::DI: Unexpected DVDInterface interrupt %d!",
static_cast<int>(interrupt_type));
result = DIResult::DriveError;
break;
}
auto di = IOS::HLE::GetIOS()->GetDeviceByName("/dev/di");
if (di)
{
std::static_pointer_cast<DI>(di)->FinishDICommand(result);
}
else
{
PanicAlert("IOS::HLE::Device::DI: Received interrupt from DVDInterface when device wasn't "
"registered!");
}
}
void DI::FinishDICommandCallback(u64 userdata, s64 ticksbehind)
{
const DIResult result = static_cast<DIResult>(userdata);
auto di = IOS::HLE::GetIOS()->GetDeviceByName("/dev/di");
if (di)
std::static_pointer_cast<DI>(di)->FinishDICommand(result);
else
PanicAlert("IOS::HLE::Device::DI: Received interrupt from DI when device wasn't registered!");
}
void DI::FinishDICommand(DIResult result)
{
if (!m_executing_command.has_value())
{
PanicAlert("IOS::HLE::Device::DI: There is no command to finish!");
return;
}
IOCtlRequest request{m_executing_command->m_request_address};
if (m_executing_command->m_copy_diimmbuf)
Memory::Write_U32(DIIMMBUF, request.buffer_out);
m_ios.EnqueueIPCReply(request, static_cast<s32>(result));
m_executing_command.reset();
// DVDInterface is now ready to execute another command,
// so we start executing a command from the queue if there is one
if (!m_commands_to_execute.empty())
{
IOCtlRequest next_request{m_commands_to_execute.front()};
StartIOCtl(next_request);
ProcessQueuedIOCtl();
}
}
IPCCommandResult DI::IOCtlV(const IOCtlVRequest& request)
{
for (const auto& vector : request.io_vectors)
Memory::Memset(vector.address, 0, vector.size);
s32 return_value = IPC_SUCCESS;
switch (request.request)
// IOCtlVs are not queued since they don't (currently) go into DVDInterface and act
// asynchronously. This does mean that an IOCtlV can be executed while an IOCtl is in progress,
// which isn't ideal.
InitializeIfFirstTime();
if (request.in_vectors[0].size != 0x20)
{
case DVDInterface::DVDLowOpenPartition:
ERROR_LOG(IOS_DI, "IOCtlV: Received bad input buffer size 0x%02x, should be 0x20",
request.in_vectors[0].size);
return GetDefaultReply(static_cast<s32>(DIResult::BadArgument));
}
const u8 command = Memory::Read_U8(request.in_vectors[0].address);
if (request.request != command)
{
DEBUG_ASSERT_MSG(IOS_DI, request.in_vectors[1].address == 0, "DVDLowOpenPartition with ticket");
DEBUG_ASSERT_MSG(IOS_DI, request.in_vectors[2].address == 0,
"DVDLowOpenPartition with cert chain");
WARN_LOG(IOS_DI,
"IOCtlV: Received conflicting commands: ioctl 0x%02x, buffer 0x%02x. Using ioctlv "
"command.",
request.request, command);
}
DIResult return_value = DIResult::BadArgument;
switch (static_cast<DIIoctl>(request.request))
{
case DIIoctl::DVDLowOpenPartition:
{
if (request.in_vectors.size() != 3 || request.io_vectors.size() != 2)
{
ERROR_LOG(IOS_DI, "DVDLowOpenPartition: bad vector count %zu in/%zu out",
request.in_vectors.size(), request.io_vectors.size());
break;
}
if (request.in_vectors[1].address != 0)
{
ERROR_LOG(IOS_DI,
"DVDLowOpenPartition with ticket - not implemented, ignoring ticket parameter");
DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DIFFERENT_PARTITION_COMMAND);
}
if (request.in_vectors[2].address != 0)
{
ERROR_LOG(IOS_DI,
"DVDLowOpenPartition with cert chain - not implemented, ignoring certs parameter");
DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DIFFERENT_PARTITION_COMMAND);
}
const u64 partition_offset =
static_cast<u64>(Memory::Read_U32(request.in_vectors[0].address + 4)) << 2;
const DiscIO::Partition partition(partition_offset);
DVDInterface::ChangePartition(partition);
ChangePartition(DiscIO::Partition(partition_offset));
INFO_LOG(IOS_DI, "DVDLowOpenPartition: partition_offset 0x%016" PRIx64, partition_offset);
INFO_LOG(IOS_DI, "DVDLowOpenPartition: partition_offset 0x%09" PRIx64, partition_offset);
// Read TMD to the buffer
const IOS::ES::TMDReader tmd = DVDThread::GetTMD(partition);
const IOS::ES::TMDReader tmd = DVDThread::GetTMD(m_current_partition);
const std::vector<u8>& raw_tmd = tmd.GetBytes();
Memory::CopyToEmu(request.io_vectors[0].address, raw_tmd.data(), raw_tmd.size());
m_ios.GetES()->DIVerify(tmd, DVDThread::GetTicket(partition));
return_value = 1;
ReturnCode es_result = m_ios.GetES()->DIVerify(tmd, DVDThread::GetTicket(m_current_partition));
Memory::Write_U32(es_result, request.io_vectors[1].address);
return_value = DIResult::Success;
break;
}
case DIIoctl::DVDLowGetNoDiscOpenPartitionParams:
ERROR_LOG(IOS_DI, "DVDLowGetNoDiscOpenPartitionParams - dummied out");
DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DIFFERENT_PARTITION_COMMAND);
request.DumpUnknown(GetDeviceName(), Common::Log::IOS_DI);
break;
case DIIoctl::DVDLowNoDiscOpenPartition:
ERROR_LOG(IOS_DI, "DVDLowNoDiscOpenPartition - dummied out");
DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DIFFERENT_PARTITION_COMMAND);
request.DumpUnknown(GetDeviceName(), Common::Log::IOS_DI);
break;
case DIIoctl::DVDLowGetNoDiscBufferSizes:
ERROR_LOG(IOS_DI, "DVDLowGetNoDiscBufferSizes - dummied out");
DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DIFFERENT_PARTITION_COMMAND);
request.DumpUnknown(GetDeviceName(), Common::Log::IOS_DI);
break;
case DIIoctl::DVDLowOpenPartitionWithTmdAndTicket:
ERROR_LOG(IOS_DI, "DVDLowOpenPartitionWithTmdAndTicket - not implemented");
DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DIFFERENT_PARTITION_COMMAND);
break;
case DIIoctl::DVDLowOpenPartitionWithTmdAndTicketView:
ERROR_LOG(IOS_DI, "DVDLowOpenPartitionWithTmdAndTicketView - not implemented");
DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_DIFFERENT_PARTITION_COMMAND);
break;
default:
ERROR_LOG(IOS_DI, "Unknown ioctlv 0x%02x", request.request);
request.DumpUnknown(GetDeviceName(), Common::Log::IOS_DI);
}
return GetDefaultReply(return_value);
return GetDefaultReply(static_cast<s32>(return_value));
}
void DI::ChangePartition(const DiscIO::Partition partition)
{
m_current_partition = partition;
}
DiscIO::Partition DI::GetCurrentPartition()
{
auto di = IOS::HLE::GetIOS()->GetDeviceByName("/dev/di");
// Note that this function is called in Gamecube mode for UpdateRunningGameMetadata,
// so both cases are hit in normal circumstances.
if (!di)
return DiscIO::PARTITION_NONE;
return std::static_pointer_cast<DI>(di)->m_current_partition;
}
void DI::InitializeIfFirstTime()
{
// Match the behavior of Nintendo's initDvdDriverStage2, which is called the first time
// an open/ioctl/ioctlv occurs. This behavior is observable by directly reading the DI registers,
// so it shouldn't be handled in the constructor.
// Note that ResetDIRegisters also clears the current partition, which actually normally happens
// earlier; however, that is not observable.
// Also, registers are not cleared if syscall_check_di_reset (0x46) returns true (bit 10 of
// HW_RESETS is set), which is not currently emulated.
if (!m_has_initialized)
{
ResetDIRegisters();
m_has_initialized = true;
}
}
void DI::ResetDIRegisters()
{
// Clear transfer complete and error interrupts (normally r/z, but here we just directly write
// zero)
DVDInterface::ClearInterrupt(DVDInterface::DIInterruptType::TCINT);
DVDInterface::ClearInterrupt(DVDInterface::DIInterruptType::DEINT);
// Enable transfer complete and error interrupts, and disable cover interrupt
DVDInterface::SetInterruptEnabled(DVDInterface::DIInterruptType::TCINT, true);
DVDInterface::SetInterruptEnabled(DVDInterface::DIInterruptType::DEINT, true);
DVDInterface::SetInterruptEnabled(DVDInterface::DIInterruptType::CVRINT, false);
// Close the current partition, if there is one
ChangePartition(DiscIO::PARTITION_NONE);
}
} // namespace IOS::HLE::Device

View File

@ -5,17 +5,29 @@
#pragma once
#include <deque>
#include <optional>
#include <string>
#include "Common/CommonTypes.h"
#include "Core/IOS/Device.h"
#include "Core/IOS/IOS.h"
#include "DiscIO/Volume.h"
class CBoot;
class PointerWrap;
namespace DVDInterface
{
enum DIInterruptType : int;
enum class DIInterruptType : int;
}
namespace CoreTiming
{
struct EventType;
}
namespace IOS::HLE
{
void Init();
}
namespace IOS::HLE::Device
@ -25,16 +37,112 @@ class DI : public Device
public:
DI(Kernel& ios, const std::string& device_name);
static void InterruptFromDVDInterface(DVDInterface::DIInterruptType interrupt_type);
static DiscIO::Partition GetCurrentPartition();
void DoState(PointerWrap& p) override;
IPCCommandResult Open(const OpenRequest& request) override;
IPCCommandResult IOCtl(const IOCtlRequest& request) override;
IPCCommandResult IOCtlV(const IOCtlVRequest& request) override;
void FinishIOCtl(DVDInterface::DIInterruptType interrupt_type);
enum class DIIoctl : u32
{
DVDLowInquiry = 0x12,
DVDLowReadDiskID = 0x70,
DVDLowRead = 0x71,
DVDLowWaitForCoverClose = 0x79,
DVDLowGetCoverRegister = 0x7a, // DVDLowPrepareCoverRegister
DVDLowNotifyReset = 0x7e,
DVDLowSetSpinupFlag = 0x7f,
DVDLowReadDvdPhysical = 0x80,
DVDLowReadDvdCopyright = 0x81,
DVDLowReadDvdDiscKey = 0x82,
DVDLowGetLength = 0x83,
DVDLowGetImmBuf = 0x84, // Unconfirmed name
DVDLowUnmaskCoverInterrupt = 0x85,
DVDLowClearCoverInterrupt = 0x86,
// 0x87 is a dummied out command
DVDLowGetCoverStatus = 0x88,
DVDLowEnableCoverInterrupt = 0x89, // Unconfirmed name
DVDLowReset = 0x8a,
DVDLowOpenPartition = 0x8b, // ioctlv only
DVDLowClosePartition = 0x8c,
DVDLowUnencryptedRead = 0x8d,
DVDLowEnableDvdVideo = 0x8e,
DVDLowGetNoDiscOpenPartitionParams = 0x90, // ioctlv, dummied out
DVDLowNoDiscOpenPartition = 0x91, // ioctlv, dummied out
DVDLowGetNoDiscBufferSizes = 0x92, // ioctlv, dummied out
DVDLowOpenPartitionWithTmdAndTicket = 0x93, // ioctlv
DVDLowOpenPartitionWithTmdAndTicketView = 0x94, // ioctlv
DVDLowGetStatusRegister = 0x95, // DVDLowPrepareStatusRegsiter
DVDLowGetControlRegister = 0x96, // DVDLowPrepareControlRegister
DVDLowReportKey = 0xa4,
// 0xa8 is unusable externally
DVDLowSeek = 0xab,
DVDLowReadDvd = 0xd0,
DVDLowReadDvdConfig = 0xd1,
DVDLowStopLaser = 0xd2,
DVDLowOffset = 0xd9,
DVDLowReadDiskBca = 0xda,
DVDLowRequestDiscStatus = 0xdb,
DVDLowRequestRetryNumber = 0xdc,
DVDLowSetMaximumRotation = 0xdd,
DVDLowSerMeasControl = 0xdf,
DVDLowRequestError = 0xe0,
DVDLowAudioStream = 0xe1,
DVDLowRequestAudioStatus = 0xe2,
DVDLowStopMotor = 0xe3,
DVDLowAudioBufferConfig = 0xe4,
};
enum class DIResult : s32
{
Success = 1,
DriveError = 2,
CoverClosed = 4,
ReadTimedOut = 16,
SecurityError = 32,
VerifyError = 64,
BadArgument = 128,
};
private:
void StartIOCtl(const IOCtlRequest& request);
struct ExecutingCommandInfo
{
ExecutingCommandInfo() {}
ExecutingCommandInfo(u32 request_address)
: m_request_address(request_address), m_copy_diimmbuf(false)
{
}
u32 m_request_address;
bool m_copy_diimmbuf;
};
friend class ::CBoot;
friend void ::IOS::HLE::Init();
void ProcessQueuedIOCtl();
std::optional<DIResult> StartIOCtl(const IOCtlRequest& request);
std::optional<DIResult> WriteIfFits(const IOCtlRequest& request, u32 value);
std::optional<DIResult> StartDMATransfer(u32 command_length, const IOCtlRequest& request);
std::optional<DIResult> StartImmediateTransfer(const IOCtlRequest& request,
bool write_to_buf = true);
void ChangePartition(const DiscIO::Partition partition);
void InitializeIfFirstTime();
void ResetDIRegisters();
static void FinishDICommandCallback(u64 userdata, s64 ticksbehind);
void FinishDICommand(DIResult result);
static CoreTiming::EventType* s_finish_executing_di_command;
std::optional<ExecutingCommandInfo> m_executing_command;
std::deque<u32> m_commands_to_execute;
DiscIO::Partition m_current_partition = DiscIO::PARTITION_NONE;
bool m_has_initialized = false;
u32 m_last_length = 0;
};
} // namespace IOS::HLE::Device

View File

@ -782,6 +782,9 @@ void Init()
device->EventNotify();
});
Device::DI::s_finish_executing_di_command =
CoreTiming::RegisterEvent("FinishDICommand", Device::DI::FinishDICommandCallback);
// Start with IOS80 to simulate part of the Wii boot process.
s_ios = std::make_unique<EmulationKernel>(Titles::SYSTEM_MENU_IOS);
// On a Wii, boot2 launches the system menu IOS, which then launches the system menu

View File

@ -73,7 +73,7 @@ static Common::Event g_compressAndDumpStateSyncEvent;
static std::thread g_save_thread;
// Don't forget to increase this after doing changes on the savestate system
constexpr u32 STATE_VERSION = 113; // Last changed in PR 8506
constexpr u32 STATE_VERSION = 114; // Last changed in PR 8394
// Maps savestate versions to Dolphin versions.
// Versions after 42 don't need to be added to this list,

View File

@ -686,7 +686,7 @@ void MainWindow::ChangeDisc()
void MainWindow::EjectDisc()
{
Core::RunAsCPUThread(DVDInterface::EjectDisc);
Core::RunAsCPUThread([] { DVDInterface::EjectDisc(DVDInterface::EjectCause::User); });
}
void MainWindow::Open()