| // Copyright 2018 The Fuchsia Authors |
| // |
| // Use of this source code is governed by a MIT-style |
| // license that can be found in the LICENSE file or at |
| // https://opensource.org/licenses/MIT |
| #include <vm/kstack.h> |
| |
| #include <assert.h> |
| #include <err.h> |
| #include <inttypes.h> |
| #include <string.h> |
| #include <trace.h> |
| |
| #include <vm/vm.h> |
| #include <vm/vm_address_region.h> |
| #include <vm/vm_aspace.h> |
| #include <vm/vm_object_paged.h> |
| |
| #include <fbl/algorithm.h> |
| #include <fbl/alloc_checker.h> |
| #include <fbl/auto_call.h> |
| #include <fbl/auto_lock.h> |
| #include <fbl/ref_ptr.h> |
| |
| #define LOCAL_TRACE 0 |
| |
| // Allocates and maps a kernel stack with one page of padding before and after the mapping. |
| static zx_status_t allocate_vmar(bool unsafe, |
| fbl::RefPtr<VmMapping>* out_kstack_mapping, |
| fbl::RefPtr<VmAddressRegion>* out_kstack_vmar) { |
| LTRACEF("allocating %s stack\n", unsafe ? "unsafe" : "safe"); |
| |
| // get a handle to the root vmar |
| auto vmar = VmAspace::kernel_aspace()->RootVmar()->as_vm_address_region(); |
| DEBUG_ASSERT(!!vmar); |
| |
| // Create a VMO for our stack |
| fbl::RefPtr<VmObject> stack_vmo; |
| zx_status_t status = VmObjectPaged::Create( |
| PMM_ALLOC_FLAG_ANY, 0u, DEFAULT_STACK_SIZE, &stack_vmo); |
| if (status != ZX_OK) { |
| TRACEF("error allocating %s stack for thread\n", |
| unsafe ? "unsafe" : "safe"); |
| return status; |
| } |
| const char* name = unsafe ? "unsafe-stack" : "safe-stack"; |
| stack_vmo->set_name(name, strlen(name)); |
| |
| // create a vmar with enough padding for a page before and after the stack |
| const size_t padding_size = PAGE_SIZE; |
| |
| fbl::RefPtr<VmAddressRegion> kstack_vmar; |
| status = vmar->CreateSubVmar( |
| 0, 2 * padding_size + DEFAULT_STACK_SIZE, 0, |
| VMAR_FLAG_CAN_MAP_SPECIFIC | |
| VMAR_FLAG_CAN_MAP_READ | |
| VMAR_FLAG_CAN_MAP_WRITE, |
| unsafe ? "unsafe_kstack_vmar" : "kstack_vmar", |
| &kstack_vmar); |
| if (status != ZX_OK) |
| return status; |
| |
| // destroy the vmar if we early abort |
| // this will also clean up any mappings that may get placed on the vmar |
| auto vmar_cleanup = fbl::MakeAutoCall([&kstack_vmar]() { |
| kstack_vmar->Destroy(); |
| }); |
| |
| LTRACEF("%s stack vmar at %#" PRIxPTR "\n", |
| unsafe ? "unsafe" : "safe", kstack_vmar->base()); |
| |
| // create a mapping offset padding_size into the vmar we created |
| fbl::RefPtr<VmMapping> kstack_mapping; |
| status = kstack_vmar->CreateVmMapping(padding_size, DEFAULT_STACK_SIZE, 0, |
| VMAR_FLAG_SPECIFIC, |
| fbl::move(stack_vmo), 0, |
| ARCH_MMU_FLAG_PERM_READ | |
| ARCH_MMU_FLAG_PERM_WRITE, |
| unsafe ? "unsafe_kstack" : "kstack", |
| &kstack_mapping); |
| if (status != ZX_OK) |
| return status; |
| |
| LTRACEF("%s stack mapping at %#" PRIxPTR "\n", |
| unsafe ? "unsafe" : "safe", kstack_mapping->base()); |
| |
| // fault in all the pages so we dont demand fault in the stack |
| status = kstack_mapping->MapRange(0, DEFAULT_STACK_SIZE, true); |
| if (status != ZX_OK) |
| return status; |
| |
| // Cancel the cleanup handler on the vmar since we're about to save a |
| // reference to it. |
| vmar_cleanup.cancel(); |
| *out_kstack_mapping = fbl::move(kstack_mapping); |
| *out_kstack_vmar = fbl::move(kstack_vmar); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t vm_allocate_kstack(kstack_t* stack) { |
| DEBUG_ASSERT(stack->base == 0); |
| DEBUG_ASSERT(stack->size == 0); |
| DEBUG_ASSERT(stack->top == 0); |
| DEBUG_ASSERT(stack->vmar == nullptr); |
| #if __has_feature(safe_stack) |
| DEBUG_ASSERT(stack->unsafe_base == 0); |
| DEBUG_ASSERT(stack->unsafe_vmar == nullptr); |
| #endif |
| |
| fbl::RefPtr<VmMapping> mapping; |
| fbl::RefPtr<VmAddressRegion> vmar; |
| zx_status_t status = allocate_vmar(false, &mapping, &vmar); |
| if (status != ZX_OK) { |
| return status; |
| } |
| stack->size = mapping->size(); |
| stack->base = mapping->base(); |
| stack->top = mapping->base() + DEFAULT_STACK_SIZE; |
| |
| // Stash address of VMAR so we can later free it in |vm_free_kstack|. |
| stack->vmar = vmar.leak_ref(); |
| |
| #if __has_feature(safe_stack) |
| status = allocate_vmar(true, &mapping, &vmar); |
| if (status != ZX_OK) { |
| vm_free_kstack(stack); |
| return status; |
| } |
| stack->size = mapping->size(); |
| stack->unsafe_base = mapping->base(); |
| |
| // Stash address of VMAR so we can later free it in |vm_free_kstack|. |
| stack->unsafe_vmar = vmar.leak_ref(); |
| #endif |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t vm_free_kstack(kstack_t* stack) { |
| stack->base = 0; |
| stack->size = 0; |
| stack->top = 0; |
| |
| if (stack->vmar != nullptr) { |
| fbl::RefPtr<VmAddressRegion> vmar = |
| fbl::internal::MakeRefPtrNoAdopt(static_cast<VmAddressRegion*>(stack->vmar)); |
| zx_status_t status = vmar->Destroy(); |
| if (status != ZX_OK) { |
| return status; |
| } |
| stack->vmar = nullptr; |
| } |
| |
| #if __has_feature(safe_stack) |
| stack->unsafe_base = 0; |
| |
| if (stack->unsafe_vmar != nullptr) { |
| fbl::RefPtr<VmAddressRegion> vmar = |
| fbl::internal::MakeRefPtrNoAdopt(static_cast<VmAddressRegion*>(stack->unsafe_vmar)); |
| zx_status_t status = vmar->Destroy(); |
| if (status != ZX_OK) { |
| return status; |
| } |
| stack->unsafe_vmar = nullptr; |
| } |
| #endif |
| |
| return ZX_OK; |
| } |