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 <[email protected]>
---
 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

Reply via email to