summaryrefslogtreecommitdiffstats
path: root/src/core/hle/kernel/shared_memory.cpp
blob: 80fa81abdbbbeb6cebac830f40ce3ae21a8b2903 (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
150
151
152
153
154
155
156
157
158
159
160
161
// 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/core.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/memory.h"
#include "core/hle/kernel/shared_memory.h"
#include "core/memory.h"

namespace Kernel {

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

SharedPtr<SharedMemory> SharedMemory::Create(SharedPtr<Process> owner_process, u64 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 +
            static_cast<PAddr>(shared_memory->backing_block_offset);

        // Increase the amount of used linear heap memory for the owner process.
        if (shared_memory->owner_process != nullptr) {
            shared_memory->owner_process->linear_heap_used += size;
        }

        // Refresh the address mappings for the current process.
        if (Core::CurrentProcess() != nullptr) {
            Core::CurrentProcess()->vm_manager.RefreshMemoryBlockMappings(linheap_memory.get());
        }
    } else {
        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);
        ASSERT_MSG(vma != vm_manager.vma_map.end(), "Invalid memory address");
        ASSERT_MSG(vma->second.backing_block, "Backing block doesn't exist for address");

        // The returned VMA might be a bigger one encompassing the desired address.
        auto vma_offset = address - vma->first;
        ASSERT_MSG(vma_offset + size <= vma->second.size,
                   "Shared memory exceeds bounds of mapped block");

        shared_memory->backing_block = vma->second.backing_block;
        shared_memory->backing_block_offset = vma->second.offset + vma_offset;
    }

    shared_memory->base_address = address;
    return shared_memory;
}

SharedPtr<SharedMemory> SharedMemory::CreateForApplet(std::shared_ptr<std::vector<u8>> heap_block,
                                                      u32 offset, u32 size,
                                                      MemoryPermission permissions,
                                                      MemoryPermission other_permissions,
                                                      std::string name) {
    SharedPtr<SharedMemory> shared_memory(new SharedMemory);

    shared_memory->owner_process = nullptr;
    shared_memory->name = std::move(name);
    shared_memory->size = size;
    shared_memory->permissions = permissions;
    shared_memory->other_permissions = other_permissions;
    shared_memory->backing_block = heap_block;
    shared_memory->backing_block_offset = offset;
    shared_memory->base_address = Memory::HEAP_VADDR + offset;

    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 ERR_INVALID_COMBINATION;
    }

    // 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={}, address=0x{:X} name={}, permissions don't match",
                    GetObjectId(), address, name);
        return ERR_INVALID_COMBINATION;
    }

    // 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={}, address=0x{:X} name={}, permissions don't match",
                    GetObjectId(), address, name);
        return ERR_WRONG_PERMISSION;
    }

    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).value();
    }

    // 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={}, target_address=0x{:X} name={}, error mapping to virtual memory",
            GetObjectId(), target_address, name);
        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 Kernel