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 
vmm_context_switch(VmAspace * oldspace,VmAspace * newaspace)32 static 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 
vmm_context_switch(vmm_aspace_t * oldspace,vmm_aspace_t * newaspace)39 void 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 
vmm_page_fault_handler(vaddr_t addr,uint flags)43 zx_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 
vmm_set_active_aspace(vmm_aspace_t * aspace)78 void 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 
vmm_get_kernel_aspace(void)95 vmm_aspace_t* vmm_get_kernel_aspace(void) {
96     return reinterpret_cast<vmm_aspace_t*>(VmAspace::kernel_aspace());
97 }
98 
cmd_vmm(int argc,const cmd_args * argv,uint32_t flags)99 static 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 
207 STATIC_COMMAND_START
208 #if LK_DEBUGLEVEL > 0
209 STATIC_COMMAND("vmm", "virtual memory manager", &cmd_vmm)
210 #endif
211 STATIC_COMMAND_END(vmm);
212