summaryrefslogtreecommitdiffstats
path: root/src/core/hle/kernel/shared_memory.cpp
blob: 2b6007caa3c9b75cc6c5336ed332a9e20212d030 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.

#include <cstring>

#include "common/logging/log.h"

#include "core/memory.h"
#include "core/hle/kernel/memory.h"
#include "core/hle/kernel/shared_memory.h"

namespace Kernel {

SharedMemory::SharedMemory() {}
SharedMemory::~SharedMemory() {}

SharedPtr<SharedMemory> SharedMemory::Create(SharedPtr<Process> owner_process, u32 size, MemoryPermission permissions,
        MemoryPermission other_permissions, VAddr address, MemoryRegion region, std::string name) {
    SharedPtr<SharedMemory> shared_memory(new SharedMemory);

    shared_memory->owner_process = owner_process;
    shared_memory->name = std::move(name);
    shared_memory->size = size;
    shared_memory->permissions = permissions;
    shared_memory->other_permissions = other_permissions;

    if (address == 0) {
        // We need to allocate a block from the Linear Heap ourselves.
        // We'll manually allocate some memory from the linear heap in the specified region.
        MemoryRegionInfo* memory_region = GetMemoryRegion(region);
        auto& linheap_memory = memory_region->linear_heap_memory;

        ASSERT_MSG(linheap_memory->size() + size <= memory_region->size, "Not enough space in region to allocate shared memory!");

        shared_memory->backing_block = linheap_memory;
        shared_memory->backing_block_offset = linheap_memory->size();
        // Allocate some memory from the end of the linear heap for this region.
        linheap_memory->insert(linheap_memory->end(), size, 0);
        memory_region->used += size;

        shared_memory->linear_heap_phys_address = Memory::FCRAM_PADDR + memory_region->base + shared_memory->backing_block_offset;

        // Refresh the address mappings for the current process.
        if (Kernel::g_current_process != nullptr) {
            Kernel::g_current_process->vm_manager.RefreshMemoryBlockMappings(linheap_memory.get());
        }
    } else {
        // TODO(Subv): What happens if an application tries to create multiple memory blocks pointing to the same address?
        auto& vm_manager = shared_memory->owner_process->vm_manager;
        // The memory is already available and mapped in the owner process.
        auto vma = vm_manager.FindVMA(address)->second;
        // Copy it over to our own storage
        shared_memory->backing_block = std::make_shared<std::vector<u8>>(vma.backing_block->data() + vma.offset,
                                                                         vma.backing_block->data() + vma.offset + size);
        // Unmap the existing pages
        vm_manager.UnmapRange(address, size);
        // Map our own block into the address space
        vm_manager.MapMemoryBlock(address, shared_memory->backing_block, 0, size, MemoryState::Shared);
        // Reprotect the block with the new permissions
        vm_manager.ReprotectRange(address, size, ConvertPermissions(permissions));
    }

    shared_memory->base_address = address;
    return shared_memory;
}

ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermission permissions,
        MemoryPermission other_permissions) {

    MemoryPermission own_other_permissions = target_process == owner_process ? this->permissions : this->other_permissions;

    // Automatically allocated memory blocks can only be mapped with other_permissions = DontCare
    if (base_address == 0 && other_permissions != MemoryPermission::DontCare) {
        return ResultCode(ErrorDescription::InvalidCombination, ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage);
    }

    // Error out if the requested permissions don't match what the creator process allows.
    if (static_cast<u32>(permissions) & ~static_cast<u32>(own_other_permissions)) {
        LOG_ERROR(Kernel, "cannot map id=%u, address=0x%08X name=%s, permissions don't match",
                  GetObjectId(), address, name.c_str());
        return ResultCode(ErrorDescription::InvalidCombination, ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage);
    }

    // Heap-backed memory blocks can not be mapped with other_permissions = DontCare
    if (base_address != 0 && other_permissions == MemoryPermission::DontCare) {
        LOG_ERROR(Kernel, "cannot map id=%u, address=0x%08X name=%s, permissions don't match",
                  GetObjectId(), address, name.c_str());
        return ResultCode(ErrorDescription::InvalidCombination, ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage);
    }

    // Error out if the provided permissions are not compatible with what the creator process needs.
    if (other_permissions != MemoryPermission::DontCare &&
        static_cast<u32>(this->permissions) & ~static_cast<u32>(other_permissions)) {
        LOG_ERROR(Kernel, "cannot map id=%u, address=0x%08X name=%s, permissions don't match",
                  GetObjectId(), address, name.c_str());
        return ResultCode(ErrorDescription::WrongPermission, ErrorModule::OS, ErrorSummary::WrongArgument, ErrorLevel::Permanent);
    }

    // TODO(Subv): Check for the Shared Device Mem flag in the creator process.
    /*if (was_created_with_shared_device_mem && address != 0) {
        return ResultCode(ErrorDescription::InvalidCombination, ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage);
    }*/

    // TODO(Subv): The same process that created a SharedMemory object
    // can not map it in its own address space unless it was created with addr=0, result 0xD900182C.

    if (address != 0) {
        if (address < Memory::HEAP_VADDR || address + size >= Memory::SHARED_MEMORY_VADDR_END) {
            LOG_ERROR(Kernel, "cannot map id=%u, address=0x%08X name=%s, invalid address",
                      GetObjectId(), address, name.c_str());
            return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::OS,
                              ErrorSummary::InvalidArgument, ErrorLevel::Usage);
        }
    }

    VAddr target_address = address;

    if (base_address == 0 && target_address == 0) {
        // Calculate the address at which to map the memory block.
        target_address = Memory::PhysicalToVirtualAddress(linear_heap_phys_address);
    }

    // Map the memory block into the target process
    auto result = target_process->vm_manager.MapMemoryBlock(target_address, backing_block, backing_block_offset, size, MemoryState::Shared);
    if (result.Failed()) {
        LOG_ERROR(Kernel, "cannot map id=%u, target_address=0x%08X name=%s, error mapping to virtual memory",
                  GetObjectId(), target_address, name.c_str());
        return result.Code();
    }

    return target_process->vm_manager.ReprotectRange(target_address, size, ConvertPermissions(permissions));
}

ResultCode SharedMemory::Unmap(Process* target_process, VAddr address) {
    // TODO(Subv): Verify what happens if the application tries to unmap an address that is not mapped to a SharedMemory.
    return target_process->vm_manager.UnmapRange(address, size);
}

VMAPermission SharedMemory::ConvertPermissions(MemoryPermission permission) {
    u32 masked_permissions = static_cast<u32>(permission) & static_cast<u32>(MemoryPermission::ReadWriteExecute);
    return static_cast<VMAPermission>(masked_permissions);
};

u8* SharedMemory::GetPointer(u32 offset) {
    return backing_block->data() + backing_block_offset + offset;
}

} // namespace