diff --git a/docs/meson.build b/docs/meson.build
new file mode 100644
index 000000000000..ead14c4015d9
--- /dev/null
+++ b/docs/meson.build
@@ -0,0 +1 @@
+subdir('reference')
diff --git a/docs/reference/intel-gpu-tools/generate_description_xml.sh b/docs/reference/intel-gpu-tools/generate_description_xml.sh
new file mode 100644
index 000000000000..8e39e0c4c289
--- /dev/null
+++ b/docs/reference/intel-gpu-tools/generate_description_xml.sh
@@ -0,0 +1,44 @@
+#!/bin/sh
+
+output=$1
+filter=$2
+testlist=$3
+testdir=$(dirname $testlist)
+
+echo "<?xml version=\"1.0\"?>" > $output
+echo "<!DOCTYPE refsect1 PUBLIC \"-//OASIS//DTD DocBook XML V4.3//EN\"" >> $output
+echo "               \"http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd\"" >> $output
+echo "[" >> $output
+echo "  <!ENTITY % local.common.attrib \"xmlns:xi  CDATA  #FIXED 'http://www.w3.org/2003/XInclude'\">" >> $output
+echo "  <!ENTITY version SYSTEM \"version.xml\">" >> $output
+echo "]>" >> $output
+echo "<refsect1>" >> $output
+echo "<title>Description</title>" >> $output
+for test in `cat $testlist | tr ' ' '\n' | grep "^$filter" | sort`; do
+	echo "<refsect2 id=\"$test\"><title>" >> $output;
+	echo "$test" | perl -pe 's/(?<=_)$(KEYWORDS)(?=(_|\W))/<acronym>\1<\/acronym>/g' >> $output;
+	echo "</title><para><![CDATA[" >> $output;
+	testprog=$testdir/$test;
+	 ./$testprog --help-description >> $output;
+	echo "]]></para>" >> $output;
+	if ./$testprog --list-subtests > /dev/null ; then
+		echo "<refsect3><title>Subtests</title>" >> $output;
+		subtest_list=`./$testprog --list-subtests`;
+		subtest_count=`echo $subtest_list | wc -w`;
+		if [ $subtest_count -gt 100 ]; then
+			echo "<para>This test has over 100 subtests. " >> $output;
+			echo "Run <command>$test</command> <option>--list-subtests</option> to list them.</para>" >> $output;
+		else
+			echo "<simplelist>" >> $output;
+			for subtest in $subtest_list; do
+				echo "<member>" >> $output;
+				echo "$subtest" | perl -pe 's/\b$(KEYWORDS)\b/<acronym>\1<\/acronym>/g' >> $output;
+				echo "</member>" >> $output;
+			done;
+			echo "</simplelist>" >> $output;
+		fi;
+		echo "</refsect3>" >> $output;
+	fi;
+	echo "</refsect2>" >> $output;
+done;
+echo "</refsect1>" >> $output
diff --git a/docs/reference/intel-gpu-tools/generate_programs_xml.sh b/docs/reference/intel-gpu-tools/generate_programs_xml.sh
new file mode 100755
index 000000000000..73adc8cc7bfc
--- /dev/null
+++ b/docs/reference/intel-gpu-tools/generate_programs_xml.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+output=$1
+filter=$2
+testlist=$3
+
+echo "<?xml version=\"1.0\"?>" > $output
+echo "<!DOCTYPE refsect1 PUBLIC \"-//OASIS//DTD DocBook XML V4.3//EN\"" >> $output
+echo "               \"http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd\"" >> $output
+echo "[" >> $output
+echo "  <!ENTITY % local.common.attrib \"xmlns:xi  CDATA  #FIXED 'http://www.w3.org/2003/XInclude'\">" >> $output
+echo "  <!ENTITY version SYSTEM \"version.xml\">" >> $output
+echo "]>" >> $output
+echo "<refsect1>" >> $output
+echo "<title>Programs</title>" >> $output
+echo "<informaltable pgwide=\"1\" frame=\"none\"><tgroup cols=\"2\"><tbody>" >> $output
+for test in `cat $testlist | tr ' ' '\n' | grep "^$filter" | sort`; do
+	echo "<row><entry role=\"program_name\">" >> $output;
+	echo "<link linkend=\"$test\">$test</link></entry></row>" >> $output;
+done;
+echo "</tbody></tgroup></informaltable>" >> $output
+echo "</refsect1>" >> $output
diff --git a/docs/reference/intel-gpu-tools/meson.build b/docs/reference/intel-gpu-tools/meson.build
new file mode 100644
index 000000000000..1c009229aae2
--- /dev/null
+++ b/docs/reference/intel-gpu-tools/meson.build
@@ -0,0 +1,80 @@
+gnome = import('gnome')
+
+ignore_headers = [
+	'gen6_render.h',
+	'gen7_media.h',
+	'gen7_render.h',
+	'gen8_media.h',
+	'gen8_render.h',
+	'gpgpu_fill.h',
+	'i830_reg.h',
+	'i915_3d.h',
+	'i915_pciids.h',
+	'i915_reg.h',
+	'igt_edid_template.h',
+	'intel_reg.h',
+	'debug.h',
+	'instdone.h',
+	'media_fill.h',
+	'rendercopy.h',
+	'media_spin.h',
+	'media_fill_gen9.h',
+	'gen9_render.h',
+	'version.h',
+]
+
+test_groups = [
+	'amdgpu',
+	'chamelium',
+	'core',
+	'debugfs',
+	'drm',
+	'drv',
+	'gem',
+	'gen3',
+	'gen7',
+	'gvt',
+	'kms',
+	'meta',
+	'perf',
+	'pm',
+	'prime',
+	'sw_sync',
+	'testdisplay',
+	'tools',
+	'vgem',
+]
+
+gen_description = find_program('generate_description_xml.sh')
+gen_programs = find_program('generate_programs_xml.sh')
+
+test_list_files = []
+
+foreach group : test_groups
+	programs_xml = 'igt_test_programs_' + group + '_programs.xml'
+	custom_target(programs_xml,
+		      output : programs_xml,
+		      build_by_default : true,
+		      command : [ gen_programs, '@OUTPUT@', group, test_list ])
+
+	description_xml = 'igt_test_programs_' + group + '_description.xml'
+	custom_target(description_xml,
+		      output : description_xml,
+		      build_by_default : true,
+		      depends : test_executables,
+		      command : [ gen_description, '@OUTPUT@', group, test_list ])
+endforeach
+
+configure_file(input: 'version.xml.in',
+	       output: 'version.xml',
+	       install: false, configuration: config)
+
+gnome.gtkdoc('intel-gpu-tools',
+	     content_files : ['igt_test_programs.xml'],
+	     dependencies : lib_igt,
+	     install : true,
+	     main_xml : 'intel-gpu-tools-docs.xml',
+	     scan_args : '--rebuild-sections',
+	     mkdb_args : '--output-format=xml',
+	     ignore_headers : ignore_headers,
+	     src_dir : inc_for_gtkdoc)
diff --git a/docs/reference/meson.build b/docs/reference/meson.build
new file mode 100644
index 000000000000..944f28a5615e
--- /dev/null
+++ b/docs/reference/meson.build
@@ -0,0 +1 @@
+subdir('intel-gpu-tools')
diff --git a/meson.build b/meson.build
index 2361866b32d2..e3fb0cd5ed61 100644
--- a/meson.build
+++ b/meson.build
@@ -28,6 +28,8 @@ endforeach
 
 inc = include_directories('include/drm-uapi', 'lib', '.')
 
