[kernel][object][vm] Add no-op pager vmos

This change adds the ability to create a no-op pager vmo. The no-op
implementation behaves like a normal vmo, except there are some extra
objects which will manage the pager state in the future.

Test: runtests
Change-Id: I39e0213e6c578206620640ea25eed96898d0f023
diff --git a/kernel/object/include/object/pager_dispatcher.h b/kernel/object/include/object/pager_dispatcher.h
index 2140e2d..3b9c11a 100644
--- a/kernel/object/include/object/pager_dispatcher.h
+++ b/kernel/object/include/object/pager_dispatcher.h
@@ -9,16 +9,51 @@
 #include <fbl/canary.h>
 #include <fbl/ref_ptr.h>
 #include <object/dispatcher.h>
+#include <object/port_dispatcher.h>
 #include <zircon/types.h>
+#include <vm/page_source.h>
+
+// Wrapper which maintains the object layer state of a PageSource.
+class PageSourceWrapper : public PageSourceCallback,
+                          public fbl::DoublyLinkedListable<fbl::unique_ptr<PageSourceWrapper>> {
+public:
+    PageSourceWrapper(PagerDispatcher* dispatcher, fbl::RefPtr<PortDispatcher> port, uint64_t key);
+    virtual ~PageSourceWrapper();
+
+    void OnClose() override;
+
+private:
+    PagerDispatcher* const pager_;
+    const fbl::RefPtr<PortDispatcher> port_;
+    const uint64_t key_;
+
+    fbl::Mutex mtx_;
+    bool closed_ TA_GUARDED(mtx_) = false;
+
+    // The PageSource this is wrapping.
+    fbl::RefPtr<PageSource> src_ TA_GUARDED(mtx_);
+
+    friend PagerDispatcher;
+};
 
 class PagerDispatcher final : public SoloDispatcher<PagerDispatcher, ZX_DEFAULT_PAGER_RIGHTS> {
 public:
     static zx_status_t Create(fbl::RefPtr<Dispatcher>* dispatcher, zx_rights_t* rights);
+    ~PagerDispatcher() final;
+
+    zx_status_t CreateSource(fbl::RefPtr<PortDispatcher> port,
+                             uint64_t key, fbl::RefPtr<PageSource>* src);
+    void ReleaseSource(PageSourceWrapper* src);
 
     zx_obj_type_t get_type() const final { return ZX_OBJ_TYPE_PAGER; }
 
+    void on_zero_handles() final;
+
 private:
     explicit PagerDispatcher();
 
     fbl::Canary<fbl::magic("PGRD")> canary_;
+
+    fbl::Mutex mtx_;
+    fbl::DoublyLinkedList<fbl::unique_ptr<PageSourceWrapper>> srcs_;
 };
diff --git a/kernel/object/pager_dispatcher.cpp b/kernel/object/pager_dispatcher.cpp
index c3b888f..660e8cd 100644
--- a/kernel/object/pager_dispatcher.cpp
+++ b/kernel/object/pager_dispatcher.cpp
@@ -5,6 +5,10 @@
 // https://opensource.org/licenses/MIT
 
 #include <object/pager_dispatcher.h>
+#include <trace.h>
+#include <vm/page_source.h>
+
+#define LOCAL_TRACE 0
 
 zx_status_t PagerDispatcher::Create(fbl::RefPtr<Dispatcher>* dispatcher, zx_rights_t* rights) {
     fbl::AllocChecker ac;
@@ -19,3 +23,74 @@
 }
 
 PagerDispatcher::PagerDispatcher() : SoloDispatcher() {}
