[ddk][mmio] Add mmio-buffer library.

mmio_buffer_t is a helper library to help manage mapping mmios as well
as managing their associated resources.

Tested: It compiles!
Change-Id: I06a51528dbe8a5ee541fedd6988a93af08310e27
diff --git a/system/ulib/ddk/include/ddk/mmio-buffer.h b/system/ulib/ddk/include/ddk/mmio-buffer.h
new file mode 100644
index 0000000..1b12c4b
--- /dev/null
+++ b/system/ulib/ddk/include/ddk/mmio-buffer.h
@@ -0,0 +1,43 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#pragma once
+
+#include <zircon/types.h>
+
+__BEGIN_CDECLS;
+
+typedef struct {
+    // |vaddr| points to the content starting at |offset| in |vmo|.
+    void* vaddr;
+    zx_off_t offset;
+    size_t size;
+    zx_handle_t vmo;
+} mmio_buffer_t;
+
+typedef struct {
+    const mmio_buffer_t* mmio;
+    zx_handle_t pmt;
+    // |paddr| points to the content starting at |mmio->offset| in |mmio->vmo|.
+    zx_paddr_t paddr;
+} mmio_pinned_buffer_t;
+
+// Takes raw mmio resources, and maps it into address space. |offset| is the
+// offset from the begining of |vmo| where the mmio region begins. |size|
+// specifies the size of the mmio region. |offset| + |size| must be less than
+// or equal to the size of |vmo|.
+// Always consumes |vmo|, including in error cases.
+zx_status_t mmio_buffer_init(mmio_buffer_t* buffer, zx_off_t offset, size_t size,
+                             zx_handle_t vmo, uint32_t cache_policy);
+// Unmaps the mmio region.
+void mmio_buffer_release(mmio_buffer_t* buffer);
+
+// Returns a pinned buffer if successful. |buffer| must outlive |out|.
+//
+// Example usage: A device needs access to another device's MMIO space.
+zx_status_t mmio_buffer_pin(mmio_buffer_t* buffer, zx_handle_t bti, mmio_pinned_buffer_t* out);
+// Unpins the buffer.
+void mmio_buffer_unpin(mmio_pinned_buffer_t* buffer);
+
+__END_CDECLS;
diff --git a/system/ulib/ddk/mmio-buffer.c b/system/ulib/ddk/mmio-buffer.c
new file mode 100644
index 0000000..47014cd
--- /dev/null
+++ b/system/ulib/ddk/mmio-buffer.c
@@ -0,0 +1,77 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <ddk/mmio-buffer.h>
+
+#include <string.h>
+
+#include <ddk/driver.h>
+#include <zircon/process.h>
+#include <zircon/syscalls.h>
+#include <zircon/types.h>
+
+zx_status_t mmio_buffer_init(mmio_buffer_t* buffer, zx_off_t offset, size_t size,
+                             zx_handle_t vmo, uint32_t cache_policy) {
+    zx_status_t status = zx_vmo_set_cache_policy(vmo, cache_policy);
+    if (status != ZX_OK) {
+        zx_handle_close(vmo);
+        return status;
+    }
+
+    uintptr_t vaddr;
+    const size_t vmo_offset = ROUNDDOWN(offset, ZX_PAGE_SIZE);
+    const size_t page_offset = offset - vmo_offset;
+    const size_t vmo_size = ROUNDUP(size + page_offset, ZX_PAGE_SIZE);
+
+    status = zx_vmar_map(zx_vmar_root_self(),
+                         ZX_VM_PERM_READ | ZX_VM_PERM_WRITE | ZX_VM_MAP_RANGE,
+                         0, vmo, vmo_offset, vmo_size, &vaddr);
+    if (status != ZX_OK) {
+        zx_handle_close(vmo);
+        return status;
+    }
+
+    buffer->vmo = vmo;
+    buffer->vaddr = (void*)(vaddr + page_offset);
+    buffer->offset = offset;
+    buffer->size = size;
+
+    return ZX_OK;
+}
+
+void mmio_buffer_release(mmio_buffer_t* buffer) {
+    if (buffer->vmo != ZX_HANDLE_INVALID) {
+        zx_vmar_unmap(zx_vmar_root_self(), (uintptr_t)buffer->vaddr, buffer->size);
+        zx_handle_close(buffer->vmo);
+        buffer->vmo = ZX_HANDLE_INVALID;
+    }
+}
+
+zx_status_t mmio_buffer_pin(mmio_buffer_t* buffer, zx_handle_t bti, mmio_pinned_buffer_t* out) {
+    zx_paddr_t paddr;
+    zx_handle_t pmt;
+    const uint32_t options = ZX_BTI_PERM_WRITE | ZX_BTI_PERM_READ;
+    const size_t vmo_offset = ROUNDDOWN(buffer->offset, ZX_PAGE_SIZE);
+    const size_t page_offset = buffer->offset - vmo_offset;
+    const size_t vmo_size = ROUNDUP(buffer->size + page_offset, ZX_PAGE_SIZE);
+
+    zx_status_t status = zx_bti_pin(bti, options, buffer->vmo, vmo_offset, vmo_size,
+                                    &paddr, 1, &pmt);
+    if (status != ZX_OK) {
+        return status;
+    }
+
+    out->mmio = buffer;
+    out->paddr = paddr + page_offset;
+    out->pmt = pmt;
+
+    return ZX_OK;
+}
+
+void mmio_buffer_unpin(mmio_pinned_buffer_t* buffer) {
+    if (buffer->pmt != ZX_HANDLE_INVALID) {
+        zx_pmt_unpin(buffer->pmt);
+        buffer->pmt = ZX_HANDLE_INVALID;
+    }
+}
diff --git a/system/ulib/ddk/rules.mk b/system/ulib/ddk/rules.mk
index f1a9509..baa6136 100644
--- a/system/ulib/ddk/rules.mk
+++ b/system/ulib/ddk/rules.mk
@@ -12,6 +12,7 @@
 
 MODULE_SRCS += \
     $(LOCAL_DIR)/io-buffer.c \
+    $(LOCAL_DIR)/mmio-buffer.c \
     $(LOCAL_DIR)/phys-iter.c \
 
 MODULE_STATIC_LIBS := system/ulib/pretty system/ulib/sync