1// Copyright 2016 The Fuchsia Authors
2// Copyright (c) 2014 Travis Geiselbrecht
3//
4// Use of this source code is governed by a MIT-style
5// license that can be found in the LICENSE file or at
6// https://opensource.org/licenses/MIT
7
8#include "vm_priv.h"
9#include <assert.h>
10#include <err.h>
11#include <inttypes.h>
12#include <kernel/mutex.h>
13#include <kernel/thread_lock.h>
14#include <lib/console.h>
15#include <lib/ktrace.h>
16#include <object/diagnostics.h>
17#include <string.h>
18#include <trace.h>
19#include <vm/fault.h>
20#include <vm/pmm.h>
21#include <vm/vm.h>
22#include <vm/vm_address_region.h>
23#include <vm/vm_aspace.h>
24#include <zircon/types.h>
25
26#define LOCAL_TRACE MAX(VM_GLOBAL_TRACE, 0)
27#define TRACE_PAGE_FAULT 0
28
29// This file mostly contains C wrappers around the underlying C++ objects, conforming to
30// the older api.
31
32static inline void vmm_context_switch(VmAspace* oldspace, VmAspace* newaspace) {
33    DEBUG_ASSERT(thread_lock_held());
34
35    ArchVmAspace::ContextSwitch(oldspace ? &oldspace->arch_aspace() : nullptr,
36                                newaspace ? &newaspace->arch_aspace() : nullptr);
37}
38
39void vmm_context_switch(vmm_aspace_t* oldspace, vmm_aspace_t* newaspace) {
40    vmm_context_switch(reinterpret_cast<VmAspace*>(oldspace), reinterpret_cast<VmAspace*>(newaspace));
41}
42
43zx_status_t vmm_page_fault_handler(vaddr_t addr, uint flags) {
44
45    // hardware fault, mark it as such
46    flags |= VMM_PF_FLAG_HW_FAULT;
47
48#if TRACE_PAGE_FAULT || LOCAL_TRACE
49    thread_t* current_thread = get_current_thread();
50    TRACEF("thread %s va %#" PRIxPTR ", flags 0x%x\n", current_thread->name, addr, flags);
51#endif
52
53    ktrace(TAG_PAGE_FAULT, (uint32_t)(addr >> 32), (uint32_t)addr, flags, arch_curr_cpu_num());
54
55    // get the address space object this pointer is in
56    VmAspace* aspace = VmAspace::vaddr_to_aspace(addr);
57    if (!aspace) {
58        return ZX_ERR_NOT_FOUND;
59    }
60
61    // page fault it
62    zx_status_t status = aspace->PageFault(addr, flags);
63
64    // If it's a user fault, dump info about process memory usage.
65    // If it's a kernel fault, the kernel could possibly already
66    // hold locks on VMOs, Aspaces, etc, so we can't safely do
67    // this.
68    if ((status == ZX_ERR_NOT_FOUND) && (flags & VMM_PF_FLAG_USER)) {
69        printf("PageFault: %zu free pages\n", pmm_count_free_pages());
70        DumpProcessMemoryUsage("PageFault: MemoryUsed: ", 8 * 256);
71    }
72
73    ktrace(TAG_PAGE_FAULT_EXIT, (uint32_t)(addr >> 32), (uint32_t)addr, flags, arch_curr_cpu_num());
74
75    return status;
76}
77
78void vmm_set_active_aspace(vmm_aspace_t* aspace) {
79    LTRACEF("aspace %p\n", aspace);
80
81    thread_t* t = get_current_thread();
82    DEBUG_ASSERT(t);
83
84    if (aspace == t->aspace) {
85        return;
86    }
87
88    // grab the thread lock and switch to the new address space
89    Guard<spin_lock_t, IrqSave> thread_lock_guard{ThreadLock::Get()};
90    vmm_aspace_t* old = t->aspace;
91    t->aspace = aspace;
92    vmm_context_switch(old, t->aspace);
93}
94
95vmm_aspace_t* vmm_get_kernel_aspace(void) {
96    return reinterpret_cast<vmm_aspace_t*>(VmAspace::kernel_aspace());
97}
98
99static int cmd_vmm(int argc, const cmd_args* argv, uint32_t flags) {
100    if (argc < 2) {
101    notenoughargs:
102        printf("not enough arguments\n");
103    usage:
104        printf("usage:\n");
105        printf("%s aspaces\n", argv[0].str);
106        printf("%s kaspace\n", argv[0].str);
107        printf("%s alloc <size> <align_pow2>\n", argv[0].str);
108        printf("%s alloc_physical <paddr> <size> <align_pow2>\n", argv[0].str);
109        printf("%s alloc_contig <size> <align_pow2>\n", argv[0].str);
110        printf("%s free_region <address>\n", argv[0].str);
111        printf("%s create_aspace\n", argv[0].str);
112        printf("%s create_test_aspace\n", argv[0].str);
113        printf("%s free_aspace <address>\n", argv[0].str);
114        printf("%s set_test_aspace <address>\n", argv[0].str);
115        return ZX_ERR_INTERNAL;
116    }
117
118    static fbl::RefPtr<VmAspace> test_aspace;
119    if (!test_aspace) {
120        test_aspace = fbl::WrapRefPtr(VmAspace::kernel_aspace());
121    }
122
123    if (!strcmp(argv[1].str, "aspaces")) {
124        DumpAllAspaces(true);
125    } else if (!strcmp(argv[1].str, "kaspace")) {
126        VmAspace::kernel_aspace()->Dump(true);
127    } else if (!strcmp(argv[1].str, "alloc")) {
128        if (argc < 3) {
129            goto notenoughargs;
130        }
131
132        void* ptr = (void*)0x99;
133        uint8_t align = (argc >= 4) ? (uint8_t)argv[3].u : 0u;
134        zx_status_t err = test_aspace->Alloc("alloc test", argv[2].u, &ptr, align, 0, 0);
135        printf("VmAspace::Alloc returns %d, ptr %p\n", err, ptr);
136    } else if (!strcmp(argv[1].str, "alloc_physical")) {
137        if (argc < 4) {
138            goto notenoughargs;
139        }
140
141        void* ptr = (void*)0x99;
142        uint8_t align = (argc >= 5) ? (uint8_t)argv[4].u : 0u;
143        zx_status_t err = test_aspace->AllocPhysical("physical test", argv[3].u, &ptr, align, argv[2].u,
144                                                     0, ARCH_MMU_FLAG_UNCACHED_DEVICE | ARCH_MMU_FLAG_PERM_READ |
145                                                            ARCH_MMU_FLAG_PERM_WRITE);
146        printf("VmAspace::AllocPhysical returns %d, ptr %p\n", err, ptr);
147    } else if (!strcmp(argv[1].str, "alloc_contig")) {
148        if (argc < 3) {
149            goto notenoughargs;
150        }
151
152        void* ptr = (void*)0x99;
153        uint8_t align = (argc >= 4) ? (uint8_t)argv[3].u : 0u;
154        zx_status_t err = test_aspace->AllocContiguous("contig test", argv[2].u, &ptr, align, 0,
155                                                       ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE);
156        printf("VmAspace::AllocContiguous returns %d, ptr %p\n", err, ptr);
157    } else if (!strcmp(argv[1].str, "free_region")) {
158        if (argc < 2) {
159            goto notenoughargs;
160        }
161
162        zx_status_t err = test_aspace->FreeRegion(reinterpret_cast<vaddr_t>(argv[2].u));
163        printf("VmAspace::FreeRegion returns %d\n", err);
164    } else if (!strcmp(argv[1].str, "create_aspace")) {
165        fbl::RefPtr<VmAspace> aspace = VmAspace::Create(0, "test");
166        printf("VmAspace::Create aspace %p\n", aspace.get());
167    } else if (!strcmp(argv[1].str, "create_test_aspace")) {
168        fbl::RefPtr<VmAspace> aspace = VmAspace::Create(0, "test");
169        printf("VmAspace::Create aspace %p\n", aspace.get());
170
171        test_aspace = aspace;
172        get_current_thread()->aspace = reinterpret_cast<vmm_aspace_t*>(aspace.get());
173        thread_sleep(1); // XXX hack to force it to reschedule and thus load the aspace
174    } else if (!strcmp(argv[1].str, "free_aspace")) {
175        if (argc < 2) {
176            goto notenoughargs;
177        }
178
179        fbl::RefPtr<VmAspace> aspace = fbl::WrapRefPtr((VmAspace*)(void*)argv[2].u);
180        if (test_aspace == aspace) {
181            test_aspace = nullptr;
182        }
183
184        if (get_current_thread()->aspace == reinterpret_cast<vmm_aspace_t*>(aspace.get())) {
185            get_current_thread()->aspace = nullptr;
186            thread_sleep(1); // hack
187        }
188
189        zx_status_t err = aspace->Destroy();
190        printf("VmAspace::Destroy() returns %d\n", err);
191    } else if (!strcmp(argv[1].str, "set_test_aspace")) {
192        if (argc < 2) {
193            goto notenoughargs;
194        }
195
196        test_aspace = fbl::WrapRefPtr((VmAspace*)(void*)argv[2].u);
197        get_current_thread()->aspace = reinterpret_cast<vmm_aspace_t*>(test_aspace.get());
198        thread_sleep(1); // XXX hack to force it to reschedule and thus load the aspace
199    } else {
200        printf("unknown command\n");
201        goto usage;
202    }
203
204    return ZX_OK;
205}
206
207STATIC_COMMAND_START
208#if LK_DEBUGLEVEL > 0
209STATIC_COMMAND("vmm", "virtual memory manager", &cmd_vmm)
210#endif
211STATIC_COMMAND_END(vmm);
212