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