+
+PagerDispatcher::~PagerDispatcher() {}
+
+zx_status_t PagerDispatcher::CreateSource(fbl::RefPtr<PortDispatcher> port,
+                                          uint64_t key, fbl::RefPtr<PageSource>* src_out) {
+    fbl::AllocChecker ac;
+    auto wrapper = fbl::make_unique_checked<PageSourceWrapper>(&ac, this, ktl::move(port), key);
+    if (!ac.check()) {
+        return ZX_ERR_NO_MEMORY;
+    }
+
+    auto src = fbl::AdoptRef(new (&ac) PageSource(wrapper.get(), get_koid()));
+    if (!ac.check()) {
+        return ZX_ERR_NO_MEMORY;
+    }
+
+    fbl::AutoLock lock(&wrapper->mtx_);
+    wrapper->src_ = src;
+
+    fbl::AutoLock lock2(&mtx_);
+    srcs_.push_front(ktl::move(wrapper));
+
+    *src_out = ktl::move(src);
+    return ZX_OK;
+}
+
+void PagerDispatcher::ReleaseSource(PageSourceWrapper* src) {
+    fbl::AutoLock lock(&mtx_);
+    srcs_.erase(*src);
+}
+
+void PagerDispatcher::on_zero_handles() {
+    fbl::DoublyLinkedList<fbl::unique_ptr<PageSourceWrapper>> srcs;
+
+    mtx_.Acquire();
+    while (!srcs_.is_empty()) {
+        auto& src = srcs_.front();
+        fbl::RefPtr<PageSource> inner;
+        {
+            fbl::AutoLock lock(&src.mtx_);
+            inner = src.src_;
+        }
+
+        // Call close outside of the lock, since it will call back into ::OnClose.
+        mtx_.Release();
+        if (inner) {
+            inner->Close();
+        }
+        mtx_.Acquire();
+    }
+    mtx_.Release();
+}
+
+PageSourceWrapper::PageSourceWrapper(PagerDispatcher* dispatcher,
+                                     fbl::RefPtr<PortDispatcher> port, uint64_t key)
+    : pager_(dispatcher), port_(ktl::move(port)), key_(key) {
+    LTRACEF("%p key %lx\n", this, key_);
+}
+
+PageSourceWrapper::~PageSourceWrapper() {
+    LTRACEF("%p\n", this);
+    DEBUG_ASSERT(closed_);
+}
+
+void PageSourceWrapper::OnClose() {
+    {
+        fbl::AutoLock lock(&mtx_);
+        closed_ = true;
+    }
+    pager_->ReleaseSource(this);
+}
diff --git a/kernel/syscalls/pager.cpp b/kernel/syscalls/pager.cpp
index c840369..a61c62d 100644
--- a/kernel/syscalls/pager.cpp
+++ b/kernel/syscalls/pager.cpp
@@ -9,6 +9,8 @@
 #include <fbl/ref_ptr.h>
 #include <ktl/move.h>
 #include <object/pager_dispatcher.h>
+#include <object/vm_object_dispatcher.h>
+#include <vm/vm_object_paged.h>
 
 // zx_status_t zx_pager_create
 zx_status_t sys_pager_create(uint32_t options, user_out_handle* out) {
@@ -25,3 +27,45 @@
 
     return out->make(ktl::move(dispatcher), rights);
 }
+
+// zx_status_t zx_pager_create_vmo
+zx_status_t sys_pager_create_vmo(zx_handle_t pager, zx_handle_t port, uint64_t key,
+                                 uint64_t size, uint32_t options, user_out_handle* out) {
+    if (options) {
+        return ZX_ERR_INVALID_ARGS;
+    }
+
+    auto up = ProcessDispatcher::GetCurrent();
+    fbl::RefPtr<PagerDispatcher> pager_dispatcher;
+    zx_status_t status = up->GetDispatcher(pager, &pager_dispatcher);
+    if (status != ZX_OK) {
+        return status;
+    }
+
+    fbl::RefPtr<PortDispatcher> port_dispatcher;
+    status = up->GetDispatcherWithRights(port, ZX_RIGHT_WRITE, &port_dispatcher);
+    if (status != ZX_OK) {
+        return status;
+    }
+
+    fbl::RefPtr<PageSource> src;
+    status = pager_dispatcher->CreateSource(ktl::move(port_dispatcher), key, &src);
+    if (status != ZX_OK) {
+        return status;
+    }
+
+    fbl::RefPtr<VmObject> vmo;
+    status = VmObjectPaged::CreateExternal(ktl::move(src), size, &vmo);
+    if (status != ZX_OK) {
+        return status;
+    }
+
+    fbl::RefPtr<Dispatcher> dispatcher;
+    zx_rights_t rights;
+    status = VmObjectDispatcher::Create(vmo, &dispatcher, &rights);
+    if (status != ZX_OK) {
+        return status;
+    }
+
+    return out->make(ktl::move(dispatcher), rights);
+}
diff --git a/kernel/vm/include/vm/page_source.h b/kernel/vm/include/vm/page_source.h
new file mode 100644
index 0000000..dac6995
--- /dev/null
+++ b/kernel/vm/include/vm/page_source.h
@@ -0,0 +1,44 @@
+// 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
+
+#pragma once
+
+#include <fbl/mutex.h>
+#include <fbl/ref_counted.h>
+#include <fbl/ref_ptr.h>
+#include <kernel/mutex.h>
+#include <vm/vm.h>
+#include <zircon/types.h>
+
+// Callback to whatever is backing the PageSource.
+class PageSourceCallback {
+public:
+    // OnClose should be called once no more requests will be made to the page source. The
+    // callback can keep a reference to the page source, so it must be called outside of
+    // the PageSource destructor.
+    virtual void OnClose() = 0;
+};
+
+// Object which bridges a vm_object to some external data source.
+class PageSource : public fbl::RefCounted<PageSource> {
+public:
+    PageSource(PageSourceCallback* callback, uint64_t page_source_id);
+    ~PageSource();
+
+    // Closes the source. All pending transactions will be aborted and all future
+    // calls will fail.
+    void Close();
+
+    // Gets an id used for ownership verification.
+    uint64_t get_page_source_id() const { return page_source_id_; }
+
+private:
+    PageSourceCallback* const callback_;
+    const uint64_t page_source_id_;
+
+    fbl::Mutex mtx_;
+    bool closed_ TA_GUARDED(mtx_) = false;
+};
diff --git a/kernel/vm/include/vm/vm_object_paged.h b/kernel/vm/include/vm/vm_object_paged.h
index a06fa04..327bfe8 100644
--- a/kernel/vm/include/vm/vm_object_paged.h
+++ b/kernel/vm/include/vm/vm_object_paged.h
@@ -17,6 +17,7 @@
 #include <lib/user_copy/user_ptr.h>
 #include <list.h>
 #include <stdint.h>
