// SPDX-License-Identifier: MIT
/*
 * Copyright © 2022 Intel Corporation
 */

#include "igt.h"
#include "igt_vgem.h"
#include "sw_sync.h"
#include "dmabuf_sync_file.h"

IGT_TEST_DESCRIPTION("Tests for sync_file support in dma-buf");

/**
 * TEST: dmabuf_sync_file
 * Category: Core
 * Mega feature: General Core features
 * Sub-category: dmabuf
 * Description: Tests for sync_file support in dma-buf
 * Functionality: dmabuf test
 *
 * SUBTEST: export-basic
 * SUBTEST: export-before-signal
 * SUBTEST: export-multiwait
 * SUBTEST: export-wait-after-attach
 * SUBTEST: import-basic
 * SUBTEST: import-multiple-read-only
 * SUBTEST: import-multiple-read-write
 *
 */

static void test_export_basic(int fd)
{
	struct vgem_bo bo;
	int dmabuf;
	uint32_t fence;

	igt_require(has_dmabuf_export_sync_file(fd));

	bo.width = 1;
	bo.height = 1;
	bo.bpp = 32;
	vgem_create(fd, &bo);

	dmabuf = prime_handle_to_fd(fd, bo.handle);

	igt_assert(!dmabuf_busy(dmabuf, DMA_BUF_SYNC_READ));
	igt_assert(!dmabuf_busy(dmabuf, DMA_BUF_SYNC_WRITE));
	igt_assert(!dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_READ));
	igt_assert(!dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_WRITE));

	fence = vgem_fence_attach(fd, &bo, 0);
	igt_assert(!dmabuf_busy(dmabuf, DMA_BUF_SYNC_READ));
	igt_assert(dmabuf_busy(dmabuf, DMA_BUF_SYNC_WRITE));
	igt_assert(dmabuf_busy(dmabuf, DMA_BUF_SYNC_RW));
	igt_assert(!dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_READ));
	igt_assert(dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_WRITE));
	igt_assert(dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_RW));

	vgem_fence_signal(fd, fence);
	igt_assert(!dmabuf_busy(dmabuf, DMA_BUF_SYNC_READ));
	igt_assert(!dmabuf_busy(dmabuf, DMA_BUF_SYNC_WRITE));
	igt_assert(!dmabuf_busy(dmabuf, DMA_BUF_SYNC_RW));
	igt_assert(!dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_READ));
	igt_assert(!dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_WRITE));
	igt_assert(!dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_RW));

	fence = vgem_fence_attach(fd, &bo, VGEM_FENCE_WRITE);
	igt_assert(dmabuf_busy(dmabuf, DMA_BUF_SYNC_READ));
	igt_assert(dmabuf_busy(dmabuf, DMA_BUF_SYNC_WRITE));
	igt_assert(dmabuf_busy(dmabuf, DMA_BUF_SYNC_RW));
	igt_assert(dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_READ));
	igt_assert(dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_WRITE));
	igt_assert(dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_RW));

	vgem_fence_signal(fd, fence);
	igt_assert(!dmabuf_busy(dmabuf, DMA_BUF_SYNC_READ));
	igt_assert(!dmabuf_busy(dmabuf, DMA_BUF_SYNC_WRITE));
	igt_assert(!dmabuf_busy(dmabuf, DMA_BUF_SYNC_RW));
	igt_assert(!dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_READ));
	igt_assert(!dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_WRITE));
	igt_assert(!dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_RW));

	close(dmabuf);
	gem_close(fd, bo.handle);
}

