Starting point for the new MMIO interface
Design doc: https://docs.google.com/document/d/11qcGCWLne1wYvmtFaSrOKZt_vHxXrcWcZsdWJ-MJnyo/edit The code is currently not used. Migration plan: 1. Implement MMIO access via MMIO::Mapping in parallel to the current method. 2. Implement all existing MMIO handlers via the new interface. 3. Remove the old hwRead/hwReadWii/hwReadIOBridge code. 4. Implement JIT optimizations for MMIO accesses.
This commit is contained in:
parent
1e94853301
commit
9fe58d28ba
|
@ -107,6 +107,7 @@ set(SRCS ActionReplay.cpp
|
||||||
HW/Memmap.cpp
|
HW/Memmap.cpp
|
||||||
HW/MemmapFunctions.cpp
|
HW/MemmapFunctions.cpp
|
||||||
HW/MemoryInterface.cpp
|
HW/MemoryInterface.cpp
|
||||||
|
HW/MMIO.cpp
|
||||||
HW/ProcessorInterface.cpp
|
HW/ProcessorInterface.cpp
|
||||||
HW/SI.cpp
|
HW/SI.cpp
|
||||||
HW/SI_DeviceAMBaseboard.cpp
|
HW/SI_DeviceAMBaseboard.cpp
|
||||||
|
|
|
@ -145,6 +145,7 @@
|
||||||
<ClCompile Include="HW\Memmap.cpp" />
|
<ClCompile Include="HW\Memmap.cpp" />
|
||||||
<ClCompile Include="HW\MemmapFunctions.cpp" />
|
<ClCompile Include="HW\MemmapFunctions.cpp" />
|
||||||
<ClCompile Include="HW\MemoryInterface.cpp" />
|
<ClCompile Include="HW\MemoryInterface.cpp" />
|
||||||
|
<ClCompile Include="HW\MMIO.cpp" />
|
||||||
<ClCompile Include="HW\ProcessorInterface.cpp" />
|
<ClCompile Include="HW\ProcessorInterface.cpp" />
|
||||||
<ClCompile Include="HW\SI.cpp" />
|
<ClCompile Include="HW\SI.cpp" />
|
||||||
<ClCompile Include="HW\SI_Device.cpp" />
|
<ClCompile Include="HW\SI_Device.cpp" />
|
||||||
|
@ -342,6 +343,8 @@
|
||||||
<ClInclude Include="HW\HW.h" />
|
<ClInclude Include="HW\HW.h" />
|
||||||
<ClInclude Include="HW\Memmap.h" />
|
<ClInclude Include="HW\Memmap.h" />
|
||||||
<ClInclude Include="HW\MemoryInterface.h" />
|
<ClInclude Include="HW\MemoryInterface.h" />
|
||||||
|
<ClInclude Include="HW\MMIO.h" />
|
||||||
|
<ClInclude Include="HW\MMIOHandlers.h" />
|
||||||
<ClInclude Include="HW\ProcessorInterface.h" />
|
<ClInclude Include="HW\ProcessorInterface.h" />
|
||||||
<ClInclude Include="HW\SI.h" />
|
<ClInclude Include="HW\SI.h" />
|
||||||
<ClInclude Include="HW\SI_Device.h" />
|
<ClInclude Include="HW\SI_Device.h" />
|
||||||
|
|
|
@ -510,6 +510,9 @@
|
||||||
<ClCompile Include="HW\MemmapFunctions.cpp">
|
<ClCompile Include="HW\MemmapFunctions.cpp">
|
||||||
<Filter>HW %28Flipper/Hollywood%29</Filter>
|
<Filter>HW %28Flipper/Hollywood%29</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="HW\MMIO.cpp">
|
||||||
|
<Filter>HW %28Flipper/Hollywood%29</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="HW\SystemTimers.cpp">
|
<ClCompile Include="HW\SystemTimers.cpp">
|
||||||
<Filter>HW %28Flipper/Hollywood%29</Filter>
|
<Filter>HW %28Flipper/Hollywood%29</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
@ -1037,6 +1040,12 @@
|
||||||
<ClInclude Include="HW\Memmap.h">
|
<ClInclude Include="HW\Memmap.h">
|
||||||
<Filter>HW %28Flipper/Hollywood%29</Filter>
|
<Filter>HW %28Flipper/Hollywood%29</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="HW\MMIO.h">
|
||||||
|
<Filter>HW %28Flipper/Hollywood%29</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="HW\MMIOHandlers.h">
|
||||||
|
<Filter>HW %28Flipper/Hollywood%29</Filter>
|
||||||
|
</ClInclude>
|
||||||
<ClInclude Include="HW\SystemTimers.h">
|
<ClInclude Include="HW\SystemTimers.h">
|
||||||
<Filter>HW %28Flipper/Hollywood%29</Filter>
|
<Filter>HW %28Flipper/Hollywood%29</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
|
|
@ -0,0 +1,383 @@
|
||||||
|
// Copyright 2013 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "MMIO.h"
|
||||||
|
#include "MMIOHandlers.h"
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace MMIO
|
||||||
|
{
|
||||||
|
|
||||||
|
// Base classes for the two handling method hierarchies. Note that a single
|
||||||
|
// class can inherit from both.
|
||||||
|
//
|
||||||
|
// At the moment the only common element between all the handling method is
|
||||||
|
// that they should be able to accept a visitor of the appropriate type.
|
||||||
|
template <typename T>
|
||||||
|
class ReadHandlingMethod
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~ReadHandlingMethod() {}
|
||||||
|
virtual void AcceptReadVisitor(ReadHandlingMethodVisitor<T>& v) const = 0;
|
||||||
|
};
|
||||||
|
template <typename T>
|
||||||
|
class WriteHandlingMethod
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~WriteHandlingMethod() {}
|
||||||
|
virtual void AcceptWriteVisitor(WriteHandlingMethodVisitor<T>& v) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Constant: handling method holds a single integer and passes it to the
|
||||||
|
// visitor. This is a read only handling method: storing to a constant does not
|
||||||
|
// mean anything.
|
||||||
|
template <typename T>
|
||||||
|
class ConstantHandlingMethod : public ReadHandlingMethod<T>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit ConstantHandlingMethod(T value) : value_(value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~ConstantHandlingMethod() {}
|
||||||
|
|
||||||
|
virtual void AcceptReadVisitor(ReadHandlingMethodVisitor<T>& v) const
|
||||||
|
{
|
||||||
|
v.VisitConstant(value_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
T value_;
|
||||||
|
};
|
||||||
|
template <typename T>
|
||||||
|
ReadHandlingMethod<T>* Constant(T value)
|
||||||
|
{
|
||||||
|
return new ConstantHandlingMethod<T>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nop: extremely simple write handling method that does nothing at all, only
|
||||||
|
// respond to visitors and dispatch to the correct method. This is write only
|
||||||
|
// since reads should always at least return a value.
|
||||||
|
template <typename T>
|
||||||
|
class NopHandlingMethod : public WriteHandlingMethod<T>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
NopHandlingMethod() {}
|
||||||
|
virtual ~NopHandlingMethod() {}
|
||||||
|
virtual void AcceptWriteVisitor(WriteHandlingMethodVisitor<T>& v) const
|
||||||
|
{
|
||||||
|
v.VisitNop();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
template <typename T>
|
||||||
|
WriteHandlingMethod<T>* Nop()
|
||||||
|
{
|
||||||
|
return new NopHandlingMethod<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Direct: handling method holds a pointer to the value where to read/write the
|
||||||
|
// data from, as well as a mask that is used to restrict reading/writing only
|
||||||
|
// to a given set of bits.
|
||||||
|
template <typename T>
|
||||||
|
class DirectHandlingMethod : public ReadHandlingMethod<T>,
|
||||||
|
public WriteHandlingMethod<T>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DirectHandlingMethod(T* addr, u32 mask) : addr_(addr), mask_(mask)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~DirectHandlingMethod() {}
|
||||||
|
|
||||||
|
virtual void AcceptReadVisitor(ReadHandlingMethodVisitor<T>& v) const
|
||||||
|
{
|
||||||
|
v.VisitDirect(addr_, mask_);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void AcceptWriteVisitor(WriteHandlingMethodVisitor<T>& v) const
|
||||||
|
{
|
||||||
|
v.VisitDirect(addr_, mask_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
T* addr_;
|
||||||
|
u32 mask_;
|
||||||
|
};
|
||||||
|
template <typename T>
|
||||||
|
ReadHandlingMethod<T>* DirectRead(const T* addr, u32 mask)
|
||||||
|
{
|
||||||
|
return new DirectHandlingMethod<T>(const_cast<T*>(addr), mask);
|
||||||
|
}
|
||||||
|
template <typename T>
|
||||||
|
ReadHandlingMethod<T>* DirectRead(volatile const T* addr, u32 mask)
|
||||||
|
{
|
||||||
|
return new DirectHandlingMethod<T>((T*)addr, mask);
|
||||||
|
}
|
||||||
|
template <typename T>
|
||||||
|
WriteHandlingMethod<T>* DirectWrite(T* addr, u32 mask)
|
||||||
|
{
|
||||||
|
return new DirectHandlingMethod<T>(addr, mask);
|
||||||
|
}
|
||||||
|
template <typename T>
|
||||||
|
WriteHandlingMethod<T>* DirectWrite(volatile T* addr, u32 mask)
|
||||||
|
{
|
||||||
|
return new DirectHandlingMethod<T>((T*)addr, mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complex: holds a lambda that is called when a read or a write is executed.
|
||||||
|
// This gives complete control to the user as to what is going to happen during
|
||||||
|
// that read or write, but reduces the optimization potential.
|
||||||
|
template <typename T>
|
||||||
|
class ComplexHandlingMethod : public ReadHandlingMethod<T>,
|
||||||
|
public WriteHandlingMethod<T>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit ComplexHandlingMethod(std::function<T(u32)> read_lambda)
|
||||||
|
: read_lambda_(read_lambda), write_lambda_(InvalidWriteLambda())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit ComplexHandlingMethod(std::function<void(u32, T)> write_lambda)
|
||||||
|
: read_lambda_(InvalidReadLambda()), write_lambda_(write_lambda)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~ComplexHandlingMethod() {}
|
||||||
|
|
||||||
|
virtual void AcceptReadVisitor(ReadHandlingMethodVisitor<T>& v) const
|
||||||
|
{
|
||||||
|
v.VisitComplex(read_lambda_);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void AcceptWriteVisitor(WriteHandlingMethodVisitor<T>& v) const
|
||||||
|
{
|
||||||
|
v.VisitComplex(write_lambda_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::function<T(u32)> InvalidReadLambda() const
|
||||||
|
{
|
||||||
|
return [](u32) {
|
||||||
|
_dbg_assert_msg_(MEMMAP, 0, "Called the read lambda on a write "
|
||||||
|
"complex handler.");
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::function<void(u32, T)> InvalidWriteLambda() const
|
||||||
|
{
|
||||||
|
return [](u32, T) {
|
||||||
|
_dbg_assert_msg_(MEMMAP, 0, "Called the write lambda on a read "
|
||||||
|
"complex handler.");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::function<T(u32)> read_lambda_;
|
||||||
|
std::function<void(u32, T)> write_lambda_;
|
||||||
|
};
|
||||||
|
template <typename T>
|
||||||
|
ReadHandlingMethod<T>* ComplexRead(std::function<T(u32)> lambda)
|
||||||
|
{
|
||||||
|
return new ComplexHandlingMethod<T>(lambda);
|
||||||
|
}
|
||||||
|
template <typename T>
|
||||||
|
WriteHandlingMethod<T>* ComplexWrite(std::function<void(u32, T)> lambda)
|
||||||
|
{
|
||||||
|
return new ComplexHandlingMethod<T>(lambda);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalid: specialization of the complex handling type with lambdas that
|
||||||
|
// display error messages.
|
||||||
|
template <typename T>
|
||||||
|
ReadHandlingMethod<T>* InvalidRead()
|
||||||
|
{
|
||||||
|
return ComplexRead<T>([](u32 addr) {
|
||||||
|
ERROR_LOG(MEMMAP, "Trying to read from an invalid MMIO (addr=%08x)",
|
||||||
|
addr);
|
||||||
|
return -1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
template <typename T>
|
||||||
|
WriteHandlingMethod<T>* InvalidWrite()
|
||||||
|
{
|
||||||
|
return ComplexWrite<T>([](u32 addr, T val) {
|
||||||
|
ERROR_LOG(MEMMAP, "Trying to write to an invalid MMIO (addr=%08x, val=%08x)",
|
||||||
|
addr, (u32)val);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converters to larger and smaller size. Probably the most complex of these
|
||||||
|
// handlers to implement. They do not define new handling method types but
|
||||||
|
// instead will internally use the types defined above.
|
||||||
|
template <typename T> struct SmallerAccessSize {};
|
||||||
|
template <> struct SmallerAccessSize<u16> { typedef u8 value; };
|
||||||
|
template <> struct SmallerAccessSize<u32> { typedef u16 value; };
|
||||||
|
|
||||||
|
template <typename T> struct LargerAccessSize {};
|
||||||
|
template <> struct LargerAccessSize<u8> { typedef u16 value; };
|
||||||
|
template <> struct LargerAccessSize<u16> { typedef u32 value; };
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
ReadHandlingMethod<T>* ReadToSmaller(Mapping* mmio, u32 high_part_addr, u32 low_part_addr)
|
||||||
|
{
|
||||||
|
typedef typename SmallerAccessSize<T>::value ST;
|
||||||
|
|
||||||
|
const ReadHandler<ST>* high_part;
|
||||||
|
const ReadHandler<ST>* low_part;
|
||||||
|
mmio->GetHandlerForRead(high_part_addr, &high_part);
|
||||||
|
mmio->GetHandlerForRead(low_part_addr, &low_part);
|
||||||
|
|
||||||
|
// TODO(delroth): optimize
|
||||||
|
return ComplexRead<T>([high_part, low_part](u32 addr) {
|
||||||
|
return ((T)high_part->Read(addr) << (8 * sizeof (ST))) | low_part->Read(addr);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
WriteHandlingMethod<T>* WriteToSmaller(Mapping* mmio, u32 high_part_addr, u32 low_part_addr)
|
||||||
|
{
|
||||||
|
typedef typename SmallerAccessSize<T>::value ST;
|
||||||
|
|
||||||
|
const WriteHandler<ST>* high_part;
|
||||||
|
const WriteHandler<ST>* low_part;
|
||||||
|
mmio->GetHandlerForWrite(high_part_addr, &high_part);
|
||||||
|
mmio->GetHandlerForWrite(low_part_addr, &low_part);
|
||||||
|
|
||||||
|
// TODO(delroth): optimize
|
||||||
|
return ComplexWrite<T>([high_part, low_part](u32 addr, T val) {
|
||||||
|
high_part->Write(addr, val >> (8 * sizeof (ST)));
|
||||||
|
low_part->Write(addr, (ST)val);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
ReadHandlingMethod<T>* ReadToLarger(Mapping* mmio, u32 larger_addr, u32 shift)
|
||||||
|
{
|
||||||
|
typedef typename LargerAccessSize<T>::value LT;
|
||||||
|
|
||||||
|
const ReadHandler<LT>* large;
|
||||||
|
mmio->GetHandlerForRead(larger_addr, &large);
|
||||||
|
|
||||||
|
// TODO(delroth): optimize
|
||||||
|
return ComplexRead<T>([large, shift](u32 addr) {
|
||||||
|
return large->Read(addr & ~(sizeof (LT) - 1)) >> shift;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inplementation of the ReadHandler and WriteHandler class. There is a lot of
|
||||||
|
// redundant code between these two classes but trying to abstract it away
|
||||||
|
// brings more trouble than it fixes.
|
||||||
|
template <typename T>
|
||||||
|
ReadHandler<T>::ReadHandler() : m_Method(nullptr)
|
||||||
|
{
|
||||||
|
ResetMethod(InvalidRead<T>());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
ReadHandler<T>::ReadHandler(ReadHandlingMethod<T>* method)
|
||||||
|
: m_Method(nullptr)
|
||||||
|
{
|
||||||
|
ResetMethod(method);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
ReadHandler<T>::~ReadHandler()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void ReadHandler<T>::Visit(ReadHandlingMethodVisitor<T>& visitor) const
|
||||||
|
{
|
||||||
|
m_Method->AcceptReadVisitor(visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void ReadHandler<T>::ResetMethod(ReadHandlingMethod<T>* method)
|
||||||
|
{
|
||||||
|
m_Method.reset(method);
|
||||||
|
|
||||||
|
struct FuncCreatorVisitor : public ReadHandlingMethodVisitor<T>
|
||||||
|
{
|
||||||
|
std::function<T(u32)> ret;
|
||||||
|
|
||||||
|
virtual void VisitConstant(T value)
|
||||||
|
{
|
||||||
|
ret = [value](u32) { return value; };
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void VisitDirect(const T* addr, u32 mask)
|
||||||
|
{
|
||||||
|
ret = [addr, mask](u32) { return *addr & mask; };
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void VisitComplex(std::function<T(u32)> lambda)
|
||||||
|
{
|
||||||
|
ret = lambda;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
FuncCreatorVisitor v;
|
||||||
|
Visit(v);
|
||||||
|
m_ReadFunc = v.ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
WriteHandler<T>::WriteHandler() : m_Method(nullptr)
|
||||||
|
{
|
||||||
|
ResetMethod(InvalidWrite<T>());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
WriteHandler<T>::WriteHandler(WriteHandlingMethod<T>* method)
|
||||||
|
: m_Method(nullptr)
|
||||||
|
{
|
||||||
|
ResetMethod(method);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
WriteHandler<T>::~WriteHandler()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void WriteHandler<T>::Visit(WriteHandlingMethodVisitor<T>& visitor) const
|
||||||
|
{
|
||||||
|
m_Method->AcceptWriteVisitor(visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void WriteHandler<T>::ResetMethod(WriteHandlingMethod<T>* method)
|
||||||
|
{
|
||||||
|
m_Method.reset(method);
|
||||||
|
|
||||||
|
struct FuncCreatorVisitor : public WriteHandlingMethodVisitor<T>
|
||||||
|
{
|
||||||
|
std::function<void(u32, T)> ret;
|
||||||
|
|
||||||
|
virtual void VisitNop()
|
||||||
|
{
|
||||||
|
ret = [](u32, T) {};
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void VisitDirect(T* ptr, u32 mask)
|
||||||
|
{
|
||||||
|
ret = [ptr, mask](u32, T val) { *ptr = val & mask; };
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void VisitComplex(std::function<void(u32, T)> lambda)
|
||||||
|
{
|
||||||
|
ret = lambda;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
FuncCreatorVisitor v;
|
||||||
|
Visit(v);
|
||||||
|
m_WriteFunc = v.ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define all the public specializations that are exported in MMIOHandlers.h.
|
||||||
|
MMIO_PUBLIC_SPECIALIZATIONS();
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,156 @@
|
||||||
|
// Copyright 2013 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Common.h"
|
||||||
|
#include <array>
|
||||||
|
#include <string>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
#include "MMIOHandlers.h"
|
||||||
|
|
||||||
|
namespace MMIO
|
||||||
|
{
|
||||||
|
|
||||||
|
// There are three main MMIO blocks on the Wii (only one on the GameCube):
|
||||||
|
// - 0xCC00xxxx: GameCube MMIOs (CP, PE, VI, PI, MI, DSP, DVD, SI, EI, AI, GP)
|
||||||
|
// - 0xCD00xxxx: Wii MMIOs and GC mirrors (IPC, DVD, SI, EI, AI)
|
||||||
|
// - 0xCD80xxxx: Mirror of 0xCD00xxxx.
|
||||||
|
//
|
||||||
|
// In practice, since the third block is a mirror of the second one, we can
|
||||||
|
// assume internally that there are only two blocks: one for GC, one for Wii.
|
||||||
|
enum Block
|
||||||
|
{
|
||||||
|
GC_BLOCK = 0,
|
||||||
|
WII_BLOCK = 1,
|
||||||
|
|
||||||
|
NUM_BLOCKS
|
||||||
|
};
|
||||||
|
const u32 BLOCK_SIZE = 0x10000;
|
||||||
|
const u32 NUM_MMIOS = NUM_BLOCKS * BLOCK_SIZE;
|
||||||
|
|
||||||
|
// Compute the internal unique ID for a given MMIO address. This ID is computed
|
||||||
|
// from a very simple formula: (1 + block_id) * lower_16_bits(address).
|
||||||
|
//
|
||||||
|
// The block ID can easily be computed by simply checking bit 24 (CC vs. CD).
|
||||||
|
inline u32 UniqueID(u32 address)
|
||||||
|
{
|
||||||
|
_dbg_assert_msg_(MEMMAP, ((address & 0xFFFF0000) == 0xCC000000) ||
|
||||||
|
((address & 0xFFFF0000) == 0xCD000000) ||
|
||||||
|
((address & 0xFFFF0000) == 0xCD800000),
|
||||||
|
"Trying to get the ID of a non-existing MMIO address.");
|
||||||
|
|
||||||
|
return (1 + ((address >> 24) & 1)) * (address & 0xFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some utilities functions to define MMIO mappings.
|
||||||
|
namespace Utils
|
||||||
|
{
|
||||||
|
// Allow grabbing pointers to the high and low part of a 32 bits pointer.
|
||||||
|
inline u16* LowPart(u32* ptr) { return (u16*)ptr; }
|
||||||
|
inline u16* LowPart(volatile u32* ptr) { return (u16*)ptr; }
|
||||||
|
inline u16* HighPart(u32* ptr) { return LowPart(ptr) + 1; }
|
||||||
|
inline u16* HighPart(volatile u32* ptr) { return LowPart(ptr) + 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class Mapping
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// MMIO registration interface. Use this to register new MMIO handlers.
|
||||||
|
//
|
||||||
|
// Example usages can be found in just about any HW/ module in Dolphin's
|
||||||
|
// codebase.
|
||||||
|
#define REGISTER_FUNCS(Size) \
|
||||||
|
void RegisterRead(u32 addr, ReadHandlingMethod<u##Size>* read) \
|
||||||
|
{ \
|
||||||
|
u32 id = UniqueID(addr) / sizeof (u##Size); \
|
||||||
|
m_Read##Size##Handlers[id].ResetMethod(read); \
|
||||||
|
} \
|
||||||
|
void RegisterWrite(u32 addr, WriteHandlingMethod<u##Size>* write) \
|
||||||
|
{ \
|
||||||
|
u32 id = UniqueID(addr) / sizeof (u##Size); \
|
||||||
|
m_Write##Size##Handlers[id].ResetMethod(write); \
|
||||||
|
} \
|
||||||
|
void Register(u32 addr, ReadHandlingMethod<u##Size>* read, \
|
||||||
|
WriteHandlingMethod<u##Size>* write) \
|
||||||
|
{ \
|
||||||
|
RegisterRead(addr, read); \
|
||||||
|
RegisterWrite(addr, write); \
|
||||||
|
}
|
||||||
|
REGISTER_FUNCS(8) REGISTER_FUNCS(16) REGISTER_FUNCS(32)
|
||||||
|
#undef REGISTER_FUNCS
|
||||||
|
|
||||||
|
// Direct read/write interface.
|
||||||
|
//
|
||||||
|
// These functions allow reading/writing an MMIO register at a given
|
||||||
|
// address. They are used by the Memory:: access functions, which are
|
||||||
|
// called in interpreter mode, from Dolphin's own code, or from JIT'd code
|
||||||
|
// where the access address could not be predicted.
|
||||||
|
//
|
||||||
|
// Note that for reads we cannot simply return the read value because C++
|
||||||
|
// allows overloading only with parameter types, not return types.
|
||||||
|
#define READ_FUNC(Size) \
|
||||||
|
void Read(u32 addr, u##Size& val) const \
|
||||||
|
{ \
|
||||||
|
u32 id = UniqueID(addr) / sizeof (u##Size); \
|
||||||
|
val = m_Read##Size##Handlers[id].Read(addr); \
|
||||||
|
}
|
||||||
|
READ_FUNC(8) READ_FUNC(16) READ_FUNC(32)
|
||||||
|
#undef READ_FUNC
|
||||||
|
|
||||||
|
#define WRITE_FUNC(Size) \
|
||||||
|
void Write(u32 addr, u##Size val) const \
|
||||||
|
{ \
|
||||||
|
u32 id = UniqueID(addr) / sizeof (u##Size); \
|
||||||
|
m_Write##Size##Handlers[id].Write(addr, val); \
|
||||||
|
}
|
||||||
|
WRITE_FUNC(8) WRITE_FUNC(16) WRITE_FUNC(32)
|
||||||
|
#undef WRITE_FUNC
|
||||||
|
|
||||||
|
// Handlers access interface.
|
||||||
|
//
|
||||||
|
// Use when you care more about how to access the MMIO register for an
|
||||||
|
// address than the current value of that register. For example, this is
|
||||||
|
// what could be used to implement fast MMIO accesses in Dolphin's JIT.
|
||||||
|
//
|
||||||
|
// Two variants of each GetHandler function are provided: one that returns
|
||||||
|
// the handler directly and one that has a pointer parameter to return the
|
||||||
|
// value. This second variant is needed because C++ doesn't do overloads
|
||||||
|
// based on return type but only based on argument types.
|
||||||
|
#define GET_HANDLERS_FUNC(Type, Size) \
|
||||||
|
const Type##Handler<u##Size>& GetHandlerFor##Type##Size(u32 addr) const \
|
||||||
|
{ \
|
||||||
|
return m_##Type##Size##Handlers[UniqueID(addr) / sizeof (u##Size)]; \
|
||||||
|
} \
|
||||||
|
void GetHandlerFor##Type(u32 addr, const Type##Handler<u##Size>** h) const \
|
||||||
|
{ \
|
||||||
|
*h = &GetHandlerFor##Type##Size(addr); \
|
||||||
|
}
|
||||||
|
GET_HANDLERS_FUNC(Read, 8) GET_HANDLERS_FUNC(Read, 16) GET_HANDLERS_FUNC(Read, 32)
|
||||||
|
GET_HANDLERS_FUNC(Write, 8) GET_HANDLERS_FUNC(Write, 16) GET_HANDLERS_FUNC(Write, 32)
|
||||||
|
#undef GET_HANDLERS_FUNC
|
||||||
|
|
||||||
|
// Dummy 64 bits variants of these functions. While 64 bits MMIO access is
|
||||||
|
// not supported, we need these in order to make the code compile.
|
||||||
|
void Read(u32 addr, u64& val) const { _dbg_assert_(MEMMAP, 0); }
|
||||||
|
void Write(u32 addr, u64 val) const { _dbg_assert_(MEMMAP, 0); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
// These arrays contain the handlers for each MMIO access type: read/write
|
||||||
|
// to 8/16/32 bits. They are indexed using the UniqueID(addr) function
|
||||||
|
// defined earlier, which maps an MMIO address to a unique ID by using the
|
||||||
|
// MMIO block ID.
|
||||||
|
//
|
||||||
|
// Each array contains NUM_MMIOS / sizeof (AccessType) because larger
|
||||||
|
// access types mean less possible adresses (assuming aligned only
|
||||||
|
// accesses).
|
||||||
|
#define HANDLERS(Size) \
|
||||||
|
std::array<ReadHandler<u##Size>, NUM_MMIOS / sizeof (u##Size)> m_Read##Size##Handlers; \
|
||||||
|
std::array<WriteHandler<u##Size>, NUM_MMIOS / sizeof (u##Size)> m_Write##Size##Handlers;
|
||||||
|
HANDLERS(8) HANDLERS(16) HANDLERS(32)
|
||||||
|
#undef HANDLERS
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,201 @@
|
||||||
|
// Copyright 2013 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Common.h"
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
// All the templated and very repetitive MMIO-related code is isolated in this
|
||||||
|
// file for easier reading. It mostly contains code related to handling methods
|
||||||
|
// (including the declaration of the public functions for creating handling
|
||||||
|
// method objects), visitors for these handling methods, and interface of the
|
||||||
|
// handler classes.
|
||||||
|
//
|
||||||
|
// This code is very genericized (aka. lots of templates) in order to handle
|
||||||
|
// u8/u16/u32 with the same code while providing type safety: it is impossible
|
||||||
|
// to mix code from these types, and the type system enforces it.
|
||||||
|
|
||||||
|
namespace MMIO
|
||||||
|
{
|
||||||
|
|
||||||
|
class Mapping;
|
||||||
|
|
||||||
|
// Read and write handling methods are separated for type safety. On top of
|
||||||
|
// that, some handling methods require different arguments for reads and writes
|
||||||
|
// (Complex, for example).
|
||||||
|
template <typename T> class ReadHandlingMethod;
|
||||||
|
template <typename T> class WriteHandlingMethod;
|
||||||
|
|
||||||
|
// Constant: use when the value read on this MMIO is always the same. This is
|
||||||
|
// only for reads.
|
||||||
|
template <typename T> ReadHandlingMethod<T>* Constant(T value);
|
||||||
|
|
||||||
|
// Nop: use for writes that shouldn't have any effect and shouldn't log an
|
||||||
|
// error either.
|
||||||
|
template <typename T> WriteHandlingMethod<T>* Nop();
|
||||||
|
|
||||||
|
// Direct: use when all the MMIO does is read/write the given value to/from a
|
||||||
|
// global variable, with an optional mask applied on the read/written value.
|
||||||
|
template <typename T> ReadHandlingMethod<T>* DirectRead(const T* addr, u32 mask = 0xFFFFFFFF);
|
||||||
|
template <typename T> ReadHandlingMethod<T>* DirectRead(volatile const T* addr, u32 mask = 0xFFFFFFFF);
|
||||||
|
template <typename T> WriteHandlingMethod<T>* DirectWrite(T* addr, u32 mask = 0xFFFFFFFF);
|
||||||
|
template <typename T> WriteHandlingMethod<T>* DirectWrite(volatile T* addr, u32 mask = 0xFFFFFFFF);
|
||||||
|
|
||||||
|
// Complex: use when no other handling method fits your needs. These allow you
|
||||||
|
// to directly provide a function that will be called when a read/write needs
|
||||||
|
// to be done.
|
||||||
|
template <typename T> ReadHandlingMethod<T>* ComplexRead(std::function<T(u32)>);
|
||||||
|
template <typename T> WriteHandlingMethod<T>* ComplexWrite(std::function<void(u32, T)>);
|
||||||
|
|
||||||
|
// Invalid: log an error and return -1 in case of a read. These are the default
|
||||||
|
// handlers set for all MMIO types.
|
||||||
|
template <typename T> ReadHandlingMethod<T>* InvalidRead();
|
||||||
|
template <typename T> WriteHandlingMethod<T>* InvalidWrite();
|
||||||
|
|
||||||
|
// {Read,Write}To{Smaller,Larger}: these functions are not themselves handling
|
||||||
|
// methods but will try to combine accesses to two handlers into one new
|
||||||
|
// handler object.
|
||||||
|
//
|
||||||
|
// This is used for example when 32 bit reads have the exact same handling as
|
||||||
|
// 16 bit. Handlers need to be registered for both 32 and 16, and it would be
|
||||||
|
// repetitive and unoptimal to require users to write the same handling code in
|
||||||
|
// both cases. Instead, an MMIO module can simply define all handlers in terms
|
||||||
|
// of 16 bit reads, then use ReadToSmaller<u32> to convert u32 reads to u16
|
||||||
|
// reads.
|
||||||
|
//
|
||||||
|
// Internally, these size conversion functions have some magic to make the
|
||||||
|
// combined handlers as fast as possible. For example, if the two underlying
|
||||||
|
// u16 handlers for a u32 reads are Direct to consecutive memory addresses,
|
||||||
|
// they can be transformed into a Direct u32 access.
|
||||||
|
//
|
||||||
|
// Warning: unlike the other handling methods, *ToSmaller are obviously not
|
||||||
|
// available for u8, and *ToLarger are not available for u32.
|
||||||
|
template <typename T> ReadHandlingMethod<T>* ReadToSmaller(Mapping* mmio, u32 high_part_addr, u32 low_part_addr);
|
||||||
|
template <typename T> WriteHandlingMethod<T>* WriteToSmaller(Mapping* mmio, u32 high_part_addr, u32 low_part_addr);
|
||||||
|
template <typename T> ReadHandlingMethod<T>* ReadToLarger(Mapping* mmio, u32 larger_addr, u32 shift);
|
||||||
|
|
||||||
|
// Use these visitors interfaces if you need to write code that performs
|
||||||
|
// different actions based on the handling method used by a handler. Write your
|
||||||
|
// visitor implementing that interface, then use handler->VisitHandlingMethod
|
||||||
|
// to run the proper function.
|
||||||
|
template <typename T>
|
||||||
|
class ReadHandlingMethodVisitor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual void VisitConstant(T value) = 0;
|
||||||
|
virtual void VisitDirect(const T* addr, u32 mask) = 0;
|
||||||
|
virtual void VisitComplex(std::function<T(u32)> lambda) = 0;
|
||||||
|
};
|
||||||
|
template <typename T>
|
||||||
|
class WriteHandlingMethodVisitor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual void VisitNop() = 0;
|
||||||
|
virtual void VisitDirect(T* addr, u32 mask) = 0;
|
||||||
|
virtual void VisitComplex(std::function<void(u32, T)> lambda) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// These classes are INTERNAL. Do not use outside of the MMIO implementation
|
||||||
|
// code. Unfortunately, because we want to make Read() and Write() fast and
|
||||||
|
// inlinable, we need to provide some of the implementation of these two
|
||||||
|
// classes here and can't just use a forward declaration.
|
||||||
|
template <typename T>
|
||||||
|
class ReadHandler : public NonCopyable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ReadHandler();
|
||||||
|
|
||||||
|
// Takes ownership of "method".
|
||||||
|
ReadHandler(ReadHandlingMethod<T>* method);
|
||||||
|
|
||||||
|
~ReadHandler();
|
||||||
|
|
||||||
|
// Entry point for read handling method visitors.
|
||||||
|
void Visit(ReadHandlingMethodVisitor<T>& visitor) const;
|
||||||
|
|
||||||
|
T Read(u32 addr) const
|
||||||
|
{
|
||||||
|
return m_ReadFunc(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal method called when changing the internal method object. Its
|
||||||
|
// main role is to make sure the read function is updated at the same time.
|
||||||
|
void ResetMethod(ReadHandlingMethod<T>* method);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<ReadHandlingMethod<T>> m_Method;
|
||||||
|
std::function<T(u32)> m_ReadFunc;
|
||||||
|
};
|
||||||
|
template <typename T>
|
||||||
|
class WriteHandler : public NonCopyable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WriteHandler();
|
||||||
|
|
||||||
|
// Takes ownership of "method".
|
||||||
|
WriteHandler(WriteHandlingMethod<T>* method);
|
||||||
|
|
||||||
|
~WriteHandler();
|
||||||
|
|
||||||
|
// Entry point for write handling method visitors.
|
||||||
|
void Visit(WriteHandlingMethodVisitor<T>& visitor) const;
|
||||||
|
|
||||||
|
void Write(u32 addr, T val) const
|
||||||
|
{
|
||||||
|
m_WriteFunc(addr, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal method called when changing the internal method object. Its
|
||||||
|
// main role is to make sure the write function is updated at the same
|
||||||
|
// time.
|
||||||
|
void ResetMethod(WriteHandlingMethod<T>* method);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<WriteHandlingMethod<T>> m_Method;
|
||||||
|
std::function<void(u32, T)> m_WriteFunc;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Boilerplate boilerplate boilerplate.
|
||||||
|
//
|
||||||
|
// This is used to be able to avoid putting the templates implementation in the
|
||||||
|
// header files and slow down compilation times. Instead, we declare 3
|
||||||
|
// specializations in the header file as already implemented in another
|
||||||
|
// compilation unit: u8, u16, u32.
|
||||||
|
//
|
||||||
|
// The "MaybeExtern" is there because that same macro is used for declaration
|
||||||
|
// (where MaybeExtern = "extern") and definition (MaybeExtern = "").
|
||||||
|
#define MMIO_GENERIC_PUBLIC_SPECIALIZATIONS(MaybeExtern, T) \
|
||||||
|
MaybeExtern template ReadHandlingMethod<T>* Constant<T>(T value); \
|
||||||
|
MaybeExtern template WriteHandlingMethod<T>* Nop<T>(); \
|
||||||
|
MaybeExtern template ReadHandlingMethod<T>* DirectRead(const T* addr, u32 mask); \
|
||||||
|
MaybeExtern template ReadHandlingMethod<T>* DirectRead(volatile const T* addr, u32 mask); \
|
||||||
|
MaybeExtern template WriteHandlingMethod<T>* DirectWrite(T* addr, u32 mask); \
|
||||||
|
MaybeExtern template WriteHandlingMethod<T>* DirectWrite(volatile T* addr, u32 mask); \
|
||||||
|
MaybeExtern template ReadHandlingMethod<T>* ComplexRead<T>(std::function<T(u32)>); \
|
||||||
|
MaybeExtern template WriteHandlingMethod<T>* ComplexWrite<T>(std::function<void(u32, T)>); \
|
||||||
|
MaybeExtern template ReadHandlingMethod<T>* InvalidRead<T>(); \
|
||||||
|
MaybeExtern template WriteHandlingMethod<T>* InvalidWrite<T>(); \
|
||||||
|
MaybeExtern template class ReadHandler<T>; \
|
||||||
|
MaybeExtern template class WriteHandler<T>
|
||||||
|
|
||||||
|
#define MMIO_SPECIAL_PUBLIC_SPECIALIZATIONS(MaybeExtern) \
|
||||||
|
MaybeExtern template ReadHandlingMethod<u16>* ReadToSmaller(Mapping* mmio, u32 high_part_addr, u32 low_part_addr); \
|
||||||
|
MaybeExtern template ReadHandlingMethod<u32>* ReadToSmaller(Mapping* mmio, u32 high_part_addr, u32 low_part_addr); \
|
||||||
|
MaybeExtern template WriteHandlingMethod<u16>* WriteToSmaller(Mapping* mmio, u32 high_part_addr, u32 low_part_addr); \
|
||||||
|
MaybeExtern template WriteHandlingMethod<u32>* WriteToSmaller(Mapping* mmio, u32 high_part_addr, u32 low_part_addr); \
|
||||||
|
MaybeExtern template ReadHandlingMethod<u8>* ReadToLarger(Mapping* mmio, u32 larger_addr, u32 shift); \
|
||||||
|
MaybeExtern template ReadHandlingMethod<u16>* ReadToLarger(Mapping* mmio, u32 larger_addr, u32 shift)
|
||||||
|
|
||||||
|
#define MMIO_PUBLIC_SPECIALIZATIONS(MaybeExtern) \
|
||||||
|
MMIO_GENERIC_PUBLIC_SPECIALIZATIONS(MaybeExtern, u8); \
|
||||||
|
MMIO_GENERIC_PUBLIC_SPECIALIZATIONS(MaybeExtern, u16); \
|
||||||
|
MMIO_GENERIC_PUBLIC_SPECIALIZATIONS(MaybeExtern, u32); \
|
||||||
|
MMIO_SPECIAL_PUBLIC_SPECIALIZATIONS(MaybeExtern);
|
||||||
|
|
||||||
|
MMIO_PUBLIC_SPECIALIZATIONS(extern)
|
||||||
|
|
||||||
|
}
|
|
@ -34,6 +34,7 @@
|
||||||
#include "../ConfigManager.h"
|
#include "../ConfigManager.h"
|
||||||
#include "../Debugger/Debugger_SymbolMap.h"
|
#include "../Debugger/Debugger_SymbolMap.h"
|
||||||
#include "VideoBackendBase.h"
|
#include "VideoBackendBase.h"
|
||||||
|
#include "MMIO.h"
|
||||||
|
|
||||||
namespace Memory
|
namespace Memory
|
||||||
{
|
{
|
||||||
|
@ -83,6 +84,9 @@ u8 *m_pVirtualUncachedEXRAM; // wii only
|
||||||
u8 *m_pVirtualL1Cache;
|
u8 *m_pVirtualL1Cache;
|
||||||
u8 *m_pVirtualFakeVMEM;
|
u8 *m_pVirtualFakeVMEM;
|
||||||
|
|
||||||
|
// MMIO mapping object.
|
||||||
|
MMIO::Mapping* mmio_mapping;
|
||||||
|
|
||||||
// =================================
|
// =================================
|
||||||
// Read and write shortcuts
|
// Read and write shortcuts
|
||||||
// ----------------
|
// ----------------
|
||||||
|
@ -348,6 +352,8 @@ void Init()
|
||||||
if (bFakeVMEM) flags |= MV_FAKE_VMEM;
|
if (bFakeVMEM) flags |= MV_FAKE_VMEM;
|
||||||
base = MemoryMap_Setup(views, num_views, flags, &g_arena);
|
base = MemoryMap_Setup(views, num_views, flags, &g_arena);
|
||||||
|
|
||||||
|
mmio_mapping = new MMIO::Mapping();
|
||||||
|
|
||||||
if (wii)
|
if (wii)
|
||||||
InitHWMemFuncsWii();
|
InitHWMemFuncsWii();
|
||||||
else
|
else
|
||||||
|
@ -382,6 +388,7 @@ void Shutdown()
|
||||||
MemoryMap_Shutdown(views, num_views, flags, &g_arena);
|
MemoryMap_Shutdown(views, num_views, flags, &g_arena);
|
||||||
g_arena.ReleaseSpace();
|
g_arena.ReleaseSpace();
|
||||||
base = NULL;
|
base = NULL;
|
||||||
|
delete mmio_mapping;
|
||||||
INFO_LOG(MEMMAP, "Memory system shut down.");
|
INFO_LOG(MEMMAP, "Memory system shut down.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
|
|
||||||
// Global declarations
|
// Global declarations
|
||||||
class PointerWrap;
|
class PointerWrap;
|
||||||
|
namespace MMIO { class Mapping; }
|
||||||
|
|
||||||
typedef void (*writeFn8 )(const u8, const u32);
|
typedef void (*writeFn8 )(const u8, const u32);
|
||||||
typedef void (*writeFn16)(const u16,const u32);
|
typedef void (*writeFn16)(const u16,const u32);
|
||||||
|
@ -83,6 +84,9 @@ enum
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// MMIO mapping object.
|
||||||
|
extern MMIO::Mapping* mmio_mapping;
|
||||||
|
|
||||||
// Init and Shutdown
|
// Init and Shutdown
|
||||||
bool IsInitialized();
|
bool IsInitialized();
|
||||||
void Init();
|
void Init();
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
#include "../Core.h"
|
#include "../Core.h"
|
||||||
#include "../PowerPC/PowerPC.h"
|
#include "../PowerPC/PowerPC.h"
|
||||||
#include "VideoBackendBase.h"
|
#include "VideoBackendBase.h"
|
||||||
|
#include "MMIO.h"
|
||||||
|
|
||||||
#ifdef USE_GDBSTUB
|
#ifdef USE_GDBSTUB
|
||||||
#include "../PowerPC/GDBStub.h"
|
#include "../PowerPC/GDBStub.h"
|
||||||
|
|
Loading…
Reference in New Issue