igt-gpu-tools: add tests for ion/gem interaction
am: 2db11e1cbb

Change-Id: Ie6504fae45f3050bf600df8455ba3055920a9845
diff --git a/Android.bp b/Android.bp
index 77b8b0e..e970a85 100644
--- a/Android.bp
+++ b/Android.bp
@@ -10,7 +10,7 @@
         "-DHAVE_LIBGEN_H",
         "-DHAVE_MEMFD_CREATE",
     ],
-    static_libs: ["libelf", "libkmod"],
+    static_libs: ["libelf", "libkmod", "libion", "liblog"],
     shared_libs: ["libdrm"],
 }
 
@@ -19,6 +19,8 @@
     defaults: ["igt-gpu-tools-defaults"],
     srcs: [
         "lib/drmtest.c",
+        "lib/gem.c",
+        "lib/gem_msm.c",
         "lib/igt_aux.c",
         "lib/igt_core.c",
         "lib/igt_debugfs.c",
@@ -29,11 +31,13 @@
         "lib/igt_kms.c",
         "lib/igt_stats.c",
         "lib/igt_sysfs.c",
+        "lib/ion.c",
         "lib/ioctl_wrappers.c",
         "lib/i915/gem_mman.c",
-	"lib/uwildmat/uwildmat.c"
+        "lib/uwildmat/uwildmat.c",
     ],
     export_include_dirs: [
+        "include",
         "lib",
         "lib/stubs/drm",
         "prebuilt-intermediates",
@@ -58,3 +62,8 @@
     srcs: ["tests/kms_flip.c"],
 }
 
+cc_test {
+    name: "ion_fb",
+    defaults: ["igt-gpu-tools-test-defaults"],
+    srcs: ["tests/ion_fb.c"],
+}
diff --git a/lib/gem.c b/lib/gem.c
new file mode 100644
index 0000000..1f40192
--- /dev/null
+++ b/lib/gem.c
@@ -0,0 +1,119 @@
+#include "gem.h"
+
+#include "gem_msm.h"
+
+struct gem_driver_lookup
+{
+	const char name[16];
+	struct gem_driver *driver;
+};
+
+static struct gem_driver_lookup drivers[] = {
+	{
+		.name = "msm_drm",
+		.driver = &gem_msm_driver
+	}
+};
+
+static inline size_t num_drivers()
+{
+	return ARRAY_SIZE(drivers);
+}
+
+struct gem_driver *gem_get_driver(int drm_fd)
+{
+	char name[16] = {0};
+	drm_version_t version = {};
+
+	version.name_len = sizeof(name);
+	version.name = name;
+
+	if (drmIoctl(drm_fd, DRM_IOCTL_VERSION, &version))
+	{
+		return NULL;
+	}
+
+	for (struct gem_driver_lookup *di = drivers;
+	     di - drivers < num_drivers();
+	     ++di)
+	{
+		if (!strncmp(name, di->name, sizeof(name)))
+		{
+			return di->driver;
+		}
+	}
+
+	return NULL;
+}
+
+int gem_size(int drm_fd, size_t *size, uint32_t gem_handle)
+{
+	struct drm_gem_flink drm_gem_flink_arg = {
+		.handle = gem_handle,
+		.name = 0
+	};
+
+	if (drmIoctl(drm_fd,
+		     DRM_IOCTL_GEM_FLINK,
+		     &drm_gem_flink_arg))
+	{
+		return -1;
+	}
+
+	struct drm_gem_open drm_gem_open_arg = {
+		.name = drm_gem_flink_arg.name,
+		.handle = 0,
+		.size = 0
+	};
+
+	if (drmIoctl(drm_fd,
+		     DRM_IOCTL_GEM_OPEN,
+		     &drm_gem_open_arg))
+	{
+		return -1;
+	}
+
+	*size = drm_gem_open_arg.size;
+
+	if (drm_gem_open_arg.handle != gem_handle)
+	{
+		gem_release_handle(drm_fd, drm_gem_open_arg.handle);
+	}
+
+	return 0;
+}
+
+void gem_release_handle(int drm_fd, uint32_t gem_handle)
+{
+	struct drm_gem_close drm_gem_close_arg = {
+		.handle = gem_handle,
+		.pad = 0
+	};
+
+	drmIoctl(drm_fd, DRM_IOCTL_GEM_CLOSE, &drm_gem_close_arg);
+}
+
+int drm_fb_for_gem_handle(int drm_fd, uint32_t *fb_id, uint32_t gem_handle,
+			     const struct fb_configuration *fb_config)
+{
+	struct drm_mode_fb_cmd2 drm_mode_addfb2_arg = {
+		.fb_id = 0,
+		.width = fb_config->width,
+		.height = fb_config->height,
+		.pixel_format = fb_config->pixel_format,
+		.flags = DRM_MODE_FB_MODIFIERS,
+		.handles = { gem_handle, 0, 0, 0 },
+		.pitches = { fb_config->width * fb_config->pixel_size, 0, 0, 0 },
+		.offsets = { 0, 0, 0, 0 },
+		.modifier = { 0 }
+	};
+
+	if (drmIoctl(drm_fd, DRM_IOCTL_MODE_ADDFB2, &drm_mode_addfb2_arg))
+	{
+		return -1;
+	}
+
+	*fb_id = drm_mode_addfb2_arg.fb_id;
+
+	return 0;
+}
diff --git a/lib/gem.h b/lib/gem.h
new file mode 100644
index 0000000..790d9ad
--- /dev/null
+++ b/lib/gem.h
@@ -0,0 +1,85 @@
+#ifndef GEM_H
+#define GEM_H
+
+#include "igt.h"
+
+struct gem_driver
+{
+	/**
+	 * mmap:
+	 * @ptr: (out) pointer to the buffer in the user process's memory
+	 * @drm_fd: open DRM device fd
+	 * @gem_handle: GEM handle
+	 * @size: exact size of the GEM buffer
+	 *
+	 * Maps the buffer backing the GEM handle for reading and writing.
+	 *
+	 * Returns: 0 on success, -1 otherwise
+	 **/
+	int (*mmap)(void **ptr, int drm_fd, uint32_t gem_handle, size_t size);
+
+	/**
+	 * munmap:
+	 * @drm_fd: open DRM device fd
+	 * @gem_handle: GEM handle
+	 * @ptr: pointer (see ptr argument to mmap)
+	 * @size: exact size of the mapped area
+	 *
+	 * Unmaps a region previously mapped with mmap.
+	 *
+	 * Returns: 0 on success: -1 otherwise
+	 **/
+	int (*munmap)(int drm_fd, uint32_t gem_handle, void *ptr, size_t size);
+};
+
+/**
+ * gem_get_driver:
+ * @drm_fd: open DRM device fd
+ *
+ * Gets the driver-specific GEM APIs for a particular device.
+ *
+ * Returns: a struct with function pointers on success; NULL otherwise
+ **/
+struct gem_driver *gem_get_driver(int drm_fd);
+
+/**
+ * gem_size:
+ * @drm_fd: open DRM device fd
+ * @size: (out) size of the buffer
+ * @gem_handle: GEM handle
+ * Returns: size of the buffer backing the GEM handle.
+ **/
+int gem_size(int drm_fd, size_t *size, uint32_t gem_handle);
+
+/**
+ * gem_release_handle
+ * @drm_fd: open DRM device fd
+ * @gem_handle: GEM handle
+ *
+ * Releases a GEM handle.
+ **/
+void gem_release_handle(int drm_fd, uint32_t gem_handle);
+
+struct fb_configuration
+{
+	uint32_t width;
+	uint32_t height;
+	uint32_t pixel_format;
+	uint32_t pixel_size;
+};
+
+/**
+ * drm_fb_for_gem_handle
+ * @drm_fd: open DRM device fd
+ * @fb_id: (out) id of the DRM KMS fb
+ * @gem_handle: GEM handle
+ * @fb_config: metadata for the fb
+ *
+ * Converts a GEM buffer into a DRM KMS fb
+ *
+ * Returns: 0 if the buffer could be converted; -1 otherwise
+ **/
+int drm_fb_for_gem_handle(int drm_fd, uint32_t *fb_id, uint32_t gem_handle,
+			  const struct fb_configuration *fb_config);
+
+#endif
diff --git a/lib/gem_msm.c b/lib/gem_msm.c
new file mode 100644
index 0000000..2160f44
--- /dev/null
+++ b/lib/gem_msm.c
@@ -0,0 +1,68 @@
+#include "gem_msm.h"
+#include "drm-uapi/msm_drm.h"
+
+#include "drm.h"
+
+static int gem_msm_mmap(void **ptr, int drm_fd, uint32_t gem_handle, size_t size)
+{
+	struct drm_msm_gem_cpu_prep gem_prep = {
+		.handle = gem_handle,
+		.op = MSM_PREP_READ,
+		.timeout = { .tv_sec = 1, .tv_nsec = 0 }
+	};
+
+	if (drmIoctl(drm_fd, DRM_IOCTL_MSM_GEM_CPU_PREP, &gem_prep))
+	{
+		return -1;
+	}
+
+	struct drm_msm_gem_info gem_info = {
+		.handle = gem_handle,
+		.flags = 0,
+		.offset = 0
+	};
+
+	if (drmIoctl(drm_fd, DRM_IOCTL_MSM_GEM_INFO, &gem_info))
+	{
+		return -1;
+	}
+
+	void *const k_addr = 0;
+	const int k_prot = PROT_READ | PROT_WRITE;
+	const int k_flags = MAP_SHARED;
+
+	void *ret = mmap(k_addr, size, k_prot, k_flags, drm_fd, gem_info.offset);
+
+	if (ret == MAP_FAILED)
+	{
+		return -1;
+	}
+
+	*ptr = ret;
+
+	return 0;
+}
+
+static int gem_msm_munmap(int drm_fd, uint32_t gem_handle, void *ptr, size_t size)
+{
+	if (munmap(ptr, size))
+	{
+		return -1;
+	}
+
+	struct drm_msm_gem_cpu_fini gem_fini = {
+		.handle = gem_handle
+	};
+
+	if (drmIoctl(drm_fd, DRM_IOCTL_MSM_GEM_CPU_FINI, &gem_fini))
+	{
+		return -1;
+	}
+
+	return 0;
+}
+
+struct gem_driver gem_msm_driver = {
+	.mmap = gem_msm_mmap,
+	.munmap = gem_msm_munmap
+};
diff --git a/lib/gem_msm.h b/lib/gem_msm.h
new file mode 100644
index 0000000..4b9724b
--- /dev/null
+++ b/lib/gem_msm.h
@@ -0,0 +1,8 @@
+#ifndef GEM_MSM_H
+#define GEM_MSM_H
+
+#include "gem.h"
+
+extern struct gem_driver gem_msm_driver;
+
+#endif
diff --git a/lib/ion.c b/lib/ion.c
new file mode 100644
index 0000000..59f7b3b
--- /dev/null
+++ b/lib/ion.c
@@ -0,0 +1,180 @@
+#include <ion/ion.h>
+#include <linux/ion_4.12.h>
+
+#include "ion.h"
+
+int ion_get_heap_id(int ion_fd, uint32_t heap_type)
+{
+	int heap_count = 0;
+	int ret = -1;
+
+	if (ion_query_heap_cnt(ion_fd, &heap_count))
+	{
+		return -1;
+	}
+
+	struct ion_heap_data *heap_data =
+			malloc(heap_count * sizeof(struct ion_heap_data));
+
+	if (ion_query_get_heaps(ion_fd, heap_count, heap_data))
+	{
+		free(heap_data);
+		return -1;
+	}
+
+	for (struct ion_heap_data *hi = heap_data;
+	     (hi - heap_data) < heap_count;
+	     ++hi)
+	{
+		if (hi->type == heap_type)
+		{
+			ret = hi->heap_id;
+			break;
+		}
+	}
+
+	free(heap_data);
+
+	return ret;
+}
+
+static const int kBitsInAnInt = (sizeof(int) * 8);
+
+int ion_alloc_one_fd(int ion_fd, size_t size, int heap_id, int *ion_buffer_fd)
+{
+	if (heap_id < 0 || heap_id >= kBitsInAnInt)
+	{
+		return -1;
+	}
+
+	const int align = 0;
+	const int heap_mask = 1 << heap_id;
+	const int flags = 0;
+
+	return ion_alloc_fd(ion_fd, size, align, heap_mask, flags, ion_buffer_fd);
+}
+
+int ion_mmap(void **ptr, int ion_buffer_fd, size_t size)
+{
+	void *const k_addr = 0;
+	const int k_prot = PROT_READ | PROT_WRITE;
+	const int k_flags = MAP_SHARED;
+
+	void *ret = mmap(k_addr, size, k_prot, k_flags, ion_buffer_fd, 0);
+
+	if (ret == MAP_FAILED)
+	{
+		return -1;
+	}
+
+	*ptr = ret;
+	return 0;
+}
+
+int ion_munmap(void *ptr, size_t size)
+{
+	if (munmap(ptr, size))
+	{
+		return -1;
+	}
+
+	return 0;
+}
+
+int drm_check_prime_caps(int drm_fd)
+{
+	struct drm_get_cap drm_get_cap_arg = {
+		.capability = DRM_CAP_PRIME,
+		.value = 0
+	};
+
+	if (drmIoctl(drm_fd, DRM_IOCTL_GET_CAP, &drm_get_cap_arg))
+	{
+		return -1;
+	}
+
+	if (!(drm_get_cap_arg.value & DRM_PRIME_CAP_IMPORT) ||
+	    !(drm_get_cap_arg.value & DRM_PRIME_CAP_EXPORT))
+	{
+		return -1;
+	}
+
+	return 0;
+}
+
+int gem_handle_for_ion_buffer(int drm_fd, uint32_t *gem_handle, int ion_buffer_fd)
+{
+	struct drm_prime_handle drm_prime_fd_to_handle_arg = {
+		.handle = 0,
+		.flags = 0,
+		.fd = ion_buffer_fd
+	};
+
+	if (drmIoctl(drm_fd,
+		     DRM_IOCTL_PRIME_FD_TO_HANDLE,
+		     &drm_prime_fd_to_handle_arg))
+	{
+		return -1;
+	}
+
+	*gem_handle = drm_prime_fd_to_handle_arg.handle;
+
+	return 0;
+}
+
+int ion_fd_for_gem_handle(int drm_fd, int *ion_fd, uint32_t gem_handle)
+{
+	struct drm_prime_handle drm_prime_handle_to_fd_arg = {
+		.handle = gem_handle,
+		.flags = 0,
+		.fd = 0
+	};
+
+	if (drmIoctl(drm_fd,
+		     DRM_IOCTL_PRIME_HANDLE_TO_FD,
+		     &drm_prime_handle_to_fd_arg))
+	{
+		return -1;
+	}
+
+	*ion_fd = drm_prime_handle_to_fd_arg.fd;
+
+	return 0;
+}
+
+int drm_fb_for_ion_buffer(int drm_fd, uint32_t *fb_id, int ion_buffer_fd,
+			  const struct fb_configuration *fb_config)
+{
+	uint32_t gem_handle = 0;
+
+	if (gem_handle_for_ion_buffer(drm_fd, &gem_handle, ion_buffer_fd))
+	{
+		return -1;
+	}
+
+	int ret = drm_fb_for_gem_handle(drm_fd, fb_id, gem_handle, fb_config);
+
+	gem_release_handle(drm_fd, gem_handle);
+	return ret;
+}
+
+void drm_release_fb(int drm_fd, uint32_t fb_id)
+{
+	(void)drmIoctl(drm_fd, DRM_IOCTL_MODE_RMFB, &fb_id);
+}
+
+int ion_clone_fd_via_gem(int drm_fd, int *cloned_fd, int ion_buffer_fd)
+{
+	uint32_t gem_handle = 0;
+	if (gem_handle_for_ion_buffer(drm_fd, &gem_handle, ion_buffer_fd))
+	{
+		return -1;
+	}
+
+	int ret = ion_fd_for_gem_handle(drm_fd, cloned_fd, gem_handle);
+	gem_release_handle(drm_fd, gem_handle);
+
+	return ret;
+}
+
+
diff --git a/lib/ion.h b/lib/ion.h
new file mode 100644
index 0000000..c598b96
--- /dev/null
+++ b/lib/ion.h
@@ -0,0 +1,108 @@
+#ifndef ION_GEM_H
+#define ION_GEM_H
+
+#include "igt.h"
+#include "gem.h"
+
+/**
+ * ion_get_heap_id:
+ * @ion_fd: open ion device fd
+ * @heap_type: ION_HEAP_TYPE_* constant
+ * Returns: the index of the first heap with type matching heap_type, or -1 on
+ * failure
+ **/
+int ion_get_heap_id(int ion_fd, uint32_t heap_type);
+
+/**
+ * ion_alloc_one_fd
+ * @ion_fd: open ion device fd
+ * @size: size of the desired ion buffer
+ * @heap_id: index of the heap to allocate from
+ * @ion_buffer_fd: (out) ion buffer fd
+ * Returns: 0 on success; not 0 otherwise
+ **/
+int ion_alloc_one_fd(int ion_fd, size_t size, int heap_id, int *ion_buffer_fd);
+
+/**
+ * ion_mmap
+ * @ptr: (out) pointer to the buffer in the user process's memory
+ * @ion_buffer_fd: ion buffer fd
+ * @size: size of the desired mapping
+ * Returns: 0 on success; not 0 otherwise
+ **/
+int ion_mmap(void **ptr, int ion_buffer_fd, size_t size);
+
+/**
+ * ion_munmap
+ * @ptr: pointer to the buffer in the user process's memory
+ * @size: exact size of the mapping
+ * Returns: 0 on success; not 0 otherwise
+ **/
+int ion_munmap(void *ptr, size_t size);
+
+/**
+ * drm_check_prime_caps
+ * drm_fd: open DRM device fd
+ * Returns: 0 if the device supports Prime import/export; -1 otherwise
+ **/
+int drm_check_prime_caps(int drm_fd);
+
+/**
+ * gem_handle_for_ion_buffer
+ * drm_fd: open DRM device fd
+ * gem_handle: (out) GEM handle
+ * ion_fd: ion buffer fd
+ *
+ * Imports an ion buffer into GEM
+ *
+ * Returns: 0 if the ion buffer could be imported; -1 otherwise
+ **/
+int gem_handle_for_ion_buffer(int drm_fd, uint32_t *gem_handle, int ion_buffer_fd);
+
+/**
+ * ion_fd_for_gem_handle
+ * drm_fd: open DRM device fd
+ * ion_fd: ion buffer fd
+ *
+ * Exports a GEM buffer into ion
+ *
+ * Returns: 0 if the buffer could be exported; -1 otherwise
+ **/
+int ion_fd_for_gem_handle(int drm_fd, int *ion_bufferfd, uint32_t gem_handle);
+
+/**
+ * drm_fb_for_ion_buffer
+ * drm_fd: open DRM device fd
+ * fb_id: (out) id of the DRM KMS fb
+ * ion_fd: ion buffer fd
+ * fb_config: metadata for the fb
+ *
+ * Converts an ion buffer into a DRM KMS fb
+ *
+ * Returns: 0 if the buffer could be exported; -1 otherwise
+ **/
+int drm_fb_for_ion_buffer(int drm_fd, uint32_t *fb_id, int ion_buffer_fd,
+			  const struct fb_configuration *fb_config);
+
+/**
+ * drm_release_fb
+ * drm_fd: open DRM device fd
+ * fb_id: id of the DRM KMS fb
+ *
+ * Releases the DRM KMS fb
+ **/
+void drm_release_fb(int drm_fd, uint32_t fb_id);
+
+/**
+ * ion_clone_fd_via_gem
+ * drm_fd: open DRM device fd
+ * cloned_fd: (out) cloned buffer fd
+ * ion_fd: ion buffer fd
+ *
+ * Uses GEM to clone an ion fd by importing and re-exporting it.
+ *
+ * Returns: 0 if the buffer could be cloned; -1 otherwise
+ **/
+int ion_clone_fd_via_gem(int drm_fd, int *cloned_fd, int ion_buffer_fd);
+
+#endif
diff --git a/tests/ion_fb.c b/tests/ion_fb.c
new file mode 100644
index 0000000..b8b8616
--- /dev/null
+++ b/tests/ion_fb.c
@@ -0,0 +1,223 @@
+#include "drm.h"
+#include "gem.h"
+#include "igt.h"
+#include "ion.h"
+
+#include <ion/ion.h>
+
+size_t size_for_fb(const struct fb_configuration *config)
+{
+	return config->width * config->height * config->pixel_size;
+}
+
+/**
+ * test_make_fb
+ *
+ * Tests that an ion buffer can be ingested into DRM to the point
+ * where it can be used for a framebuffer
+ **/
+
+static void make_fb_with_buffer(int drm_fd, int ion_fd,
+				const struct fb_configuration *config,
+				int ion_buffer_fd)
+{
+	uint32_t fb_id = 0;
+
+	igt_assert_eq(0, drm_check_prime_caps(drm_fd));
+	igt_assert_eq(0, drm_fb_for_ion_buffer(
+			drm_fd, &fb_id, ion_buffer_fd, config));
+	drm_release_fb(drm_fd, fb_id);
+}
+
+static void make_fb_with_fds(int drm_fd, int ion_fd,
+			     const struct fb_configuration *config)
+{
+	int ion_buffer_fd;
+
+	const int heap_id = ion_get_heap_id(ion_fd, ION_HEAP_TYPE_SYSTEM);
+	igt_assert(heap_id != -1);
+
+	igt_assert(!ion_alloc_one_fd(
+			ion_fd,
+			size_for_fb(config),
+			heap_id,
+			&ion_buffer_fd));
+
+        make_fb_with_buffer(drm_fd, ion_fd, config, ion_buffer_fd);
+
+	close(ion_buffer_fd);
+}
+
+static void test_make_fb(const struct fb_configuration *config)
+{
+	const int drm_fd = drm_open_driver(DRIVER_ANY);
+	igt_assert(drm_fd >= 0);
+
+	const int ion_fd = ion_open();
+	igt_assert(ion_fd >= 0);
+
+	make_fb_with_fds(drm_fd, ion_fd, config);
+
+	ion_close(ion_fd);
+	close(drm_fd);
+}
+
+/**
+ * test_clone
+ *
+ * Tests that an ion buffer can be 'cloned' by making a GEM buffer out of
+ * it and then reversing the process
+ **/
+
+static void clone_with_fds(int drm_fd, int ion_fd,
+			   const struct fb_configuration *config)
+{
+	int ion_buffer_fd;
+
+	const int heap_id = ion_get_heap_id(ion_fd, ION_HEAP_TYPE_SYSTEM);
+	igt_assert(heap_id != -1);
+
+	igt_assert(!ion_alloc_one_fd(
+			ion_fd,
+			size_for_fb(config),
+			heap_id,
+			&ion_buffer_fd));
+
+	int clone_fd = 0;
+
+	igt_assert(!ion_clone_fd_via_gem(drm_fd, &clone_fd, ion_buffer_fd));
+
+	igt_assert(clone_fd >= 0);
+	igt_assert(clone_fd != ion_buffer_fd);
+
+	close(clone_fd);
+	close(ion_buffer_fd);
+}
+
+static void test_clone(const struct fb_configuration *config)
+{
+	const int drm_fd = drm_open_driver(DRIVER_ANY);
+	igt_assert(drm_fd >= 0);
+
+	const int ion_fd = ion_open();
+	igt_assert(ion_fd >= 0);
+
+	clone_with_fds(drm_fd, ion_fd, config);
+
+	ion_close(ion_fd);
+	close(drm_fd);
+}
+
+/**
+ * test_mmap
+ *
+ * Tests that the GEM version of an ion buffer contains the same data that
+ * the original ion buffer did
+ **/
+
+static void mmap_with_buffer(int drm_fd, int ion_fd,
+			     uint8_t *buffer, size_t size)
+{
+	int ion_buffer_fd;
+
+	const int heap_id = ion_get_heap_id(ion_fd, ION_HEAP_TYPE_SYSTEM);
+	igt_assert(heap_id != -1);
+
+	const struct gem_driver *gem = gem_get_driver(drm_fd);
+	igt_assert(gem != NULL);
+
+	igt_assert(!ion_alloc_one_fd(
+			ion_fd,
+			size,
+			heap_id,
+			&ion_buffer_fd));
+
+	void *ion_ptr = NULL;
+
+	igt_assert(!ion_mmap(&ion_ptr, ion_buffer_fd, size));
+
+	memcpy(buffer, ion_ptr, size);
+
+	igt_assert(!ion_munmap(ion_ptr, size));
+
+	uint32_t gem_handle = 0;
+
+	igt_assert(!gem_handle_for_ion_buffer(
+			drm_fd,
+			&gem_handle,
+			ion_buffer_fd));
+
+	close(ion_buffer_fd);
+
+	size_t gem_buf_size = 0;
+
+	igt_assert(!gem_size(drm_fd, &gem_buf_size, gem_handle));
+	igt_assert_eq(gem_buf_size, size);
+
+	void *gem_ptr = NULL;
+	igt_assert(!gem->mmap(
+			&gem_ptr,
+			drm_fd,
+			gem_handle,
+			size));
+
+	igt_assert(!memcmp(buffer, gem_ptr, size));
+
+	igt_assert(!gem->munmap(
+			drm_fd,
+			gem_handle,
+			gem_ptr,
+			size));
+
+	gem_release_handle(drm_fd, gem_handle);
+}
+
+static void mmap_with_fds(int drm_fd, int ion_fd,
+			      const struct fb_configuration *config)
+{
+	uint8_t *buffer = malloc(size_for_fb(config));
+	igt_assert(buffer);
+
+	mmap_with_buffer(drm_fd, ion_fd, buffer, size_for_fb(config));
+
+	free(buffer);
+}
+
+static void test_mmap(const struct fb_configuration *config)
+{
+	const int drm_fd = drm_open_driver(DRIVER_ANY);
+	igt_assert(drm_fd >= 0);
+
+	const int ion_fd = ion_open();
+	igt_assert(ion_fd >= 0);
+
+	mmap_with_fds(drm_fd, ion_fd, config);
+
+	ion_close(ion_fd);
+	close(drm_fd);
+}
+
+igt_main
+{
+	const struct fb_configuration config = {
+		.width = 1024,
+		.height = 1024,
+		.pixel_format = DRM_FORMAT_ABGR8888,
+		.pixel_size = 4
+	};
+
+	igt_subtest("make-fb")
+	{
+		test_make_fb(&config);
+	}
+
+	igt_subtest("clone")
+	{
+		test_clone(&config);
+	}
+
+	igt_subtest("mmap")
+	{
+		test_mmap(&config);
+	}
+}