+inc_for_gtkdoc = include_directories('lib')
+
 config = configuration_data()
 
 libdrm = dependency('libdrm', version : '>=2.4.82')
@@ -139,3 +141,4 @@ if libdrm_intel.found()
 	endif
 endif
 subdir('man')
+subdir('docs')
diff --git a/meson.sh b/meson.sh
index cbf1a9326dbe..ae8e3488d962 100755
--- a/meson.sh
+++ b/meson.sh
@@ -28,7 +28,7 @@ install uninstall:
 	echo "meson install support not yet completed" && false
 
 docs:
-	echo "meson gtkdoc support not yet completed" && false
+	ninja -C build intel-gpu-tools-doc
 
 EOF
 
diff --git a/tests/meson.build b/tests/meson.build
index 9030cf0d1b5e..5555d5886164 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -261,12 +261,14 @@ endif
 
 libexecdir = join_paths(get_option('prefix'), get_option('libexecdir'), 'intel-gpu-tools')
 
+test_executables = []
+
 foreach prog : test_progs
 	link = []
 	if prog == 'perf_pmu'
 		link += lib_igt_perf
 	endif
-	executable(prog, prog + '.c',
+	test_executables += executable(prog, prog + '.c',
 		   dependencies : test_deps,
 		   install_dir : libexecdir,
 		   link_with : link,
@@ -288,6 +290,8 @@ custom_target('testlist',
 	      install : true,
 	      install_dir : pkgdatadir)
 
+test_list = files('test-list.txt')
+
 test_script = find_program('igt_command_line.sh')
 foreach prog : test_progs
 	test('testcase check: ' + prog, test_script,
