From: Arun R Murthy <arun.r.murthy@intel.com>
To: dri-devel@lists.freedesktop.org
Cc: intel-gfx@lists.freedesktop.org, intel-xe@lists.freedesktop.org,
"Jani Nikula" <jani.nikula@linux.intel.com>,
"Ville Syrjälä" <ville.syrjala@linux.intel.com>,
ankit.k.nautiyal@intel.com, adarsh.g.m@intel.com,
"Arun R Murthy" <arun.r.murthy@intel.com>
Subject: [PATCH libdrm 1/2] xf86drmMode: add drmModeGetCrtcList() to enumerate available CRTCs
Date: Mon, 21 Sep 2026 18:37:45 +0530 [thread overview]
Message-ID: <20260921130747.2557722-2-arun.r.murthy@intel.com> (raw)
In-Reply-To: <20260921130747.2557722-1-arun.r.murthy@intel.com>
drmModeGetResources() tells the system capability and not runtime
availability of the hardware resource.
DRM_IOCTL_MODE_GETCRTC only tells userspace which CRTCs are enabled
through KMS, not which are active in hardware. A pipe can be actively
scanning out without being enabled through the KMS state, so relying on
GETCRTC alone would report such a CRTC as free and assignable when it is
not.
An active pipe keeps its vblank running, so reading the CRTC's vblank
sequence reveals the active pipes: an active pipe's counter keeps
advancing while an inactive pipe's does not. For every CRTC that GETCRTC
reports as free, sample the vblank via drmCrtcGetSequence() across a
single short wait and, if it advanced, flag the entry as driver_active
and exclude it from count_available.
The list is allocated by drmModeGetCrtcList() and released with the new
drmModeFreeCrtcList(). Both symbols are exported via core-symbols.txt.
The helper is a thin wrapper over the standard KMS uAPI, so it works
identically on i915, xe and any other KMS driver.
Assisted-by: Claude:claude-opus-4.7
Signed-off-by: Arun R Murthy <arun.r.murthy@intel.com>
---
core-symbols.txt | 2 +
xf86drmMode.c | 123 +++++++++++++++++++++++++++++++++++++++++++++++
xf86drmMode.h | 38 +++++++++++++++
3 files changed, 163 insertions(+)
diff --git a/core-symbols.txt b/core-symbols.txt
index 766c342d..a14add40 100644
--- a/core-symbols.txt
+++ b/core-symbols.txt
@@ -119,6 +119,7 @@ drmModeDirtyFB
drmModeFormatModifierBlobIterNext
drmModeFreeConnector
drmModeFreeCrtc
+drmModeFreeCrtcList
drmModeFreeEncoder
drmModeFreeFB
drmModeFreeFB2
@@ -133,6 +134,7 @@ drmModeGetConnector
drmModeGetConnectorCurrent
drmModeGetConnectorTypeName
drmModeGetCrtc
+drmModeGetCrtcList
drmModeGetEncoder
drmModeGetFB
drmModeGetFB2
diff --git a/xf86drmMode.c b/xf86drmMode.c
index 5b5d7053..224212bb 100644
--- a/xf86drmMode.c
+++ b/xf86drmMode.c
@@ -35,6 +35,7 @@
#include <dirent.h>
#include <unistd.h>
#include <errno.h>
+#include <time.h>
#define memclear(s) memset(&s, 0, sizeof(s))
@@ -383,6 +384,128 @@ drm_public drmModeCrtcPtr drmModeGetCrtc(int fd, uint32_t crtcId)
return r;
}
+drm_public drmModeCrtcListPtr drmModeGetCrtcList(int fd)
+{
+ drmModeResPtr res;
+ drmModeCrtcListPtr list;
+ uint64_t *seq0 = NULL;
+ char *probe = NULL;
+ int need_probe = 0;
+ int i;
+
+ res = drmModeGetResources(fd);
+ if (!res)
+ return NULL;
+
+ list = drmMalloc(sizeof(*list));
+ if (!list)
+ goto err_res;
+
+ list->count = res->count_crtcs;
+ list->count_available = 0;
+
+ if (res->count_crtcs) {
+ list->crtcs = drmMalloc(res->count_crtcs * sizeof(*list->crtcs));
+ if (!list->crtcs)
+ goto err_list;
+ seq0 = drmMalloc(res->count_crtcs * sizeof(*seq0));
+ probe = drmMalloc(res->count_crtcs * sizeof(*probe));
+ if (!seq0 || !probe)
+ goto err_probe;
+ }
+
+ for (i = 0; i < res->count_crtcs; i++) {
+ drmModeCrtcInfoPtr info = &list->crtcs[i];
+ drmModeCrtcPtr crtc = drmModeGetCrtc(fd, res->crtcs[i]);
+
+ info->crtc_id = res->crtcs[i];
+ info->index = i;
+
+ if (!crtc) {
+ /* Unreadable CRTC: report as busy so callers don't grab it. */
+ info->in_use = 1;
+ continue;
+ }
+
+ info->buffer_id = crtc->buffer_id;
+ if (crtc->mode_valid) {
+ info->in_use = 1;
+ info->width = crtc->width;
+ info->height = crtc->height;
+ info->mode = crtc->mode;
+ } else {
+ /*
+ * GETCRTC only tells us which pipes are enabled through
+ * KMS, not which are active in hardware. A pipe can be
+ * active (scanning out) without being enabled here, and
+ * an active pipe keeps its vblank counter advancing.
+ * Snapshot the vblank sequence now and re-check it below
+ * to find such active pipes.
+ */
+ uint64_t ns;
+
+ if (!drmCrtcGetSequence(fd, info->crtc_id, &seq0[i], &ns)) {
+ probe[i] = 1;
+ need_probe = 1;
+ }
+ }
+
+ drmModeFreeCrtc(crtc);
+ }
+
+ /*
+ * One shared wait covers every candidate: an active pipe keeps
+ * advancing its vblank counter, an inactive pipe does not. 50ms
+ * crosses at least one vblank down to ~24Hz.
+ */
+ if (need_probe) {
+ struct timespec ts = { .tv_sec = 0, .tv_nsec = 50000000 };
+
+ nanosleep(&ts, NULL);
+
+ for (i = 0; i < res->count_crtcs; i++) {
+ uint64_t seq1, ns;
+
+ if (!probe[i])
+ continue;
+ if (drmCrtcGetSequence(fd, list->crtcs[i].crtc_id,
+ &seq1, &ns))
+ continue;
+ if (seq1 != seq0[i])
+ list->crtcs[i].driver_active = 1;
+ }
+ }
+
+ for (i = 0; i < res->count_crtcs; i++) {
+ if (!list->crtcs[i].in_use && !list->crtcs[i].driver_active)
+ list->count_available++;
+ }
+
+ drmFree(probe);
+ drmFree(seq0);
+ drmModeFreeResources(res);
+ return list;
+
+err_probe:
+ drmFree(probe);
+ drmFree(seq0);
+ drmFree(list->crtcs);
+err_list:
+ drmFree(list);
+err_res:
+ drmModeFreeResources(res);
+ return NULL;
+}
+
+drm_public void drmModeFreeCrtcList(drmModeCrtcListPtr ptr)
+{
+ if (!ptr)
+ return;
+
+ drmFree(ptr->crtcs);
+ drmFree(ptr);
+}
+
drm_public int drmModeSetCrtc(int fd, uint32_t crtcId, uint32_t bufferId,
uint32_t x, uint32_t y, uint32_t *connectors, int count,
drmModeModeInfoPtr mode)
diff --git a/xf86drmMode.h b/xf86drmMode.h
index 2a7156db..c634333b 100644
--- a/xf86drmMode.h
+++ b/xf86drmMode.h
@@ -147,6 +147,30 @@ typedef struct _drmModeCrtc {
} drmModeCrtc, *drmModeCrtcPtr;
+/**
+ * Per-CRTC availability information, produced by drmModeGetCrtcList().
+ */
+typedef struct _drmModeCrtcInfo {
+ uint32_t crtc_id; /**< KMS object id of the CRTC */
+ int index; /**< Index in drmModeRes.crtcs[]; usable for vblank high-crtc bits */
+ int in_use; /**< Non-zero if userspace has a mode set on this CRTC */
+ int driver_active; /**< Non-zero if the CRTC is active in hardware (its
+ * vblank is running) even though it is not enabled
+ * through KMS. GETCRTC only reports enabled pipes;
+ * reading the vblank counter reveals active pipes,
+ * which must not be treated as free. */
+ uint32_t buffer_id; /**< FB currently scanned out, 0 if none */
+ uint32_t width, height; /**< Active mode resolution, 0 when not in use */
+ drmModeModeInfo mode; /**< Active mode; valid only when in_use is set */
+} drmModeCrtcInfo, *drmModeCrtcInfoPtr;
+
+typedef struct _drmModeCrtcList {
+ int count; /**< Total number of CRTCs on the device */
+ int count_available; /**< CRTCs that are free and assignable (neither
+ * in_use nor driver_active) */
+ drmModeCrtcInfoPtr crtcs; /**< Array of 'count' CRTC entries */
+} drmModeCrtcList, *drmModeCrtcListPtr;
+
typedef struct _drmModeEncoder {
uint32_t encoder_id;
uint32_t encoder_type;
@@ -317,6 +341,20 @@ extern int drmModeDirtyFB(int fd, uint32_t bufferId,
*/
extern drmModeCrtcPtr drmModeGetCrtc(int fd, uint32_t crtcId);
+/**
+ * Enumerate every CRTC on the device and report which ones are available.
+ *
+ * Convenience wrapper around drmModeGetResources() + drmModeGetCrtc() intended
+ * for compositors that need to discover free CRTCs. The returned list must be
+ * released with drmModeFreeCrtcList(). Returns NULL on error.
+ */
+extern drmModeCrtcListPtr drmModeGetCrtcList(int fd);
+
+/**
+ * Free a list previously returned by drmModeGetCrtcList().
+ */
+extern void drmModeFreeCrtcList(drmModeCrtcListPtr ptr);
+
/**
* Set the mode on a crtc crtcId with the given mode modeId.
*/
--
2.25.1
next prev parent reply other threads:[~2026-09-21 13:09 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-21 13:07 [PATCH libdrm 0/2] Get active crtc list Arun R Murthy
2026-09-21 13:07 ` Arun R Murthy [this message]
2026-09-21 13:07 ` [PATCH libdrm 2/2] tests/crtclist: add utility to inspect CRTC usage Arun R Murthy
2026-09-21 14:07 ` Murthy, Arun R
2026-09-22 14:16 ` Michel Dänzer
2026-09-23 4:22 ` Murthy, Arun R
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260921130747.2557722-2-arun.r.murthy@intel.com \
--to=arun.r.murthy@intel.com \
--cc=adarsh.g.m@intel.com \
--cc=ankit.k.nautiyal@intel.com \
--cc=dri-devel@lists.freedesktop.org \
--cc=intel-gfx@lists.freedesktop.org \
--cc=intel-xe@lists.freedesktop.org \
--cc=jani.nikula@linux.intel.com \
--cc=ville.syrjala@linux.intel.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox