1 /*
2 * Copyright (c) 2013-2019 Huawei Technologies Co., Ltd. All rights reserved.
3 * Copyright (c) 2020-2021 Huawei Device Co., Ltd. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without modification,
6 * are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this list of
9 * conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice, this list
12 * of conditions and the following disclaimer in the documentation and/or other materials
13 * provided with the distribution.
14 *
15 * 3. Neither the name of the copyright holder nor the names of its contributors may be used
16 * to endorse or promote products derived from this software without specific prior written
17 * permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
23 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
26 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
27 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
28 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
29 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 /**
33 * @defgroup los_vm_fault vm fault definition
34 * @ingroup kernel
35 */
36 #include "los_vm_fault.h"
37 #include "los_vm_map.h"
38 #include "los_vm_dump.h"
39 #include "los_vm_filemap.h"
40 #include "los_vm_page.h"
41 #include "los_vm_lock.h"
42 #include "los_exc.h"
43 #include "los_oom.h"
44 #include "los_printf.h"
45 #include "los_process_pri.h"
46 #include "arm.h"
47
48 #ifdef LOSCFG_FS_VFS
49 #include "vnode.h"
50 #endif
51
52
53 #ifdef LOSCFG_KERNEL_VM
54
55 extern char __exc_table_start[];
56 extern char __exc_table_end[];
57
OsVmRegionPermissionCheck(LosVmMapRegion * region,UINT32 flags)58 STATIC STATUS_T OsVmRegionPermissionCheck(LosVmMapRegion *region, UINT32 flags)
59 {
60 if ((region->regionFlags & VM_MAP_REGION_FLAG_PERM_READ) != VM_MAP_REGION_FLAG_PERM_READ) {
61 VM_ERR("read permission check failed operation flags %x, region flags %x", flags, region->regionFlags);
62 return LOS_NOK;
63 }
64
65 if ((flags & VM_MAP_PF_FLAG_WRITE) == VM_MAP_PF_FLAG_WRITE) {
66 if ((region->regionFlags & VM_MAP_REGION_FLAG_PERM_WRITE) != VM_MAP_REGION_FLAG_PERM_WRITE) {
67 VM_ERR("write permission check failed operation flags %x, region flags %x", flags, region->regionFlags);
68 return LOS_NOK;
69 }
70 }
71
72 if ((flags & VM_MAP_PF_FLAG_INSTRUCTION) == VM_MAP_PF_FLAG_INSTRUCTION) {
73 if ((region->regionFlags & VM_MAP_REGION_FLAG_PERM_EXECUTE) != VM_MAP_REGION_FLAG_PERM_EXECUTE) {
74 VM_ERR("exec permission check failed operation flags %x, region flags %x", flags, region->regionFlags);
75 return LOS_NOK;
76 }
77 }
78
79 return LOS_OK;
80 }
81
OsFaultTryFixup(ExcContext * frame,VADDR_T excVaddr,STATUS_T * status)82 STATIC VOID OsFaultTryFixup(ExcContext *frame, VADDR_T excVaddr, STATUS_T *status)
83 {
84 INT32 tableNum = (__exc_table_end - __exc_table_start) / sizeof(LosExcTable);
85 LosExcTable *excTable = (LosExcTable *)__exc_table_start;
86
87 if ((frame->regCPSR & CPSR_MODE_MASK) != CPSR_MODE_USR) {
88 for (int i = 0; i < tableNum; ++i, ++excTable) {
89 if (frame->PC == (UINTPTR)excTable->excAddr) {
90 frame->PC = (UINTPTR)excTable->fixAddr;
91 frame->R2 = (UINTPTR)excVaddr;
92 *status = LOS_OK;
93 return;
94 }
95 }
96 }
97 }
98
99 #ifdef LOSCFG_FS_VFS
OsDoReadFault(LosVmMapRegion * region,LosVmPgFault * vmPgFault)100 STATIC STATUS_T OsDoReadFault(LosVmMapRegion *region, LosVmPgFault *vmPgFault)
101 {
102 status_t ret;
103 PADDR_T paddr;
104 LosVmPage *page = NULL;
105 VADDR_T vaddr = (VADDR_T)vmPgFault->vaddr;
106 LosVmSpace *space = region->space;
107
108 ret = LOS_ArchMmuQuery(&space->archMmu, vaddr, NULL, NULL);
109 if (ret == LOS_OK) {
110 return LOS_OK;
111 }
112 if (region->unTypeData.rf.vmFOps == NULL || region->unTypeData.rf.vmFOps->fault == NULL) {
113 VM_ERR("region args invalid, file path: %s", region->unTypeData.rf.vnode->filePath);
114 return LOS_ERRNO_VM_INVALID_ARGS;
115 }
116
117 (VOID)LOS_MuxAcquire(®ion->unTypeData.rf.vnode->mapping.mux_lock);
118 ret = region->unTypeData.rf.vmFOps->fault(region, vmPgFault);
119 if (ret == LOS_OK) {
120 paddr = LOS_PaddrQuery(vmPgFault->pageKVaddr);
121 page = LOS_VmPageGet(paddr);
122 if (page != NULL) { /* just incase of page null */
123 LOS_AtomicInc(&page->refCounts);
124 OsCleanPageLocked(page);
125 }
126 ret = LOS_ArchMmuMap(&space->archMmu, vaddr, paddr, 1,
127 region->regionFlags & (~VM_MAP_REGION_FLAG_PERM_WRITE));
128 if (ret < 0) {
129 VM_ERR("LOS_ArchMmuMap failed");
130 OsDelMapInfo(region, vmPgFault, false);
131 (VOID)LOS_MuxRelease(®ion->unTypeData.rf.vnode->mapping.mux_lock);
132 return LOS_ERRNO_VM_NO_MEMORY;
133 }
134
135 (VOID)LOS_MuxRelease(®ion->unTypeData.rf.vnode->mapping.mux_lock);
136 return LOS_OK;
137 }
138 (VOID)LOS_MuxRelease(®ion->unTypeData.rf.vnode->mapping.mux_lock);
139
140 return LOS_ERRNO_VM_NO_MEMORY;
141 }
142
143 /* unmap a page when cow happened only */
OsCowUnmapOrg(LosArchMmu * archMmu,LosVmMapRegion * region,LosVmPgFault * vmf)144 STATIC LosVmPage *OsCowUnmapOrg(LosArchMmu *archMmu, LosVmMapRegion *region, LosVmPgFault *vmf)
145 {
146 UINT32 intSave;
147 LosVmPage *oldPage = NULL;
148 LosMapInfo *mapInfo = NULL;
149 LosFilePage *fpage = NULL;
150 VADDR_T vaddr = (VADDR_T)vmf->vaddr;
151
152 LOS_SpinLockSave(®ion->unTypeData.rf.vnode->mapping.list_lock, &intSave);
153 fpage = OsFindGetEntry(®ion->unTypeData.rf.vnode->mapping, vmf->pgoff);
154 if (fpage != NULL) {
155 oldPage = fpage->vmPage;
156 OsSetPageLocked(oldPage);
157 mapInfo = OsGetMapInfo(fpage, archMmu, vaddr);
158 if (mapInfo != NULL) {
159 OsUnmapPageLocked(fpage, mapInfo);
160 } else {
161 LOS_ArchMmuUnmap(archMmu, vaddr, 1);
162 }
163 } else {
164 LOS_ArchMmuUnmap(archMmu, vaddr, 1);
165 }
166 LOS_SpinUnlockRestore(®ion->unTypeData.rf.vnode->mapping.list_lock, intSave);
167
168 return oldPage;
169 }
170 #endif
171
OsDoCowFault(LosVmMapRegion * region,LosVmPgFault * vmPgFault)172 status_t OsDoCowFault(LosVmMapRegion *region, LosVmPgFault *vmPgFault)
173 {
174 STATUS_T ret;
175 VOID *kvaddr = NULL;
176 PADDR_T oldPaddr = 0;
177 PADDR_T newPaddr;
178 LosVmPage *oldPage = NULL;
179 LosVmPage *newPage = NULL;
180 LosVmSpace *space = NULL;
181
182 if ((vmPgFault == NULL) || (region == NULL) ||
183 (region->unTypeData.rf.vmFOps == NULL) || (region->unTypeData.rf.vmFOps->fault == NULL)) {
184 VM_ERR("region args invalid");
185 return LOS_ERRNO_VM_INVALID_ARGS;
186 }
187
188 space = region->space;
189 ret = LOS_ArchMmuQuery(&space->archMmu, (VADDR_T)vmPgFault->vaddr, &oldPaddr, NULL);
190 if (ret == LOS_OK) {
191 oldPage = OsCowUnmapOrg(&space->archMmu, region, vmPgFault);
192 }
193
194 newPage = LOS_PhysPageAlloc();
195 if (newPage == NULL) {
196 VM_ERR("LOS_PhysPageAlloc failed");
197 ret = LOS_ERRNO_VM_NO_MEMORY;
198 goto ERR_OUT;
199 }
200
201 newPaddr = VM_PAGE_TO_PHYS(newPage);
202 kvaddr = OsVmPageToVaddr(newPage);
203
204 (VOID)LOS_MuxAcquire(®ion->unTypeData.rf.vnode->mapping.mux_lock);
205 ret = region->unTypeData.rf.vmFOps->fault(region, vmPgFault);
206 if (ret != LOS_OK) {
207 VM_ERR("call region->vm_ops->fault fail");
208 (VOID)LOS_MuxRelease(®ion->unTypeData.rf.vnode->mapping.mux_lock);
209 goto ERR_OUT;
210 }
211
212 /**
213 * here we get two conditions, 1.this page hasn't mapped or mapped from pagecache,
214 * we can take it as a normal file cow map. 2.this page has done file cow map,
215 * we can take it as a anonymous cow map.
216 */
217 if ((oldPaddr == 0) || (LOS_PaddrToKVaddr(oldPaddr) == vmPgFault->pageKVaddr)) {
218 (VOID)memcpy_s(kvaddr, PAGE_SIZE, vmPgFault->pageKVaddr, PAGE_SIZE);
219 LOS_AtomicInc(&newPage->refCounts);
220 OsCleanPageLocked(LOS_VmPageGet(LOS_PaddrQuery(vmPgFault->pageKVaddr)));
221 } else {
222 OsPhysSharePageCopy(oldPaddr, &newPaddr, newPage);
223 /* use old page free the new one */
224 if (newPaddr == oldPaddr) {
225 LOS_PhysPageFree(newPage);
226 newPage = NULL;
227 }
228 }
229
230 ret = LOS_ArchMmuMap(&space->archMmu, (VADDR_T)vmPgFault->vaddr, newPaddr, 1, region->regionFlags);
231 if (ret < 0) {
232 VM_ERR("LOS_ArchMmuMap failed");
233 ret = LOS_ERRNO_VM_NO_MEMORY;
234 (VOID)LOS_MuxRelease(®ion->unTypeData.rf.vnode->mapping.mux_lock);
235 goto ERR_OUT;
236 }
237 (VOID)LOS_MuxRelease(®ion->unTypeData.rf.vnode->mapping.mux_lock);
238
239 if (oldPage != NULL) {
240 OsCleanPageLocked(oldPage);
241 }
242
243 return LOS_OK;
244
245 ERR_OUT:
246 if (newPage != NULL) {
247 LOS_PhysPageFree(newPage);
248 }
249 if (oldPage != NULL) {
250 OsCleanPageLocked(oldPage);
251 }
252
253 return ret;
254 }
255
OsDoSharedFault(LosVmMapRegion * region,LosVmPgFault * vmPgFault)256 status_t OsDoSharedFault(LosVmMapRegion *region, LosVmPgFault *vmPgFault)
257 {
258 STATUS_T ret;
259 UINT32 intSave;
260 PADDR_T paddr = 0;
261 VADDR_T vaddr = (VADDR_T)vmPgFault->vaddr;
262 LosVmSpace *space = region->space;
263 LosVmPage *page = NULL;
264 LosFilePage *fpage = NULL;
265
266 if ((region->unTypeData.rf.vmFOps == NULL) || (region->unTypeData.rf.vmFOps->fault == NULL)) {
267 VM_ERR("region args invalid");
268 return LOS_ERRNO_VM_INVALID_ARGS;
269 }
270
271 ret = LOS_ArchMmuQuery(&space->archMmu, vmPgFault->vaddr, &paddr, NULL);
272 if (ret == LOS_OK) {
273 LOS_ArchMmuUnmap(&space->archMmu, vmPgFault->vaddr, 1);
274 ret = LOS_ArchMmuMap(&space->archMmu, vaddr, paddr, 1, region->regionFlags);
275 if (ret < 0) {
276 VM_ERR("LOS_ArchMmuMap failed. ret=%d", ret);
277 return LOS_ERRNO_VM_NO_MEMORY;
278 }
279
280 LOS_SpinLockSave(®ion->unTypeData.rf.vnode->mapping.list_lock, &intSave);
281 fpage = OsFindGetEntry(®ion->unTypeData.rf.vnode->mapping, vmPgFault->pgoff);
282 if (fpage) {
283 OsMarkPageDirty(fpage, region, 0, 0);
284 }
285 LOS_SpinUnlockRestore(®ion->unTypeData.rf.vnode->mapping.list_lock, intSave);
286
287 return LOS_OK;
288 }
289
290 (VOID)LOS_MuxAcquire(®ion->unTypeData.rf.vnode->mapping.mux_lock);
291 ret = region->unTypeData.rf.vmFOps->fault(region, vmPgFault);
292 if (ret == LOS_OK) {
293 paddr = LOS_PaddrQuery(vmPgFault->pageKVaddr);
294 page = LOS_VmPageGet(paddr);
295 /* just in case of page null */
296 if (page != NULL) {
297 LOS_AtomicInc(&page->refCounts);
298 OsCleanPageLocked(page);
299 }
300 ret = LOS_ArchMmuMap(&space->archMmu, vaddr, paddr, 1, region->regionFlags);
301 if (ret < 0) {
302 VM_ERR("LOS_ArchMmuMap failed. ret=%d", ret);
303 OsDelMapInfo(region, vmPgFault, TRUE);
304 (VOID)LOS_MuxRelease(®ion->unTypeData.rf.vnode->mapping.mux_lock);
305 return LOS_ERRNO_VM_NO_MEMORY;
306 }
307
308 (VOID)LOS_MuxRelease(®ion->unTypeData.rf.vnode->mapping.mux_lock);
309 return LOS_OK;
310 }
311 (VOID)LOS_MuxRelease(®ion->unTypeData.rf.vnode->mapping.mux_lock);
312 return ret;
313 }
314
315 /**
316 * Page read operation is a simple case, just share the pagecache(save memory)
317 * and make a read permission mmapping (region->arch_mmu_flags & (~ARCH_MMU_FLAG_PERM_WRITE)).
318 * However for write operation, vmflag (VM_PRIVATE|VM_SHREAD) decides COW or SHARED fault.
319 * For COW fault, pagecache is copied to private anonyous pages and the changes on this page
320 * won't write through to the underlying file. For SHARED fault, pagecache is mapping with
321 * region->arch_mmu_flags and the changes on this page will write through to the underlying file
322 */
OsDoFileFault(LosVmMapRegion * region,LosVmPgFault * vmPgFault,UINT32 flags)323 STATIC STATUS_T OsDoFileFault(LosVmMapRegion *region, LosVmPgFault *vmPgFault, UINT32 flags)
324 {
325 STATUS_T ret;
326
327 if (flags & VM_MAP_PF_FLAG_WRITE) {
328 if (region->regionFlags & VM_MAP_REGION_FLAG_SHARED) {
329 ret = OsDoSharedFault(region, vmPgFault);
330 } else {
331 ret = OsDoCowFault(region, vmPgFault);
332 }
333 } else {
334 ret = OsDoReadFault(region, vmPgFault);
335 }
336 return ret;
337 }
338
OsVmPageFaultHandler(VADDR_T vaddr,UINT32 flags,ExcContext * frame)339 STATUS_T OsVmPageFaultHandler(VADDR_T vaddr, UINT32 flags, ExcContext *frame)
340 {
341 LosVmSpace *space = LOS_SpaceGet(vaddr);
342 LosVmMapRegion *region = NULL;
343 STATUS_T status;
344 PADDR_T oldPaddr;
345 PADDR_T newPaddr;
346 VADDR_T excVaddr = vaddr;
347 LosVmPage *newPage = NULL;
348 LosVmPgFault vmPgFault = { 0 };
349
350 if (space == NULL) {
351 VM_ERR("vm space not exists, vaddr: %#x", vaddr);
352 status = LOS_ERRNO_VM_NOT_FOUND;
353 OsFaultTryFixup(frame, excVaddr, &status);
354 return status;
355 }
356
357 if (((flags & VM_MAP_PF_FLAG_USER) != 0) && (!LOS_IsUserAddress(vaddr))) {
358 VM_ERR("user space not allowed to access invalid address: %#x", vaddr);
359 return LOS_ERRNO_VM_ACCESS_DENIED;
360 }
361
362 #ifdef LOSCFG_KERNEL_PLIMITS
363 if (OsMemLimitCheckAndMemAdd(PAGE_SIZE) != LOS_OK) {
364 return LOS_ERRNO_VM_NO_MEMORY;
365 }
366 #endif
367
368 (VOID)LOS_MuxAcquire(&space->regionMux);
369 region = LOS_RegionFind(space, vaddr);
370 if (region == NULL) {
371 VM_ERR("region not exists, vaddr: %#x", vaddr);
372 status = LOS_ERRNO_VM_NOT_FOUND;
373 goto CHECK_FAILED;
374 }
375
376 status = OsVmRegionPermissionCheck(region, flags);
377 if (status != LOS_OK) {
378 status = LOS_ERRNO_VM_ACCESS_DENIED;
379 goto CHECK_FAILED;
380 }
381
382 if (OomCheckProcess()) {
383 /*
384 * under low memory, when user process request memory allocation
385 * it will fail, and result is LOS_NOK and current user process
386 * will be deleted. memory usage detail will be printed.
387 */
388 status = LOS_ERRNO_VM_NO_MEMORY;
389 goto CHECK_FAILED;
390 }
391
392 vaddr = ROUNDDOWN(vaddr, PAGE_SIZE);
393 #ifdef LOSCFG_FS_VFS
394 if (LOS_IsRegionFileValid(region)) {
395 if (region->unTypeData.rf.vnode == NULL) {
396 goto CHECK_FAILED;
397 }
398 vmPgFault.vaddr = vaddr;
399 vmPgFault.pgoff = ((vaddr - region->range.base) >> PAGE_SHIFT) + region->pgOff;
400 vmPgFault.flags = flags;
401 vmPgFault.pageKVaddr = NULL;
402
403 status = OsDoFileFault(region, &vmPgFault, flags);
404 if (status) {
405 VM_ERR("vm fault error, status=%d", status);
406 goto CHECK_FAILED;
407 }
408 goto DONE;
409 }
410 #endif
411
412 newPage = LOS_PhysPageAlloc();
413 if (newPage == NULL) {
414 status = LOS_ERRNO_VM_NO_MEMORY;
415 goto CHECK_FAILED;
416 }
417
418 newPaddr = VM_PAGE_TO_PHYS(newPage);
419 (VOID)memset_s(OsVmPageToVaddr(newPage), PAGE_SIZE, 0, PAGE_SIZE);
420 status = LOS_ArchMmuQuery(&space->archMmu, vaddr, &oldPaddr, NULL);
421 if (status >= 0) {
422 LOS_ArchMmuUnmap(&space->archMmu, vaddr, 1);
423 OsPhysSharePageCopy(oldPaddr, &newPaddr, newPage);
424 /* use old page free the new one */
425 if (newPaddr == oldPaddr) {
426 LOS_PhysPageFree(newPage);
427 newPage = NULL;
428 }
429
430 /* map all of the pages */
431 status = LOS_ArchMmuMap(&space->archMmu, vaddr, newPaddr, 1, region->regionFlags);
432 if (status < 0) {
433 VM_ERR("failed to map replacement page, status:%d", status);
434 status = LOS_ERRNO_VM_MAP_FAILED;
435 goto VMM_MAP_FAILED;
436 }
437
438 status = LOS_OK;
439 goto DONE;
440 } else {
441 /* map all of the pages */
442 LOS_AtomicInc(&newPage->refCounts);
443 status = LOS_ArchMmuMap(&space->archMmu, vaddr, newPaddr, 1, region->regionFlags);
444 if (status < 0) {
445 VM_ERR("failed to map page, status:%d", status);
446 status = LOS_ERRNO_VM_MAP_FAILED;
447 goto VMM_MAP_FAILED;
448 }
449 }
450
451 status = LOS_OK;
452 goto DONE;
453 VMM_MAP_FAILED:
454 if (newPage != NULL) {
455 LOS_PhysPageFree(newPage);
456 }
457 CHECK_FAILED:
458 OsFaultTryFixup(frame, excVaddr, &status);
459 #ifdef LOSCFG_KERNEL_PLIMITS
460 OsMemLimitMemFree(PAGE_SIZE);
461 #endif
462 DONE:
463 (VOID)LOS_MuxRelease(&space->regionMux);
464 return status;
465 }
466 #endif
467
468