diff --git a/tools/perf/util/Build b/tools/perf/util/Build
index 034a6603d5a8..f153816f6fe1 100644
--- a/tools/perf/util/Build
+++ b/tools/perf/util/Build
@@ -84,6 +84,7 @@ perf-util-y += pmu.o
 perf-util-y += pmus.o
 perf-util-y += pmu-flex.o
 perf-util-y += pmu-bison.o
+perf-util-y += drm_pmu.o
 perf-util-y += hwmon_pmu.o
 perf-util-y += tool_pmu.o
 perf-util-y += svghelper.o
diff --git a/tools/perf/util/drm_pmu.c b/tools/perf/util/drm_pmu.c
new file mode 100644
index 000000000000..a76bba4fe47f
--- /dev/null
+++ b/tools/perf/util/drm_pmu.c
@@ -0,0 +1,689 @@
+// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
+#include "drm_pmu.h"
+#include "counts.h"
+#include "cpumap.h"
+#include "debug.h"
+#include "evsel.h"
+#include "pmu.h"
+#include <perf/threadmap.h>
+#include <api/fs/fs.h>
+#include <api/io.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <linux/unistd.h>
+#include <linux/kcmp.h>
+#include <linux/zalloc.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+
+enum drm_pmu_unit {
+	DRM_PMU_UNIT_BYTES,
+	DRM_PMU_UNIT_CAPACITY,
+	DRM_PMU_UNIT_CYCLES,
+	DRM_PMU_UNIT_HZ,
+	DRM_PMU_UNIT_NS,
+
+	DRM_PMU_UNIT_MAX,
+};
+
+struct drm_pmu_event {
+	const char *name;
+	const char *desc;
+	enum drm_pmu_unit unit;
+};
+
+struct drm_pmu {
+	struct perf_pmu pmu;
+	struct drm_pmu_event *events;
+	int num_events;
+};
+
+static const char * const drm_pmu_unit_strs[DRM_PMU_UNIT_MAX] = {
+	"bytes",
+	"capacity",
+	"cycles",
+	"hz",
+	"ns",
+};
+
+static const char * const drm_pmu_scale_unit_strs[DRM_PMU_UNIT_MAX] = {
+	"1bytes",
+	"1capacity",
+	"1cycles",
+	"1hz",
+	"1ns",
+};
+
+bool perf_pmu__is_drm(const struct perf_pmu *pmu)
+{
+	return pmu && pmu->type >= PERF_PMU_TYPE_DRM_START &&
+		pmu->type <= PERF_PMU_TYPE_DRM_END;
+}
+
+bool evsel__is_drm(const struct evsel *evsel)
+{
+	return perf_pmu__is_drm(evsel->pmu);
+}
+
+static struct drm_pmu *add_drm_pmu(struct list_head *pmus, char *line, size_t line_len)
+{
+	struct drm_pmu *drm;
+	struct perf_pmu *pmu;
+	const char *name;
+	__u32 max_drm_pmu_type = 0;
+	int i = 12;
+
+	if (line[line_len - 1] == '\n')
+		line[line_len - 1] = '\0';
+	while (isspace(line[i]))
+		i++;
+
+	line[--i] = '_';
+	line[--i] = 'm';
+	line[--i] = 'r';
+	line[--i] = 'd';
+	name = &line[i];
+
+	list_for_each_entry(pmu, pmus, list) {
+		if (!perf_pmu__is_drm(pmu))
+			continue;
+		if (pmu->type > max_drm_pmu_type)
+			max_drm_pmu_type = pmu->type;
+		if (!strcmp(pmu->name, name)) {
+			/* PMU already exists. */
+			return NULL;
+		}
+	}
+
+	drm = zalloc(sizeof(*drm));
+	if (!drm)
+		return NULL;
+
+	if (max_drm_pmu_type != 0)
+		drm->pmu.type = max_drm_pmu_type + 1;
+	else
+		drm->pmu.type = PERF_PMU_TYPE_DRM_START;
+
+	if (drm->pmu.type > PERF_PMU_TYPE_DRM_END) {
+		zfree(&drm);
+		pr_err("Unable to encode DRM PMU type for %s\n", name);
+		return NULL;
+	}
+	drm->pmu.name = strdup(name);
+	if (!drm->pmu.name) {
+		zfree(&drm);
+		return NULL;
+	}
+	drm->pmu.cpus = perf_cpu_map__new("0");
+	if (!drm->pmu.cpus) {
+		zfree(&drm->pmu.name);
+		zfree(&drm);
+		return NULL;
+	}
+	INIT_LIST_HEAD(&drm->pmu.format);
+	INIT_LIST_HEAD(&drm->pmu.aliases);
+	INIT_LIST_HEAD(&drm->pmu.caps);
+	return drm;
+}
+
+
+static bool starts_with(const char *str, const char *prefix)
+{
+	return !strncmp(prefix, str, strlen(prefix));
+}
+
+static int add_event(struct drm_pmu_event **events, int *num_events,
+		     const char *line, enum drm_pmu_unit unit, const char *desc)
+{
+	const char *colon = strchr(line, ':');
+	struct drm_pmu_event *tmp;
+
+	if (!colon)
+		return -EINVAL;
+
+	tmp = reallocarray(*events, *num_events + 1, sizeof(struct drm_pmu_event));
+	if (!tmp)
+		return -ENOMEM;
+	tmp[*num_events].unit = unit;
+	tmp[*num_events].desc = desc;
+	tmp[*num_events].name = strndup(line, colon - line);
+	if (!tmp[*num_events].name)
+		return -ENOMEM;
+	(*num_events)++;
+	*events = tmp;
+	return 0;
+}
+
+static int read_drm_pmus_cb(void *args, int fdinfo_dir_fd, const char *fd_name)
+{
+	struct list_head *pmus = args;
+	char buf[640];
+	struct io io;
+	char *line = NULL;
+	size_t line_len;
+	struct drm_pmu *drm = NULL;
+	struct drm_pmu_event *events = NULL;
+	int num_events = 0;
+
+	io__init(&io, openat(fdinfo_dir_fd, fd_name, O_RDONLY), buf, sizeof(buf));
+	if (io.fd == -1) {
+		/* Failed to open file, ignore. */
+		return 0;
+	}
+
+	while (io__getline(&io, &line, &line_len) > 0) {
+		if (starts_with(line, "drm-driver:")) {
+			drm = add_drm_pmu(pmus, line, line_len);
+			if (!drm)
+				break;
+			continue;
+		}
+		/*
+		 * Note the string matching below is alphabetical, with more
+		 * specific matches appearing before less specific.
+		 */
+		if (starts_with(line, "drm-active-")) {
+			add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES,
+				  "Total memory active in one or more engines");
+			continue;
+		}
+		if (starts_with(line, "drm-cycles-")) {
+			add_event(&events, &num_events, line, DRM_PMU_UNIT_CYCLES,
+				"Busy cycles");
+			continue;
+		}
+		if (starts_with(line, "drm-engine-capacity-")) {
+			add_event(&events, &num_events, line, DRM_PMU_UNIT_CAPACITY,
+				"Engine capacity");
+			continue;
+		}
+		if (starts_with(line, "drm-engine-")) {
+			add_event(&events, &num_events, line, DRM_PMU_UNIT_NS,
+				  "Utilization in ns");
+			continue;
+		}
+		if (starts_with(line, "drm-maxfreq-")) {
+			add_event(&events, &num_events, line, DRM_PMU_UNIT_HZ,
+				  "Maximum frequency");
+			continue;
+		}
+		if (starts_with(line, "drm-purgeable-")) {
+			add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES,
+				  "Size of resident and purgeable memory bufers");
+			continue;
+		}
+		if (starts_with(line, "drm-resident-")) {
+			add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES,
+				  "Size of resident memory bufers");
+			continue;
+		}
+		if (starts_with(line, "drm-shared-")) {
+			add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES,
+				  "Size of shared memory bufers");
+			continue;
+		}
+		if (starts_with(line, "drm-total-cycles-")) {
+			add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES,
+				  "Total busy cycles");
+			continue;
+		}
+		if (starts_with(line, "drm-total-")) {
+			add_event(&events, &num_events, line, DRM_PMU_UNIT_BYTES,
+				  "Size of shared and private memory");
+			continue;
+		}
+		if (verbose > 1 && starts_with(line, "drm-") &&
+		    !starts_with(line, "drm-client-id:") &&
+		    !starts_with(line, "drm-pdev:"))
+			pr_debug("Unhandled DRM PMU fdinfo line match '%s'\n", line);
+	}
+	if (drm) {
+		drm->events = events;
+		drm->num_events = num_events;
+		list_add_tail(&drm->pmu.list, pmus);
+	}
+	free(line);
+	if (io.fd != -1)
+		close(io.fd);
+	return 0;
+}
+
+void drm_pmu__exit(struct perf_pmu *pmu)
+{
+	struct drm_pmu *drm = container_of(pmu, struct drm_pmu, pmu);
+
+	free(drm->events);
+}
+
+bool drm_pmu__have_event(const struct perf_pmu *pmu, const char *name)
+{
+	struct drm_pmu *drm = container_of(pmu, struct drm_pmu, pmu);
+
+	if (!starts_with(name, "drm-"))
+		return false;
+
+	for (int i = 0; i < drm->num_events; i++) {
+		if (!strcasecmp(drm->events[i].name, name))
+			return true;
+	}
+	return false;
+}
+
+int drm_pmu__for_each_event(const struct perf_pmu *pmu, void *state, pmu_event_callback cb)
+{
+	struct drm_pmu *drm = container_of(pmu, struct drm_pmu, pmu);
+
+	for (int i = 0; i < drm->num_events; i++) {
+		char encoding_buf[128];
+		struct pmu_event_info info = {
+			.pmu = pmu,
+			.name = drm->events[i].name,
+			.alias = NULL,
+			.scale_unit = drm_pmu_scale_unit_strs[drm->events[i].unit],
+			.desc = drm->events[i].desc,
+			.long_desc = NULL,
+			.encoding_desc = encoding_buf,
+			.topic = "drm",
+			.pmu_name = pmu->name,
+			.event_type_desc = "DRM event",
+		};
+		int ret;
+
+		snprintf(encoding_buf, sizeof(encoding_buf), "%s/config=0x%x/", pmu->name, i);
+
+		ret = cb(state, &info);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+size_t drm_pmu__num_events(const struct perf_pmu *pmu)
+{
+	const struct drm_pmu *drm = container_of(pmu, struct drm_pmu, pmu);
+
+	return drm->num_events;
+}
+
+static int drm_pmu__index_for_event(const struct drm_pmu *drm, const char *name)
+{
+	for (int i = 0; i < drm->num_events; i++) {
+		if (!strcmp(drm->events[i].name, name))
+			return i;
+	}
+	return -1;
+}
+
+static int drm_pmu__config_term(const struct drm_pmu *drm,
+				  struct perf_event_attr *attr,
+				  struct parse_events_term *term,
+				  struct parse_events_error *err)
+{
+	if (term->type_term == PARSE_EVENTS__TERM_TYPE_USER) {
+		int i = drm_pmu__index_for_event(drm, term->config);
+
+		if (i >= 0) {
+			attr->config = i;
+			return 0;
+		}
+	}
+	if (err) {
+		char *err_str;
+
+		parse_events_error__handle(err, term->err_val,
+					asprintf(&err_str,
+						"unexpected drm event term (%s) %s",
+						parse_events__term_type_str(term->type_term),
+						term->config) < 0
+					? strdup("unexpected drm event term")
+					: err_str,
+					NULL);
+	}
+	return -EINVAL;
+}
+
+int drm_pmu__config_terms(const struct perf_pmu *pmu,
+			    struct perf_event_attr *attr,
+			    struct parse_events_terms *terms,
+			    struct parse_events_error *err)
+{
+	struct drm_pmu *drm = container_of(pmu, struct drm_pmu, pmu);
+	struct parse_events_term *term;
+
+	list_for_each_entry(term, &terms->terms, list) {
+		if (drm_pmu__config_term(drm, attr, term, err))
+			return -EINVAL;
+	}
+
+	return 0;
+}
+
+int drm_pmu__check_alias(const struct perf_pmu *pmu, struct parse_events_terms *terms,
+			 struct perf_pmu_info *info, struct parse_events_error *err)
+{
+	struct drm_pmu *drm = container_of(pmu, struct drm_pmu, pmu);
+	struct parse_events_term *term =
+		list_first_entry(&terms->terms, struct parse_events_term, list);
+
+	if (term->type_term == PARSE_EVENTS__TERM_TYPE_USER) {
+		int i = drm_pmu__index_for_event(drm, term->config);
+
+		if (i >= 0) {
+			info->unit = drm_pmu_unit_strs[drm->events[i].unit];
+			info->scale = 1;
+			return 0;
+		}
+	}
+	if (err) {
+		char *err_str;
+
+		parse_events_error__handle(err, term->err_val,
+					asprintf(&err_str,
+						"unexpected drm event term (%s) %s",
+						parse_events__term_type_str(term->type_term),
+						term->config) < 0
+					? strdup("unexpected drm event term")
+					: err_str,
+					NULL);
+	}
+	return -EINVAL;
+}
+
+struct minor_info {
+	unsigned int *minors;
+	int minors_num, minors_len;
+};
+
+static int for_each_drm_fdinfo_in_dir(int (*cb)(void *args, int fdinfo_dir_fd, const char *fd_name),
+				      void *args, int proc_dir, const char *pid_name,
+				      struct minor_info *minors)
+{
+	char buf[256];
+	DIR *fd_dir;
+	struct dirent *fd_entry;
+	int fd_dir_fd, fdinfo_dir_fd = -1;
+
+
+	scnprintf(buf, sizeof(buf), "%s/fd", pid_name);
+	fd_dir_fd = openat(proc_dir, buf, O_DIRECTORY);
+	if (fd_dir_fd == -1)
+		return 0; /* Presumably lost race to open. */
+	fd_dir = fdopendir(fd_dir_fd);
+	if (!fd_dir) {
+		close(fd_dir_fd);
+		return -ENOMEM;
+	}
+	while ((fd_entry = readdir(fd_dir)) != NULL) {
+		struct stat stat;
+		unsigned int minor;
+		bool is_dup = false;
+		int ret;
+
+		if (fd_entry->d_type != DT_LNK)
+			continue;
+
+		if (fstatat(fd_dir_fd, fd_entry->d_name, &stat, 0) != 0)
+			continue;
+
+		if ((stat.st_mode & S_IFMT) != S_IFCHR || major(stat.st_rdev) != 226)
+			continue;
+
+		minor = minor(stat.st_rdev);
+		for (int i = 0; i < minors->minors_num; i++) {
+			if (minor(stat.st_rdev) == minors->minors[i]) {
+				is_dup = true;
+				break;
+			}
+		}
+		if (is_dup)
+			continue;
+
+		if (minors->minors_num == minors->minors_len) {
+			unsigned int *tmp = reallocarray(minors->minors, minors->minors_len + 4,
+							 sizeof(unsigned int));
+
+			if (tmp) {
+				minors->minors = tmp;
+				minors->minors_len += 4;
+			}
+		}
+		minors->minors[minors->minors_num++] = minor;
+		if (fdinfo_dir_fd == -1) {
+			/* Open fdinfo dir if we have a DRM fd. */
+			scnprintf(buf, sizeof(buf), "%s/fdinfo", pid_name);
+			fdinfo_dir_fd = openat(proc_dir, buf, O_DIRECTORY);
+			if (fdinfo_dir_fd == -1)
+				continue;
+		}
+		ret = cb(args, fdinfo_dir_fd, fd_entry->d_name);
+		if (ret)
+			return ret;
+	}
+	if (fdinfo_dir_fd != -1)
+		close(fdinfo_dir_fd);
+	closedir(fd_dir);
+	return 0;
+}
+
+static int for_each_drm_fdinfo(bool skip_all_duplicates,
+			       int (*cb)(void *args, int fdinfo_dir_fd, const char *fd_name),
+			       void *args)
+{
+	DIR *proc_dir;
+	struct dirent *proc_entry;
+	int ret;
+	/*
+	 * minors maintains an array of DRM minor device numbers seen for a pid,
+	 * or for all pids if skip_all_duplicates is true, so that duplicates
+	 * are ignored.
+	 */
+	struct minor_info minors = {
+		.minors = NULL,
+		.minors_num = 0,
+		.minors_len = 0,
+	};
+
+	proc_dir = opendir(procfs__mountpoint());
+	if (!proc_dir)
+		return 0;
+
+	/* Walk through the /proc directory. */
+	while ((proc_entry = readdir(proc_dir)) != NULL) {
+		if (proc_entry->d_type != DT_DIR ||
+		    !isdigit(proc_entry->d_name[0]))
+			continue;
+		if (!skip_all_duplicates) {
+			/* Reset the seen minor numbers for each pid. */
+			minors.minors_num = 0;
+		}
+		ret = for_each_drm_fdinfo_in_dir(cb, args,
+						 dirfd(proc_dir), proc_entry->d_name,
+						 &minors);
+		if (ret)
+			break;
+	}
+	free(minors.minors);
+	closedir(proc_dir);
+	return ret;
+}
+
+int perf_pmus__read_drm_pmus(struct list_head *pmus)
+{
+	return for_each_drm_fdinfo(/*skip_all_duplicates=*/true, read_drm_pmus_cb, pmus);
+}
+
+int evsel__drm_pmu_open(struct evsel *evsel,
+			struct perf_thread_map *threads,
+			int start_cpu_map_idx, int end_cpu_map_idx)
+{
+	(void)evsel;
+	(void)threads;
+	(void)start_cpu_map_idx;
+	(void)end_cpu_map_idx;
+	return 0;
+}
+
+static uint64_t read_count_and_apply_unit(const char *count_and_unit, enum drm_pmu_unit unit)
+{
+	char *unit_ptr = NULL;
+	uint64_t count = strtoul(count_and_unit, &unit_ptr, 10);
+
+	if (!unit_ptr)
+		return 0;
+
+	while (isblank(*unit_ptr))
+		unit_ptr++;
+
+	switch (unit) {
+	case DRM_PMU_UNIT_BYTES:
+		if (*unit_ptr == '\0')
+			assert(count == 0); /* Generally undocumented, happens for 0. */
+		else if (!strcmp(unit_ptr, "KiB"))
+			count *= 1024;
+		else if (!strcmp(unit_ptr, "MiB"))
+			count *= 1024 * 1024;
+		else
+			pr_err("Unexpected bytes unit '%s'\n", unit_ptr);
+		break;
+	case DRM_PMU_UNIT_CAPACITY:
+		/* No units expected. */
+		break;
+	case DRM_PMU_UNIT_CYCLES:
+		/* No units expected. */
+		break;
+	case DRM_PMU_UNIT_HZ:
+		if (!strcmp(unit_ptr, "Hz"))
+			count *= 1;
+		else if (!strcmp(unit_ptr, "KHz"))
+			count *= 1000;
+		else if (!strcmp(unit_ptr, "MHz"))
+			count *= 1000000;
+		else
+			pr_err("Unexpected hz unit '%s'\n", unit_ptr);
+		break;
+	case DRM_PMU_UNIT_NS:
+		/* Only unit ns expected. */
+		break;
+	case DRM_PMU_UNIT_MAX:
+	default:
+		break;
+	}
+	return count;
+}
+
+static uint64_t read_drm_event(int fdinfo_dir_fd, const char *fd_name,
+			       const char *match, enum drm_pmu_unit unit)
+{
+	char buf[640];
+	struct io io;
+	char *line = NULL;
+	size_t line_len;
+	uint64_t count = 0;
+
+	io__init(&io, openat(fdinfo_dir_fd, fd_name, O_RDONLY), buf, sizeof(buf));
+	if (io.fd == -1) {
+		/* Failed to open file, ignore. */
+		return 0;
+	}
+	while (io__getline(&io, &line, &line_len) > 0) {
+		size_t i = strlen(match);
+
+		if (strncmp(line, match, i))
+			continue;
+		if (line[i] != ':')
+			continue;
+		while (isblank(line[++i]))
+			;
+		if (line[line_len - 1] == '\n')
+			line[line_len - 1] = '\0';
+		count = read_count_and_apply_unit(&line[i], unit);
+		break;
+	}
+	free(line);
+	close(io.fd);
+	return count;
+}
+
+struct read_drm_event_cb_args {
+	const char *match;
+	uint64_t count;
+	enum drm_pmu_unit unit;
+};
+
+static int read_drm_event_cb(void *vargs, int fdinfo_dir_fd, const char *fd_name)
+{
+	struct read_drm_event_cb_args *args = vargs;
+
+	args->count += read_drm_event(fdinfo_dir_fd, fd_name, args->match, args->unit);
+	return 0;
+}
+
+static uint64_t drm_pmu__read_system_wide(struct drm_pmu *drm, struct evsel *evsel)
+{
+	struct read_drm_event_cb_args args = {
+		.count = 0,
+		.match = drm->events[evsel->core.attr.config].name,
+		.unit = drm->events[evsel->core.attr.config].unit,
+	};
+
+	for_each_drm_fdinfo(/*skip_all_duplicates=*/false, read_drm_event_cb, &args);
+	return args.count;
+}
+
+static uint64_t drm_pmu__read_for_pid(struct drm_pmu *drm, struct evsel *evsel, int pid)
+{
+	struct read_drm_event_cb_args args = {
+		.count = 0,
+		.match = drm->events[evsel->core.attr.config].name,
+		.unit = drm->events[evsel->core.attr.config].unit,
+	};
+	struct minor_info minors = {
+		.minors = NULL,
+		.minors_num = 0,
+		.minors_len = 0,
+	};
+	int proc_dir = open(procfs__mountpoint(), O_DIRECTORY);
+	char pid_name[12];
+	int ret;
+
+	if (proc_dir < 0)
+		return 0;
+
+	snprintf(pid_name, sizeof(pid_name), "%d", pid);
+	ret = for_each_drm_fdinfo_in_dir(read_drm_event_cb, &args, proc_dir, pid_name, &minors);
+	free(minors.minors);
+	close(proc_dir);
+	return ret == 0 ? args.count : 0;
+}
+
+int evsel__drm_pmu_read(struct evsel *evsel, int cpu_map_idx, int thread)
+{
+	struct drm_pmu *drm = container_of(evsel->pmu, struct drm_pmu, pmu);
+	struct perf_counts_values *count, *old_count = NULL;
+	int pid = perf_thread_map__pid(evsel->core.threads, thread);
+	uint64_t counter;
+
+	if (pid != -1)
+		counter = drm_pmu__read_for_pid(drm, evsel, pid);
+	else
+		counter = drm_pmu__read_system_wide(drm, evsel);
+
+	if (evsel->prev_raw_counts)
+		old_count = perf_counts(evsel->prev_raw_counts, cpu_map_idx, thread);
+
+	count = perf_counts(evsel->counts, cpu_map_idx, thread);
+	if (old_count) {
+		count->val = old_count->val + counter;
+		count->run = old_count->run + 1;
+		count->ena = old_count->ena + 1;
+	} else {
+		count->val = counter;
+		count->run++;
+		count->ena++;
+	}
+	return 0;
+}
diff --git a/tools/perf/util/drm_pmu.h b/tools/perf/util/drm_pmu.h
new file mode 100644
index 000000000000..e7f366fca8a4
--- /dev/null
+++ b/tools/perf/util/drm_pmu.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
+#ifndef __DRM_PMU_H
+#define __DRM_PMU_H
+/*
+ * Linux DRM clients expose information through usage stats as documented in
+ * Documentation/gpu/drm-usage-stats.rst (available online at
+ * https://docs.kernel.org/gpu/drm-usage-stats.html). This is a tool like PMU
+ * that exposes DRM information.
+ */
+
+#include "pmu.h"
+#include <stdbool.h>
+
+struct list_head;
+struct perf_thread_map;
+
+void drm_pmu__exit(struct perf_pmu *pmu);
+bool drm_pmu__have_event(const struct perf_pmu *pmu, const char *name);
+int drm_pmu__for_each_event(const struct perf_pmu *pmu, void *state, pmu_event_callback cb);
+size_t drm_pmu__num_events(const struct perf_pmu *pmu);
+int drm_pmu__config_terms(const struct perf_pmu *pmu,
+			  struct perf_event_attr *attr,
+			  struct parse_events_terms *terms,
+			  struct parse_events_error *err);
+int drm_pmu__check_alias(const struct perf_pmu *pmu, struct parse_events_terms *terms,
+			 struct perf_pmu_info *info, struct parse_events_error *err);
+
+
+bool perf_pmu__is_drm(const struct perf_pmu *pmu);
+bool evsel__is_drm(const struct evsel *evsel);
+
+int perf_pmus__read_drm_pmus(struct list_head *pmus);
+
+int evsel__drm_pmu_open(struct evsel *evsel,
+			struct perf_thread_map *threads,
+			int start_cpu_map_idx, int end_cpu_map_idx);
+int evsel__drm_pmu_read(struct evsel *evsel, int cpu_map_idx, int thread);
+
+#endif /* __DRM_PMU_H */
diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c
index 1974395492d7..6722fbdbc72b 100644
--- a/tools/perf/util/evsel.c
+++ b/tools/perf/util/evsel.c
@@ -56,6 +56,7 @@
 #include "off_cpu.h"
 #include "pmu.h"
 #include "pmus.h"
+#include "drm_pmu.h"
 #include "hwmon_pmu.h"
 #include "tool_pmu.h"
 #include "rlimit.h"
@@ -1888,6 +1889,9 @@ int evsel__read_counter(struct evsel *evsel, int cpu_map_idx, int thread)
 	if (evsel__is_hwmon(evsel))
 		return evsel__hwmon_pmu_read(evsel, cpu_map_idx, thread);
 
+	if (evsel__is_drm(evsel))
+		return evsel__drm_pmu_read(evsel, cpu_map_idx, thread);
+
 	if (evsel__is_retire_lat(evsel))
 		return evsel__read_retire_lat(evsel, cpu_map_idx, thread);
 
@@ -2628,6 +2632,11 @@ static int evsel__open_cpu(struct evsel *evsel, struct perf_cpu_map *cpus,
 					     start_cpu_map_idx,
 					     end_cpu_map_idx);
 	}
+	if (evsel__is_drm(evsel)) {
+		return evsel__drm_pmu_open(evsel, threads,
+					   start_cpu_map_idx,
+					   end_cpu_map_idx);
+	}
 
 	for (idx = start_cpu_map_idx; idx < end_cpu_map_idx; idx++) {
 		cpu = perf_cpu_map__cpu(cpus, idx);
diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index b7ebac5ab1d1..6a2561f8b153 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -20,6 +20,7 @@
 #include "debug.h"
 #include "evsel.h"
 #include "pmu.h"
+#include "drm_pmu.h"
 #include "hwmon_pmu.h"
 #include "pmus.h"
 #include "tool_pmu.h"
@@ -1546,6 +1547,8 @@ int perf_pmu__config_terms(const struct perf_pmu *pmu,
 
 	if (perf_pmu__is_hwmon(pmu))
 		return hwmon_pmu__config_terms(pmu, attr, terms, err);
+	if (perf_pmu__is_drm(pmu))
+		return drm_pmu__config_terms(pmu, attr, terms, err);
 
 	list_for_each_entry(term, &terms->terms, list) {
 		if (pmu_config_term(pmu, attr, term, terms, zero, apply_hardcoded, err))
@@ -1683,6 +1686,10 @@ int perf_pmu__check_alias(struct perf_pmu *pmu, struct parse_events_terms *head_
 		ret = hwmon_pmu__check_alias(head_terms, info, err);
 		goto out;
 	}
+	if (perf_pmu__is_drm(pmu)) {
+		ret = drm_pmu__check_alias(pmu, head_terms, info, err);
+		goto out;
+	}
 
 	/* Fake PMU doesn't rewrite terms. */
 	if (perf_pmu__is_fake(pmu))
@@ -1860,6 +1867,8 @@ bool perf_pmu__have_event(struct perf_pmu *pmu, const char *name)
 		return false;
 	if (perf_pmu__is_hwmon(pmu))
 		return hwmon_pmu__have_event(pmu, name);
+	if (perf_pmu__is_drm(pmu))
+		return drm_pmu__have_event(pmu, name);
 	if (perf_pmu__find_alias(pmu, name, /*load=*/ true) != NULL)
 		return true;
 	if (pmu->cpu_aliases_added || !pmu->events_table)
@@ -1873,6 +1882,8 @@ size_t perf_pmu__num_events(struct perf_pmu *pmu)
 
 	if (perf_pmu__is_hwmon(pmu))
 		return hwmon_pmu__num_events(pmu);
+	if (perf_pmu__is_drm(pmu))
+		return drm_pmu__num_events(pmu);
 
 	pmu_aliases_parse(pmu);
 	nr = pmu->sysfs_aliases + pmu->sys_json_aliases;
@@ -1940,6 +1951,8 @@ int perf_pmu__for_each_event(struct perf_pmu *pmu, bool skip_duplicate_pmus,
 
 	if (perf_pmu__is_hwmon(pmu))
 		return hwmon_pmu__for_each_event(pmu, state, cb);
+	if (perf_pmu__is_drm(pmu))
+		return drm_pmu__for_each_event(pmu, state, cb);
 
 	strbuf_init(&sb, /*hint=*/ 0);
 	pmu_aliases_parse(pmu);
@@ -2403,6 +2416,8 @@ void perf_pmu__delete(struct perf_pmu *pmu)
 {
 	if (perf_pmu__is_hwmon(pmu))
 		hwmon_pmu__exit(pmu);
+	else if (perf_pmu__is_drm(pmu))
+		drm_pmu__exit(pmu);
 
 	perf_pmu__del_formats(&pmu->format);
 	perf_pmu__del_aliases(pmu);
diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h
index b93014cc3670..7bc5ac61e93a 100644
--- a/tools/perf/util/pmu.h
+++ b/tools/perf/util/pmu.h
@@ -38,7 +38,9 @@ struct perf_pmu_caps {
 
 enum {
 	PERF_PMU_TYPE_PE_START    = 0,
-	PERF_PMU_TYPE_PE_END      = 0xFFFEFFFF,
+	PERF_PMU_TYPE_PE_END      = 0xFFFDFFFF,
+	PERF_PMU_TYPE_DRM_START   = 0xFFFE0000,
+	PERF_PMU_TYPE_DRM_END     = 0xFFFEFFFF,
 	PERF_PMU_TYPE_HWMON_START = 0xFFFF0000,
 	PERF_PMU_TYPE_HWMON_END   = 0xFFFFFFFD,
 	PERF_PMU_TYPE_TOOL = 0xFFFFFFFE,
diff --git a/tools/perf/util/pmus.c b/tools/perf/util/pmus.c
index 93d5db1c89b4..f7782d6c396a 100644
--- a/tools/perf/util/pmus.c
+++ b/tools/perf/util/pmus.c
@@ -12,6 +12,7 @@
 #include <unistd.h>
 #include "cpumap.h"
 #include "debug.h"
+#include "drm_pmu.h"
 #include "evsel.h"
 #include "pmus.h"
 #include "pmu.h"
@@ -43,16 +44,19 @@ enum perf_tool_pmu_type {
 	PERF_TOOL_PMU_TYPE_PE_OTHER,
 	PERF_TOOL_PMU_TYPE_TOOL,
 	PERF_TOOL_PMU_TYPE_HWMON,
+	PERF_TOOL_PMU_TYPE_DRM,
 
 #define PERF_TOOL_PMU_TYPE_PE_CORE_MASK (1 << PERF_TOOL_PMU_TYPE_PE_CORE)
 #define PERF_TOOL_PMU_TYPE_PE_OTHER_MASK (1 << PERF_TOOL_PMU_TYPE_PE_OTHER)
 #define PERF_TOOL_PMU_TYPE_TOOL_MASK (1 << PERF_TOOL_PMU_TYPE_TOOL)
 #define PERF_TOOL_PMU_TYPE_HWMON_MASK (1 << PERF_TOOL_PMU_TYPE_HWMON)
+#define PERF_TOOL_PMU_TYPE_DRM_MASK (1 << PERF_TOOL_PMU_TYPE_DRM)
 
 #define PERF_TOOL_PMU_TYPE_ALL_MASK (PERF_TOOL_PMU_TYPE_PE_CORE_MASK |	\
 					PERF_TOOL_PMU_TYPE_PE_OTHER_MASK | \
 					PERF_TOOL_PMU_TYPE_TOOL_MASK |	\
-					PERF_TOOL_PMU_TYPE_HWMON_MASK)
+					PERF_TOOL_PMU_TYPE_HWMON_MASK | \
+					PERF_TOOL_PMU_TYPE_DRM_MASK)
 };
 static unsigned int read_pmu_types;
 
@@ -173,6 +177,8 @@ struct perf_pmu *perf_pmus__find(const char *name)
 	/* Looking up an individual perf event PMU failed, check if a tool PMU should be read. */
 	if (!strncmp(name, "hwmon_", 6))
 		to_read_pmus |= PERF_TOOL_PMU_TYPE_HWMON_MASK;
+	else if (!strncmp(name, "drm_", 4))
+		to_read_pmus |= PERF_TOOL_PMU_TYPE_DRM_MASK;
 	else if (!strcmp(name, "tool"))
 		to_read_pmus |= PERF_TOOL_PMU_TYPE_TOOL_MASK;
 
@@ -273,6 +279,10 @@ static void pmu_read_sysfs(unsigned int to_read_types)
 	    (read_pmu_types & PERF_TOOL_PMU_TYPE_HWMON_MASK) == 0)
 		perf_pmus__read_hwmon_pmus(&other_pmus);
 
+	if ((to_read_types & PERF_TOOL_PMU_TYPE_DRM_MASK) != 0 &&
+	    (read_pmu_types & PERF_TOOL_PMU_TYPE_DRM_MASK) == 0)
+		perf_pmus__read_drm_pmus(&other_pmus);
+
 	list_sort(NULL, &other_pmus, pmus_cmp);
 
 	read_pmu_types |= to_read_types;
@@ -305,6 +315,8 @@ struct perf_pmu *perf_pmus__find_by_type(unsigned int type)
 	if (type >= PERF_PMU_TYPE_PE_START && type <= PERF_PMU_TYPE_PE_END) {
 		to_read_pmus = PERF_TOOL_PMU_TYPE_PE_CORE_MASK |
 			PERF_TOOL_PMU_TYPE_PE_OTHER_MASK;
+	} else if (type >= PERF_PMU_TYPE_DRM_START && type <= PERF_PMU_TYPE_DRM_END) {
+		to_read_pmus = PERF_TOOL_PMU_TYPE_DRM_MASK;
 	} else if (type >= PERF_PMU_TYPE_HWMON_START && type <= PERF_PMU_TYPE_HWMON_END) {
 		to_read_pmus = PERF_TOOL_PMU_TYPE_HWMON_MASK;
 	} else {
@@ -371,6 +383,10 @@ struct perf_pmu *perf_pmus__scan_for_event(struct perf_pmu *pmu, const char *eve
 		if (parse_hwmon_filename(event, &type, &number, /*item=*/NULL, /*alarm=*/NULL))
 			to_read_pmus |= PERF_TOOL_PMU_TYPE_HWMON_MASK;
 
+		/* Could the event be a DRM event? */
+		if (strlen(event) > 4 && strncmp("drm-", event, 4) == 0)
+			to_read_pmus |= PERF_TOOL_PMU_TYPE_DRM_MASK;
+
 		pmu_read_sysfs(to_read_pmus);
 		pmu = list_prepare_entry(pmu, &core_pmus, list);
 	}
@@ -403,11 +419,17 @@ struct perf_pmu *perf_pmus__scan_matching_wildcard(struct perf_pmu *pmu, const c
 		 * Hwmon PMUs have an alias from a sysfs name like hwmon0,
 		 * hwmon1, etc. or have a name of hwmon_<name>. They therefore
 		 * can only have a wildcard match if the wildcard begins with
-		 * "hwmon".
+		 * "hwmon". Similarly drm PMUs must start "drm_", avoid reading
+		 * such events unless the PMU could match.
 		 */
-		if (strisglob(wildcard) ||
-		    (strlen(wildcard) >= 5 && strncmp("hwmon", wildcard, 5) == 0))
+		if (strisglob(wildcard)) {
+			to_read_pmus |= PERF_TOOL_PMU_TYPE_HWMON_MASK |
+				PERF_TOOL_PMU_TYPE_DRM_MASK;
+		} else if (strlen(wildcard) >= 4 && strncmp("drm_", wildcard, 4) == 0) {
+			to_read_pmus |= PERF_TOOL_PMU_TYPE_DRM_MASK;
+		} else if (strlen(wildcard) >= 5 && strncmp("hwmon", wildcard, 5) == 0) {
 			to_read_pmus |= PERF_TOOL_PMU_TYPE_HWMON_MASK;
+		}
 
 		pmu_read_sysfs(to_read_pmus);
 		pmu = list_prepare_entry(pmu, &core_pmus, list);
