• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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(&region->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(&region->unTypeData.rf.vnode->mapping.mux_lock);
132             return LOS_ERRNO_VM_NO_MEMORY;
133         }
134 
135         (VOID)LOS_MuxRelease(&region->unTypeData.rf.vnode->mapping.mux_lock);
136         return LOS_OK;
137     }
138     (VOID)LOS_MuxRelease(&region->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(&region->unTypeData.rf.vnode->mapping.list_lock, &intSave);
153     fpage = OsFindGetEntry(&region->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(&region->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(&region->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(&region->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(&region->unTypeData.rf.vnode->mapping.mux_lock);
235         goto ERR_OUT;
236     }
237     (VOID)LOS_MuxRelease(&region->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(&region->unTypeData.rf.vnode->mapping.list_lock, &intSave);
281         fpage = OsFindGetEntry(&region->unTypeData.rf.vnode->mapping, vmPgFault->pgoff);
282         if (fpage) {
283             OsMarkPageDirty(fpage, region, 0, 0);
284         }
285         LOS_SpinUnlockRestore(&region->unTypeData.rf.vnode->mapping.list_lock, intSave);
286 
287         return LOS_OK;
288     }
289 
290     (VOID)LOS_MuxAcquire(&region->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(&region->unTypeData.rf.vnode->mapping.mux_lock);
305             return LOS_ERRNO_VM_NO_MEMORY;
306         }
307 
308         (VOID)LOS_MuxRelease(&region->unTypeData.rf.vnode->mapping.mux_lock);
309         return LOS_OK;
310     }
311     (VOID)LOS_MuxRelease(&region->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