+#include <vm/page_source.h>
 #include <vm/pmm.h>
 #include <vm/vm.h>
 #include <vm/vm_aspace.h>
@@ -44,6 +45,9 @@
 
     static zx_status_t CreateFromROData(const void* data, size_t size, fbl::RefPtr<VmObject>* vmo);
 
+    static zx_status_t CreateExternal(fbl::RefPtr<PageSource> src,
+                                      uint64_t size, fbl::RefPtr<VmObject>* vmo);
+
     zx_status_t Resize(uint64_t size) override;
     zx_status_t ResizeLocked(uint64_t size) override TA_REQ(lock_);
     uint32_t create_options() const override { return options_; }
@@ -101,7 +105,8 @@
 private:
     // private constructor (use Create())
     VmObjectPaged(
-        uint32_t options, uint32_t pmm_alloc_flags, uint64_t size, fbl::RefPtr<VmObject> parent);
+        uint32_t options, uint32_t pmm_alloc_flags, uint64_t size,
+        fbl::RefPtr<VmObject> parent, fbl::RefPtr<PageSource> page_source);
 
     // private destructor, only called from refptr
     ~VmObjectPaged() override;
@@ -144,6 +149,9 @@
     uint32_t pmm_alloc_flags_ TA_GUARDED(lock_) = PMM_ALLOC_FLAG_ANY;
     uint32_t cache_policy_ TA_GUARDED(lock_) = ARCH_MMU_FLAG_CACHED;
 
+    // The page source, if any.
+    const fbl::RefPtr<PageSource> page_source_;
+
     // a tree of pages
     VmPageList page_list_ TA_GUARDED(lock_);
 };
diff --git a/kernel/vm/page_source.cpp b/kernel/vm/page_source.cpp
new file mode 100644
index 0000000..982953a
--- /dev/null
+++ b/kernel/vm/page_source.cpp
@@ -0,0 +1,31 @@
+// 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 <fbl/auto_lock.h>
+#include <trace.h>
+#include <vm/page_source.h>
+
+#define LOCAL_TRACE 0
+
+PageSource::PageSource(PageSourceCallback* callback, uint64_t page_source_id)
+        : callback_(callback), page_source_id_(page_source_id) {
+    LTRACEF("%p callback %p\n", this, callback_);
+}
+
+PageSource::~PageSource() {
+    LTRACEF("%p\n", this);
+    DEBUG_ASSERT(closed_);
+}
+
+void PageSource::Close() {
+    fbl::AutoLock info_lock(&mtx_);
+    LTRACEF("%p\n", this);
+
+    if (!closed_) {
+        closed_ = true;
+        callback_->OnClose();
+    }
+}
diff --git a/kernel/vm/rules.mk b/kernel/vm/rules.mk
index c84d074..8820f52 100644
--- a/kernel/vm/rules.mk
+++ b/kernel/vm/rules.mk
@@ -20,6 +20,7 @@
     $(LOCAL_DIR)/bootreserve.cpp \
     $(LOCAL_DIR)/kstack.cpp \
     $(LOCAL_DIR)/page.cpp \
+    $(LOCAL_DIR)/page_source.cpp \
     $(LOCAL_DIR)/pinned_vm_object.cpp \
     $(LOCAL_DIR)/pmm.cpp \
     $(LOCAL_DIR)/pmm_arena.cpp \