static void test_export_before_signal(int fd)
{
	struct vgem_bo bo;
	int dmabuf, read_fd, write_fd;
	uint32_t fence;

	igt_require(has_dmabuf_export_sync_file(fd));

	bo.width = 1;
	bo.height = 1;
	bo.bpp = 32;
	vgem_create(fd, &bo);

	dmabuf = prime_handle_to_fd(fd, bo.handle);

	igt_assert(!dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_READ));
	igt_assert(!dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_WRITE));

	fence = vgem_fence_attach(fd, &bo, 0);

	read_fd = dmabuf_export_sync_file(dmabuf, DMA_BUF_SYNC_READ);
	write_fd = dmabuf_export_sync_file(dmabuf, DMA_BUF_SYNC_WRITE);

	igt_assert(!sync_file_busy(read_fd));
	igt_assert(sync_file_busy(write_fd));

	vgem_fence_signal(fd, fence);

	igt_assert(!sync_file_busy(read_fd));
	igt_assert(!sync_file_busy(write_fd));

	close(read_fd);
	close(write_fd);

	fence = vgem_fence_attach(fd, &bo, VGEM_FENCE_WRITE);

	read_fd = dmabuf_export_sync_file(dmabuf, DMA_BUF_SYNC_READ);
	write_fd = dmabuf_export_sync_file(dmabuf, DMA_BUF_SYNC_WRITE);

	igt_assert(sync_file_busy(read_fd));
	igt_assert(sync_file_busy(write_fd));

	vgem_fence_signal(fd, fence);

	igt_assert(!sync_file_busy(read_fd));
	igt_assert(!sync_file_busy(write_fd));

	close(read_fd);
	close(write_fd);

	close(dmabuf);
	gem_close(fd, bo.handle);
}

static void test_export_multiwait(int fd)
{
	struct vgem_bo bo;
	int dmabuf, sync_file;
	uint32_t fence1, fence2, fence3;

	igt_require(has_dmabuf_export_sync_file(fd));

	bo.width = 1;
	bo.height = 1;
	bo.bpp = 32;
	vgem_create(fd, &bo);

	dmabuf = prime_handle_to_fd(fd, bo.handle);

	fence1 = vgem_fence_attach(fd, &bo, 0);
	fence2 = vgem_fence_attach(fd, &bo, 0);

	sync_file = dmabuf_export_sync_file(dmabuf, DMA_BUF_SYNC_WRITE);

	fence3 = vgem_fence_attach(fd, &bo, 0);

	igt_assert(sync_file_busy(sync_file));

	vgem_fence_signal(fd, fence1);

	igt_assert(sync_file_busy(sync_file));

	vgem_fence_signal(fd, fence2);

	igt_assert(!sync_file_busy(sync_file));

	vgem_fence_signal(fd, fence3);

	close(sync_file);
	close(dmabuf);
	gem_close(fd, bo.handle);
}

static void test_export_wait_after_attach(int fd)
{
	struct vgem_bo bo;
	int dmabuf, read_sync_file, write_sync_file;
	uint32_t fence1, fence2;

	igt_require(has_dmabuf_export_sync_file(fd));

	bo.width = 1;
	bo.height = 1;
	bo.bpp = 32;
	vgem_create(fd, &bo);

	dmabuf = prime_handle_to_fd(fd, bo.handle);

	read_sync_file = dmabuf_export_sync_file(dmabuf, DMA_BUF_SYNC_READ);
	write_sync_file = dmabuf_export_sync_file(dmabuf, DMA_BUF_SYNC_WRITE);

	fence1 = vgem_fence_attach(fd, &bo, VGEM_FENCE_WRITE);

	igt_assert(!sync_file_busy(read_sync_file));
	igt_assert(!sync_file_busy(write_sync_file));
	close(read_sync_file);
	close(write_sync_file);

	/* These wait on fence1 */
	read_sync_file = dmabuf_export_sync_file(dmabuf, DMA_BUF_SYNC_READ);
	write_sync_file = dmabuf_export_sync_file(dmabuf, DMA_BUF_SYNC_WRITE);

	igt_assert(sync_file_busy(read_sync_file));
	igt_assert(sync_file_busy(write_sync_file));

	vgem_fence_signal(fd, fence1);
	fence2 = vgem_fence_attach(fd, &bo, VGEM_FENCE_WRITE);

	/* fence1 has signaled */
	igt_assert(!sync_file_busy(read_sync_file));
	igt_assert(!sync_file_busy(write_sync_file));

	/* fence2 has not */
	igt_assert(dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_READ));
	igt_assert(dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_WRITE));

	vgem_fence_signal(fd, fence2);
	close(read_sync_file);
	close(write_sync_file);

	close(dmabuf);
	gem_close(fd, bo.handle);
}

