diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 861b711c7..edf5fcc44 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -250,6 +250,7 @@ set(HEADERS tracer/citrace.h memory.h memory_setup.h + mmio.h settings.h system.h ) diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp index 2610acf76..1e289f38a 100644 --- a/src/core/hle/kernel/vm_manager.cpp +++ b/src/core/hle/kernel/vm_manager.cpp @@ -8,6 +8,7 @@ #include "core/hle/kernel/vm_manager.h" #include "core/memory_setup.h" +#include "core/mmio.h" namespace Kernel { @@ -104,7 +105,7 @@ ResultVal VMManager::MapBackingMemory(VAddr target, u8 * m return MakeResult(MergeAdjacent(vma_handle)); } -ResultVal VMManager::MapMMIO(VAddr target, PAddr paddr, u32 size, MemoryState state) { +ResultVal VMManager::MapMMIO(VAddr target, PAddr paddr, u32 size, MemoryState state, Memory::MMIORegionPointer mmio_handler) { // This is the appropriately sized VMA that will turn into our allocation. CASCADE_RESULT(VMAIter vma_handle, CarveVMA(target, size)); VirtualMemoryArea& final_vma = vma_handle->second; @@ -114,6 +115,7 @@ ResultVal VMManager::MapMMIO(VAddr target, PAddr paddr, u3 final_vma.permissions = VMAPermission::ReadWrite; final_vma.meminfo_state = state; final_vma.paddr = paddr; + final_vma.mmio_handler = mmio_handler; UpdatePageTableForVMA(final_vma); return MakeResult(MergeAdjacent(vma_handle)); @@ -330,8 +332,7 @@ void VMManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) { Memory::MapMemoryRegion(vma.base, vma.size, vma.backing_memory); break; case VMAType::MMIO: - // TODO(yuriks): Add support for MMIO handlers. - Memory::MapIoRegion(vma.base, vma.size); + Memory::MapIoRegion(vma.base, vma.size, vma.mmio_handler); break; } } diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h index 4e95f1f0c..91d40655b 100644 --- a/src/core/hle/kernel/vm_manager.h +++ b/src/core/hle/kernel/vm_manager.h @@ -11,6 +11,7 @@ #include "common/common_types.h" #include "core/hle/result.h" +#include "core/mmio.h" namespace Kernel { @@ -92,6 +93,7 @@ struct VirtualMemoryArea { // Settings for type = MMIO /// Physical address of the register area this VMA maps to. PAddr paddr = 0; + Memory::MMIORegionPointer mmio_handler = nullptr; /// Tests if this area can be merged to the right with `next`. bool CanBeMergedWith(const VirtualMemoryArea& next) const; @@ -168,8 +170,9 @@ public: * @param paddr The physical address where the registers are present. * @param size Size of the mapping. * @param state MemoryState tag to attach to the VMA. + * @param mmio_handler The handler that will implement read and write for this MMIO region. */ - ResultVal MapMMIO(VAddr target, PAddr paddr, u32 size, MemoryState state); + ResultVal MapMMIO(VAddr target, PAddr paddr, u32 size, MemoryState state, Memory::MMIORegionPointer mmio_handler); /// Unmaps a range of addresses, splitting VMAs as necessary. ResultCode UnmapRange(VAddr target, u32 size); diff --git a/src/core/memory.cpp b/src/core/memory.cpp index fc79c3ee9..4753c63a7 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -13,6 +13,7 @@ #include "core/hle/kernel/process.h" #include "core/memory.h" #include "core/memory_setup.h" +#include "core/mmio.h" namespace Memory { @@ -25,6 +26,12 @@ enum class PageType { Special, }; +struct SpecialRegion { + VAddr base; + u32 size; + MMIORegionPointer handler; +}; + /** * A (reasonably) fast way of allowing switchable and remappable process address spaces. It loosely * mimics the way a real CPU page table works, but instead is optimized for minimal decoding and @@ -40,9 +47,14 @@ struct PageTable { */ std::array pointers; + /** + * Contains MMIO handlers that back memory regions whose entries in the `attribute` array is of type `Special`. + */ + std::vector special_regions; + /** * Array of fine grained page attributes. If it is set to any value other than `Memory`, then - * the corresponding entry in `pointer` MUST be set to null. + * the corresponding entry in `pointers` MUST be set to null. */ std::array attributes; }; @@ -80,10 +92,12 @@ void MapMemoryRegion(VAddr base, u32 size, u8* target) { MapPages(base / PAGE_SIZE, size / PAGE_SIZE, target, PageType::Memory); } -void MapIoRegion(VAddr base, u32 size) { +void MapIoRegion(VAddr base, u32 size, MMIORegionPointer mmio_handler) { ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: %08X", size); ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: %08X", base); MapPages(base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Special); + + current_page_table->special_regions.emplace_back(SpecialRegion{base, size, mmio_handler}); } void UnmapRegion(VAddr base, u32 size) { @@ -92,6 +106,22 @@ void UnmapRegion(VAddr base, u32 size) { MapPages(base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Unmapped); } +/** + * This function should only be called for virtual addreses with attribute `PageType::Special`. + */ +static MMIORegionPointer GetMMIOHandler(VAddr vaddr) { + for (const auto& region : current_page_table->special_regions) { + if (vaddr >= region.base && vaddr < (region.base + region.size)) { + return region.handler; + } + } + ASSERT_MSG(false, "Mapped IO page without a handler @ %08X", vaddr); + return nullptr; // Should never happen +} + +template +T ReadMMIO(MMIORegionPointer mmio_handler, VAddr addr); + template T Read(const VAddr vaddr) { const u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS]; @@ -108,14 +138,17 @@ T Read(const VAddr vaddr) { return 0; case PageType::Memory: ASSERT_MSG(false, "Mapped memory page without a pointer @ %08X", vaddr); + break; case PageType::Special: - LOG_ERROR(HW_Memory, "I/O reads aren't implemented yet @ %08X", vaddr); - return 0; + return ReadMMIO(GetMMIOHandler(vaddr), vaddr); default: UNREACHABLE(); } } +template +void WriteMMIO(MMIORegionPointer mmio_handler, VAddr addr, const T data); + template void Write(const VAddr vaddr, const T data) { u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS]; @@ -131,9 +164,10 @@ void Write(const VAddr vaddr, const T data) { return; case PageType::Memory: ASSERT_MSG(false, "Mapped memory page without a pointer @ %08X", vaddr); + break; case PageType::Special: - LOG_ERROR(HW_Memory, "I/O writes aren't implemented yet @ %08X", vaddr); - return; + WriteMMIO(GetMMIOHandler(vaddr), vaddr, data); + break; default: UNREACHABLE(); } @@ -191,6 +225,46 @@ void WriteBlock(const VAddr addr, const u8* data, const size_t size) { } } +template<> +u8 ReadMMIO(MMIORegionPointer mmio_handler, VAddr addr) { + return mmio_handler->Read8(addr); +} + +template<> +u16 ReadMMIO(MMIORegionPointer mmio_handler, VAddr addr) { + return mmio_handler->Read16(addr); +} + +template<> +u32 ReadMMIO(MMIORegionPointer mmio_handler, VAddr addr) { + return mmio_handler->Read32(addr); +} + +template<> +u64 ReadMMIO(MMIORegionPointer mmio_handler, VAddr addr) { + return mmio_handler->Read64(addr); +} + +template<> +void WriteMMIO(MMIORegionPointer mmio_handler, VAddr addr, const u8 data) { + mmio_handler->Write8(addr, data); +} + +template<> +void WriteMMIO(MMIORegionPointer mmio_handler, VAddr addr, const u16 data) { + mmio_handler->Write16(addr, data); +} + +template<> +void WriteMMIO(MMIORegionPointer mmio_handler, VAddr addr, const u32 data) { + mmio_handler->Write32(addr, data); +} + +template<> +void WriteMMIO(MMIORegionPointer mmio_handler, VAddr addr, const u64 data) { + mmio_handler->Write64(addr, data); +} + PAddr VirtualToPhysicalAddress(const VAddr addr) { if (addr == 0) { return 0; diff --git a/src/core/memory_setup.h b/src/core/memory_setup.h index 84ff30120..05f70a1fe 100644 --- a/src/core/memory_setup.h +++ b/src/core/memory_setup.h @@ -23,10 +23,11 @@ void MapMemoryRegion(VAddr base, u32 size, u8* target); /** * Maps a region of the emulated process address space as a IO region. - * @note Currently this can only be used to mark a region as being IO, since actual memory-mapped - * IO isn't yet supported. + * @param base The address to start mapping at. Must be page-aligned. + * @param size The amount of bytes to map. Must be page-aligned. + * @param mmio_handler The handler that backs the mapping. */ -void MapIoRegion(VAddr base, u32 size); +void MapIoRegion(VAddr base, u32 size, MMIORegionPointer mmio_handler); void UnmapRegion(VAddr base, u32 size); diff --git a/src/core/mmio.h b/src/core/mmio.h new file mode 100644 index 000000000..06b555e98 --- /dev/null +++ b/src/core/mmio.h @@ -0,0 +1,34 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "common/common_types.h" + +namespace Memory { + +/** + * Represents a device with memory mapped IO. + * A device may be mapped to multiple regions of memory. + */ +class MMIORegion { +public: + virtual ~MMIORegion() = default; + + virtual u8 Read8(VAddr addr) = 0; + virtual u16 Read16(VAddr addr) = 0; + virtual u32 Read32(VAddr addr) = 0; + virtual u64 Read64(VAddr addr) = 0; + + virtual void Write8(VAddr addr, u8 data) = 0; + virtual void Write16(VAddr addr, u16 data) = 0; + virtual void Write32(VAddr addr, u32 data) = 0; + virtual void Write64(VAddr addr, u64 data) = 0; +}; + +using MMIORegionPointer = std::shared_ptr; + +};