diff --git a/kernel/vm/vm_object_paged.cpp b/kernel/vm/vm_object_paged.cpp
index 22e3226..489c003 100644
--- a/kernel/vm/vm_object_paged.cpp
+++ b/kernel/vm/vm_object_paged.cpp
@@ -65,14 +65,17 @@
 } // namespace
 
 VmObjectPaged::VmObjectPaged(
-    uint32_t options, uint32_t pmm_alloc_flags, uint64_t size, fbl::RefPtr<VmObject> parent)
+    uint32_t options, uint32_t pmm_alloc_flags, uint64_t size,
+    fbl::RefPtr<VmObject> parent, fbl::RefPtr<PageSource> page_source)
     : VmObject(ktl::move(parent)),
       options_(options),
       size_(size),
-      pmm_alloc_flags_(pmm_alloc_flags) {
+      pmm_alloc_flags_(pmm_alloc_flags),
+      page_source_(ktl::move(page_source)) {
     LTRACEF("%p\n", this);
 
     DEBUG_ASSERT(IS_PAGE_ALIGNED(size_));
+    DEBUG_ASSERT(page_source_ == nullptr || parent_ == nullptr);
 }
 
 VmObjectPaged::~VmObjectPaged() {
@@ -91,6 +94,10 @@
 
     // free all of the pages attached to us
     page_list_.FreeAllPages();
+
+    if (page_source_) {
+        page_source_->Close();
+    }
 }
 
 zx_status_t VmObjectPaged::Create(uint32_t pmm_alloc_flags,
@@ -109,7 +116,7 @@
 
     fbl::AllocChecker ac;
     auto vmo = fbl::AdoptRef<VmObject>(
-        new (&ac) VmObjectPaged(options, pmm_alloc_flags, size, nullptr));
+        new (&ac) VmObjectPaged(options, pmm_alloc_flags, size, nullptr, nullptr));
     if (!ac.check()) {
         return ZX_ERR_NO_MEMORY;
     }
@@ -130,7 +137,7 @@
 
     fbl::AllocChecker ac;
     auto vmo = fbl::AdoptRef<VmObject>(
-        new (&ac) VmObjectPaged(kContiguous, pmm_alloc_flags, size, nullptr));
+        new (&ac) VmObjectPaged(kContiguous, pmm_alloc_flags, size, nullptr, nullptr));
     if (!ac.check()) {
         return ZX_ERR_NO_MEMORY;
     }
@@ -236,6 +243,26 @@
     return ZX_OK;
 }
 
+zx_status_t VmObjectPaged::CreateExternal(fbl::RefPtr<PageSource> src,
+                                          uint64_t size, fbl::RefPtr<VmObject>* obj) {
+    // make sure size is page aligned
+    zx_status_t status = RoundSize(size, &size);
+    if (status != ZX_OK) {
+        return status;
+    }
+
+    fbl::AllocChecker ac;
+    auto vmo = fbl::AdoptRef<VmObject>(new (&ac) VmObjectPaged(
+            kResizable, PMM_ALLOC_FLAG_ANY, size, nullptr, ktl::move(src)));
+    if (!ac.check()) {
+        return ZX_ERR_NO_MEMORY;
+    }
+
+    *obj = ktl::move(vmo);
+
+    return ZX_OK;
+}
+
 zx_status_t VmObjectPaged::CloneCOW(bool resizable, uint64_t offset, uint64_t size,
                                     bool copy_name, fbl::RefPtr<VmObject>* clone_vmo) {
     LTRACEF("vmo %p offset %#" PRIx64 " size %#" PRIx64 "\n", this, offset, size);
@@ -253,7 +280,7 @@
     // allocate the clone up front outside of our lock
     fbl::AllocChecker ac;
     auto vmo = fbl::AdoptRef<VmObjectPaged>(
-        new (&ac) VmObjectPaged(options, pmm_alloc_flags_, size, fbl::WrapRefPtr(this)));
+        new (&ac) VmObjectPaged(options, pmm_alloc_flags_, size, fbl::WrapRefPtr(this), nullptr));
     if (!ac.check()) {
         return ZX_ERR_NO_MEMORY;
     }
diff --git a/system/public/zircon/syscalls.abigen b/system/public/zircon/syscalls.abigen
index 214788b..3a51d71 100644
--- a/system/public/zircon/syscalls.abigen
+++ b/system/public/zircon/syscalls.abigen
@@ -991,6 +991,10 @@
     (options: uint32_t)
     returns (zx_status_t, out_pager: zx_handle_t pager);
 
+syscall pager_create_vmo
+    (pager: zx_handle_t, port: zx_handle_t, key: uint64_t, size: uint64_t, options: uint32_t)
+    returns (zx_status_t, out_pager_vmo: zx_handle_t out_pager_vmo);
+
 # Test syscalls (keep at the end)
 
 syscall syscall_test_0() returns (zx_status_t);