static void test_import_basic(int fd)
{
	struct vgem_bo bo;
	int dmabuf, timeline;

	igt_require_sw_sync();
	igt_require(has_dmabuf_import_sync_file(fd));

	bo.width = 1;
	bo.height = 1;
	bo.bpp = 32;
	vgem_create(fd, &bo);

	dmabuf = prime_handle_to_fd(fd, bo.handle);

	igt_assert(!dmabuf_busy(dmabuf, DMA_BUF_SYNC_READ));
	igt_assert(!dmabuf_busy(dmabuf, DMA_BUF_SYNC_WRITE));

	timeline = sw_sync_timeline_create();

	dmabuf_import_timeline_fence(dmabuf, DMA_BUF_SYNC_READ, timeline, 1);
	igt_assert(!dmabuf_busy(dmabuf, DMA_BUF_SYNC_READ));
	igt_assert(dmabuf_busy(dmabuf, DMA_BUF_SYNC_WRITE));
	igt_assert(dmabuf_busy(dmabuf, DMA_BUF_SYNC_RW));
	igt_assert(!dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_READ));
	igt_assert(dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_WRITE));
	igt_assert(dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_RW));

	sw_sync_timeline_inc(timeline, 1);
	igt_assert(!dmabuf_busy(dmabuf, DMA_BUF_SYNC_READ));
	igt_assert(!dmabuf_busy(dmabuf, DMA_BUF_SYNC_WRITE));
	igt_assert(!dmabuf_busy(dmabuf, DMA_BUF_SYNC_RW));
	igt_assert(!dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_READ));
	igt_assert(!dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_WRITE));
	igt_assert(!dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_RW));

	dmabuf_import_timeline_fence(dmabuf, DMA_BUF_SYNC_WRITE, timeline, 2);
	igt_assert(dmabuf_busy(dmabuf, DMA_BUF_SYNC_READ));
	igt_assert(dmabuf_busy(dmabuf, DMA_BUF_SYNC_WRITE));
	igt_assert(dmabuf_busy(dmabuf, DMA_BUF_SYNC_RW));
	igt_assert(dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_READ));
	igt_assert(dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_WRITE));
	igt_assert(dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_RW));

	sw_sync_timeline_inc(timeline, 1);
	igt_assert(!dmabuf_busy(dmabuf, DMA_BUF_SYNC_READ));
	igt_assert(!dmabuf_busy(dmabuf, DMA_BUF_SYNC_WRITE));
	igt_assert(!dmabuf_busy(dmabuf, DMA_BUF_SYNC_RW));
	igt_assert(!dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_READ));
	igt_assert(!dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_WRITE));
	igt_assert(!dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_RW));

	dmabuf_import_timeline_fence(dmabuf, DMA_BUF_SYNC_RW, timeline, 3);
	igt_assert(dmabuf_busy(dmabuf, DMA_BUF_SYNC_READ));
	igt_assert(dmabuf_busy(dmabuf, DMA_BUF_SYNC_WRITE));
	igt_assert(dmabuf_busy(dmabuf, DMA_BUF_SYNC_RW));
	igt_assert(dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_READ));
	igt_assert(dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_WRITE));
	igt_assert(dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_RW));

	sw_sync_timeline_inc(timeline, 1);
	igt_assert(!dmabuf_busy(dmabuf, DMA_BUF_SYNC_READ));
	igt_assert(!dmabuf_busy(dmabuf, DMA_BUF_SYNC_WRITE));
	igt_assert(!dmabuf_busy(dmabuf, DMA_BUF_SYNC_RW));
	igt_assert(!dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_READ));
	igt_assert(!dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_WRITE));
	igt_assert(!dmabuf_sync_file_busy(dmabuf, DMA_BUF_SYNC_RW));
}

