diff --git a/src/aio-dio-regress/aio-dio-write-verify.c b/src/aio-dio-regress/aio-dio-write-verify.c
new file mode 100644
index 00000000..402ddcdb
--- /dev/null
+++ b/src/aio-dio-regress/aio-dio-write-verify.c
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2019 Red Hat, Inc. All Rights reserved.
+ *
+ * AIO writes from a start offset on a truncated file, verify there's not
+ * data corruption.
+ */
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include <libaio.h>
+
+unsigned long buf_size = 0;
+unsigned long size_KB  = 0;
+#define IO_PATTERN	0x5a
+
+void
+usage(char *progname)
+{
+	fprintf(stderr, "usage: %s [-s datasize] [-b bufsize] [-o startoff] [-t truncsize] filename\n"
+	        "\t-s datasize: specify the minimum data size(KB), doesn't count holes\n"
+	        "\t-b bufsize: buffer size\n"
+	        "\t-o startoff: start offset to write data, 0 by default\n"
+	        "\t-t truncsize: truncate the file to a special size at first 0 by default\n",
+	        progname);
+	exit(1);
+}
+
+void
+dump_buffer(
+	void	*buf,
+	off64_t	offset,
+	ssize_t	len)
+{
+	int	i, j;
+	char	*p;
+	int	new;
+
+	for (i = 0, p = (char *)buf; i < len; i += 16) {
+		char    *s = p;
+
+		if (i && !memcmp(p, p - 16, 16)) {
+			new = 0;
+		} else {
+			if (i)
+				printf("*\n");
+			new = 1;
+		}
+
+		if (!new) {
+			p += 16;
+			continue;
+		}
+
+		printf("%08llx  ", (unsigned long long)offset + i);
+		for (j = 0; j < 16 && i + j < len; j++, p++)
+			printf("%02x ", *p);
+		printf(" ");
+		for (j = 0; j < 16 && i + j < len; j++, s++) {
+			if (isalnum((int)*s))
+				printf("%c", *s);
+			else
+				printf(".");
+		}
+		printf("\n");
+
+	}
+	printf("%08llx\n", (unsigned long long)offset + i);
+}
+
+int main(int argc, char *argv[])
+{
+	struct io_context *ctx = NULL;
+	struct io_event evs[2];
+	struct iocb iocb1, iocb2;
+	struct iocb *iocbs[] = { &iocb1, &iocb2 };
+	void *buf;
+	int fd, err = 0;
+	off_t bytes;
+	int c;
+	char *cmp_buf = NULL;
+	char *filename = NULL;
+	/* start offset to write */
+	long long startoff = 0;
+	/* truncate size */
+	off_t tsize = 0;
+
+	while ((c = getopt(argc, argv, "s:b:o:t:")) != -1) {
+		char *endp;
+
+		switch (c) {
+		case 's':	/* XXX MB size will be extended */
+			size_KB = strtol(optarg, &endp, 0);
+			break;
+		case 'b':	/* buffer size */
+			buf_size = strtol(optarg, &endp, 0);
+			break;
+		case 'o':	/* start offset */
+			startoff = strtoll(optarg, &endp, 0);
+			break;
+		case 't':	/* initial truncate size */
+			tsize = strtoul(optarg, &endp, 0);
+			break;
+		default:
+			usage(argv[0]);
+		}
+	}
+
+	if (size_KB == 0)	/* default size is 64KB */
+		size_KB = 64;
+	if (buf_size < 2048)	/* default minimum buffer size is 2048 bytes */
+		buf_size = 2048;
+
+	if (optind == argc - 1)
+		filename = argv[optind];
+	else
+		usage(argv[0]);
+
+	fd = open(filename, O_DIRECT | O_CREAT | O_TRUNC | O_RDWR, 0600);
+	if (fd == -1) {
+		perror("open");
+		return 1;
+	}
+
+	/* truncate the file to a special size at first */
+	if (tsize != 0) {
+		ftruncate(fd, tsize);
+		if (fd == -1) {
+			perror("ftruncate");
+			return 1;
+		}
+	}
+
+	err = posix_memalign(&buf, getpagesize(), buf_size);
+	if (err) {
+		fprintf(stderr, "error %s during %s\n",
+			strerror(err),
+			"posix_memalign");
+		return 1;
+	}
+	cmp_buf = malloc(buf_size);
+	memset(cmp_buf, IO_PATTERN, buf_size);
+
+	err = io_setup(5, &ctx);
+	if (err) {
+		fprintf(stderr, "error %s during %s\n",
+			strerror(err),
+			"io_setup");
+		return 1;
+	}
+
+	bytes = 0;
+	/* Keep extending until size_KB */
+	while (bytes < size_KB * 1024) {
+		ssize_t sret;
+		int i;
+
+		memset(buf, IO_PATTERN, buf_size);
+
+		io_prep_pwrite(&iocb1, fd, buf, buf_size/2, \
+		               startoff + bytes + 0*buf_size/2);
+		io_prep_pwrite(&iocb2, fd, buf, buf_size/2, \
+		               startoff + bytes + 1*buf_size/2);
+
+		err = io_submit(ctx, 2, iocbs);
+		if (err != 2) {
+			fprintf(stderr, "error %s during %s\n",
+				strerror(err),
+				"io_submit");
+			return 1;
+		}
+
+		err = io_getevents(ctx, 2, 2, evs, NULL);
+		if (err != 2) {
+			fprintf(stderr, "error %s during %s\n",
+				strerror(err),
+				"io_getevents");
+			return 1;
+		}
+
+		for (i = 0; i < err; i++) {
+			/*
+			 * res is unsigned for some reason, so this is the best
+			 * way to detect that it contains a negative errno.
+			 */
+			if (evs[i].res > buf_size / 2) {
+				fprintf(stderr, "pwrite: %s\n",
+					strerror(-evs[i].res));
+				return 1;
+			}
+		}
+		fsync(fd);
+
+		/*
+		 * And then read it back, then compare with original content.
+		 */
+		sret = pread(fd, buf, buf_size, startoff + bytes);
+		if (sret == -1) {
+			perror("pread");
+			return 1;
+		} else if (sret != buf_size) {
+			fprintf(stderr, "short read %zd was less than %zu\n",
+				sret, buf_size);
+			return 1;
+		}
+		if (memcmp(buf, cmp_buf, buf_size)) {
+			printf("Find corruption\n");
+			dump_buffer(buf, 0, buf_size);
+			return 1;
+		}
+
+		/* Calculate how many bytes we've written after pread */
+		bytes += buf_size;
+	}
+
+	return 0;
+}
diff --git a/tests/generic/999 b/tests/generic/999
new file mode 100755
index 00000000..6fcc593a
--- /dev/null
+++ b/tests/generic/999
@@ -0,0 +1,71 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2019 Red Hat, Inc.  All Rights Reserved.
+#
+# FS QA Test No. 999
+#
+# Non-page-aligned direct AIO write test. AIO write from unalinged offset
+# on a file with different initial truncate i_size.
+#
+# Uncover "ext4: Fix data corruption caused by unaligned direct AIO"
+#
+seq=`basename $0`
+seqres=$RESULT_DIR/$seq
+echo "QA output created by $seq"
+
+here=`pwd`
+tmp=/tmp/$$
+status=1	# failure is the default!
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+_cleanup()
+{
+	cd /
+	rm -f $tmp.*
+}
+
+# get standard environment, filters and checks
+. ./common/rc
+. ./common/filter
+
+# remove previous $seqres.full before test
+rm -f $seqres.full
+
+# real QA test starts here
+_supported_fs generic
+_supported_os Linux
+_require_test
+_require_aiodio aio-dio-write-verify
+_require_test_program "feature"
+
+localfile=$TEST_DIR/tst-aio-dio-testfile
+diosize=`_min_dio_alignment $TEST_DEV`
+pagesize=`src/feature -s`
+bufsize=$((pagesize * 2))
+filesize=$((bufsize * 3 / 1024))
+
+# Need smaller logical block size to do non-page-aligned test
+if [ $diosize -ge $pagesize ];then
+	_notrun "Need device logical block size($diosize) < page size($pagesize)"
+fi
+
+rm -rf $localfile 2>/dev/null
+# page-aligned aiodio write verification at first
+$AIO_TEST -s $((bufsize * 10)) -b $bufsize $localfile
+
+# non-page-aligned aiodio write verification
+i=0
+while [ $((diosize * i)) -lt $bufsize ];do
+	truncsize=$((diosize * i++))
+	# non-page-aligned AIO write on different i_size file
+	$AIO_TEST -s $filesize -b $bufsize -o $diosize -t $truncsize $localfile
+	if [ $? -ne 0 ];then
+		echo "[FAIL] $AIO_TEST -s $filesize -b $bufsize -o $diosize -t $truncsize $localfile"
+	fi
+done
+
+echo "Silence is golden"
+
+# success, all done
+status=0
+exit
diff --git a/tests/generic/999.out b/tests/generic/999.out
new file mode 100644
index 00000000..3b276ca8
--- /dev/null
+++ b/tests/generic/999.out
@@ -0,0 +1,2 @@
+QA output created by 999
+Silence is golden
diff --git a/tests/generic/group b/tests/generic/group
index 15227b67..b1f93d99 100644
--- a/tests/generic/group
+++ b/tests/generic/group
@@ -534,3 +534,4 @@
 529 auto quick attr
 530 auto quick unlink
 531 auto quick unlink
+999 auto quick aio