static void test_import_multiple(int fd, bool write)
{
	struct vgem_bo bo;
	int i, dmabuf, read_sync_file, write_sync_file;
	int write_timeline = -1, read_timelines[32];

	igt_require_sw_sync();
	igt_require(has_dmabuf_import_sync_file(fd));

	bo.width = 1;
	bo.height = 1;
	bo.bpp = 32;
	vgem_create(fd, &bo);

	dmabuf = prime_handle_to_fd(fd, bo.handle);

	igt_assert(!dmabuf_busy(dmabuf, DMA_BUF_SYNC_READ));
	igt_assert(!dmabuf_busy(dmabuf, DMA_BUF_SYNC_WRITE));

	for (i = 0; i < ARRAY_SIZE(read_timelines); i++) {
		read_timelines[i] = sw_sync_timeline_create();
		dmabuf_import_timeline_fence(dmabuf, DMA_BUF_SYNC_READ,
					     read_timelines[i], 1);
	}

	if (write) {
		write_timeline = sw_sync_timeline_create();
		dmabuf_import_timeline_fence(dmabuf, DMA_BUF_SYNC_WRITE,
					     write_timeline, 1);
	}

	read_sync_file = dmabuf_export_sync_file(dmabuf, DMA_BUF_SYNC_READ);
	write_sync_file = dmabuf_export_sync_file(dmabuf, DMA_BUF_SYNC_WRITE);

	for (i = ARRAY_SIZE(read_timelines) - 1; i >= 0; i--) {
		igt_assert_eq(dmabuf_busy(dmabuf, DMA_BUF_SYNC_READ), write);
		igt_assert_eq(sync_file_busy(read_sync_file), write);
		igt_assert(dmabuf_busy(dmabuf, DMA_BUF_SYNC_WRITE));
		igt_assert(sync_file_busy(write_sync_file));

		sw_sync_timeline_inc(read_timelines[i], 1);
	}

	igt_assert_eq(dmabuf_busy(dmabuf, DMA_BUF_SYNC_READ), write);
	igt_assert_eq(sync_file_busy(read_sync_file), write);
	igt_assert_eq(dmabuf_busy(dmabuf, DMA_BUF_SYNC_WRITE), write);
	igt_assert_eq(sync_file_busy(write_sync_file), write);

	if (write)
		sw_sync_timeline_inc(write_timeline, 1);

	igt_assert(!dmabuf_busy(dmabuf, DMA_BUF_SYNC_READ));
	igt_assert(!sync_file_busy(read_sync_file));
	igt_assert(!dmabuf_busy(dmabuf, DMA_BUF_SYNC_WRITE));
	igt_assert(!sync_file_busy(write_sync_file));
}

igt_main
{
	int fd;

	igt_fixture {
		fd = drm_open_driver(DRIVER_VGEM);
	}

	igt_describe("Sanity test for exporting a sync_file from a dma-buf.");
	igt_subtest("export-basic")
		test_export_basic(fd);

	igt_describe("Test exporting a sync_file from a dma-buf before "
		     "signaling any of its fences.");
	igt_subtest("export-before-signal")
		test_export_before_signal(fd);

	igt_describe("Test exporting a sync_file from a dma-buf with multiple "
		     "fences on it.");
	igt_subtest("export-multiwait")
		test_export_multiwait(fd);

	igt_describe("Test exporting a sync_file from a dma-buf then adding "
		     "fences to the dma-buf before we wait.  The sync_file "
		     "should snapshot the current set of fences and not wait "
		     "for any fences added after it was exported.");
	igt_subtest("export-wait-after-attach")
		test_export_wait_after_attach(fd);

	igt_describe("Sanity test for importing a sync_file into a dma-buf.");
	igt_subtest("import-basic")
		test_import_basic(fd);

	igt_describe("Test importing multiple read-only fences into a dma-buf. "
		     "They should all block any write operations but not other "
		     "read operations.");
	igt_subtest("import-multiple-read-only")
		test_import_multiple(fd, false);

	igt_describe("Test importing multiple read-write fences into a "
		     "dma-buf. They should all block any read or write "
		     "operations.");
	igt_subtest("import-multiple-read-write")
		test_import_multiple(fd, true);

	igt_fixture
		drm_close_driver(fd);
}
